Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TraceView - Optimization of queries #2349

Merged
merged 19 commits into from
Feb 12, 2025
Merged

Conversation

TackAdam
Copy link
Collaborator

@TackAdam TackAdam commented Feb 6, 2025

Description

  1. Remove the query request for handleTraceViewRequest(), handleServicesPieChartRequest(), handleSpansGanttRequest() by utilizing the primary payload and parsing it on the UI level. This reduces the number of queries when moving from Trace content to a specific trace went from 4 to 1 (Service map still generating 4 calls pending optimization).
  • Testing on a OpenSearch cluster with 200gb of spans indicated that the overview query call was causing a 2x delay to retrieve the metrics already available in payload. Average time reduced by 50% for trace view page for non-cached data.
  1. Remove handleSpansRequest() from Span List and Tree view to prevent unnecessary queries when switching between Timeline and table views.
  • Span_detail_table will maintain a handleSpansRequest() to cover application analytics pending its depreciation as it does not have a payload request to use the new functionality.
  1. Fixed a bug where Jaeger overview was not being populated with information through edge case handiling in the helper function getOverviewFields()

  2. Fixed Jaeger Tree Span table not working

  3. Add sorting to Jaeger Span Table.

  4. Fix gantt chart displaying errors. The time text we added to the right was overriding the error field. We now check if it has an error and display that as priority over the time.

Queries Before 8 request + 1 per table change

QueriesBefore.mov

Queries after 5 request + 0 per table change

QueriesAfter.mov

Jaeger Before:
JaegerBefore

Jaeger After:
Screenshot 2025-02-10 at 11 56 29 AM

Gantt chart error display fix:

hasErrorFix

Issues Resolved

#2334 - Trace View section

Check List

  • New functionality includes testing.
    • All tests pass, including unit test, integration test and doctest
  • New functionality has been documented.
    • New functionality has javadoc added
    • New functionality has user manual doc added
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@TackAdam TackAdam added backport 2.x bug Something isn't working traces traces telemetry related features maintenance labels Feb 6, 2025
Copy link

codecov bot commented Feb 6, 2025

Codecov Report

Attention: Patch coverage is 36.50190% with 167 lines in your changes missing coverage. Please review.

Project coverage is 56.13%. Comparing base (5cfbba1) to head (e102bd1).
Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
..._analytics/components/traces/span_detail_table.tsx 19.75% 65 Missing ⚠️
..._analytics/components/traces/span_detail_panel.tsx 14.28% 36 Missing ⚠️
...e_analytics/components/common/helper_functions.tsx 5.26% 18 Missing ⚠️
...s/trace_analytics/components/traces/trace_view.tsx 15.00% 17 Missing ⚠️
...analytics/components/traces/trace_view_helpers.tsx 78.26% 13 Missing and 2 partials ⚠️
...mponents/flyout_components/trace_detail_render.tsx 18.18% 9 Missing ⚠️
...trace_analytics/requests/traces_request_handler.ts 50.00% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2349      +/-   ##
==========================================
- Coverage   56.53%   56.13%   -0.40%     
==========================================
  Files         393      395       +2     
  Lines       15574    15807     +233     
  Branches     4284     4390     +106     
