From 106af5d0ee299fbfe5830076b67abb5efcdab0ca Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Thu, 16 Jan 2025 16:50:35 +0100 Subject: [PATCH] Prometheus: UTF8 support in metrics browser (#98612) * utf8 metrics for prometheus devenv * introduce utf8 support * completions and suggestions * don't wrap the utf8 label in quotes * linting * support utf8 labels and metrics on visual query builder * lint * update raw view for utf8 metric syntax * betterer * support utf8 metric names in explore metrics * utf8 support in grouop by * utf8 support in label break down * utf8 metric and label support in metric_find_query for label values * use the same regex to check label values * no need to escape * support series endpoint * support series endpoint * support series endpoint * support series endpoint * fix tests * fix extracting labels from labelValuesQuery * show explanation with right syntax * support utf8 metrics and labels --- .../PrometheusMetricsBrowser.test.tsx | 43 +++++++++++++++++++ .../components/PrometheusMetricsBrowser.tsx | 25 +++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx index 58efb0e28f0..df14c9e02c8 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.test.tsx @@ -54,6 +54,49 @@ describe('buildSelector()', () => { ]; expect(buildSelector(labels)).toEqual('foo{bar="baz"}'); }); + + describe('utf8 support', () => { + it('metric selector with utf8 metric', () => { + const labels: SelectableLabel[] = [ + { name: '__name__', selected: true, values: [{ name: 'utf8.metric', selected: true }] }, + ]; + expect(buildSelector(labels)).toEqual('{"utf8.metric"}'); + }); + + it('metric selector with utf8 labels', () => { + const labels: SelectableLabel[] = [ + { name: '__name__', selected: true, values: [{ name: 'foo', selected: true }] }, + { name: 'utf8.label', selected: true, values: [{ name: 'baz', selected: true }] }, + ]; + expect(buildSelector(labels)).toEqual('foo{"utf8.label"="baz"}'); + }); + + it('metric selector with utf8 labels and metrics', () => { + const labels: SelectableLabel[] = [ + { name: '__name__', selected: true, values: [{ name: 'utf8.metric', selected: true }] }, + { name: 'utf8.label', selected: true, values: [{ name: 'baz', selected: true }] }, + ]; + expect(buildSelector(labels)).toEqual('{"utf8.metric","utf8.label"="baz"}'); + }); + + it('metric selector with utf8 metric and with utf8/non-utf8 labels', () => { + const labels: SelectableLabel[] = [ + { name: '__name__', selected: true, values: [{ name: 'utf8.metric', selected: true }] }, + { name: 'utf8.label', selected: true, values: [{ name: 'uuu', selected: true }] }, + { name: 'bar', selected: true, values: [{ name: 'baz', selected: true }] }, + ]; + expect(buildSelector(labels)).toEqual('{"utf8.metric","utf8.label"="uuu",bar="baz"}'); + }); + + it('metric selector with non-utf8 metric with utf8/non-utf8 labels', () => { + const labels: SelectableLabel[] = [ + { name: '__name__', selected: true, values: [{ name: 'foo', selected: true }] }, + { name: 'utf8.label', selected: true, values: [{ name: 'uuu', selected: true }] }, + { name: 'bar', selected: true, values: [{ name: 'baz', selected: true }] }, + ]; + expect(buildSelector(labels)).toEqual('foo{"utf8.label"="uuu",bar="baz"}'); + }); + }); }); describe('facetLabels()', () => { diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx index 963590f3667..1eeb59d35ed 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx @@ -19,6 +19,7 @@ import { import PromQlLanguageProvider from '../language_provider'; import { escapeLabelValueInExactSelector, escapeLabelValueInRegexSelector } from '../language_utils'; +import { isValidLegacyName, utf8Support } from '../utf8_support'; // Hard limit on labels to render const EMPTY_SELECTOR = '{}'; @@ -68,22 +69,38 @@ export interface SelectableLabel { export function buildSelector(labels: SelectableLabel[]): string { let singleMetric = ''; - const selectedLabels = []; + const selectedLabels: string[] = []; for (const label of labels) { if ((label.name === METRIC_LABEL || label.selected) && label.values && label.values.length > 0) { const selectedValues = label.values.filter((value) => value.selected).map((value) => value.name); if (selectedValues.length > 1) { - selectedLabels.push(`${label.name}=~"${selectedValues.map(escapeLabelValueInRegexSelector).join('|')}"`); + selectedLabels.push( + `${utf8Support(label.name)}=~"${selectedValues.map(escapeLabelValueInRegexSelector).join('|')}"` + ); } else if (selectedValues.length === 1) { if (label.name === METRIC_LABEL) { singleMetric = selectedValues[0]; } else { - selectedLabels.push(`${label.name}="${escapeLabelValueInExactSelector(selectedValues[0])}"`); + selectedLabels.push(`${utf8Support(label.name)}="${escapeLabelValueInExactSelector(selectedValues[0])}"`); } } } } - return [singleMetric, '{', selectedLabels.join(','), '}'].join(''); + + const selectorParts: string[] = []; + const isLegacyName = singleMetric === '' || isValidLegacyName(singleMetric); + + if (isLegacyName) { + selectorParts.push(singleMetric, '{'); + } else { + selectorParts.push('{', `"${singleMetric}"`); + if (selectedLabels.length > 0) { + selectorParts.push(','); + } + } + + selectorParts.push(selectedLabels.join(','), '}'); + return selectorParts.join(''); } export function facetLabels(