Commit 2a3aa951 by Hugo Häggmark Committed by GitHub

Variables: Adds queryparam formatting option (#30858)

* Variables: Adds queryparam formatting option

* Chore: fixes strict errors

* Chore: changes after PR comments
parent 95efd3e5
......@@ -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());
})
......
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);
}
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 => {
......
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