Просмотр исходного кода

feat(issue-trace): Replace missing trace alert with a placeholder trace (#46564)

Closes https://github.com/getsentry/sentry/issues/45968

Moves the text inside the alert to a custom placeholder component that
will display the help text on hover.
Malachi Willey 1 год назад
Родитель
Сommit
c15ccf0a50

+ 41 - 0
static/app/views/issueDetails/quickTrace/index.spec.tsx

@@ -0,0 +1,41 @@
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
+import QuickTrace from 'sentry/views/issueDetails/quickTrace';
+
+describe('IssueQuickTrace', () => {
+  const defaultProps = {
+    organization: TestStubs.Organization({features: ['performance-view']}),
+    event: TestStubs.Event({contexts: {trace: {trace_id: 100}}}),
+    group: TestStubs.Group(),
+    location: TestStubs.location(),
+  };
+
+  it('renders nothing without performance-view flag', () => {
+    const {container} = render(
+      <QuickTrace {...defaultProps} organization={TestStubs.Organization()} />
+    );
+
+    expect(container).toBeEmptyDOMElement();
+  });
+
+  it('renders nothing if event does not have a trace context', () => {
+    const {container} = render(
+      <QuickTrace {...defaultProps} event={TestStubs.Event({contexts: {}})} />
+    );
+
+    expect(container).toBeEmptyDOMElement();
+  });
+
+  it('renders a placeholder if event has a trace context but finds nothing', () => {
+    MockApiClient.addMockResponse({});
+
+    render(
+      <QuickTraceContext.Provider value={undefined}>
+        <QuickTrace {...defaultProps} />
+      </QuickTraceContext.Provider>
+    );
+
+    expect(screen.getByTestId('missing-trace-placeholder')).toBeInTheDocument();
+  });
+});

+ 2 - 3
static/app/views/issueDetails/quickTrace/index.tsx

@@ -14,10 +14,9 @@ type Props = {
   group: Group;
   location: Location;
   organization: Organization;
-  isPerformanceIssue?: boolean;
 };
 
-function QuickTrace({event, organization, location, isPerformanceIssue}: Props) {
+function QuickTrace({group, event, organization, location}: Props) {
   const hasPerformanceView = organization.features.includes('performance-view');
   const hasTraceContext = Boolean(event.contexts?.trace?.trace_id);
   const quickTrace = useContext(QuickTraceContext);
@@ -35,7 +34,7 @@ function QuickTrace({event, organization, location, isPerformanceIssue}: Props)
       organization={organization}
       event={event}
       location={location}
-      isPerformanceIssue={isPerformanceIssue}
+      group={group}
       quickTrace={quickTrace}
     />
   );

+ 60 - 56
static/app/views/issueDetails/quickTrace/issueQuickTrace.tsx

@@ -1,89 +1,97 @@
+import {useEffect} from 'react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
-import {Alert} from 'sentry/components/alert';
-import {Button} from 'sentry/components/button';
 import ErrorBoundary from 'sentry/components/errorBoundary';
 import ExternalLink from 'sentry/components/links/externalLink';
 import QuickTrace from 'sentry/components/quickTrace';
-import {IconClose} from 'sentry/icons';
+import {
+  ErrorNodeContent,
+  EventNode,
+  QuickTraceContainer,
+  TraceConnector,
+} from 'sentry/components/quickTrace/styles';
+import {Tooltip} from 'sentry/components/tooltip';
+import {IconFire} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import {Organization} from 'sentry/types';
+import {Group, IssueCategory, Organization} from 'sentry/types';
 import {Event} from 'sentry/types/event';
+import {defined} from 'sentry/utils';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
 import {QuickTraceQueryChildrenProps} from 'sentry/utils/performance/quickTrace/types';
-import usePromptCheck from 'sentry/views/issueDetails/quickTrace/usePromptCheck';
+import useOrganization from 'sentry/utils/useOrganization';
 
 type Props = {
   event: Event;
+  group: Group;
   location: Location;
   organization: Organization;
   quickTrace: undefined | QuickTraceQueryChildrenProps;
-  isPerformanceIssue?: boolean;
 };
 
-function IssueQuickTrace({
-  event,
-  location,
-  organization,
-  quickTrace,
-  isPerformanceIssue,
-}: Props) {
-  const {shouldShowPrompt, snoozePrompt} = usePromptCheck({
-    projectId: event.projectID,
-    feature: 'quick_trace_missing',
-    organization,
-  });
-
-  if (
-    !quickTrace ||
-    quickTrace.error ||
-    quickTrace.trace === null ||
-    quickTrace.trace.length === 0
-  ) {
-    if (!shouldShowPrompt) {
-      return null;
-    }
-
+function TransactionMissingPlaceholder({
+  type,
+  group,
+}: {
+  group: Group;
+  type?: QuickTraceQueryChildrenProps['type'];
+}) {
+  const organization = useOrganization();
+  useEffect(() => {
     trackAdvancedAnalyticsEvent('issue.quick_trace_status', {
       organization,
-      status: quickTrace?.type === 'missing' ? 'transaction missing' : 'trace missing',
-      is_performance_issue: isPerformanceIssue ?? false,
+      status: type === 'missing' ? 'transaction missing' : 'trace missing',
+      is_performance_issue: group.issueCategory === IssueCategory.PERFORMANCE,
     });
+  });
 
-    return (
-      <StyledAlert
-        type="info"
-        showIcon
-        trailingItems={
-          <Button
-            aria-label={t('Remind me later')}
-            title={t('Dismiss for a month')}
-            priority="link"
-            size="xs"
-            icon={<IconClose />}
-            onClick={snoozePrompt}
-          />
-        }
-      >
-        {tct(
-          'The [type] for this event cannot be found. [link:Read the docs to understand why].',
+  return (
+    <QuickTraceWrapper>
+      <Tooltip
+        isHoverable
+        position="bottom"
+        title={tct(
+          'The [type] for this event cannot be found. [link:Read the  docs] to understand why.',
           {
-            type: quickTrace?.type === 'missing' ? t('transaction') : t('trace'),
+            type: type === 'missing' ? t('transaction') : t('trace'),
             link: (
               <ExternalLink href="https://docs.sentry.io/product/sentry-basics/tracing/trace-view/#troubleshooting" />
             ),
           }
         )}
-      </StyledAlert>
-    );
+      >
+        <QuickTraceContainer data-test-id="missing-trace-placeholder">
+          <EventNode type="white" icon={null}>
+            ???
+          </EventNode>
+          <TraceConnector />
+          <EventNode type="error" data-test-id="event-node">
+            <ErrorNodeContent>
+              <IconFire size="xs" />
+              {t('This Event')}
+            </ErrorNodeContent>
+          </EventNode>
+        </QuickTraceContainer>
+      </Tooltip>
+    </QuickTraceWrapper>
+  );
+}
+
+function IssueQuickTrace({group, event, location, organization, quickTrace}: Props) {
+  if (
+    !quickTrace ||
+    quickTrace.error ||
+    !defined(quickTrace.trace) ||
+    quickTrace.trace.length === 0
+  ) {
+    return <TransactionMissingPlaceholder group={group} type={quickTrace?.type} />;
   }
 
   trackAdvancedAnalyticsEvent('issue.quick_trace_status', {
     organization,
     status: 'success',
-    is_performance_issue: isPerformanceIssue ?? false,
+    is_performance_issue: group.issueCategory === IssueCategory.PERFORMANCE,
   });
 
   return (
@@ -107,8 +115,4 @@ const QuickTraceWrapper = styled('div')`
   margin-top: ${space(0.5)};
 `;
 
-const StyledAlert = styled(Alert)`
-  margin: ${space(1)} 0;
-`;
-
 export default IssueQuickTrace;

+ 0 - 60
static/app/views/issueDetails/quickTrace/traceLink.tsx

@@ -1,60 +0,0 @@
-import {useCallback, useContext} from 'react';
-import styled from '@emotion/styled';
-
-import Link from 'sentry/components/links/link';
-import {generateTraceTarget} from 'sentry/components/quickTrace/utils';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-import {Event} from 'sentry/types';
-import {trackAnalyticsEvent} from 'sentry/utils/analytics';
-import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
-import useOrganization from 'sentry/utils/useOrganization';
-
-type TraceLinkProps = {
-  event: Event;
-};
-
-export function TraceLink({event}: TraceLinkProps) {
-  const organization = useOrganization();
-  const quickTrace = useContext(QuickTraceContext);
-  const handleTraceLink = useCallback(() => {
-    trackAnalyticsEvent({
-      eventKey: 'quick_trace.trace_id.clicked',
-      eventName: 'Quick Trace: Trace ID clicked',
-      organization_id: parseInt(organization.id, 10),
-      source: 'issues',
-    });
-  }, [organization.id]);
-
-  if (
-    !quickTrace ||
-    quickTrace.isLoading ||
-    quickTrace.error ||
-    quickTrace.type === 'empty'
-  ) {
-    return null;
-  }
-  return (
-    <LinkContainer>
-      <Link to={generateTraceTarget(event, organization)} onClick={handleTraceLink}>
-        {t('View Full Trace')}
-      </Link>
-    </LinkContainer>
-  );
-}
-
-const LinkContainer = styled('span')`
-  margin-left: ${space(1)};
-  padding-left: ${space(1)};
-  position: relative;
-
-  &:before {
-    display: block;
-    position: absolute;
-    content: '';
-    left: 0;
-    top: 2px;
-    height: 14px;
-    border-left: 1px solid ${p => p.theme.border};
-  }
-`;