==========================================
+ Hits         8804     8873      +69     
- Misses       6705     6863     +158     
- Partials       65       71       +6     
Flag Coverage Δ
dashboards-observability 56.13% <36.50%> (-0.40%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Signed-off-by: Adam Tackett <[email protected]>
@TackAdam TackAdam marked this pull request as ready for review February 7, 2025 04:55
Signed-off-by: Adam Tackett <[email protected]>
Comment on lines 8 to 16
export function normalizePayload(parsed: any): any[] {
if (Array.isArray(parsed)) {
return parsed;
}
if (parsed.hits && Array.isArray(parsed.hits.hits)) {
return parsed.hits.hits;
}
return [];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this function to normalizePayload? Since we are using just 1 request for all trace related details shouldn't we normalize this at source where we receive the request?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to traces_request_handler
and used in handlePayloadRequest

const normalizedData = normalizePayload(response);
setPayloadData(JSON.stringify(normalizedData, null, 2));

Copy link
Member

@ps48 ps48 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, since we are moving lot of computation from OpenSearch to browser we need to manually do some sanity checks, to see if the calculation before and after is the same. May be use our sanity data and check the values in 2.18 (playground) and now with this PR in your local.

Comment on lines 312 to 317
const hitsArray =
parsed.hits && Array.isArray(parsed.hits.hits)
? parsed.hits.hits
: Array.isArray(parsed)
? parsed
: [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason that this is different from

const parsed = JSON.parse(payloadData);
let hits: any[] = [];
if (parsed.hits && Array.isArray(parsed.hits.hits)) {
hits = parsed.hits.hits;
} else if (Array.isArray(parsed)) {
hits = parsed;
? and maybe extract the parsing into a util function?

Comment on lines 157 to 173
if (!props.payloadData) {
console.warn('No payloadData provided to SpanDetailPanel');
return;
}

try {
const parsed = JSON.parse(props.payloadData);
let hits: any[] = [];
if (parsed.hits && Array.isArray(parsed.hits.hits)) {
hits = parsed.hits.hits;
} else if (Array.isArray(parsed)) {
hits = parsed;
} else {
console.warn('Unexpected payload format:', parsed);
return;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, we'd want to keep useEffect lean and put the logic inside a function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved logic to helper above it

const parseAndFilterHits = (payloadData: string, mode: string, spanFilters: any[]) => {
  try {
    const parsed = JSON.parse(payloadData);
    let hits: any[] = [];
    if (parsed.hits && Array.isArray(parsed.hits.hits)) {
      hits = parsed.hits.hits;
    } else if (Array.isArray(parsed)) {
      hits = parsed;
    } else {
      console.warn('Unexpected payload format:', parsed);
      return [];
    }

    hits = hits.map((hit) => {
      if (mode === 'jaeger') {
        if (!hit.sort || !hit.sort[0]) {
          return {
            ...hit,
            sort: [Number(hit._source.startTime) * 1000], // Jaeger: startTime is in microseconds
          };
        }
      } else {
        if (!hit.sort || !hit.sort[0]) {
          return {
            ...hit,
            sort: [parseIsoToNano(hit._source.startTime)],
          };
        }
      }
      return hit;
    });

    hits.sort((a, b) => b.sort[0] - a.sort[0]);

    if (spanFilters.length > 0) {
      hits = hits.filter((hit) => {
        return spanFilters.every(({ field, value }) => {
          let fieldVal;
          if (mode === 'jaeger' && field.startsWith('process.')) {
            fieldVal = hit._source?.process?.[field.split('.')[1]];
          } else {
            fieldVal = hit._source?.[field];
          }
          return fieldVal === value;
        });
      });
    }

    hits = hits.filter((hit) => {
      if (mode === '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 [];
  }
};

useEffect(() => {
  if (!props.payloadData) {
    console.warn('No payloadData provided to SpanDetailPanel');
    return;
  }

  const hits = parseAndFilterHits(props.payloadData, mode, spanFilters);

  if (hits.length === 0) {
    return;
  }

  hitsToSpanDetailData(hits, props.colorMap, mode)
    .then((transformedData) => {
      setData(transformedData);
    })
    .catch((error) => {
      console.error('Error in hitsToSpanDetailData:', error);
    })
    .finally(() => {
      if (props.setGanttChartLoading) {
        props.setGanttChartLoading(false);
      }
    });
}, [props.payloadData, props.colorMap, mode, spanFilters]);


hits = hits.map((hit) => {
if (mode === 'jaeger') {
if (!hit.sort || !hit.sort[0]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing this check? hits won't have sort field by default?

Signed-off-by: Adam Tackett <[email protected]>
@TackAdam
Copy link
Collaborator Author

TackAdam commented Feb 7, 2025

In general, since we are moving lot of computation from OpenSearch to browser we need to manually do some sanity checks, to see if the calculation before and after is the same. May be use our sanity data and check the values in 2.18 (playground) and now with this PR in your local.

Comparing results

NewCode Playground 2.18
Before Image 1 After Image 1
Before Image 2 After Image 2
Before Image 3 After Image 3
Before Image 4 After Image 4
Before Image 5 After Image 5

Comment on lines 21 to 22
const firstSpan = hits[0]._source;
if (!firstSpan) return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a small check here, that first span doesn't have a parentTraceId? To account for cases we the OTel data might miss parent traces.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

    let computedLatency: number | null = null;
    const tgDuration = get(firstSpan, 'traceGroupFields.durationInNanos');
    if (firstSpan.traceGroupFields && firstSpan.traceGroupFields.durationInNanos != null) {
      computedLatency = Number(firstSpan.traceGroupFields.durationInNanos) / NANOS_TO_MS;
    } else if (tgDuration != null) {
      computedLatency = Number(tgDuration) / NANOS_TO_MS;
    } else {
      const startTimes = parsed.map((span: any) => moment(span._source.startTime));
      const endTimes = parsed.map((span: any) => moment(span._source.endTime));
      
      const minStartTime = moment.min(startTimes);
      const maxEndTime = moment.max(endTimes);
      
      if (minStartTime && maxEndTime) {
        computedLatency = maxEndTime.diff(minStartTime, 'milliseconds', true);
        fallbackValueUsed = true;
      }
    }

So if the first span is not present we get a more accurate latency instead of 0 or a smaller number

Before - invalid date:
Before

After - invalid date:
After

Latency is now much more accurate when handling missing data

[field]: value,
},
} else {
if (!hit.sort || !hit.sort[0]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. since this if is the same for both modes, it might be better to check traceMode inside this if

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

      hits = hits.map((hit) => {
        if (!hit.sort || !hit.sort[0]) {
          const time = traceMode === 'jaeger' 
            ? Number(hit._source.startTime) * MILI_TO_SEC
            : parseIsoToNano(hit._source.startTime);
          
          return {
            ...hit,
            sort: [time],
          };
        }
        return hit;
      });

return hit;
});

hits.sort((a, b) => b.sort[0] - a.sort[0]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we use hit.sort anywhere else? if not then probably just do hits.sort((a, b) => b._source.startTime - a._source.startTime); here and remove the inserting sort logic above

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the inserting sort logic is needed for handling jaeger vrs data prepper. Using just the hits.sort you show fails to render the gantt visualization.

Comment on lines 179 to 185
let fieldVal;
if (traceMode === 'jaeger' && field.startsWith('process.')) {
fieldVal = hit._source?.process?.[field.split('.')[1]];
} else {
fieldVal = hit._source?.[field];
}
return fieldVal === value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lodash can handle the nested and flattened fields when reading value

Suggested change
let fieldVal;
if (traceMode === 'jaeger' && field.startsWith('process.')) {
fieldVal = hit._source?.process?.[field.split('.')[1]];
} else {
fieldVal = hit._source?.[field];
}
return fieldVal === value;
return _.get(hit._source, field) === value;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

      if (payloadSpanFilters.length > 0) {
        hits = hits.filter((hit) => {
          return payloadSpanFilters.every(({ field, value }) => {
            return _.get(hit._source, field) === value;
          });
        });
      }

Comment on lines 312 to 317
const hitsArray =
parsed.hits && Array.isArray(parsed.hits.hits)
? parsed.hits.hits
: Array.isArray(parsed)
? parsed
: [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason that this is different from

const parsed = JSON.parse(payloadData);
let hits: any[] = [];
if (parsed.hits && Array.isArray(parsed.hits.hits)) {
hits = parsed.hits.hits;
} else if (Array.isArray(parsed)) {
hits = parsed;
? and maybe extract the parsing into a util function?

if (props.filters.length > 0) {
spans = spans.filter((span: any) => {
return props.filters.every(
({ field, value }: { field: string; value: any }) => span[field] === value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will there be negate filters like field a is not b?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not currently supported the flyout only allows filters to be added as such:
Screenshot 2025-02-10 at 4 19 45 PM
Where the user can only clear. Intended use is for investigating based on the flyout
Screenshot 2025-02-10 at 4 20 37 PM

rootSpans.push(spanMap[span[spanIdKey]]);
alreadyAddedRootSpans.add(span[spanIdKey]);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might need some unit tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added cypress test for jaeger tree view functionality

 it('Checks tree view for specific traceId in Jaeger mode', () => {
   cy.contains('15b0b4004a651c4c').click();
   cy.get('[data-test-subj="globalLoadingIndicator"]').should('not.exist');

   cy.get('.euiButtonGroup').contains('Tree view').click();
   cy.get("[data-test-subj='treeExpandAll']").should('exist');
   cy.get("[data-test-subj='treeCollapseAll']").should('exist');

   // Waiting time for render to complete
   cy.get("[data-test-subj='treeExpandAll']").click();
   cy.get("[data-test-subj='treeCollapseAll']").click();
   
   cy.get("[data-test-subj='treeViewExpandArrow']").should('have.length', 1);
   cy.get("[data-test-subj='treeExpandAll']").click();
   cy.get("[data-test-subj='treeViewExpandArrow']").should('have.length', 6);
 });

Adam Tackett added 2 commits February 10, 2025 16:26
Signed-off-by: Adam Tackett <[email protected]>
Adam Tackett added 3 commits February 10, 2025 16:54
Signed-off-by: Adam Tackett <[email protected]>
Signed-off-by: Adam Tackett <[email protected]>
Signed-off-by: Adam Tackett <[email protected]>
try {
const parsedPayload = JSON.parse(payloadData);
const overview = getOverviewFields(parsedPayload, mode);
if (overview) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Do we have a reset mechanism for these setters? If we previously loaded data with an overview, but a new payload arrives without an overview for some reason, it seems like the overview wouldn't be updated here, potentially leaving it with stale data. Should we handle this case explicitly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That one specifically is for application analytics and the useEffect triggers on payload change. As long as it is a valid payload the overview will get the new information from it. If the payload is successful from handlePayloadRequest causing new data the overview will always be generated from that information.

@@ -0,0 +1,22 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. Should we consider moving this common folder one level up? It seems like each logical component should have its own common folder.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having it on the same level as our helper_functions makes sense as this would be for constants shared between trace_analytics including both traces and services as we continue to refactor.

export const parseHits = (payloadData: string) => {
try {
const parsed = JSON.parse(payloadData);
let hits: any[] = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

interface Span {
  traceId: string;
  spanId: string;
  traceState: string;
  parentSpanId: string;
  name: string;
  kind: string;
  startTime: string;
  endTime: string;
  durationInNanos: number;
  serviceName: string;
  events: any[];
  links: any[];
  droppedAttributesCount: number;
  droppedEventsCount: number;
  droppedLinksCount: number;
  traceGroup: string;
  traceGroupFields: {
    endTime: string;
    durationInNanos: number;
    statusCode: number;
  };
  status: {
    code: number;
  };
  instrumentationLibrary: {
    name: string;
    version: string;
  };
}

interface ParsedHit {
  _index: string;
  _id: string;
  _score: number;
  _source: Span;
  sort?: any[];
}

export const parseHits = (payloadData: string): ParsedHit[] => {
  try {
    const parsed = JSON.parse(payloadData);
    let hits: ParsedHit[] = [];

    if (parsed.hits && Array.isArray(parsed.hits.hits)) {
      hits = parsed.hits.hits;
    } else if (Array.isArray(parsed)) {
      hits = parsed;
    }

    return hits;
  } catch (error) {
    console.error('Error processing payloadData:', error);
    return [];
  }
};

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
import _ from 'lodash';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we are avoiding using lodash?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, replaced with

      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;
          });
        });
      }

}
if (ref.refType === 'FOLLOWS_FROM') {
if (!alreadyAddedRootSpans.has(span[spanIdKey])) {
rootSpans.push(spanMap[span[spanIdKey]]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be able to refactor these a little bit for example like extracting these duplicated root span handing to a function:

rootSpans.push(spanMap[span[spanIdKey]]);
alreadyAddedRootSpans.add(span[spanIdKey]);

and also for these nested if conditions, I don't see you are having any else if or else then why not to merge them like

if (ref.refType === 'FOLLOWS_FROM') {
            if (!alreadyAddedRootSpans.has(span[spanIdKey])) {
...

to

if (ref.refType === 'FOLLOWS_FROM' && !alreadyAddedRootSpans.has(span[spanIdKey])) {
...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

interface SpanReference {
    refType: 'CHILD_OF' | 'FOLLOWS_FROM';
    spanID: string;
  }
  
  const addRootSpan = (
    spanId: string,
    spanMap: SpanMap,
    rootSpans: Span[],
    alreadyAddedRootSpans: Set<string>
  ) => {
    if (!alreadyAddedRootSpans.has(spanId)) {
      rootSpans.push(spanMap[spanId]);
      alreadyAddedRootSpans.add(spanId);
    }
  };
  
  const buildHierarchy = (spans: Span[]): Span[] => {
    const spanMap: SpanMap = {};
  
    spans.forEach((span) => {
      const spanIdKey = props.mode === 'jaeger' ? 'spanID' : 'spanId';
      spanMap[span[spanIdKey]] = { ...span, children: [] };
    });
  
    const rootSpans: Span[] = [];
    const alreadyAddedRootSpans: Set<string> = new Set(); // Track added root spans
  
    spans.forEach((span) => {
      const spanIdKey = props.mode === 'jaeger' ? 'spanID' : 'spanId';
      const references: SpanReference[] = span.references || [];
  
      if (props.mode === 'jaeger') {
        references.forEach((ref: SpanReference) => {
          if (ref.refType === 'CHILD_OF') {
            const parentSpan = spanMap[ref.spanID];
            if (parentSpan) {
              parentSpan.children.push(spanMap[span[spanIdKey]]);
            }
          }
  
          if (ref.refType === 'FOLLOWS_FROM' && !alreadyAddedRootSpans.has(span[spanIdKey])) {
            addRootSpan(span[spanIdKey], spanMap, rootSpans, alreadyAddedRootSpans);
          }
        });
  
        if (references.length === 0 || references.every((ref) => ref.refType === 'FOLLOWS_FROM')) {
          addRootSpan(span[spanIdKey], spanMap, rootSpans, alreadyAddedRootSpans);
        }
      } else {
        // Data Prepper handling
        if (span.parentSpanId && spanMap[span.parentSpanId]) {
          spanMap[span.parentSpanId].children.push(spanMap[span[spanIdKey]]);
        } else {
          addRootSpan(span[spanIdKey], spanMap, rootSpans, alreadyAddedRootSpans);
        }
      }
    });
  
    return rootSpans;
  };

size="m"
type="alert"
color="warning"
content="Latency may not be accurate due to missing or unexpected duration data."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

                        <EuiIconTip
                          aria-label={i18n.translate('tracesOverview.iconTip.ariaLabel', {
                            defaultMessage: 'Warning',
                          })}
                          size="m"
                          type="alert"
                          color="warning"
                          content={i18n.translate('tracesOverview.iconTip.content', {
                            defaultMessage: 'Latency may not be accurate due to missing or unexpected duration data.',
                          })}
                        />

@@ -472,6 +324,16 @@ const hitsToSpanDetailData = async (hits: any, colorMap: any, mode: TraceAnalyti
return data;
};

export function normalizePayload(parsed: any): any[] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

interface Hit {
  _index: string;
  _id: string;
  _score: number;
  _source: any;
  sort?: any[];
}

interface ParsedResponse {
  hits?: {
    hits: Hit[];
  };
  [key: string]: any;
}

export function normalizePayload(parsed: ParsedResponse): Hit[] {
  if (Array.isArray(parsed)) {
    return parsed;
  }
  if (parsed.hits && Array.isArray(parsed.hits.hits)) {
    return parsed.hits.hits;
  }
  return [];
}

@@ -370,7 +222,7 @@ export const handleSpansFlyoutRequest = (
});
};

const hitsToSpanDetailData = async (hits: any, colorMap: any, mode: TraceAnalyticsMode) => {
export const hitsToSpanDetailData = async (hits: any, colorMap: any, mode: TraceAnalyticsMode) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type?

* SPDX-License-Identifier: Apache-2.0
*/

import { get } from 'lodash';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid using lodash?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed and refactored to work without it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a replacement for lodash? some of them like get are useful and not trivial in plain js
https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore?tab=readme-ov-file#_get

Adam Tackett added 2 commits February 11, 2025 11:52
Signed-off-by: Adam Tackett <[email protected]>
Signed-off-by: Adam Tackett <[email protected]>
Comment on lines 342 to 350
export function normalizePayload(parsed: ParsedResponse): Hit[] {
if (Array.isArray(parsed)) {
return parsed;
}
if (parsed.hits && Array.isArray(parsed.hits.hits)) {
return parsed.hits.hits;
}
return [];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we still need this? If we know one check is for Jaeger and other is data prepper can we just use mode based if conditions instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated too

export function normalizePayload(parsed: ParsedResponse): Hit[] {
  if (parsed.hits && Array.isArray(parsed.hits.hits)) {
    return parsed.hits.hits;
  }
  return [];
}

Jaeger and data prepper should be under parsed.hits

Comment on lines 637 to 668
interface Span {
traceId: string;
spanId: string;
traceState: string;
parentSpanId: string;
name: string;
kind: string;
startTime: string;
endTime: string;
durationInNanos: number;
serviceName: string;
events: any[];
links: any[];
droppedAttributesCount: number;
droppedEventsCount: number;
droppedLinksCount: number;
traceGroup: string;
traceGroupFields: {
endTime: string;
durationInNanos: number;
statusCode: number;
};
status: {
code: number;
};
instrumentationLibrary: {
name: string;
version: string;
};
}

interface ParsedHit {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please move these to common constants?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to constants

@ps48 ps48 merged commit 36311f5 into opensearch-project:main Feb 12, 2025
19 checks passed
opensearch-trigger-bot bot pushed a commit that referenced this pull request Feb 12, 2025
* optimize trace veiw removing queries

Signed-off-by: Adam Tackett <[email protected]>

* fix date format

Signed-off-by: Adam Tackett <[email protected]>

* update app cypress for rounding

Signed-off-by: Adam Tackett <[email protected]>

* address comments

Signed-off-by: Adam Tackett <[email protected]>

* fix jaeger tree span and overview

Signed-off-by: Adam Tackett <[email protected]>

* match old behavior for jaeger, if an error is present display it in overview

Signed-off-by: Adam Tackett <[email protected]>

* update test to reflect error in jaeger trace

Signed-off-by: Adam Tackett <[email protected]>

* address comments

Signed-off-by: Adam Tackett <[email protected]>

* add cypress test for jaeger tree view

Signed-off-by: Adam Tackett <[email protected]>

* add fallback value for latency

Signed-off-by: Adam Tackett <[email protected]>

* fix sorting on span table

Signed-off-by: Adam Tackett <[email protected]>

* add back total use effect

Signed-off-by: Adam Tackett <[email protected]>

* status code of 2 indicates error

Signed-off-by: Adam Tackett <[email protected]>

* address comments

Signed-off-by: Adam Tackett <[email protected]>

* add sorting support to jaeger span table

Signed-off-by: Adam Tackett <[email protected]>

* address comments, refactor sorting

Signed-off-by: Adam Tackett <[email protected]>

* fix flaky cypress test

Signed-off-by: Adam Tackett <[email protected]>

* combine adding sort, and sorting action

Signed-off-by: Adam Tackett <[email protected]>

* fix jaeger sorting for errors, fix gantt chart error override from text

Signed-off-by: Adam Tackett <[email protected]>

---------

Signed-off-by: Adam Tackett <[email protected]>
Co-authored-by: Adam Tackett <[email protected]>
(cherry picked from commit 36311f5)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
TackAdam pushed a commit that referenced this pull request Feb 13, 2025
* TraceView - Optimization of queries  (#2349)

* optimize trace veiw removing queries

Signed-off-by: Adam Tackett <[email protected]>

* fix date format

Signed-off-by: Adam Tackett <[email protected]>

* update app cypress for rounding

Signed-off-by: Adam Tackett <[email protected]>

* address comments

Signed-off-by: Adam Tackett <[email protected]>

* fix jaeger tree span and overview

Signed-off-by: Adam Tackett <[email protected]>

* match old behavior for jaeger, if an error is present display it in overview

Signed-off-by: Adam Tackett <[email protected]>

* update test to reflect error in jaeger trace

Signed-off-by: Adam Tackett <[email protected]>

* address comments

Signed-off-by: Adam Tackett <[email protected]>

* add cypress test for jaeger tree view

Signed-off-by: Adam Tackett <[email protected]>

* add fallback value for latency

Signed-off-by: Adam Tackett <[email protected]>

* fix sorting on span table

Signed-off-by: Adam Tackett <[email protected]>

* add back total use effect

Signed-off-by: Adam Tackett <[email protected]>

* status code of 2 indicates error

Signed-off-by: Adam Tackett <[email protected]>

* address comments

Signed-off-by: Adam Tackett <[email protected]>

* add sorting support to jaeger span table

Signed-off-by: Adam Tackett <[email protected]>

* address comments, refactor sorting

Signed-off-by: Adam Tackett <[email protected]>

* fix flaky cypress test

Signed-off-by: Adam Tackett <[email protected]>

* combine adding sort, and sorting action

Signed-off-by: Adam Tackett <[email protected]>

* fix jaeger sorting for errors, fix gantt chart error override from text

Signed-off-by: Adam Tackett <[email protected]>

---------

Signed-off-by: Adam Tackett <[email protected]>
Co-authored-by: Adam Tackett <[email protected]>
(cherry picked from commit 36311f5)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* update snapshots

Signed-off-by: Adam Tackett <[email protected]>

---------

Signed-off-by: Adam Tackett <[email protected]>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Adam Tackett <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport 2.x bug Something isn't working maintenance traces traces telemetry related features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants