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 {
|
||||
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,
|
||||
|
@ -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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user