Skip to content

Commit

Permalink
fix an issue with prettify query (#169)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dmitryk-dk committed Jun 24, 2024
1 parent bc562ef commit 694e775
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 3 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

* 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).
Expand Down
104 changes: 104 additions & 0 deletions src/components/PrettifyQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<PrettifyQuery
datasource={datasource}
query={{ expr: got, refId: 'A' }}
onChange={mockCallback}
/>);
});

const btn = await screen.findByRole('button');

await act(async () => {
btn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
});
});
});
40 changes: 37 additions & 3 deletions src/components/PrettifyQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props> = ({
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}`)
}
Expand Down

0 comments on commit 694e775

Please sign in to comment.