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';
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<true> = {};
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,
......
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,
},
]);
});
});
});
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