Skip to content

Commit

Permalink
add support for special characters in metric names (#138)
Browse files Browse the repository at this point in the history
* add support metrics with special symbols #131

* refactor: change CompletionType from type to enum
  • Loading branch information
Loori-R authored Jan 31, 2024
1 parent 40e4e15 commit d772431
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## tip

* FEATURE: add support metrics with special characters in query builder. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/131)

* BUGFIX: fix the default link to vmui. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/132)
* BUGFIX: fix the parsing logic in `renderLegendFormat` to correctly replace legend label names. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/133)
* BUGFIX: fix query editor which produce a lot of requests for alerting rule evaluation. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/134)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"server": "docker-compose up --build",
"tar": "tar -czf $npm_package_name-v$npm_package_version.tar.gz $npm_package_name && sha1sum ./$npm_package_name-v$npm_package_version.tar.gz >$npm_package_name-v$npm_package_version.tar.gz.sha1",
"zip": "zip $npm_package_name-v$npm_package_version.zip $npm_package_name -r && sha1sum ./$npm_package_name-v$npm_package_version.zip >$npm_package_name-v$npm_package_version.zip.sha1",
"preinstall": "cd packages/lezer-metricsql && yarn install"
"preinstall": "cd packages/lezer-metricsql && yarn install && cd ../.. && yarn upgrade lezer-metricsql"
},
"author": "VictoriaMetrics",
"license": "AGPL-3.0-only",
Expand Down
2 changes: 1 addition & 1 deletion packages/lezer-metricsql/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lezer-metricsql",
"version": "0.1.1",
"version": "0.1.2",
"main": "index.cjs",
"type": "module",
"exports": {
Expand Down
4 changes: 3 additions & 1 deletion packages/lezer-metricsql/src/metricsql.grammar
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,9 @@ NumberLiteral {
( ( std.digit+ "y" )? ( std.digit+ "w" )? ( std.digit+ "d" )? ( std.digit+ "h" )? ( std.digit+ "m" )? ( std.digit+ "s" ) ( std.digit+ "ms" )? ) |
( ( std.digit+ "y" )? ( std.digit+ "w" )? ( std.digit+ "d" )? ( std.digit+ "h" )? ( std.digit+ "m" )? ( std.digit+ "s" )? ( std.digit+ "ms" ) )
}
Identifier { (std.asciiLetter | "_" | ":" | ".") (std.asciiLetter | std.digit | "_" | ":" | ".")*}
EscapedChar {("\\" AnyEscapesChar) ("\\" AnyEscapesChar)*}
AnyEscapesChar { "-" | "+" | "*" | "/" | "%" | "^" | "=" }
Identifier {(std.asciiLetter | "_" | ":" | "." | EscapedChar) (std.asciiLetter | std.digit | "_" | ":" | "." | "-" | EscapedChar)*}
LabelName { (std.asciiLetter | "_") (std.asciiLetter | std.digit | "_")* }

// Operator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ import type { Situation, Label } from './situation';
import { NeverCaseError } from './util';
// FIXME: we should not load this from the "outside", but we cannot do that while we have the "old" query-field too

export type CompletionType = 'HISTORY' | 'FUNCTION' | 'METRIC_NAME' | 'WITH_TEMPLATE' | 'DURATION' | 'LABEL_NAME' | 'LABEL_VALUE';
export enum CompletionType {
history = 'HISTORY',
function = 'FUNCTION',
metricName = 'METRIC_NAME',
withTemplate = 'WITH_TEMPLATE',
duration = 'DURATION',
labelName = 'LABEL_NAME',
labelValue = 'LABEL_VALUE',
}

type Completion = {
type: CompletionType;
Expand Down Expand Up @@ -60,7 +68,7 @@ export type DataProvider = {
async function getAllMetricNamesCompletions(dataProvider: DataProvider): Promise<Completion[]> {
const metrics = await dataProvider.getAllMetricNames();
return metrics.map((metric) => ({
type: 'METRIC_NAME',
type: CompletionType.metricName,
label: metric.name,
insertText: metric.name,
detail: `${metric.name} : ${metric.type}`,
Expand All @@ -71,7 +79,7 @@ async function getAllMetricNamesCompletions(dataProvider: DataProvider): Promise
async function getAllWithTemplatesCompletions(dataProvider: DataProvider): Promise<Completion[]> {
const metrics = await dataProvider.getAllWithTemplates();
return metrics.map((metric) => ({
type: 'WITH_TEMPLATE',
type: CompletionType.withTemplate,
label: metric.name,
insertText: metric.name,
detail: metric.value,
Expand All @@ -80,7 +88,7 @@ async function getAllWithTemplatesCompletions(dataProvider: DataProvider): Promi
}

const FUNCTION_COMPLETIONS: Completion[] = FUNCTIONS.map((f) => ({
type: 'FUNCTION',
type: CompletionType.function,
label: f.label,
insertText: f.insertText ?? '', // i don't know what to do when this is nullish. it should not be.
detail: f.detail,
Expand All @@ -104,7 +112,7 @@ const DURATION_COMPLETIONS: Completion[] = [
'1h',
'1d',
].map((text) => ({
type: 'DURATION',
type: CompletionType.duration,
label: text,
insertText: text,
}));
Expand All @@ -115,7 +123,7 @@ async function getAllHistoryCompletions(dataProvider: DataProvider): Promise<Com
const allHistory = await dataProvider.getHistory();
// FIXME: find a better history-limit
return allHistory.slice(0, 10).map((expr) => ({
type: 'HISTORY',
type: CompletionType.history,
label: expr,
insertText: expr,
}));
Expand Down Expand Up @@ -162,7 +170,7 @@ async function getLabelNamesForCompletions(
): Promise<Completion[]> {
const labelNames = await getLabelNames(metric, otherLabels, dataProvider);
const labelNamesCompletion: Completion[] = labelNames.map((text) => ({
type: 'LABEL_NAME',
type: CompletionType.labelName,
label: text,
insertText: `${text}${suffix}`,
triggerOnInsert,
Expand Down Expand Up @@ -215,7 +223,7 @@ async function getLabelValuesForMetricCompletions(
): Promise<Completion[]> {
const values = await getLabelValues(metric, labelName, otherLabels, dataProvider);
return values.map((text) => ({
type: 'LABEL_VALUE',
type: CompletionType.labelValue,
label: text,
insertText: betweenQuotes ? text : `"${text}"`, // FIXME: escaping strange characters?
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import { IMarkdownString } from "monaco-editor";

import type { Monaco, monacoTypes } from '@grafana/ui';

import { getCompletions, DataProvider, CompletionType } from './completions';
import { escapeMetricNameSpecialCharacters } from "../../../language_utils";

import { CompletionType, DataProvider, getCompletions } from './completions';
import { getSituation } from './situation';
import { NeverCaseError } from './util';

Expand All @@ -47,19 +49,19 @@ export function getSuggestOptions(): monacoTypes.editor.ISuggestOptions {

function getMonacoCompletionItemKind(type: CompletionType, monaco: Monaco): monacoTypes.languages.CompletionItemKind {
switch (type) {
case 'DURATION':
case CompletionType.duration:
return monaco.languages.CompletionItemKind.Unit;
case 'FUNCTION':
case CompletionType.function:
return monaco.languages.CompletionItemKind.Variable;
case 'HISTORY':
case CompletionType.history:
return monaco.languages.CompletionItemKind.Snippet;
case 'LABEL_NAME':
case CompletionType.labelName:
return monaco.languages.CompletionItemKind.Enum;
case 'LABEL_VALUE':
case CompletionType.labelValue:
return monaco.languages.CompletionItemKind.EnumMember;
case 'METRIC_NAME':
case CompletionType.metricName:
return monaco.languages.CompletionItemKind.Constructor;
case 'WITH_TEMPLATE':
case CompletionType.withTemplate:
return monaco.languages.CompletionItemKind.Constant;
default:
throw new NeverCaseError();
Expand Down Expand Up @@ -100,7 +102,7 @@ export function getCompletionProvider(
const suggestions: monacoTypes.languages.CompletionItem[] = items.map((item, index) => ({
kind: getMonacoCompletionItemKind(item.type, monaco),
label: item.label,
insertText: item.insertText,
insertText: item.type === CompletionType.metricName ? escapeMetricNameSpecialCharacters(item.insertText) : item.insertText,
detail: item.detail,
documentation: { value: item.documentation } as IMarkdownString,
sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have
Expand Down
5 changes: 5 additions & 0 deletions src/language_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ export function escapeLabelValueInRegexSelector(labelValue: string): string {
return escapeLabelValueInExactSelector(escapePrometheusRegexp(labelValue));
}

export function escapeMetricNameSpecialCharacters(metricName: string) {
const specialChars = /[-+*\/%^=]/g;
return metricName.replace(specialChars, (match) => '\\' + match);
}

export enum AbstractLabelOperator {
Equal = "Equal",
NotEqual = "NotEqual",
Expand Down
3 changes: 2 additions & 1 deletion src/querybuilder/components/MetricSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SelectableValue, toOption, GrafanaTheme2 } from '@grafana/data';
import { Select, FormatOptionLabelMeta, useStyles2 } from '@grafana/ui';

import { EditorField, EditorFieldGroup } from '../../components/QueryEditor';
import { escapeMetricNameSpecialCharacters } from "../../language_utils";
import { PromVisualQuery } from '../types';

// We are matching words split with space
Expand Down Expand Up @@ -95,7 +96,7 @@ export function MetricSelect({ query, onChange, onGetMetrics }: Props) {
options={state.metrics}
onChange={({ value }) => {
if (value) {
onChange({ ...query, metric: value });
onChange({ ...query, metric: escapeMetricNameSpecialCharacters(value) });
}
}}
/>
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6831,7 +6831,7 @@ levn@^0.4.1:
type-check "~0.4.0"

"lezer-metricsql@file:packages/lezer-metricsql":
version "0.1.0"
version "0.1.2"

lines-and-columns@^1.1.6:
version "1.2.4"
Expand Down

0 comments on commit d772431

Please sign in to comment.