Matchers: support both name and display name (#35466)

pull/36024/head
Ryan McKinley 4 years ago committed by GitHub
parent ffc18ebbcf
commit 970481bdec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts
  2. 16
      packages/grafana-data/src/transformations/matchers/nameMatcher.ts
  3. 4
      packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts
  4. 117
      packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts
  5. 39
      packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx
  6. 41
      packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx
  7. 94
      packages/grafana-ui/src/components/MatchersUI/utils.ts

@ -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]);
};

@ -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…
Cancel
Save