Browse Source

feat(trace view): Add performance issues to trace navigator (#45859)

- This updates the trace navigator so it also shows performance issues
that way they can be linked back to their transactions and vice versa
- Closes #45393
William Mak 2 years ago
parent
commit
9c00919fa0

+ 1 - 0
static/app/components/quickTrace/index.spec.tsx

@@ -34,6 +34,7 @@ describe('Quick Trace', function () {
             : parentId === null
             ? `s${generation - 1}${parentId}`
             : `s${parentId}`,
+        performance_issues: [],
       });
     }
     return events;

+ 29 - 9
static/app/components/quickTrace/index.tsx

@@ -26,6 +26,7 @@ import {
   QuickTrace as QuickTraceType,
   QuickTraceEvent,
   TraceError,
+  TracePerformanceIssue,
 } from 'sentry/utils/performance/quickTrace/types';
 import {parseQuickTrace} from 'sentry/utils/performance/quickTrace/utils';
 import Projects from 'sentry/utils/projects';
@@ -288,10 +289,13 @@ function EventNodeSelector({
   numEvents = 5,
 }: EventNodeSelectorProps) {
   let errors: TraceError[] = events.flatMap(event => event.errors ?? []);
+  let perfIssues: TracePerformanceIssue[] = events.flatMap(
+    event => event.performance_issues ?? []
+  );
 
   let type: keyof Theme['tag'] = nodeKey === 'current' ? 'black' : 'white';
 
-  const hasErrors = errors.length > 0;
+  const hasErrors = errors.length > 0 || perfIssues.length > 0;
 
   if (hasErrors) {
     type = nodeKey === 'current' ? 'error' : 'warning';
@@ -303,29 +307,45 @@ function EventNodeSelector({
     );
   }
 
+  const isError = currentEvent.hasOwnProperty('groupID') && currentEvent.groupID !== null;
   // make sure to exclude the current event from the dropdown
-  events = events.filter(event => event.event_id !== currentEvent.id);
+  events = events.filter(
+    event =>
+      event.event_id !== currentEvent.id ||
+      // if the current event is a perf issue, we don't want to filter out the matching txn
+      (event.event_id === currentEvent.id && isError)
+  );
   errors = errors.filter(error => error.event_id !== currentEvent.id);
+  perfIssues = perfIssues.filter(
+    issue =>
+      issue.event_id !== currentEvent.id ||
+      // if the current event is a txn, we don't want to filter out the matching perf issue
+      (issue.event_id === currentEvent.id && !isError)
+  );
+
+  const totalErrors = errors.length + perfIssues.length;
 
-  if (events.length + errors.length === 0) {
+  if (events.length + totalErrors === 0) {
     return (
       <EventNode type={type} data-test-id="event-node">
         {text}
       </EventNode>
     );
   }
-  if (events.length + errors.length === 1) {
+  if (events.length + totalErrors === 1) {
     /**
      * When there is only 1 event, clicking the node should take the user directly to
      * the event without additional steps.
      */
-    const hoverText = errors.length ? (
+    const hoverText = totalErrors ? (
       t('View the error for this Transaction')
     ) : (
       <SingleEventHoverText event={events[0]} />
     );
     const target = errors.length
       ? generateSingleErrorTarget(errors[0], organization, location, errorDest)
+      : perfIssues.length
+      ? generateSingleErrorTarget(perfIssues[0], organization, location, errorDest)
       : generateSingleTransactionTarget(
           events[0],
           organization,
@@ -362,12 +382,12 @@ function EventNodeSelector({
         title={<StyledEventNode text={text} hoverText={hoverText} type={type} />}
         anchorRight={anchor === 'right'}
       >
-        {errors.length > 0 && (
+        {totalErrors > 0 && (
           <DropdownMenuHeader first>
-            {tn('Related Error', 'Related Errors', errors.length)}
+            {tn('Related Issue', 'Related Issues', totalErrors)}
           </DropdownMenuHeader>
         )}
-        {errors.slice(0, numEvents).map(error => {
+        {[...errors, ...perfIssues].slice(0, numEvents).map(error => {
           const target = generateSingleErrorTarget(
             error,
             organization,
@@ -431,7 +451,7 @@ function EventNodeSelector({
 
 type DropdownNodeProps = {
   anchor: 'left' | 'right';
-  event: TraceError | QuickTraceEvent;
+  event: TraceError | QuickTraceEvent | TracePerformanceIssue;
   organization: OrganizationSummary;
   allowDefaultEvent?: boolean;
   onSelect?: (eventKey: any) => void;

+ 5 - 4
static/app/components/quickTrace/utils.tsx

@@ -14,6 +14,7 @@ import {
   EventLite,
   QuickTraceEvent,
   TraceError,
+  TracePerformanceIssue,
 } from 'sentry/utils/performance/quickTrace/types';
 import {getTraceTimeRangeFromEvent} from 'sentry/utils/performance/quickTrace/utils';
 import {getTransactionDetailsUrl} from 'sentry/utils/performance/urls';
@@ -21,7 +22,7 @@ import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
 
 export function isQuickTraceEvent(
-  event: QuickTraceEvent | TraceError
+  event: QuickTraceEvent | TraceError | TracePerformanceIssue
 ): event is QuickTraceEvent {
   return defined((event as QuickTraceEvent)['transaction.duration']);
 }
@@ -31,7 +32,7 @@ export type ErrorDestination = 'discover' | 'issue';
 export type TransactionDestination = 'discover' | 'performance';
 
 export function generateIssueEventTarget(
-  event: TraceError,
+  event: TraceError | TracePerformanceIssue,
   organization: OrganizationSummary
 ): LocationDescriptor {
   return `/organizations/${organization.slug}/issues/${event.issue_id}/events/${event.event_id}`;
@@ -54,7 +55,7 @@ function generatePerformanceEventTarget(
 }
 
 function generateDiscoverEventTarget(
-  event: EventLite | TraceError,
+  event: EventLite | TraceError | TracePerformanceIssue,
   organization: OrganizationSummary,
   location: Location
 ): LocationDescriptor {
@@ -78,7 +79,7 @@ function generateDiscoverEventTarget(
 }
 
 export function generateSingleErrorTarget(
-  event: TraceError,
+  event: TraceError | TracePerformanceIssue,
   organization: OrganizationSummary,
   location: Location,
   destination: ErrorDestination

+ 6 - 0
static/app/utils/performance/quickTrace/types.tsx

@@ -15,6 +15,7 @@ export type EventLite = {
   generation: number | null;
   parent_event_id: string | null;
   parent_span_id: string | null;
+  performance_issues: TracePerformanceIssue[];
   project_id: number;
   project_slug: string;
   span_id: string;
@@ -33,6 +34,11 @@ export type TraceError = {
   title: string;
 };
 
+export type TracePerformanceIssue = Omit<TraceError, 'issue' | 'span'> & {
+  span: string[];
+  suspect_spans: string[];
+};
+
 export type TraceLite = EventLite[];
 
 export type QuickTraceEvent = EventLite & {

+ 1 - 0
static/app/utils/performance/quickTrace/utils.spec.tsx

@@ -70,6 +70,7 @@ function generateTransactionLite({
     project_slug: generateProjectSlug(position),
     parent_event_id: generation <= 0 ? null : generateEventId(parentPosition),
     parent_span_id: generation <= 0 ? null : generateSpanId(parentPosition),
+    performance_issues: [],
     errors: [],
   };
 }