Browse Source

feat(perf-issues): Show Trace Navigator in Span Tree section for Performance Issues (#37428)

Ash Anand 2 years ago
parent
commit
e9c75a424e

+ 83 - 53
static/app/components/events/interfaces/spans/embeddedSpanTree.tsx

@@ -1,14 +1,16 @@
-import React from 'react';
+import React, {useContext} from 'react';
 import styled from '@emotion/styled';
 
 import ErrorBoundary from 'sentry/components/errorBoundary';
 import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
+import QuickTrace from 'sentry/components/quickTrace';
+import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Event, EventTransaction, Organization} from 'sentry/types';
 import EventView from 'sentry/utils/discover/eventView';
 import GenericDiscoverQuery from 'sentry/utils/discover/genericDiscoverQuery';
-import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
+import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
 import useApi from 'sentry/utils/useApi';
 import {useLocation} from 'sentry/utils/useLocation';
 
@@ -28,6 +30,7 @@ export function EmbeddedSpanTree(props: Props) {
   const {event, organization, projectSlug, focusedSpanIds} = props;
   const api = useApi();
   const location = useLocation();
+  const quickTrace = useContext(QuickTraceContext);
 
   const eventView = EventView.fromNewQueryWithLocation(
     {
@@ -42,62 +45,84 @@ export function EmbeddedSpanTree(props: Props) {
     location
   );
 
-  return (
-    <React.Fragment>
-      <ErrorBoundary mini>
-        <QuickTraceQuery event={event} location={location} orgSlug={organization.slug}>
-          {results => {
-            if (results.isLoading) {
-              return <LoadingIndicator />;
-            }
-
-            if (!results.currentEvent) {
-              return (
-                <LoadingError message="Error loading the span tree because the root transaction is missing." />
-              );
-            }
+  function getContent() {
+    if (!quickTrace) {
+      return null;
+    }
+
+    if (quickTrace.isLoading) {
+      return <LoadingIndicator />;
+    }
+
+    if (!quickTrace.currentEvent) {
+      return (
+        <LoadingError
+          message={t(
+            'Error loading the span tree because the root transaction is missing'
+          )}
+        />
+      );
+    }
 
+    return (
+      <GenericDiscoverQuery
+        eventView={eventView}
+        orgSlug={organization.slug}
+        route={`events/${projectSlug}:${quickTrace.currentEvent.event_id}`}
+        api={api}
+        location={location}
+      >
+        {_results => {
+          if (_results.isLoading) {
+            return <LoadingIndicator />;
+          }
+
+          if (!_results.tableData) {
             return (
-              <GenericDiscoverQuery
-                eventView={eventView}
-                orgSlug={organization.slug}
-                route={`events/${projectSlug}:${results.currentEvent?.event_id}`}
-                api={api}
-                location={location}
-              >
-                {_results => {
-                  if (_results.isLoading) {
-                    return <LoadingIndicator />;
-                  }
+              <LoadingError
+                message={t(
+                  'Error loading the span tree because the root transaction is missing'
+                )}
+              />
+            );
+          }
 
-                  if (!_results.tableData) {
-                    return (
-                      <LoadingError message="Error loading the span tree because the root transaction is missing." />
-                    );
+          return (
+            <Wrapper>
+              <Header>
+                <h3>{t('Span Tree')}</h3>
+                <QuickTrace
+                  event={event}
+                  quickTrace={quickTrace!}
+                  location={location}
+                  organization={organization}
+                  anchor="left"
+                  errorDest="issue"
+                  transactionDest="performance"
+                />
+              </Header>
+
+              <Section>
+                <TraceView
+                  organization={organization}
+                  waterfallModel={
+                    new WaterfallModel(
+                      _results.tableData as EventTransaction,
+                      focusedSpanIds
+                    )
                   }
+                />
+              </Section>
+            </Wrapper>
+          );
+        }}
+      </GenericDiscoverQuery>
+    );
+  }
 
-                  return (
-                    <Wrapper>
-                      <h3>Span Tree</h3>
-                      <Section>
-                        <TraceView
-                          organization={organization}
-                          waterfallModel={
-                            new WaterfallModel(
-                              _results.tableData as EventTransaction,
-                              focusedSpanIds
-                            )
-                          }
-                        />
-                      </Section>
-                    </Wrapper>
-                  );
-                }}
-              </GenericDiscoverQuery>
-            );
-          }}
-        </QuickTraceQuery>
-      </ErrorBoundary>
+  return (
+    <React.Fragment>
+      <ErrorBoundary mini>{getContent()}</ErrorBoundary>
     </React.Fragment>
   );
 }
@@ -128,6 +153,11 @@ export const Wrapper = styled('div')`
   }
 `;
 
+const Header = styled('div')`
+  display: flex;
+  justify-content: space-between;
+`;
+
 const Section = styled('div')`
   border: 1px solid ${p => p.theme.innerBorder};
   border-radius: ${p => p.theme.borderRadius};

+ 1 - 1
static/app/components/events/interfaces/spans/index.tsx

@@ -14,7 +14,7 @@ import {Organization} from 'sentry/types';
 import {EventTransaction} from 'sentry/types/event';
 import {objectIsEmpty} from 'sentry/utils';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
-import * as QuickTraceContext from 'sentry/utils/performance/quickTrace/quickTraceContext';
+import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
 import {TraceError} from 'sentry/utils/performance/quickTrace/types';
 import withOrganization from 'sentry/utils/withOrganization';
 

+ 4 - 2
static/app/components/events/interfaces/spans/spanBar.tsx

@@ -46,8 +46,10 @@ import {EventTransaction} from 'sentry/types/event';
 import {defined} from 'sentry/utils';
 import {trackAnalyticsEvent} from 'sentry/utils/analytics';
 import {generateEventSlug} from 'sentry/utils/discover/urls';
-import * as QuickTraceContext from 'sentry/utils/performance/quickTrace/quickTraceContext';
-import {QuickTraceContextChildrenProps} from 'sentry/utils/performance/quickTrace/quickTraceContext';
+import {
+  QuickTraceContext,
+  QuickTraceContextChildrenProps,
+} from 'sentry/utils/performance/quickTrace/quickTraceContext';
 import {QuickTraceEvent, TraceError} from 'sentry/utils/performance/quickTrace/types';
 import {isTraceFull} from 'sentry/utils/performance/quickTrace/utils';
 

+ 1 - 5
static/app/utils/performance/quickTrace/quickTraceContext.tsx

@@ -4,8 +4,4 @@ import {QuickTraceQueryChildrenProps} from 'sentry/utils/performance/quickTrace/
 
 export type QuickTraceContextChildrenProps = QuickTraceQueryChildrenProps | undefined;
 
-const QuickTraceContext = createContext<QuickTraceContextChildrenProps>(undefined);
-
-export const Provider = QuickTraceContext.Provider;
-
-export const Consumer = QuickTraceContext.Consumer;
+export const QuickTraceContext = createContext<QuickTraceContextChildrenProps>(undefined);

+ 18 - 12
static/app/utils/performance/quickTrace/quickTraceQuery.tsx

@@ -13,24 +13,30 @@ import {
 
 type QueryProps = Omit<DiscoverQueryProps, 'api' | 'eventView'> & {
   children: (props: QuickTraceQueryChildrenProps) => React.ReactNode;
-  event: Event;
+  event: Event | undefined;
 };
 
 export default function QuickTraceQuery({children, event, ...props}: QueryProps) {
+  const renderEmpty = () => (
+    <Fragment>
+      {children({
+        isLoading: false,
+        error: null,
+        trace: [],
+        type: 'empty',
+        currentEvent: null,
+      })}
+    </Fragment>
+  );
+
+  if (!event) {
+    return renderEmpty();
+  }
+
   const traceId = event.contexts?.trace?.trace_id;
 
   if (!traceId) {
-    return (
-      <Fragment>
-        {children({
-          isLoading: false,
-          error: null,
-          trace: [],
-          type: 'empty',
-          currentEvent: null,
-        })}
-      </Fragment>
-    );
+    return renderEmpty();
   }
 
   const {start, end} = getTraceTimeRangeFromEvent(event);

+ 1 - 1
static/app/views/eventsV2/eventDetails/content.tsx

@@ -30,7 +30,7 @@ import EventView from 'sentry/utils/discover/eventView';
 import {formatTagKey} from 'sentry/utils/discover/fields';
 import {eventDetailsRoute} from 'sentry/utils/discover/urls';
 import {getMessage} from 'sentry/utils/events';
-import * as QuickTraceContext from 'sentry/utils/performance/quickTrace/quickTraceContext';
+import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
 import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
 import TraceMetaQuery, {
   TraceMetaQueryChildrenProps,

+ 9 - 7
static/app/views/organizationGroupDetails/eventToolbar.tsx

@@ -165,13 +165,15 @@ class GroupEventToolbar extends Component<Props> {
           project={project}
           organization={organization}
         />
-        <QuickTrace
-          event={evt}
-          group={group}
-          organization={organization}
-          location={location}
-          isPerformanceIssue={isPerformanceIssue}
-        />
+        {/* If this is a Performance issue, the QuickTrace will be rendered along with the embedded span tree instead */}
+        {!isPerformanceIssue && (
+          <QuickTrace
+            event={evt}
+            group={group}
+            organization={organization}
+            location={location}
+          />
+        )}
       </StyledDataSection>
     );
   }

+ 44 - 29
static/app/views/organizationGroupDetails/groupEventDetails/groupEventDetails.tsx

@@ -26,6 +26,8 @@ import {
 } from 'sentry/types';
 import {Event} from 'sentry/types/event';
 import fetchSentryAppInstallations from 'sentry/utils/fetchSentryAppInstallations';
+import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
+import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
 
 import GroupEventToolbar from '../eventToolbar';
 import ReprocessingProgress from '../reprocessingProgress';
@@ -219,7 +221,7 @@ class GroupEventDetails extends Component<GroupEventDetailsProps, State> {
       groupReprocessingStatus,
     } = this.props;
 
-    const eventWithMeta = withMeta(event) as Event;
+    const eventWithMeta = withMeta(event);
 
     // Reprocessing
     const hasReprocessingV2Feature = organization.features?.includes('reprocessing-v2');
@@ -240,34 +242,47 @@ class GroupEventDetails extends Component<GroupEventDetailsProps, State> {
             />
           ) : (
             <Fragment>
-              <StyledLayoutMain>
-                {eventWithMeta && (
-                  <GroupEventToolbar
-                    group={group}
-                    event={eventWithMeta}
-                    organization={organization}
-                    location={location}
-                    project={project}
-                  />
-                )}
-                <Wrapper>
-                  {group.status === 'ignored' && (
-                    <MutedBox statusDetails={group.statusDetails} />
-                  )}
-                  {group.status === 'resolved' && (
-                    <ResolutionBox
-                      statusDetails={group.statusDetails}
-                      activities={activities}
-                      projectId={project.id}
-                    />
-                  )}
-                  {this.renderReprocessedBox(
-                    groupReprocessingStatus,
-                    mostRecentActivity as GroupActivityReprocess
-                  )}
-                </Wrapper>
-                {this.renderContent(eventWithMeta)}
-              </StyledLayoutMain>
+              <QuickTraceQuery
+                event={eventWithMeta}
+                location={location}
+                orgSlug={organization.slug}
+              >
+                {results => {
+                  return (
+                    <StyledLayoutMain>
+                      <QuickTraceContext.Provider value={results}>
+                        {eventWithMeta && (
+                          <GroupEventToolbar
+                            group={group}
+                            event={eventWithMeta}
+                            organization={organization}
+                            location={location}
+                            project={project}
+                          />
+                        )}
+                        <Wrapper>
+                          {group.status === 'ignored' && (
+                            <MutedBox statusDetails={group.statusDetails} />
+                          )}
+                          {group.status === 'resolved' && (
+                            <ResolutionBox
+                              statusDetails={group.statusDetails}
+                              activities={activities}
+                              projectId={project.id}
+                            />
+                          )}
+                          {this.renderReprocessedBox(
+                            groupReprocessingStatus,
+                            mostRecentActivity as GroupActivityReprocess
+                          )}
+                        </Wrapper>
+                        {this.renderContent(eventWithMeta)}
+                      </QuickTraceContext.Provider>
+                    </StyledLayoutMain>
+                  );
+                }}
+              </QuickTraceQuery>
+
               <StyledLayoutSide>
                 <GroupSidebar
                   organization={organization}

+ 4 - 1
static/app/views/organizationGroupDetails/quickTrace/index.tsx

@@ -1,8 +1,9 @@
-import {Fragment} from 'react';
+import {Fragment, useContext} from 'react';
 import {Location} from 'history';
 
 import {Group, Organization} from 'sentry/types';
 import {Event} from 'sentry/types/event';
+import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
 
 import DistributedTracingPrompt from './configureDistributedTracing';
 import IssueQuickTrace from './issueQuickTrace';
@@ -18,6 +19,7 @@ type Props = {
 function QuickTrace({event, group, organization, location, isPerformanceIssue}: Props) {
   const hasPerformanceView = organization.features.includes('performance-view');
   const hasTraceContext = Boolean(event.contexts?.trace?.trace_id);
+  const quickTrace = useContext(QuickTraceContext);
 
   return (
     <Fragment>
@@ -34,6 +36,7 @@ function QuickTrace({event, group, organization, location, isPerformanceIssue}:
           event={event}
           location={location}
           isPerformanceIssue={isPerformanceIssue}
+          quickTrace={quickTrace!}
         />
       )}
     </Fragment>

+ 21 - 27
static/app/views/organizationGroupDetails/quickTrace/issueQuickTrace.tsx

@@ -19,7 +19,7 @@ import {Organization} from 'sentry/types';
 import {Event} from 'sentry/types/event';
 import {trackAnalyticsEvent} from 'sentry/utils/analytics';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
-import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
+import {QuickTraceQueryChildrenProps} from 'sentry/utils/performance/quickTrace/types';
 import {promptIsDismissed} from 'sentry/utils/promptIsDismissed';
 import withApi from 'sentry/utils/withApi';
 
@@ -28,6 +28,7 @@ type Props = {
   event: Event;
   location: Location;
   organization: Organization;
+  quickTrace: QuickTraceQueryChildrenProps;
   isPerformanceIssue?: boolean;
 };
 
@@ -47,7 +48,8 @@ class IssueQuickTrace extends Component<Props, State> {
   shouldComponentUpdate(nextProps, nextState: State) {
     return (
       this.props.event !== nextProps.event ||
-      this.state.shouldShow !== nextState.shouldShow
+      this.state.shouldShow !== nextState.shouldShow ||
+      this.props.quickTrace !== nextProps.quickTrace
     );
   }
 
@@ -102,7 +104,7 @@ class IssueQuickTrace extends Component<Props, State> {
     promptsUpdate(api, data).then(() => this.setState({shouldShow: false}));
   };
 
-  renderQuickTrace(results) {
+  renderQuickTrace(results: QuickTraceQueryChildrenProps) {
     const {event, location, organization, isPerformanceIssue} = this.props;
     const {shouldShow} = this.state;
     const {isLoading, error, trace, type} = results;
@@ -156,35 +158,27 @@ class IssueQuickTrace extends Component<Props, State> {
     });
 
     return (
-      <QuickTrace
-        event={event}
-        quickTrace={results}
-        location={location}
-        organization={organization}
-        anchor="left"
-        errorDest="issue"
-        transactionDest="performance"
-      />
+      <Fragment>
+        {this.renderTraceLink(results)}
+        <QuickTraceWrapper>
+          <QuickTrace
+            event={event}
+            quickTrace={results}
+            location={location}
+            organization={organization}
+            anchor="left"
+            errorDest="issue"
+            transactionDest="performance"
+          />
+        </QuickTraceWrapper>
+      </Fragment>
     );
   }
 
   render() {
-    const {event, organization, location} = this.props;
+    const {quickTrace} = this.props;
 
-    return (
-      <ErrorBoundary mini>
-        <QuickTraceQuery event={event} location={location} orgSlug={organization.slug}>
-          {results => {
-            return (
-              <Fragment>
-                {this.renderTraceLink(results)}
-                <QuickTraceWrapper>{this.renderQuickTrace(results)}</QuickTraceWrapper>
-              </Fragment>
-            );
-          }}
-        </QuickTraceQuery>
-      </ErrorBoundary>
-    );
+    return <ErrorBoundary mini>{this.renderQuickTrace(quickTrace)}</ErrorBoundary>;
   }
 }
 

Some files were not shown because too many files changed in this diff