mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 07:56:56 +00:00
fix: added fix for supporting older queries (#8834)
* fix: added fix for supporting older queries * fix: added fix for exist operator * chore: minor fix for quick filters * chore: added tests for convertfilterstoexpression * chore: added fix for regex to regexp conversion * test: added test for regex to regexp * fix: added fix for functions conversion and tests * fix: added fix for negated non_value_operators
This commit is contained in:
parent
674556d672
commit
4f45801729
536
frontend/src/components/QueryBuilderV2/__tests__/utils.test.ts
Normal file
536
frontend/src/components/QueryBuilderV2/__tests__/utils.test.ts
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import { convertFiltersToExpression } from '../utils';
|
||||||
|
|
||||||
|
describe('convertFiltersToExpression', () => {
|
||||||
|
it('should handle empty, null, and undefined inputs', () => {
|
||||||
|
// Test null and undefined
|
||||||
|
expect(convertFiltersToExpression(null as any)).toEqual({ expression: '' });
|
||||||
|
expect(convertFiltersToExpression(undefined as any)).toEqual({
|
||||||
|
expression: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test empty filters
|
||||||
|
expect(convertFiltersToExpression({ items: [], op: 'AND' })).toEqual({
|
||||||
|
expression: '',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
convertFiltersToExpression({ items: undefined, op: 'AND' } as any),
|
||||||
|
).toEqual({ expression: '' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert basic comparison operators with proper value formatting', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: 'api-gateway',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'status', type: 'string' },
|
||||||
|
op: '!=',
|
||||||
|
value: 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'duration', type: 'number' },
|
||||||
|
op: '>',
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'count', type: 'number' },
|
||||||
|
op: '<=',
|
||||||
|
value: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'is_active', type: 'boolean' },
|
||||||
|
op: '=',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
key: { key: 'enabled', type: 'boolean' },
|
||||||
|
op: '=',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
key: { key: 'count', type: 'number' },
|
||||||
|
op: '=',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
key: { key: 'regex', type: 'string' },
|
||||||
|
op: 'regex',
|
||||||
|
value: '.*',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"service = 'api-gateway' AND status != 'error' AND duration > 100 AND count <= 50 AND is_active = true AND enabled = false AND count = 0 AND regex REGEXP '.*'",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle string value formatting and escaping', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'message', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: "user's data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'description', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'path', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: '/api/v1/users',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"message = 'user\\'s data' AND description = '' AND path = '/api/v1/users'",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle IN operator with various value types and array formatting', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: ['api-gateway', 'user-service', 'auth-service'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'status', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: 'success', // Single value should be converted to array
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: [], // Empty array
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'name', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: ["John's", "Mary's", 'Bob'], // Values with quotes
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"service in ['api-gateway', 'user-service', 'auth-service'] AND status in ['success'] AND tags in [] AND name in ['John\\'s', 'Mary\\'s', 'Bob']",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert deprecated operators to their modern equivalents', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: 'nin',
|
||||||
|
value: ['api-gateway', 'user-service'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'message', type: 'string' },
|
||||||
|
op: 'nlike',
|
||||||
|
value: 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'path', type: 'string' },
|
||||||
|
op: 'nregex',
|
||||||
|
value: '/api/.*',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: 'NIN', // Test case insensitivity
|
||||||
|
value: ['api-gateway'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'user_id', type: 'string' },
|
||||||
|
op: 'nexists',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
key: { key: 'description', type: 'string' },
|
||||||
|
op: 'ncontains',
|
||||||
|
value: 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'nhas',
|
||||||
|
value: 'production',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
key: { key: 'labels', type: 'string' },
|
||||||
|
op: 'nhasany',
|
||||||
|
value: ['env:prod', 'service:api'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"service NOT IN ['api-gateway', 'user-service'] AND message NOT LIKE 'error' AND path NOT REGEXP '/api/.*' AND service NOT IN ['api-gateway'] AND user_id NOT EXISTS AND description NOT CONTAINS 'error' AND NOT has(tags, 'production') AND NOT hasAny(labels, ['env:prod', 'service:api'])",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-value operators and function operators', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'user_id', type: 'string' },
|
||||||
|
op: 'EXISTS',
|
||||||
|
value: '', // Value should be ignored for EXISTS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'user_id', type: 'string' },
|
||||||
|
op: 'EXISTS',
|
||||||
|
value: 'some-value', // Value should be ignored for EXISTS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'has',
|
||||||
|
value: 'production',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'hasAny',
|
||||||
|
value: ['production', 'staging'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'hasAll',
|
||||||
|
value: ['production', 'monitoring'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"user_id exists AND user_id exists AND has(tags, 'production') AND hasAny(tags, ['production', 'staging']) AND hasAll(tags, ['production', 'monitoring'])",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out invalid filters and handle edge cases', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: 'api-gateway',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: undefined, // Invalid filter - should be skipped
|
||||||
|
op: '=',
|
||||||
|
value: 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: '', type: 'string' }, // Invalid filter with empty key - should be skipped
|
||||||
|
op: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'status', type: 'string' },
|
||||||
|
op: ' = ', // Test whitespace handling
|
||||||
|
value: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: 'In', // Test mixed case handling
|
||||||
|
value: ['api-gateway'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"service = 'api-gateway' AND status = 'success' AND service in ['api-gateway']",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex mixed operator scenarios with proper joining', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'service', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: ['api-gateway', 'user-service'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'user_id', type: 'string' },
|
||||||
|
op: 'EXISTS',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'has',
|
||||||
|
value: 'production',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'duration', type: 'number' },
|
||||||
|
op: '>',
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'status', type: 'string' },
|
||||||
|
op: 'nin',
|
||||||
|
value: ['error', 'timeout'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
key: { key: 'method', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: 'POST',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"service in ['api-gateway', 'user-service'] AND user_id exists AND has(tags, 'production') AND duration > 100 AND status NOT IN ['error', 'timeout'] AND method = 'POST'",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle all numeric comparison operators and edge cases', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'count', type: 'number' },
|
||||||
|
op: '=',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'score', type: 'number' },
|
||||||
|
op: '>',
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'limit', type: 'number' },
|
||||||
|
op: '>=',
|
||||||
|
value: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'threshold', type: 'number' },
|
||||||
|
op: '<',
|
||||||
|
value: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'max_value', type: 'number' },
|
||||||
|
op: '<=',
|
||||||
|
value: 999,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
key: { key: 'values', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: ['1', '2', '3', '4', '5'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"count = 0 AND score > 100 AND limit >= 50 AND threshold < 1000 AND max_value <= 999 AND values in ['1', '2', '3', '4', '5']",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle boolean values and string comparisons with special characters', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'is_active', type: 'boolean' },
|
||||||
|
op: '=',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'is_deleted', type: 'boolean' },
|
||||||
|
op: '=',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'email', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: 'user@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'description', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: 'Contains "quotes" and \'apostrophes\'',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'path', type: 'string' },
|
||||||
|
op: '=',
|
||||||
|
value: '/api/v1/users/123?filter=true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"is_active = true AND is_deleted = false AND email = 'user@example.com' AND description = 'Contains \"quotes\" and \\'apostrophes\\'' AND path = '/api/v1/users/123?filter=true'",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle all function operators and complex array scenarios', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'has',
|
||||||
|
value: 'production',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'labels', type: 'string' },
|
||||||
|
op: 'hasAny',
|
||||||
|
value: ['env:prod', 'service:api'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'metadata', type: 'string' },
|
||||||
|
op: 'hasAll',
|
||||||
|
value: ['version:1.0', 'team:backend'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'services', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: ['api-gateway', 'user-service', 'auth-service', 'payment-service'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
key: { key: 'excluded_services', type: 'string' },
|
||||||
|
op: 'nin',
|
||||||
|
value: ['legacy-service', 'deprecated-service'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
key: { key: 'status_codes', type: 'string' },
|
||||||
|
op: 'IN',
|
||||||
|
value: ['200', '201', '400', '500'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"has(tags, 'production') AND hasAny(labels, ['env:prod', 'service:api']) AND hasAll(metadata, ['version:1.0', 'team:backend']) AND services in ['api-gateway', 'user-service', 'auth-service', 'payment-service'] AND excluded_services NOT IN ['legacy-service', 'deprecated-service'] AND status_codes in ['200', '201', '400', '500']",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle specific deprecated operators: nhas, ncontains, nexists', () => {
|
||||||
|
const filters: TagFilter = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: { key: 'user_id', type: 'string' },
|
||||||
|
op: 'nexists',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: { key: 'description', type: 'string' },
|
||||||
|
op: 'ncontains',
|
||||||
|
value: 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
key: { key: 'tags', type: 'string' },
|
||||||
|
op: 'nhas',
|
||||||
|
value: 'production',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
key: { key: 'labels', type: 'string' },
|
||||||
|
op: 'nhasany',
|
||||||
|
value: ['env:prod', 'service:api'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = convertFiltersToExpression(filters);
|
||||||
|
expect(result).toEqual({
|
||||||
|
expression:
|
||||||
|
"user_id NOT EXISTS AND description NOT CONTAINS 'error' AND NOT has(tags, 'production') AND NOT hasAny(labels, ['env:prod', 'service:api'])",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,6 +1,10 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
|
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
|
||||||
import { OPERATORS } from 'constants/antlrQueryConstants';
|
import {
|
||||||
|
DEPRECATED_OPERATORS_MAP,
|
||||||
|
OPERATORS,
|
||||||
|
QUERY_BUILDER_FUNCTIONS,
|
||||||
|
} from 'constants/antlrQueryConstants';
|
||||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { IQueryPair } from 'types/antlrQueryTypes';
|
import { IQueryPair } from 'types/antlrQueryTypes';
|
||||||
@ -21,7 +25,7 @@ import { EQueryType } from 'types/common/dashboard';
|
|||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { extractQueryPairs } from 'utils/queryContextUtils';
|
import { extractQueryPairs } from 'utils/queryContextUtils';
|
||||||
import { unquote } from 'utils/stringUtils';
|
import { unquote } from 'utils/stringUtils';
|
||||||
import { isFunctionOperator } from 'utils/tokenUtils';
|
import { isFunctionOperator, isNonValueOperator } from 'utils/tokenUtils';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,12 +91,32 @@ export const convertFiltersToExpression = (
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFunctionOperator(op)) {
|
let operator = op.trim().toLowerCase();
|
||||||
return `${op}(${key.key}, ${value})`;
|
if (Object.keys(DEPRECATED_OPERATORS_MAP).includes(operator)) {
|
||||||
|
operator =
|
||||||
|
DEPRECATED_OPERATORS_MAP[
|
||||||
|
operator as keyof typeof DEPRECATED_OPERATORS_MAP
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedValue = formatValueForExpression(value, op);
|
if (isNonValueOperator(operator)) {
|
||||||
return `${key.key} ${op} ${formattedValue}`;
|
return `${key.key} ${operator}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFunctionOperator(operator)) {
|
||||||
|
// Get the proper function name from QUERY_BUILDER_FUNCTIONS
|
||||||
|
const functionOperators = Object.values(QUERY_BUILDER_FUNCTIONS);
|
||||||
|
const properFunctionName =
|
||||||
|
functionOperators.find(
|
||||||
|
(func: string) => func.toLowerCase() === operator.toLowerCase(),
|
||||||
|
) || operator;
|
||||||
|
|
||||||
|
const formattedValue = formatValueForExpression(value, operator);
|
||||||
|
return `${properFunctionName}(${key.key}, ${formattedValue})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedValue = formatValueForExpression(value, operator);
|
||||||
|
return `${key.key} ${operator} ${formattedValue}`;
|
||||||
})
|
})
|
||||||
.filter((expression) => expression !== ''); // Remove empty expressions
|
.filter((expression) => expression !== ''); // Remove empty expressions
|
||||||
|
|
||||||
@ -117,7 +141,6 @@ export const convertExpressionToFilters = (
|
|||||||
if (!expression) return [];
|
if (!expression) return [];
|
||||||
|
|
||||||
const queryPairs = extractQueryPairs(expression);
|
const queryPairs = extractQueryPairs(expression);
|
||||||
|
|
||||||
const filters: TagFilterItem[] = [];
|
const filters: TagFilterItem[] = [];
|
||||||
|
|
||||||
queryPairs.forEach((pair) => {
|
queryPairs.forEach((pair) => {
|
||||||
@ -145,19 +168,36 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
|||||||
filters: TagFilter,
|
filters: TagFilter,
|
||||||
existingQuery: string | undefined,
|
existingQuery: string | undefined,
|
||||||
): { filters: TagFilter; filter: { expression: string } } => {
|
): { filters: TagFilter; filter: { expression: string } } => {
|
||||||
|
// Check for deprecated operators and replace them with new operators
|
||||||
|
const updatedFilters = cloneDeep(filters);
|
||||||
|
|
||||||
|
// Replace deprecated operators in filter items
|
||||||
|
if (updatedFilters?.items) {
|
||||||
|
updatedFilters.items = updatedFilters.items.map((item) => {
|
||||||
|
const opLower = item.op?.toLowerCase();
|
||||||
|
if (Object.keys(DEPRECATED_OPERATORS_MAP).includes(opLower)) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
op: DEPRECATED_OPERATORS_MAP[
|
||||||
|
opLower as keyof typeof DEPRECATED_OPERATORS_MAP
|
||||||
|
].toLowerCase(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!existingQuery) {
|
if (!existingQuery) {
|
||||||
// If no existing query, return filters with a newly generated expression
|
// If no existing query, return filters with a newly generated expression
|
||||||
return {
|
return {
|
||||||
filters,
|
filters: updatedFilters,
|
||||||
filter: convertFiltersToExpression(filters),
|
filter: convertFiltersToExpression(updatedFilters),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract query pairs from the existing query
|
// Extract query pairs from the existing query
|
||||||
const queryPairs = extractQueryPairs(existingQuery.trim());
|
const queryPairs = extractQueryPairs(existingQuery.trim());
|
||||||
let queryPairsMap: Map<string, IQueryPair> = new Map();
|
let queryPairsMap: Map<string, IQueryPair> = new Map();
|
||||||
|
|
||||||
const updatedFilters = cloneDeep(filters); // Clone filters to avoid direct mutation
|
|
||||||
const nonExistingFilters: TagFilterItem[] = [];
|
const nonExistingFilters: TagFilterItem[] = [];
|
||||||
let modifiedQuery = existingQuery; // We'll modify this query as we proceed
|
let modifiedQuery = existingQuery; // We'll modify this query as we proceed
|
||||||
const visitedPairs: Set<string> = new Set(); // Set to track visited query pairs
|
const visitedPairs: Set<string> = new Set(); // Set to track visited query pairs
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
export const OPERATORS = {
|
export const OPERATORS = {
|
||||||
IN: 'IN',
|
IN: 'IN',
|
||||||
LIKE: 'LIKE',
|
LIKE: 'LIKE',
|
||||||
@ -21,6 +23,44 @@ export const QUERY_BUILDER_FUNCTIONS = {
|
|||||||
HASALL: 'hasAll',
|
HASALL: 'hasAll',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function negateOperator(operatorOrFunction: string): string {
|
||||||
|
// Special cases for equals/not equals
|
||||||
|
if (operatorOrFunction === OPERATORS['=']) {
|
||||||
|
return OPERATORS['!='];
|
||||||
|
}
|
||||||
|
if (operatorOrFunction === OPERATORS['!=']) {
|
||||||
|
return OPERATORS['='];
|
||||||
|
}
|
||||||
|
// For all other operators and functions, add NOT in front
|
||||||
|
return `${OPERATORS.NOT} ${operatorOrFunction}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DEPRECATED_OPERATORS {
|
||||||
|
REGEX = 'regex',
|
||||||
|
NIN = 'nin',
|
||||||
|
NREGEX = 'nregex',
|
||||||
|
NLIKE = 'nlike',
|
||||||
|
NILIKE = 'nilike',
|
||||||
|
NEXTISTS = 'nexists',
|
||||||
|
NCONTAINS = 'ncontains',
|
||||||
|
NHAS = 'nhas',
|
||||||
|
NHASANY = 'nhasany',
|
||||||
|
NHASALL = 'nhasall',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEPRECATED_OPERATORS_MAP = {
|
||||||
|
[DEPRECATED_OPERATORS.REGEX]: OPERATORS.REGEXP,
|
||||||
|
[DEPRECATED_OPERATORS.NIN]: negateOperator(OPERATORS.IN),
|
||||||
|
[DEPRECATED_OPERATORS.NREGEX]: negateOperator(OPERATORS.REGEXP),
|
||||||
|
[DEPRECATED_OPERATORS.NLIKE]: negateOperator(OPERATORS.LIKE),
|
||||||
|
[DEPRECATED_OPERATORS.NILIKE]: negateOperator(OPERATORS.ILIKE),
|
||||||
|
[DEPRECATED_OPERATORS.NEXTISTS]: negateOperator(OPERATORS.EXISTS),
|
||||||
|
[DEPRECATED_OPERATORS.NCONTAINS]: negateOperator(OPERATORS.CONTAINS),
|
||||||
|
[DEPRECATED_OPERATORS.NHAS]: negateOperator(QUERY_BUILDER_FUNCTIONS.HAS),
|
||||||
|
[DEPRECATED_OPERATORS.NHASANY]: negateOperator(QUERY_BUILDER_FUNCTIONS.HASANY),
|
||||||
|
[DEPRECATED_OPERATORS.NHASALL]: negateOperator(QUERY_BUILDER_FUNCTIONS.HASALL),
|
||||||
|
};
|
||||||
|
|
||||||
export const NON_VALUE_OPERATORS = [OPERATORS.EXISTS];
|
export const NON_VALUE_OPERATORS = [OPERATORS.EXISTS];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
@ -82,15 +122,3 @@ export const queryOperatorSuggestions = [
|
|||||||
{ label: OPERATORS.NOT, type: 'operator', info: 'Not' },
|
{ label: OPERATORS.NOT, type: 'operator', info: 'Not' },
|
||||||
...negationQueryOperatorSuggestions,
|
...negationQueryOperatorSuggestions,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function negateOperator(operatorOrFunction: string): string {
|
|
||||||
// Special cases for equals/not equals
|
|
||||||
if (operatorOrFunction === OPERATORS['=']) {
|
|
||||||
return OPERATORS['!='];
|
|
||||||
}
|
|
||||||
if (operatorOrFunction === OPERATORS['!=']) {
|
|
||||||
return OPERATORS['='];
|
|
||||||
}
|
|
||||||
// For all other operators and functions, add NOT in front
|
|
||||||
return `${OPERATORS.NOT} ${operatorOrFunction}`;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -100,16 +100,36 @@ export function isFunctionOperator(operator: string): boolean {
|
|||||||
const functionOperators = Object.values(QUERY_BUILDER_FUNCTIONS);
|
const functionOperators = Object.values(QUERY_BUILDER_FUNCTIONS);
|
||||||
|
|
||||||
const sanitizedOperator = operator.trim();
|
const sanitizedOperator = operator.trim();
|
||||||
// Check if it's a direct function operator
|
// Check if it's a direct function operator (case-insensitive)
|
||||||
if (functionOperators.includes(sanitizedOperator)) {
|
if (
|
||||||
|
functionOperators.some(
|
||||||
|
(func) => func.toLowerCase() === sanitizedOperator.toLowerCase(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a NOT function operator (e.g., "NOT has")
|
// Check if it's a NOT function operator (e.g., "NOT has")
|
||||||
if (sanitizedOperator.toUpperCase().startsWith(OPERATORS.NOT)) {
|
if (sanitizedOperator.toUpperCase().startsWith(OPERATORS.NOT)) {
|
||||||
const operatorWithoutNot = sanitizedOperator.substring(4).toLowerCase();
|
const operatorWithoutNot = sanitizedOperator.substring(4).toLowerCase();
|
||||||
return functionOperators.includes(operatorWithoutNot);
|
return functionOperators.some(
|
||||||
|
(func) => func.toLowerCase() === operatorWithoutNot,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isNonValueOperator(operator: string): boolean {
|
||||||
|
const upperOperator = operator.toUpperCase();
|
||||||
|
// Check if it's a direct non-value operator
|
||||||
|
if (NON_VALUE_OPERATORS.includes(upperOperator)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check if it's a NOT non-value operator (e.g., "NOT EXISTS")
|
||||||
|
if (upperOperator.startsWith(OPERATORS.NOT)) {
|
||||||
|
const operatorWithoutNot = upperOperator.substring(4).trim(); // Remove "NOT " prefix
|
||||||
|
return NON_VALUE_OPERATORS.includes(operatorWithoutNot);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user