diff --git a/public/app/features/panel/panellinks/link_srv.ts b/public/app/features/panel/panellinks/link_srv.ts index ea4125275b2..f73ef1d7833 100644 --- a/public/app/features/panel/panellinks/link_srv.ts +++ b/public/app/features/panel/panellinks/link_srv.ts @@ -5,22 +5,23 @@ import coreModule from 'app/core/core_module'; import { getConfig } from 'app/core/config'; import { DataFrame, + DataLink, DataLinkBuiltInVars, + DataLinkClickEvent, deprecationWarning, Field, FieldType, + getFieldDisplayName, KeyValue, LinkModel, locationUtil, + PanelPlugin, ScopedVars, + textUtil, + urlUtil, VariableOrigin, VariableSuggestion, VariableSuggestionsScope, - urlUtil, - textUtil, - DataLink, - PanelPlugin, - DataLinkClickEvent, } from '@grafana/data'; const timeRangeVars = [ @@ -75,7 +76,7 @@ const valueVars = [ ]; const buildLabelPath = (label: string) => { - return label.indexOf('.') > -1 ? `["${label}"]` : `.${label}`; + return label.includes('.') || label.trim().includes(' ') ? `["${label}"]` : `.${label}`; }; export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [ @@ -126,33 +127,35 @@ const getFieldVars = (dataFrames: DataFrame[]) => { ]; }; -const getDataFrameVars = (dataFrames: DataFrame[]) => { +export const getDataFrameVars = (dataFrames: DataFrame[]) => { let numeric: Field | undefined = undefined; let title: Field | undefined = undefined; const suggestions: VariableSuggestion[] = []; const keys: KeyValue = {}; - for (const df of dataFrames) { - for (const f of df.fields) { - if (keys[f.name]) { + for (const frame of dataFrames) { + for (const field of frame.fields) { + const displayName = getFieldDisplayName(field, frame, dataFrames); + + if (keys[displayName]) { continue; } suggestions.push({ - value: `__data.fields[${f.name}]`, - label: `${f.name}`, - documentation: `Formatted value for ${f.name} on the same row`, + value: `__data.fields${buildLabelPath(displayName)}`, + label: `${displayName}`, + documentation: `Formatted value for ${displayName} on the same row`, origin: VariableOrigin.Fields, }); - keys[f.name] = true; + keys[displayName] = true; - if (!numeric && f.type === FieldType.number) { - numeric = f; + if (!numeric && field.type === FieldType.number) { + numeric = { ...field, name: displayName }; } - if (!title && f.config.displayName && f.config.displayName !== f.name) { - title = f; + if (!title && field.config.displayName && field.config.displayName !== field.name) { + title = { ...field, name: displayName }; } } } @@ -168,13 +171,13 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => { if (numeric) { suggestions.push({ - value: `__data.fields[${numeric.name}].numeric`, + value: `__data.fields${buildLabelPath(numeric.name)}.numeric`, label: `Show numeric value`, documentation: `the numeric field value`, origin: VariableOrigin.Fields, }); suggestions.push({ - value: `__data.fields[${numeric.name}].text`, + value: `__data.fields${buildLabelPath(numeric.name)}.text`, label: `Show text value`, documentation: `the text value`, origin: VariableOrigin.Fields, @@ -183,7 +186,7 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => { if (title) { suggestions.push({ - value: `__data.fields[${title.config.displayName}]`, + value: `__data.fields${buildLabelPath(title.name)}`, label: `Select by title`, documentation: `Use the title to pick the field`, origin: VariableOrigin.Fields, diff --git a/public/app/features/panel/panellinks/specs/link_srv.test.ts b/public/app/features/panel/panellinks/specs/link_srv.test.ts index bfa35128bc6..71fb60ee64d 100644 --- a/public/app/features/panel/panellinks/specs/link_srv.test.ts +++ b/public/app/features/panel/panellinks/specs/link_srv.test.ts @@ -1,8 +1,16 @@ -import { LinkSrv } from '../link_srv'; -import { DataLinkBuiltInVars, locationUtil, VariableModel } from '@grafana/data'; +import { advanceTo } from 'jest-date-mock'; +import { + DataLinkBuiltInVars, + FieldType, + locationUtil, + toDataFrame, + VariableModel, + VariableOrigin, +} from '@grafana/data'; + +import { getDataFrameVars, LinkSrv } from '../link_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { advanceTo } from 'jest-date-mock'; import { updateConfig } from '../../../../core/config'; import { variableAdapters } from '../../../variables/adapters'; import { createQueryVariableAdapter } from '../../../variables/query/adapter'; @@ -267,3 +275,212 @@ describe('linkSrv', () => { ); }); }); + +describe('getDataFrameVars', () => { + describe('when called with a DataFrame that contains fields without nested path', () => { + it('then it should return correct suggestions', () => { + const frame = toDataFrame({ + name: 'indoor', + fields: [ + { name: 'time', type: FieldType.time, values: [1, 2, 3] }, + { name: 'temperature', type: FieldType.number, values: [10, 11, 12] }, + ], + }); + + const suggestions = getDataFrameVars([frame]); + + expect(suggestions).toEqual([ + { + value: '__data.fields.time', + label: 'time', + documentation: `Formatted value for time on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: '__data.fields.temperature', + label: 'temperature', + documentation: `Formatted value for temperature on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields[0]`, + label: `Select by index`, + documentation: `Enter the field order`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields.temperature.numeric`, + label: `Show numeric value`, + documentation: `the numeric field value`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields.temperature.text`, + label: `Show text value`, + documentation: `the text value`, + origin: VariableOrigin.Fields, + }, + ]); + }); + }); + + describe('when called with a DataFrame that contains fields with nested path', () => { + it('then it should return correct suggestions', () => { + const frame = toDataFrame({ + name: 'temperatures', + fields: [ + { name: 'time', type: FieldType.time, values: [1, 2, 3] }, + { name: 'temperature.indoor', type: FieldType.number, values: [10, 11, 12] }, + ], + }); + + const suggestions = getDataFrameVars([frame]); + + expect(suggestions).toEqual([ + { + value: '__data.fields.time', + label: 'time', + documentation: `Formatted value for time on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: '__data.fields["temperature.indoor"]', + label: 'temperature.indoor', + documentation: `Formatted value for temperature.indoor on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields[0]`, + label: `Select by index`, + documentation: `Enter the field order`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["temperature.indoor"].numeric`, + label: `Show numeric value`, + documentation: `the numeric field value`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["temperature.indoor"].text`, + label: `Show text value`, + documentation: `the text value`, + origin: VariableOrigin.Fields, + }, + ]); + }); + }); + + describe('when called with a DataFrame that contains fields with displayName', () => { + it('then it should return correct suggestions', () => { + const frame = toDataFrame({ + name: 'temperatures', + fields: [ + { name: 'time', type: FieldType.time, values: [1, 2, 3] }, + { name: 'temperature.indoor', type: FieldType.number, values: [10, 11, 12] }, + ], + }); + + frame.fields[1].config = { ...frame.fields[1].config, displayName: 'Indoor Temperature' }; + + const suggestions = getDataFrameVars([frame]); + + expect(suggestions).toEqual([ + { + value: '__data.fields.time', + label: 'time', + documentation: `Formatted value for time on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: '__data.fields["Indoor Temperature"]', + label: 'Indoor Temperature', + documentation: `Formatted value for Indoor Temperature on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields[0]`, + label: `Select by index`, + documentation: `Enter the field order`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["Indoor Temperature"].numeric`, + label: `Show numeric value`, + documentation: `the numeric field value`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["Indoor Temperature"].text`, + label: `Show text value`, + documentation: `the text value`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["Indoor Temperature"]`, + label: `Select by title`, + documentation: `Use the title to pick the field`, + origin: VariableOrigin.Fields, + }, + ]); + }); + }); + + describe('when called with a DataFrame that contains fields with duplicate names', () => { + it('then it should ignore duplicates', () => { + const frame = toDataFrame({ + name: 'temperatures', + fields: [ + { name: 'time', type: FieldType.time, values: [1, 2, 3] }, + { name: 'temperature.indoor', type: FieldType.number, values: [10, 11, 12] }, + { name: 'temperature.outdoor', type: FieldType.number, values: [20, 21, 22] }, + ], + }); + + frame.fields[1].config = { ...frame.fields[1].config, displayName: 'Indoor Temperature' }; + // Someone makes a mistake when renaming a field + frame.fields[2].config = { ...frame.fields[2].config, displayName: 'Indoor Temperature' }; + + const suggestions = getDataFrameVars([frame]); + + expect(suggestions).toEqual([ + { + value: '__data.fields.time', + label: 'time', + documentation: `Formatted value for time on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: '__data.fields["Indoor Temperature"]', + label: 'Indoor Temperature', + documentation: `Formatted value for Indoor Temperature on the same row`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields[0]`, + label: `Select by index`, + documentation: `Enter the field order`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["Indoor Temperature"].numeric`, + label: `Show numeric value`, + documentation: `the numeric field value`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["Indoor Temperature"].text`, + label: `Show text value`, + documentation: `the text value`, + origin: VariableOrigin.Fields, + }, + { + value: `__data.fields["Indoor Temperature"]`, + label: `Select by title`, + documentation: `Use the title to pick the field`, + origin: VariableOrigin.Fields, + }, + ]); + }); + }); +});