123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- import { isArray, map, replace } from 'lodash';
- import { dateTime, Registry, RegistryItem, textUtil, VariableModel } from '@grafana/data';
- import kbn from 'app/core/utils/kbn';
- import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../variables/constants';
- import { formatVariableLabel } from '../variables/shared/formatVariable';
- export interface FormatOptions {
- value: any;
- text: string;
- args: string[];
- }
- export interface FormatRegistryItem extends RegistryItem {
- formatter(options: FormatOptions, variable: VariableModel): string;
- }
- export enum FormatRegistryID {
- lucene = 'lucene',
- raw = 'raw',
- regex = 'regex',
- pipe = 'pipe',
- distributed = 'distributed',
- csv = 'csv',
- html = 'html',
- json = 'json',
- percentEncode = 'percentencode',
- singleQuote = 'singlequote',
- doubleQuote = 'doublequote',
- sqlString = 'sqlstring',
- date = 'date',
- glob = 'glob',
- text = 'text',
- queryParam = 'queryparam',
- }
- export const formatRegistry = new Registry<FormatRegistryItem>(() => {
- const formats: FormatRegistryItem[] = [
- {
- id: FormatRegistryID.lucene,
- name: 'Lucene',
- description: 'Values are lucene escaped and multi-valued variables generate an OR expression',
- formatter: ({ value }) => {
- if (typeof value === 'string') {
- return luceneEscape(value);
- }
- if (value instanceof Array && value.length === 0) {
- return '__empty__';
- }
- const quotedValues = map(value, (val: string) => {
- return '"' + luceneEscape(val) + '"';
- });
- return '(' + quotedValues.join(' OR ') + ')';
- },
- },
- {
- id: FormatRegistryID.raw,
- name: 'raw',
- description: 'Keep value as is',
- formatter: ({ value }) => value,
- },
- {
- id: FormatRegistryID.regex,
- name: 'Regex',
- description: 'Values are regex escaped and multi-valued variables generate a (<value>|<value>) expression',
- formatter: ({ value }) => {
- if (typeof value === 'string') {
- return kbn.regexEscape(value);
- }
- const escapedValues = map(value, kbn.regexEscape);
- if (escapedValues.length === 1) {
- return escapedValues[0];
- }
- return '(' + escapedValues.join('|') + ')';
- },
- },
- {
- id: FormatRegistryID.pipe,
- name: 'Pipe',
- description: 'Values are separated by | character',
- formatter: ({ value }) => {
- if (typeof value === 'string') {
- return value;
- }
- return value.join('|');
- },
- },
- {
- id: FormatRegistryID.distributed,
- name: 'Distributed',
- description: 'Multiple values are formatted like variable=value',
- formatter: ({ value }, variable) => {
- if (typeof value === 'string') {
- return value;
- }
- value = map(value, (val: any, index: number) => {
- if (index !== 0) {
- return variable.name + '=' + val;
- } else {
- return val;
- }
- });
- return value.join(',');
- },
- },
- {
- id: FormatRegistryID.csv,
- name: 'Csv',
- description: 'Comma-separated values',
- formatter: ({ value }) => {
- if (isArray(value)) {
- return value.join(',');
- }
- return value;
- },
- },
- {
- id: FormatRegistryID.html,
- name: 'HTML',
- description: 'HTML escaping of values',
- formatter: ({ value }) => {
- if (isArray(value)) {
- return textUtil.escapeHtml(value.join(', '));
- }
- return textUtil.escapeHtml(value);
- },
- },
- {
- id: FormatRegistryID.json,
- name: 'JSON',
- description: 'JSON stringify valu',
- formatter: ({ value }) => {
- return JSON.stringify(value);
- },
- },
- {
- id: FormatRegistryID.percentEncode,
- name: 'Percent encode',
- description: 'Useful for URL escaping values',
- formatter: ({ value }) => {
- // like glob, but url escaped
- if (isArray(value)) {
- return encodeURIComponentStrict('{' + value.join(',') + '}');
- }
- return encodeURIComponentStrict(value);
- },
- },
- {
- id: FormatRegistryID.singleQuote,
- name: 'Single quote',
- description: 'Single quoted values',
- formatter: ({ value }) => {
- // escape single quotes with backslash
- const regExp = new RegExp(`'`, 'g');
- if (isArray(value)) {
- return map(value, (v: string) => `'${replace(v, regExp, `\\'`)}'`).join(',');
- }
- return `'${replace(value, regExp, `\\'`)}'`;
- },
- },
- {
- id: FormatRegistryID.doubleQuote,
- name: 'Double quote',
- description: 'Double quoted values',
- formatter: ({ value }) => {
- // escape double quotes with backslash
- const regExp = new RegExp('"', 'g');
- if (isArray(value)) {
- return map(value, (v: string) => `"${replace(v, regExp, '\\"')}"`).join(',');
- }
- return `"${replace(value, regExp, '\\"')}"`;
- },
- },
- {
- id: FormatRegistryID.sqlString,
- name: 'SQL string',
- description: 'SQL string quoting and commas for use in IN statements and other scenarios',
- formatter: ({ value }) => {
- // escape single quotes by pairing them
- const regExp = new RegExp(`'`, 'g');
- if (isArray(value)) {
- return map(value, (v) => `'${replace(v, regExp, "''")}'`).join(',');
- }
- return `'${replace(value, regExp, "''")}'`;
- },
- },
- {
- id: FormatRegistryID.date,
- name: 'Date',
- description: 'Format date in different ways',
- formatter: ({ value, args }) => {
- const arg = args[0] ?? 'iso';
- switch (arg) {
- case 'ms':
- return value;
- case 'seconds':
- return `${Math.round(parseInt(value, 10)! / 1000)}`;
- case 'iso':
- return dateTime(parseInt(value, 10)).toISOString();
- default:
- return dateTime(parseInt(value, 10)).format(arg);
- }
- },
- },
- {
- id: FormatRegistryID.glob,
- name: 'Glob',
- description: 'Format multi-valued variables using glob syntax, example {value1,value2}',
- formatter: ({ value }) => {
- if (isArray(value) && value.length > 1) {
- return '{' + value.join(',') + '}';
- }
- return value;
- },
- },
- {
- id: FormatRegistryID.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.value === ALL_VARIABLE_VALUE ? ALL_VARIABLE_TEXT : options.text;
- }
- const current = (variable as any)?.current;
- if (!current) {
- return options.value;
- }
- return formatVariableLabel(variable);
- },
- },
- {
- id: FormatRegistryID.queryParam,
- name: 'Query parameter',
- description:
- 'Format variables as URL parameters. Example in multi-variable scenario A + B + C => var-foo=A&var-foo=B&var-foo=C.',
- formatter: (options, variable) => {
- const { value } = options;
- const { name } = variable;
- if (Array.isArray(value)) {
- return value.map((v) => formatQueryParameter(name, v)).join('&');
- }
- return formatQueryParameter(name, value);
- },
- },
- ];
- return formats;
- });
- function luceneEscape(value: string) {
- return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
- }
- /**
- * encode string according to RFC 3986; in contrast to encodeURIComponent()
- * also the sub-delims "!", "'", "(", ")" and "*" are encoded;
- * unicode handling uses UTF-8 as in ECMA-262.
- */
- function encodeURIComponentStrict(str: string) {
- return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
- 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);
- }
|