diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index aebd30b3b64e..fc516f49e95a 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -257,6 +257,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h s.config.APIServer.Timeout.Max, ).Wrap) r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap) + r.Use(middleware.NewComment().Wrap) apiHandler.RegisterRoutes(r, am) apiHandler.RegisterLogsRoutes(r, am) diff --git a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts index e77786492e40..737dbecb5421 100644 --- a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts +++ b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts @@ -5,7 +5,10 @@ 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, @@ -276,6 +279,103 @@ 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)); + + return { + stepInterval: queryData?.stepInterval || undefined, + groupBy: + queryData.groupBy?.length > 0 + ? queryData.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 + ? queryData.limit || queryData.pageSize || undefined + : queryData.limit || undefined, + offset: + requestType === 'raw' || requestType === 'trace' + ? queryData.offset + : undefined, + order: + queryData.orderBy?.length > 0 + ? queryData.orderBy.map( + (order: any): OrderBy => ({ + key: { + name: order.columnName, + }, + direction: order.order, + }), + ) + : undefined, + legend: isEmpty(queryData.legend) ? undefined : queryData.legend, + having: isEmpty(queryData.having) ? undefined : (queryData?.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, + ); + let spec: QueryEnvelope['spec']; + + // Skip aggregation for raw request type + const aggregations = + requestType === 'raw' + ? undefined + : createAggregation(traceOperatorData, panelType); + + spec = { + name: queryName, + returnSpansFrom: traceOperatorData.returnSpansFrom || '', + ...baseSpec, + expression: traceOperatorData.expression || '', + aggregations: aggregations as TraceAggregation[], + }; + + return { + type: 'builder_trace_operator' as QueryType, + spec, + }; + }, + ); +} + /** * Converts PromQL queries to V5 format */ @@ -357,14 +457,27 @@ 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', + ); + // Combine legend maps legendMap = { ...currentQueryData.newLegendMap, ...currentFormulas.newLegendMap, + ...currentTraceOperator.newLegendMap, }; // Convert builder queries @@ -397,8 +510,36 @@ export const prepareQueryRangePayloadV5 = ({ }), ); + const traceOperatorQueries = convertTraceOperatorToV5( + currentTraceOperator.data, + requestType, + graphType, + ); + + // const traceOperatorQueries = Object.entries(currentTraceOperator.data).map( + // ([queryName, traceOperatorData]): QueryEnvelope => ({ + // type: 'builder_trace_operator' as const, + // spec: { + // name: queryName, + // expression: traceOperatorData.expression || '', + // legend: isEmpty(traceOperatorData.legend) + // ? undefined + // : traceOperatorData.legend, + // limit: 10, + // order: traceOperatorData.orderBy?.map( + // // eslint-disable-next-line sonarjs/no-identical-functions + // (order: any): OrderBy => ({ + // key: { + // name: order.columnName, + // }, + // direction: order.order, + // }), + // ), + // }, + // }), + // ); // Combine both types - queries = [...builderQueries, ...formulaQueries]; + queries = [...builderQueries, ...formulaQueries, ...traceOperatorQueries]; break; } case EQueryType.PROM: { diff --git a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.styles.scss b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.styles.scss index f5404ea8776a..a5caffe15da6 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 { @@ -331,6 +335,12 @@ ); left: 15px; } + + &.has-trace-operator { + &::before { + height: 0px; + } + } } .formula-name { @@ -347,7 +357,7 @@ &::before { content: ''; - height: 65px; + height: 128px; content: ''; position: absolute; left: 0; diff --git a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx index d0621dad7a02..4ecbec900f72 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,14 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({ newPanelType, ]); + const isMultiQueryAllowed = useMemo( + () => + !showOnlyWhereClause || + !isListViewPanel || + (currentDataSource === DataSource.TRACES && showTraceOperator), + [showOnlyWhereClause, currentDataSource, showTraceOperator, isListViewPanel], + ); + const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { const config: QueryBuilderProps['filterConfigs'] = { stepInterval: { isHidden: true, isDisabled: true }, @@ -97,11 +109,45 @@ 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 shouldShowTraceOperator = useMemo( + () => + showTraceOperator && + currentDataSource === DataSource.TRACES && + Boolean(traceOperator), + [currentDataSource, showTraceOperator, traceOperator], + ); + + const shouldShowFooter = useMemo( + () => + (!showOnlyWhereClause && !isListViewPanel) || + (currentDataSource === DataSource.TRACES && showTraceOperator), + [isListViewPanel, showTraceOperator, showOnlyWhereClause, currentDataSource], + ); + + const showFormula = useMemo(() => { + if (currentDataSource === DataSource.TRACES) { + return !isListViewPanel; + } + + return true; + }, [isListViewPanel, currentDataSource]); + return (
- {isListViewPanel && ( + {!isMultiQueryAllowed ? ( - )} - - {!isListViewPanel && + ) : ( currentQuery.builder.queryData.map((query, index) => ( - ))} + )) + )} {!showOnlyWhereClause && currentQuery.builder.queryFormulas.length > 0 && (
@@ -158,15 +207,25 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
)} - {!showOnlyWhereClause && !isListViewPanel && ( + {shouldShowFooter && ( + )} + + {shouldShowTraceOperator && ( + )}
- {!showOnlyWhereClause && !isListViewPanel && ( + {isMultiQueryAllowed && (
{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..4447598a48fe 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/QueryAddOns.tsx @@ -144,6 +144,8 @@ function QueryAddOns({ showReduceTo, panelType, index, + isForTraceOperator = false, + children, }: { query: IBuilderQuery; version: string; @@ -151,6 +153,8 @@ function QueryAddOns({ showReduceTo: boolean; panelType: PANEL_TYPES | null; index: number; + isForTraceOperator?: boolean; + children?: React.ReactNode; }): JSX.Element { const [addOns, setAddOns] = useState(ADD_ONS); @@ -160,6 +164,7 @@ function QueryAddOns({ index, query, entityVersion: '', + isForTraceOperator, }); const { handleSetQueryData } = useQueryBuilder(); @@ -486,6 +491,7 @@ function QueryAddOns({ ))} + {children}
); diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx index 683f375ca497..986df51d0408 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..28d9b3c4ca73 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryFooter/QueryFooter.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryFooter/QueryFooter.tsx @@ -4,9 +4,15 @@ import { Plus, Sigma } from 'lucide-react'; 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 +28,62 @@ 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..fc7eb0734748 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx @@ -26,9 +26,11 @@ export const QueryV2 = memo(function QueryV2({ query, filterConfigs, isListViewPanel = false, + showTraceOperator = false, version, showOnlyWhereClause = false, signalSource = '', + isMultiQueryAllowed = false, }: QueryProps & { ref: React.RefObject }): JSX.Element { const { cloneQuery, panelType } = useQueryBuilder(); @@ -108,11 +110,15 @@ export const QueryV2 = memo(function QueryV2({ ref={ref} >
- {!showOnlyWhereClause && ( + {isMultiQueryAllowed && (
- {!isListViewPanel && ( + {!isCollapsed && + (showTraceOperator || + (isListViewPanel && dataSource === DataSource.TRACES)) && ( +
+
+ +
+ + {showSpanScopeSelector && ( +
+
in
+ +
+ )} +
+ )} + + {isMultiQueryAllowed && ( )} -
-
- -
+ {!showTraceOperator && + !(isListViewPanel && dataSource === DataSource.TRACES) && ( +
+
+ +
- {showSpanScopeSelector && ( -
-
in
- + {showSpanScopeSelector && ( +
+
in
+ +
+ )}
)} -
{!showOnlyWhereClause && !isListViewPanel && + !showTraceOperator && dataSource !== DataSource.METRICS && ( )} - {!showOnlyWhereClause && ( + {!showOnlyWhereClause && !isListViewPanel && !showTraceOperator && ( { + handleChangeQueryData('expression', traceOperatorExpression); + }, + [handleChangeQueryData], + ); + + const handleChangeAggregateEvery = useCallback( + (value: IBuilderQuery['stepInterval']) => { + handleChangeQueryData('stepInterval', value); + }, + [handleChangeQueryData], + ); + + const handleChangeAggregation = useCallback( + (value: string) => { + handleChangeQueryData('aggregations', [ + { + expression: value, + }, + ]); + }, + [handleChangeQueryData], + ); + + const handleChangeSpanSource = useCallback( + (value: string) => { + handleChangeQueryData('returnSpansFrom', value); + }, + [handleChangeQueryData], + ); + + const defaultSpanSource = useMemo( + () => + traceOperator.returnSpansFrom || + currentQuery.builder.queryData[0].queryName || + '', + [currentQuery.builder.queryData, traceOperator?.returnSpansFrom], + ); + + const spanSourceOptions = useMemo( + () => + currentQuery.builder.queryData.map((query) => ({ + value: query.queryName, + label: ( +
+ Query +

+ {query.queryName} +

+
+ ), + })), + [currentQuery.builder.queryData], + ); + + return ( +
+
+ + {!isListViewPanel && ( +
+
+ +
+
+ +
+ Using spans from +