From 694e7757eabb6bd7ab3584bc34f2cd7ee627e7a4 Mon Sep 17 00:00:00 2001 From: Dmytro Kozlov Date: Mon, 24 Jun 2024 10:38:53 +0200 Subject: [PATCH] fix an issue with prettify query (#169) * fix an issue with prettify query * made logic better add tests * datasource change for better testing * update logic, fix handle multiple grafana variables, improve regex --- CHANGELOG.md | 2 + src/components/PrettifyQuery.test.tsx | 104 ++++++++++++++++++++++++++ src/components/PrettifyQuery.tsx | 40 +++++++++- 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/components/PrettifyQuery.test.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d070f33..e6ab6b39 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## tip +* BUGFIX: fix an issue with prettify query if the query includes Grafana variables in the lookbehind window. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/166). + ## [v0.8.2](https://github.com/VictoriaMetrics/grafana-datasource/releases/tag/v0.8.2) * BUGFIX: fix parsing of label names with special characters for the query builder. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/131#issuecomment-2105662179). diff --git a/src/components/PrettifyQuery.test.tsx b/src/components/PrettifyQuery.test.tsx new file mode 100644 index 00000000..1432c3cb --- /dev/null +++ b/src/components/PrettifyQuery.test.tsx @@ -0,0 +1,104 @@ +import { render, screen, act } from '@testing-library/react'; +import React from 'react'; + +import { PrometheusDatasource } from "../datasource"; + +import PrettifyQuery from './PrettifyQuery'; + + +const testQueries = [ + { + name: 'empty query', + got:'', + want:'' + }, + { + name: 'query with defined lookbehind window', + got:'sum(rate(node_cpu_seconds_total{mode="idle"}[5m]))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}[5m]))' + }, + { + name: 'query with grafana $__interval variable', + got:'sum(rate(node_cpu_seconds_total{mode="idle"}[$__interval]))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}[$__interval]))' + }, + { + name: 'query with grafana variable and lookbehind window', + got:'sum(rate(node_cpu_seconds_total{mode="idle"}))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}))' + }, + { + name: 'query with grafana $__interval_ms variable', + got: 'sum(rate(node_cpu_seconds_total{mode="idle"}[$__interval_ms]))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}[$__interval_ms]))' + }, + { + name: 'query with grafana $__range variable', + got: 'sum(rate(node_cpu_seconds_total{mode="idle"}[$__range]))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}[$__range]))' + }, + { + name: 'query with grafana $__range variable', + got: 'sum(rate(node_cpu_seconds_total{mode="idle"}[$__range_s]))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}[$__range_s]))' + }, + { + name: 'query with grafana $__rate_interval variable', + got: 'sum(rate(node_cpu_seconds_total{mode="idle"}[$__rate_interval]))', + want:'sum(rate(node_cpu_seconds_total{mode="idle"}[$__rate_interval]))' + }, + { + name: 'query with two grafana variables', + got: 'rate(metric_name[$__interval]) + rate(metric_name[$__range]) ', + want:'rate(metric_name[$__interval]) + rate(metric_name[$__range]) ' + }, + { + name: 'query with grafana variable and label value as lookbehind window', + got: 'rate(metric_name{mode="idle"}[$__interval]) + up{instance="[1i]"} ', + want:'rate(metric_name{mode="idle"}[$__interval]) + up{instance="[1i]"} ' + } +] + +const datasource = { + languageProvider: { + start: () => Promise.resolve([]), + syntax: () => {}, + getLabelKeys: () => [], + metrics: [], + }, + getInitHints: () => [], + prettifyRequest: async (expr: string) => { + return { + data: { + query: expr, + status: 'success' + } + } + } +} as unknown as PrometheusDatasource; + +describe("Prettyfied Query", () => { + testQueries.forEach(async ({ name, got, want }) => { + it(`should prettify the query ${name}`, async () => { + + const mockCallback = jest.fn(resp => { + const { expr } = resp; + expect(expr).toBe(want) + }); + + act(() => { + render(); + }); + + const btn = await screen.findByRole('button'); + + await act(async () => { + btn.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + }); + }); +}); diff --git a/src/components/PrettifyQuery.tsx b/src/components/PrettifyQuery.tsx index 4b7f5322..907dd5a1 100755 --- a/src/components/PrettifyQuery.tsx +++ b/src/components/PrettifyQuery.tsx @@ -16,20 +16,54 @@ enum ResponseStatus { Error = 'error' } +const GRAFANA_VARIABLES = [ + "$__interval", + "$__interval_ms", + "$__range", + "$__range_s", + "$__range_ms", + "$__rate_interval", +]; + +interface GrafanaVariableReplacer { + variable: string; + defaultWindow: string; +} + const PrettifyQuery: FC = ({ datasource, query, onChange }) => { - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(false); + const handleClickPrettify = async () => { setLoading(true) try { - const response = await datasource.prettifyRequest(query.expr) + let { expr } = query; + let grafanaVariables = [] as GrafanaVariableReplacer[]; + GRAFANA_VARIABLES.forEach((variable, idx) => { + const regex = new RegExp(`\\[(\\${variable})\\]\\)`, 'g'); + if (regex.test(expr)) { + expr = expr.replace(regex, `[${idx+1}i])`); + grafanaVariables.push({ + variable, + defaultWindow: `${idx+1}i`, + }) + } + }); + const response = await datasource.prettifyRequest(expr); const { data, status } = response if (data?.status === ResponseStatus.Success) { - onChange({ ...query, expr: data.query }); + let { query } = data; + if (grafanaVariables.length > 0) { + grafanaVariables.forEach(grafanaVariable => { + const regex = new RegExp(`\\[(${grafanaVariable.defaultWindow})\\]\\)`, 'g'); + query = query.replace(regex, `[${grafanaVariable.variable}])`); + }); + } + onChange({ ...query, expr: query }); } else { console.error(`Error requesting /prettify-query, status: ${status}`) }