diff --git a/public/app/features/variables/query/reducer.test.ts b/public/app/features/variables/query/reducer.test.ts index 73efcf43cf0..fd061ddba3c 100644 --- a/public/app/features/variables/query/reducer.test.ts +++ b/public/app/features/variables/query/reducer.test.ts @@ -1,6 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { - getAllMatches, + metricNamesToVariableValues, queryVariableReducer, sortVariableValues, updateVariableOptions, @@ -12,7 +12,7 @@ import { VariablesState } from '../state/variablesReducer'; import { getVariableTestContext } from '../state/helpers'; import { toVariablePayload } from '../state/types'; import { createQueryVariableAdapter } from './adapter'; -import { MetricFindValue, stringToJsRegex } from '@grafana/data'; +import { MetricFindValue } from '@grafana/data'; describe('queryVariableReducer', () => { const adapter = createQueryVariableAdapter(); @@ -307,37 +307,79 @@ describe('sortVariableValues', () => { }); }); -describe('getAllMatches', () => { - it.each` - str | regex | expected - ${'A{somelabel="atext",somevalue="avalue"}'} | ${'/unknown/gi'} | ${{}} - ${'A{somelabel="atext",somevalue="avalue"}'} | ${'/unknown/i'} | ${{}} - ${'A{somelabel="atext",somevalue="avalue"}'} | ${'/some(\\w+)/gi'} | ${{ 0: 'somevalue', 1: 'value', index: 20, input: 'A{somelabel="atext",somevalue="avalue"}' }} - ${'A{somelabel="atext",somevalue="avalue"}'} | ${'/some(\\w+)/i'} | ${{ 0: 'somelabel', 1: 'label', index: 2, input: 'A{somelabel="atext",somevalue="avalue"}' }} - ${'A{somelabel="atext",somevalue="avalue"}'} | ${'/somevalue="(?[^"]+)|somelabel="(?[^"]+)/gi'} | ${{ - 0: 'somevalue="avalue', - 1: 'avalue', - 2: 'atext', - groups: { - text: 'atext', - value: 'avalue', - }, - index: 20, - input: 'A{somelabel="atext",somevalue="avalue"}', -}} - ${'A{somelabel="atext",somevalue="avalue"}'} | ${'/somevalue="(?[^"]+)|somelabel="(?[^"]+)/i'} | ${{ - 0: 'somelabel="atext', - 1: undefined, - 2: 'atext', - groups: { - text: 'atext', - }, - index: 2, - input: 'A{somelabel="atext",somevalue="avalue"}', -}} - `('when called with str:{$str}, regex:{$regex} then it should return correct matches', ({ str, regex, expected }) => { - const result = getAllMatches(str, stringToJsRegex(regex)); +describe('metricNamesToVariableValues', () => { + const item = (str: string) => ({ text: str, value: str, selected: false }); + const metricsNames = [ + item('go_info{instance="demo.robustperception.io:9090",job="prometheus",version="go1.15.6"} 1 1613047998000'), + item('go_info{instance="demo.robustperception.io:9091",job="pushgateway",version="go1.15.6"} 1 1613047998000'), + item('go_info{instance="demo.robustperception.io:9093",job="alertmanager",version="go1.14.4"} 1 1613047998000'), + item('go_info{instance="demo.robustperception.io:9100",job="node",version="go1.14.4"} 1 1613047998000'), + ]; + + const expected1 = [ + { value: 'demo.robustperception.io:9090', text: 'demo.robustperception.io:9090', selected: false }, + { value: 'demo.robustperception.io:9091', text: 'demo.robustperception.io:9091', selected: false }, + { value: 'demo.robustperception.io:9093', text: 'demo.robustperception.io:9093', selected: false }, + { value: 'demo.robustperception.io:9100', text: 'demo.robustperception.io:9100', selected: false }, + ]; + + const expected2 = [ + { value: 'prometheus', text: 'prometheus', selected: false }, + { value: 'pushgateway', text: 'pushgateway', selected: false }, + { value: 'alertmanager', text: 'alertmanager', selected: false }, + { value: 'node', text: 'node', selected: false }, + ]; + const expected3 = [ + { value: 'demo.robustperception.io:9090', text: 'prometheus', selected: false }, + { value: 'demo.robustperception.io:9091', text: 'pushgateway', selected: false }, + { value: 'demo.robustperception.io:9093', text: 'alertmanager', selected: false }, + { value: 'demo.robustperception.io:9100', text: 'node', selected: false }, + ]; + + const expected4 = [ + { value: 'demo.robustperception.io:9090', text: 'demo.robustperception.io:9090', selected: false }, + { value: undefined, text: undefined, selected: false }, + { value: 'demo.robustperception.io:9091', text: 'demo.robustperception.io:9091', selected: false }, + { value: 'demo.robustperception.io:9093', text: 'demo.robustperception.io:9093', selected: false }, + { value: 'demo.robustperception.io:9100', text: 'demo.robustperception.io:9100', selected: false }, + ]; + + it.each` + variableRegEx | expected + ${''} | ${metricsNames} + ${'/unknown/'} | ${[]} + ${'/unknown/g'} | ${[]} + ${'/go/'} | ${metricsNames} + ${'/go/g'} | ${metricsNames} + ${'/(go)/'} | ${[{ value: 'go', text: 'go', selected: false }]} + ${'/(go)/g'} | ${[{ value: 'go', text: 'go', selected: false }]} + ${'/(go)?/'} | ${[{ value: 'go', text: 'go', selected: false }]} + ${'/(go)?/g'} | ${[{ value: 'go', text: 'go', selected: false }, { value: undefined, text: undefined, selected: false }]} + ${'/go(\\w+)/'} | ${[{ value: '_info', text: '_info', selected: false }]} + ${'/go(\\w+)/g'} | ${[{ value: '_info', text: '_info', selected: false }, { value: '1', text: '1', selected: false }]} + ${'/.*_(\\w+)\\{/'} | ${[{ value: 'info', text: 'info', selected: false }]} + ${'/.*_(\\w+)\\{/g'} | ${[{ value: 'info', text: 'info', selected: false }]} + ${'/instance="(?[^"]+)/'} | ${expected1} + ${'/instance="(?[^"]+)/g'} | ${expected1} + ${'/instance="(?[^"]+)/'} | ${expected1} + ${'/instance="(?[^"]+)/g'} | ${expected1} + ${'/instancee="(?[^"]+)/'} | ${[]} + ${'/job="(?[^"]+)/'} | ${expected2} + ${'/job="(?[^"]+)/g'} | ${expected2} + ${'/job="(?[^"]+)/'} | ${expected2} + ${'/job="(?[^"]+)/g'} | ${expected2} + ${'/jobb="(?[^"]+)/g'} | ${[]} + ${'/instance="(?[^"]+)|job="(?[^"]+)/'} | ${expected1} + ${'/instance="(?[^"]+)|job="(?[^"]+)/g'} | ${expected3} + ${'/instance="(?[^"]+)|job="(?[^"]+)/'} | ${expected1} + ${'/instance="(?[^"]+)|job="(?[^"]+)/g'} | ${expected4} + ${'/instance="(?[^"]+).*job="(?[^"]+)/'} | ${expected3} + ${'/instance="(?[^"]+).*job="(?[^"]+)/g'} | ${expected3} + ${'/instance="(?[^"]+).*job="(?[^"]+)/'} | ${expected1} + ${'/instance="(?[^"]+).*job="(?[^"]+)/g'} | ${expected1} + `('when called with variableRegEx:$variableRegEx then it return correct options', ({ variableRegEx, expected }) => { + const result = metricNamesToVariableValues(variableRegEx, VariableSort.disabled, metricsNames); expect(result).toEqual(expected); }); }); diff --git a/public/app/features/variables/query/reducer.ts b/public/app/features/variables/query/reducer.ts index 4513e7e6d78..ac3c1d21116 100644 --- a/public/app/features/variables/query/reducer.ts +++ b/public/app/features/variables/query/reducer.ts @@ -88,30 +88,33 @@ export const sortVariableValues = (options: any[], sortOrder: VariableSort) => { return options; }; -export const getAllMatches = (str: string, regex: RegExp): any => { - const results = {}; - let matches; +const getAllMatches = (str: string, regex: RegExp): RegExpExecArray[] => { + const results: RegExpExecArray[] = []; + let matches = null; + + regex.lastIndex = 0; do { matches = regex.exec(str); - _.merge(results, matches); - } while (regex.global && matches); + if (matches) { + results.push(matches); + } + } while (regex.global && matches && matches[0] !== '' && matches[0] !== undefined); return results; }; -const metricNamesToVariableValues = (variableRegEx: string, sort: VariableSort, metricNames: any[]) => { - let regex, i, matches; +export const metricNamesToVariableValues = (variableRegEx: string, sort: VariableSort, metricNames: any[]) => { + let regex; let options: VariableOption[] = []; if (variableRegEx) { regex = stringToJsRegex(variableRegEx); } - for (i = 0; i < metricNames.length; i++) { + for (let i = 0; i < metricNames.length; i++) { const item = metricNames[i]; let text = item.text === undefined || item.text === null ? item.value : item.text; - let value = item.value === undefined || item.value === null ? item.text : item.value; if (_.isNumber(value)) { @@ -123,24 +126,28 @@ const metricNamesToVariableValues = (variableRegEx: string, sort: VariableSort, } if (regex) { - matches = getAllMatches(value, regex); - - if (_.isEmpty(matches)) { + const matches = getAllMatches(value, regex); + if (!matches.length) { continue; } - if (matches.groups && matches.groups.value && matches.groups.text) { - value = matches.groups.value; - text = matches.groups.text; - } else if (matches.groups && matches.groups.value) { - value = matches.groups.value; - text = value; - } else if (matches.groups && matches.groups.text) { - text = matches.groups.text; - value = text; - } else if (matches['1']) { - text = matches['1']; - value = matches['1']; + const valueGroup = matches.find((m) => m.groups && m.groups.value); + const textGroup = matches.find((m) => m.groups && m.groups.text); + const firstMatch = matches.find((m) => m.length > 1); + const manyMatches = matches.length > 1 && firstMatch; + + if (valueGroup || textGroup) { + value = valueGroup?.groups?.value ?? textGroup?.groups?.text; + text = textGroup?.groups?.text ?? valueGroup?.groups?.value; + } else if (manyMatches) { + for (let j = 0; j < matches.length; j++) { + const match = matches[j]; + options.push({ text: match[1], value: match[1], selected: false }); + } + continue; + } else if (firstMatch) { + text = firstMatch[1]; + value = firstMatch[1]; } }