mirror of https://github.com/grafana/grafana
Timeline: split "periodic" mode into its own panel (#34171)
parent
c630393cf4
commit
de5cd4a7d3
@ -0,0 +1,53 @@ |
||||
import React, { useMemo } from 'react'; |
||||
import { PanelProps } from '@grafana/data'; |
||||
import { useTheme2, ZoomPlugin } from '@grafana/ui'; |
||||
import { TimelineMode, TimelineOptions } from './types'; |
||||
import { TimelineChart } from './TimelineChart'; |
||||
import { prepareTimelineFields } from './utils'; |
||||
|
||||
interface TimelinePanelProps extends PanelProps<TimelineOptions> {} |
||||
|
||||
/** |
||||
* @alpha |
||||
*/ |
||||
export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({ |
||||
data, |
||||
timeRange, |
||||
timeZone, |
||||
options, |
||||
width, |
||||
height, |
||||
onChangeTimeRange, |
||||
}) => { |
||||
const theme = useTheme2(); |
||||
|
||||
const { frames, warn } = useMemo(() => prepareTimelineFields(data?.series, options.mergeValues ?? true), [ |
||||
data, |
||||
options.mergeValues, |
||||
]); |
||||
|
||||
if (!frames || warn) { |
||||
return ( |
||||
<div className="panel-empty"> |
||||
<p>{warn ?? 'No data found in response'}</p> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<TimelineChart |
||||
theme={theme} |
||||
frames={frames} |
||||
structureRev={data.structureRev} |
||||
timeRange={timeRange} |
||||
timeZone={timeZone} |
||||
width={width} |
||||
height={height} |
||||
{...options} |
||||
// hardcoded
|
||||
mode={TimelineMode.Changes} |
||||
> |
||||
{(config) => <ZoomPlugin config={config} onZoom={onChangeTimeRange} />} |
||||
</TimelineChart> |
||||
); |
||||
}; |
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@ -1,7 +1,7 @@ |
||||
{ |
||||
"type": "panel", |
||||
"name": "Timeline", |
||||
"id": "timeline", |
||||
"name": "State timeline", |
||||
"id": "state-timeline", |
||||
|
||||
"state": "alpha", |
||||
|
||||
@ -0,0 +1,60 @@ |
||||
import { FieldType, toDataFrame } from '@grafana/data'; |
||||
import { prepareTimelineFields } from './utils'; |
||||
|
||||
describe('prepare timeline graph', () => { |
||||
it('errors with no time fields', () => { |
||||
const frames = [ |
||||
toDataFrame({ |
||||
fields: [ |
||||
{ name: 'a', values: [1, 2, 3] }, |
||||
{ name: 'b', values: ['a', 'b', 'c'] }, |
||||
], |
||||
}), |
||||
]; |
||||
const info = prepareTimelineFields(frames, true); |
||||
expect(info.warn).toEqual('Data does not have a time field'); |
||||
}); |
||||
|
||||
it('requires a number, string, or boolean value', () => { |
||||
const frames = [ |
||||
toDataFrame({ |
||||
fields: [ |
||||
{ name: 'a', type: FieldType.time, values: [1, 2, 3] }, |
||||
{ name: 'b', type: FieldType.other, values: [{}, {}, {}] }, |
||||
], |
||||
}), |
||||
]; |
||||
const info = prepareTimelineFields(frames, true); |
||||
expect(info.warn).toEqual('No graphable fields'); |
||||
}); |
||||
|
||||
it('will merge duplicate values', () => { |
||||
const frames = [ |
||||
toDataFrame({ |
||||
fields: [ |
||||
{ name: 'a', type: FieldType.time, values: [1, 2, 3, 4, 5, 6, 7] }, |
||||
{ name: 'b', values: [1, 1, undefined, 1, 2, 2, null, 2, 3] }, |
||||
], |
||||
}), |
||||
]; |
||||
const info = prepareTimelineFields(frames, true); |
||||
expect(info.warn).toBeUndefined(); |
||||
|
||||
const out = info.frames![0]; |
||||
|
||||
const field = out.fields.find((f) => f.name === 'b'); |
||||
expect(field?.values.toArray()).toMatchInlineSnapshot(` |
||||
Array [ |
||||
1, |
||||
undefined, |
||||
undefined, |
||||
undefined, |
||||
2, |
||||
undefined, |
||||
null, |
||||
2, |
||||
3, |
||||
] |
||||
`);
|
||||
}); |
||||
}); |
||||
|
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,78 @@ |
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data'; |
||||
import { StatusGridPanel } from './StatusGridPanel'; |
||||
import { StatusPanelOptions, StatusFieldConfig, defaultStatusFieldConfig } from './types'; |
||||
import { BarValueVisibility } from '@grafana/ui'; |
||||
|
||||
export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(StatusGridPanel) |
||||
.useFieldConfig({ |
||||
standardOptions: { |
||||
[FieldConfigProperty.Color]: { |
||||
settings: { |
||||
byValueSupport: true, |
||||
}, |
||||
defaultValue: { |
||||
mode: FieldColorModeId.PaletteClassic, |
||||
}, |
||||
}, |
||||
}, |
||||
useCustomConfig: (builder) => { |
||||
builder |
||||
.addSliderInput({ |
||||
path: 'lineWidth', |
||||
name: 'Line width', |
||||
defaultValue: defaultStatusFieldConfig.lineWidth, |
||||
settings: { |
||||
min: 0, |
||||
max: 10, |
||||
step: 1, |
||||
}, |
||||
}) |
||||
.addSliderInput({ |
||||
path: 'fillOpacity', |
||||
name: 'Fill opacity', |
||||
defaultValue: defaultStatusFieldConfig.fillOpacity, |
||||
settings: { |
||||
min: 0, |
||||
max: 100, |
||||
step: 1, |
||||
}, |
||||
}); |
||||
}, |
||||
}) |
||||
.setPanelOptions((builder) => { |
||||
builder |
||||
.addRadio({ |
||||
path: 'showValue', |
||||
name: 'Show values', |
||||
settings: { |
||||
options: [ |
||||
//{ value: BarValueVisibility.Auto, label: 'Auto' },
|
||||
{ value: BarValueVisibility.Always, label: 'Always' }, |
||||
{ value: BarValueVisibility.Never, label: 'Never' }, |
||||
], |
||||
}, |
||||
defaultValue: BarValueVisibility.Always, |
||||
}) |
||||
.addSliderInput({ |
||||
path: 'rowHeight', |
||||
name: 'Row height', |
||||
defaultValue: 0.9, |
||||
settings: { |
||||
min: 0, |
||||
max: 1, |
||||
step: 0.01, |
||||
}, |
||||
}) |
||||
.addSliderInput({ |
||||
path: 'colWidth', |
||||
name: 'Column width', |
||||
defaultValue: 0.9, |
||||
settings: { |
||||
min: 0, |
||||
max: 1, |
||||
step: 0.01, |
||||
}, |
||||
}); |
||||
|
||||
//addLegendOptions(builder);
|
||||
}); |
||||
@ -0,0 +1,19 @@ |
||||
{ |
||||
"type": "panel", |
||||
"name": "Status grid", |
||||
"id": "status-grid", |
||||
|
||||
"state": "alpha", |
||||
|
||||
"info": { |
||||
"description": "System status map", |
||||
"author": { |
||||
"name": "Grafana Labs", |
||||
"url": "https://grafana.com" |
||||
}, |
||||
"logos": { |
||||
"small": "img/status.svg", |
||||
"large": "img/status.svg" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
import { VizLegendOptions, HideableFieldConfig, BarValueVisibility } from '@grafana/ui'; |
||||
|
||||
/** |
||||
* @alpha |
||||
*/ |
||||
export interface StatusPanelOptions { |
||||
legend: VizLegendOptions; |
||||
showValue: BarValueVisibility; |
||||
rowHeight: number; |
||||
colWidth?: number; |
||||
} |
||||
|
||||
/** |
||||
* @alpha |
||||
*/ |
||||
export interface StatusFieldConfig extends HideableFieldConfig { |
||||
lineWidth?: number; // 0
|
||||
fillOpacity?: number; // 100
|
||||
} |
||||
|
||||
/** |
||||
* @alpha |
||||
*/ |
||||
export const defaultStatusFieldConfig: StatusFieldConfig = { |
||||
lineWidth: 1, |
||||
fillOpacity: 70, |
||||
}; |
||||
Loading…
Reference in new issue