Skip to content

Commit 0a7ff35

Browse files
432 fix backward compatibility with queries containing regexp vars (#433)
Related issue: #432 * change regexp var error to warning * fix backward compatibility with queries containing regexp vars * update doc: change all images with regexp vars queries to images with word filter
1 parent 6a1a463 commit 0a7ff35

File tree

12 files changed

+113
-19
lines changed

12 files changed

+113
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## tip
44

5+
* BUGFIX: fix backward compatibility with queries containing regexp variables. Error in queries with regexp variables has been replaced with a warning with quick fix action. See [#432](https://github.com/VictoriaMetrics/victorialogs-datasource/pull/432).
6+
57
## v0.21.3
68

79
* BUGFIX: sanitize URLs to prevent potential security issues, properly close gzip reader to prevent resource leaks, fix text filter interpolation for `*` character. See pr [#429](https://github.com/VictoriaMetrics/victorialogs-datasource/pull/429).

src/LogsQL/regExpOperator.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,21 @@ describe('regExpOperator', () => {
139139
const result = replaceRegExpOperatorToOperator('field.name.with.dots:~$variable');
140140
expect(result).toEqual('field.name.with.dots:$variable');
141141
});
142+
143+
it('should replace all regexp vars', () => {
144+
const result = replaceRegExpOperatorToOperator('field.name.with.dots1:~$variable field.name.with.dots2:~$variable | field.name.with.dots3:~$variable field.name.with.dots4:~variable field.name.with.dots3:$variable');
145+
expect(result).toEqual('field.name.with.dots1:$variable field.name.with.dots2:$variable | field.name.with.dots3:$variable field.name.with.dots4:~variable field.name.with.dots3:$variable');
146+
});
147+
148+
it('should replace all regexp vars with new lines', () => {
149+
const result = replaceRegExpOperatorToOperator(`kubernetes.pod_namespace:~"$namespace" kubernetes.pod_name:~"$pod"
150+
| stats by(kubernetes.pod_name) count()
151+
| count()`
152+
);
153+
expect(result).toEqual(`kubernetes.pod_namespace:"$namespace" kubernetes.pod_name:"$pod"
154+
| stats by(kubernetes.pod_name) count()
155+
| count()`
156+
);
157+
});
142158
});
143159
});

src/LogsQL/regExpOperator.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
/**
22
* Regular expression for matching variables in a query expression -> filterName:~"$VariableName"
33
* */
4-
const variableRegExp = /(^|\||\s)([^\s:~]+)\s*:\s*~\s*(?:"(\$[A-Za-z0-9_.-]+)"|(\$[A-Za-z0-9_.-]+)|"([^"]+)"|([^\s|]+))(?=\s|\||$)/;
4+
const variableRegExpPattern = /(^|\||\s)([^\s:~]+)\s*:\s*~\s*(?:"(\$[A-Za-z0-9_.-]+)"|(\$[A-Za-z0-9_.-]+)|"([^"]+)"|([^\s|]+))(?=\s|\||$)/;
55

66
export function getQueryExprVariableRegExp(queryExpr: string) {
7-
return variableRegExp.exec(queryExpr);
7+
return new RegExp(variableRegExpPattern).exec(queryExpr);
88
}
99

1010
export function replaceRegExpOperatorToOperator(queryExpr: string, operator = ':') {
11-
return queryExpr.replace(variableRegExp, `$1$2${operator}$4`);
11+
return queryExpr.replace(new RegExp(variableRegExpPattern, 'g'), (match, p1, p2, p3, p4) => {
12+
// p3 - "$variable"
13+
// p4 - $variable
14+
const variable = p3 || p4;
15+
if (variable) {
16+
const value = p3 ? `"${variable}"` : variable;
17+
return `${p1}${p2}${operator}${value}`;
18+
}
19+
return match;
20+
});
1221
}

