mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeSeries: Support multiple timezones in x axis (#52424)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
17ea5f4f3e
commit
2fa10dc903
@ -4532,7 +4532,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "25"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "26"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "27"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "28"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "28"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "29"]
|
||||
],
|
||||
"public/app/features/dashboard/state/TimeModel.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
@ -173,6 +173,11 @@ OptionsWithLegend: {
|
||||
legend: VizLegendOptions
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// TODO docs
|
||||
OptionsWithTimezones: {
|
||||
timezones?: [...string]
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// TODO docs
|
||||
OptionsWithTextFormatting: {
|
||||
text?: VizTextDisplayOptions
|
||||
|
@ -208,6 +208,14 @@ export interface OptionsWithLegend {
|
||||
legend: VizLegendOptions;
|
||||
}
|
||||
|
||||
export interface OptionsWithTimezones {
|
||||
timezones?: string[];
|
||||
}
|
||||
|
||||
export const defaultOptionsWithTimezones: Partial<OptionsWithTimezones> = {
|
||||
timezones: [],
|
||||
};
|
||||
|
||||
export interface OptionsWithTextFormatting {
|
||||
text?: VizTextDisplayOptions;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export interface GraphNGProps extends Themeable2 {
|
||||
width: number;
|
||||
height: number;
|
||||
timeRange: TimeRange;
|
||||
timeZone: TimeZone;
|
||||
timeZones: TimeZone[] | TimeZone;
|
||||
legend: VizLegendOptions;
|
||||
fields?: XYFieldMatchers; // default will assume timeseries data
|
||||
renderers?: Renderers;
|
||||
@ -216,17 +216,17 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: GraphNGProps) {
|
||||
const { frames, structureRev, timeZone, propsToDiff } = this.props;
|
||||
const { frames, structureRev, timeZones, propsToDiff } = this.props;
|
||||
|
||||
const propsChanged = !sameProps(prevProps, this.props, propsToDiff);
|
||||
|
||||
if (frames !== prevProps.frames || propsChanged || timeZone !== prevProps.timeZone) {
|
||||
if (frames !== prevProps.frames || propsChanged || timeZones !== prevProps.timeZones) {
|
||||
let newState = this.prepState(this.props, false);
|
||||
|
||||
if (newState) {
|
||||
const shouldReconfig =
|
||||
this.state.config === undefined ||
|
||||
timeZone !== prevProps.timeZone ||
|
||||
timeZones !== prevProps.timeZones ||
|
||||
structureRev !== prevProps.structureRev ||
|
||||
!structureRev ||
|
||||
propsChanged;
|
||||
|
@ -214,7 +214,7 @@ describe('GraphNG utils', () => {
|
||||
const result = preparePlotConfigBuilder({
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
timeZones: [DefaultTimeZone],
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
sync: () => DashboardCursorSync.Tooltip,
|
||||
|
@ -22,12 +22,12 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
|
||||
|
||||
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
|
||||
const { eventBus, sync } = this.context as PanelContext;
|
||||
const { theme, timeZone, renderers, tweakAxis, tweakScale } = this.props;
|
||||
const { theme, timeZones, renderers, tweakAxis, tweakScale } = this.props;
|
||||
|
||||
return preparePlotConfigBuilder({
|
||||
frame: alignedFrame,
|
||||
theme,
|
||||
timeZone,
|
||||
timeZones: Array.isArray(timeZones) ? timeZones : [timeZones],
|
||||
getTimeRange,
|
||||
eventBus,
|
||||
sync,
|
||||
|
@ -48,7 +48,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
}> = ({
|
||||
frame,
|
||||
theme,
|
||||
timeZone,
|
||||
timeZones,
|
||||
getTimeRange,
|
||||
eventBus,
|
||||
sync,
|
||||
@ -57,7 +57,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
tweakScale = (opts) => opts,
|
||||
tweakAxis = (opts) => opts,
|
||||
}) => {
|
||||
const builder = new UPlotConfigBuilder(timeZone);
|
||||
const builder = new UPlotConfigBuilder(timeZones[0]);
|
||||
|
||||
let alignedFrame: DataFrame;
|
||||
|
||||
@ -97,16 +97,51 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
},
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: xScaleKey,
|
||||
isTime: true,
|
||||
placement: xFieldAxisPlacement,
|
||||
show: xFieldAxisShow,
|
||||
label: xField.config.custom?.axisLabel,
|
||||
timeZone,
|
||||
theme,
|
||||
grid: { show: xField.config.custom?.axisGridShow },
|
||||
});
|
||||
// filters first 2 ticks to make space for timezone labels
|
||||
const filterTicks: uPlot.Axis.Filter | undefined =
|
||||
timeZones.length > 1
|
||||
? (u, splits) => {
|
||||
return splits.map((v, i) => (i < 2 ? null : v));
|
||||
}
|
||||
: undefined;
|
||||
|
||||
for (let i = 0; i < timeZones.length; i++) {
|
||||
const timeZone = timeZones[i];
|
||||
builder.addAxis({
|
||||
scaleKey: xScaleKey,
|
||||
isTime: true,
|
||||
placement: xFieldAxisPlacement,
|
||||
show: xFieldAxisShow,
|
||||
label: xField.config.custom?.axisLabel,
|
||||
timeZone,
|
||||
theme,
|
||||
grid: { show: i === 0 && xField.config.custom?.axisGridShow },
|
||||
filter: filterTicks,
|
||||
});
|
||||
}
|
||||
|
||||
// render timezone labels
|
||||
if (timeZones.length > 1) {
|
||||
builder.addHook('drawAxes', (u: uPlot) => {
|
||||
u.ctx.save();
|
||||
|
||||
u.ctx.fillStyle = theme.colors.text.primary;
|
||||
u.ctx.textAlign = 'left';
|
||||
u.ctx.textBaseline = 'bottom';
|
||||
|
||||
let i = 0;
|
||||
u.axes.forEach((a) => {
|
||||
if (a.side === 2) {
|
||||
//@ts-ignore
|
||||
let cssBaseline: number = a._pos + a._size;
|
||||
u.ctx.fillText(timeZones[i], u.bbox.left, cssBaseline * uPlot.pxRatio);
|
||||
i++;
|
||||
}
|
||||
});
|
||||
|
||||
u.ctx.restore();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Not time!
|
||||
if (xField.config.unit) {
|
||||
|
@ -151,7 +151,7 @@ export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> {
|
||||
ticks
|
||||
),
|
||||
splits,
|
||||
values: values,
|
||||
values,
|
||||
space:
|
||||
space ??
|
||||
((self, axisIdx, scaleMin, scaleMax, plotDim) => {
|
||||
@ -227,7 +227,7 @@ export function formatTime(
|
||||
format = systemDateFormats.interval.month;
|
||||
}
|
||||
|
||||
return splits.map((v) => dateTimeFormat(v, { format, timeZone }));
|
||||
return splits.map((v) => (v == null ? '' : dateTimeFormat(v, { format, timeZone })));
|
||||
}
|
||||
|
||||
export function getUPlotSideFromAxis(axis: AxisPlacement) {
|
||||
|
@ -87,8 +87,14 @@ export class UPlotConfigBuilder {
|
||||
addAxis(props: AxisProps) {
|
||||
props.placement = props.placement ?? AxisPlacement.Auto;
|
||||
props.grid = props.grid ?? {};
|
||||
if (this.axes[props.scaleKey]) {
|
||||
this.axes[props.scaleKey].merge(props);
|
||||
let scaleKey = props.scaleKey;
|
||||
|
||||
if (scaleKey === 'x') {
|
||||
scaleKey += props.timeZone ?? '';
|
||||
}
|
||||
|
||||
if (this.axes[scaleKey]) {
|
||||
this.axes[scaleKey].merge(props);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -106,7 +112,7 @@ export class UPlotConfigBuilder {
|
||||
props.size = 0;
|
||||
}
|
||||
|
||||
this.axes[props.scaleKey] = new UPlotAxisBuilder(props);
|
||||
this.axes[scaleKey] = new UPlotAxisBuilder(props);
|
||||
}
|
||||
|
||||
getAxisPlacement(scaleKey: string): AxisPlacement {
|
||||
@ -285,7 +291,7 @@ export type Renderers = Array<{
|
||||
type UPlotConfigPrepOpts<T extends Record<string, any> = {}> = {
|
||||
frame: DataFrame;
|
||||
theme: GrafanaTheme2;
|
||||
timeZone: TimeZone;
|
||||
timeZones: TimeZone[];
|
||||
getTimeRange: () => TimeRange;
|
||||
eventBus: EventBus;
|
||||
allFrames: DataFrame[];
|
||||
|
@ -236,7 +236,7 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
|
||||
frame: alignedFrame,
|
||||
getTimeRange,
|
||||
theme,
|
||||
timeZone,
|
||||
timeZones: [timeZone],
|
||||
eventBus,
|
||||
orientation,
|
||||
barWidth,
|
||||
@ -266,7 +266,7 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
|
||||
preparePlotFrame={(f) => f[0]} // already processed in by the panel above!
|
||||
renderLegend={renderLegend}
|
||||
legend={options.legend}
|
||||
timeZone={timeZone}
|
||||
timeZones={timeZone}
|
||||
timeRange={{ from: 1, to: 1 } as unknown as TimeRange} // HACK
|
||||
structureRev={structureRev}
|
||||
width={width}
|
||||
|
@ -116,7 +116,7 @@ describe('BarChart utils', () => {
|
||||
orientation: v,
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
timeZones: [DefaultTimeZone],
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
allFrames: [frame],
|
||||
@ -131,7 +131,7 @@ describe('BarChart utils', () => {
|
||||
showValue: v,
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
timeZones: [DefaultTimeZone],
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
allFrames: [frame],
|
||||
@ -146,7 +146,7 @@ describe('BarChart utils', () => {
|
||||
stacking: v,
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
timeZones: [DefaultTimeZone],
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
allFrames: [frame],
|
||||
|
@ -233,7 +233,7 @@ export const CandlestickPanel: React.FC<CandlestickPanelProps> = ({
|
||||
frames={[info.frame]}
|
||||
structureRev={data.structureRev}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
timeZones={timeZone}
|
||||
width={width}
|
||||
height={height}
|
||||
legend={options.legend}
|
||||
|
@ -8,6 +8,7 @@ import { AnnotationEditorPlugin } from '../timeseries/plugins/AnnotationEditorPl
|
||||
import { AnnotationsPlugin } from '../timeseries/plugins/AnnotationsPlugin';
|
||||
import { ContextMenuPlugin } from '../timeseries/plugins/ContextMenuPlugin';
|
||||
import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin';
|
||||
import { getTimezones } from '../timeseries/utils';
|
||||
|
||||
import { StateTimelineTooltip } from './StateTimelineTooltip';
|
||||
import { TimelineChart } from './TimelineChart';
|
||||
@ -42,6 +43,8 @@ export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
[frames, options.legend, theme]
|
||||
);
|
||||
|
||||
const timezones = useMemo(() => getTimezones(options.timezones, timeZone), [options.timezones, timeZone]);
|
||||
|
||||
const renderCustomTooltip = useCallback(
|
||||
(alignedData: DataFrame, seriesIdx: number | null, datapointIdx: number | null) => {
|
||||
const data = frames ?? [];
|
||||
@ -104,7 +107,7 @@ export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
frames={frames}
|
||||
structureRev={data.structureRev}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
timeZones={timezones}
|
||||
width={width}
|
||||
height={height}
|
||||
legendItems={legendItems}
|
||||
|
@ -61,6 +61,9 @@ export class TimelineChart extends React.Component<TimelineProps> {
|
||||
allFrames: this.props.frames,
|
||||
...this.props,
|
||||
|
||||
// Ensure timezones is passed as an array
|
||||
timeZones: Array.isArray(this.props.timeZones) ? this.props.timeZones : [this.props.timeZones],
|
||||
|
||||
// When there is only one row, use the full space
|
||||
rowHeight: alignedFrame.fields.length > 2 ? this.props.rowHeight : 1,
|
||||
getValueColor: this.getValueColor,
|
||||
|
@ -32,6 +32,7 @@ Panel: thema.#Lineage & {
|
||||
mode?: TimelineMode
|
||||
ui.OptionsWithLegend
|
||||
ui.OptionsWithTooltip
|
||||
ui.OptionsWithTimezones
|
||||
showValue: ui.VisibilityMode | *"auto"
|
||||
rowHeight: number | *0.9
|
||||
colWidth?: number
|
||||
|
@ -1,10 +1,16 @@
|
||||
import { DashboardCursorSync } from '@grafana/data';
|
||||
import { HideableFieldConfig, OptionsWithLegend, OptionsWithTooltip, VisibilityMode } from '@grafana/schema';
|
||||
import {
|
||||
HideableFieldConfig,
|
||||
OptionsWithLegend,
|
||||
OptionsWithTimezones,
|
||||
OptionsWithTooltip,
|
||||
VisibilityMode,
|
||||
} from '@grafana/schema';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface TimelineOptions extends OptionsWithLegend, OptionsWithTooltip {
|
||||
export interface TimelineOptions extends OptionsWithLegend, OptionsWithTooltip, OptionsWithTimezones {
|
||||
mode: TimelineMode; // not in the saved model!
|
||||
|
||||
showValue: VisibilityMode;
|
||||
|
@ -56,7 +56,7 @@ export function mapMouseEventToMode(event: React.MouseEvent): SeriesVisibilityCh
|
||||
export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
frame,
|
||||
theme,
|
||||
timeZone,
|
||||
timeZones,
|
||||
getTimeRange,
|
||||
mode,
|
||||
eventBus,
|
||||
@ -68,7 +68,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
mergeValues,
|
||||
getValueColor,
|
||||
}) => {
|
||||
const builder = new UPlotConfigBuilder(timeZone);
|
||||
const builder = new UPlotConfigBuilder(timeZones[0]);
|
||||
|
||||
const xScaleUnit = 'time';
|
||||
const xScaleKey = 'x';
|
||||
@ -185,7 +185,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
isTime: true,
|
||||
splits: coreConfig.xSplits!,
|
||||
placement: AxisPlacement.Bottom,
|
||||
timeZone,
|
||||
timeZone: timeZones[0],
|
||||
theme,
|
||||
grid: { show: true },
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ import { TimelineChart } from '../state-timeline/TimelineChart';
|
||||
import { TimelineMode } from '../state-timeline/types';
|
||||
import { prepareTimelineFields, prepareTimelineLegendItems } from '../state-timeline/utils';
|
||||
import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin';
|
||||
import { getTimezones } from '../timeseries/utils';
|
||||
|
||||
import { StatusPanelOptions } from './types';
|
||||
|
||||
@ -36,6 +37,8 @@ export const StatusHistoryPanel: React.FC<TimelinePanelProps> = ({
|
||||
[frames, options.legend, theme]
|
||||
);
|
||||
|
||||
const timezones = useMemo(() => getTimezones(options.timezones, timeZone), [options.timezones, timeZone]);
|
||||
|
||||
if (!frames || warn) {
|
||||
return (
|
||||
<div className="panel-empty">
|
||||
@ -62,7 +65,7 @@ export const StatusHistoryPanel: React.FC<TimelinePanelProps> = ({
|
||||
frames={frames}
|
||||
structureRev={data.structureRev}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
timeZones={timezones}
|
||||
width={width}
|
||||
height={height}
|
||||
legendItems={legendItems}
|
||||
|
@ -28,6 +28,7 @@ Panel: thema.#Lineage & {
|
||||
PanelOptions: {
|
||||
ui.OptionsWithLegend
|
||||
ui.OptionsWithTooltip
|
||||
ui.OptionsWithTimezones
|
||||
showValue: ui.VisibilityMode
|
||||
rowHeight: number
|
||||
colWidth?: number
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { HideableFieldConfig, VisibilityMode, OptionsWithTooltip, OptionsWithLegend } from '@grafana/schema';
|
||||
import {
|
||||
HideableFieldConfig,
|
||||
VisibilityMode,
|
||||
OptionsWithTooltip,
|
||||
OptionsWithLegend,
|
||||
OptionsWithTimezones,
|
||||
} from '@grafana/schema';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface StatusPanelOptions extends OptionsWithTooltip, OptionsWithLegend {
|
||||
export interface StatusPanelOptions extends OptionsWithTooltip, OptionsWithLegend, OptionsWithTimezones {
|
||||
showValue: VisibilityMode;
|
||||
rowHeight: number;
|
||||
colWidth?: number;
|
||||
|
@ -14,7 +14,7 @@ import { ExemplarsPlugin } from './plugins/ExemplarsPlugin';
|
||||
import { OutsideRangePlugin } from './plugins/OutsideRangePlugin';
|
||||
import { ThresholdControlsPlugin } from './plugins/ThresholdControlsPlugin';
|
||||
import { TimeSeriesOptions } from './types';
|
||||
import { prepareGraphableFields } from './utils';
|
||||
import { getTimezones, prepareGraphableFields } from './utils';
|
||||
|
||||
interface TimeSeriesPanelProps extends PanelProps<TimeSeriesOptions> {}
|
||||
|
||||
@ -37,6 +37,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
};
|
||||
|
||||
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2, timeRange), [data, timeRange]);
|
||||
const timezones = useMemo(() => getTimezones(options.timezones, timeZone), [options.timezones, timeZone]);
|
||||
|
||||
if (!frames) {
|
||||
return (
|
||||
@ -57,7 +58,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
frames={frames}
|
||||
structureRev={data.structureRev}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
timeZones={timezones}
|
||||
width={width}
|
||||
height={height}
|
||||
legend={options.legend}
|
||||
|
70
public/app/plugins/panel/timeseries/TimezonesEditor.tsx
Normal file
70
public/app/plugins/panel/timeseries/TimezonesEditor.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, InternalTimeZones, StandardEditorProps } from '@grafana/data';
|
||||
import { OptionsWithTimezones } from '@grafana/schema';
|
||||
import { IconButton, TimeZonePicker, useStyles2 } from '@grafana/ui';
|
||||
|
||||
type Props = StandardEditorProps<string[], unknown, OptionsWithTimezones>;
|
||||
|
||||
export const TimezonesEditor = ({ value, onChange }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!value || value.length < 1) {
|
||||
value = [''];
|
||||
}
|
||||
|
||||
const addTimezone = () => {
|
||||
onChange([...value, InternalTimeZones.default]);
|
||||
};
|
||||
|
||||
const removeTimezone = (idx: number) => {
|
||||
const copy = value.slice();
|
||||
copy.splice(idx, 1);
|
||||
onChange(copy);
|
||||
};
|
||||
|
||||
const setTimezone = (idx: number, tz?: string) => {
|
||||
const copy = value.slice();
|
||||
copy[idx] = tz ?? InternalTimeZones.default;
|
||||
if (copy.length === 0 || (copy.length === 1 && copy[0] === '')) {
|
||||
onChange(undefined);
|
||||
} else {
|
||||
onChange(copy);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{value.map((tz, idx) => (
|
||||
<div className={styles.wrapper} key={`${idx}.${tz}`}>
|
||||
<span className={styles.first}>
|
||||
<TimeZonePicker
|
||||
onChange={(v) => setTimezone(idx, v)}
|
||||
includeInternal={true}
|
||||
value={tz ?? InternalTimeZones.default}
|
||||
/>
|
||||
</span>
|
||||
{idx === value.length - 1 ? (
|
||||
<IconButton ariaLabel="Add timezone" name="plus" onClick={addTimezone} />
|
||||
) : (
|
||||
<IconButton ariaLabel="Remove timezone" name="times" onClick={() => removeTimezone(idx)} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: rows;
|
||||
align-items: center;
|
||||
`,
|
||||
first: css`
|
||||
margin-right: 8px;
|
||||
flex-grow: 2;
|
||||
`,
|
||||
});
|
@ -3,6 +3,7 @@ import { GraphFieldConfig } from '@grafana/schema';
|
||||
import { commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
import { TimeSeriesPanel } from './TimeSeriesPanel';
|
||||
import { TimezonesEditor } from './TimezonesEditor';
|
||||
import { defaultGraphConfig, getGraphFieldConfig } from './config';
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
import { TimeSeriesSuggestionsSupplier } from './suggestions';
|
||||
@ -14,6 +15,15 @@ export const plugin = new PanelPlugin<TimeSeriesOptions, GraphFieldConfig>(TimeS
|
||||
.setPanelOptions((builder) => {
|
||||
commonOptionsBuilder.addTooltipOptions(builder);
|
||||
commonOptionsBuilder.addLegendOptions(builder);
|
||||
|
||||
builder.addCustomEditor({
|
||||
id: 'timezones',
|
||||
name: 'Timezone',
|
||||
path: 'timezones',
|
||||
category: ['Axis'],
|
||||
editor: TimezonesEditor,
|
||||
defaultValue: undefined,
|
||||
});
|
||||
})
|
||||
.setSuggestionsSupplier(new TimeSeriesSuggestionsSupplier())
|
||||
.setDataSupport({ annotations: true, alertStates: true });
|
||||
|
@ -1,3 +1,3 @@
|
||||
import { OptionsWithLegend, OptionsWithTooltip } from '@grafana/schema';
|
||||
import { OptionsWithLegend, OptionsWithTimezones, OptionsWithTooltip } from '@grafana/schema';
|
||||
|
||||
export interface TimeSeriesOptions extends OptionsWithLegend, OptionsWithTooltip {}
|
||||
export interface TimeSeriesOptions extends OptionsWithLegend, OptionsWithTooltip, OptionsWithTimezones {}
|
||||
|
@ -116,3 +116,10 @@ export function prepareGraphableFields(
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getTimezones(timezones: string[] | undefined, defaultTimezone: string): string[] {
|
||||
if (!timezones || !timezones.length) {
|
||||
return [defaultTimezone];
|
||||
}
|
||||
return timezones.map((v) => (v?.length ? v : defaultTimezone));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user