mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Variables: Adds queryparam formatting option (#30858)
* Variables: Adds queryparam formatting option * Chore: fixes strict errors * Chore: changes after PR comments
This commit is contained in:
parent
95efd3e51d
commit
2a3aa95163
@ -27,14 +27,14 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 16,
|
||||
"h": 18,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 11,
|
||||
"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* `__user.email` = `${__user.email}`\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",
|
||||
"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* `__user.email` = `${__user.email}`\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* `Server:queryparam` = `${Server:queryparam}`\n\n",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "7.1.0",
|
||||
|
@ -49,6 +49,6 @@ Value-specific variables are available under ``__value`` namespace:
|
||||
|
||||
When linking to another dashboard that uses template variables, select variable values for whoever clicks the link.
|
||||
|
||||
``var-myvar=${myvar}`` - where ``myvar`` is a name of the template variable that matches one in the current dashboard that you want to use.
|
||||
``${myvar:queryparams}`` - where ``myvar`` is a name of the template variable that matches one in the current dashboard that you want to use.
|
||||
|
||||
If you want to add all of the current dashboard's variables to the URL, then use ``__all_variables``.
|
||||
|
@ -149,3 +149,13 @@ servers = ["test1", "test2"]
|
||||
String to interpolate: '${servers:text}'
|
||||
Interpolation result: "test1 + test2"
|
||||
```
|
||||
|
||||
## Query parameters
|
||||
|
||||
Formats single- and multi-valued variables into their query parameter representation. Example: `var-foo=value1&var-foo=value2`
|
||||
|
||||
```bash
|
||||
servers = ["test1", "test2"]
|
||||
String to interpolate: '${servers:queryparam}'
|
||||
Interpolation result: "var-servers=test1&var-servers=test2"
|
||||
```
|
||||
|
@ -35,11 +35,12 @@ e2e.scenario({
|
||||
`Server:sqlstring = 'A''A"A','BB\\\B','CCC'`,
|
||||
`Server:date = null`,
|
||||
`Server:text = All`,
|
||||
`Server:queryparam = var-Server=All`,
|
||||
];
|
||||
|
||||
e2e()
|
||||
.get('.markdown-html li')
|
||||
.should('have.length', 23)
|
||||
.should('have.length', 24)
|
||||
.each((element) => {
|
||||
items.push(element.text());
|
||||
})
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useState, useMemo, useContext, useRef, RefObject, memo, useEffect } from 'react';
|
||||
import React, { memo, RefObject, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { DataLinkSuggestions } from './DataLinkSuggestions';
|
||||
import { ThemeContext, makeValue } from '../../index';
|
||||
import { makeValue, ThemeContext } from '../../index';
|
||||
import { SelectionReference } from './SelectionReference';
|
||||
import { Portal, getFormStyles } from '../index';
|
||||
import { getFormStyles, Portal } from '../index';
|
||||
|
||||
// @ts-ignore
|
||||
import Prism, { Grammar, LanguageMap } from 'prismjs';
|
||||
@ -16,7 +16,7 @@ import { css, cx } from 'emotion';
|
||||
import { SlatePrism } from '../../slate-plugins';
|
||||
import { SCHEMA } from '../../utils/slate';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { GrafanaTheme, VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data';
|
||||
import { DataLinkBuiltInVars, GrafanaTheme, VariableOrigin, VariableSuggestion } from '@grafana/data';
|
||||
|
||||
const modulo = (a: number, n: number) => a - n * Math.floor(a / n);
|
||||
|
||||
@ -130,7 +130,7 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = memo(
|
||||
if (item.origin !== VariableOrigin.Template || item.value === DataLinkBuiltInVars.includeVars) {
|
||||
editor.insertText(`${includeDollarSign ? '$' : ''}\{${item.value}}`);
|
||||
} else {
|
||||
editor.insertText(`var-${item.value}=$\{${item.value}}`);
|
||||
editor.insertText(`\${${item.value}:queryparam}`);
|
||||
}
|
||||
|
||||
setLinkUrl(editor.value);
|
||||
|
@ -3,6 +3,8 @@ import { dateTime, Registry, RegistryItem, textUtil, VariableModel } from '@graf
|
||||
import { isArray, map, replace } from 'lodash';
|
||||
import { formatVariableLabel } from '../variables/shared/formatVariable';
|
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../variables/state/types';
|
||||
import { variableAdapters } from '../variables/adapters';
|
||||
import { VariableModel as ExtendedVariableModel } from '../variables/types';
|
||||
|
||||
export interface FormatOptions {
|
||||
value: any;
|
||||
@ -217,6 +219,23 @@ export const formatRegistry = new Registry<FormatRegistryItem>(() => {
|
||||
return formatVariableLabel(variable);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'queryparam',
|
||||
name: 'Query Parameter',
|
||||
description:
|
||||
'Format variables as url parameter. Example in multi variable scenario A + B + C => var-foo=A&var-foo=B&var-foo=C.',
|
||||
formatter: (options, variable) => {
|
||||
const { name, type } = variable;
|
||||
const adapter = variableAdapters.get(type);
|
||||
const valueForUrl = adapter.getValueForUrl(variable as ExtendedVariableModel);
|
||||
|
||||
if (Array.isArray(valueForUrl)) {
|
||||
return valueForUrl.map((v) => formatQueryParameter(name, v)).join('&');
|
||||
}
|
||||
|
||||
return formatQueryParameter(name, valueForUrl);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return formats;
|
||||
@ -236,3 +255,11 @@ function encodeURIComponentStrict(str: string) {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function formatQueryParameter(name: string, value: string): string {
|
||||
return `var-${name}=${encodeURIComponentStrict(value)}`;
|
||||
}
|
||||
|
||||
export function isAllValue(value: any) {
|
||||
return value === ALL_VARIABLE_VALUE || (Array.isArray(value) && value[0] === ALL_VARIABLE_VALUE);
|
||||
}
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { dateTime, TimeRange } from '@grafana/data';
|
||||
import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv';
|
||||
import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
|
||||
import { VariableAdapter, variableAdapters } from '../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../variables/query/adapter';
|
||||
import { createAdHocVariableAdapter } from '../variables/adhoc/adapter';
|
||||
import { VariableModel } from '../variables/types';
|
||||
|
||||
variableAdapters.setInit(() => [
|
||||
(createQueryVariableAdapter() as unknown) as VariableAdapter<VariableModel>,
|
||||
(createAdHocVariableAdapter() as unknown) as VariableAdapter<VariableModel>,
|
||||
]);
|
||||
|
||||
describe('templateSrv', () => {
|
||||
silenceConsoleOutput();
|
||||
@ -225,6 +234,11 @@ describe('templateSrv', () => {
|
||||
const target = _templateSrv.replace('${test:pipe},$test', {}, 'glob');
|
||||
expect(target).toBe('value1|value2,{value1,value2}');
|
||||
});
|
||||
|
||||
it('should replace ${test:queryparam} with correct query parameter', () => {
|
||||
const target = _templateSrv.replace('${test:queryparam}', {});
|
||||
expect(target).toBe('var-test=All');
|
||||
});
|
||||
});
|
||||
|
||||
describe('variable with all option and custom value', () => {
|
||||
@ -264,6 +278,11 @@ describe('templateSrv', () => {
|
||||
const target = _templateSrv.replace('this.$test', {}, 'regex');
|
||||
expect(target).toBe('this.*');
|
||||
});
|
||||
|
||||
it('should replace ${test:queryparam} with correct query parameter', () => {
|
||||
const target = _templateSrv.replace('${test:queryparam}', {});
|
||||
expect(target).toBe('var-test=All');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lucene format', () => {
|
||||
@ -640,4 +659,43 @@ describe('templateSrv', () => {
|
||||
expect(passedValue).toBe('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryparam', () => {
|
||||
beforeEach(() => {
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'single',
|
||||
current: { value: 'value1' },
|
||||
options: [{ value: 'value1' }, { value: 'value2' }],
|
||||
},
|
||||
{
|
||||
type: 'query',
|
||||
name: 'multi',
|
||||
current: { value: ['value1', 'value2'] },
|
||||
options: [{ value: 'value1' }, { value: 'value2' }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('query variable with single value with queryparam format should return correct queryparam', () => {
|
||||
const target = _templateSrv.replace('${single:queryparam}', {});
|
||||
expect(target).toBe('var-single=value1');
|
||||
});
|
||||
|
||||
it('query variable with single value and queryparam format should return correct queryparam', () => {
|
||||
const target = _templateSrv.replace('${single}', {}, 'queryparam');
|
||||
expect(target).toBe('var-single=value1');
|
||||
});
|
||||
|
||||
it('query variable with multi value with queryparam format should return correct queryparam', () => {
|
||||
const target = _templateSrv.replace('${multi:queryparam}', {});
|
||||
expect(target).toBe('var-multi=value1&var-multi=value2');
|
||||
});
|
||||
|
||||
it('query variable with multi value and queryparam format should return correct queryparam', () => {
|
||||
const target = _templateSrv.replace('${multi}', {}, 'queryparam');
|
||||
expect(target).toBe('var-multi=value1&var-multi=value2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -282,7 +282,7 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
value = this.getAllValue(variable);
|
||||
text = ALL_VARIABLE_TEXT;
|
||||
// skip formatting of custom all values
|
||||
if (variable.allValue && fmt !== 'text') {
|
||||
if (variable.allValue && fmt !== 'text' && fmt !== 'queryparam') {
|
||||
return this.replace(value);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
QueryEditorProps,
|
||||
StandardVariableQuery,
|
||||
StandardVariableSupport,
|
||||
VariableModel,
|
||||
VariableSupportType,
|
||||
} from '@grafana/data';
|
||||
|
||||
@ -18,7 +19,6 @@ import {
|
||||
AdHocVariableModel,
|
||||
ConstantVariableModel,
|
||||
QueryVariableModel,
|
||||
VariableModel,
|
||||
VariableQueryEditorType,
|
||||
VariableWithMultiSupport,
|
||||
VariableWithOptions,
|
||||
|
@ -3,23 +3,42 @@ import { VariableRefresh } from './types';
|
||||
|
||||
describe('isAllVariable', () => {
|
||||
it.each`
|
||||
variable | expected
|
||||
${null} | ${false}
|
||||
${undefined} | ${false}
|
||||
${{}} | ${false}
|
||||
${{ current: {} }} | ${false}
|
||||
${{ current: { text: '' } }} | ${false}
|
||||
${{ current: { text: null } }} | ${false}
|
||||
${{ current: { text: undefined } }} | ${false}
|
||||
${{ current: { text: 'Alll' } }} | ${false}
|
||||
${{ current: { text: 'All' } }} | ${true}
|
||||
${{ current: { text: [] } }} | ${false}
|
||||
${{ current: { text: [null] } }} | ${false}
|
||||
${{ current: { text: [undefined] } }} | ${false}
|
||||
${{ current: { text: ['Alll'] } }} | ${false}
|
||||
${{ current: { text: ['Alll', 'All'] } }} | ${false}
|
||||
${{ current: { text: ['All'] } }} | ${true}
|
||||
${{ current: { text: { prop1: 'test' } } }} | ${false}
|
||||
variable | expected
|
||||
${null} | ${false}
|
||||
${undefined} | ${false}
|
||||
${{}} | ${false}
|
||||
${{ current: {} }} | ${false}
|
||||
${{ current: { text: '' } }} | ${false}
|
||||
${{ current: { text: null } }} | ${false}
|
||||
${{ current: { text: undefined } }} | ${false}
|
||||
${{ current: { text: 'Alll' } }} | ${false}
|
||||
${{ current: { text: 'All' } }} | ${true}
|
||||
${{ current: { text: [] } }} | ${false}
|
||||
${{ current: { text: [null] } }} | ${false}
|
||||
${{ current: { text: [undefined] } }} | ${false}
|
||||
${{ current: { text: ['Alll'] } }} | ${false}
|
||||
${{ current: { text: ['Alll', 'All'] } }} | ${false}
|
||||
${{ current: { text: ['All'] } }} | ${true}
|
||||
${{ current: { text: ['All', 'Alll'] } }} | ${true}
|
||||
${{ current: { text: { prop1: 'test' } } }} | ${false}
|
||||
${{ current: { value: '' } }} | ${false}
|
||||
${{ current: { value: null } }} | ${false}
|
||||
${{ current: { value: undefined } }} | ${false}
|
||||
${{ current: { value: '$__alll' } }} | ${false}
|
||||
${{ current: { value: '$__all' } }} | ${true}
|
||||
${{ current: { value: [] } }} | ${false}
|
||||
${{ current: { value: [null] } }} | ${false}
|
||||
${{ current: { value: [undefined] } }} | ${false}
|
||||
${{ current: { value: ['$__alll'] } }} | ${false}
|
||||
${{ current: { value: ['$__alll', '$__all'] } }} | ${false}
|
||||
${{ current: { value: ['$__all'] } }} | ${true}
|
||||
${{ current: { value: ['$__all', '$__alll'] } }} | ${true}
|
||||
${{ current: { value: { prop1: 'test' } } }} | ${false}
|
||||
${{ current: { value: '', text: '' } }} | ${false}
|
||||
${{ current: { value: '', text: 'All' } }} | ${true}
|
||||
${{ current: { value: '$__all', text: '' } }} | ${true}
|
||||
${{ current: { value: '', text: ['All'] } }} | ${true}
|
||||
${{ current: { value: ['$__all'], text: '' } }} | ${true}
|
||||
`("when called with params: 'variable': '$variable' then result should be '$expected'", ({ variable, expected }) => {
|
||||
expect(isAllVariable(variable)).toEqual(expected);
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import isString from 'lodash/isString';
|
||||
import { ScopedVars, VariableType } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { ALL_VARIABLE_TEXT } from './state/types';
|
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from './state/types';
|
||||
import { QueryVariableModel, VariableModel, VariableRefresh } from './types';
|
||||
import { getTimeSrv } from '../dashboard/services/TimeSrv';
|
||||
import { variableAdapters } from './adapters';
|
||||
@ -74,15 +74,29 @@ export const isAllVariable = (variable: any): boolean => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!variable.current.text) {
|
||||
return false;
|
||||
if (variable.current.value) {
|
||||
const isArray = Array.isArray(variable.current.value);
|
||||
if (isArray && variable.current.value.length && variable.current.value[0] === ALL_VARIABLE_VALUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isArray && variable.current.value === ALL_VARIABLE_VALUE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(variable.current.text)) {
|
||||
return variable.current.text.length ? variable.current.text[0] === ALL_VARIABLE_TEXT : false;
|
||||
if (variable.current.text) {
|
||||
const isArray = Array.isArray(variable.current.text);
|
||||
if (isArray && variable.current.text.length && variable.current.text[0] === ALL_VARIABLE_TEXT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isArray && variable.current.text === ALL_VARIABLE_TEXT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return variable.current.text === ALL_VARIABLE_TEXT;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getCurrentText = (variable: any): string => {
|
||||
|
Loading…
Reference in New Issue
Block a user