src/components/QueryEditor/QueryEditor.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { changeEditorMode, getQueryWithDefaults } from "./state";
2626
const QueryEditor = React.memo<VictoriaLogsQueryEditorProps>((props) => {
2727
const styles = useStyles2(getStyles);
2828

29-
const { onChange, onRunQuery: runQuery, data, app, queries, datasource, range: timeRange } = props;
29+
const { onChange, onRunQuery, data, app, queries, datasource, range: timeRange } = props;
3030
const [dataIsStale, setDataIsStale] = useState(false);
3131
const [parseModalOpen, setParseModalOpen] = useState(false);
3232

@@ -62,13 +62,6 @@ const QueryEditor = React.memo<VictoriaLogsQueryEditorProps>((props) => {
6262
onChange(query);
6363
};
6464

65-
const onRunQuery = useCallback(() => {
66-
if(varRegExp) {
67-
return;
68-
}
69-
runQuery();
70-
}, [runQuery, varRegExp]);
71-
7265
useEffect(() => {
7366
// grafana with a version below 12 doesn't support subscribe function on store
7467
if ('subscribe' in store) {
@@ -125,7 +118,7 @@ const QueryEditor = React.memo<VictoriaLogsQueryEditorProps>((props) => {
125118
) : (
126119
<QueryCodeEditor {...props} query={query} onChange={onChangeInternal} showExplain={true}/>
127120
)}
128-
{varRegExp && (<QueryEditorVariableRegexpError regExp={varRegExp} />)}
121+
{varRegExp && (<QueryEditorVariableRegexpError regExp={varRegExp} query={query} onChange={onChange}/>)}
129122
<QueryEditorOptions
130123
query={query}
131124
onChange={onChange}

src/components/QueryEditor/QueryEditorVariableRegexpError.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
import { css } from "@emotion/css";
2-
import React from 'react';
2+
import React, { useCallback } from 'react';
33

44
import { GrafanaTheme2 } from "@grafana/data";
5-
import { Badge, useTheme2 } from "@grafana/ui";
5+
import { Badge, TextLink, useTheme2 } from "@grafana/ui";
66

77
import { replaceRegExpOperatorToOperator } from "../../LogsQL/regExpOperator";
8-
8+
import { Query } from "../../types";
99

1010
interface Props {
1111
regExp: string;
12+
query: Query;
13+
onChange: (query: Query) => void;
1214
}
1315

14-
const QueryEditorVariableRegexpError = ({ regExp }: Props) => {
16+
const QueryEditorVariableRegexpError = ({ regExp, query, onChange }: Props) => {
1517
const theme = useTheme2();
1618
const styles = getStyles(theme);
1719
const fixedFilter = replaceRegExpOperatorToOperator(regExp);
20+
const onApply = useCallback((e: React.MouseEvent) => {
21+
e.preventDefault();
22+
const queryExpr = replaceRegExpOperatorToOperator(query.expr);
23+
onChange({ ...query, expr: queryExpr });
24+
}, [onChange, query])
1825

1926
const text = (
2027
<div>
21-
Regexp operator `~` cannot be used with variables in `{regExp}`. Use exact match operator `:` (e.g. `{fixedFilter}`) or use non variable in regexp instead.
28+
Regexp operator `~` cannot be used with variables in `{regExp}`. Use word filter operator `:` (e.g. `{fixedFilter}`)
29+
or use non variable in regexp instead. <TextLink onClick={onApply} href={""}>Apply fix</TextLink>
2230
</div>
2331
)
2432

src/datasource.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { escapeLabelValueInSelector } from "./languageUtils";
4040
import LogsQlLanguageProvider from "./language_provider";
4141
import { LOGS_VOLUME_BARS, queryLogsVolume } from "./logsVolumeLegacy";
4242
import { addLabelToQuery, addSortPipeToQuery, queryHasFilter, removeLabelFromQuery } from "./modifyQuery";
43+
import { removeDoubleQuotesAroundVar } from "./parsing";
4344
import { replaceOperatorWithIn, returnVariables } from "./parsingUtils";
4445
import { storeKeys } from "./store/constants";
4546
import store from "./store/store";
@@ -286,14 +287,21 @@ export class VictoriaLogsDatasource
286287
return Array.isArray(value) ? value.includes(VARIABLE_ALL_VALUE) : false;
287288
}
288289

289-
replaceOperatorsToInForMultiQueryVariables(expr: string,) {
290+
replaceAllOption(queryExpr: string, variableName: string, regExpAllValue: string): string {
291+
queryExpr = queryExpr.replace(`~"$${variableName}"`, `~"${regExpAllValue}"` || '~".*"');
292+
queryExpr = queryExpr.replace(`$${variableName}`, '*');
293+
return queryExpr;
294+
}
295+
296+
replaceOperatorsToInForMultiQueryVariables(expr: string) {
290297
const variables = this.templateSrv.getVariables();
291298
const fieldValuesVariables = variables.filter(v => v.type === 'query' && v.query.type === 'fieldValue' && v.multi || this.isAllOption(v)) as QueryVariableModel[];
292299
let result = expr;
293300
for (let variable of fieldValuesVariables) {
301+
result = removeDoubleQuotesAroundVar(result, variable.name);
294302
result = replaceOperatorWithIn(result, variable.name);
295303
if (this.isAllOption(variable)) {
296-
result = result.replace(`$${variable.name}`, '*');
304+
result = this.replaceAllOption(result, variable.name, variable.allValue || '.*');
297305
}
298306
}
299307
return result;

src/img/panel_logs.png

-18.5 KB
Loading

src/img/panel_stat.png

-5.66 KB
Loading

src/img/panel_table.png

-12.4 KB
Loading

src/img/panel_time_series.png

-19.7 KB
Loading

0 commit comments

Comments
 (0)