diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts index 42a653a832c..1ca7c454166 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.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' }], diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts index 24609ad8f4d..2f7f6b34687 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts @@ -40,7 +40,7 @@ const fieldNameMatcher: FieldMatcherInfo = { 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 = { const { names, mode = ByNamesMatcherMode.include } = options; const uniqueNames = new Set(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 => { diff --git a/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts b/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts index 7b7d34954bf..5ed213c2fe7 100644 --- a/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts +++ b/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts @@ -55,9 +55,7 @@ describe('ensureColumns transformer', () => { Object { "config": Object {}, "name": "TheTime", - "state": Object { - "displayName": "TheTime", - }, + "state": Object {}, "type": "time", "values": Array [ 1000, diff --git a/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts b/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts index d97edad7ef0..ffc8522def3 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts @@ -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, diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx index 1d67ddccc3f..bcfe77446a7 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx @@ -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>((props) => { const { data, options, onChange: onChangeFromProps } = props; @@ -10,10 +11,10 @@ export const FieldNameMatcherEditor = memo>((props) => { const onChange = useCallback( (selection: SelectableValue) => { - 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 = { description: 'Set properties for a specific field', optionsToLabel: (options) => options, }; - -const useFieldDisplayNames = (data: DataFrame[]): Set => { - return useMemo(() => { - const names: Set = 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, currentName: string): Array> => { - 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]); -}; diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx index 5deb5132ef9..69fc04506c4 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx @@ -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>((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>) => { @@ -26,10 +20,10 @@ export const FieldNamesMatcherEditor = memo { - 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 (options.names ?? []).join(', '), excludeFromPicker: true, }; - -const useFieldDisplayNames = (data: DataFrame[]): Set => { - return useMemo(() => { - const names: Set = 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): Array> => { - return useMemo(() => { - return Array.from(displayNames).map((n) => ({ - value: n, - label: n, - })); - }, [displayNames]); -}; diff --git a/packages/grafana-ui/src/components/MatchersUI/utils.ts b/packages/grafana-ui/src/components/MatchersUI/utils.ts new file mode 100644 index 00000000000..124448fa69e --- /dev/null +++ b/packages/grafana-ui/src/components/MatchersUI/utils.ts @@ -0,0 +1,94 @@ +import { useMemo } from 'react'; +import { DataFrame, getFieldDisplayName, SelectableValue } from '@grafana/data'; + +/** + * @internal + */ +export interface FrameFieldsDisplayNames { + // The display names + display: Set; + + // raw field names (that are explicitly not visible) + raw: Set; +} + +/** + * @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(), + raw: new Set(), + }; + + 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> { + return useMemo(() => { + let found = false; + const options: Array> = []; + 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]); +}