mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Matchers: support both name and display name (#35466)
This commit is contained in:
parent
ffc18ebbcf
commit
970481bdec
@ -90,6 +90,33 @@ describe('Field Name Matcher', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('Match name or displayName', () => {
|
||||
const seriesWithNames = toDataFrame({
|
||||
fields: [{ name: 'a', config: { displayName: 'LongerName' } }],
|
||||
});
|
||||
const field = seriesWithNames.fields[0];
|
||||
expect(
|
||||
getFieldMatcher({
|
||||
id: FieldMatcherID.byName,
|
||||
options: 'c',
|
||||
})(field, seriesWithNames, [seriesWithNames])
|
||||
).toBe(false); // No match
|
||||
|
||||
expect(
|
||||
getFieldMatcher({
|
||||
id: FieldMatcherID.byName,
|
||||
options: 'a',
|
||||
})(field, seriesWithNames, [seriesWithNames])
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
getFieldMatcher({
|
||||
id: FieldMatcherID.byName,
|
||||
options: 'LongerName',
|
||||
})(field, seriesWithNames, [seriesWithNames])
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('Match none of the field names', () => {
|
||||
const seriesWithNames = toDataFrame({
|
||||
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
|
||||
|
@ -40,7 +40,7 @@ const fieldNameMatcher: FieldMatcherInfo<string> = {
|
||||
|
||||
get: (name: string): FieldMatcher => {
|
||||
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
||||
return getFieldDisplayName(field, frame, allFrames) === name;
|
||||
return name === field.name || getFieldDisplayName(field, frame, allFrames) === name;
|
||||
};
|
||||
},
|
||||
|
||||
@ -62,12 +62,16 @@ const multipleFieldNamesMatcher: FieldMatcherInfo<ByNamesMatcherOptions> = {
|
||||
const { names, mode = ByNamesMatcherMode.include } = options;
|
||||
const uniqueNames = new Set<string>(names ?? []);
|
||||
|
||||
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
||||
if (mode === ByNamesMatcherMode.exclude) {
|
||||
return !uniqueNames.has(getFieldDisplayName(field, frame, allFrames));
|
||||
}
|
||||
return uniqueNames.has(getFieldDisplayName(field, frame, allFrames));
|
||||
const matcher = (field: Field, frame: DataFrame, frames: DataFrame[]) => {
|
||||
return uniqueNames.has(field.name) || uniqueNames.has(getFieldDisplayName(field, frame, frames));
|
||||
};
|
||||
|
||||
if (mode === ByNamesMatcherMode.exclude) {
|
||||
return (field: Field, frame: DataFrame, frames: DataFrame[]) => {
|
||||
return !matcher(field, frame, frames);
|
||||
};
|
||||
}
|
||||
return matcher;
|
||||
},
|
||||
|
||||
getOptionsDisplayText: (options: ByNamesMatcherOptions): string => {
|
||||
|
@ -55,9 +55,7 @@ describe('ensureColumns transformer', () => {
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "TheTime",
|
||||
"state": Object {
|
||||
"displayName": "TheTime",
|
||||
},
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
1000,
|
||||
|
@ -49,9 +49,7 @@ describe('SeriesToColumns Transformer', () => {
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "time",
|
||||
"state": Object {
|
||||
"displayName": "time",
|
||||
},
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
1000,
|
||||
@ -148,7 +146,102 @@ describe('SeriesToColumns Transformer', () => {
|
||||
(received) => {
|
||||
const data = received[0];
|
||||
const filtered = data[0];
|
||||
expect(filtered.fields).toMatchInlineSnapshot(`Array []`);
|
||||
expect(filtered.fields).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "temperature",
|
||||
"state": Object {},
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
10.3,
|
||||
10.4,
|
||||
10.5,
|
||||
10.6,
|
||||
11.1,
|
||||
11.3,
|
||||
11.5,
|
||||
11.7,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"name": "even",
|
||||
},
|
||||
"name": "time",
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
3000,
|
||||
4000,
|
||||
5000,
|
||||
6000,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"name": "even",
|
||||
},
|
||||
"name": "humidity",
|
||||
"state": Object {},
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
10000.3,
|
||||
10000.4,
|
||||
10000.5,
|
||||
10000.6,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"name": "odd",
|
||||
},
|
||||
"name": "time",
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
1000,
|
||||
3000,
|
||||
5000,
|
||||
7000,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"name": "odd",
|
||||
},
|
||||
"name": "humidity",
|
||||
"state": Object {},
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
11000.1,
|
||||
11000.3,
|
||||
11000.5,
|
||||
11000.7,
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -174,9 +267,7 @@ describe('SeriesToColumns Transformer', () => {
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "time",
|
||||
"state": Object {
|
||||
"displayName": "time",
|
||||
},
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
1000,
|
||||
@ -295,9 +386,7 @@ describe('SeriesToColumns Transformer', () => {
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "time",
|
||||
"state": Object {
|
||||
"displayName": "time",
|
||||
},
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
1000,
|
||||
@ -380,9 +469,7 @@ describe('SeriesToColumns Transformer', () => {
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "time",
|
||||
"state": Object {
|
||||
"displayName": "time",
|
||||
},
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
1,
|
||||
@ -453,9 +540,7 @@ describe('SeriesToColumns Transformer', () => {
|
||||
Object {
|
||||
"config": Object {},
|
||||
"name": "time",
|
||||
"state": Object {
|
||||
"displayName": "time",
|
||||
},
|
||||
"state": Object {},
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
1,
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { memo, useMemo, useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types';
|
||||
import { FieldMatcherID, fieldMatchers, getFieldDisplayName, SelectableValue, DataFrame } from '@grafana/data';
|
||||
import { FieldMatcherID, fieldMatchers, SelectableValue } from '@grafana/data';
|
||||
import { Select } from '../Select/Select';
|
||||
import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils';
|
||||
|
||||
export const FieldNameMatcherEditor = memo<MatcherUIProps<string>>((props) => {
|
||||
const { data, options, onChange: onChangeFromProps } = props;
|
||||
@ -10,10 +11,10 @@ export const FieldNameMatcherEditor = memo<MatcherUIProps<string>>((props) => {
|
||||
|
||||
const onChange = useCallback(
|
||||
(selection: SelectableValue<string>) => {
|
||||
if (!selection.value || !names.has(selection.value)) {
|
||||
if (!frameHasName(selection.value, names)) {
|
||||
return;
|
||||
}
|
||||
return onChangeFromProps(selection.value);
|
||||
return onChangeFromProps(selection.value!);
|
||||
},
|
||||
[names, onChangeFromProps]
|
||||
);
|
||||
@ -31,33 +32,3 @@ export const fieldNameMatcherItem: FieldMatcherUIRegistryItem<string> = {
|
||||
description: 'Set properties for a specific field',
|
||||
optionsToLabel: (options) => options,
|
||||
};
|
||||
|
||||
const useFieldDisplayNames = (data: DataFrame[]): Set<string> => {
|
||||
return useMemo(() => {
|
||||
const names: Set<string> = new Set();
|
||||
|
||||
for (const frame of data) {
|
||||
for (const field of frame.fields) {
|
||||
names.add(getFieldDisplayName(field, frame, data));
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}, [data]);
|
||||
};
|
||||
|
||||
const useSelectOptions = (displayNames: Set<string>, currentName: string): Array<SelectableValue<string>> => {
|
||||
return useMemo(() => {
|
||||
const vals = Array.from(displayNames).map((n) => ({
|
||||
value: n,
|
||||
label: n,
|
||||
}));
|
||||
if (currentName && !displayNames.has(currentName)) {
|
||||
vals.push({
|
||||
value: currentName,
|
||||
label: `${currentName} (not found)`,
|
||||
});
|
||||
}
|
||||
return vals;
|
||||
}, [displayNames, currentName]);
|
||||
};
|
||||
|
@ -1,21 +1,15 @@
|
||||
import React, { memo, useMemo, useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types';
|
||||
import {
|
||||
FieldMatcherID,
|
||||
fieldMatchers,
|
||||
getFieldDisplayName,
|
||||
SelectableValue,
|
||||
DataFrame,
|
||||
ByNamesMatcherOptions,
|
||||
} from '@grafana/data';
|
||||
import { FieldMatcherID, fieldMatchers, SelectableValue, ByNamesMatcherOptions } from '@grafana/data';
|
||||
import { MultiSelect } from '../Select/Select';
|
||||
import { Input } from '../Input/Input';
|
||||
import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils';
|
||||
|
||||
export const FieldNamesMatcherEditor = memo<MatcherUIProps<ByNamesMatcherOptions>>((props) => {
|
||||
const { data, options, onChange: onChangeFromProps } = props;
|
||||
const { readOnly, prefix } = options;
|
||||
const names = useFieldDisplayNames(data);
|
||||
const selectOptions = useSelectOptions(names);
|
||||
const selectOptions = useSelectOptions(names, undefined);
|
||||
|
||||
const onChange = useCallback(
|
||||
(selections: Array<SelectableValue<string>>) => {
|
||||
@ -26,10 +20,10 @@ export const FieldNamesMatcherEditor = memo<MatcherUIProps<ByNamesMatcherOptions
|
||||
return onChangeFromProps({
|
||||
...options,
|
||||
names: selections.reduce((all: string[], current) => {
|
||||
if (!current?.value || !names.has(current.value)) {
|
||||
if (!frameHasName(current.value, names)) {
|
||||
return all;
|
||||
}
|
||||
all.push(current.value);
|
||||
all.push(current.value!);
|
||||
return all;
|
||||
}, []),
|
||||
});
|
||||
@ -55,26 +49,3 @@ export const fieldNamesMatcherItem: FieldMatcherUIRegistryItem<ByNamesMatcherOpt
|
||||
optionsToLabel: (options) => (options.names ?? []).join(', '),
|
||||
excludeFromPicker: true,
|
||||
};
|
||||
|
||||
const useFieldDisplayNames = (data: DataFrame[]): Set<string> => {
|
||||
return useMemo(() => {
|
||||
const names: Set<string> = new Set();
|
||||
|
||||
for (const frame of data) {
|
||||
for (const field of frame.fields) {
|
||||
names.add(getFieldDisplayName(field, frame, data));
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}, [data]);
|
||||
};
|
||||
|
||||
const useSelectOptions = (displayNames: Set<string>): Array<SelectableValue<string>> => {
|
||||
return useMemo(() => {
|
||||
return Array.from(displayNames).map((n) => ({
|
||||
value: n,
|
||||
label: n,
|
||||
}));
|
||||
}, [displayNames]);
|
||||
};
|
||||
|
94
packages/grafana-ui/src/components/MatchersUI/utils.ts
Normal file
94
packages/grafana-ui/src/components/MatchersUI/utils.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { useMemo } from 'react';
|
||||
import { DataFrame, getFieldDisplayName, SelectableValue } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface FrameFieldsDisplayNames {
|
||||
// The display names
|
||||
display: Set<string>;
|
||||
|
||||
// raw field names (that are explicitly not visible)
|
||||
raw: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function frameHasName(name: string | undefined, names: FrameFieldsDisplayNames) {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return names.display.has(name) || names.raw.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retuns the distinct names in a set of frames
|
||||
*/
|
||||
function getFrameFieldsDisplayNames(data: DataFrame[]): FrameFieldsDisplayNames {
|
||||
const names: FrameFieldsDisplayNames = {
|
||||
display: new Set<string>(),
|
||||
raw: new Set<string>(),
|
||||
};
|
||||
|
||||
for (const frame of data) {
|
||||
for (const field of frame.fields) {
|
||||
const disp = getFieldDisplayName(field, frame, data);
|
||||
names.display.add(disp);
|
||||
if (field.name && disp !== field.name) {
|
||||
names.raw.add(field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function useFieldDisplayNames(data: DataFrame[]): FrameFieldsDisplayNames {
|
||||
return useMemo(() => {
|
||||
return getFrameFieldsDisplayNames(data);
|
||||
}, [data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function useSelectOptions(
|
||||
displayNames: FrameFieldsDisplayNames,
|
||||
currentName?: string
|
||||
): Array<SelectableValue<string>> {
|
||||
return useMemo(() => {
|
||||
let found = false;
|
||||
const options: Array<SelectableValue<string>> = [];
|
||||
for (const name of displayNames.display) {
|
||||
if (!found && name === currentName) {
|
||||
found = true;
|
||||
}
|
||||
options.push({
|
||||
value: name,
|
||||
label: name,
|
||||
});
|
||||
}
|
||||
for (const name of displayNames.raw) {
|
||||
if (!displayNames.display.has(name)) {
|
||||
if (!found && name === currentName) {
|
||||
found = true;
|
||||
}
|
||||
options.push({
|
||||
value: name,
|
||||
label: `${name} (base field name)`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (currentName && !found) {
|
||||
options.push({
|
||||
value: currentName,
|
||||
label: `${currentName} (not found)`,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}, [displayNames, currentName]);
|
||||
}
|
Loading…
Reference in New Issue
Block a user