mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-21 15:50:27 +01:00
Compare commits
3 Commits
fix/quick-
...
fix/parent
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4120b53c27 | ||
|
|
a7cbfe4587 | ||
|
|
74d480e113 |
@@ -1,16 +1,10 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
|
||||
import { OPERATORS } from 'constants/antlrQueryConstants';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { IQueryPair } from 'types/antlrQueryTypes';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
Having,
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
TagFilter,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
LogAggregation,
|
||||
@@ -19,8 +13,6 @@ import {
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { extractQueryPairs } from 'utils/queryContextUtils';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
/**
|
||||
* Check if an operator requires array values (like IN, NOT IN)
|
||||
@@ -28,7 +20,7 @@ import { v4 as uuid } from 'uuid';
|
||||
* @returns True if the operator requires array values
|
||||
*/
|
||||
const isArrayOperator = (operator: string): boolean => {
|
||||
const arrayOperators = ['in', 'not in', 'IN', 'NOT IN'];
|
||||
const arrayOperators = ['in', 'nin', 'IN', 'NOT IN'];
|
||||
return arrayOperators.includes(operator);
|
||||
};
|
||||
|
||||
@@ -95,330 +87,6 @@ export const convertFiltersToExpression = (
|
||||
};
|
||||
};
|
||||
|
||||
function unquote(str: string): string {
|
||||
if (typeof str !== 'string') return str;
|
||||
|
||||
const startsWithQuote = str.startsWith('"') || str.startsWith("'");
|
||||
const endsWithSameQuote =
|
||||
(str.endsWith('"') && str[0] === '"') ||
|
||||
(str.endsWith("'") && str[0] === "'");
|
||||
|
||||
if (startsWithQuote && endsWithSameQuote && str.length >= 2) {
|
||||
return str.slice(1, -1);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const formatValuesForFilter = (value: string | string[]): string | string[] => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((v) => (typeof v === 'string' ? unquote(v) : String(v)));
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return unquote(value);
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
export const convertFiltersToExpressionWithExistingQuery = (
|
||||
filters: TagFilter,
|
||||
existingQuery: string | undefined,
|
||||
): { filters: TagFilter; filter: { expression: string } } => {
|
||||
if (!existingQuery) {
|
||||
// If no existing query, return filters with a newly generated expression
|
||||
return {
|
||||
filters,
|
||||
filter: convertFiltersToExpression(filters),
|
||||
};
|
||||
}
|
||||
|
||||
// Extract query pairs from the existing query
|
||||
const queryPairs = extractQueryPairs(existingQuery.trim());
|
||||
let queryPairsMap: Map<string, IQueryPair> = new Map();
|
||||
|
||||
const updatedFilters = cloneDeep(filters); // Clone filters to avoid direct mutation
|
||||
const nonExistingFilters: TagFilterItem[] = [];
|
||||
let modifiedQuery = existingQuery; // We'll modify this query as we proceed
|
||||
const visitedPairs: Set<string> = new Set(); // Set to track visited query pairs
|
||||
|
||||
// Map extracted query pairs to key-specific pair information for faster access
|
||||
if (queryPairs.length > 0) {
|
||||
queryPairsMap = new Map(
|
||||
queryPairs.map((pair) => {
|
||||
const key = pair.hasNegation
|
||||
? `${pair.key}-not ${pair.operator}`.trim().toLowerCase()
|
||||
: `${pair.key}-${pair.operator}`.trim().toLowerCase();
|
||||
return [key, pair];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
filters.items.forEach((filter) => {
|
||||
const { key, op, value } = filter;
|
||||
|
||||
// Skip invalid filters with no key
|
||||
if (!key) return;
|
||||
|
||||
let shouldAddToNonExisting = true; // Flag to decide if the filter should be added to non-existing filters
|
||||
const sanitizedOperator = op.trim().toUpperCase();
|
||||
|
||||
// Check if the operator is IN or NOT IN
|
||||
if (
|
||||
[OPERATORS.IN, `${OPERATORS.NOT} ${OPERATORS.IN}`].includes(
|
||||
sanitizedOperator,
|
||||
)
|
||||
) {
|
||||
const existingPair = queryPairsMap.get(
|
||||
`${key.key}-${op}`.trim().toLowerCase(),
|
||||
);
|
||||
const formattedValue = formatValueForExpression(value, op);
|
||||
|
||||
// If a matching query pair exists, modify the query
|
||||
if (
|
||||
existingPair &&
|
||||
existingPair.position?.valueStart &&
|
||||
existingPair.position?.valueEnd
|
||||
) {
|
||||
visitedPairs.add(`${key.key}-${op}`.trim().toLowerCase());
|
||||
modifiedQuery =
|
||||
modifiedQuery.slice(0, existingPair.position.valueStart) +
|
||||
formattedValue +
|
||||
modifiedQuery.slice(existingPair.position.valueEnd + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the different cases for IN operator
|
||||
switch (sanitizedOperator) {
|
||||
case OPERATORS.IN:
|
||||
// If there's a NOT IN or equal operator, merge the filter
|
||||
if (
|
||||
queryPairsMap.has(
|
||||
`${key.key}-${OPERATORS.NOT} ${op}`.trim().toLowerCase(),
|
||||
)
|
||||
) {
|
||||
const notInPair = queryPairsMap.get(
|
||||
`${key.key}-${OPERATORS.NOT} ${op}`.trim().toLowerCase(),
|
||||
);
|
||||
visitedPairs.add(
|
||||
`${key.key}-${OPERATORS.NOT} ${op}`.trim().toLowerCase(),
|
||||
);
|
||||
if (notInPair?.position?.valueEnd) {
|
||||
modifiedQuery = `${modifiedQuery.slice(
|
||||
0,
|
||||
notInPair.position.negationStart,
|
||||
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
|
||||
notInPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
} else if (
|
||||
queryPairsMap.has(`${key.key}-${OPERATORS['=']}`.trim().toLowerCase())
|
||||
) {
|
||||
const equalsPair = queryPairsMap.get(
|
||||
`${key.key}-${OPERATORS['=']}`.trim().toLowerCase(),
|
||||
);
|
||||
visitedPairs.add(`${key.key}-${OPERATORS['=']}`.trim().toLowerCase());
|
||||
if (equalsPair?.position?.valueEnd) {
|
||||
modifiedQuery = `${modifiedQuery.slice(
|
||||
0,
|
||||
equalsPair.position.operatorStart,
|
||||
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
|
||||
equalsPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
} else if (
|
||||
queryPairsMap.has(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase())
|
||||
) {
|
||||
const notEqualsPair = queryPairsMap.get(
|
||||
`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase(),
|
||||
);
|
||||
visitedPairs.add(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase());
|
||||
if (notEqualsPair?.position?.valueEnd) {
|
||||
modifiedQuery = `${modifiedQuery.slice(
|
||||
0,
|
||||
notEqualsPair.position.operatorStart,
|
||||
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
|
||||
notEqualsPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
}
|
||||
break;
|
||||
case `${OPERATORS.NOT} ${OPERATORS.IN}`:
|
||||
if (
|
||||
queryPairsMap.has(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase())
|
||||
) {
|
||||
const notEqualsPair = queryPairsMap.get(
|
||||
`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase(),
|
||||
);
|
||||
visitedPairs.add(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase());
|
||||
if (notEqualsPair?.position?.valueEnd) {
|
||||
modifiedQuery = `${modifiedQuery.slice(
|
||||
0,
|
||||
notEqualsPair.position.operatorStart,
|
||||
)}${OPERATORS.NOT} ${
|
||||
OPERATORS.IN
|
||||
} ${formattedValue} ${modifiedQuery.slice(
|
||||
notEqualsPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
}
|
||||
break; // No operation needed for NOT IN case
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
queryPairsMap.has(`${filter.key?.key}-${filter.op}`.trim().toLowerCase())
|
||||
) {
|
||||
visitedPairs.add(`${filter.key?.key}-${filter.op}`.trim().toLowerCase());
|
||||
}
|
||||
|
||||
// Add filters that don't have an existing pair to non-existing filters
|
||||
if (
|
||||
shouldAddToNonExisting &&
|
||||
!queryPairsMap.has(`${filter.key?.key}-${filter.op}`.trim().toLowerCase())
|
||||
) {
|
||||
nonExistingFilters.push(filter);
|
||||
}
|
||||
});
|
||||
|
||||
// Create new filters from non-visited query pairs
|
||||
const newFilterItems: TagFilterItem[] = [];
|
||||
queryPairsMap.forEach((pair, key) => {
|
||||
if (!visitedPairs.has(key)) {
|
||||
const operator = pair.hasNegation
|
||||
? getOperatorValue(`NOT_${pair.operator}`.toUpperCase())
|
||||
: getOperatorValue(pair.operator.toUpperCase());
|
||||
|
||||
newFilterItems.push({
|
||||
id: uuid(),
|
||||
op: operator,
|
||||
key: {
|
||||
id: pair.key,
|
||||
key: pair.key,
|
||||
type: '',
|
||||
},
|
||||
value: pair.isMultiValue
|
||||
? formatValuesForFilter(pair.valueList as string[]) ?? ''
|
||||
: formatValuesForFilter(pair.value as string) ?? '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Merge new filter items with existing ones
|
||||
if (newFilterItems.length > 0) {
|
||||
updatedFilters.items = [...updatedFilters.items, ...newFilterItems];
|
||||
}
|
||||
|
||||
// If no non-existing filters, return the modified query directly
|
||||
if (nonExistingFilters.length === 0) {
|
||||
return {
|
||||
filters: updatedFilters,
|
||||
filter: { expression: modifiedQuery },
|
||||
};
|
||||
}
|
||||
|
||||
// Convert non-existing filters to an expression and append to the modified query
|
||||
const nonExistingFilterExpression = convertFiltersToExpression({
|
||||
items: nonExistingFilters,
|
||||
op: filters.op || 'AND',
|
||||
});
|
||||
|
||||
if (nonExistingFilterExpression.expression) {
|
||||
return {
|
||||
filters: updatedFilters,
|
||||
filter: {
|
||||
expression: `${modifiedQuery.trim()} ${
|
||||
nonExistingFilterExpression.expression
|
||||
}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Return the final result with the modified query
|
||||
return {
|
||||
filters: updatedFilters,
|
||||
filter: { expression: modifiedQuery || '' },
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes specified key-value pairs from a logical query expression string.
|
||||
*
|
||||
* This function parses the given query expression and removes any query pairs
|
||||
* whose keys match those in the `keysToRemove` array. It also removes any trailing
|
||||
* logical conjunctions (e.g., `AND`, `OR`) and whitespace that follow the matched pairs,
|
||||
* ensuring that the resulting expression remains valid and clean.
|
||||
*
|
||||
* @param expression - The full query string.
|
||||
* @param keysToRemove - An array of keys (case-insensitive) that should be removed from the expression.
|
||||
* @returns A new expression string with the specified keys and their associated clauses removed.
|
||||
*/
|
||||
export const removeKeysFromExpression = (
|
||||
expression: string,
|
||||
keysToRemove: string[],
|
||||
): string => {
|
||||
if (!keysToRemove || keysToRemove.length === 0) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
let updatedExpression = expression;
|
||||
|
||||
if (updatedExpression) {
|
||||
keysToRemove.forEach((key) => {
|
||||
// Extract key-value query pairs from the expression
|
||||
const existingQueryPairs = extractQueryPairs(updatedExpression);
|
||||
|
||||
let queryPairsMap: Map<string, IQueryPair>;
|
||||
|
||||
if (existingQueryPairs.length > 0) {
|
||||
// Build a map for quick lookup of query pairs by their lowercase trimmed keys
|
||||
queryPairsMap = new Map(
|
||||
existingQueryPairs.map((pair) => {
|
||||
const key = pair.key.trim().toLowerCase();
|
||||
return [key, pair];
|
||||
}),
|
||||
);
|
||||
|
||||
// Lookup the current query pair using the attribute key (case-insensitive)
|
||||
const currentQueryPair = queryPairsMap.get(`${key}`.trim().toLowerCase());
|
||||
if (currentQueryPair && currentQueryPair.isComplete) {
|
||||
// Determine the start index of the query pair (fallback order: key → operator → value)
|
||||
const queryPairStart =
|
||||
currentQueryPair.position.keyStart ??
|
||||
currentQueryPair.position.operatorStart ??
|
||||
currentQueryPair.position.valueStart;
|
||||
// Determine the end index of the query pair (fallback order: value → operator → key)
|
||||
let queryPairEnd =
|
||||
currentQueryPair.position.valueEnd ??
|
||||
currentQueryPair.position.operatorEnd ??
|
||||
currentQueryPair.position.keyEnd;
|
||||
// Get the part of the expression that comes after the current query pair
|
||||
const expressionAfterPair = `${expression.slice(queryPairEnd + 1)}`;
|
||||
// Match optional spaces and an optional conjunction (AND/OR), case-insensitive
|
||||
const conjunctionOrSpacesRegex = /^(\s*((AND|OR)\s+)?)/i;
|
||||
const match = expressionAfterPair.match(conjunctionOrSpacesRegex);
|
||||
if (match && match.length > 0) {
|
||||
// If match is found, extend the queryPairEnd to include the matched part
|
||||
queryPairEnd += match[0].length;
|
||||
}
|
||||
// Remove the full query pair (including any conjunction/whitespace) from the expression
|
||||
updatedExpression = `${expression.slice(
|
||||
0,
|
||||
queryPairStart,
|
||||
)}${expression.slice(queryPairEnd + 1)}`.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return updatedExpression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert old having format to new having format
|
||||
* @param having - Array of old having objects with columnName, op, and value
|
||||
|
||||
@@ -6,13 +6,14 @@ import './Checkbox.styles.scss';
|
||||
|
||||
import { Button, Checkbox, Input, Skeleton, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
IQuickFiltersConfig,
|
||||
QuickFiltersSource,
|
||||
} from 'components/QuickFilters/types';
|
||||
import { OPERATORS } from 'constants/antlrQueryConstants';
|
||||
import { DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY } from 'constants/queryBuilder';
|
||||
import {
|
||||
DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY,
|
||||
OPERATORS,
|
||||
} from 'constants/queryBuilder';
|
||||
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues';
|
||||
@@ -29,7 +30,7 @@ import { v4 as uuid } from 'uuid';
|
||||
import LogsQuickFilterEmptyState from './LogsQuickFilterEmptyState';
|
||||
|
||||
const SELECTED_OPERATORS = [OPERATORS['='], 'in'];
|
||||
const NON_SELECTED_OPERATORS = [OPERATORS['!='], 'not in'];
|
||||
const NON_SELECTED_OPERATORS = [OPERATORS['!='], 'nin'];
|
||||
|
||||
const SOURCES_WITH_EMPTY_STATE_ENABLED = [QuickFiltersSource.LOGS_EXPLORER];
|
||||
|
||||
@@ -167,11 +168,6 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item, idx) => ({
|
||||
...item,
|
||||
filter: {
|
||||
expression: removeKeysFromExpression(item.filter?.expression ?? '', [
|
||||
filter.attributeKey.key,
|
||||
]),
|
||||
},
|
||||
filters: {
|
||||
...item.filters,
|
||||
items:
|
||||
@@ -217,14 +213,6 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(q) => !isEqual(q.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
|
||||
if (query.filter?.expression) {
|
||||
query.filter.expression = removeKeysFromExpression(
|
||||
query.filter.expression,
|
||||
[filter.attributeKey.key],
|
||||
);
|
||||
}
|
||||
|
||||
if (isOnlyOrAll === 'Only') {
|
||||
const newFilterItem: TagFilterItem = {
|
||||
id: uuid(),
|
||||
@@ -305,7 +293,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'not in':
|
||||
case 'nin':
|
||||
// if the current running operator is NIN then when unchecking the value it gets
|
||||
// added to the clause like key NIN [value1 , currentUnselectedValue]
|
||||
if (!checked) {
|
||||
@@ -384,7 +372,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
if (!checked) {
|
||||
const newFilter = {
|
||||
...currentFilter,
|
||||
op: getOperatorValue('NOT_IN'),
|
||||
op: getOperatorValue(OPERATORS.NIN),
|
||||
value: [currentFilter.value as string, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
@@ -407,7 +395,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
// case - when there is no filter for the current key that means all are selected right now.
|
||||
const newFilterItem: TagFilterItem = {
|
||||
id: uuid(),
|
||||
op: getOperatorValue('NOT_IN'),
|
||||
op: getOperatorValue(OPERATORS.NIN),
|
||||
key: filter.attributeKey,
|
||||
value,
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ export const CompositeQueryOperatorsConfig: Array<{
|
||||
traceValue: 'In',
|
||||
},
|
||||
{
|
||||
label: 'not in',
|
||||
label: 'nin',
|
||||
metricValue: '!~',
|
||||
traceValue: 'NotIn',
|
||||
},
|
||||
@@ -49,7 +49,7 @@ export const CompositeQueryOperatorsConfig: Array<{
|
||||
traceValue: 'Exists',
|
||||
},
|
||||
{
|
||||
label: 'not exists',
|
||||
label: 'nexists',
|
||||
metricValue: '!~',
|
||||
traceValue: 'NotExists',
|
||||
},
|
||||
@@ -59,7 +59,7 @@ export const CompositeQueryOperatorsConfig: Array<{
|
||||
traceValue: 'Contains',
|
||||
},
|
||||
{
|
||||
label: 'not contains',
|
||||
label: 'ncontains',
|
||||
metricValue: '!~',
|
||||
traceValue: 'NotContains',
|
||||
},
|
||||
|
||||
@@ -58,31 +58,27 @@ export function getOperatorValue(op: string): string {
|
||||
case 'IN':
|
||||
return 'in';
|
||||
case 'NOT_IN':
|
||||
return 'not in';
|
||||
return 'nin';
|
||||
case OPERATORS.REGEX:
|
||||
return 'regex';
|
||||
case OPERATORS.HAS:
|
||||
return 'has';
|
||||
case OPERATORS.NHAS:
|
||||
return 'not has';
|
||||
return 'nhas';
|
||||
case OPERATORS.NREGEX:
|
||||
return 'not regex';
|
||||
return 'nregex';
|
||||
case 'LIKE':
|
||||
return 'like';
|
||||
case 'ILIKE':
|
||||
return 'ilike';
|
||||
case 'NOT_LIKE':
|
||||
return 'not like';
|
||||
case 'NOT_ILIKE':
|
||||
return 'not ilike';
|
||||
return 'nlike';
|
||||
case 'EXISTS':
|
||||
return 'exists';
|
||||
case 'NOT_EXISTS':
|
||||
return 'not exists';
|
||||
return 'nexists';
|
||||
case 'CONTAINS':
|
||||
return 'contains';
|
||||
case 'NOT_CONTAINS':
|
||||
return 'not contains';
|
||||
return 'ncontains';
|
||||
default:
|
||||
return op;
|
||||
}
|
||||
@@ -92,27 +88,27 @@ export function getOperatorFromValue(op: string): string {
|
||||
switch (op) {
|
||||
case 'in':
|
||||
return 'IN';
|
||||
case 'not in':
|
||||
case 'nin':
|
||||
return 'NOT_IN';
|
||||
case 'like':
|
||||
return 'LIKE';
|
||||
case 'regex':
|
||||
return OPERATORS.REGEX;
|
||||
case 'not regex':
|
||||
case 'nregex':
|
||||
return OPERATORS.NREGEX;
|
||||
case 'not like':
|
||||
case 'nlike':
|
||||
return 'NOT_LIKE';
|
||||
case 'exists':
|
||||
return 'EXISTS';
|
||||
case 'not exists':
|
||||
case 'nexists':
|
||||
return 'NOT_EXISTS';
|
||||
case 'contains':
|
||||
return 'CONTAINS';
|
||||
case 'not contains':
|
||||
case 'ncontains':
|
||||
return 'NOT_CONTAINS';
|
||||
case 'has':
|
||||
return OPERATORS.HAS;
|
||||
case 'not has':
|
||||
case 'nhas':
|
||||
return OPERATORS.NHAS;
|
||||
default:
|
||||
return op;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Select } from 'antd';
|
||||
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -120,18 +119,8 @@ function SpanScopeSelector({
|
||||
return [...nonScopeFilters, ...newScopeFilter];
|
||||
};
|
||||
|
||||
const keysToRemove = Object.values(SPAN_FILTER_CONFIG)
|
||||
.map((config) => config?.key)
|
||||
.filter((key): key is string => typeof key === 'string');
|
||||
|
||||
newQuery.builder.queryData = newQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filter: {
|
||||
expression: removeKeysFromExpression(
|
||||
item.filter?.expression ?? '',
|
||||
keysToRemove,
|
||||
),
|
||||
},
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: getUpdatedFilters(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
convertAggregationToExpression,
|
||||
convertFiltersToExpressionWithExistingQuery,
|
||||
convertFiltersToExpression,
|
||||
convertHavingToExpression,
|
||||
} from 'components/QueryBuilderV2/utils';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -28,15 +28,13 @@ export const useGetCompositeQueryParam = (): Query | null => {
|
||||
if (parsedCompositeQuery?.builder?.queryData) {
|
||||
parsedCompositeQuery.builder.queryData = parsedCompositeQuery.builder.queryData.map(
|
||||
(query) => {
|
||||
const existingExpression = query.filter?.expression || '';
|
||||
const convertedQuery = { ...query };
|
||||
|
||||
const convertedFilter = convertFiltersToExpressionWithExistingQuery(
|
||||
query.filters,
|
||||
existingExpression,
|
||||
);
|
||||
convertedQuery.filter = convertedFilter.filter;
|
||||
convertedQuery.filters = convertedFilter.filters;
|
||||
// Convert filters if needed
|
||||
if (query.filters?.items?.length > 0 && !query.filter?.expression) {
|
||||
const convertedFilter = convertFiltersToExpression(query.filters);
|
||||
convertedQuery.filter = convertedFilter;
|
||||
}
|
||||
|
||||
// Convert having if needed
|
||||
if (query.having?.length > 0 && !query.havingExpression?.expression) {
|
||||
@@ -55,6 +53,7 @@ export const useGetCompositeQueryParam = (): Query | null => {
|
||||
) as any; // Type assertion to handle union type
|
||||
convertedQuery.aggregations = convertedAggregation;
|
||||
}
|
||||
|
||||
return convertedQuery;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -718,7 +718,6 @@ export function getQueryContextAtCursor(
|
||||
isInValueBoundary ||
|
||||
isInConjunctionBoundary ||
|
||||
isInBracketListBoundary ||
|
||||
isInParenthesisBoundary ||
|
||||
isAfterClosingBracketList
|
||||
) {
|
||||
// Extract information from the current pair (if available)
|
||||
@@ -968,6 +967,30 @@ export function getQueryContextAtCursor(
|
||||
currentPair: currentPair,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
lastTokenContext.isInParenthesis &&
|
||||
lastTokenBeforeCursor.type === FilterQueryLexer.RPAREN
|
||||
) {
|
||||
// If we are after a parenthesis we should enter the conjunction context.
|
||||
return {
|
||||
tokenType: lastTokenBeforeCursor.type,
|
||||
text: lastTokenBeforeCursor.text,
|
||||
start: adjustedCursorIndex,
|
||||
stop: adjustedCursorIndex,
|
||||
currentToken: lastTokenBeforeCursor.text,
|
||||
isInKey: false,
|
||||
isInNegation: false,
|
||||
isInOperator: false,
|
||||
isInValue: false,
|
||||
isInFunction: false,
|
||||
isInConjunction: true, // After RPARAN + space, should be conjunction context
|
||||
isInParenthesis: false,
|
||||
isInBracketList: false,
|
||||
queryPairs: queryPairs,
|
||||
currentPair: currentPair,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// FIXED: Consider the case where the cursor is at the end of a token
|
||||
@@ -1454,6 +1477,19 @@ export function extractQueryPairs(query: string): IQueryPair[] {
|
||||
}
|
||||
}
|
||||
|
||||
function getIndexTillSpace(pair: IQueryPair, query: string): number {
|
||||
const { position } = pair;
|
||||
let pairEnd = position.valueEnd || position.operatorEnd || position.keyEnd;
|
||||
|
||||
// Start from the next index after pairEnd
|
||||
pairEnd += 1;
|
||||
while (pairEnd < query.length && query.charAt(pairEnd) === ' ') {
|
||||
pairEnd += 1;
|
||||
}
|
||||
|
||||
return pairEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current query pair at the cursor position
|
||||
* This is useful for getting suggestions based on the current context
|
||||
@@ -1483,12 +1519,14 @@ export function getCurrentQueryPair(
|
||||
position.valueEnd || position.operatorEnd || position.keyEnd;
|
||||
|
||||
const pairStart =
|
||||
position.keyStart || position.operatorStart || position.valueStart || 0;
|
||||
position.keyStart ?? (position.operatorStart || position.valueStart || 0);
|
||||
|
||||
// If this pair ends at or before the cursor, and it's further right than our previous best match
|
||||
if (
|
||||
pairEnd >= cursorIndex &&
|
||||
pairStart <= cursorIndex &&
|
||||
((pairEnd >= cursorIndex && pairStart <= cursorIndex) ||
|
||||
(!pair.isComplete &&
|
||||
pairStart <= cursorIndex &&
|
||||
getIndexTillSpace(pair, query) >= cursorIndex)) &&
|
||||
(!bestMatch ||
|
||||
pairEnd >
|
||||
(bestMatch.position.valueEnd ||
|
||||
|
||||
Reference in New Issue
Block a user