From bf704333b3f6ac23f25eaf01ce90d47e7ceae2ca Mon Sep 17 00:00:00 2001 From: Abhi kumar Date: Fri, 5 Sep 2025 21:30:24 +0530 Subject: [PATCH] Feature/trace operators (#8869) * feat: added traceoperator component and styles * chore: minor style improvments * feat: added conditions for traceoperator * chore: minor UI fixes * chore: type changes * chore: added initialvalue for trace operators * chore: Added changes to prepare request payload * chore: fixed minor styles + minor ux fix * feat: added span selector * chore: added ui changes in the editor * chore: removed traceoperations and reused queryoperations * chore: minor changes in queryaddon and aggregation for support * chore: added traceoperators in alerts * chore: minor pr review change * chore: linter fix * fix: fixed minor ts issues * fix: added limit support in traceoperator * chore: minor fix in traceoperator styles * chore: linting fix + icon changes * chore: updated type * chore: lint fixes * feat: added changes for showing querynames in alerts * feat: added trace operator grammer + antlr files * feat: added traceoperator context util * chore: added traceoperator validation function * feat: added traceoperator editor * feat: added queryname boost + operator constants * fix: pr reviews * chore: minor ui fix * fix: updated grammer files * test: added test for traceoperatorcontext * chore: removed check for multiple queries in traceexplorer * test: minor test fix * test: fixed breaking mapQueryDataFromApi test * chore: fixed logic to show trace operator * chore: updated docs link * chore: minor ui issue fix * chore: changed trace operator query name * chore: removed using spans from in trace opeartors * fix: added fix for order by in trace opeartor * feat: added changes related to saved in views in trace opeartor * chore: added changes to keep indirect descendent operator at bottom * chore: removed returnspansfrom field from traceoperator * chore: updated file names + regenerated grammer * chore: added beta tag in trace opeartor * chore: pr review fixes * Fix/tsc trace operator + tests (#8942) * fix: added tsc fixes for trace operator * chore: moved traceoperator utils * test: added test for traceopertor util * chore: tsc fix * fix: fixed tsc issue * Feat/trace operator dashboards (#8992) * chore: added callout message for multiple queries without trace operators * feat: added changes for supporting trace operators in dashboards * chore: minor changes for list panel --- frontend/.eslintignore | 3 +- frontend/.prettierignore | 4 +- frontend/package.json | 1 + .../prepareQueryRangePayloadV5.test.ts | 10 +- .../queryRange/prepareQueryRangePayloadV5.ts | 135 ++++- .../HostMetricTraces/constants.ts | 1 + .../HostMetricsLogs/constants.ts | 1 + .../QueryBuilderV2/QueryBuilderV2.styles.scss | 26 +- .../QueryBuilderV2/QueryBuilderV2.tsx | 87 ++- .../QueryAddOns/QueryAddOns.styles.scss | 6 +- .../QueryV2/QueryAddOns/QueryAddOns.tsx | 3 + .../QueryAggregation/QueryAggregation.tsx | 7 +- .../QueryV2/QueryFooter/QueryFooter.tsx | 93 ++- .../QuerySearch/QuerySearch.styles.scss | 1 + .../QueryBuilderV2/QueryV2/QueryV2.tsx | 98 ++- .../TraceOperator/TraceOperator.styles.scss | 159 +++++ .../QueryV2/TraceOperator/TraceOperator.tsx | 119 ++++ .../TraceOperator/TraceOperatorEditor.tsx | 491 +++++++++++++++ .../traceOperatorContextUtils.test.ts | 425 +++++++++++++ .../TraceOperator/__tests__/utils.test.ts | 46 ++ .../utils/traceOperatorContextUtils.ts | 562 ++++++++++++++++++ .../QueryV2/TraceOperator/utils/utils.ts | 22 + .../SignozRadioGroup.styles.scss | 13 + .../SignozRadioGroup/SignozRadioGroup.tsx | 6 +- frontend/src/constants/antlrQueryConstants.ts | 21 + frontend/src/constants/queryBuilder.ts | 9 + frontend/src/constants/regExp.ts | 4 + .../src/container/ApiMonitoring/utils.tsx | 11 + .../container/FormAlertRules/QuerySection.tsx | 1 + .../src/container/FormAlertRules/index.tsx | 10 +- .../src/container/FormAlertRules/utils.ts | 7 +- .../GridCard/WidgetGraphComponent.test.tsx | 1 + .../GridCardLayout/__tests__/utils.test.ts | 5 + .../GridTableComponent/__tests__/response.ts | 2 + .../Clusters/ClusterDetails/constants.ts | 8 + .../DaemonSets/DaemonSetDetails/constants.ts | 4 + .../DeploymentDetails/constants.ts | 4 + .../EntityDetailsUtils/utils.tsx | 2 + .../Jobs/JobDetails/constants.ts | 4 + .../Namespaces/NamespaceDetails/constants.ts | 10 + .../Nodes/NodeDetails/constants.ts | 10 + .../Pods/PodDetails/constants.ts | 13 + .../StatefulSetDetails/constants.ts | 6 + .../Volumes/VolumeDetails/constants.ts | 5 + .../ContextView/__tests__/mockData.ts | 1 + .../LogDetailedView/InfraMetrics/constants.ts | 23 + .../container/LogsExplorerViews/tests/mock.ts | 4 + .../MeterExplorer/Breakdown/graphs.ts | 1 + .../MetricsPageQueriesFactory.ts | 2 + .../Explorer/useGetRelatedMetricsGraphs.ts | 1 + .../MetricsExplorer/MetricDetails/utils.tsx | 1 + .../LeftContainer/QuerySection/index.tsx | 1 + .../__tests__/ColumnSelector.test.tsx | 1 + .../NewWidget/__test__/utils.test.ts | 1 + frontend/src/container/NewWidget/utils.ts | 4 + .../LogsConnectionStatus.tsx | 1 + .../QueryBuilder/QueryBuilder.interfaces.ts | 9 + .../QBEntityOptions/QBEntityOptions.tsx | 12 +- .../components/Query/Query.interfaces.ts | 3 + .../TraceDetail/SelectedSpanDetails/config.ts | 1 + .../TracesExplorer/ListView/index.tsx | 13 + .../TracesExplorer/QuerySection/index.tsx | 10 +- .../queryBuilder/useQueryBuilderOperations.ts | 16 +- .../__tests__/mapQueryDataFromApiInputs.ts | 4 + .../mapCompositeQueryFromQuery.ts | 7 +- .../mapQueryDataFromApi.ts | 14 +- .../transformQueryBuilderDataModel.ts | 18 +- .../__tests__/LogsExplorer.test.tsx | 5 + .../MQDetails/MetricPage/MetricPageUtil.ts | 1 + .../MessagingQueues/MessagingQueuesUtils.ts | 1 + frontend/src/pages/TracesExplorer/index.tsx | 102 ++-- .../TraceOperatorGrammar.interp | 33 + .../TraceOperatorGrammar.tokens | 16 + .../TraceOperatorGrammarLexer.interp | 44 ++ .../TraceOperatorGrammarLexer.tokens | 16 + .../TraceOperatorGrammarLexer.ts | 92 +++ .../TraceOperatorGrammarListener.ts | 58 ++ .../TraceOperatorGrammarParser.ts | 423 +++++++++++++ .../TraceOperatorGrammarVisitor.ts | 45 ++ frontend/src/providers/QueryBuilder.tsx | 147 ++++- .../api/queryBuilder/queryBuilderData.ts | 5 +- frontend/src/types/api/v5/queryRange.ts | 2 + frontend/src/types/common/operations.types.ts | 10 + frontend/src/types/common/queryBuilder.ts | 9 + .../utils/compositeQueryToQueryEnvelope.ts | 25 +- frontend/src/utils/queryValidationUtils.ts | 65 ++ frontend/tsconfig.json | 2 +- frontend/yarn.lock | 19 + grammar/TraceOperatorGrammar.g4 | 39 ++ 89 files changed, 3616 insertions(+), 147 deletions(-) create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.styles.scss create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.tsx create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/traceOperatorContextUtils.test.ts create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/utils.test.ts create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/traceOperatorContextUtils.ts create mode 100644 frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils.ts create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammar.interp create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammar.tokens create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.interp create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.tokens create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.ts create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarListener.ts create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarParser.ts create mode 100644 frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarVisitor.ts create mode 100644 grammar/TraceOperatorGrammar.g4 diff --git a/frontend/.eslintignore b/frontend/.eslintignore index 402f7ae028e9..e9d38dfe02c1 100644 --- a/frontend/.eslintignore +++ b/frontend/.eslintignore @@ -1,4 +1,5 @@ node_modules build *.typegen.ts -i18-generate-hash.js \ No newline at end of file +i18-generate-hash.js +src/parser/TraceOperatorParser/** \ No newline at end of file diff --git a/frontend/.prettierignore b/frontend/.prettierignore index a3a90bf3c62e..0b802be4b4ee 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -10,4 +10,6 @@ public/ **/*.json # Ignore all files in parser folder: -src/parser/** \ No newline at end of file +src/parser/** + +src/TraceOperator/parser/** \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index e04e2847b359..b1b5d100226e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,6 +45,7 @@ "@sentry/webpack-plugin": "2.22.6", "@signozhq/badge": "0.0.2", "@signozhq/calendar": "0.0.0", + "@signozhq/callout": "0.0.2", "@signozhq/design-tokens": "1.1.4", "@signozhq/input": "0.0.2", "@signozhq/popover": "0.0.0", diff --git a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.test.ts b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.test.ts index aef670e54929..952ee1c09125 100644 --- a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.test.ts +++ b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.test.ts @@ -92,6 +92,7 @@ describe('prepareQueryRangePayloadV5', () => { builder: { queryData: [baseBuilderQuery()], queryFormulas: [baseFormula()], + queryTraceOperator: [], }, }, graphType: PANEL_TYPES.TIME_SERIES, @@ -215,7 +216,7 @@ describe('prepareQueryRangePayloadV5', () => { }, ], clickhouse_sql: [], - builder: { queryData: [], queryFormulas: [] }, + builder: { queryData: [], queryFormulas: [], queryTraceOperator: [] }, }, graphType: PANEL_TYPES.TIME_SERIES, originalGraphType: PANEL_TYPES.TABLE, @@ -286,7 +287,7 @@ describe('prepareQueryRangePayloadV5', () => { legend: 'LC', }, ], - builder: { queryData: [], queryFormulas: [] }, + builder: { queryData: [], queryFormulas: [], queryTraceOperator: [] }, }, graphType: PANEL_TYPES.TABLE, selectedTime: 'GLOBAL_TIME', @@ -345,7 +346,7 @@ describe('prepareQueryRangePayloadV5', () => { unit: undefined, promql: [], clickhouse_sql: [], - builder: { queryData: [], queryFormulas: [] }, + builder: { queryData: [], queryFormulas: [], queryTraceOperator: [] }, }, graphType: PANEL_TYPES.TIME_SERIES, selectedTime: 'GLOBAL_TIME', @@ -386,6 +387,7 @@ describe('prepareQueryRangePayloadV5', () => { builder: { queryData: [baseBuilderQuery()], queryFormulas: [], + queryTraceOperator: [], }, }, graphType: PANEL_TYPES.TABLE, @@ -459,6 +461,7 @@ describe('prepareQueryRangePayloadV5', () => { builder: { queryData: [logsQuery], queryFormulas: [], + queryTraceOperator: [], }, }, graphType: PANEL_TYPES.LIST, @@ -572,6 +575,7 @@ describe('prepareQueryRangePayloadV5', () => { }, ], queryFormulas: [], + queryTraceOperator: [], }, }, graphType: PANEL_TYPES.TIME_SERIES, diff --git a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts index f5c036a31d12..16e43c36e74c 100644 --- a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts +++ b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts @@ -1,11 +1,15 @@ /* eslint-disable sonarjs/cognitive-complexity */ +/* eslint-disable sonarjs/no-identical-functions */ import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import getStartEndRangeTime from 'lib/getStartEndRangeTime'; import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi'; import { isEmpty } from 'lodash-es'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQuery, + IBuilderTraceOperator, +} from 'types/api/queryBuilder/queryBuilderData'; import { BaseBuilderQuery, FieldContext, @@ -332,6 +336,109 @@ export function convertBuilderQueriesToV5( ); } +function createTraceOperatorBaseSpec( + queryData: IBuilderTraceOperator, + requestType: RequestType, + panelType?: PANEL_TYPES, +): BaseBuilderQuery { + const nonEmptySelectColumns = (queryData.selectColumns as ( + | BaseAutocompleteData + | TelemetryFieldKey + )[])?.filter((c) => ('key' in c ? c?.key : c?.name)); + + const { + stepInterval, + groupBy, + limit, + offset, + legend, + having, + orderBy, + pageSize, + } = queryData; + + return { + stepInterval: stepInterval || undefined, + groupBy: + groupBy?.length > 0 + ? groupBy.map( + (item: any): GroupByKey => ({ + name: item.key, + fieldDataType: item?.dataType, + fieldContext: item?.type, + description: item?.description, + unit: item?.unit, + signal: item?.signal, + materialized: item?.materialized, + }), + ) + : undefined, + limit: + panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST + ? limit || pageSize || undefined + : limit || undefined, + offset: requestType === 'raw' || requestType === 'trace' ? offset : undefined, + order: + orderBy?.length > 0 + ? orderBy.map( + (order: any): OrderBy => ({ + key: { + name: order.columnName, + }, + direction: order.order, + }), + ) + : undefined, + legend: isEmpty(legend) ? undefined : legend, + having: isEmpty(having) ? undefined : (having as Having), + selectFields: isEmpty(nonEmptySelectColumns) + ? undefined + : nonEmptySelectColumns?.map( + (column: any): TelemetryFieldKey => ({ + name: column.name ?? column.key, + fieldDataType: + column?.fieldDataType ?? (column?.dataType as FieldDataType), + fieldContext: column?.fieldContext ?? (column?.type as FieldContext), + signal: column?.signal ?? undefined, + }), + ), + }; +} + +export function convertTraceOperatorToV5( + traceOperator: Record, + requestType: RequestType, + panelType?: PANEL_TYPES, +): QueryEnvelope[] { + return Object.entries(traceOperator).map( + ([queryName, traceOperatorData]): QueryEnvelope => { + const baseSpec = createTraceOperatorBaseSpec( + traceOperatorData, + requestType, + panelType, + ); + + // Skip aggregation for raw request type + const aggregations = + requestType === 'raw' + ? undefined + : createAggregation(traceOperatorData, panelType); + + const spec: QueryEnvelope['spec'] = { + name: queryName, + ...baseSpec, + expression: traceOperatorData.expression || '', + aggregations: aggregations as TraceAggregation[], + }; + + return { + type: 'builder_trace_operator' as QueryType, + spec, + }; + }, + ); +} + /** * Converts PromQL queries to V5 format */ @@ -413,14 +520,28 @@ export const prepareQueryRangePayloadV5 = ({ switch (query.queryType) { case EQueryType.QUERY_BUILDER: { - const { queryData: data, queryFormulas } = query.builder; + const { queryData: data, queryFormulas, queryTraceOperator } = query.builder; const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams); const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName'); + const filteredTraceOperator = + queryTraceOperator && queryTraceOperator.length > 0 + ? queryTraceOperator.filter((traceOperator) => + Boolean(traceOperator.expression.trim()), + ) + : []; + + const currentTraceOperator = mapQueryDataToApi( + filteredTraceOperator, + 'queryName', + tableParams, + ); + // Combine legend maps legendMap = { ...currentQueryData.newLegendMap, ...currentFormulas.newLegendMap, + ...currentTraceOperator.newLegendMap, }; // Convert builder queries @@ -453,8 +574,14 @@ export const prepareQueryRangePayloadV5 = ({ }), ); - // Combine both types - queries = [...builderQueries, ...formulaQueries]; + const traceOperatorQueries = convertTraceOperatorToV5( + currentTraceOperator.data, + requestType, + graphType, + ); + + // Combine all query types + queries = [...builderQueries, ...formulaQueries, ...traceOperatorQueries]; break; } case EQueryType.PROM: { diff --git a/frontend/src/components/HostMetricsDetail/HostMetricTraces/constants.ts b/frontend/src/components/HostMetricsDetail/HostMetricTraces/constants.ts index 80a4d3ed2173..474c87fc0d3e 100644 --- a/frontend/src/components/HostMetricsDetail/HostMetricTraces/constants.ts +++ b/frontend/src/components/HostMetricsDetail/HostMetricTraces/constants.ts @@ -125,6 +125,7 @@ export const getHostTracesQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, id: '572f1d91-6ac0-46c0-b726-c21488b34434', queryType: EQueryType.QUERY_BUILDER, diff --git a/frontend/src/components/HostMetricsDetail/HostMetricsLogs/constants.ts b/frontend/src/components/HostMetricsDetail/HostMetricsLogs/constants.ts index 694a841993ca..917526c0ca26 100644 --- a/frontend/src/components/HostMetricsDetail/HostMetricsLogs/constants.ts +++ b/frontend/src/components/HostMetricsDetail/HostMetricsLogs/constants.ts @@ -51,6 +51,7 @@ export const getHostLogsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, id: uuidv4(), queryType: EQueryType.QUERY_BUILDER, diff --git a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.styles.scss b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.styles.scss index f5404ea8776a..fcfafc3688cc 100644 --- a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.styles.scss +++ b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.styles.scss @@ -22,6 +22,10 @@ flex: 1; position: relative; + + .qb-trace-view-selector-container { + padding: 12px 8px 8px 8px; + } } .qb-content-section { @@ -179,7 +183,7 @@ flex-direction: column; gap: 8px; - margin-left: 32px; + margin-left: 26px; padding-bottom: 16px; padding-left: 8px; @@ -195,8 +199,8 @@ } .formula-container { - margin-left: 82px; - padding: 4px 0px; + padding: 8px; + margin-left: 74px; .ant-col { &::before { @@ -291,6 +295,13 @@ ); } } + .qb-trace-operator-button-container { + &-text { + display: flex; + align-items: center; + gap: 8px; + } + } } } @@ -331,6 +342,12 @@ ); left: 15px; } + + &.has-trace-operator { + &::before { + height: 0px; + } + } } .formula-name { @@ -347,7 +364,7 @@ &::before { content: ''; - height: 65px; + height: 128px; content: ''; position: absolute; left: 0; @@ -387,6 +404,7 @@ } .qb-search-filter-container { + flex: 1; display: flex; flex-direction: row; align-items: flex-start; diff --git a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx index d0621dad7a02..7be666c27084 100644 --- a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx @@ -5,11 +5,13 @@ import { Formula } from 'container/QueryBuilder/components/Formula'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { memo, useEffect, useMemo, useRef } from 'react'; +import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { QueryBuilderV2Provider } from './QueryBuilderV2Context'; import QueryFooter from './QueryV2/QueryFooter/QueryFooter'; import { QueryV2 } from './QueryV2/QueryV2'; +import TraceOperator from './QueryV2/TraceOperator/TraceOperator'; export const QueryBuilderV2 = memo(function QueryBuilderV2({ config, @@ -18,6 +20,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({ queryComponents, isListViewPanel = false, showOnlyWhereClause = false, + showTraceOperator = false, version, }: QueryBuilderProps): JSX.Element { const { @@ -25,6 +28,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({ addNewBuilderQuery, addNewFormula, handleSetConfig, + addTraceOperator, panelType, initialDataSource, } = useQueryBuilder(); @@ -54,6 +58,11 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({ newPanelType, ]); + const isMultiQueryAllowed = useMemo( + () => !isListViewPanel || showTraceOperator, + [showTraceOperator, isListViewPanel], + ); + const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { const config: QueryBuilderProps['filterConfigs'] = { stepInterval: { isHidden: true, isDisabled: true }, @@ -97,11 +106,60 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({ listViewTracesFilterConfigs, ]); + const traceOperator = useMemo((): IBuilderTraceOperator | undefined => { + if ( + currentQuery.builder.queryTraceOperator && + currentQuery.builder.queryTraceOperator.length > 0 + ) { + return currentQuery.builder.queryTraceOperator[0]; + } + + return undefined; + }, [currentQuery.builder.queryTraceOperator]); + + const hasAtLeastOneTraceQuery = useMemo( + () => + currentQuery.builder.queryData.some( + (query) => query.dataSource === DataSource.TRACES, + ), + [currentQuery.builder.queryData], + ); + + const hasTraceOperator = useMemo( + () => showTraceOperator && hasAtLeastOneTraceQuery && Boolean(traceOperator), + [showTraceOperator, traceOperator, hasAtLeastOneTraceQuery], + ); + + const shouldShowFooter = useMemo( + () => + (!showOnlyWhereClause && !isListViewPanel) || + (currentDataSource === DataSource.TRACES && showTraceOperator), + [isListViewPanel, showTraceOperator, showOnlyWhereClause, currentDataSource], + ); + + const showQueryList = useMemo( + () => (!showOnlyWhereClause && !isListViewPanel) || showTraceOperator, + [isListViewPanel, showOnlyWhereClause, showTraceOperator], + ); + + const showFormula = useMemo(() => { + if (currentDataSource === DataSource.TRACES) { + return !isListViewPanel; + } + + return true; + }, [isListViewPanel, currentDataSource]); + + const showAddTraceOperator = useMemo( + () => showTraceOperator && !traceOperator && hasAtLeastOneTraceQuery, + [showTraceOperator, traceOperator, hasAtLeastOneTraceQuery], + ); + return (
- {isListViewPanel && ( + {!isMultiQueryAllowed ? ( - )} - - {!isListViewPanel && + ) : ( currentQuery.builder.queryData.map((query, index) => ( - ))} + )) + )} {!showOnlyWhereClause && currentQuery.builder.queryFormulas.length > 0 && (
@@ -158,15 +221,25 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
)} - {!showOnlyWhereClause && !isListViewPanel && ( + {shouldShowFooter && ( + )} + + {hasTraceOperator && ( + )}
- {!showOnlyWhereClause && !isListViewPanel && ( + {showQueryList && (
{currentQuery.builder.queryData.map((query) => (
diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.styles.scss b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.styles.scss index 90cc001e4c8c..db7bd881f405 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.styles.scss +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.styles.scss @@ -1,7 +1,11 @@ +.query-add-ons { + width: 100%; +} + .add-ons-list { display: flex; - justify-content: space-between; align-items: center; + gap: 16px; .add-ons-tabs { display: flex; diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.tsx index 211ed3da2810..997390f20989 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.tsx @@ -144,6 +144,7 @@ function QueryAddOns({ showReduceTo, panelType, index, + isForTraceOperator = false, }: { query: IBuilderQuery; version: string; @@ -151,6 +152,7 @@ function QueryAddOns({ showReduceTo: boolean; panelType: PANEL_TYPES | null; index: number; + isForTraceOperator?: boolean; }): JSX.Element { const [addOns, setAddOns] = useState(ADD_ONS); @@ -160,6 +162,7 @@ function QueryAddOns({ index, query, entityVersion: '', + isForTraceOperator, }); const { handleSetQueryData } = useQueryBuilder(); diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx index 137718428f75..1a728cc504bd 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx @@ -4,7 +4,10 @@ import { Tooltip } from 'antd'; import InputWithLabel from 'components/InputWithLabel/InputWithLabel'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { useMemo } from 'react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQuery, + IBuilderTraceOperator, +} from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import QueryAggregationSelect from './QueryAggregationSelect'; @@ -20,7 +23,7 @@ function QueryAggregationOptions({ panelType?: string; onAggregationIntervalChange: (value: number) => void; onChange?: (value: string) => void; - queryData: IBuilderQuery; + queryData: IBuilderQuery | IBuilderTraceOperator; }): JSX.Element { const showAggregationInterval = useMemo(() => { // eslint-disable-next-line sonarjs/prefer-single-boolean-return diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryFooter/QueryFooter.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryFooter/QueryFooter.tsx index 94750cf8389a..9907cbdc5d38 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryFooter/QueryFooter.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryFooter/QueryFooter.tsx @@ -1,12 +1,20 @@ +/* eslint-disable react/require-default-props */ import { Button, Tooltip, Typography } from 'antd'; -import { Plus, Sigma } from 'lucide-react'; +import { DraftingCompass, Plus, Sigma } from 'lucide-react'; +import BetaTag from 'periscope/components/BetaTag/BetaTag'; export default function QueryFooter({ addNewBuilderQuery, addNewFormula, + addTraceOperator, + showAddFormula = true, + showAddTraceOperator = false, }: { addNewBuilderQuery: () => void; addNewFormula: () => void; + addTraceOperator?: () => void; + showAddTraceOperator: boolean; + showAddFormula?: boolean; }): JSX.Element { return (
@@ -22,32 +30,65 @@ export default function QueryFooter({
-
- - Add New Formula - - {' '} -
- Learn more -
-
- } - > - - -
+ + +
+ )} + {showAddTraceOperator && ( +
+ + Add Trace Matching + + {' '} +
+ Learn more +
+
+ } + > + + +
+ )} ); diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss index 75abfba59e10..28303f429437 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss @@ -7,6 +7,7 @@ 'Helvetica Neue', sans-serif; .query-where-clause-editor-container { + position: relative; display: flex; flex-direction: row; diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx index 2108161a307f..b628164efe51 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/cognitive-complexity */ import { Dropdown } from 'antd'; import cx from 'classnames'; import { ENTITY_VERSION_V4, ENTITY_VERSION_V5 } from 'constants/app'; @@ -26,9 +27,12 @@ export const QueryV2 = memo(function QueryV2({ query, filterConfigs, isListViewPanel = false, + showTraceOperator = false, + hasTraceOperator = false, version, showOnlyWhereClause = false, signalSource = '', + isMultiQueryAllowed = false, }: QueryProps & { ref: React.RefObject }): JSX.Element { const { cloneQuery, panelType } = useQueryBuilder(); @@ -75,6 +79,15 @@ export const QueryV2 = memo(function QueryV2({ dataSource, ]); + const showInlineQuerySearch = useMemo(() => { + if (!showTraceOperator) { + return false; + } + return ( + dataSource === DataSource.TRACES && (hasTraceOperator || isListViewPanel) + ); + }, [hasTraceOperator, isListViewPanel, showTraceOperator, dataSource]); + const handleChangeAggregateEvery = useCallback( (value: IBuilderQuery['stepInterval']) => { handleChangeQueryData('stepInterval', value); @@ -108,11 +121,12 @@ export const QueryV2 = memo(function QueryV2({ ref={ref} >
- {!showOnlyWhereClause && ( + {(!showOnlyWhereClause || showTraceOperator) && (
- {!isListViewPanel && ( + {!isCollapsed && showInlineQuerySearch && ( +
+
+ +
+ + {showSpanScopeSelector && ( +
+
in
+ +
+ )} +
+ )} + + {isMultiQueryAllowed && ( )} -
-
- -
- - {showSpanScopeSelector && ( -
-
in
- + {!showInlineQuerySearch && ( +
+
+
- )} -
+ + {showSpanScopeSelector && ( +
+
in
+ +
+ )} +
+ )}
{!showOnlyWhereClause && !isListViewPanel && + !(hasTraceOperator && dataSource === DataSource.TRACES) && dataSource !== DataSource.METRICS && ( )} - {!showOnlyWhereClause && ( - - )} + {!showOnlyWhereClause && + !(hasTraceOperator && query.dataSource === DataSource.TRACES) && ( + + )}
)}
diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.styles.scss b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.styles.scss new file mode 100644 index 000000000000..6e9eca66a4d4 --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.styles.scss @@ -0,0 +1,159 @@ +.qb-trace-operator { + padding: 8px; + display: flex; + gap: 8px; + + &.non-list-view { + padding-left: 40px; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 24px; + left: 12px; + height: 88px; + width: 1px; + background: repeating-linear-gradient( + to bottom, + var(--bg-slate-400), + var(--bg-slate-400) 4px, + transparent 4px, + transparent 8px + ); + } + } + + &-arrow { + position: relative; + &::before { + content: ''; + position: absolute; + top: 16px; + transform: translateY(-50%); + left: -26px; + height: 1px; + width: 20px; + background: repeating-linear-gradient( + to right, + var(--bg-slate-400), + var(--bg-slate-400) 4px, + transparent 4px, + transparent 8px + ); + } + + &::after { + content: ''; + position: absolute; + top: 16px; + left: -10px; + transform: translateY(-50%); + height: 4px; + width: 4px; + border-radius: 50%; + background-color: var(--bg-slate-400); + } + } + + &-input { + width: 100%; + } + + &-container { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + } + + &-aggregation-container { + display: flex; + flex-direction: column; + gap: 8px; + } + + &-add-ons-container { + width: 100%; + display: flex; + flex-direction: row; + gap: 16px; + } + + &-label-with-input { + position: relative; + display: flex; + align-items: center; + flex-direction: row; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + + .qb-trace-operator-editor-container { + flex: 1; + } + + &.arrow-left { + &::before { + content: ''; + position: absolute; + left: -16px; + top: 50%; + height: 1px; + width: 16px; + background-color: var(--bg-slate-400); + } + } + + .label { + color: var(--bg-vanilla-400); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 0px 8px; + border-right: 1px solid var(--bg-slate-400); + } + } +} + +.lightMode { + .qb-trace-operator { + &-arrow { + &::before { + background: repeating-linear-gradient( + to right, + var(--bg-vanilla-300), + var(--bg-vanilla-300) 4px, + transparent 4px, + transparent 8px + ); + } + &::after { + background-color: var(--bg-vanilla-300); + } + } + &.non-list-view { + &::before { + background: repeating-linear-gradient( + to bottom, + var(--bg-vanilla-300), + var(--bg-vanilla-300) 4px, + transparent 4px, + transparent 8px + ); + } + } + + &-label-with-input { + border: 1px solid var(--bg-vanilla-300) !important; + background: var(--bg-vanilla-100) !important; + + .label { + color: var(--bg-ink-500) !important; + border-right: 1px solid var(--bg-vanilla-300) !important; + background: var(--bg-vanilla-100) !important; + } + } + } +} diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.tsx new file mode 100644 index 000000000000..764ae9426b99 --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperator.tsx @@ -0,0 +1,119 @@ +/* eslint-disable react/require-default-props */ +/* eslint-disable sonarjs/no-duplicate-string */ + +import './TraceOperator.styles.scss'; + +import { Button, Tooltip, Typography } from 'antd'; +import cx from 'classnames'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; +import { Trash2 } from 'lucide-react'; +import { useCallback } from 'react'; +import { + IBuilderQuery, + IBuilderTraceOperator, +} from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import QueryAddOns from '../QueryAddOns/QueryAddOns'; +import QueryAggregation from '../QueryAggregation/QueryAggregation'; +import TraceOperatorEditor from './TraceOperatorEditor'; + +export default function TraceOperator({ + traceOperator, + isListViewPanel = false, +}: { + traceOperator: IBuilderTraceOperator; + isListViewPanel?: boolean; +}): JSX.Element { + const { panelType, removeTraceOperator } = useQueryBuilder(); + const { handleChangeQueryData } = useQueryOperations({ + index: 0, + query: traceOperator, + entityVersion: '', + isForTraceOperator: true, + }); + + const handleTraceOperatorChange = useCallback( + (traceOperatorExpression: string) => { + handleChangeQueryData('expression', traceOperatorExpression); + }, + [handleChangeQueryData], + ); + + const handleChangeAggregateEvery = useCallback( + (value: IBuilderQuery['stepInterval']) => { + handleChangeQueryData('stepInterval', value); + }, + [handleChangeQueryData], + ); + + const handleChangeAggregation = useCallback( + (value: string) => { + handleChangeQueryData('aggregations', [ + { + expression: value, + }, + ]); + }, + [handleChangeQueryData], + ); + + return ( +
+
+
+ TRACE OPERATOR +
+ +
+
+ + {!isListViewPanel && ( +
+
+ +
+
+ +
+
+ )} +
+ + + +
+ ); +} diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx new file mode 100644 index 000000000000..749e3f145e2d --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx @@ -0,0 +1,491 @@ +/* eslint-disable sonarjs/cognitive-complexity */ +/* eslint-disable sonarjs/no-identical-functions */ + +import '../QuerySearch/QuerySearch.styles.scss'; + +import { CheckCircleFilled } from '@ant-design/icons'; +import { + autocompletion, + closeCompletion, + CompletionContext, + completionKeymap, + CompletionResult, + startCompletion, +} from '@codemirror/autocomplete'; +import { javascript } from '@codemirror/lang-javascript'; +import { Color } from '@signozhq/design-tokens'; +import { copilot } from '@uiw/codemirror-theme-copilot'; +import { githubLight } from '@uiw/codemirror-theme-github'; +import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror'; +import { Button, Popover } from 'antd'; +import cx from 'classnames'; +import { + TRACE_OPERATOR_OPERATORS, + TRACE_OPERATOR_OPERATORS_LABELS, + TRACE_OPERATOR_OPERATORS_WITH_PRIORITY, +} from 'constants/antlrQueryConstants'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { TriangleAlert } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { IDetailedError, IValidationResult } from 'types/antlrQueryTypes'; +import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; +import { validateTraceOperatorQuery } from 'utils/queryValidationUtils'; + +import { getTraceOperatorContextAtCursor } from './utils/traceOperatorContextUtils'; +import { getInvolvedQueriesInTraceOperator } from './utils/utils'; + +// Custom extension to stop events +const stopEventsExtension = EditorView.domEventHandlers({ + keydown: (event) => { + // Stop all keyboard events from propagating to global shortcuts + event.stopPropagation(); + event.stopImmediatePropagation(); + return false; // Important for CM to know you handled it + }, + input: (event) => { + event.stopPropagation(); + return false; + }, + focus: (event) => { + // Ensure focus events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, + blur: (event) => { + // Ensure blur events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, +}); + +interface TraceOperatorEditorProps { + value: string; + traceOperator: IBuilderTraceOperator; + onChange: (value: string) => void; + placeholder?: string; + onRun?: (query: string) => void; +} + +function TraceOperatorEditor({ + value, + onChange, + traceOperator, + placeholder = 'Enter your trace operator query', + onRun, +}: TraceOperatorEditorProps): JSX.Element { + const isDarkMode = useIsDarkMode(); + const [isFocused, setIsFocused] = useState(false); + const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 }); + const editorRef = useRef(null); + const [validation, setValidation] = useState({ + isValid: false, + message: '', + errors: [], + }); + // Track if the query was changed externally (from props) vs internally (user input) + const [isExternalQueryChange, setIsExternalQueryChange] = useState(false); + const [lastExternalValue, setLastExternalValue] = useState(''); + const { currentQuery, handleRunQuery } = useQueryBuilder(); + + const queryOptions = useMemo( + () => + currentQuery.builder.queryData + .filter((query) => query.dataSource === DataSource.TRACES) // Only show trace queries + .map((query) => ({ + label: query.queryName, + type: 'atom', + apply: query.queryName, + })), + [currentQuery.builder.queryData], + ); + + const toggleSuggestions = useCallback( + (timeout?: number) => { + const timeoutId = setTimeout(() => { + if (!editorRef.current) return; + if (isFocused) { + startCompletion(editorRef.current); + } else { + closeCompletion(editorRef.current); + } + }, timeout); + + return (): void => clearTimeout(timeoutId); + }, + [isFocused], + ); + + const handleQueryValidation = (newQuery: string): void => { + try { + const validationResponse = validateTraceOperatorQuery(newQuery); + setValidation(validationResponse); + } catch (error) { + setValidation({ + isValid: false, + message: 'Failed to process trace operator', + errors: [error as IDetailedError], + }); + } + }; + + // Detect external value changes and mark for validation + useEffect(() => { + const newValue = value || ''; + if (newValue !== lastExternalValue) { + setIsExternalQueryChange(true); + setLastExternalValue(newValue); + } + }, [value, lastExternalValue]); + + // Validate when the value changes externally (including on mount) + useEffect(() => { + if (isExternalQueryChange && value) { + handleQueryValidation(value); + setIsExternalQueryChange(false); + } + }, [isExternalQueryChange, value]); + + // Enhanced autosuggestion function with context awareness + function autoSuggestions(context: CompletionContext): CompletionResult | null { + // This matches words before the cursor position + // eslint-disable-next-line no-useless-escape + const word = context.matchBefore(/[a-zA-Z0-9_.:/?&=#%\-\[\]]*/); + if (word?.from === word?.to && !context.explicit) return null; + + // Get the trace operator context at the cursor position + const queryContext = getTraceOperatorContextAtCursor(value, cursorPos.ch); + + // Define autocomplete options based on the context + let options: { + label: string; + type: string; + info?: string; + apply: + | string + | ((view: EditorView, completion: any, from: number, to: number) => void); + detail?: string; + boost?: number; + }[] = []; + + // Helper function to add space after selection + const addSpaceAfterSelection = ( + view: EditorView, + completion: any, + from: number, + to: number, + shouldAddSpace = true, + ): void => { + view.dispatch({ + changes: { + from, + to, + insert: shouldAddSpace ? `${completion.apply} ` : `${completion.apply}`, + }, + selection: { + anchor: + from + + (shouldAddSpace ? completion.apply.length + 1 : completion.apply.length), + }, + }); + // Do not reopen here; onUpdate will handle reopening via toggleSuggestions + }; + + // Helper function to add space after selection to options + const addSpaceToOptions = (opts: typeof options): typeof options => + opts.map((option) => { + const originalApply = option.apply || option.label; + return { + ...option, + apply: ( + view: EditorView, + completion: any, + from: number, + to: number, + ): void => { + addSpaceAfterSelection(view, { apply: originalApply }, from, to); + }, + }; + }); + + if (queryContext.isInAtom) { + // Suggest atoms (identifiers) for trace operators + + const involvedQueries = getInvolvedQueriesInTraceOperator([traceOperator]); + + options = queryOptions.map((option) => ({ + ...option, + boost: !involvedQueries.includes(option.apply as string) ? 100 : -99, + })); + + // Filter options based on what user is typing + const searchText = word?.text.toLowerCase().trim() ?? ''; + options = options.filter((option) => + option.label.toLowerCase().includes(searchText), + ); + + // Add space after selection for atoms + const optionsWithSpace = addSpaceToOptions(options); + + return { + from: word?.from ?? 0, + to: word?.to ?? cursorPos.ch, + options: optionsWithSpace, + }; + } + + if (queryContext.isInOperator) { + // Suggest operators for trace operators + const operators = Object.values(TRACE_OPERATOR_OPERATORS); + options = operators.map((operator) => ({ + label: TRACE_OPERATOR_OPERATORS_LABELS[operator] + ? `${operator} (${TRACE_OPERATOR_OPERATORS_LABELS[operator]})` + : operator, + type: 'operator', + apply: operator, + boost: TRACE_OPERATOR_OPERATORS_WITH_PRIORITY[operator] * -10, + })); + + // Add space after selection for operators + const optionsWithSpace = addSpaceToOptions(options); + + return { + from: word?.from ?? 0, + to: word?.to ?? cursorPos.ch, + options: optionsWithSpace, + }; + } + + if (queryContext.isInParenthesis) { + // Different suggestions based on the context within parenthesis + const curChar = value.charAt(cursorPos.ch - 1) || ''; + + if (curChar === '(') { + // Right after opening parenthesis, suggest atoms or nested expressions + options = [ + { label: '(', type: 'parenthesis', apply: '(' }, + ...queryOptions, + ]; + + // Add space after selection for opening parenthesis context + const optionsWithSpace = addSpaceToOptions(options); + + return { + from: word?.from ?? 0, + options: optionsWithSpace, + }; + } + + if (curChar === ')') { + // After closing parenthesis, suggest operators + const operators = Object.values(TRACE_OPERATOR_OPERATORS); + options = operators.map((operator) => ({ + label: TRACE_OPERATOR_OPERATORS_LABELS[operator] + ? `${operator} (${TRACE_OPERATOR_OPERATORS_LABELS[operator]})` + : operator, + type: 'operator', + apply: operator, + boost: TRACE_OPERATOR_OPERATORS_WITH_PRIORITY[operator] * -10, + })); + + // Add space after selection for closing parenthesis context + const optionsWithSpace = addSpaceToOptions(options); + + return { + from: word?.from ?? 0, + options: optionsWithSpace, + }; + } + } + + // Default: suggest atoms if no specific context + options = [ + ...queryOptions, + { + label: '(', + type: 'parenthesis', + apply: '(', + }, + ]; + + // Filter options based on what user is typing + const searchText = word?.text.toLowerCase().trim() ?? ''; + options = options.filter((option) => + option.label.toLowerCase().includes(searchText), + ); + + // Add space after selection + const optionsWithSpace = addSpaceToOptions(options); + + return { + from: word?.from ?? 0, + to: word?.to ?? context.pos, + options: optionsWithSpace, + }; + } + + const handleUpdate = useCallback( + (viewUpdate: { view: EditorView }): void => { + if (!editorRef.current) { + editorRef.current = viewUpdate.view; + } + + const selection = viewUpdate.view.state.selection.main; + const pos = selection.head; + + const lineInfo = viewUpdate.view.state.doc.lineAt(pos); + const newPos = { + line: lineInfo.number, + ch: pos - lineInfo.from, + }; + + if (newPos.line !== cursorPos.line || newPos.ch !== cursorPos.ch) { + setCursorPos(newPos); + // Trigger suggestions on context update + toggleSuggestions(10); + } + }, + [cursorPos, toggleSuggestions], + ); + + const handleChange = (newValue: string): void => { + // Mark as internal change to avoid triggering external validation + setIsExternalQueryChange(false); + setLastExternalValue(newValue); + onChange(newValue); + }; + + const handleBlur = (): void => { + handleQueryValidation(value); + setIsFocused(false); + }; + + // Effect to handle focus state and trigger suggestions on focus + useEffect(() => { + const clearTimeout = toggleSuggestions(10); + return (): void => clearTimeout(); + }, [isFocused, toggleSuggestions]); + + return ( +
+
+ 0, + })} + extensions={[ + autocompletion({ + override: [autoSuggestions], + defaultKeymap: true, + closeOnBlur: true, + activateOnTyping: true, + maxRenderedOptions: 50, + }), + javascript({ jsx: false, typescript: false }), + EditorView.lineWrapping, + stopEventsExtension, + Prec.highest( + keymap.of([ + ...completionKeymap, + { + key: 'Escape', + run: closeCompletion, + }, + { + key: 'Enter', + preventDefault: true, + // Prevent default behavior of Enter to add new line + // and instead run a custom action + run: (): boolean => true, + }, + { + key: 'Mod-Enter', + preventDefault: true, + run: (): boolean => { + if (onRun && typeof onRun === 'function') { + onRun(value); + } else { + handleRunQuery(); + } + return true; + }, + }, + { + key: 'Shift-Enter', + preventDefault: true, + // Prevent default behavior of Shift-Enter to add new line + run: (): boolean => true, + }, + ]), + ), + ]} + placeholder={placeholder} + basicSetup={{ + lineNumbers: false, + }} + onFocus={(): void => { + setIsFocused(true); + }} + onBlur={handleBlur} + /> + {value && validation.isValid === false && !isFocused && ( +
0, + })} + > + +
+
+
+ {validation.errors.map((error) => ( +
+
+ {error.line}:{error.column} - {error.message} +
+
+ ))} +
+
+
+
+ } + overlayClassName="query-status-popover" + > + {validation.isValid ? ( +
+ )} +
+
+ ); +} + +TraceOperatorEditor.defaultProps = { + onRun: undefined, + placeholder: 'Enter your trace operator query', +}; + +export default TraceOperatorEditor; diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/traceOperatorContextUtils.test.ts b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/traceOperatorContextUtils.test.ts new file mode 100644 index 000000000000..5dfd3e708066 --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/traceOperatorContextUtils.test.ts @@ -0,0 +1,425 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +/* eslint-disable sonarjs/cognitive-complexity */ + +import { Token } from 'antlr4'; +import TraceOperatorGrammarLexer from 'parser/TraceOperatorParser/TraceOperatorGrammarLexer'; + +import { + createTraceOperatorContext, + extractTraceExpressionPairs, + getTraceOperatorContextAtCursor, +} from '../utils/traceOperatorContextUtils'; + +describe('traceOperatorContextUtils', () => { + describe('createTraceOperatorContext', () => { + it('should create a context object with all required properties', () => { + const mockToken = { + type: TraceOperatorGrammarLexer.IDENTIFIER, + text: 'test', + start: 0, + stop: 3, + } as Token; + + const context = createTraceOperatorContext( + mockToken, + true, + false, + false, + false, + 'atom', + 'operator', + [], + null, + ); + + expect(context).toEqual({ + tokenType: TraceOperatorGrammarLexer.IDENTIFIER, + text: 'test', + start: 0, + stop: 3, + currentToken: 'test', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + atomToken: 'atom', + operatorToken: 'operator', + expressionPairs: [], + currentPair: null, + }); + }); + + it('should create a context object with default values', () => { + const mockToken = { + type: TraceOperatorGrammarLexer.IDENTIFIER, + text: 'test', + start: 0, + stop: 3, + } as Token; + + const context = createTraceOperatorContext( + mockToken, + false, + true, + false, + false, + ); + + expect(context).toEqual({ + tokenType: TraceOperatorGrammarLexer.IDENTIFIER, + text: 'test', + start: 0, + stop: 3, + currentToken: 'test', + isInAtom: false, + isInOperator: true, + isInParenthesis: false, + isInExpression: false, + atomToken: undefined, + operatorToken: undefined, + expressionPairs: [], + currentPair: undefined, + }); + }); + }); + + describe('extractTraceExpressionPairs', () => { + it('should extract simple expression pair', () => { + const query = 'A => B'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(1); + expect(result[0].leftAtom).toBe('A'); + expect(result[0].position.leftStart).toBe(0); + expect(result[0].position.leftEnd).toBe(0); + expect(result[0].operator).toBe('=>'); + expect(result[0].position.operatorStart).toBe(2); + expect(result[0].position.operatorEnd).toBe(3); + expect(result[0].rightAtom).toBe('B'); + expect(result[0].position.rightStart).toBe(5); + expect(result[0].position.rightEnd).toBe(5); + expect(result[0].isComplete).toBe(true); + }); + + it('should extract multiple expression pairs', () => { + const query = 'A => B && C => D'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(2); + + // First pair: A => B + expect(result[0].leftAtom).toBe('A'); + expect(result[0].operator).toBe('=>'); + expect(result[0].rightAtom).toBe('B'); + + // Second pair: C => D + expect(result[1].leftAtom).toBe('C'); + expect(result[1].operator).toBe('=>'); + expect(result[1].rightAtom).toBe('D'); + }); + + it('should handle NOT operator', () => { + const query = 'NOT A => B'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(1); + expect(result[0].leftAtom).toBe('A'); + expect(result[0].operator).toBe('=>'); + expect(result[0].rightAtom).toBe('B'); + }); + + it('should handle parentheses', () => { + const query = '(A => B) && (C => D)'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(2); + expect(result[0].leftAtom).toBe('A'); + expect(result[0].rightAtom).toBe('B'); + expect(result[1].leftAtom).toBe('C'); + expect(result[1].rightAtom).toBe('D'); + }); + + it('should handle incomplete expressions', () => { + const query = 'A =>'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(1); + expect(result[0].leftAtom).toBe('A'); + expect(result[0].operator).toBe('=>'); + expect(result[0].rightAtom).toBeUndefined(); + expect(result[0].isComplete).toBe(true); + }); + + it('should handle complex nested expressions', () => { + const query = 'A => B && (C => D || E => F)'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(3); + expect(result[0].leftAtom).toBe('A'); + expect(result[0].rightAtom).toBe('B'); + expect(result[1].leftAtom).toBe('C'); + expect(result[1].rightAtom).toBe('D'); + expect(result[2].leftAtom).toBe('E'); + expect(result[2].rightAtom).toBe('F'); + }); + + it('should handle whitespace variations', () => { + const query = 'A=>B'; + const result = extractTraceExpressionPairs(query); + + expect(result).toHaveLength(1); + expect(result[0].leftAtom).toBe('A'); + expect(result[0].operator).toBe('=>'); + expect(result[0].rightAtom).toBe('B'); + }); + + it('should handle error cases gracefully', () => { + const query = 'invalid syntax @#$%'; + const result = extractTraceExpressionPairs(query); + + // Should return an array (even if empty or with partial results) + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBeGreaterThanOrEqual(0); + }); + }); + + describe('getTraceOperatorContextAtCursor', () => { + beforeEach(() => { + // Reset console.error mock + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should return default context for empty query', () => { + const result = getTraceOperatorContextAtCursor('', 0); + + expect(result).toEqual({ + tokenType: -1, + text: '', + start: 0, + stop: 0, + currentToken: '', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs: [], + currentPair: null, + }); + }); + + it('should return default context for null query', () => { + const result = getTraceOperatorContextAtCursor(null as any, 0); + + expect(result).toEqual({ + tokenType: -1, + text: '', + start: 0, + stop: 0, + currentToken: '', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs: [], + currentPair: null, + }); + }); + + it('should return default context for undefined query', () => { + const result = getTraceOperatorContextAtCursor(undefined as any, 0); + + expect(result).toEqual({ + tokenType: -1, + text: '', + start: 0, + stop: 0, + currentToken: '', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs: [], + currentPair: null, + }); + }); + + it('should identify atom context', () => { + const query = 'A => B'; + const result = getTraceOperatorContextAtCursor(query, 0); // cursor at 'A' + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBe('=>'); + expect(result.isInAtom).toBe(true); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(0); + expect(result.stop).toBe(0); + }); + + it('should identify operator context', () => { + const query = 'A => B'; + const result = getTraceOperatorContextAtCursor(query, 2); // cursor at '=' + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBeUndefined(); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(true); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(2); + expect(result.stop).toBe(2); + }); + + it('should identify parenthesis context', () => { + const query = '(A => B)'; + const result = getTraceOperatorContextAtCursor(query, 0); // cursor at '(' + + expect(result.atomToken).toBeUndefined(); + expect(result.operatorToken).toBeUndefined(); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(true); + expect(result.start).toBe(0); + expect(result.stop).toBe(0); + }); + + it('should handle cursor at space', () => { + const query = 'A => B'; + const result = getTraceOperatorContextAtCursor(query, 1); // cursor at space + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBeUndefined(); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(true); + expect(result.isInParenthesis).toBe(false); + }); + + it('should handle cursor at end of query', () => { + const query = 'A => B'; + const result = getTraceOperatorContextAtCursor(query, 5); // cursor at end + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBe('=>'); + expect(result.isInAtom).toBe(true); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(5); + expect(result.stop).toBe(5); + }); + + it('should handle complex query', () => { + const query = 'A => B && C => D'; + const result = getTraceOperatorContextAtCursor(query, 8); // cursor at '&' + + expect(result.atomToken).toBeUndefined(); + expect(result.operatorToken).toBe('&&'); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(true); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(7); + expect(result.stop).toBe(8); + }); + + it('should identify operator position in complex query', () => { + const query = 'A => B && C => D'; + const result = getTraceOperatorContextAtCursor(query, 10); // cursor at 'C' + + expect(result.atomToken).toBe('C'); + expect(result.operatorToken).toBe('&&'); + expect(result.isInAtom).toBe(true); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(10); + expect(result.stop).toBe(10); + }); + + it('should identify atom position in complex query', () => { + const query = 'A => B && C => D'; + const result = getTraceOperatorContextAtCursor(query, 13); // cursor at '>' + + expect(result.atomToken).toBe('C'); + expect(result.operatorToken).toBe('=>'); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(true); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(12); + expect(result.stop).toBe(13); + }); + + it('should handle transition points', () => { + const query = 'A => B'; + const result = getTraceOperatorContextAtCursor(query, 4); // cursor at 'B' + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBe('=>'); + expect(result.isInAtom).toBe(true); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(4); + expect(result.stop).toBe(4); + }); + + it('should handle whitespace in complex queries', () => { + const query = 'A=>B && C=>D'; + const result = getTraceOperatorContextAtCursor(query, 6); // cursor at '&' + + expect(result.atomToken).toBeUndefined(); + expect(result.operatorToken).toBe('&&'); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(true); + expect(result.isInParenthesis).toBe(false); + expect(result.start).toBe(5); + expect(result.stop).toBe(6); + }); + + it('should handle NOT operator context', () => { + const query = 'NOT A => B'; + const result = getTraceOperatorContextAtCursor(query, 0); // cursor at 'N' + + expect(result.atomToken).toBeUndefined(); + expect(result.operatorToken).toBeUndefined(); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(true); + }); + + it('should handle parentheses context', () => { + const query = '(A => B)'; + const result = getTraceOperatorContextAtCursor(query, 1); // cursor at 'A' + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBe('=>'); + expect(result.isInAtom).toBe(false); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(true); + expect(result.start).toBe(0); + expect(result.stop).toBe(0); + }); + + it('should handle expression pairs context', () => { + const query = 'A => B && C => D'; + const result = getTraceOperatorContextAtCursor(query, 5); // cursor at 'A' in "&&" + + expect(result.atomToken).toBe('A'); + expect(result.operatorToken).toBe('=>'); + expect(result.isInAtom).toBe(true); + expect(result.isInOperator).toBe(false); + expect(result.isInParenthesis).toBe(false); + }); + + it('should handle various cursor positions', () => { + const query = 'A => B'; + + // Test cursor at each position + for (let i = 0; i < query.length; i++) { + const result = getTraceOperatorContextAtCursor(query, i); + expect(result).toBeDefined(); + expect(typeof result.start).toBe('number'); + expect(typeof result.stop).toBe('number'); + } + }); + }); +}); diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/utils.test.ts b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/utils.test.ts new file mode 100644 index 000000000000..872c1e853779 --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/__tests__/utils.test.ts @@ -0,0 +1,46 @@ +import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData'; + +import { getInvolvedQueriesInTraceOperator } from '../utils/utils'; + +const makeTraceOperator = (expression: string): IBuilderTraceOperator => + (({ expression } as unknown) as IBuilderTraceOperator); + +describe('getInvolvedQueriesInTraceOperator', () => { + it('returns empty array for empty input', () => { + const result = getInvolvedQueriesInTraceOperator([]); + expect(result).toEqual([]); + }); + + it('extracts identifiers from expression', () => { + const result = getInvolvedQueriesInTraceOperator([ + makeTraceOperator('A => B'), + ]); + expect(result).toEqual(['A', 'B']); + }); + + it('extracts identifiers from complex expression', () => { + const result = getInvolvedQueriesInTraceOperator([ + makeTraceOperator('A => (NOT B || C)'), + ]); + expect(result).toEqual(['A', 'B', 'C']); + }); + + it('filters out querynames from complex expression', () => { + const result = getInvolvedQueriesInTraceOperator([ + makeTraceOperator( + '(A1 && (NOT B2 || (C3 -> (D4 && E5)))) => ((F6 || G7) && (NOT (H8 -> I9)))', + ), + ]); + expect(result).toEqual([ + 'A1', + 'B2', + 'C3', + 'D4', + 'E5', + 'F6', + 'G7', + 'H8', + 'I9', + ]); + }); +}); diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/traceOperatorContextUtils.ts b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/traceOperatorContextUtils.ts new file mode 100644 index 000000000000..90b49d52f6f2 --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/traceOperatorContextUtils.ts @@ -0,0 +1,562 @@ +/* eslint-disable sonarjs/cognitive-complexity */ +/* eslint-disable no-continue */ + +import { CharStreams, CommonTokenStream, Token } from 'antlr4'; +import TraceOperatorGrammarLexer from 'parser/TraceOperatorParser/TraceOperatorGrammarLexer'; +import { IToken } from 'types/antlrQueryTypes'; + +// Trace Operator Context Interface +export interface ITraceOperatorContext { + tokenType: number; + text: string; + start: number; + stop: number; + currentToken: string; + isInAtom: boolean; + isInOperator: boolean; + isInParenthesis: boolean; + isInExpression: boolean; + atomToken?: string; + operatorToken?: string; + expressionPairs: ITraceExpressionPair[]; + currentPair?: ITraceExpressionPair | null; +} + +// Trace Expression Pair Interface +export interface ITraceExpressionPair { + leftAtom: string; + operator: string; + rightAtom?: string; + rightExpression?: string; + position: { + leftStart: number; + leftEnd: number; + operatorStart: number; + operatorEnd: number; + rightStart?: number; + rightEnd?: number; + }; + isComplete: boolean; +} + +// Helper functions to determine token types +function isAtomToken(tokenType: number): boolean { + return tokenType === TraceOperatorGrammarLexer.IDENTIFIER; +} + +function isOperatorToken(tokenType: number): boolean { + return [ + TraceOperatorGrammarLexer.T__2, // '=>' + TraceOperatorGrammarLexer.T__3, // '&&' + TraceOperatorGrammarLexer.T__4, // '||' + TraceOperatorGrammarLexer.T__5, // 'NOT' + TraceOperatorGrammarLexer.T__6, // '->' + ].includes(tokenType); +} + +function isParenthesisToken(tokenType: number): boolean { + return ( + tokenType === TraceOperatorGrammarLexer.T__0 || + tokenType === TraceOperatorGrammarLexer.T__1 + ); +} + +function isOpeningParenthesis(tokenType: number): boolean { + return tokenType === TraceOperatorGrammarLexer.T__0; +} + +function isClosingParenthesis(tokenType: number): boolean { + return tokenType === TraceOperatorGrammarLexer.T__1; +} + +// Function to create a context object +export function createTraceOperatorContext( + token: Token, + isInAtom: boolean, + isInOperator: boolean, + isInParenthesis: boolean, + isInExpression: boolean, + atomToken?: string, + operatorToken?: string, + expressionPairs?: ITraceExpressionPair[], + currentPair?: ITraceExpressionPair | null, +): ITraceOperatorContext { + return { + tokenType: token.type, + text: token.text || '', + start: token.start, + stop: token.stop, + currentToken: token.text || '', + isInAtom, + isInOperator, + isInParenthesis, + isInExpression, + atomToken, + operatorToken, + expressionPairs: expressionPairs || [], + currentPair, + }; +} + +// Helper to determine token context +function determineTraceTokenContext( + token: IToken, +): { + isInAtom: boolean; + isInOperator: boolean; + isInParenthesis: boolean; + isInExpression: boolean; +} { + const tokenType = token.type; + + return { + isInAtom: isAtomToken(tokenType), + isInOperator: isOperatorToken(tokenType), + isInParenthesis: isParenthesisToken(tokenType), + isInExpression: false, // Will be determined by broader context + }; +} + +/** + * Extracts all expression pairs from a trace operator query string + * This parses the query according to the TraceOperatorGrammar.g4 grammar + * + * @param query The trace operator query string to parse + * @returns An array of ITraceExpressionPair objects representing the expression pairs + */ +export function extractTraceExpressionPairs( + query: string, +): ITraceExpressionPair[] { + try { + const input = query || ''; + const chars = CharStreams.fromString(input); + const lexer = new TraceOperatorGrammarLexer(chars); + + const tokenStream = new CommonTokenStream(lexer); + tokenStream.fill(); + + const allTokens = tokenStream.tokens as IToken[]; + const expressionPairs: ITraceExpressionPair[] = []; + let currentPair: Partial | null = null; + + let i = 0; + while (i < allTokens.length) { + const token = allTokens[i]; + i++; + + // Skip EOF and whitespace tokens + if (token.type === TraceOperatorGrammarLexer.EOF || token.channel !== 0) { + continue; + } + + // If token is an IDENTIFIER (atom), start or continue a pair + if (isAtomToken(token.type)) { + // If we don't have a current pair, start one + if (!currentPair) { + currentPair = { + leftAtom: token.text, + position: { + leftStart: token.start, + leftEnd: token.stop, + operatorStart: 0, + operatorEnd: 0, + }, + }; + } + // If we have a current pair but no operator yet, this is still the left atom + else if (!currentPair.operator && currentPair.position) { + currentPair.leftAtom = token.text; + currentPair.position.leftStart = token.start; + currentPair.position.leftEnd = token.stop; + } + // If we have an operator, this is the right atom + else if ( + currentPair.operator && + !currentPair.rightAtom && + currentPair.position + ) { + currentPair.rightAtom = token.text; + currentPair.position.rightStart = token.start; + currentPair.position.rightEnd = token.stop; + currentPair.isComplete = true; + + // Add the completed pair to the result + expressionPairs.push(currentPair as ITraceExpressionPair); + currentPair = null; + } + } + // If token is an operator and we have a left atom + else if ( + isOperatorToken(token.type) && + currentPair && + currentPair.leftAtom && + currentPair.position + ) { + currentPair.operator = token.text; + currentPair.position.operatorStart = token.start; + currentPair.position.operatorEnd = token.stop; + + // If this is a NOT operator, it might be followed by another operator + if (token.type === TraceOperatorGrammarLexer.T__5 && i < allTokens.length) { + // Look ahead for the next operator + const nextToken = allTokens[i]; + if (isOperatorToken(nextToken.type) && nextToken.channel === 0) { + currentPair.operator = `${token.text} ${nextToken.text}`; + currentPair.position.operatorEnd = nextToken.stop; + i++; // Skip the next token since we've consumed it + } + } + } + // If token is an opening parenthesis after an operator, this is a right expression + else if ( + isOpeningParenthesis(token.type) && + currentPair && + currentPair.operator && + !currentPair.rightAtom && + currentPair.position + ) { + // Find the matching closing parenthesis + let parenCount = 1; + let j = i; + let rightExpression = ''; + const rightStart = token.start; + let rightEnd = token.stop; + + while (j < allTokens.length && parenCount > 0) { + const parenToken = allTokens[j]; + if (parenToken.channel === 0) { + if (isOpeningParenthesis(parenToken.type)) { + parenCount++; + } else if (isClosingParenthesis(parenToken.type)) { + parenCount--; + if (parenCount === 0) { + rightEnd = parenToken.stop; + break; + } + } + } + rightExpression += parenToken.text; + j++; + } + + if (parenCount === 0) { + currentPair.rightExpression = rightExpression; + currentPair.position.rightStart = rightStart; + currentPair.position.rightEnd = rightEnd; + currentPair.isComplete = true; + + // Add the completed pair to the result + expressionPairs.push(currentPair as ITraceExpressionPair); + currentPair = null; + + // Skip to the end of the expression + i = j; + } + } + } + + // Add any remaining incomplete pair + if (currentPair && currentPair.leftAtom && currentPair.position) { + expressionPairs.push({ + ...currentPair, + isComplete: !!(currentPair.leftAtom && currentPair.operator), + } as ITraceExpressionPair); + } + + return expressionPairs; + } catch (error) { + console.error('Error in extractTraceExpressionPairs:', error); + return []; + } +} + +/** + * Gets the current expression pair at the cursor position + * + * @param expressionPairs An array of ITraceExpressionPair objects + * @param query The full query string + * @param cursorIndex The position of the cursor in the query + * @returns The expression pair at the cursor position, or null if not found + */ +export function getCurrentTraceExpressionPair( + expressionPairs: ITraceExpressionPair[], + cursorIndex: number, +): ITraceExpressionPair | null { + try { + if (expressionPairs.length === 0) { + return null; + } + + // Find the rightmost pair whose end position is before or at the cursor + let bestMatch: ITraceExpressionPair | null = null; + + // eslint-disable-next-line no-restricted-syntax + for (const pair of expressionPairs) { + const { position } = pair; + const pairEnd = + position.rightEnd || position.operatorEnd || position.leftEnd; + const pairStart = position.leftStart; + + // If this pair ends at or before the cursor, and it's further right than our previous best match + if ( + pairStart <= cursorIndex && + cursorIndex <= pairEnd + 1 && + (!bestMatch || + pairEnd > + (bestMatch.position.rightEnd || + bestMatch.position.operatorEnd || + bestMatch.position.leftEnd)) + ) { + bestMatch = pair; + } + } + + return bestMatch; + } catch (error) { + console.error('Error in getCurrentTraceExpressionPair:', error); + return null; + } +} + +/** + * Gets the current trace operator context at the cursor position + * This is useful for determining what kind of suggestions to show + * + * @param query The trace operator query string + * @param cursorIndex The position of the cursor in the query + * @returns The trace operator context at the cursor position + */ +export function getTraceOperatorContextAtCursor( + query: string, + cursorIndex: number, +): ITraceOperatorContext { + try { + // Guard against infinite recursion + const stackTrace = new Error().stack || ''; + const callCount = (stackTrace.match(/getTraceOperatorContextAtCursor/g) || []) + .length; + if (callCount > 3) { + console.warn( + 'Potential infinite recursion detected in getTraceOperatorContextAtCursor', + ); + return { + tokenType: -1, + text: '', + start: cursorIndex, + stop: cursorIndex, + currentToken: '', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs: [], + currentPair: null, + }; + } + + // Create input stream and lexer + const input = query || ''; + const chars = CharStreams.fromString(input); + const lexer = new TraceOperatorGrammarLexer(chars); + + const tokenStream = new CommonTokenStream(lexer); + tokenStream.fill(); + + const allTokens = tokenStream.tokens as IToken[]; + + // Get expression pairs information + const expressionPairs = extractTraceExpressionPairs(query); + const currentPair = getCurrentTraceExpressionPair( + expressionPairs, + cursorIndex, + ); + + // Find the token at or just before the cursor + let lastTokenBeforeCursor: IToken | null = null; + for (let i = 0; i < allTokens.length; i++) { + const token = allTokens[i]; + if (token.type === TraceOperatorGrammarLexer.EOF) continue; + + if (token.stop < cursorIndex || token.stop + 1 === cursorIndex) { + lastTokenBeforeCursor = token; + } + + if (token.start > cursorIndex) { + break; + } + } + + // Find exact token at cursor + let exactToken: IToken | null = null; + for (let i = 0; i < allTokens.length; i++) { + const token = allTokens[i]; + if (token.type === TraceOperatorGrammarLexer.EOF) continue; + + if (token.start <= cursorIndex && cursorIndex <= token.stop + 1) { + exactToken = token; + break; + } + } + + // If we don't have any tokens, return default context + if (!lastTokenBeforeCursor && !exactToken) { + return { + tokenType: -1, + text: '', + start: cursorIndex, + stop: cursorIndex, + currentToken: '', + isInAtom: true, // Default to atom context when input is empty + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs, + currentPair: null, + }; + } + + // Check if cursor is at a space after a token (transition point) + const isAtSpace = cursorIndex < query.length && query[cursorIndex] === ' '; + const isAfterSpace = cursorIndex > 0 && query[cursorIndex - 1] === ' '; + const isAfterToken = cursorIndex > 0 && query[cursorIndex - 1] !== ' '; + const isTransitionPoint = + (isAtSpace && isAfterToken) || + (cursorIndex === query.length && isAfterToken); + + // If we're at a transition point after a token, progress the context + if ( + lastTokenBeforeCursor && + (isAtSpace || isAfterSpace || isTransitionPoint) + ) { + const lastTokenContext = determineTraceTokenContext(lastTokenBeforeCursor); + + // Apply context progression: atom → operator → atom/expression → operator → atom + if (lastTokenContext.isInAtom) { + // After atom + space, move to operator context + return { + tokenType: lastTokenBeforeCursor.type, + text: lastTokenBeforeCursor.text, + start: cursorIndex, + stop: cursorIndex, + currentToken: lastTokenBeforeCursor.text, + isInAtom: false, + isInOperator: true, + isInParenthesis: false, + isInExpression: false, + atomToken: lastTokenBeforeCursor.text, + expressionPairs, + currentPair, + }; + } + + if (lastTokenContext.isInOperator) { + // After operator + space, move to atom/expression context + return { + tokenType: lastTokenBeforeCursor.type, + text: lastTokenBeforeCursor.text, + start: cursorIndex, + stop: cursorIndex, + currentToken: lastTokenBeforeCursor.text, + isInAtom: true, // Expecting an atom or expression after operator + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + operatorToken: lastTokenBeforeCursor.text, + atomToken: currentPair?.leftAtom, + expressionPairs, + currentPair, + }; + } + + if ( + lastTokenContext.isInParenthesis && + isClosingParenthesis(lastTokenBeforeCursor.type) + ) { + // After closing parenthesis, move to operator context + return { + tokenType: lastTokenBeforeCursor.type, + text: lastTokenBeforeCursor.text, + start: cursorIndex, + stop: cursorIndex, + currentToken: lastTokenBeforeCursor.text, + isInAtom: false, + isInOperator: true, + isInParenthesis: false, + isInExpression: false, + expressionPairs, + currentPair, + }; + } + } + + // If cursor is at the end of a token, return the current token context + if (exactToken && cursorIndex === exactToken.stop + 1) { + const tokenContext = determineTraceTokenContext(exactToken); + + return { + tokenType: exactToken.type, + text: exactToken.text, + start: exactToken.start, + stop: exactToken.stop, + currentToken: exactToken.text, + ...tokenContext, + atomToken: tokenContext.isInAtom ? exactToken.text : currentPair?.leftAtom, + operatorToken: tokenContext.isInOperator + ? exactToken.text + : currentPair?.operator, + expressionPairs, + currentPair, + }; + } + + // Regular token-based context detection + if (exactToken?.channel === 0) { + const tokenContext = determineTraceTokenContext(exactToken); + + return { + tokenType: exactToken.type, + text: exactToken.text, + start: exactToken.start, + stop: exactToken.stop, + currentToken: exactToken.text, + ...tokenContext, + atomToken: tokenContext.isInAtom ? exactToken.text : currentPair?.leftAtom, + operatorToken: tokenContext.isInOperator + ? exactToken.text + : currentPair?.operator, + expressionPairs, + currentPair, + }; + } + + // Default fallback to atom context + return { + tokenType: -1, + text: '', + start: cursorIndex, + stop: cursorIndex, + currentToken: '', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs, + currentPair, + }; + } catch (error) { + console.error('Error in getTraceOperatorContextAtCursor:', error); + return { + tokenType: -1, + text: '', + start: cursorIndex, + stop: cursorIndex, + currentToken: '', + isInAtom: true, + isInOperator: false, + isInParenthesis: false, + isInExpression: false, + expressionPairs: [], + currentPair: null, + }; + } +} diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils.ts b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils.ts new file mode 100644 index 000000000000..4ebf1c6bbefc --- /dev/null +++ b/frontend/src/components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils.ts @@ -0,0 +1,22 @@ +import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData'; + +export const getInvolvedQueriesInTraceOperator = ( + traceOperators: IBuilderTraceOperator[], +): string[] => { + if ( + !traceOperators || + traceOperators.length === 0 || + traceOperators.length > 1 + ) + return []; + + const currentTraceOperator = traceOperators[0]; + + // Match any word starting with letter or underscore + const tokens = + currentTraceOperator.expression.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) || []; + + // Filter out operator keywords + const operators = new Set(['NOT']); + return tokens.filter((t) => !operators.has(t)); +}; diff --git a/frontend/src/components/SignozRadioGroup/SignozRadioGroup.styles.scss b/frontend/src/components/SignozRadioGroup/SignozRadioGroup.styles.scss index 5c2a7b56f93e..ab5baa5380c5 100644 --- a/frontend/src/components/SignozRadioGroup/SignozRadioGroup.styles.scss +++ b/frontend/src/components/SignozRadioGroup/SignozRadioGroup.styles.scss @@ -17,6 +17,19 @@ font-weight: var(--font-weight-normal); } + .view-title-container { + display: flex; + align-items: center; + gap: 6px; + justify-content: center; + + .icon-container { + display: flex; + align-items: center; + justify-content: center; + } + } + .tab { border: 1px solid var(--bg-slate-400); &:hover { diff --git a/frontend/src/components/SignozRadioGroup/SignozRadioGroup.tsx b/frontend/src/components/SignozRadioGroup/SignozRadioGroup.tsx index 3bb789e7749c..9c9f0b88aa2b 100644 --- a/frontend/src/components/SignozRadioGroup/SignozRadioGroup.tsx +++ b/frontend/src/components/SignozRadioGroup/SignozRadioGroup.tsx @@ -6,6 +6,7 @@ import { RadioChangeEvent } from 'antd/es/radio'; interface Option { value: string; label: string; + icon?: React.ReactNode; } interface SignozRadioGroupProps { @@ -37,7 +38,10 @@ function SignozRadioGroup({ value={option.value} className={value === option.value ? 'selected_view tab' : 'tab'} > - {option.label} +
+ {option.icon &&
{option.icon}
} + {option.label} +
))} diff --git a/frontend/src/constants/antlrQueryConstants.ts b/frontend/src/constants/antlrQueryConstants.ts index 49c7c06c71ab..3c5b87abba8b 100644 --- a/frontend/src/constants/antlrQueryConstants.ts +++ b/frontend/src/constants/antlrQueryConstants.ts @@ -17,6 +17,27 @@ export const OPERATORS = { '<': '<', }; +export const TRACE_OPERATOR_OPERATORS = { + AND: '&&', + OR: '||', + NOT: 'NOT', + DIRECT_DESCENDENT: '=>', + INDIRECT_DESCENDENT: '->', +}; + +export const TRACE_OPERATOR_OPERATORS_WITH_PRIORITY = { + [TRACE_OPERATOR_OPERATORS.DIRECT_DESCENDENT]: 1, + [TRACE_OPERATOR_OPERATORS.AND]: 2, + [TRACE_OPERATOR_OPERATORS.OR]: 3, + [TRACE_OPERATOR_OPERATORS.NOT]: 4, + [TRACE_OPERATOR_OPERATORS.INDIRECT_DESCENDENT]: 5, +}; + +export const TRACE_OPERATOR_OPERATORS_LABELS = { + [TRACE_OPERATOR_OPERATORS.DIRECT_DESCENDENT]: 'Direct Descendant', + [TRACE_OPERATOR_OPERATORS.INDIRECT_DESCENDENT]: 'Indirect Descendant', +}; + export const QUERY_BUILDER_FUNCTIONS = { HAS: 'has', HASANY: 'hasAny', diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 64b786f47ab3..4638cb7c27fa 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -12,6 +12,7 @@ import { HavingForm, IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, IClickHouseQuery, IPromQLQuery, Query, @@ -50,6 +51,8 @@ import { export const MAX_FORMULAS = 20; export const MAX_QUERIES = 26; +export const TRACE_OPERATOR_QUERY_NAME = 'Trace Operator'; + export const idDivider = '--'; export const selectValueDivider = '__'; @@ -263,6 +266,11 @@ export const initialFormulaBuilderFormValues: IBuilderFormula = { legend: '', }; +export const initialQueryBuilderFormTraceOperatorValues: IBuilderTraceOperator = { + ...initialQueryBuilderFormTracesValues, + queryName: TRACE_OPERATOR_QUERY_NAME, +}; + export const initialQueryPromQLData: IPromQLQuery = { name: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }), query: '', @@ -280,6 +288,7 @@ export const initialClickHouseData: IClickHouseQuery = { export const initialQueryBuilderData: QueryBuilderData = { queryData: [initialQueryBuilderFormValues], queryFormulas: [], + queryTraceOperator: [], }; export const initialSingleQueryMap: Record< diff --git a/frontend/src/constants/regExp.ts b/frontend/src/constants/regExp.ts index 40de740221c6..2059fcd9322f 100644 --- a/frontend/src/constants/regExp.ts +++ b/frontend/src/constants/regExp.ts @@ -1,3 +1,5 @@ +import { TRACE_OPERATOR_QUERY_NAME } from './queryBuilder'; + export const FORMULA_REGEXP = /F\d+/; export const HAVING_FILTER_REGEXP = /^[-\d.,\s]+$/; @@ -5,3 +7,5 @@ export const HAVING_FILTER_REGEXP = /^[-\d.,\s]+$/; export const TYPE_ADDON_REGEXP = /_(.+)/; export const SPLIT_FIRST_UNDERSCORE = /(? { + const involvedQueriesInTraceOperator = getInvolvedQueriesInTraceOperator( + currentQuery.builder.queryTraceOperator, + ); const queryConfig: Record SelectProps['options']> = { [EQueryType.QUERY_BUILDER]: () => [ - ...(getSelectedQueryOptions(currentQuery.builder.queryData) || []), + ...(getSelectedQueryOptions(currentQuery.builder.queryData)?.filter( + (option) => + !involvedQueriesInTraceOperator.includes(option.value as string), + ) || []), ...(getSelectedQueryOptions(currentQuery.builder.queryFormulas) || []), + ...(getSelectedQueryOptions(currentQuery.builder.queryTraceOperator) || []), ], [EQueryType.PROM]: () => getSelectedQueryOptions(currentQuery.promql), [EQueryType.CLICKHOUSE]: () => diff --git a/frontend/src/container/FormAlertRules/utils.ts b/frontend/src/container/FormAlertRules/utils.ts index 3015f0c42666..e66a9c870d2d 100644 --- a/frontend/src/container/FormAlertRules/utils.ts +++ b/frontend/src/container/FormAlertRules/utils.ts @@ -5,6 +5,7 @@ import getStep from 'lib/getStep'; import { IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, IClickHouseQuery, IPromQLQuery, } from 'types/api/queryBuilder/queryBuilderData'; @@ -53,7 +54,11 @@ export const getUpdatedStepInterval = (evalWindow?: string): number => { export const getSelectedQueryOptions = ( queries: Array< - IBuilderQuery | IBuilderFormula | IClickHouseQuery | IPromQLQuery + | IBuilderQuery + | IBuilderTraceOperator + | IBuilderFormula + | IClickHouseQuery + | IPromQLQuery >, ): SelectProps['options'] => queries diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.test.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.test.tsx index ea94d567208c..3de8d2fdf9e0 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.test.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.test.tsx @@ -90,6 +90,7 @@ const mockProps: WidgetGraphComponentProps = { }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/GridCardLayout/__tests__/utils.test.ts b/frontend/src/container/GridCardLayout/__tests__/utils.test.ts index 67ba61c260d9..254d09d1efdd 100644 --- a/frontend/src/container/GridCardLayout/__tests__/utils.test.ts +++ b/frontend/src/container/GridCardLayout/__tests__/utils.test.ts @@ -131,6 +131,7 @@ describe('GridCardLayout Utils', () => { }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [], promql: [], @@ -171,6 +172,7 @@ describe('GridCardLayout Utils', () => { }, ], queryFormulas: [], + queryTraceOperator: [], }, }; @@ -195,6 +197,7 @@ describe('GridCardLayout Utils', () => { }, ], queryFormulas: [], + queryTraceOperator: [], }, }; @@ -240,6 +243,7 @@ describe('GridCardLayout Utils', () => { }, ], queryFormulas: [], + queryTraceOperator: [], }, }; @@ -268,6 +272,7 @@ describe('GridCardLayout Utils', () => { }, ], queryFormulas: [], + queryTraceOperator: [], }, }; diff --git a/frontend/src/container/GridTableComponent/__tests__/response.ts b/frontend/src/container/GridTableComponent/__tests__/response.ts index bf18d4909572..071cb69aa402 100644 --- a/frontend/src/container/GridTableComponent/__tests__/response.ts +++ b/frontend/src/container/GridTableComponent/__tests__/response.ts @@ -162,6 +162,7 @@ export const widgetQueryWithLegend = { }, ], queryFormulas: [], + queryTraceOperator: [], }, id: '48ad5a67-9a3c-49d4-a886-d7a34f8b875d', queryType: 'builder', @@ -457,6 +458,7 @@ export const widgetQueryQBv5MultiAggregations = { }, ], queryFormulas: [], + queryTraceOperator: [], }, id: 'qb-v5-multi-aggregations-test', queryType: 'builder', diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts index 647bec7e4b7f..ffaed5b94ada 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts @@ -301,6 +301,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -490,6 +491,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -575,6 +577,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -660,6 +663,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -797,6 +801,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1050,6 +1055,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1257,6 +1263,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1522,6 +1529,7 @@ export const getClusterMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/constants.ts index 39ccdd849ee8..cf958ac98a0a 100644 --- a/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/constants.ts @@ -233,6 +233,7 @@ export const getDaemonSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -416,6 +417,7 @@ export const getDaemonSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -512,6 +514,7 @@ export const getDaemonSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -608,6 +611,7 @@ export const getDaemonSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts index 2212b9ccd3c3..8f4185c30a49 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts @@ -196,6 +196,7 @@ export const getDeploymentMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -346,6 +347,7 @@ export const getDeploymentMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -431,6 +433,7 @@ export const getDeploymentMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -516,6 +519,7 @@ export const getDeploymentMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx index 3b82925f009e..ec1847179cf9 100644 --- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx @@ -79,6 +79,7 @@ export const getEntityEventsOrLogsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, id: uuidv4(), queryType: EQueryType.QUERY_BUILDER, @@ -226,6 +227,7 @@ export const getEntityTracesQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, id: '572f1d91-6ac0-46c0-b726-c21488b34434', queryType: EQueryType.QUERY_BUILDER, diff --git a/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/constants.ts index ced669949e2a..1774024e14e3 100644 --- a/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/constants.ts @@ -108,6 +108,7 @@ export const getJobMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -191,6 +192,7 @@ export const getJobMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -287,6 +289,7 @@ export const getJobMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -383,6 +386,7 @@ export const getJobMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts index 94467f772691..304359b794ab 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts @@ -309,6 +309,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -576,6 +577,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -655,6 +657,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -734,6 +737,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -819,6 +823,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -904,6 +909,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1075,6 +1081,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1212,6 +1219,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1429,6 +1437,7 @@ export const getNamespaceMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1561,6 +1570,7 @@ export const getNamespaceMetricsQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts index f17b1fa6b396..8e175eb1f431 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts @@ -341,6 +341,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -647,6 +648,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -810,6 +812,7 @@ export const getNodeMetricsQueryPayload = ( queryName: 'F2', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -973,6 +976,7 @@ export const getNodeMetricsQueryPayload = ( queryName: 'F2', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1052,6 +1056,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1131,6 +1136,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1216,6 +1222,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1301,6 +1308,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1451,6 +1459,7 @@ export const getNodeMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1569,6 +1578,7 @@ export const getNodeMetricsQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts index ed3a9380a276..7d6e798c713d 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts @@ -335,6 +335,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -668,6 +669,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -851,6 +853,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1184,6 +1187,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1324,6 +1328,7 @@ export const getPodMetricsQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1407,6 +1412,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1497,6 +1503,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1714,6 +1721,7 @@ export const getPodMetricsQueryPayload = ( queryName: 'F2', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -1918,6 +1926,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -2135,6 +2144,7 @@ export const getPodMetricsQueryPayload = ( queryName: 'F2', }, ], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -2231,6 +2241,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -2327,6 +2338,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { @@ -2510,6 +2522,7 @@ export const getPodMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [ { diff --git a/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/constants.ts index 4a3a6348a003..c7c983c3bf72 100644 --- a/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/constants.ts @@ -246,6 +246,7 @@ export const getStatefulSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -365,6 +366,7 @@ export const getStatefulSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -534,6 +536,7 @@ export const getStatefulSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -653,6 +656,7 @@ export const getStatefulSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -735,6 +739,7 @@ export const getStatefulSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -817,6 +822,7 @@ export const getStatefulSetMetricsQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), diff --git a/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/constants.ts index e17ce4426a3a..efa94723a905 100644 --- a/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/constants.ts @@ -148,6 +148,7 @@ export const getVolumeQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -239,6 +240,7 @@ export const getVolumeQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -330,6 +332,7 @@ export const getVolumeQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -421,6 +424,7 @@ export const getVolumeQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), @@ -512,6 +516,7 @@ export const getVolumeQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: v4(), diff --git a/frontend/src/container/LogDetailedView/ContextView/__tests__/mockData.ts b/frontend/src/container/LogDetailedView/ContextView/__tests__/mockData.ts index 8e0b3894cb75..29d55c25fc18 100644 --- a/frontend/src/container/LogDetailedView/ContextView/__tests__/mockData.ts +++ b/frontend/src/container/LogDetailedView/ContextView/__tests__/mockData.ts @@ -58,6 +58,7 @@ export const mockQuery: Query = { }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [], id: 'test-query-id', diff --git a/frontend/src/container/LogDetailedView/InfraMetrics/constants.ts b/frontend/src/container/LogDetailedView/InfraMetrics/constants.ts index 0882ec6d1c58..b7bb060bf6ce 100644 --- a/frontend/src/container/LogDetailedView/InfraMetrics/constants.ts +++ b/frontend/src/container/LogDetailedView/InfraMetrics/constants.ts @@ -121,6 +121,7 @@ export const getPodQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '9b92756a-b445-45f8-90f4-d26f3ef28f8f', @@ -197,6 +198,7 @@ export const getPodQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'a22c1e03-4876-4b3e-9a96-a3c3a28f9c0f', @@ -337,6 +339,7 @@ export const getPodQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '7bb3a6f5-d1c6-4f2e-9cc9-7dcc46db398f', @@ -477,6 +480,7 @@ export const getPodQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '6d5ccd81-0ea1-4fb9-a66b-7f0fe2f15165', @@ -624,6 +628,7 @@ export const getPodQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '4d03a0ff-4fa5-4b19-b397-97f80ba9e0ac', @@ -772,6 +777,7 @@ export const getPodQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'ad491f19-0f83-4dd4-bb8f-bec295c18d1b', @@ -920,6 +926,7 @@ export const getPodQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '16908d4e-1565-4847-8d87-01ebb8fc494a', @@ -1001,6 +1008,7 @@ export const getPodQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '4b255d6d-4cde-474d-8866-f4418583c18b', @@ -1177,6 +1185,7 @@ export const getNodeQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '259295b5-774d-4b2e-8a4f-e5dd63e6c38d', @@ -1314,6 +1323,7 @@ export const getNodeQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '486af4da-2a1a-4b8f-992c-eba098d3a6f9', @@ -1409,6 +1419,7 @@ export const getNodeQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'b56143c0-7d2f-4425-97c5-65ad6fc87366', @@ -1557,6 +1568,7 @@ export const getNodeQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '57eeac15-615c-4a71-9c61-8e0c0c76b045', @@ -1718,6 +1730,7 @@ export const getHostQueryPayload = ( queryName: 'F1', }, ], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', @@ -1786,6 +1799,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '40218bfb-a9b7-4974-aead-5bf666e139bf', @@ -1928,6 +1942,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '8e6485ea-7018-43b0-ab27-b210f77b59ad', @@ -2009,6 +2024,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '47173220-44df-4ef6-87f4-31e333c180c7', @@ -2084,6 +2100,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '62eedbc6-c8ad-4d13-80a8-129396e1d1dc', @@ -2159,6 +2176,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '5ddb1b38-53bb-46f5-b4fe-fe832d6b9b24', @@ -2234,6 +2252,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'a849bcce-7684-4852-9134-530b45419b8f', @@ -2309,6 +2328,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'ab685a3d-fa4c-4663-8d94-c452e59038f3', @@ -2369,6 +2389,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '9bd40b51-0790-4cdd-9718-551b2ded5926', @@ -2450,6 +2471,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: '9c6d18ad-89ff-4e38-a15a-440e72ed6ca8', @@ -2524,6 +2546,7 @@ export const getHostQueryPayload = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'f4cfc2a5-78fc-42cc-8f4a-194c8c916132', diff --git a/frontend/src/container/LogsExplorerViews/tests/mock.ts b/frontend/src/container/LogsExplorerViews/tests/mock.ts index 1e05c1d1714d..29ebbd167eaf 100644 --- a/frontend/src/container/LogsExplorerViews/tests/mock.ts +++ b/frontend/src/container/LogsExplorerViews/tests/mock.ts @@ -178,6 +178,10 @@ export const mockQueryBuilderContextValue = { panelType: PANEL_TYPES.TIME_SERIES, isEnabledQuery: false, lastUsedQuery: 0, + handleSetTraceOperatorData: noop, + removeAllQueryBuilderEntities: noop, + removeTraceOperator: noop, + addTraceOperator: noop, setLastUsedQuery: noop, handleSetQueryData: noop, handleSetFormulaData: noop, diff --git a/frontend/src/container/MeterExplorer/Breakdown/graphs.ts b/frontend/src/container/MeterExplorer/Breakdown/graphs.ts index 1be079c99046..a122bdcf7c0f 100644 --- a/frontend/src/container/MeterExplorer/Breakdown/graphs.ts +++ b/frontend/src/container/MeterExplorer/Breakdown/graphs.ts @@ -71,6 +71,7 @@ export function getWidgetQuery( builder: { queryData: props.queryData, queryFormulas: (props.queryFormulas as IBuilderFormula[]) || [], + queryTraceOperator: [], }, clickhouse_sql: [], id: uuid(), diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts index e8b0fcc80776..d7963dc05041 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts @@ -64,6 +64,7 @@ export const getQueryBuilderQueries = ({ return newQueryData; }), + queryTraceOperator: [], }); export const getQueryBuilderQuerieswithFormula = ({ @@ -106,4 +107,5 @@ export const getQueryBuilderQuerieswithFormula = ({ }), dataSource, })), + queryTraceOperator: [], }); diff --git a/frontend/src/container/MetricsExplorer/Explorer/useGetRelatedMetricsGraphs.ts b/frontend/src/container/MetricsExplorer/Explorer/useGetRelatedMetricsGraphs.ts index 953423706cc1..384490e50d24 100644 --- a/frontend/src/container/MetricsExplorer/Explorer/useGetRelatedMetricsGraphs.ts +++ b/frontend/src/container/MetricsExplorer/Explorer/useGetRelatedMetricsGraphs.ts @@ -71,6 +71,7 @@ export const useGetRelatedMetricsGraphs = ({ builder: { queryData: [metric.query], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [], id: uuidv4(), diff --git a/frontend/src/container/MetricsExplorer/MetricDetails/utils.tsx b/frontend/src/container/MetricsExplorer/MetricDetails/utils.tsx index f12deacdc6e1..f4cc569af713 100644 --- a/frontend/src/container/MetricsExplorer/MetricDetails/utils.tsx +++ b/frontend/src/container/MetricsExplorer/MetricDetails/utils.tsx @@ -150,6 +150,7 @@ export function getMetricDetailsQuery( }, ], queryFormulas: [], + queryTraceOperator: [], }, }; } diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index ca15bf2a139e..5049f278af89 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -164,6 +164,7 @@ function QuerySection({ ): Query => ({ ...(extras || {}), }, ], + queryTraceOperator: [], }, }); diff --git a/frontend/src/container/NewWidget/utils.ts b/frontend/src/container/NewWidget/utils.ts index 9707853d7a3d..1be37cf5a433 100644 --- a/frontend/src/container/NewWidget/utils.ts +++ b/frontend/src/container/NewWidget/utils.ts @@ -528,6 +528,10 @@ export function handleQueryChange( return tempQuery; }), + queryTraceOperator: + newPanelType === PANEL_TYPES.LIST + ? [] + : supersetQuery.builder.queryTraceOperator, }, }; } diff --git a/frontend/src/container/OnboardingContainer/Steps/LogsConnectionStatus/LogsConnectionStatus.tsx b/frontend/src/container/OnboardingContainer/Steps/LogsConnectionStatus/LogsConnectionStatus.tsx index a569d791d43c..5dd31bd81df3 100644 --- a/frontend/src/container/OnboardingContainer/Steps/LogsConnectionStatus/LogsConnectionStatus.tsx +++ b/frontend/src/container/OnboardingContainer/Steps/LogsConnectionStatus/LogsConnectionStatus.tsx @@ -77,6 +77,7 @@ export default function LogsConnectionStatus(): JSX.Element { }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [], id: '', diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index a80639e5ef02..798d45e897f3 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -30,5 +30,14 @@ export type QueryBuilderProps = { isListViewPanel?: boolean; showFunctions?: boolean; showOnlyWhereClause?: boolean; + showOnlyTraceOperator?: boolean; + showTraceViewSelector?: boolean; + showTraceOperator?: boolean; version: string; + onChangeTraceView?: (view: TraceView) => void; }; + +export enum TraceView { + SPANS = 'spans', + TRACES = 'traces', +} diff --git a/frontend/src/container/QueryBuilder/components/QBEntityOptions/QBEntityOptions.tsx b/frontend/src/container/QueryBuilder/components/QBEntityOptions/QBEntityOptions.tsx index bd7142cc54ef..5cae7050be3a 100644 --- a/frontend/src/container/QueryBuilder/components/QBEntityOptions/QBEntityOptions.tsx +++ b/frontend/src/container/QueryBuilder/components/QBEntityOptions/QBEntityOptions.tsx @@ -39,6 +39,8 @@ interface QBEntityOptionsProps { showCloneOption?: boolean; isListViewPanel?: boolean; index?: number; + showTraceOperator?: boolean; + hasTraceOperator?: boolean; queryVariant?: 'dropdown' | 'static'; onChangeDataSource?: (value: DataSource) => void; } @@ -61,6 +63,8 @@ export default function QBEntityOptions({ onCloneQuery, index, queryVariant, + hasTraceOperator = false, + showTraceOperator = false, onChangeDataSource, }: QBEntityOptionsProps): JSX.Element { const handleCloneEntity = (): void => { @@ -97,7 +101,7 @@ export default function QBEntityOptions({ value="query-builder" className="periscope-btn visibility-toggle" onClick={onToggleVisibility} - disabled={isListViewPanel} + disabled={isListViewPanel && !showTraceOperator} > {entityData.disabled ? : } @@ -115,6 +119,10 @@ export default function QBEntityOptions({ className={cx( 'periscope-btn', entityType === 'query' ? 'query-name' : 'formula-name', + query?.dataSource === DataSource.TRACES && + (hasTraceOperator || (showTraceOperator && isListViewPanel)) + ? 'has-trace-operator' + : '', isLogsExplorerPage && lastUsedQuery === index ? 'sync-btn' : '', )} > @@ -183,4 +191,6 @@ QBEntityOptions.defaultProps = { showCloneOption: true, queryVariant: 'static', onChangeDataSource: noop, + hasTraceOperator: false, + showTraceOperator: false, }; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts index b5dd84a5732c..9da542fd467b 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts +++ b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts @@ -11,5 +11,8 @@ export type QueryProps = { version: string; showSpanScopeSelector?: boolean; showOnlyWhereClause?: boolean; + showTraceOperator?: boolean; + hasTraceOperator?: boolean; signalSource?: string; + isMultiQueryAllowed?: boolean; } & Pick; diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/config.ts b/frontend/src/container/TraceDetail/SelectedSpanDetails/config.ts index 209683d6e4ab..f81f7781fa30 100644 --- a/frontend/src/container/TraceDetail/SelectedSpanDetails/config.ts +++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/config.ts @@ -67,6 +67,7 @@ export const getTraceToLogsQuery = ( }, ], queryFormulas: [], + queryTraceOperator: [], }, }; diff --git a/frontend/src/container/TracesExplorer/ListView/index.tsx b/frontend/src/container/TracesExplorer/ListView/index.tsx index d47fce21cfe6..d7663c9b21eb 100644 --- a/frontend/src/container/TracesExplorer/ListView/index.tsx +++ b/frontend/src/container/TracesExplorer/ListView/index.tsx @@ -106,6 +106,19 @@ function ListView({ ]; } + // add order by to trace operator + if ( + query.builder.queryTraceOperator && + query.builder.queryTraceOperator.length > 0 + ) { + query.builder.queryTraceOperator[0].orderBy = [ + { + columnName: orderBy.split(':')[0], + order: orderBy.split(':')[1] as 'asc' | 'desc', + }, + ]; + } + return query; }, [stagedQuery, orderBy]); diff --git a/frontend/src/container/TracesExplorer/QuerySection/index.tsx b/frontend/src/container/TracesExplorer/QuerySection/index.tsx index 96f11fd77849..1c38417e9333 100644 --- a/frontend/src/container/TracesExplorer/QuerySection/index.tsx +++ b/frontend/src/container/TracesExplorer/QuerySection/index.tsx @@ -37,11 +37,15 @@ function QuerySection(): JSX.Element { }; }, [panelTypes, renderOrderBy]); + const isListViewPanel = useMemo( + () => panelTypes === PANEL_TYPES.LIST || panelTypes === PANEL_TYPES.TRACE, + [panelTypes], + ); + return ( { const { handleSetQueryData, + handleSetTraceOperatorData, handleSetFormulaData, removeQueryBuilderEntityByIndex, panelType, @@ -400,9 +402,19 @@ export const useQueryOperations: UseQueryOperations = ({ : value, }; - handleSetQueryData(index, newQuery); + if (isForTraceOperator) { + handleSetTraceOperatorData(index, newQuery); + } else { + handleSetQueryData(index, newQuery); + } }, - [query, index, handleSetQueryData], + [ + query, + index, + handleSetQueryData, + handleSetTraceOperatorData, + isForTraceOperator, + ], ); const handleChangeFormulaData: HandleChangeFormulaData = useCallback( diff --git a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/__tests__/mapQueryDataFromApiInputs.ts b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/__tests__/mapQueryDataFromApiInputs.ts index e1a23d4c9ff2..b4976164d228 100644 --- a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/__tests__/mapQueryDataFromApiInputs.ts +++ b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/__tests__/mapQueryDataFromApiInputs.ts @@ -78,6 +78,7 @@ export const stepIntervalUnchanged = { }, ], queryFormulas: [], + queryTraceOperator: [], }, promql: [{ name: 'A', query: '', legend: '', disabled: false }], clickhouse_sql: [{ name: 'A', legend: '', disabled: false, query: '' }], @@ -242,6 +243,7 @@ export const replaceVariables = { }, ], queryFormulas: [], + queryTraceOperator: [], }, promql: [{ name: 'A', query: '', legend: '', disabled: false }], clickhouse_sql: [{ name: 'A', legend: '', disabled: false, query: '' }], @@ -292,6 +294,7 @@ export const defaultOutput = { }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [{ disabled: false, legend: '', name: 'A', query: '' }], id: 'test-id', @@ -469,6 +472,7 @@ export const outputWithFunctions = { ShiftBy: 0, }, ], + queryTraceOperator: [], }, promql: [{ name: 'A', query: '', legend: '', disabled: false }], clickhouse_sql: [{ name: 'A', legend: '', disabled: false, query: '' }], diff --git a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery.ts b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery.ts index a498b6e4a673..7a0ebb3abadf 100644 --- a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery.ts +++ b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery.ts @@ -25,12 +25,17 @@ const buildBuilderQuery = ( query: Query, panelType: PANEL_TYPES | null, ): ICompositeMetricQuery => { - const { queryData, queryFormulas } = query.builder; + const { queryData, queryFormulas, queryTraceOperator } = query.builder; const currentQueryData = mapQueryDataToApi(queryData, 'queryName'); const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName'); + const currentTraceOperator = mapQueryDataToApi( + queryTraceOperator, + 'queryName', + ); const builderQueries = { ...currentQueryData.data, ...currentFormulas.data, + ...currentTraceOperator.data, }; const compositeQuery = defaultCompositeQuery; diff --git a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts index b47281b71a3c..a852bdc415a1 100644 --- a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts +++ b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts @@ -1,8 +1,10 @@ +/* eslint-disable sonarjs/cognitive-complexity */ import { initialQueryState } from 'constants/queryBuilder'; import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery'; import { IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, IClickHouseQuery, IPromQLQuery, Query, @@ -22,10 +24,13 @@ import { v4 as uuid } from 'uuid'; import { transformQueryBuilderDataModel } from '../transformQueryBuilderDataModel'; const mapQueryFromV5 = (compositeQuery: ICompositeMetricQuery): Query => { - const builderQueries: Record = {}; + const builderQueries: Record< + string, + IBuilderQuery | IBuilderFormula | IBuilderTraceOperator + > = {}; const builderQueryTypes: Record< string, - 'builder_query' | 'builder_formula' + 'builder_query' | 'builder_formula' | 'builder_trace_operator' > = {}; const promQueries: IPromQLQuery[] = []; const clickhouseQueries: IClickHouseQuery[] = []; @@ -46,6 +51,11 @@ const mapQueryFromV5 = (compositeQuery: ICompositeMetricQuery): Query => { ); builderQueryTypes[spec.name] = 'builder_formula'; } + } else if (q.type === 'builder_trace_operator') { + if (spec.name) { + builderQueries[spec.name] = (spec as unknown) as IBuilderTraceOperator; + builderQueryTypes[spec.name] = 'builder_trace_operator'; + } } else if (q.type === 'promql') { const promSpec = spec as PromQuery; promQueries.push({ diff --git a/frontend/src/lib/newQueryBuilder/transformQueryBuilderDataModel.ts b/frontend/src/lib/newQueryBuilder/transformQueryBuilderDataModel.ts index 2b619f7d57df..9301d46d4fd9 100644 --- a/frontend/src/lib/newQueryBuilder/transformQueryBuilderDataModel.ts +++ b/frontend/src/lib/newQueryBuilder/transformQueryBuilderDataModel.ts @@ -2,29 +2,41 @@ import { initialFormulaBuilderFormValues, initialQueryBuilderFormValuesMap, } from 'constants/queryBuilder'; -import { FORMULA_REGEXP } from 'constants/regExp'; +import { FORMULA_REGEXP, TRACE_OPERATOR_REGEXP } from 'constants/regExp'; import { BuilderQueryDataResourse, IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, } from 'types/api/queryBuilder/queryBuilderData'; import { QueryBuilderData } from 'types/common/queryBuilder'; export const transformQueryBuilderDataModel = ( data: BuilderQueryDataResourse, - queryTypes?: Record, + queryTypes?: Record< + string, + 'builder_query' | 'builder_formula' | 'builder_trace_operator' + >, ): QueryBuilderData => { const queryData: QueryBuilderData['queryData'] = []; const queryFormulas: QueryBuilderData['queryFormulas'] = []; + const queryTraceOperator: QueryBuilderData['queryTraceOperator'] = []; Object.entries(data).forEach(([key, value]) => { const isFormula = queryTypes ? queryTypes[key] === 'builder_formula' : FORMULA_REGEXP.test(value.queryName); + const isTraceOperator = queryTypes + ? queryTypes[key] === 'builder_trace_operator' + : TRACE_OPERATOR_REGEXP.test(value.queryName); + if (isFormula) { const formula = value as IBuilderFormula; queryFormulas.push({ ...initialFormulaBuilderFormValues, ...formula }); + } else if (isTraceOperator) { + const traceOperator = value as IBuilderTraceOperator; + queryTraceOperator.push({ ...traceOperator }); } else { const queryFromData = value as IBuilderQuery; queryData.push({ @@ -34,5 +46,5 @@ export const transformQueryBuilderDataModel = ( } }); - return { queryData, queryFormulas }; + return { queryData, queryFormulas, queryTraceOperator }; }; diff --git a/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx b/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx index e609b7acdae3..cd7b6e7069ea 100644 --- a/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx +++ b/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx @@ -206,6 +206,7 @@ describe('Logs Explorer Tests', () => { initialQueryBuilderFormValues, initialQueryBuilderFormValues, ], + queryTraceOperator: [], }, }, setSupersetQuery: jest.fn(), @@ -215,6 +216,10 @@ describe('Logs Explorer Tests', () => { panelType: PANEL_TYPES.TIME_SERIES, isEnabledQuery: false, lastUsedQuery: 0, + handleSetTraceOperatorData: noop, + removeAllQueryBuilderEntities: noop, + removeTraceOperator: noop, + addTraceOperator: noop, setLastUsedQuery: noop, handleSetQueryData: noop, handleSetFormulaData: noop, diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts b/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts index c81fa8817ac2..974b0c176493 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts +++ b/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts @@ -72,6 +72,7 @@ export function getWidgetQuery( builder: { queryData: props.queryData, queryFormulas: (props.queryFormulas as IBuilderFormula[]) || [], + queryTraceOperator: [], }, clickhouse_sql: [], id: uuid(), diff --git a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts index 636e961e9fd3..195ce45d9614 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts +++ b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts @@ -155,6 +155,7 @@ export function getWidgetQuery({ }, ], queryFormulas: [], + queryTraceOperator: [], }, clickhouse_sql: [], id: uuid(), diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 8ffe15a8f7c6..8523d2640bdf 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -1,6 +1,7 @@ import './TracesExplorer.styles.scss'; import * as Sentry from '@sentry/react'; +import { Callout } from '@signozhq/callout'; import { Card } from 'antd'; import logEvent from 'api/common/logEvent'; import cx from 'classnames'; @@ -35,7 +36,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { Warning } from 'types/api'; import { Dashboard } from 'types/api/dashboard/getAll'; -import { Query } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderTraceOperator, + Query, +} from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink'; import { @@ -52,7 +56,6 @@ function TracesExplorer(): JSX.Element { handleRunQuery, stagedQuery, handleSetConfig, - updateQueriesData, } = useQueryBuilder(); const { options } = useOptionsMenu({ @@ -103,32 +106,14 @@ function TracesExplorer(): JSX.Element { handleSetConfig(PANEL_TYPES.LIST, DataSource.TRACES); } - if (view === ExplorerViews.LIST) { - if ( - selectedView !== ExplorerViews.LIST && - currentQuery?.builder?.queryData?.[0] - ) { - const filterToRetain = currentQuery.builder.queryData[0].filter; - - const newDefaultQuery = updateAllQueriesOperators( - initialQueriesMap.traces, - PANEL_TYPES.LIST, - DataSource.TRACES, - ); - - const newListQuery = updateQueriesData( - newDefaultQuery, - 'queryData', - (item, index) => { - if (index === 0) { - return { ...item, filter: filterToRetain }; - } - return item; - }, - ); - setDefaultQuery(newListQuery); - } - setShouldReset(true); + if ( + (selectedView === ExplorerViews.TRACE || + selectedView === ExplorerViews.LIST) && + stagedQuery?.builder?.queryTraceOperator && + stagedQuery.builder.queryTraceOperator.length > 0 + ) { + // remove order by from trace operator + set(stagedQuery, 'builder.queryTraceOperator[0].orderBy', []); } setSelectedView(view); @@ -141,10 +126,8 @@ function TracesExplorer(): JSX.Element { handleSetConfig, handleExplorerTabChange, selectedView, - currentQuery, - updateAllQueriesOperators, - updateQueriesData, setSelectedView, + stagedQuery, ], ); @@ -211,19 +194,44 @@ function TracesExplorer(): JSX.Element { useShareBuilderUrl({ defaultValue: defaultQuery, forceReset: shouldReset }); - const isMultipleQueries = useMemo(() => { - const builder = currentQuery?.builder; - const queriesLen = builder?.queryData?.length ?? 0; - const formulasLen = builder?.queryFormulas?.length ?? 0; - return queriesLen > 1 || formulasLen > 0; - }, [currentQuery]); - const isGroupByExist = useMemo(() => { const queryData = currentQuery?.builder?.queryData ?? []; return queryData.some((q) => (q?.groupBy?.length ?? 0) > 0); }, [currentQuery]); + + const hasMultipleQueries = useMemo( + () => currentQuery?.builder?.queryData?.length > 1, + [currentQuery], + ); + + const traceOperator = useMemo((): IBuilderTraceOperator | undefined => { + if ( + currentQuery.builder.queryTraceOperator && + currentQuery.builder.queryTraceOperator.length > 0 + ) { + return currentQuery.builder.queryTraceOperator[0]; + } + + return undefined; + }, [currentQuery.builder.queryTraceOperator]); + + const showTraceOperatorCallout = useMemo( + () => + (selectedView === ExplorerViews.LIST || + selectedView === ExplorerViews.TRACE) && + hasMultipleQueries && + !traceOperator, + [selectedView, hasMultipleQueries, traceOperator], + ); + + const traceOperatorCalloutDescription = useMemo(() => { + if (currentQuery.builder.queryData.length === 0) return ''; + const firstQuery = currentQuery.builder.queryData[0]; + return `Please use a Trace Operator to combine results of multiple span queries. Else you'd only see the results from query "${firstQuery.queryName}"`; + }, [currentQuery]); + useEffect(() => { - const shouldChangeView = isMultipleQueries || isGroupByExist; + const shouldChangeView = isGroupByExist; if ( (selectedView === ExplorerViews.LIST || @@ -233,12 +241,7 @@ function TracesExplorer(): JSX.Element { // Switch to timeseries view automatically handleChangeSelectedView(ExplorerViews.TIMESERIES); } - }, [ - selectedView, - isMultipleQueries, - isGroupByExist, - handleChangeSelectedView, - ]); + }, [selectedView, isGroupByExist, handleChangeSelectedView]); useEffect(() => { if (shouldReset) { @@ -365,6 +368,15 @@ function TracesExplorer(): JSX.Element { /> + {showTraceOperatorCallout && ( + + )} + {selectedView === ExplorerViews.LIST && (
' +'&&' +'||' +'->' +null +null + +token symbolic names: +null +null +null +null +null +null +null +null +IDENTIFIER +WS + +rule names: +query +expression +atom +operator + + +atn: +[4, 1, 9, 45, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, 0, 4, 0, 10, 8, 0, 11, 0, 12, 0, 11, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 39, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 0, 0, 4, 0, 2, 4, 6, 0, 1, 2, 0, 1, 1, 4, 7, 46, 0, 9, 1, 0, 0, 0, 2, 38, 1, 0, 0, 0, 4, 40, 1, 0, 0, 0, 6, 42, 1, 0, 0, 0, 8, 10, 3, 2, 1, 0, 9, 8, 1, 0, 0, 0, 10, 11, 1, 0, 0, 0, 11, 9, 1, 0, 0, 0, 11, 12, 1, 0, 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 5, 0, 0, 1, 14, 1, 1, 0, 0, 0, 15, 16, 5, 1, 0, 0, 16, 39, 3, 2, 1, 0, 17, 18, 5, 2, 0, 0, 18, 19, 3, 2, 1, 0, 19, 20, 5, 3, 0, 0, 20, 21, 3, 6, 3, 0, 21, 22, 3, 2, 1, 0, 22, 39, 1, 0, 0, 0, 23, 24, 5, 2, 0, 0, 24, 25, 3, 2, 1, 0, 25, 26, 5, 3, 0, 0, 26, 39, 1, 0, 0, 0, 27, 28, 3, 4, 2, 0, 28, 29, 3, 6, 3, 0, 29, 30, 3, 2, 1, 0, 30, 39, 1, 0, 0, 0, 31, 32, 3, 4, 2, 0, 32, 33, 3, 6, 3, 0, 33, 34, 5, 2, 0, 0, 34, 35, 3, 2, 1, 0, 35, 36, 5, 3, 0, 0, 36, 39, 1, 0, 0, 0, 37, 39, 3, 4, 2, 0, 38, 15, 1, 0, 0, 0, 38, 17, 1, 0, 0, 0, 38, 23, 1, 0, 0, 0, 38, 27, 1, 0, 0, 0, 38, 31, 1, 0, 0, 0, 38, 37, 1, 0, 0, 0, 39, 3, 1, 0, 0, 0, 40, 41, 5, 8, 0, 0, 41, 5, 1, 0, 0, 0, 42, 43, 7, 0, 0, 0, 43, 7, 1, 0, 0, 0, 2, 11, 38] \ No newline at end of file diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammar.tokens b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammar.tokens new file mode 100644 index 000000000000..3ed3c7d404e4 --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammar.tokens @@ -0,0 +1,16 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +T__4=5 +T__5=6 +T__6=7 +IDENTIFIER=8 +WS=9 +'NOT'=1 +'('=2 +')'=3 +'=>'=4 +'&&'=5 +'||'=6 +'->'=7 diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.interp b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.interp new file mode 100644 index 000000000000..0e99c0d7f6b1 --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.interp @@ -0,0 +1,44 @@ +token literal names: +null +'NOT' +'(' +')' +'=>' +'&&' +'||' +'->' +null +null + +token symbolic names: +null +null +null +null +null +null +null +null +IDENTIFIER +WS + +rule names: +T__0 +T__1 +T__2 +T__3 +T__4 +T__5 +T__6 +IDENTIFIER +WS + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 9, 57, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 4, 7, 41, 8, 7, 11, 7, 12, 7, 42, 1, 7, 5, 7, 46, 8, 7, 10, 7, 12, 7, 49, 9, 7, 1, 8, 4, 8, 52, 8, 8, 11, 8, 12, 8, 53, 1, 8, 1, 8, 0, 0, 9, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 1, 0, 3, 2, 0, 65, 90, 97, 122, 1, 0, 48, 57, 3, 0, 9, 10, 13, 13, 32, 32, 59, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 1, 19, 1, 0, 0, 0, 3, 23, 1, 0, 0, 0, 5, 25, 1, 0, 0, 0, 7, 27, 1, 0, 0, 0, 9, 30, 1, 0, 0, 0, 11, 33, 1, 0, 0, 0, 13, 36, 1, 0, 0, 0, 15, 40, 1, 0, 0, 0, 17, 51, 1, 0, 0, 0, 19, 20, 5, 78, 0, 0, 20, 21, 5, 79, 0, 0, 21, 22, 5, 84, 0, 0, 22, 2, 1, 0, 0, 0, 23, 24, 5, 40, 0, 0, 24, 4, 1, 0, 0, 0, 25, 26, 5, 41, 0, 0, 26, 6, 1, 0, 0, 0, 27, 28, 5, 61, 0, 0, 28, 29, 5, 62, 0, 0, 29, 8, 1, 0, 0, 0, 30, 31, 5, 38, 0, 0, 31, 32, 5, 38, 0, 0, 32, 10, 1, 0, 0, 0, 33, 34, 5, 124, 0, 0, 34, 35, 5, 124, 0, 0, 35, 12, 1, 0, 0, 0, 36, 37, 5, 45, 0, 0, 37, 38, 5, 62, 0, 0, 38, 14, 1, 0, 0, 0, 39, 41, 7, 0, 0, 0, 40, 39, 1, 0, 0, 0, 41, 42, 1, 0, 0, 0, 42, 40, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 47, 1, 0, 0, 0, 44, 46, 7, 1, 0, 0, 45, 44, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 16, 1, 0, 0, 0, 49, 47, 1, 0, 0, 0, 50, 52, 7, 2, 0, 0, 51, 50, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 51, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 56, 6, 8, 0, 0, 56, 18, 1, 0, 0, 0, 4, 0, 42, 47, 53, 1, 6, 0, 0] \ No newline at end of file diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.tokens b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.tokens new file mode 100644 index 000000000000..3ed3c7d404e4 --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.tokens @@ -0,0 +1,16 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +T__4=5 +T__5=6 +T__6=7 +IDENTIFIER=8 +WS=9 +'NOT'=1 +'('=2 +')'=3 +'=>'=4 +'&&'=5 +'||'=6 +'->'=7 diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.ts b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.ts new file mode 100644 index 000000000000..0d7b3f53c357 --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarLexer.ts @@ -0,0 +1,92 @@ +// Generated from ./TraceOperatorGrammar.g4 by ANTLR 4.13.1 +// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols +import { + ATN, + ATNDeserializer, + CharStream, + DecisionState, DFA, + Lexer, + LexerATNSimulator, + RuleContext, + PredictionContextCache, + Token +} from "antlr4"; +export default class TraceOperatorGrammarLexer extends Lexer { + public static readonly T__0 = 1; + public static readonly T__1 = 2; + public static readonly T__2 = 3; + public static readonly T__3 = 4; + public static readonly T__4 = 5; + public static readonly T__5 = 6; + public static readonly T__6 = 7; + public static readonly IDENTIFIER = 8; + public static readonly WS = 9; + public static readonly EOF = Token.EOF; + + public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]; + public static readonly literalNames: (string | null)[] = [ null, "'NOT'", + "'('", "')'", + "'=>'", "'&&'", + "'||'", "'->'" ]; + public static readonly symbolicNames: (string | null)[] = [ null, null, + null, null, + null, null, + null, null, + "IDENTIFIER", + "WS" ]; + public static readonly modeNames: string[] = [ "DEFAULT_MODE", ]; + + public static readonly ruleNames: string[] = [ + "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "IDENTIFIER", + "WS", + ]; + + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(this, TraceOperatorGrammarLexer._ATN, TraceOperatorGrammarLexer.DecisionsToDFA, new PredictionContextCache()); + } + + public get grammarFileName(): string { return "TraceOperatorGrammar.g4"; } + + public get literalNames(): (string | null)[] { return TraceOperatorGrammarLexer.literalNames; } + public get symbolicNames(): (string | null)[] { return TraceOperatorGrammarLexer.symbolicNames; } + public get ruleNames(): string[] { return TraceOperatorGrammarLexer.ruleNames; } + + public get serializedATN(): number[] { return TraceOperatorGrammarLexer._serializedATN; } + + public get channelNames(): string[] { return TraceOperatorGrammarLexer.channelNames; } + + public get modeNames(): string[] { return TraceOperatorGrammarLexer.modeNames; } + + public static readonly _serializedATN: number[] = [4,0,9,57,6,-1,2,0,7, + 0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,1,0,1, + 0,1,0,1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,3,1,4,1,4,1,4,1,5,1,5,1,5,1,6,1,6,1, + 6,1,7,4,7,41,8,7,11,7,12,7,42,1,7,5,7,46,8,7,10,7,12,7,49,9,7,1,8,4,8,52, + 8,8,11,8,12,8,53,1,8,1,8,0,0,9,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9, + 1,0,3,2,0,65,90,97,122,1,0,48,57,3,0,9,10,13,13,32,32,59,0,1,1,0,0,0,0, + 3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0, + 0,15,1,0,0,0,0,17,1,0,0,0,1,19,1,0,0,0,3,23,1,0,0,0,5,25,1,0,0,0,7,27,1, + 0,0,0,9,30,1,0,0,0,11,33,1,0,0,0,13,36,1,0,0,0,15,40,1,0,0,0,17,51,1,0, + 0,0,19,20,5,78,0,0,20,21,5,79,0,0,21,22,5,84,0,0,22,2,1,0,0,0,23,24,5,40, + 0,0,24,4,1,0,0,0,25,26,5,41,0,0,26,6,1,0,0,0,27,28,5,61,0,0,28,29,5,62, + 0,0,29,8,1,0,0,0,30,31,5,38,0,0,31,32,5,38,0,0,32,10,1,0,0,0,33,34,5,124, + 0,0,34,35,5,124,0,0,35,12,1,0,0,0,36,37,5,45,0,0,37,38,5,62,0,0,38,14,1, + 0,0,0,39,41,7,0,0,0,40,39,1,0,0,0,41,42,1,0,0,0,42,40,1,0,0,0,42,43,1,0, + 0,0,43,47,1,0,0,0,44,46,7,1,0,0,45,44,1,0,0,0,46,49,1,0,0,0,47,45,1,0,0, + 0,47,48,1,0,0,0,48,16,1,0,0,0,49,47,1,0,0,0,50,52,7,2,0,0,51,50,1,0,0,0, + 52,53,1,0,0,0,53,51,1,0,0,0,53,54,1,0,0,0,54,55,1,0,0,0,55,56,6,8,0,0,56, + 18,1,0,0,0,4,0,42,47,53,1,6,0,0]; + + private static __ATN: ATN; + public static get _ATN(): ATN { + if (!TraceOperatorGrammarLexer.__ATN) { + TraceOperatorGrammarLexer.__ATN = new ATNDeserializer().deserialize(TraceOperatorGrammarLexer._serializedATN); + } + + return TraceOperatorGrammarLexer.__ATN; + } + + + static DecisionsToDFA = TraceOperatorGrammarLexer._ATN.decisionToState.map( (ds: DecisionState, index: number) => new DFA(ds, index) ); +} \ No newline at end of file diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarListener.ts b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarListener.ts new file mode 100644 index 000000000000..ecfc8ae9802c --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarListener.ts @@ -0,0 +1,58 @@ +// Generated from ./TraceOperatorGrammar.g4 by ANTLR 4.13.1 + +import {ParseTreeListener} from "antlr4"; + + +import { QueryContext } from "./TraceOperatorGrammarParser"; +import { ExpressionContext } from "./TraceOperatorGrammarParser"; +import { AtomContext } from "./TraceOperatorGrammarParser"; +import { OperatorContext } from "./TraceOperatorGrammarParser"; + + +/** + * This interface defines a complete listener for a parse tree produced by + * `TraceOperatorGrammarParser`. + */ +export default class TraceOperatorGrammarListener extends ParseTreeListener { + /** + * Enter a parse tree produced by `TraceOperatorGrammarParser.query`. + * @param ctx the parse tree + */ + enterQuery?: (ctx: QueryContext) => void; + /** + * Exit a parse tree produced by `TraceOperatorGrammarParser.query`. + * @param ctx the parse tree + */ + exitQuery?: (ctx: QueryContext) => void; + /** + * Enter a parse tree produced by `TraceOperatorGrammarParser.expression`. + * @param ctx the parse tree + */ + enterExpression?: (ctx: ExpressionContext) => void; + /** + * Exit a parse tree produced by `TraceOperatorGrammarParser.expression`. + * @param ctx the parse tree + */ + exitExpression?: (ctx: ExpressionContext) => void; + /** + * Enter a parse tree produced by `TraceOperatorGrammarParser.atom`. + * @param ctx the parse tree + */ + enterAtom?: (ctx: AtomContext) => void; + /** + * Exit a parse tree produced by `TraceOperatorGrammarParser.atom`. + * @param ctx the parse tree + */ + exitAtom?: (ctx: AtomContext) => void; + /** + * Enter a parse tree produced by `TraceOperatorGrammarParser.operator`. + * @param ctx the parse tree + */ + enterOperator?: (ctx: OperatorContext) => void; + /** + * Exit a parse tree produced by `TraceOperatorGrammarParser.operator`. + * @param ctx the parse tree + */ + exitOperator?: (ctx: OperatorContext) => void; +} + diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarParser.ts b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarParser.ts new file mode 100644 index 000000000000..eac6da7812da --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarParser.ts @@ -0,0 +1,423 @@ +// Generated from ./TraceOperatorGrammar.g4 by ANTLR 4.13.1 +// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols + +import { + ATN, + ATNDeserializer, DecisionState, DFA, FailedPredicateException, + RecognitionException, NoViableAltException, BailErrorStrategy, + Parser, ParserATNSimulator, + RuleContext, ParserRuleContext, PredictionMode, PredictionContextCache, + TerminalNode, RuleNode, + Token, TokenStream, + Interval, IntervalSet +} from 'antlr4'; +import TraceOperatorGrammarListener from "./TraceOperatorGrammarListener.js"; +import TraceOperatorGrammarVisitor from "./TraceOperatorGrammarVisitor.js"; + +// for running tests with parameters, TODO: discuss strategy for typed parameters in CI +// eslint-disable-next-line no-unused-vars +type int = number; + +export default class TraceOperatorGrammarParser extends Parser { + public static readonly T__0 = 1; + public static readonly T__1 = 2; + public static readonly T__2 = 3; + public static readonly T__3 = 4; + public static readonly T__4 = 5; + public static readonly T__5 = 6; + public static readonly T__6 = 7; + public static readonly IDENTIFIER = 8; + public static readonly WS = 9; + public static readonly EOF = Token.EOF; + public static readonly RULE_query = 0; + public static readonly RULE_expression = 1; + public static readonly RULE_atom = 2; + public static readonly RULE_operator = 3; + public static readonly literalNames: (string | null)[] = [ null, "'NOT'", + "'('", "')'", + "'=>'", "'&&'", + "'||'", "'->'" ]; + public static readonly symbolicNames: (string | null)[] = [ null, null, + null, null, + null, null, + null, null, + "IDENTIFIER", + "WS" ]; + // tslint:disable:no-trailing-whitespace + public static readonly ruleNames: string[] = [ + "query", "expression", "atom", "operator", + ]; + public get grammarFileName(): string { return "TraceOperatorGrammar.g4"; } + public get literalNames(): (string | null)[] { return TraceOperatorGrammarParser.literalNames; } + public get symbolicNames(): (string | null)[] { return TraceOperatorGrammarParser.symbolicNames; } + public get ruleNames(): string[] { return TraceOperatorGrammarParser.ruleNames; } + public get serializedATN(): number[] { return TraceOperatorGrammarParser._serializedATN; } + + protected createFailedPredicateException(predicate?: string, message?: string): FailedPredicateException { + return new FailedPredicateException(this, predicate, message); + } + + constructor(input: TokenStream) { + super(input); + this._interp = new ParserATNSimulator(this, TraceOperatorGrammarParser._ATN, TraceOperatorGrammarParser.DecisionsToDFA, new PredictionContextCache()); + } + // @RuleVersion(0) + public query(): QueryContext { + let localctx: QueryContext = new QueryContext(this, this._ctx, this.state); + this.enterRule(localctx, 0, TraceOperatorGrammarParser.RULE_query); + let _la: number; + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 9; + this._errHandler.sync(this); + _la = this._input.LA(1); + do { + { + { + this.state = 8; + this.expression(); + } + } + this.state = 11; + this._errHandler.sync(this); + _la = this._input.LA(1); + } while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 262) !== 0)); + this.state = 13; + this.match(TraceOperatorGrammarParser.EOF); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public expression(): ExpressionContext { + let localctx: ExpressionContext = new ExpressionContext(this, this._ctx, this.state); + this.enterRule(localctx, 2, TraceOperatorGrammarParser.RULE_expression); + try { + this.state = 38; + this._errHandler.sync(this); + switch ( this._interp.adaptivePredict(this._input, 1, this._ctx) ) { + case 1: + this.enterOuterAlt(localctx, 1); + { + this.state = 15; + this.match(TraceOperatorGrammarParser.T__0); + this.state = 16; + this.expression(); + } + break; + case 2: + this.enterOuterAlt(localctx, 2); + { + this.state = 17; + this.match(TraceOperatorGrammarParser.T__1); + this.state = 18; + this.expression(); + this.state = 19; + this.match(TraceOperatorGrammarParser.T__2); + this.state = 20; + this.operator(); + this.state = 21; + this.expression(); + } + break; + case 3: + this.enterOuterAlt(localctx, 3); + { + this.state = 23; + this.match(TraceOperatorGrammarParser.T__1); + this.state = 24; + this.expression(); + this.state = 25; + this.match(TraceOperatorGrammarParser.T__2); + } + break; + case 4: + this.enterOuterAlt(localctx, 4); + { + this.state = 27; + localctx._left = this.atom(); + this.state = 28; + this.operator(); + this.state = 29; + localctx._right = this.expression(); + } + break; + case 5: + this.enterOuterAlt(localctx, 5); + { + this.state = 31; + localctx._left = this.atom(); + this.state = 32; + this.operator(); + this.state = 33; + this.match(TraceOperatorGrammarParser.T__1); + this.state = 34; + localctx._expr = this.expression(); + this.state = 35; + this.match(TraceOperatorGrammarParser.T__2); + } + break; + case 6: + this.enterOuterAlt(localctx, 6); + { + this.state = 37; + this.atom(); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public atom(): AtomContext { + let localctx: AtomContext = new AtomContext(this, this._ctx, this.state); + this.enterRule(localctx, 4, TraceOperatorGrammarParser.RULE_atom); + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 40; + this.match(TraceOperatorGrammarParser.IDENTIFIER); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public operator(): OperatorContext { + let localctx: OperatorContext = new OperatorContext(this, this._ctx, this.state); + this.enterRule(localctx, 6, TraceOperatorGrammarParser.RULE_operator); + let _la: number; + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 42; + _la = this._input.LA(1); + if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 242) !== 0))) { + this._errHandler.recoverInline(this); + } + else { + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + + public static readonly _serializedATN: number[] = [4,1,9,45,2,0,7,0,2,1, + 7,1,2,2,7,2,2,3,7,3,1,0,4,0,10,8,0,11,0,12,0,11,1,0,1,0,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,3,1,39,8,1,1,2,1,2,1,3,1,3,1,3,0,0,4,0,2,4,6,0,1,2,0,1,1,4,7,46,0,9, + 1,0,0,0,2,38,1,0,0,0,4,40,1,0,0,0,6,42,1,0,0,0,8,10,3,2,1,0,9,8,1,0,0,0, + 10,11,1,0,0,0,11,9,1,0,0,0,11,12,1,0,0,0,12,13,1,0,0,0,13,14,5,0,0,1,14, + 1,1,0,0,0,15,16,5,1,0,0,16,39,3,2,1,0,17,18,5,2,0,0,18,19,3,2,1,0,19,20, + 5,3,0,0,20,21,3,6,3,0,21,22,3,2,1,0,22,39,1,0,0,0,23,24,5,2,0,0,24,25,3, + 2,1,0,25,26,5,3,0,0,26,39,1,0,0,0,27,28,3,4,2,0,28,29,3,6,3,0,29,30,3,2, + 1,0,30,39,1,0,0,0,31,32,3,4,2,0,32,33,3,6,3,0,33,34,5,2,0,0,34,35,3,2,1, + 0,35,36,5,3,0,0,36,39,1,0,0,0,37,39,3,4,2,0,38,15,1,0,0,0,38,17,1,0,0,0, + 38,23,1,0,0,0,38,27,1,0,0,0,38,31,1,0,0,0,38,37,1,0,0,0,39,3,1,0,0,0,40, + 41,5,8,0,0,41,5,1,0,0,0,42,43,7,0,0,0,43,7,1,0,0,0,2,11,38]; + + private static __ATN: ATN; + public static get _ATN(): ATN { + if (!TraceOperatorGrammarParser.__ATN) { + TraceOperatorGrammarParser.__ATN = new ATNDeserializer().deserialize(TraceOperatorGrammarParser._serializedATN); + } + + return TraceOperatorGrammarParser.__ATN; + } + + + static DecisionsToDFA = TraceOperatorGrammarParser._ATN.decisionToState.map( (ds: DecisionState, index: number) => new DFA(ds, index) ); + +} + +export class QueryContext extends ParserRuleContext { + constructor(parser?: TraceOperatorGrammarParser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public EOF(): TerminalNode { + return this.getToken(TraceOperatorGrammarParser.EOF, 0); + } + public expression_list(): ExpressionContext[] { + return this.getTypedRuleContexts(ExpressionContext) as ExpressionContext[]; + } + public expression(i: number): ExpressionContext { + return this.getTypedRuleContext(ExpressionContext, i) as ExpressionContext; + } + public get ruleIndex(): number { + return TraceOperatorGrammarParser.RULE_query; + } + public enterRule(listener: TraceOperatorGrammarListener): void { + if(listener.enterQuery) { + listener.enterQuery(this); + } + } + public exitRule(listener: TraceOperatorGrammarListener): void { + if(listener.exitQuery) { + listener.exitQuery(this); + } + } + // @Override + public accept(visitor: TraceOperatorGrammarVisitor): Result { + if (visitor.visitQuery) { + return visitor.visitQuery(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class ExpressionContext extends ParserRuleContext { + public _left!: AtomContext; + public _right!: ExpressionContext; + public _expr!: ExpressionContext; + constructor(parser?: TraceOperatorGrammarParser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public expression_list(): ExpressionContext[] { + return this.getTypedRuleContexts(ExpressionContext) as ExpressionContext[]; + } + public expression(i: number): ExpressionContext { + return this.getTypedRuleContext(ExpressionContext, i) as ExpressionContext; + } + public operator(): OperatorContext { + return this.getTypedRuleContext(OperatorContext, 0) as OperatorContext; + } + public atom(): AtomContext { + return this.getTypedRuleContext(AtomContext, 0) as AtomContext; + } + public get ruleIndex(): number { + return TraceOperatorGrammarParser.RULE_expression; + } + public enterRule(listener: TraceOperatorGrammarListener): void { + if(listener.enterExpression) { + listener.enterExpression(this); + } + } + public exitRule(listener: TraceOperatorGrammarListener): void { + if(listener.exitExpression) { + listener.exitExpression(this); + } + } + // @Override + public accept(visitor: TraceOperatorGrammarVisitor): Result { + if (visitor.visitExpression) { + return visitor.visitExpression(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class AtomContext extends ParserRuleContext { + constructor(parser?: TraceOperatorGrammarParser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public IDENTIFIER(): TerminalNode { + return this.getToken(TraceOperatorGrammarParser.IDENTIFIER, 0); + } + public get ruleIndex(): number { + return TraceOperatorGrammarParser.RULE_atom; + } + public enterRule(listener: TraceOperatorGrammarListener): void { + if(listener.enterAtom) { + listener.enterAtom(this); + } + } + public exitRule(listener: TraceOperatorGrammarListener): void { + if(listener.exitAtom) { + listener.exitAtom(this); + } + } + // @Override + public accept(visitor: TraceOperatorGrammarVisitor): Result { + if (visitor.visitAtom) { + return visitor.visitAtom(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class OperatorContext extends ParserRuleContext { + constructor(parser?: TraceOperatorGrammarParser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public get ruleIndex(): number { + return TraceOperatorGrammarParser.RULE_operator; + } + public enterRule(listener: TraceOperatorGrammarListener): void { + if(listener.enterOperator) { + listener.enterOperator(this); + } + } + public exitRule(listener: TraceOperatorGrammarListener): void { + if(listener.exitOperator) { + listener.exitOperator(this); + } + } + // @Override + public accept(visitor: TraceOperatorGrammarVisitor): Result { + if (visitor.visitOperator) { + return visitor.visitOperator(this); + } else { + return visitor.visitChildren(this); + } + } +} diff --git a/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarVisitor.ts b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarVisitor.ts new file mode 100644 index 000000000000..9a413e5429b9 --- /dev/null +++ b/frontend/src/parser/TraceOperatorParser/TraceOperatorGrammarVisitor.ts @@ -0,0 +1,45 @@ +// Generated from ./TraceOperatorGrammar.g4 by ANTLR 4.13.1 + +import {ParseTreeVisitor} from 'antlr4'; + + +import { QueryContext } from "./TraceOperatorGrammarParser"; +import { ExpressionContext } from "./TraceOperatorGrammarParser"; +import { AtomContext } from "./TraceOperatorGrammarParser"; +import { OperatorContext } from "./TraceOperatorGrammarParser"; + + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by `TraceOperatorGrammarParser`. + * + * @param The return type of the visit operation. Use `void` for + * operations with no return type. + */ +export default class TraceOperatorGrammarVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by `TraceOperatorGrammarParser.query`. + * @param ctx the parse tree + * @return the visitor result + */ + visitQuery?: (ctx: QueryContext) => Result; + /** + * Visit a parse tree produced by `TraceOperatorGrammarParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitExpression?: (ctx: ExpressionContext) => Result; + /** + * Visit a parse tree produced by `TraceOperatorGrammarParser.atom`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAtom?: (ctx: AtomContext) => Result; + /** + * Visit a parse tree produced by `TraceOperatorGrammarParser.operator`. + * @param ctx the parse tree + * @return the visitor result + */ + visitOperator?: (ctx: OperatorContext) => Result; +} + diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 576cef827abc..e3a2ac8af508 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -7,6 +7,7 @@ import { initialClickHouseData, initialFormulaBuilderFormValues, initialQueriesMap, + initialQueryBuilderFormTraceOperatorValues, initialQueryBuilderFormValuesMap, initialQueryPromQLData, initialQueryState, @@ -14,6 +15,7 @@ import { MAX_FORMULAS, MAX_QUERIES, PANEL_TYPES, + TRACE_OPERATOR_QUERY_NAME, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import { @@ -45,6 +47,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe import { IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, IClickHouseQuery, IPromQLQuery, Query, @@ -72,14 +75,18 @@ export const QueryBuilderContext = createContext({ panelType: PANEL_TYPES.TIME_SERIES, isEnabledQuery: false, handleSetQueryData: () => {}, + handleSetTraceOperatorData: () => {}, handleSetFormulaData: () => {}, handleSetQueryItemData: () => {}, handleSetConfig: () => {}, removeQueryBuilderEntityByIndex: () => {}, + removeAllQueryBuilderEntities: () => {}, removeQueryTypeItemByIndex: () => {}, addNewBuilderQuery: () => {}, cloneQuery: () => {}, addNewFormula: () => {}, + addTraceOperator: () => {}, + removeTraceOperator: () => {}, addNewQueryItem: () => {}, redirectWithQueryBuilderData: () => {}, handleRunQuery: () => {}, @@ -166,6 +173,10 @@ export function QueryBuilderProvider({ ...initialFormulaBuilderFormValues, ...item, })), + queryTraceOperator: query.builder.queryTraceOperator?.map((item) => ({ + ...initialQueryBuilderFormTraceOperatorValues, + ...item, + })), }; const setupedQueryData = builder.queryData.map((item) => { @@ -378,8 +389,11 @@ export function QueryBuilderProvider({ const removeQueryBuilderEntityByIndex = useCallback( (type: keyof QueryBuilderData, index: number) => { setCurrentQuery((prevState) => { - const currentArray: (IBuilderQuery | IBuilderFormula)[] = - prevState.builder[type]; + const currentArray: ( + | IBuilderQuery + | IBuilderFormula + | IBuilderTraceOperator + )[] = prevState.builder[type]; const filteredArray = currentArray.filter((_, i) => index !== i); @@ -393,8 +407,11 @@ export function QueryBuilderProvider({ }); // eslint-disable-next-line sonarjs/no-identical-functions setSupersetQuery((prevState) => { - const currentArray: (IBuilderQuery | IBuilderFormula)[] = - prevState.builder[type]; + const currentArray: ( + | IBuilderQuery + | IBuilderFormula + | IBuilderTraceOperator + )[] = prevState.builder[type]; const filteredArray = currentArray.filter((_, i) => index !== i); @@ -410,6 +427,20 @@ export function QueryBuilderProvider({ [], ); + const removeAllQueryBuilderEntities = useCallback( + (type: keyof QueryBuilderData) => { + setCurrentQuery((prevState) => ({ + ...prevState, + builder: { ...prevState.builder, [type]: [] }, + })); + setSupersetQuery((prevState) => ({ + ...prevState, + builder: { ...prevState.builder, [type]: [] }, + })); + }, + [setCurrentQuery, setSupersetQuery], + ); + const removeQueryTypeItemByIndex = useCallback( (type: EQueryType.PROM | EQueryType.CLICKHOUSE, index: number) => { setCurrentQuery((prevState) => { @@ -632,6 +663,68 @@ export function QueryBuilderProvider({ }); }, [createNewBuilderFormula]); + const addTraceOperator = useCallback((expression = '') => { + const trimmed = (expression || '').trim(); + + setCurrentQuery((prevState) => { + const existing = prevState.builder.queryTraceOperator?.[0] || null; + const updated: IBuilderTraceOperator = existing + ? { ...existing, expression: trimmed } + : { + ...initialQueryBuilderFormTraceOperatorValues, + queryName: TRACE_OPERATOR_QUERY_NAME, + expression: trimmed, + }; + + return { + ...prevState, + builder: { + ...prevState.builder, + // enforce single trace operator and replace only expression + queryTraceOperator: [updated], + }, + }; + }); + // eslint-disable-next-line sonarjs/no-identical-functions + setSupersetQuery((prevState) => { + const existing = prevState.builder.queryTraceOperator?.[0] || null; + const updated: IBuilderTraceOperator = existing + ? { ...existing, expression: trimmed } + : { + ...initialQueryBuilderFormTraceOperatorValues, + queryName: TRACE_OPERATOR_QUERY_NAME, + expression: trimmed, + }; + + return { + ...prevState, + builder: { + ...prevState.builder, + // enforce single trace operator and replace only expression + queryTraceOperator: [updated], + }, + }; + }); + }, []); + + const removeTraceOperator = useCallback(() => { + setCurrentQuery((prevState) => ({ + ...prevState, + builder: { + ...prevState.builder, + queryTraceOperator: [], + }, + })); + // eslint-disable-next-line sonarjs/no-identical-functions + setSupersetQuery((prevState) => ({ + ...prevState, + builder: { + ...prevState.builder, + queryTraceOperator: [], + }, + })); + }, []); + const updateQueryBuilderData: ( arr: T[], index: number, @@ -738,6 +831,44 @@ export function QueryBuilderProvider({ }, [updateQueryBuilderData, updateSuperSetQueryBuilderData], ); + + const handleSetTraceOperatorData = useCallback( + (index: number, traceOperatorData: IBuilderTraceOperator): void => { + setCurrentQuery((prevState) => { + const updatedTraceOperatorBuilderData = updateQueryBuilderData( + prevState.builder.queryTraceOperator, + index, + traceOperatorData, + ); + + return { + ...prevState, + builder: { + ...prevState.builder, + queryTraceOperator: updatedTraceOperatorBuilderData, + }, + }; + }); + // eslint-disable-next-line sonarjs/no-identical-functions + setSupersetQuery((prevState) => { + const updatedTraceOperatorBuilderData = updateQueryBuilderData( + prevState.builder.queryTraceOperator, + index, + traceOperatorData, + ); + + return { + ...prevState, + builder: { + ...prevState.builder, + queryTraceOperator: updatedTraceOperatorBuilderData, + }, + }; + }); + }, + [updateQueryBuilderData], + ); + const handleSetFormulaData = useCallback( (index: number, formulaData: IBuilderFormula): void => { setCurrentQuery((prevState) => { @@ -1020,14 +1151,18 @@ export function QueryBuilderProvider({ panelType, isEnabledQuery, handleSetQueryData, + handleSetTraceOperatorData, handleSetFormulaData, handleSetQueryItemData, handleSetConfig, removeQueryBuilderEntityByIndex, removeQueryTypeItemByIndex, + removeAllQueryBuilderEntities, cloneQuery, addNewBuilderQuery, addNewFormula, + addTraceOperator, + removeTraceOperator, addNewQueryItem, redirectWithQueryBuilderData, handleRunQuery, @@ -1048,14 +1183,18 @@ export function QueryBuilderProvider({ panelType, isEnabledQuery, handleSetQueryData, + handleSetTraceOperatorData, handleSetFormulaData, handleSetQueryItemData, handleSetConfig, removeQueryBuilderEntityByIndex, removeQueryTypeItemByIndex, + removeAllQueryBuilderEntities, cloneQuery, addNewBuilderQuery, addNewFormula, + addTraceOperator, + removeTraceOperator, addNewQueryItem, redirectWithQueryBuilderData, handleRunQuery, diff --git a/frontend/src/types/api/queryBuilder/queryBuilderData.ts b/frontend/src/types/api/queryBuilder/queryBuilderData.ts index 6934456bebed..ff63a9f3531e 100644 --- a/frontend/src/types/api/queryBuilder/queryBuilderData.ts +++ b/frontend/src/types/api/queryBuilder/queryBuilderData.ts @@ -29,6 +29,8 @@ export interface IBuilderFormula { orderBy?: OrderByPayload[]; } +export type IBuilderTraceOperator = IBuilderQuery; + export interface TagFilterItem { id: string; key?: BaseAutocompleteData; @@ -118,12 +120,13 @@ export type BuilderClickHouseResource = Record; export type BuilderPromQLResource = Record; export type BuilderQueryDataResourse = Record< string, - IBuilderQuery | IBuilderFormula + IBuilderQuery | IBuilderFormula | IBuilderTraceOperator >; export type MapData = | IBuilderQuery | IBuilderFormula + | IBuilderTraceOperator | IClickHouseQuery | IPromQLQuery; diff --git a/frontend/src/types/api/v5/queryRange.ts b/frontend/src/types/api/v5/queryRange.ts index 8cb04a6daa3e..f9778204a1ea 100644 --- a/frontend/src/types/api/v5/queryRange.ts +++ b/frontend/src/types/api/v5/queryRange.ts @@ -14,6 +14,7 @@ export type RequestType = export type QueryType = | 'builder_query' + | 'builder_trace_operator' | 'builder_formula' | 'builder_sub_query' | 'builder_join' @@ -220,6 +221,7 @@ export interface BaseBuilderQuery { secondaryAggregations?: SecondaryAggregation[]; functions?: QueryFunction[]; legend?: string; + expression?: string; // for trace operator } export interface TraceBuilderQuery extends BaseBuilderQuery { diff --git a/frontend/src/types/common/operations.types.ts b/frontend/src/types/common/operations.types.ts index 3994bfda401b..ed4f854b5cbf 100644 --- a/frontend/src/types/common/operations.types.ts +++ b/frontend/src/types/common/operations.types.ts @@ -4,6 +4,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe import { IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, } from 'types/api/queryBuilder/queryBuilderData'; import { BaseBuilderQuery, @@ -18,6 +19,7 @@ import { SelectOption } from './select'; type UseQueryOperationsParams = Pick & Pick & { + isForTraceOperator?: boolean; formula?: IBuilderFormula; isListViewPanel?: boolean; entityVersion: string; @@ -32,6 +34,14 @@ export type HandleChangeQueryData = < value: Value, ) => void; +export type HandleChangeTraceOperatorData = < + Key extends keyof T, + Value extends T[Key] +>( + key: Key, + value: Value, +) => void; + // Legacy version for backward compatibility export type HandleChangeQueryDataLegacy = HandleChangeQueryData; diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index 8f987ec8aed5..0bff0331da99 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -6,6 +6,7 @@ import { Dispatch, SetStateAction } from 'react'; import { IBuilderFormula, IBuilderQuery, + IBuilderTraceOperator, IClickHouseQuery, IPromQLQuery, Query, @@ -222,6 +223,7 @@ export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min'; export type QueryBuilderData = { queryData: IBuilderQuery[]; queryFormulas: IBuilderFormula[]; + queryTraceOperator: IBuilderTraceOperator[]; }; export type QueryBuilderContextType = { @@ -235,6 +237,10 @@ export type QueryBuilderContextType = { panelType: PANEL_TYPES | null; isEnabledQuery: boolean; handleSetQueryData: (index: number, queryData: IBuilderQuery) => void; + handleSetTraceOperatorData: ( + index: number, + traceOperatorData: IBuilderTraceOperator, + ) => void; handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void; handleSetQueryItemData: ( index: number, @@ -249,12 +255,15 @@ export type QueryBuilderContextType = { type: keyof QueryBuilderData, index: number, ) => void; + removeAllQueryBuilderEntities: (type: keyof QueryBuilderData) => void; removeQueryTypeItemByIndex: ( type: EQueryType.PROM | EQueryType.CLICKHOUSE, index: number, ) => void; addNewBuilderQuery: () => void; addNewFormula: () => void; + removeTraceOperator: () => void; + addTraceOperator: (expression?: string) => void; cloneQuery: (type: string, query: IBuilderQuery) => void; addNewQueryItem: (type: EQueryType.PROM | EQueryType.CLICKHOUSE) => void; redirectWithQueryBuilderData: ( diff --git a/frontend/src/utils/compositeQueryToQueryEnvelope.ts b/frontend/src/utils/compositeQueryToQueryEnvelope.ts index a9060a5f2a20..55631f391d34 100644 --- a/frontend/src/utils/compositeQueryToQueryEnvelope.ts +++ b/frontend/src/utils/compositeQueryToQueryEnvelope.ts @@ -2,10 +2,15 @@ import { convertBuilderQueriesToV5, convertClickHouseQueriesToV5, convertPromQueriesToV5, + convertTraceOperatorToV5, mapPanelTypeToRequestType, } from 'api/v5/queryRange/prepareQueryRangePayloadV5'; +import { TRACE_OPERATOR_QUERY_NAME } from 'constants/queryBuilder'; import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery'; -import { BuilderQueryDataResourse } from 'types/api/queryBuilder/queryBuilderData'; +import { + BuilderQueryDataResourse, + IBuilderTraceOperator, +} from 'types/api/queryBuilder/queryBuilderData'; import { OrderBy, QueryEnvelope } from 'types/api/v5/queryRange'; function convertFormulasToV5( @@ -46,9 +51,12 @@ export function compositeQueryToQueryEnvelope( const regularQueries: BuilderQueryDataResourse = {}; const formulaQueries: BuilderQueryDataResourse = {}; + const traceOperatorQueries: BuilderQueryDataResourse = {}; Object.entries(builderQueries || {}).forEach(([queryName, queryData]) => { - if ('dataSource' in queryData) { + if (queryData.queryName === TRACE_OPERATOR_QUERY_NAME) { + traceOperatorQueries[queryName] = queryData; + } else if ('dataSource' in queryData) { regularQueries[queryName] = queryData; } else { formulaQueries[queryName] = queryData; @@ -64,6 +72,12 @@ export function compositeQueryToQueryEnvelope( ); const formulaQueriesV5 = convertFormulasToV5(formulaQueries); + const traceOperatorQueriesV5 = convertTraceOperatorToV5( + traceOperatorQueries as Record, + requestType, + panelType, + ); + const promQueriesV5 = convertPromQueriesToV5(promQueries || {}); const chQueriesV5 = convertClickHouseQueriesToV5(chQueries || {}); @@ -72,7 +86,11 @@ export function compositeQueryToQueryEnvelope( switch (queryType) { case 'builder': - queries = [...builderQueriesV5, ...formulaQueriesV5]; + queries = [ + ...builderQueriesV5, + ...formulaQueriesV5, + ...traceOperatorQueriesV5, + ]; break; case 'promql': queries = [...promQueriesV5]; @@ -85,6 +103,7 @@ export function compositeQueryToQueryEnvelope( queries = [ ...builderQueriesV5, ...formulaQueriesV5, + ...traceOperatorQueriesV5, ...promQueriesV5, ...chQueriesV5, ]; diff --git a/frontend/src/utils/queryValidationUtils.ts b/frontend/src/utils/queryValidationUtils.ts index 7bbb90592f16..b81054afe25e 100644 --- a/frontend/src/utils/queryValidationUtils.ts +++ b/frontend/src/utils/queryValidationUtils.ts @@ -3,6 +3,8 @@ import { CharStreams, CommonTokenStream } from 'antlr4'; import FilterQueryLexer from 'parser/FilterQueryLexer'; import FilterQueryParser from 'parser/FilterQueryParser'; +import TraceOperatorGrammarLexer from 'parser/TraceOperatorParser/TraceOperatorGrammarLexer'; +import TraceOperatorGrammarParser from 'parser/TraceOperatorParser/TraceOperatorGrammarParser'; import { IDetailedError, IValidationResult } from 'types/antlrQueryTypes'; // Custom error listener to capture ANTLR errors @@ -169,3 +171,66 @@ export const validateQuery = (query: string): IValidationResult => { }; } }; + +export const validateTraceOperatorQuery = ( + query: string, +): IValidationResult => { + // Empty query is considered valid + if (!query.trim()) { + return { + isValid: true, + message: 'Trace operator query is empty', + errors: [], + }; + } + + try { + const errorListener = new QueryErrorListener(); + const inputStream = CharStreams.fromString(query); + + // Setup lexer + const lexer = new TraceOperatorGrammarLexer(inputStream); + lexer.removeErrorListeners(); // Remove default error listeners + lexer.addErrorListener(errorListener); + + // Setup parser + const tokenStream = new CommonTokenStream(lexer); + const parser = new TraceOperatorGrammarParser(tokenStream); + parser.removeErrorListeners(); // Remove default error listeners + parser.addErrorListener(errorListener); + + // Try parsing + parser.query(); + + // Check if any errors were captured + if (errorListener.hasErrors()) { + return { + isValid: false, + message: 'Trace operator syntax error', + errors: errorListener.getErrors(), + }; + } + + return { + isValid: true, + message: 'Trace operator is valid!', + errors: [], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Invalid trace operator syntax'; + + const detailedError: IDetailedError = { + message: errorMessage, + line: 0, + column: 0, + offendingSymbol: '', + expectedTokens: [], + }; + return { + isValid: false, + message: 'Invalid trace operator syntax', + errors: [detailedError], + }; + } +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 81d975cbbc86..9408ac345e3c 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -31,7 +31,7 @@ ], "types": ["node", "jest"] }, - "exclude": ["node_modules", "src/parser/*.ts"], + "exclude": ["node_modules", "src/parser/*.ts", "src/parser/TraceOperatorParser/*.ts"], "include": [ "./src", "./src/**/*.ts", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1671450c276e..99360b670401 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4276,6 +4276,20 @@ tailwind-merge "^2.5.2" tailwindcss-animate "^1.0.7" +"@signozhq/callout@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@signozhq/callout/-/callout-0.0.2.tgz#131ca15f89a8ee6729fecc4d322f11359c02e5cf" + integrity sha512-tmguHm+/JVRKjMElJOFyG7LJcdqCW1hHnFfp8ZkjQ+Gi7MfFt/r2foLZG2DNdOcfxSvhf2zhzr7D+epgvmbQ1A== + dependencies: + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-slot" "^1.1.0" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + lucide-react "^0.445.0" + lucide-solid "^0.510.0" + tailwind-merge "^2.5.2" + tailwindcss-animate "^1.0.7" + "@signozhq/design-tokens@1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-1.1.4.tgz#5d5de5bd9d19b6a3631383db015cc4b70c3f7661" @@ -12370,6 +12384,11 @@ lucide-react@^0.445.0: resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.445.0.tgz#35c42341e98fbf0475b2a6cf74fd25ef7cbfcd62" integrity sha512-YrLf3aAHvmd4dZ8ot+mMdNFrFpJD7YRwQ2pUcBhgqbmxtrMP4xDzIorcj+8y+6kpuXBF4JB0NOCTUWIYetJjgA== +lucide-solid@^0.510.0: + version "0.510.0" + resolved "https://registry.yarnpkg.com/lucide-solid/-/lucide-solid-0.510.0.tgz#f5b17397ef1df3017f62f96f4d00e080abfb492f" + integrity sha512-G6rKYxURfSLG/zeOCN/BEl2dq2ezujFKPbcHjl7RLJ4bBQwWk4ZF2Swga/8anWglSVZyqYz7HMrrpb8/+vOcXw== + lz-string@^1.4.4: version "1.5.0" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" diff --git a/grammar/TraceOperatorGrammar.g4 b/grammar/TraceOperatorGrammar.g4 new file mode 100644 index 000000000000..e52931f3fa6b --- /dev/null +++ b/grammar/TraceOperatorGrammar.g4 @@ -0,0 +1,39 @@ +grammar TraceOperatorGrammar; + +// Entry point of the grammar (the root of the parse tree) +query : expression+ EOF; + +// Expression rules +expression + : 'NOT' expression // NOT prefix expression + | '(' expression ')' operator expression // Parenthesized operator expression + | '(' expression ')' // Parenthesized expression + | left=atom operator right=expression // Binary operator with expression on right + | left=atom operator '(' expr=expression ')' // Expression with parentheses inside + | atom // Simple expression (atom) + ; + +// Atom definition: atoms are identifiers (letters and optional numbers) +atom + : IDENTIFIER // General atom (combination of letters and numbers) + ; + +// Operator definition +operator + : '=>' // Implication + | '&&' // AND + | '||' // OR + | 'NOT' // NOT + | '->' // Implication + ; + +// Lexer rules + +// IDENTIFIER can be a sequence of letters followed by optional numbers +IDENTIFIER + : [a-zA-Z]+[0-9]* // Letters followed by optional numbers (e.g., A1, B123, C99) + ; + +// Whitespace (to be skipped) +WS + : [ \t\r\n]+ -> skip; // Skip whitespace