Commit 0c8390ce by Marcus Andersson Committed by GitHub

Templating: global/system variables should be properly replaced in templated values. (#27394)

* Fixed so we try to use the variables in the redux store to replace values in template variables.

* First draft of working version.

* Including fieldPath when adding :text format.

* cleaned up code by introducing helper function.

* some minor refactoring.

* Added tests and support for multi variables.

* added test and code to handle the All scenario of a multivariable.

* fixed according to feedback.

* added docs.

* added text format to gdev dashboard.

* updated e2e tests.

* make sure we use the same function for formatting och variable lable.

* increased the number to 22.

* changed label for tests to be All.

* existing format should be respected.
parent b3b72b8a
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
}, },
"id": 11, "id": 11,
"options": { "options": {
"content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n\n", "content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n* `Server:text` = `${Server:text}`\n\n",
"mode": "markdown" "mode": "markdown"
}, },
"pluginVersion": "7.1.0", "pluginVersion": "7.1.0",
......
...@@ -143,3 +143,13 @@ servers = ["test'1", "test2"] ...@@ -143,3 +143,13 @@ servers = ["test'1", "test2"]
String to interpolate: '${servers:sqlstring}' String to interpolate: '${servers:sqlstring}'
Interpolation result: "'test''1','test2'" Interpolation result: "'test''1','test2'"
``` ```
## Text
Formats single- and multi-valued variables into their text representation. For a single variable it will just return the text representation. For multi-valued variables it will return the text representation combined with `+`.
```bash
servers = ["test1", "test2"]
String to interpolate: '${servers:text}'
Interpolation result: "test1 + test2"
```
\ No newline at end of file
...@@ -33,11 +33,12 @@ e2e.scenario({ ...@@ -33,11 +33,12 @@ e2e.scenario({
`Server:doublequote = "A'A\\"A","BB\\B","CCC"`, `Server:doublequote = "A'A\\"A","BB\\B","CCC"`,
`Server:sqlstring = 'A''A"A','BB\\\B','CCC'`, `Server:sqlstring = 'A''A"A','BB\\\B','CCC'`,
`Server:date = null`, `Server:date = null`,
`Server:text = All`,
]; ];
e2e() e2e()
.get('.markdown-html li') .get('.markdown-html li')
.should('have.length', 21) .should('have.length', 22)
.each(element => { .each(element => {
items.push(element.text()); items.push(element.text());
}) })
......
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { Registry, RegistryItem, VariableModel, textUtil, dateTime } from '@grafana/data'; import { Registry, RegistryItem, VariableModel, textUtil, dateTime } from '@grafana/data';
import { map, isArray, replace } from 'lodash'; import { map, isArray, replace } from 'lodash';
import { formatVariableLabel } from '../variables/shared/formatVariable';
export interface FormatOptions {
value: any;
text: string;
args: string[];
}
export interface FormatRegistryItem extends RegistryItem { export interface FormatRegistryItem extends RegistryItem {
formatter(value: any, args: string[], variable: VariableModel): string; formatter(options: FormatOptions, variable: VariableModel): string;
} }
export const formatRegistry = new Registry<FormatRegistryItem>(() => { export const formatRegistry = new Registry<FormatRegistryItem>(() => {
...@@ -12,7 +19,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -12,7 +19,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'lucene', id: 'lucene',
name: 'Lucene', name: 'Lucene',
description: 'Values are lucene escaped and multi-valued variables generate an OR expression', description: 'Values are lucene escaped and multi-valued variables generate an OR expression',
formatter: value => { formatter: ({ value }) => {
if (typeof value === 'string') { if (typeof value === 'string') {
return luceneEscape(value); return luceneEscape(value);
} }
...@@ -32,13 +39,13 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -32,13 +39,13 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'raw', id: 'raw',
name: 'raw', name: 'raw',
description: 'Keep value as is', description: 'Keep value as is',
formatter: value => value, formatter: ({ value }) => value,
}, },
{ {
id: 'regex', id: 'regex',
name: 'Regex', name: 'Regex',
description: 'Values are regex escaped and multi-valued variables generate a (<value>|<value>) expression', description: 'Values are regex escaped and multi-valued variables generate a (<value>|<value>) expression',
formatter: value => { formatter: ({ value }) => {
if (typeof value === 'string') { if (typeof value === 'string') {
return kbn.regexEscape(value); return kbn.regexEscape(value);
} }
...@@ -54,7 +61,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -54,7 +61,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'pipe', id: 'pipe',
name: 'Pipe', name: 'Pipe',
description: 'Values are seperated by | character', description: 'Values are seperated by | character',
formatter: value => { formatter: ({ value }) => {
if (typeof value === 'string') { if (typeof value === 'string') {
return value; return value;
} }
...@@ -65,7 +72,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -65,7 +72,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'distributed', id: 'distributed',
name: 'Distributed', name: 'Distributed',
description: 'Multiple values are formatted like variable=value', description: 'Multiple values are formatted like variable=value',
formatter: (value, args, variable) => { formatter: ({ value }, variable) => {
if (typeof value === 'string') { if (typeof value === 'string') {
return value; return value;
} }
...@@ -84,7 +91,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -84,7 +91,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'csv', id: 'csv',
name: 'Csv', name: 'Csv',
description: 'Comma seperated values', description: 'Comma seperated values',
formatter: (value, args, variable) => { formatter: ({ value }) => {
if (isArray(value)) { if (isArray(value)) {
return value.join(','); return value.join(',');
} }
...@@ -95,7 +102,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -95,7 +102,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'html', id: 'html',
name: 'HTML', name: 'HTML',
description: 'HTML escaping of values', description: 'HTML escaping of values',
formatter: (value, args, variable) => { formatter: ({ value }) => {
if (isArray(value)) { if (isArray(value)) {
return textUtil.escapeHtml(value.join(', ')); return textUtil.escapeHtml(value.join(', '));
} }
...@@ -106,7 +113,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -106,7 +113,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'json', id: 'json',
name: 'JSON', name: 'JSON',
description: 'JSON stringify valu', description: 'JSON stringify valu',
formatter: (value, args, variable) => { formatter: ({ value }) => {
return JSON.stringify(value); return JSON.stringify(value);
}, },
}, },
...@@ -114,7 +121,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -114,7 +121,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'percentencode', id: 'percentencode',
name: 'Percent encode', name: 'Percent encode',
description: 'Useful for url escaping values', description: 'Useful for url escaping values',
formatter: (value, args, variable) => { formatter: ({ value }) => {
// like glob, but url escaped // like glob, but url escaped
if (isArray(value)) { if (isArray(value)) {
return encodeURIComponentStrict('{' + value.join(',') + '}'); return encodeURIComponentStrict('{' + value.join(',') + '}');
...@@ -126,7 +133,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -126,7 +133,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'singlequote', id: 'singlequote',
name: 'Single quote', name: 'Single quote',
description: 'Single quoted values', description: 'Single quoted values',
formatter: (value, args, variable) => { formatter: ({ value }) => {
// escape single quotes with backslash // escape single quotes with backslash
const regExp = new RegExp(`'`, 'g'); const regExp = new RegExp(`'`, 'g');
if (isArray(value)) { if (isArray(value)) {
...@@ -139,7 +146,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -139,7 +146,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'doublequote', id: 'doublequote',
name: 'Double quote', name: 'Double quote',
description: 'Double quoted values', description: 'Double quoted values',
formatter: (value, args, variable) => { formatter: ({ value }) => {
// escape double quotes with backslash // escape double quotes with backslash
const regExp = new RegExp('"', 'g'); const regExp = new RegExp('"', 'g');
if (isArray(value)) { if (isArray(value)) {
...@@ -152,7 +159,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -152,7 +159,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'sqlstring', id: 'sqlstring',
name: 'SQL string', name: 'SQL string',
description: 'SQL string quoting and commas for use in IN statements and other scenarios', description: 'SQL string quoting and commas for use in IN statements and other scenarios',
formatter: (value, args, variable) => { formatter: ({ value }) => {
// escape single quotes by pairing them // escape single quotes by pairing them
const regExp = new RegExp(`'`, 'g'); const regExp = new RegExp(`'`, 'g');
if (isArray(value)) { if (isArray(value)) {
...@@ -165,7 +172,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -165,7 +172,7 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'date', id: 'date',
name: 'Date', name: 'Date',
description: 'Format date in different ways', description: 'Format date in different ways',
formatter: (value, args, variable) => { formatter: ({ value, args }) => {
const arg = args[0] ?? 'iso'; const arg = args[0] ?? 'iso';
switch (arg) { switch (arg) {
...@@ -184,13 +191,31 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => { ...@@ -184,13 +191,31 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
id: 'glob', id: 'glob',
name: 'Glob', name: 'Glob',
description: 'Format multi valued variables using glob syntax, example {value1,value2}', description: 'Format multi valued variables using glob syntax, example {value1,value2}',
formatter: (value, args, variable) => { formatter: ({ value }) => {
if (isArray(value) && value.length > 1) { if (isArray(value) && value.length > 1) {
return '{' + value.join(',') + '}'; return '{' + value.join(',') + '}';
} }
return value; return value;
}, },
}, },
{
id: 'text',
name: 'Text',
description: 'Format variables in their text representation. Example in multi variable scenario A + B + C.',
formatter: (options, variable) => {
if (typeof options.text === 'string') {
return options.text;
}
const current = (variable as any)?.current;
if (!current) {
return options.value;
}
return formatVariableLabel(variable);
},
},
]; ];
return formats; return formats;
......
...@@ -95,11 +95,18 @@ describe('templateSrv', () => { ...@@ -95,11 +95,18 @@ describe('templateSrv', () => {
expect(target).toBe('this.asd.filters'); expect(target).toBe('this.asd.filters');
}); });
it('should replace ${test:glob} with scoped text', () => { it('should replace ${test.name} with scoped text', () => {
const target = _templateSrv.replaceWithText('this.${test.name}.filters', {
test: { value: { name: 'mupp' }, text: 'asd' },
});
expect(target).toBe('this.mupp.filters');
});
it('should not replace ${test:glob} with scoped text', () => {
const target = _templateSrv.replaceWithText('this.${test:glob}.filters', { const target = _templateSrv.replaceWithText('this.${test:glob}.filters', {
test: { value: 'mupp', text: 'asd' }, test: { value: 'mupp', text: 'asd' },
}); });
expect(target).toBe('this.asd.filters'); expect(target).toBe('this.mupp.filters');
}); });
}); });
...@@ -595,6 +602,45 @@ describe('templateSrv', () => { ...@@ -595,6 +602,45 @@ describe('templateSrv', () => {
}); });
}); });
describe('replaceWithText can pass all / multi value', () => {
beforeEach(() => {
initTemplateSrv([
{
type: 'query',
name: 'server',
current: { value: ['server1', 'server2'], text: ['Server 1', 'Server 2'] },
},
{
type: 'textbox',
name: 'empty_on_init',
current: { value: '', text: '' },
},
{
type: 'query',
name: 'databases',
current: { value: '$__all', text: '' },
options: [{ value: '$__all' }, { value: 'db1', text: 'Database 1' }, { value: 'db2', text: 'Database 2' }],
},
]);
_templateSrv.updateIndex();
});
it('should replace with text with variable label', () => {
const target = _templateSrv.replaceWithText('Server: $server');
expect(target).toBe('Server: Server 1 + Server 2');
});
it('should replace empty string-values with an empty string', () => {
const target = _templateSrv.replaceWithText('Hello $empty_on_init');
expect(target).toBe('Hello ');
});
it('should replace $__all with All', () => {
const target = _templateSrv.replaceWithText('Db: $databases');
expect(target).toBe('Db: All');
});
});
describe('built in interval variables', () => { describe('built in interval variables', () => {
beforeEach(() => { beforeEach(() => {
initTemplateSrv([]); initTemplateSrv([]);
......
...@@ -6,7 +6,8 @@ import { isAdHoc } from '../variables/guard'; ...@@ -6,7 +6,8 @@ import { isAdHoc } from '../variables/guard';
import { VariableModel } from '../variables/types'; import { VariableModel } from '../variables/types';
import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime'; import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime';
import { variableAdapters } from '../variables/adapters'; import { variableAdapters } from '../variables/adapters';
import { formatRegistry } from './formatRegistry'; import { formatRegistry, FormatOptions } from './formatRegistry';
import { ALL_VARIABLE_TEXT } from '../variables/state/types';
interface FieldAccessorCache { interface FieldAccessorCache {
[key: string]: (obj: any) => any; [key: string]: (obj: any) => any;
...@@ -107,7 +108,7 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -107,7 +108,7 @@ export class TemplateSrv implements BaseTemplateSrv {
return filters; return filters;
} }
formatValue(value: any, format: any, variable: any) { formatValue(value: any, format: any, variable: any, text?: string) {
// for some scopedVars there is no variable // for some scopedVars there is no variable
variable = variable || {}; variable = variable || {};
...@@ -133,7 +134,8 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -133,7 +134,8 @@ export class TemplateSrv implements BaseTemplateSrv {
throw new Error(`Variable format ${format} not found`); throw new Error(`Variable format ${format} not found`);
} }
return formatItem.formatter(value, args, variable); const options: FormatOptions = { value, args, text: text ?? value };
return formatItem.formatter(options, variable);
} }
setGrafanaVariable(name: string, value: any) { setGrafanaVariable(name: string, value: any) {
...@@ -197,7 +199,7 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -197,7 +199,7 @@ export class TemplateSrv implements BaseTemplateSrv {
return values; return values;
} }
getFieldAccessor(fieldPath: string) { private getFieldAccessor(fieldPath: string) {
const accessor = this.fieldAccessorCache[fieldPath]; const accessor = this.fieldAccessorCache[fieldPath];
if (accessor) { if (accessor) {
return accessor; return accessor;
...@@ -206,7 +208,7 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -206,7 +208,7 @@ export class TemplateSrv implements BaseTemplateSrv {
return (this.fieldAccessorCache[fieldPath] = _.property(fieldPath)); return (this.fieldAccessorCache[fieldPath] = _.property(fieldPath));
} }
getVariableValue(variableName: string, fieldPath: string | undefined, scopedVars: ScopedVars) { private getVariableValue(variableName: string, fieldPath: string | undefined, scopedVars: ScopedVars) {
const scopedVar = scopedVars[variableName]; const scopedVar = scopedVars[variableName];
if (!scopedVar) { if (!scopedVar) {
return null; return null;
...@@ -219,6 +221,20 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -219,6 +221,20 @@ export class TemplateSrv implements BaseTemplateSrv {
return scopedVar.value; return scopedVar.value;
} }
private getVariableText(variableName: string, value: any, scopedVars: ScopedVars) {
const scopedVar = scopedVars[variableName];
if (!scopedVar) {
return null;
}
if (scopedVar.value === value || typeof value !== 'string') {
return scopedVar.text;
}
return value;
}
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string { replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string {
if (!target) { if (!target) {
return target ?? ''; return target ?? '';
...@@ -233,8 +249,10 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -233,8 +249,10 @@ export class TemplateSrv implements BaseTemplateSrv {
if (scopedVars) { if (scopedVars) {
const value = this.getVariableValue(variableName, fieldPath, scopedVars); const value = this.getVariableValue(variableName, fieldPath, scopedVars);
const text = this.getVariableText(variableName, value, scopedVars);
if (value !== null && value !== undefined) { if (value !== null && value !== undefined) {
return this.formatValue(value, fmt, variable); return this.formatValue(value, fmt, variable, text);
} }
} }
...@@ -248,8 +266,11 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -248,8 +266,11 @@ export class TemplateSrv implements BaseTemplateSrv {
} }
let value = variable.current.value; let value = variable.current.value;
let text = variable.current.text;
if (this.isAllValue(value)) { if (this.isAllValue(value)) {
value = this.getAllValue(variable); value = this.getAllValue(variable);
text = ALL_VARIABLE_TEXT;
// skip formatting of custom all values // skip formatting of custom all values
if (variable.allValue) { if (variable.allValue) {
return this.replace(value); return this.replace(value);
...@@ -258,14 +279,14 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -258,14 +279,14 @@ export class TemplateSrv implements BaseTemplateSrv {
if (fieldPath) { if (fieldPath) {
const fieldValue = this.getVariableValue(variableName, fieldPath, { const fieldValue = this.getVariableValue(variableName, fieldPath, {
[variableName]: { value: value, text: '' }, [variableName]: { value, text },
}); });
if (fieldValue !== null && fieldValue !== undefined) { if (fieldValue !== null && fieldValue !== undefined) {
return this.formatValue(fieldValue, fmt, variable); return this.formatValue(fieldValue, fmt, variable, text);
} }
} }
const res = this.formatValue(value, fmt, variable); const res = this.formatValue(value, fmt, variable, text);
return res; return res;
}); });
} }
...@@ -275,30 +296,8 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -275,30 +296,8 @@ export class TemplateSrv implements BaseTemplateSrv {
} }
replaceWithText(target: string, scopedVars?: ScopedVars) { replaceWithText(target: string, scopedVars?: ScopedVars) {
if (!target) { deprecationWarning('template_srv.ts', 'replaceWithText()', 'replace(), and specify the :text format');
return target; return this.replace(target, scopedVars, 'text');
}
let variable;
this.regex.lastIndex = 0;
return target.replace(this.regex, (match: any, var1: any, var2: any, fmt2: any, var3: any) => {
if (scopedVars) {
const option = scopedVars[var1 || var2 || var3];
if (option) {
return option.text;
}
}
variable = this.getVariableAtIndex(var1 || var2 || var3);
if (!variable) {
return match;
}
const value = this.grafanaVariables[variable.current.value];
return typeof value === 'string' ? value : variable.current.text;
});
} }
fillVariableValuesForUrl = (params: any, scopedVars?: ScopedVars) => { fillVariableValuesForUrl = (params: any, scopedVars?: ScopedVars) => {
......
...@@ -10,6 +10,7 @@ import { VariableOption, VariableTag, VariableWithMultiSupport, VariableWithOpti ...@@ -10,6 +10,7 @@ import { VariableOption, VariableTag, VariableWithMultiSupport, VariableWithOpti
import { VariableOptions } from '../shared/VariableOptions'; import { VariableOptions } from '../shared/VariableOptions';
import { isQuery } from '../../guard'; import { isQuery } from '../../guard';
import { VariablePickerProps } from '../types'; import { VariablePickerProps } from '../types';
import { formatVariableLabel } from '../../shared/formatVariable';
interface OwnProps extends VariablePickerProps<VariableWithMultiSupport> {} interface OwnProps extends VariablePickerProps<VariableWithMultiSupport> {}
...@@ -64,7 +65,7 @@ export class OptionsPickerUnconnected extends PureComponent<Props> { ...@@ -64,7 +65,7 @@ export class OptionsPickerUnconnected extends PureComponent<Props> {
return null; return null;
} }
const linkText = getLinkText(variable); const linkText = formatVariableLabel(variable);
const tags = getSelectedTags(variable); const tags = getSelectedTags(variable);
return <VariableLink text={linkText} tags={tags} onClick={this.onShowOptions} />; return <VariableLink text={linkText} tags={tags} onClick={this.onShowOptions} />;
...@@ -104,44 +105,6 @@ const getSelectedTags = (variable: VariableWithOptions): VariableTag[] => { ...@@ -104,44 +105,6 @@ const getSelectedTags = (variable: VariableWithOptions): VariableTag[] => {
return variable.tags.filter(t => t.selected); return variable.tags.filter(t => t.selected);
}; };
const getLinkText = (variable: VariableWithOptions) => {
const { current, options } = variable;
if (!current.tags || current.tags.length === 0) {
if (Array.isArray(current.text)) {
return current.text.join(' + ');
}
return current.text;
}
// filer out values that are in selected tags
const selectedAndNotInTag = options.filter(option => {
if (!option.selected) {
return false;
}
if (!current || !current.tags || !current.tags.length) {
return false;
}
for (let i = 0; i < current.tags.length; i++) {
const tag = current.tags[i];
const foundIndex = tag?.values?.findIndex(v => v === option.value);
if (foundIndex && foundIndex !== -1) {
return false;
}
}
return true;
});
// convert values to text
const currentTexts = selectedAndNotInTag.map(s => s.text);
// join texts
const newLinkText = currentTexts.join(' + ');
return newLinkText.length > 0 ? `${newLinkText} + ` : newLinkText;
};
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
showOptions, showOptions,
commitChangesToVariable, commitChangesToVariable,
......
import { VariableModel } from '@grafana/data';
import { VariableWithOptions } from '../types';
export const formatVariableLabel = (variable: VariableModel) => {
if (!isVariableWithOptions(variable)) {
return variable.name;
}
const { current, options = [] } = variable;
if (!current.tags || current.tags.length === 0) {
if (Array.isArray(current.text)) {
return current.text.join(' + ');
}
return current.text;
}
// filer out values that are in selected tags
const selectedAndNotInTag = options.filter(option => {
if (!option.selected) {
return false;
}
if (!current || !current.tags || !current.tags.length) {
return false;
}
for (let i = 0; i < current.tags.length; i++) {
const tag = current.tags[i];
const foundIndex = tag?.values?.findIndex(v => v === option.value);
if (foundIndex && foundIndex !== -1) {
return false;
}
}
return true;
});
// convert values to text
const currentTexts = selectedAndNotInTag.map(s => s.text);
// join texts
const newLinkText = currentTexts.join(' + ');
return newLinkText.length > 0 ? `${newLinkText} + ` : newLinkText;
};
const isVariableWithOptions = (variable: VariableModel): variable is VariableWithOptions => {
return (
Array.isArray((variable as VariableWithOptions)?.options) ||
typeof (variable as VariableWithOptions)?.current === 'object'
);
};
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