Commit e86ff52d by Hugo Häggmark Committed by GitHub

DataLinks: Respects display name and adds field quoting (#27616)

* DataLinks: Adds field quoting and respects DisplayName

* Update public/app/features/panel/panellinks/link_srv.ts

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
parent 8475bd2f
...@@ -5,22 +5,23 @@ import coreModule from 'app/core/core_module'; ...@@ -5,22 +5,23 @@ import coreModule from 'app/core/core_module';
import { getConfig } from 'app/core/config'; import { getConfig } from 'app/core/config';
import { import {
DataFrame, DataFrame,
DataLink,
DataLinkBuiltInVars, DataLinkBuiltInVars,
DataLinkClickEvent,
deprecationWarning, deprecationWarning,
Field, Field,
FieldType, FieldType,
getFieldDisplayName,
KeyValue, KeyValue,
LinkModel, LinkModel,
locationUtil, locationUtil,
PanelPlugin,
ScopedVars, ScopedVars,
textUtil,
urlUtil,
VariableOrigin, VariableOrigin,
VariableSuggestion, VariableSuggestion,
VariableSuggestionsScope, VariableSuggestionsScope,
urlUtil,
textUtil,
DataLink,
PanelPlugin,
DataLinkClickEvent,
} from '@grafana/data'; } from '@grafana/data';
const timeRangeVars = [ const timeRangeVars = [
...@@ -75,7 +76,7 @@ const valueVars = [ ...@@ -75,7 +76,7 @@ const valueVars = [
]; ];
const buildLabelPath = (label: string) => { const buildLabelPath = (label: string) => {
return label.indexOf('.') > -1 ? `["${label}"]` : `.${label}`; return label.includes('.') || label.trim().includes(' ') ? `["${label}"]` : `.${label}`;
}; };
export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [ export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [
...@@ -126,33 +127,35 @@ const getFieldVars = (dataFrames: DataFrame[]) => { ...@@ -126,33 +127,35 @@ const getFieldVars = (dataFrames: DataFrame[]) => {
]; ];
}; };
const getDataFrameVars = (dataFrames: DataFrame[]) => { export const getDataFrameVars = (dataFrames: DataFrame[]) => {
let numeric: Field | undefined = undefined; let numeric: Field | undefined = undefined;
let title: Field | undefined = undefined; let title: Field | undefined = undefined;
const suggestions: VariableSuggestion[] = []; const suggestions: VariableSuggestion[] = [];
const keys: KeyValue<true> = {}; const keys: KeyValue<true> = {};
for (const df of dataFrames) { for (const frame of dataFrames) {
for (const f of df.fields) { for (const field of frame.fields) {
if (keys[f.name]) { const displayName = getFieldDisplayName(field, frame, dataFrames);
if (keys[displayName]) {
continue; continue;
} }
suggestions.push({ suggestions.push({
value: `__data.fields[${f.name}]`, value: `__data.fields${buildLabelPath(displayName)}`,
label: `${f.name}`, label: `${displayName}`,
documentation: `Formatted value for ${f.name} on the same row`, documentation: `Formatted value for ${displayName} on the same row`,
origin: VariableOrigin.Fields, origin: VariableOrigin.Fields,
}); });
keys[f.name] = true; keys[displayName] = true;
if (!numeric && f.type === FieldType.number) { if (!numeric && field.type === FieldType.number) {
numeric = f; numeric = { ...field, name: displayName };
} }
if (!title && f.config.displayName && f.config.displayName !== f.name) { if (!title && field.config.displayName && field.config.displayName !== field.name) {
title = f; title = { ...field, name: displayName };
} }
} }
} }
...@@ -168,13 +171,13 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => { ...@@ -168,13 +171,13 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => {
if (numeric) { if (numeric) {
suggestions.push({ suggestions.push({
value: `__data.fields[${numeric.name}].numeric`, value: `__data.fields${buildLabelPath(numeric.name)}.numeric`,
label: `Show numeric value`, label: `Show numeric value`,
documentation: `the numeric field value`, documentation: `the numeric field value`,
origin: VariableOrigin.Fields, origin: VariableOrigin.Fields,
}); });
suggestions.push({ suggestions.push({
value: `__data.fields[${numeric.name}].text`, value: `__data.fields${buildLabelPath(numeric.name)}.text`,
label: `Show text value`, label: `Show text value`,
documentation: `the text value`, documentation: `the text value`,
origin: VariableOrigin.Fields, origin: VariableOrigin.Fields,
...@@ -183,7 +186,7 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => { ...@@ -183,7 +186,7 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => {
if (title) { if (title) {
suggestions.push({ suggestions.push({
value: `__data.fields[${title.config.displayName}]`, value: `__data.fields${buildLabelPath(title.name)}`,
label: `Select by title`, label: `Select by title`,
documentation: `Use the title to pick the field`, documentation: `Use the title to pick the field`,
origin: VariableOrigin.Fields, origin: VariableOrigin.Fields,
......
import { LinkSrv } from '../link_srv'; import { advanceTo } from 'jest-date-mock';
import { DataLinkBuiltInVars, locationUtil, VariableModel } from '@grafana/data'; 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 { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { advanceTo } from 'jest-date-mock';
import { updateConfig } from '../../../../core/config'; import { updateConfig } from '../../../../core/config';
import { variableAdapters } from '../../../variables/adapters'; import { variableAdapters } from '../../../variables/adapters';
import { createQueryVariableAdapter } from '../../../variables/query/adapter'; import { createQueryVariableAdapter } from '../../../variables/query/adapter';
...@@ -267,3 +275,212 @@ describe('linkSrv', () => { ...@@ -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,
},
]);
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment