From b66ea623d9d09f695a337b49e431db2b1a1db216 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Wed, 19 Feb 2025 14:08:06 -0800 Subject: [PATCH] Restore spans limit to 3000 in trace view (#2353) * restore spans max value to 3000 in trace view Signed-off-by: Shenoy Pratik * update filtering mechanism for span payloads Signed-off-by: Shenoy Pratik * avoid parsing again on re-render in trace view Signed-off-by: Shenoy Pratik --------- Signed-off-by: Shenoy Pratik (cherry picked from commit 84fbc2fe1a6809e7f95b4e8d58c32026e8ef4ec8) --- .../__snapshots__/flyout.test.tsx.snap | 2 + .../flyout_components/trace_detail_render.tsx | 18 ++++- .../components/common/constants.tsx | 5 ++ .../components/services/service_view.tsx | 5 +- .../__snapshots__/trace_view.test.tsx.snap | 2 + .../__tests__/span_detail_panel.test.tsx | 2 + .../components/traces/span_detail_panel.tsx | 80 +++++-------------- .../components/traces/span_detail_table.tsx | 4 +- .../components/traces/trace_view.tsx | 47 +++++++++-- .../components/traces/trace_view_helpers.tsx | 25 +++++- .../requests/queries/traces_queries.ts | 2 +- .../requests/traces_request_handler.ts | 6 +- 12 files changed, 122 insertions(+), 76 deletions(-) diff --git a/public/components/application_analytics/__tests__/__snapshots__/flyout.test.tsx.snap b/public/components/application_analytics/__tests__/__snapshots__/flyout.test.tsx.snap index 3e6f2bb88..50b35c8e2 100644 --- a/public/components/application_analytics/__tests__/__snapshots__/flyout.test.tsx.snap +++ b/public/components/application_analytics/__tests__/__snapshots__/flyout.test.tsx.snap @@ -751,6 +751,8 @@ exports[`Trace Detail Render Flyout component render trace detail 1`] = ` openSpanFlyout={[Function]} page="app" payloadData="" + setSpanFiltersWithStorage={[Function]} + spanFilters={Array []} traceId="mockTrace" > (() => + storedFilters ? JSON.parse(storedFilters) : [] + ); + + const setSpanFiltersWithStorage = (newFilters: TraceFilter[]) => { + handlePayloadRequest(traceId, http, payloadData, setPayloadData, mode); + setSpanFilters(newFilters); + sessionStorage.setItem('TraceAnalyticsSpanFilters', JSON.stringify(newFilters)); + }; + const renderContent = useMemo(() => { if (!traceId) return <>; const overviewList = [ @@ -79,6 +91,8 @@ export const TraceDetailRender = ({ dataSourceMDSId={dataSourceMDSId} isApplicationFlyout={true} payloadData={payloadData} + spanFilters={spanFilters} + setSpanFiltersWithStorage={setSpanFiltersWithStorage} /> diff --git a/public/components/trace_analytics/components/common/constants.tsx b/public/components/trace_analytics/components/common/constants.tsx index 9c723c5d6..5864392f9 100644 --- a/public/components/trace_analytics/components/common/constants.tsx +++ b/public/components/trace_analytics/components/common/constants.tsx @@ -59,3 +59,8 @@ export interface ParsedHit { _source: Span; sort?: any[]; } + +export interface TraceFilter { + field: string; + value: any; +} diff --git a/public/components/trace_analytics/components/services/service_view.tsx b/public/components/trace_analytics/components/services/service_view.tsx index a616b5e79..c4fae341c 100644 --- a/public/components/trace_analytics/components/services/service_view.tsx +++ b/public/components/trace_analytics/components/services/service_view.tsx @@ -45,6 +45,7 @@ import { handleServiceMapRequest, handleServiceViewRequest, } from '../../requests/services_request_handler'; +import { TraceFilter } from '../common/constants'; import { FilterType } from '../common/filters/filters'; import { PanelTitle, @@ -442,12 +443,12 @@ export function ServiceView(props: ServiceViewProps) { const [currentSpan, setCurrentSpan] = useState(''); const storedFilters = sessionStorage.getItem('TraceAnalyticsSpanFilters'); - const [spanFilters, setSpanFilters] = useState>( + const [spanFilters, setSpanFilters] = useState( storedFilters ? JSON.parse(storedFilters) : [] ); const [DSL, setDSL] = useState({}); - const setSpanFiltersWithStorage = (newFilters: Array<{ field: string; value: any }>) => { + const setSpanFiltersWithStorage = (newFilters: TraceFilter[]) => { setSpanFilters(newFilters); sessionStorage.setItem('TraceAnalyticsSpanFilters', JSON.stringify(newFilters)); }; diff --git a/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap index 1cf21dfba..9f3f46e13 100644 --- a/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap +++ b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/trace_view.test.tsx.snap @@ -147,6 +147,8 @@ exports[`Trace view component renders trace view 1`] = ` payloadData="" setGanttChartLoading={[Function]} setGanttData={[Function]} + setSpanFiltersWithStorage={[Function]} + spanFilters={Array []} traceId="test" /> diff --git a/public/components/trace_analytics/components/traces/__tests__/span_detail_panel.test.tsx b/public/components/trace_analytics/components/traces/__tests__/span_detail_panel.test.tsx index bb3061776..cff6b01cf 100644 --- a/public/components/trace_analytics/components/traces/__tests__/span_detail_panel.test.tsx +++ b/public/components/trace_analytics/components/traces/__tests__/span_detail_panel.test.tsx @@ -79,6 +79,8 @@ const mockProps = { setData: mockSetData, addSpanFilter: mockAddSpanFilter, removeSpanFilter: jest.fn(), + spanFilters: [], + setSpanFiltersWithStorage: jest.fn(), }; describe('SpanDetailPanel component', () => { diff --git a/public/components/trace_analytics/components/traces/span_detail_panel.tsx b/public/components/trace_analytics/components/traces/span_detail_panel.tsx index 82e6dfc52..38a2af63c 100644 --- a/public/components/trace_analytics/components/traces/span_detail_panel.tsx +++ b/public/components/trace_analytics/components/traces/span_detail_panel.tsx @@ -21,10 +21,11 @@ import { HttpSetup } from '../../../../../../../src/core/public'; import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { coreRefs } from '../../../../framework/core_refs'; import { Plt } from '../../../visualizations/plotly/plot'; +import { hitsToSpanDetailData } from '../../requests/traces_request_handler'; +import { TraceFilter } from '../common/constants'; import { PanelTitle, parseHits } from '../common/helper_functions'; import { SpanDetailFlyout } from './span_detail_flyout'; import { SpanDetailTable, SpanDetailTableHierarchy } from './span_detail_table'; -import { hitsToSpanDetailData } from '../../requests/traces_request_handler'; export function SpanDetailPanel(props: { http: HttpSetup; @@ -33,6 +34,8 @@ export function SpanDetailPanel(props: { mode: TraceAnalyticsMode; dataSourceMDSId: string; dataSourceMDSLabel: string | undefined; + spanFilters: TraceFilter[]; + setSpanFiltersWithStorage: (newFilters: TraceFilter[]) => void; page?: string; openSpanFlyout?: any; data?: { gantt: any[]; table: any[]; ganttMaxX: number }; @@ -44,11 +47,8 @@ export function SpanDetailPanel(props: { }) { const { chrome } = coreRefs; const { mode } = props; - const storedFilters = sessionStorage.getItem('TraceAnalyticsSpanFilters'); const fromApp = props.page === 'app'; - const [spanFilters, setSpanFilters] = useState>( - storedFilters ? JSON.parse(storedFilters) : [] - ); + let data: { gantt: any[]; table: any[]; ganttMaxX: number }; let setData: (data: { gantt: any[]; table: any[]; ganttMaxX: number }) => void; const [localData, localSetData] = useState<{ gantt: any[]; table: any[]; ganttMaxX: number }>({ @@ -109,70 +109,34 @@ export function SpanDetailPanel(props: { setSelectedRange(fullRange); }, [data.ganttMaxX]); - const setSpanFiltersWithStorage = (newFilters: Array<{ field: string; value: any }>) => { - setSpanFilters(newFilters); - sessionStorage.setItem('TraceAnalyticsSpanFilters', JSON.stringify(newFilters)); - }; - const addSpanFilter = (field: string, value: any) => { - const newFilters = [...spanFilters]; + const newFilters = [...props.spanFilters]; const index = newFilters.findIndex(({ field: filterField }) => field === filterField); if (index === -1) { newFilters.push({ field, value }); } else { newFilters.splice(index, 1, { field, value }); } - setSpanFiltersWithStorage(newFilters); + props.setSpanFiltersWithStorage(newFilters); }; const removeSpanFilter = (field: string) => { - const newFilters = [...spanFilters]; + const newFilters = [...props.spanFilters]; const index = newFilters.findIndex(({ field: filterField }) => field === filterField); if (index !== -1) { newFilters.splice(index, 1); - setSpanFiltersWithStorage(newFilters); - } - }; - - const parseAndFilterHits = ( - payloadData: string, - traceMode: string, - payloadSpanFilters: any[] - ) => { - try { - let hits = parseHits(props.payloadData); - - if (payloadSpanFilters.length > 0) { - hits = hits.filter((hit) => { - return payloadSpanFilters.every(({ field, value }) => { - const fieldValue = field.split('.').reduce((acc, part) => acc?.[part], hit._source); - return fieldValue === value; - }); - }); - } - - hits = hits.filter((hit) => { - if (traceMode === 'jaeger') { - return Boolean(hit._source?.process?.serviceName); - } else { - return Boolean(hit._source?.serviceName); - } - }); - - return hits; - } catch (error) { - console.error('Error processing payloadData in parseAndFilterHits:', error); - return []; + props.setSpanFiltersWithStorage(newFilters); } }; useEffect(() => { if (!props.payloadData) { - console.warn('No payloadData provided to SpanDetailPanel'); + props.setGanttChartLoading?.(false); + console.error('No payloadData provided to SpanDetailPanel'); return; } - const hits = parseAndFilterHits(props.payloadData, mode, spanFilters); + const hits = parseHits(props.payloadData); if (hits.length === 0) { return; @@ -186,11 +150,9 @@ export function SpanDetailPanel(props: { console.error('Error in hitsToSpanDetailData:', error); }) .finally(() => { - if (props.setGanttChartLoading) { - props.setGanttChartLoading(false); - } + props.setGanttChartLoading?.(false); }); - }, [props.payloadData, props.colorMap, mode, spanFilters]); + }, [props.payloadData, props.colorMap, mode, props.spanFilters]); const getSpanDetailLayout = ( plotTraces: Plotly.Data[], @@ -317,7 +279,7 @@ export function SpanDetailPanel(props: { ); const renderFilters = useMemo(() => { - return spanFilters.map(({ field, value }) => ( + return props.spanFilters.map(({ field, value }) => ( )); - }, [spanFilters]); + }, [props.spanFilters]); const onHover = useCallback(() => { const dragLayer = document.getElementsByClassName('nsewdrag')?.[0]; @@ -387,11 +349,11 @@ export function SpanDetailPanel(props: { dataSourceMDSId={props.dataSourceMDSId} availableWidth={dynamicLayoutAdjustment} payloadData={props.payloadData} - filters={spanFilters} + filters={props.spanFilters} /> ), - [setCurrentSpan, dynamicLayoutAdjustment, props.payloadData, spanFilters] + [setCurrentSpan, dynamicLayoutAdjustment, props.payloadData, props.spanFilters] ); const spanDetailTableHierarchy = useMemo( @@ -411,11 +373,11 @@ export function SpanDetailPanel(props: { dataSourceMDSId={props.dataSourceMDSId} availableWidth={dynamicLayoutAdjustment} payloadData={props.payloadData} - filters={spanFilters} + filters={props.spanFilters} /> ), - [setCurrentSpan, dynamicLayoutAdjustment, props.payloadData, spanFilters] + [setCurrentSpan, dynamicLayoutAdjustment, props.payloadData, props.spanFilters] ); const ganttChart = useMemo( @@ -491,7 +453,7 @@ export function SpanDetailPanel(props: { ) : ( <> - {spanFilters.length > 0 && ( + {props.spanFilters.length > 0 && ( diff --git a/public/components/trace_analytics/components/traces/span_detail_table.tsx b/public/components/trace_analytics/components/traces/span_detail_table.tsx index 83c36b5ef..f194230cc 100644 --- a/public/components/trace_analytics/components/traces/span_detail_table.tsx +++ b/public/components/trace_analytics/components/traces/span_detail_table.tsx @@ -11,9 +11,9 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { HttpSetup } from '../../../../../../../src/core/public'; import { TRACE_ANALYTICS_DATE_FORMAT } from '../../../../../common/constants/trace_analytics'; import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; +import { handleSpansRequest } from '../../requests/traces_request_handler'; import { microToMilliSec, nanoToMilliSec, parseHits } from '../common/helper_functions'; import { RenderCustomDataGrid } from '../common/shared_components/custom_datagrid'; -import { handleSpansRequest } from '../../requests/traces_request_handler'; interface SpanDetailTableProps { http: HttpSetup; @@ -25,7 +25,7 @@ interface SpanDetailTableProps { dataSourceMDSId: string; availableWidth?: number; payloadData: string; - filters: Array<{ field: string; value: any }>; + filters: TraceFilter[]; } interface Span { diff --git a/public/components/trace_analytics/components/traces/trace_view.tsx b/public/components/trace_analytics/components/traces/trace_view.tsx index 0a36f8e86..c8034646b 100644 --- a/public/components/trace_analytics/components/traces/trace_view.tsx +++ b/public/components/trace_analytics/components/traces/trace_view.tsx @@ -19,9 +19,9 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import _ from 'lodash'; -import React, { useEffect, useState } from 'react'; import { i18n } from '@osd/i18n'; +import round from 'lodash/round'; +import React, { useEffect, useState } from 'react'; import { MountPoint } from '../../../../../../../src/core/public'; import { DataSourceManagementPluginSetup } from '../../../../../../../src/plugins/data_source_management/public'; import { DataSourceOption } from '../../../../../../../src/plugins/data_source_management/public/components/data_source_menu/types'; @@ -31,11 +31,12 @@ import { coreRefs } from '../../../../framework/core_refs'; import { TraceAnalyticsCoreDeps } from '../../home'; import { handleServiceMapRequest } from '../../requests/services_request_handler'; import { handlePayloadRequest } from '../../requests/traces_request_handler'; +import { TraceFilter } from '../common/constants'; import { PanelTitle, filtersToDsl, processTimeStamp } from '../common/helper_functions'; import { ServiceMap, ServiceObject } from '../common/plots/service_map'; import { ServiceBreakdownPanel } from './service_breakdown_panel'; import { SpanDetailPanel } from './span_detail_panel'; -import { getOverviewFields, getServiceBreakdownData } from './trace_view_helpers'; +import { getOverviewFields, getServiceBreakdownData, spanFiltersToDSL } from './trace_view_helpers'; const newNavigation = coreRefs.chrome?.navGroup.getNavGroupEnabled(); @@ -85,6 +86,12 @@ export function TraceView(props: TraceViewProps) { const [isServicesPieChartLoading, setIsServicesPieChartLoading] = useState(false); const [isGanttChartLoading, setIsGanttChartLoading] = useState(false); + const storedFilters = sessionStorage.getItem('TraceAnalyticsSpanFilters'); + const [spanFilters, setSpanFilters] = useState(() => + storedFilters ? JSON.parse(storedFilters) : [] + ); + const [filteredPayload, setFilteredPayload] = useState(''); + const renderOverview = (overviewFields: any) => { return ( @@ -224,10 +231,36 @@ export function TraceView(props: TraceViewProps) { ).finally(() => setIsServicesDataLoading(false)); }; + const setSpanFiltersWithStorage = (newFilters: TraceFilter[]) => { + refreshFilteredPayload(newFilters); + setSpanFilters(newFilters); + sessionStorage.setItem('TraceAnalyticsSpanFilters', JSON.stringify(newFilters)); + }; + + const refreshFilteredPayload = async (newFilters: TraceFilter[]) => { + const spanDSL = spanFiltersToDSL(newFilters); + setIsGanttChartLoading(true); + handlePayloadRequest( + props.traceId, + props.http, + spanDSL, + setFilteredPayload, + mode, + props.dataSourceMDSId[0].id + ); + }; + useEffect(() => { if (!payloadData) return; try { + if (spanFilters.length > 0) { + refreshFilteredPayload(spanFilters); + } else { + setFilteredPayload(payloadData); + } + + setFilteredPayload(payloadData); const parsedPayload = JSON.parse(payloadData); const overview = getOverviewFields(parsedPayload, mode); if (overview) { @@ -267,8 +300,8 @@ export function TraceView(props: TraceViewProps) { Object.entries(services).forEach(([serviceName, service]: [string, any]) => { if (!serviceMap[serviceName]) return; filteredServiceMap[serviceName] = serviceMap[serviceName]; - filteredServiceMap[serviceName].latency = _.round(service.latency / service.throughput, 2); - filteredServiceMap[serviceName].error_rate = _.round( + filteredServiceMap[serviceName].latency = round(service.latency / service.throughput, 2); + filteredServiceMap[serviceName].error_rate = round( (service.errors / service.throughput) * 100, 2 ); @@ -330,9 +363,11 @@ export function TraceView(props: TraceViewProps) { setGanttData={setGanttData} dataSourceMDSId={props.dataSourceMDSId[0].id} dataSourceMDSLabel={props.dataSourceMDSId[0].label} - payloadData={payloadData} + payloadData={filteredPayload} isGanttChartLoading={isGanttChartLoading} setGanttChartLoading={setIsGanttChartLoading} + spanFilters={spanFilters} + setSpanFiltersWithStorage={setSpanFiltersWithStorage} /> diff --git a/public/components/trace_analytics/components/traces/trace_view_helpers.tsx b/public/components/trace_analytics/components/traces/trace_view_helpers.tsx index 32b5b12f3..975f5f9e7 100644 --- a/public/components/trace_analytics/components/traces/trace_view_helpers.tsx +++ b/public/components/trace_analytics/components/traces/trace_view_helpers.tsx @@ -4,7 +4,7 @@ */ import moment from 'moment'; -import { MILI_TO_SEC, NANOS_TO_MS, pieChartColors } from '../common/constants'; +import { MILI_TO_SEC, NANOS_TO_MS, pieChartColors, TraceFilter } from '../common/constants'; export function getOverviewFields(parsed: any, mode: string) { if (parsed.length === 0) return null; @@ -121,3 +121,26 @@ export function getServiceBreakdownData(parsed: any, mode: string) { return { serviceBreakdownData, colorMap }; } + +export const spanFiltersToDSL = (spanFilters: TraceFilter[]) => { + const spanDSL: any = { + query: { + bool: { + must: [], + filter: [], + should: [], + must_not: [], + }, + }, + }; + spanFilters.map(({ field, value }) => { + if (value != null) { + spanDSL.query.bool.must.push({ + term: { + [field]: value, + }, + }); + } + }); + return spanDSL; +}; diff --git a/public/components/trace_analytics/requests/queries/traces_queries.ts b/public/components/trace_analytics/requests/queries/traces_queries.ts index 503378c1b..03e273655 100644 --- a/public/components/trace_analytics/requests/queries/traces_queries.ts +++ b/public/components/trace_analytics/requests/queries/traces_queries.ts @@ -194,7 +194,7 @@ export const getTracesQuery = ( return mode === 'jaeger' ? jaegerQuery : dataPrepperQuery; }; -export const getPayloadQuery = (mode: TraceAnalyticsMode, traceId: string, size = 1000) => { +export const getPayloadQuery = (mode: TraceAnalyticsMode, traceId: string, size = 3000) => { if (mode === 'jaeger') { return { size, diff --git a/public/components/trace_analytics/requests/traces_request_handler.ts b/public/components/trace_analytics/requests/traces_request_handler.ts index 27575fe1d..e9b28c2d0 100644 --- a/public/components/trace_analytics/requests/traces_request_handler.ts +++ b/public/components/trace_analytics/requests/traces_request_handler.ts @@ -15,6 +15,7 @@ import { BarOrientation } from '../../../../common/constants/shared'; import { TRACE_ANALYTICS_DATE_FORMAT } from '../../../../common/constants/trace_analytics'; import { TraceAnalyticsMode, TraceQueryMode } from '../../../../common/types/trace_analytics'; import { coreRefs } from '../../../../public/framework/core_refs'; +import { MILI_TO_SEC } from '../components/common/constants'; import { getTimestampPrecision, microToMilliSec, @@ -31,7 +32,6 @@ import { getTracesQuery, } from './queries/traces_queries'; import { handleDslRequest } from './request_handler'; -import { MILI_TO_SEC } from '../components/common/constants'; export const handleCustomIndicesTracesRequest = async ( http: HttpSetup, @@ -351,12 +351,12 @@ export function normalizePayload(parsed: ParsedResponse): Hit[] { export const handlePayloadRequest = ( traceId: string, http: HttpSetup, - payloadData: any, + spanDSL: any, setPayloadData: (payloadData: any) => void, mode: TraceAnalyticsMode, dataSourceMDSId?: string ) => { - return handleDslRequest(http, null, getPayloadQuery(mode, traceId), mode, dataSourceMDSId) + return handleDslRequest(http, spanDSL, getPayloadQuery(mode, traceId), mode, dataSourceMDSId) .then((response) => { const normalizedData = normalizePayload(response); const sortedData = normalizedData