diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx new file mode 100644 index 0000000000..d98bbf18fe --- /dev/null +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx @@ -0,0 +1,127 @@ +import { Input, notification } from 'antd'; +import getFilters from 'api/trace/getFilters'; +import { AxiosError } from 'axios'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Dispatch } from 'redux'; +import { getFilter, updateURL } from 'store/actions/trace/util'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_ALL_FILTERS } from 'types/actions/trace'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { TraceReducer } from 'types/reducer/trace'; + +const { Search } = Input; + +function TraceID(): JSX.Element { + const { + selectedFilter, + filterToFetchData, + spansAggregate, + selectedTags, + userSelectedFilter, + isFilterExclude, + } = useSelector((state) => state.traces); + const dispatch = useDispatch>(); + const globalTime = useSelector( + (state) => state.globalTime, + ); + const [isLoading, setIsLoading] = useState(false); + const [userEnteredValue, setUserEnteredValue] = useState(''); + useEffect(() => { + setUserEnteredValue(selectedFilter.get('traceID')?.[0] || ''); + }, [selectedFilter]); + const onSearch = async (value: string): Promise => { + try { + setIsLoading(true); + const preSelectedFilter = new Map(selectedFilter); + const preUserSelected = new Map(userSelectedFilter); + + if (value !== '') { + preUserSelected.set('traceID', [value]); + preSelectedFilter.set('traceID', [value]); + } else { + preUserSelected.delete('traceID'); + preSelectedFilter.delete('traceID'); + } + const response = await getFilters({ + other: Object.fromEntries(preSelectedFilter), + end: String(globalTime.maxTime), + start: String(globalTime.minTime), + getFilters: filterToFetchData, + isFilterExclude, + }); + + if (response.statusCode === 200) { + const preFilter = getFilter(response.payload); + preFilter.set('traceID', { traceID: value }); + preFilter.forEach((value, key) => { + const values = Object.keys(value); + if (key !== 'duration' && values.length) { + preUserSelected.set(key, values); + } + }); + + dispatch({ + type: UPDATE_ALL_FILTERS, + payload: { + current: spansAggregate.currentPage, + filter: preFilter, + filterToFetchData, + selectedFilter: preSelectedFilter, + selectedTags, + userSelected: preUserSelected, + isFilterExclude, + order: spansAggregate.order, + pageSize: spansAggregate.pageSize, + orderParam: spansAggregate.orderParam, + }, + }); + + updateURL( + preSelectedFilter, + filterToFetchData, + spansAggregate.currentPage, + selectedTags, + isFilterExclude, + userSelectedFilter, + spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, + ); + } + } catch (error) { + notification.error({ + message: (error as AxiosError).toString() || 'Something went wrong', + }); + } finally { + setIsLoading(false); + } + }; + const onChange = (e: React.ChangeEvent): void => { + setUserEnteredValue(e.target.value); + }; + const onBlur = (): void => { + if (userEnteredValue !== selectedFilter.get('traceID')?.[0]) { + onSearch(userEnteredValue); + } + }; + return ( +
+ +
+ ); +} + +export default TraceID; diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx index 5782a02f04..fda3599305 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ import { Card } from 'antd'; import Spinner from 'components/Spinner'; import React from 'react'; @@ -7,6 +8,7 @@ import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace'; import CommonCheckBox from './CommonCheckBox'; import Duration from './Duration'; +import TraceID from './SearchTraceID'; function PanelBody(props: PanelBodyProps): JSX.Element { const { type } = props; @@ -22,12 +24,17 @@ function PanelBody(props: PanelBodyProps): JSX.Element { ); } - - return ( - - {type === 'duration' ? : } - - ); + const renderBody = (type: TraceFilterEnum): JSX.Element => { + switch (type) { + case 'traceID': + return ; + case 'duration': + return ; + default: + return ; + } + }; + return {renderBody(type)}; } interface PanelBodyProps { diff --git a/frontend/src/container/Trace/Filters/index.tsx b/frontend/src/container/Trace/Filters/index.tsx index 95f73f4ed9..36edd206fe 100644 --- a/frontend/src/container/Trace/Filters/index.tsx +++ b/frontend/src/container/Trace/Filters/index.tsx @@ -16,6 +16,7 @@ export const AllTraceFilterEnum: TraceFilterEnum[] = [ 'httpMethod', 'httpRoute', 'httpUrl', + 'traceID', ]; function Filters(): JSX.Element { diff --git a/frontend/src/store/reducers/trace.ts b/frontend/src/store/reducers/trace.ts index 9b2b837478..ea6b9a7671 100644 --- a/frontend/src/store/reducers/trace.ts +++ b/frontend/src/store/reducers/trace.ts @@ -68,6 +68,7 @@ const initialValue: TraceReducer = { ['responseStatusCode', INITIAL_FILTER_VALUE], ['serviceName', INITIAL_FILTER_VALUE], ['status', INITIAL_FILTER_VALUE], + ['traceID', INITIAL_FILTER_VALUE], ]), }; diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index fed82dd0be..fa37924f5e 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -71,7 +71,8 @@ export type TraceFilterEnum = | 'serviceName' | 'status' | 'responseStatusCode' - | 'rpcMethod'; + | 'rpcMethod' + | 'traceID'; export const AllPanelHeading: { key: TraceFilterEnum; @@ -125,4 +126,8 @@ export const AllPanelHeading: { key: 'status', displayValue: 'Status', }, + { + key: 'traceID', + displayValue: 'Trace ID', + }, ]; diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index cabb84f3a4..b6bcc7b55e 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -936,6 +936,9 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode } args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -995,6 +998,8 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode for _, e := range queryParams.GetFilters { switch e { + case constants.TraceID: + continue case constants.ServiceName: finalQuery := fmt.Sprintf("SELECT serviceName, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query @@ -1271,6 +1276,9 @@ func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *mo var query string args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1461,6 +1469,9 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model var query string args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1557,6 +1568,9 @@ func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model. var query string args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1864,6 +1878,9 @@ func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, query query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) } + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index 2e01c976cb..3c6e8f6317 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -41,6 +41,7 @@ var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/rout var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db") const ( + TraceID = "traceID" ServiceName = "serviceName" HttpRoute = "httpRoute" HttpCode = "httpCode" diff --git a/pkg/query-service/model/queryParams.go b/pkg/query-service/model/queryParams.go index 06d416537a..6d78e7438f 100644 --- a/pkg/query-service/model/queryParams.go +++ b/pkg/query-service/model/queryParams.go @@ -182,6 +182,7 @@ type TagQuery struct { } type GetFilteredSpansParams struct { + TraceID []string `json:"traceID"` ServiceName []string `json:"serviceName"` Operation []string `json:"operation"` Kind string `json:"kind"` @@ -209,6 +210,7 @@ type GetFilteredSpansParams struct { } type GetFilteredSpanAggregatesParams struct { + TraceID []string `json:"traceID"` ServiceName []string `json:"serviceName"` Operation []string `json:"operation"` Kind string `json:"kind"` @@ -237,6 +239,7 @@ type GetFilteredSpanAggregatesParams struct { } type SpanFilterParams struct { + TraceID []string `json:"traceID"` Status []string `json:"status"` ServiceName []string `json:"serviceName"` HttpRoute []string `json:"httpRoute"` @@ -259,6 +262,7 @@ type SpanFilterParams struct { } type TagFilterParams struct { + TraceID []string `json:"traceID"` Status []string `json:"status"` ServiceName []string `json:"serviceName"` HttpRoute []string `json:"httpRoute"`