mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: adding possibility to toggle tooltip, graph and legend for series (#29575)
This commit is contained in:
@@ -102,6 +102,7 @@ export class GrafanaApp {
|
||||
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
||||
standardTransformersRegistry.setInit(getStandardTransformers);
|
||||
variableAdapters.setInit(getDefaultVariableAdapters);
|
||||
|
||||
setVariableQueryRunner(new VariableQueryRunner());
|
||||
|
||||
app.config(
|
||||
|
||||
@@ -11,6 +11,7 @@ interface DynamicConfigValueEditorProps {
|
||||
context: FieldOverrideContext;
|
||||
onRemove: () => void;
|
||||
isCollapsible?: boolean;
|
||||
isSystemOverride?: boolean;
|
||||
}
|
||||
|
||||
export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> = ({
|
||||
@@ -20,6 +21,7 @@ export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> =
|
||||
onChange,
|
||||
onRemove,
|
||||
isCollapsible,
|
||||
isSystemOverride,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme);
|
||||
@@ -37,9 +39,11 @@ export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> =
|
||||
{item.name}
|
||||
{!isExpanded && includeCounter && item.getItemsCount && <Counter value={item.getItemsCount(property.value)} />}
|
||||
</Label>
|
||||
<div>
|
||||
<IconButton name="times" onClick={onRemove} />
|
||||
</div>
|
||||
{!isSystemOverride && (
|
||||
<div>
|
||||
<IconButton name="times" onClick={onRemove} />
|
||||
</div>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
FieldConfigOptionsRegistry,
|
||||
FieldConfigProperty,
|
||||
GrafanaTheme,
|
||||
isSystemOverride as isSystemOverrideGuard,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
@@ -136,6 +137,8 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const isSystemOverride = isSystemOverrideGuard(override);
|
||||
|
||||
return (
|
||||
<OptionsGroup renderTitle={renderOverrideTitle} id={name} key={name}>
|
||||
<Field label={matcherLabel}>
|
||||
@@ -150,6 +153,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
|
||||
<>
|
||||
{override.properties.map((p, j) => {
|
||||
const item = registry.getIfExists(p.id);
|
||||
console.log('item', item);
|
||||
|
||||
if (!item) {
|
||||
return <div>Unknown property: {p.id}</div>;
|
||||
@@ -162,6 +166,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
|
||||
<DynamicConfigValueEditor
|
||||
key={`${p.id}/${j}`}
|
||||
isCollapsible={isCollapsible}
|
||||
isSystemOverride={isSystemOverride}
|
||||
onChange={value => onDynamicConfigValueChange(j, value)}
|
||||
onRemove={() => onDynamicConfigValueRemove(j)}
|
||||
property={p}
|
||||
@@ -173,7 +178,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{override.matcher.options && (
|
||||
{!isSystemOverride && override.matcher.options && (
|
||||
<div className={styles.propertyPickerWrapper}>
|
||||
<ValuePicker
|
||||
label="Add override property"
|
||||
|
||||
@@ -82,6 +82,7 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
|
||||
variant="secondary"
|
||||
options={fieldMatchersUI
|
||||
.list()
|
||||
.filter(o => !o.excludeFromPicker)
|
||||
.map<SelectableValue<string>>(i => ({ label: i.name, value: i.id, description: i.description }))}
|
||||
onChange={value => onOverrideAdd(value)}
|
||||
isFullWidth={false}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { TooltipPlugin, ZoomPlugin, GraphNG } from '@grafana/ui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { TooltipPlugin, ZoomPlugin, GraphNG, GraphNGLegendEvent } from '@grafana/ui';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
import { Options } from './types';
|
||||
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
|
||||
import { ExemplarsPlugin } from './plugins/ExemplarsPlugin';
|
||||
import { hideSeriesConfigFactory } from './hideSeriesConfigFactory';
|
||||
import { ContextMenuPlugin } from './plugins/ContextMenuPlugin';
|
||||
|
||||
interface GraphPanelProps extends PanelProps<Options> {}
|
||||
@@ -15,9 +16,18 @@ export const GraphPanel: React.FC<GraphPanelProps> = ({
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
fieldConfig,
|
||||
onChangeTimeRange,
|
||||
onFieldConfigChange,
|
||||
replaceVariables,
|
||||
}) => {
|
||||
const onLegendClick = useCallback(
|
||||
(event: GraphNGLegendEvent) => {
|
||||
onFieldConfigChange(hideSeriesConfigFactory(event, fieldConfig, data.series));
|
||||
},
|
||||
[fieldConfig, onFieldConfigChange, data.series]
|
||||
);
|
||||
|
||||
return (
|
||||
<GraphNG
|
||||
data={data.series}
|
||||
@@ -26,6 +36,7 @@ export const GraphPanel: React.FC<GraphPanelProps> = ({
|
||||
width={width}
|
||||
height={height}
|
||||
legend={options.legend}
|
||||
onLegendClick={onLegendClick}
|
||||
>
|
||||
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
||||
<ZoomPlugin onZoom={onChangeTimeRange} />
|
||||
|
||||
32
public/app/plugins/panel/graph3/HideSeriesConfigEditor.tsx
Normal file
32
public/app/plugins/panel/graph3/HideSeriesConfigEditor.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { FilterPill, HorizontalGroup } from '@grafana/ui';
|
||||
import { FieldConfigEditorProps } from '@grafana/data';
|
||||
import { HideSeriesConfig } from '@grafana/ui/src/components/uPlot/config';
|
||||
|
||||
export const SeriesConfigEditor: React.FC<FieldConfigEditorProps<HideSeriesConfig, {}>> = props => {
|
||||
const { value, onChange } = props;
|
||||
|
||||
const onChangeToggle = useCallback(
|
||||
(prop: keyof HideSeriesConfig) => {
|
||||
onChange({ ...value, [prop]: !value[prop] });
|
||||
},
|
||||
[value, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<HorizontalGroup spacing="xs">
|
||||
{Object.keys(value).map((key: keyof HideSeriesConfig) => {
|
||||
return (
|
||||
<FilterPill
|
||||
icon={value[key] ? 'eye-slash' : 'eye'}
|
||||
onClick={() => onChangeToggle(key)}
|
||||
key={key}
|
||||
label={_.startCase(key)}
|
||||
selected={value[key]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
419
public/app/plugins/panel/graph3/hideSeriesConfigFactory.test.ts
Normal file
419
public/app/plugins/panel/graph3/hideSeriesConfigFactory.test.ts
Normal file
@@ -0,0 +1,419 @@
|
||||
import {
|
||||
ByNamesMatcherMode,
|
||||
DataFrame,
|
||||
FieldConfigSource,
|
||||
FieldMatcherID,
|
||||
FieldType,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { GraphNGLegendEvent, GraphNGLegendEventMode } from '@grafana/ui';
|
||||
import { hideSeriesConfigFactory } from './hideSeriesConfigFactory';
|
||||
|
||||
describe('hideSeriesConfigFactory', () => {
|
||||
it('should create config override matching one series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override matching one series if selected with others', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature', 'humidity'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'pressure', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override that append series to existing override', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 1,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'pressure', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature', 'humidity'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override that hides all series if appending only existing series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride([])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override that removes series if appending existing field', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature', 'humidity'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['humidity'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override replacing existing series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 1,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['humidity'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override removing existing series', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove override if all fields are appended', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 1,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create config override hiding appended series if no previous override exists', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.AppendToSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'pressure', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['humidity', 'pressure'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return existing override if invalid index is passed', () => {
|
||||
const event: GraphNGLegendEvent = {
|
||||
mode: GraphNGLegendEventMode.ToggleSelection,
|
||||
fieldIndex: {
|
||||
frameIndex: 4,
|
||||
fieldIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const existingConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
};
|
||||
|
||||
const data: DataFrame[] = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] },
|
||||
{ name: 'humidity', type: FieldType.number, values: [1, 3, 5, 7] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const config = hideSeriesConfigFactory(event, existingConfig, data);
|
||||
|
||||
expect(config).toEqual({
|
||||
defaults: {},
|
||||
overrides: [createOverride(['temperature'])],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const createOverride = (matchers: string[]) => {
|
||||
return {
|
||||
__systemRef: 'hideSeriesFrom',
|
||||
matcher: {
|
||||
id: FieldMatcherID.byNames,
|
||||
options: {
|
||||
mode: ByNamesMatcherMode.exclude,
|
||||
names: matchers,
|
||||
prefix: 'All except:',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.hideFrom',
|
||||
value: {
|
||||
graph: true,
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
175
public/app/plugins/panel/graph3/hideSeriesConfigFactory.ts
Normal file
175
public/app/plugins/panel/graph3/hideSeriesConfigFactory.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import {
|
||||
ByNamesMatcherMode,
|
||||
DataFrame,
|
||||
DynamicConfigValue,
|
||||
FieldConfigSource,
|
||||
FieldMatcherID,
|
||||
FieldType,
|
||||
getFieldDisplayName,
|
||||
isSystemOverrideWithRef,
|
||||
SystemConfigOverrideRule,
|
||||
} from '@grafana/data';
|
||||
import { GraphNGLegendEvent, GraphNGLegendEventMode } from '@grafana/ui';
|
||||
|
||||
const displayOverrideRef = 'hideSeriesFrom';
|
||||
const isHideSeriesOverride = isSystemOverrideWithRef(displayOverrideRef);
|
||||
|
||||
export const hideSeriesConfigFactory = (
|
||||
event: GraphNGLegendEvent,
|
||||
fieldConfig: FieldConfigSource<any>,
|
||||
data: DataFrame[]
|
||||
): FieldConfigSource<any> => {
|
||||
const { fieldIndex, mode } = event;
|
||||
const { overrides } = fieldConfig;
|
||||
|
||||
const frame = data[fieldIndex.frameIndex];
|
||||
|
||||
if (!frame) {
|
||||
return fieldConfig;
|
||||
}
|
||||
|
||||
const field = frame.fields[fieldIndex.fieldIndex];
|
||||
|
||||
if (!field) {
|
||||
return fieldConfig;
|
||||
}
|
||||
|
||||
const displayName = getFieldDisplayName(field, frame, data);
|
||||
const currentIndex = overrides.findIndex(isHideSeriesOverride);
|
||||
|
||||
if (currentIndex < 0) {
|
||||
if (mode === GraphNGLegendEventMode.ToggleSelection) {
|
||||
const override = createOverride([displayName]);
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...fieldConfig.overrides],
|
||||
};
|
||||
}
|
||||
|
||||
const displayNames = getDisplayNames(data, displayName);
|
||||
const override = createOverride(displayNames);
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...fieldConfig.overrides],
|
||||
};
|
||||
}
|
||||
|
||||
const overridesCopy = Array.from(overrides);
|
||||
const [current] = overridesCopy.splice(currentIndex, 1) as SystemConfigOverrideRule[];
|
||||
|
||||
if (mode === GraphNGLegendEventMode.ToggleSelection) {
|
||||
const existing = getExistingDisplayNames(current);
|
||||
|
||||
if (existing[0] === displayName && existing.length === 1) {
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: overridesCopy,
|
||||
};
|
||||
}
|
||||
|
||||
const override = createOverride([displayName]);
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...overridesCopy],
|
||||
};
|
||||
}
|
||||
|
||||
const override = createExtendedOverride(current, displayName);
|
||||
|
||||
if (allFieldsAreExcluded(override, data)) {
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: overridesCopy,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
overrides: [override, ...overridesCopy],
|
||||
};
|
||||
};
|
||||
|
||||
const createExtendedOverride = (current: SystemConfigOverrideRule, displayName: string): SystemConfigOverrideRule => {
|
||||
const property = current.properties.find(p => p.id === 'custom.hideFrom');
|
||||
const existing = getExistingDisplayNames(current);
|
||||
const index = existing.findIndex(name => name === displayName);
|
||||
|
||||
if (index < 0) {
|
||||
existing.push(displayName);
|
||||
} else {
|
||||
existing.splice(index, 1);
|
||||
}
|
||||
|
||||
return createOverride(existing, property);
|
||||
};
|
||||
|
||||
const getExistingDisplayNames = (rule: SystemConfigOverrideRule): string[] => {
|
||||
const names = rule.matcher.options?.names;
|
||||
if (!Array.isArray(names)) {
|
||||
return [];
|
||||
}
|
||||
return names;
|
||||
};
|
||||
|
||||
const createOverride = (names: string[], property?: DynamicConfigValue): SystemConfigOverrideRule => {
|
||||
property = property ?? {
|
||||
id: 'custom.hideFrom',
|
||||
value: {
|
||||
graph: true,
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
__systemRef: displayOverrideRef,
|
||||
matcher: {
|
||||
id: FieldMatcherID.byNames,
|
||||
options: {
|
||||
mode: ByNamesMatcherMode.exclude,
|
||||
names: names,
|
||||
prefix: 'All except:',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
...property,
|
||||
value: {
|
||||
graph: true,
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const allFieldsAreExcluded = (override: SystemConfigOverrideRule, data: DataFrame[]): boolean => {
|
||||
return getExistingDisplayNames(override).length === getDisplayNames(data).length;
|
||||
};
|
||||
|
||||
const getDisplayNames = (data: DataFrame[], excludeName?: string): string[] => {
|
||||
const unique = new Set<string>();
|
||||
|
||||
for (const frame of data) {
|
||||
for (const field of frame.fields) {
|
||||
if (field.type !== FieldType.number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = getFieldDisplayName(field, frame, data);
|
||||
|
||||
if (name === excludeName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unique.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(unique);
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ScaleDistribution,
|
||||
ScaleDistributionConfig,
|
||||
} from '@grafana/ui';
|
||||
import { SeriesConfigEditor } from './HideSeriesConfigEditor';
|
||||
import { GraphPanel } from './GraphPanel';
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
import { Options } from './types';
|
||||
@@ -154,6 +155,23 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
|
||||
defaultValue: { type: ScaleDistribution.Linear },
|
||||
shouldApply: f => f.type === FieldType.number,
|
||||
process: identityOverrideProcessor,
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'hideFrom',
|
||||
name: 'Hide in area',
|
||||
category: ['Series'],
|
||||
path: 'hideFrom',
|
||||
defaultValue: {
|
||||
tooltip: false,
|
||||
graph: false,
|
||||
legend: false,
|
||||
},
|
||||
editor: SeriesConfigEditor,
|
||||
override: SeriesConfigEditor,
|
||||
shouldApply: () => true,
|
||||
hideFromDefaults: true,
|
||||
hideFromOverrides: true,
|
||||
process: value => value,
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user