mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
parent
8475bd2fae
commit
e86ff52d44
@ -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 = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
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[]) => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
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[]) => {
|
|||||||
|
|
||||||
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[]) => {
|
|||||||
|
|
||||||
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,
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
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', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user