diff --git a/packages/grafana-prometheus/src/language_utils.test.ts b/packages/grafana-prometheus/src/language_utils.test.ts index e71a505aad8..e30360b1b69 100644 --- a/packages/grafana-prometheus/src/language_utils.test.ts +++ b/packages/grafana-prometheus/src/language_utils.test.ts @@ -9,84 +9,12 @@ import { fixSummariesMetadata, getPrometheusTime, getRangeSnapInterval, - parseSelector, processLabels, toPromLikeQuery, truncateResult, } from './language_utils'; import { PrometheusCacheLevel } from './types'; -describe('parseSelector()', () => { - let parsed; - - it('returns a clean selector from an empty selector', () => { - parsed = parseSelector('{}', 1); - expect(parsed.selector).toBe('{}'); - expect(parsed.labelKeys).toEqual([]); - }); - - it('returns a clean selector from an unclosed selector', () => { - const parsed = parseSelector('{foo'); - expect(parsed.selector).toBe('{}'); - }); - - it('returns the selector sorted by label key', () => { - parsed = parseSelector('{foo="bar"}'); - expect(parsed.selector).toBe('{foo="bar"}'); - expect(parsed.labelKeys).toEqual(['foo']); - - parsed = parseSelector('{foo="bar",baz="xx"}'); - expect(parsed.selector).toBe('{baz="xx",foo="bar"}'); - }); - - it('returns a clean selector from an incomplete one', () => { - parsed = parseSelector('{foo}'); - expect(parsed.selector).toBe('{}'); - - parsed = parseSelector('{foo="bar",baz}'); - expect(parsed.selector).toBe('{foo="bar"}'); - - parsed = parseSelector('{foo="bar",baz="}'); - expect(parsed.selector).toBe('{foo="bar"}'); - - // Cursor in value area counts as incomplete - parsed = parseSelector('{foo="bar",baz=""}', 16); - expect(parsed.selector).toBe('{foo="bar"}'); - - parsed = parseSelector('{foo="bar",baz="4"}', 17); - expect(parsed.selector).toBe('{foo="bar"}'); - }); - - it('throws if not inside a selector', () => { - expect(() => parseSelector('foo{}', 0)).toThrow(); - expect(() => parseSelector('foo{} + bar{}', 5)).toThrow(); - }); - - it('returns the selector nearest to the cursor offset', () => { - expect(() => parseSelector('{foo="bar"} + {foo="bar"}', 0)).toThrow(); - - parsed = parseSelector('{foo="bar"} + {foo="bar"}', 1); - expect(parsed.selector).toBe('{foo="bar"}'); - - parsed = parseSelector('{foo="bar"} + {baz="xx"}', 1); - expect(parsed.selector).toBe('{foo="bar"}'); - - parsed = parseSelector('{baz="xx"} + {foo="bar"}', 16); - expect(parsed.selector).toBe('{foo="bar"}'); - }); - - it('returns a selector with metric if metric is given', () => { - parsed = parseSelector('bar{foo}', 4); - expect(parsed.selector).toBe('{__name__="bar"}'); - - parsed = parseSelector('baz{foo="bar"}', 13); - expect(parsed.selector).toBe('{__name__="baz",foo="bar"}'); - - parsed = parseSelector('bar:metric:1m{}', 14); - expect(parsed.selector).toBe('{__name__="bar:metric:1m"}'); - }); -}); - describe('fixSummariesMetadata', () => { const synthetics = { ALERTS: { diff --git a/packages/grafana-prometheus/src/language_utils.ts b/packages/grafana-prometheus/src/language_utils.ts index ebe92f3d08c..44eb516447e 100644 --- a/packages/grafana-prometheus/src/language_utils.ts +++ b/packages/grafana-prometheus/src/language_utils.ts @@ -63,9 +63,6 @@ export function processLabels(labels: Array<{ [key: string]: string }>, withName return { values: valueArray, keys: Object.keys(valueArray) }; } -// const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/; -export const selectorRegexp = /\{[^}]*?(\}|$)/; - // This will capture 4 groups. Example label filter => {instance="10.4.11.4:9003"} // 1. label: instance // 2. operator: = @@ -75,70 +72,6 @@ export const selectorRegexp = /\{[^}]*?(\}|$)/; // comma and space is useful for addLabelsToExpression function export const labelRegexp = /\b(\w+)(!?=~?)("[^"\n]*?")(,)?(\s*)?/g; -export function parseSelector(query: string, cursorOffset = 1): { labelKeys: string[]; selector: string } { - if (!query.match(selectorRegexp)) { - // Special matcher for metrics - if (query.match(/^[A-Za-z:][\w:]*$/)) { - return { - selector: `{__name__="${query}"}`, - labelKeys: ['__name__'], - }; - } - throw new Error('Query must contain a selector: ' + query); - } - - // Check if inside a selector - const prefix = query.slice(0, cursorOffset); - const prefixOpen = prefix.lastIndexOf('{'); - const prefixClose = prefix.lastIndexOf('}'); - if (prefixOpen === -1) { - throw new Error('Not inside selector, missing open brace: ' + prefix); - } - if (prefixClose > -1 && prefixClose > prefixOpen) { - throw new Error('Not inside selector, previous selector already closed: ' + prefix); - } - const suffix = query.slice(cursorOffset); - const suffixCloseIndex = suffix.indexOf('}'); - const suffixClose = suffixCloseIndex + cursorOffset; - const suffixOpenIndex = suffix.indexOf('{'); - const suffixOpen = suffixOpenIndex + cursorOffset; - if (suffixClose === -1) { - throw new Error('Not inside selector, missing closing brace in suffix: ' + suffix); - } - if (suffixOpenIndex > -1 && suffixOpen < suffixClose) { - throw new Error('Not inside selector, next selector opens before this one closed: ' + suffix); - } - - // Extract clean labels to form clean selector, incomplete labels are dropped - const selector = query.slice(prefixOpen, suffixClose); - const labels: { [key: string]: { value: string; operator: string } } = {}; - selector.replace(labelRegexp, (label, key, operator, value) => { - const labelOffset = query.indexOf(label); - const valueStart = labelOffset + key.length + operator.length + 1; - const valueEnd = labelOffset + key.length + operator.length + value.length - 1; - // Skip label if cursor is in value - if (cursorOffset < valueStart || cursorOffset > valueEnd) { - labels[key] = { value, operator }; - } - return ''; - }); - - // Add metric if there is one before the selector - const metricPrefix = query.slice(0, prefixOpen); - const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/); - if (metricMatch) { - labels['__name__'] = { value: `"${metricMatch[0]}"`, operator: '=' }; - } - - // Build sorted selector - const labelKeys = Object.keys(labels).sort(); - const cleanSelector = labelKeys.map((key) => `${key}${labels[key].operator}${labels[key].value}`).join(','); - - const selectorString = ['{', cleanSelector, '}'].join(''); - - return { labelKeys, selector: selectorString }; -} - export function expandRecordingRules(query: string, mapping: { [name: string]: RecordingRuleIdentifier }): string { const getRuleRegex = (ruleName: string) => new RegExp(`(\\s|\\(|^)(${ruleName})(\\s|$|\\(|\\[|\\{)`, 'ig');