Skip to content

Commit

Permalink
fix: correct filter application in queries (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
Loori-R authored Oct 16, 2024
1 parent 5bb8717 commit 6ba3ee3
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* BUGFIX: fix variable substitution in queries. See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/77).
* BUGFIX: fixed health path for case, when url ends with trailing slash.
* BUGFIX: fix the application of filtering in queries. See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/81).

## v0.5.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ interface Context {
type ParsedExpression = string | ParsedExpression[];

export const buildVisualQueryFromString = (expr: string): Context => {
// This will be modified in the handleExpression
const visQuery: VisualQuery = {
filters: { operators: [], values: [] },
pipes: []
Expand Down Expand Up @@ -46,6 +45,10 @@ const handleExpression = (expr: string) => {
return { filters, pipes: pipeParts };
}

export const splitExpression = (expr: string): string[] => {
return expr.split('|').map(part => part.trim());
};

const parseStringToFilterVisualQuery = (expression: string): FilterVisualQuery => {
const parsedExpressions = parseExpression(expression)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ const filterVisualQueryToString = (query: FilterVisualQuery): string => {
}

export const parseVisualQueryToString = (query: VisualQuery): string => {
// TODO add parse pipes
return filterVisualQueryToString(query.filters);
const pipesPart = query.pipes?.length ? ` | ${query.pipes.join(' | ')}` : ''
return filterVisualQueryToString(query.filters) + pipesPart;
}
49 changes: 49 additions & 0 deletions src/modifyQuery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { addLabelToQuery, removeLabelFromQuery } from './modifyQuery';

describe('modifyQuery', () => {
describe('addLabelToQuery', () => {
it('should add a label to the query with the specified operator', () => {
const query = 'foo: bar';
const key = 'baz';
const value = 'qux';
const operator = 'AND';
const result = addLabelToQuery(query, key, value, operator);
expect(result).toBe('foo: bar AND baz:"qux"');
});

it('should add a label to the query and retain pipes', () => {
const query = 'foo: bar | pipe1 | pipe2';
const key = 'baz';
const value = 'qux';
const operator = 'AND';
const result = addLabelToQuery(query, key, value, operator);
expect(result).toBe('foo: bar AND baz:"qux" | pipe1 | pipe2');
});
});

describe('removeLabelFromQuery', () => {
it('should remove a label from the query', () => {
const query = 'foo: bar AND baz:"qux"';
const key = 'baz';
const value = 'qux';
const result = removeLabelFromQuery(query, key, value);
expect(result).toBe('foo: bar');
});

it('should remove a label from the query and retain pipes', () => {
const query = 'foo: bar AND baz:"qux" | pipe1 | pipe2';
const key = 'baz';
const value = 'qux';
const result = removeLabelFromQuery(query, key, value);
expect(result).toBe('foo: bar | pipe1 | pipe2');
});

it('should handle nested filters correctly', () => {
const query = 'foo: bar AND (baz:"qux" OR quux:"corge")';
const key = 'baz';
const value = 'qux';
const result = removeLabelFromQuery(query, key, value);
expect(result).toBe('foo: bar AND (quux:"corge")');
});
});
});
60 changes: 42 additions & 18 deletions src/modifyQuery.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,54 @@
export function queryHasFilter(query: string, key: string, value: string): boolean {
return query.includes(`${key}:${value}`)
import { buildVisualQueryFromString, splitExpression } from "./components/QueryEditor/QueryBuilder/utils/parseFromString";
import { parseVisualQueryToString } from "./components/QueryEditor/QueryBuilder/utils/parseToString";
import { FilterVisualQuery } from "./types";

const getKeyValue = (key: string, value: string): string => {
return `${key}:"${value}"`
}

export const removeLabelFromQuery = (query: string, key: string, value: string): string => {
const operators = ['AND', 'NOT']
const parts = query.split(' ')
const index = parts.findIndex((part) => part.includes(`${key}:${value}`))
const newParts = removeAtIndexAndBefore(parts, index, operators.includes(parts[index - 1]))
return newParts.join(' ')
export function queryHasFilter(query: string, key: string, value: string): boolean {
return query.includes(getKeyValue(key, value))
}

export const addLabelToQuery = (query: string, key: string, value: string, operator: string): string => {
return `${query} ${operator} ${key}:${value}`
const [filters, ...pipes] = splitExpression(query)
const insertPart = `${operator} ${getKeyValue(key, value)}`
const pipesPart = pipes?.length ? `| ${pipes.join(' | ')}` : ''
return (`${filters} ${insertPart} ${pipesPart}`).trim()
}

const removeAtIndexAndBefore = (arr: string[], index: number, removeBefore: boolean): string[] => {
if (index < 0 || index >= arr.length) {
return arr;
export const removeLabelFromQuery = (query: string, key: string, value: string): string => {
const { query: { filters, pipes }, errors } = buildVisualQueryFromString(query);
if (errors.length) {
console.error(errors.join('\n'));
return query;
}

if (removeBefore) {
const isStart = index === 0;
arr.splice(isStart ? index : index - 1, isStart ? 1 : 2);
} else {
arr.splice(index, 1);
const keyValue = getKeyValue(key, value);
recursiveRemove(filters, keyValue)
return parseVisualQueryToString({ filters, pipes })
}

const recursiveRemove = (filters: FilterVisualQuery, keyValue: string): boolean => {
const { values, operators } = filters;
let removed = false;

for (let i = values.length - 1; i >= 0; i--) {
const val = values[i];
const isString = typeof val === 'string'
const isFilterObject = typeof val === 'object' && 'values' in val

if (isString && val === keyValue) {
// If the string matches keyValue, delete it and the operator
values.splice(i, 1);
(i > 0 && i - 1 < operators.length) && operators.splice(i - 1, 1);
removed = true;
} else if (isFilterObject) {
// If it is an object of type FilterVisualQuery, recursively check it
const wasRemoved = recursiveRemove(val, keyValue);
removed = wasRemoved || removed;
}
}

return arr;
return removed;
}

0 comments on commit 6ba3ee3

Please sign in to comment.