Browse Source

feat(issues): Add suspect commit directly to stack trace (#78850)

Scott Cooper 5 months ago
parent
commit
e0a1ddd392

+ 1 - 1
static/app/components/events/eventEntries.tsx

@@ -82,7 +82,7 @@ function EventEntries({
       )}
       {!isShare && isNotSharedOrganization(organization) && (
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           eventId={event.id}
           group={group}
           commitRow={CommitRow}

+ 2 - 0
static/app/components/events/eventEntry.tsx

@@ -47,6 +47,7 @@ function EventEntryContent({
       return (
         <Exception
           event={event}
+          group={group}
           data={entry.data}
           projectSlug={projectSlug}
           groupingCurrentLevel={groupingCurrentLevel}
@@ -98,6 +99,7 @@ function EventEntryContent({
       return (
         <Threads
           event={event}
+          group={group}
           data={entry.data}
           projectSlug={projectSlug}
           groupingCurrentLevel={groupingCurrentLevel}

+ 52 - 23
static/app/components/events/interfaces/exception.tsx

@@ -1,9 +1,15 @@
+import {Fragment} from 'react';
+
+import {CommitRow} from 'sentry/components/commitRow';
+import ErrorBoundary from 'sentry/components/errorBoundary';
+import {SuspectCommits} from 'sentry/components/events/suspectCommits';
 import {t} from 'sentry/locale';
 import type {Event, ExceptionType} from 'sentry/types/event';
 import {EntryType} from 'sentry/types/event';
 import type {Group} from 'sentry/types/group';
 import type {Project} from 'sentry/types/project';
 import {StackType, StackView} from 'sentry/types/stacktrace';
+import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
 
 import {TraceEventDataSection} from '../traceEventDataSection';
 
@@ -14,13 +20,21 @@ import {isStacktraceNewestFirst} from './utils';
 type Props = {
   data: ExceptionType;
   event: Event;
+  group: Group | undefined;
   projectSlug: Project['slug'];
   groupingCurrentLevel?: Group['metadata']['current_level'];
   hideGuide?: boolean;
 };
 
-export function Exception({event, data, projectSlug, groupingCurrentLevel}: Props) {
+export function Exception({
+  event,
+  data,
+  projectSlug,
+  group,
+  groupingCurrentLevel,
+}: Props) {
   const eventHasThreads = !!event.entries.some(entry => entry.type === EntryType.THREADS);
+  const hasStreamlinedUI = useHasStreamlinedUI();
 
   // in case there are threads in the event data, we don't render the
   // exception block.  Instead the exception is contained within the
@@ -78,30 +92,45 @@ export function Exception({event, data, projectSlug, groupingCurrentLevel}: Prop
       }
       stackTraceNotFound={stackTraceNotFound}
     >
-      {({recentFirst, display, fullStackTrace}) =>
-        stackTraceNotFound ? (
+      {({recentFirst, display, fullStackTrace}) => {
+        return stackTraceNotFound ? (
           <NoStackTraceMessage />
         ) : (
-          <ExceptionContent
-            stackType={
-              display.includes('minified') ? StackType.MINIFIED : StackType.ORIGINAL
-            }
-            stackView={
-              display.includes('raw-stack-trace')
-                ? StackView.RAW
-                : fullStackTrace
-                  ? StackView.FULL
-                  : StackView.APP
-            }
-            projectSlug={projectSlug}
-            newestFirst={recentFirst}
-            event={event}
-            values={data.values}
-            groupingCurrentLevel={groupingCurrentLevel}
-            meta={meta}
-          />
-        )
-      }
+          <Fragment>
+            <ExceptionContent
+              stackType={
+                display.includes('minified') ? StackType.MINIFIED : StackType.ORIGINAL
+              }
+              stackView={
+                display.includes('raw-stack-trace')
+                  ? StackView.RAW
+                  : fullStackTrace
+                    ? StackView.FULL
+                    : StackView.APP
+              }
+              projectSlug={projectSlug}
+              newestFirst={recentFirst}
+              event={event}
+              values={data.values}
+              groupingCurrentLevel={groupingCurrentLevel}
+              meta={meta}
+            />
+            {hasStreamlinedUI && group && (
+              <ErrorBoundary
+                mini
+                message={t('There was an error loading the suspect commits')}
+              >
+                <SuspectCommits
+                  projectSlug={projectSlug}
+                  eventId={event.id}
+                  group={group}
+                  commitRow={CommitRow}
+                />
+              </ErrorBoundary>
+            )}
+          </Fragment>
+        );
+      }}
     </TraceEventDataSection>
   );
 }

+ 41 - 0
static/app/components/events/interfaces/threads.spec.tsx

@@ -1,14 +1,17 @@
 import merge from 'lodash/merge';
 import {GitHubIntegrationFixture} from 'sentry-fixture/githubIntegration';
+import {GroupFixture} from 'sentry-fixture/group';
 import {OrganizationFixture} from 'sentry-fixture/organization';
 import {ProjectFixture} from 'sentry-fixture/project';
 import {RepositoryFixture} from 'sentry-fixture/repository';
 import {RepositoryProjectPathConfigFixture} from 'sentry-fixture/repositoryProjectPathConfig';
+import {UserFixture} from 'sentry-fixture/user';
 
 import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
 
 import {Threads} from 'sentry/components/events/interfaces/threads';
 import {displayOptions} from 'sentry/components/events/traceEventDataSection';
+import ConfigStore from 'sentry/stores/configStore';
 import ProjectsStore from 'sentry/stores/projectsStore';
 import type {Event} from 'sentry/types/event';
 import {EntryType, EventOrGroupType} from 'sentry/types/event';
@@ -35,6 +38,7 @@ describe('Threads', function () {
       body: {config, sourceUrl: 'https://something.io', integrations: [integration]},
     });
     ProjectsStore.loadInitialData([project]);
+    ConfigStore.set('user', UserFixture());
   });
 
   describe('non native platform', function () {
@@ -218,6 +222,7 @@ describe('Threads', function () {
         event,
         groupingCurrentLevel: 0,
         projectSlug: project.slug,
+        group: undefined,
       };
 
       it('renders', async function () {
@@ -323,6 +328,41 @@ describe('Threads', function () {
           await screen.findByText('Minified version not available')
         ).toBeInTheDocument();
       });
+
+      it('renders suspect commits', async function () {
+        const user = UserFixture();
+        user.options.prefersIssueDetailsStreamlinedUI = true;
+        ConfigStore.set('user', user);
+        const group = GroupFixture();
+        const committers = [
+          {
+            author: {name: 'Max Bittker', id: '1'},
+            commits: [
+              {
+                message: 'feat: xyz',
+                score: 4,
+                id: 'ab2709293d0c9000829084ac7b1c9221fb18437c',
+                repository: RepositoryFixture(),
+                dateCreated: '2018-03-02T18:30:26Z',
+              },
+            ],
+          },
+        ];
+        MockApiClient.addMockResponse({
+          method: 'GET',
+          url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/committers/`,
+          body: {
+            committers,
+          },
+        });
+        render(<Threads {...props} group={group} />, {
+          organization,
+        });
+        expect(await screen.findByText('Stack Trace')).toBeInTheDocument();
+
+        // Suspect commits
+        expect(await screen.findByTestId('commit-row')).toBeInTheDocument();
+      });
     });
   });
 
@@ -860,6 +900,7 @@ describe('Threads', function () {
         event,
         groupingCurrentLevel: 0,
         projectSlug: project.slug,
+        group: undefined,
       };
 
       it('renders', async function () {

+ 18 - 1
static/app/components/events/interfaces/threads.tsx

@@ -1,6 +1,7 @@
 import {Fragment, useEffect, useState} from 'react';
 import styled from '@emotion/styled';
 
+import {CommitRow} from 'sentry/components/commitRow';
 import ErrorBoundary from 'sentry/components/errorBoundary';
 import {StacktraceBanners} from 'sentry/components/events/interfaces/crashContent/exception/banners/stacktraceBanners';
 import {getLockReason} from 'sentry/components/events/interfaces/threads/threadSelector/lockReason';
@@ -9,6 +10,7 @@ import {
   getThreadStateHelpText,
   ThreadStates,
 } from 'sentry/components/events/interfaces/threads/threadSelector/threadStates';
+import {SuspectCommits} from 'sentry/components/events/suspectCommits';
 import Pill from 'sentry/components/pill';
 import Pills from 'sentry/components/pills';
 import QuestionTooltip from 'sentry/components/questionTooltip';
@@ -18,6 +20,7 @@ import {t, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Event, Thread} from 'sentry/types/event';
 import {EntryType} from 'sentry/types/event';
+import type {Group} from 'sentry/types/group';
 import type {Project} from 'sentry/types/project';
 import {StackType, StackView} from 'sentry/types/stacktrace';
 import {defined} from 'sentry/utils';
@@ -43,6 +46,7 @@ type Props = Pick<ExceptionProps, 'groupingCurrentLevel'> & {
     values?: Array<Thread>;
   };
   event: Event;
+  group: Group | undefined;
   projectSlug: Project['slug'];
 };
 
@@ -96,7 +100,7 @@ const useActiveThreadState = (
   return [activeThread, setActiveThread];
 };
 
-export function Threads({data, event, projectSlug, groupingCurrentLevel}: Props) {
+export function Threads({data, event, projectSlug, groupingCurrentLevel, group}: Props) {
   const threads = data.values ?? [];
   const hasStreamlinedUI = useHasStreamlinedUI();
   const [activeThread, setActiveThread] = useActiveThreadState(event, threads);
@@ -351,6 +355,19 @@ export function Threads({data, event, projectSlug, groupingCurrentLevel}: Props)
                 </ErrorBoundary>
               )}
               {renderContent(childrenProps)}
+              {hasStreamlinedUI && group && (
+                <ErrorBoundary
+                  mini
+                  message={t('There was an error loading the suspect commits')}
+                >
+                  <SuspectCommits
+                    projectSlug={projectSlug}
+                    eventId={event.id}
+                    commitRow={CommitRow}
+                    group={group}
+                  />
+                </ErrorBoundary>
+              )}
             </Fragment>
           );
         }}

+ 12 - 8
static/app/components/events/suspectCommits.spec.tsx

@@ -68,12 +68,16 @@ describe('SuspectCommits', function () {
           committers,
         },
       });
+      MockApiClient.addMockResponse({
+        url: `/organizations/${organization.slug}/projects/`,
+        body: [project],
+      });
     });
 
     it('Renders base commit row', async function () {
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={CommitRow}
           eventId={event.id}
           group={group}
@@ -88,7 +92,7 @@ describe('SuspectCommits', function () {
     it('Renders quick context commit row', async function () {
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={QuickContextCommitRow}
           eventId={event.id}
           group={group}
@@ -112,7 +116,7 @@ describe('SuspectCommits', function () {
 
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={CommitRow}
           eventId={event.id}
           group={group}
@@ -126,7 +130,7 @@ describe('SuspectCommits', function () {
     it('expands', async function () {
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={CommitRow}
           eventId={event.id}
           group={group}
@@ -165,7 +169,7 @@ describe('SuspectCommits', function () {
 
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={CommitRow}
           eventId={event.id}
           group={group}
@@ -237,7 +241,7 @@ describe('SuspectCommits', function () {
     it('Renders base commit row', async function () {
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={CommitRow}
           eventId={event.id}
           group={group}
@@ -253,7 +257,7 @@ describe('SuspectCommits', function () {
     it('Renders quick context commit row', async function () {
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={QuickContextCommitRow}
           eventId={event.id}
           group={group}
@@ -275,7 +279,7 @@ describe('SuspectCommits', function () {
       });
       render(
         <SuspectCommits
-          project={project}
+          projectSlug={project.slug}
           commitRow={CommitRow}
           eventId={event.id}
           group={group}

+ 13 - 26
static/app/components/events/suspectCommits.tsx

@@ -2,8 +2,6 @@ import {Fragment, useState} from 'react';
 import styled from '@emotion/styled';
 import uniqBy from 'lodash/uniqBy';
 
-import bannerIllustration from 'sentry-images/spot/alerts-empty-state.svg';
-
 import type {CommitRowProps} from 'sentry/components/commitRow';
 import {SuspectCommitHeader} from 'sentry/components/events/styles';
 import Panel from 'sentry/components/panels/panel';
@@ -13,27 +11,34 @@ import {t, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Group} from 'sentry/types/group';
 import type {Commit} from 'sentry/types/integrations';
-import type {AvatarProject} from 'sentry/types/project';
+import type {Project} from 'sentry/types/project';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {getAnalyticsDataForGroup} from 'sentry/utils/events';
 import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
 import useCommitters from 'sentry/utils/useCommitters';
 import useOrganization from 'sentry/utils/useOrganization';
+import useProjectFromSlug from 'sentry/utils/useProjectFromSlug';
 import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
 
 interface Props {
   commitRow: React.ComponentType<CommitRowProps>;
   eventId: string;
-  project: AvatarProject;
+  projectSlug: Project['slug'];
   group?: Group;
 }
 
-export function SuspectCommits({group, eventId, project, commitRow: CommitRow}: Props) {
+export function SuspectCommits({
+  group,
+  eventId,
+  projectSlug,
+  commitRow: CommitRow,
+}: Props) {
   const organization = useOrganization();
   const [isExpanded, setIsExpanded] = useState(false);
+  const project = useProjectFromSlug({organization, projectSlug});
   const {data} = useCommitters({
     eventId,
-    projectSlug: project.slug,
+    projectSlug,
   });
   const committers = data?.committers ?? [];
 
@@ -66,7 +71,7 @@ export function SuspectCommits({group, eventId, project, commitRow: CommitRow}:
   const handlePullRequestClick = (commit: Commit, commitIndex: number) => {
     trackAnalytics('issue_details.suspect_commits.pull_request_clicked', {
       organization,
-      project_id: parseInt(project.id as string, 10),
+      project_id: parseInt(project?.id as string, 10),
       suspect_commit_calculation: commit.suspectCommitType ?? 'unknown',
       suspect_commit_index: commitIndex,
       ...getAnalyticsDataForGroup(group),
@@ -76,7 +81,7 @@ export function SuspectCommits({group, eventId, project, commitRow: CommitRow}:
   const handleCommitClick = (commit: Commit, commitIndex: number) => {
     trackAnalytics('issue_details.suspect_commits.commit_clicked', {
       organization,
-      project_id: parseInt(project.id as string, 10),
+      project_id: parseInt(project?.id as string, 10),
       has_pull_request: commit.pullRequest?.id !== undefined,
       suspect_commit_calculation: commit.suspectCommitType ?? 'unknown',
       suspect_commit_index: commitIndex,
@@ -101,9 +106,6 @@ export function SuspectCommits({group, eventId, project, commitRow: CommitRow}:
                 project={project}
               />
             </div>
-            <IllustrationContainer>
-              <Illustration src={bannerIllustration} />
-            </IllustrationContainer>
           </StreamlinedPanel>
         ))}
       </ScrollCarousel>
@@ -168,21 +170,6 @@ const StreamlinedPanel = styled(Panel)`
   min-width: 85%;
 `;
 
-const IllustrationContainer = styled('div')`
-  position: absolute;
-  top: 0px;
-  right: 50px;
-
-  @media (max-width: ${p => p.theme.breakpoints.xlarge}) {
-    display: none;
-    pointer-events: none;
-  }
-`;
-
-const Illustration = styled('img')`
-  height: 110px;
-`;
-
 const SuspectCommitWrapper = styled('div')`
   margin-right: 0;
   margin-left: 0;

+ 4 - 0
static/app/views/discover/table/quickContext/issueContext.spec.tsx

@@ -56,6 +56,10 @@ describe('Quick Context Content Issue Column', function () {
       method: 'GET',
       body: mockedGroup,
     });
+    MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/projects/`,
+      body: [ProjectFixture()],
+    });
   });
 
   afterEach(function () {

+ 1 - 1
static/app/views/discover/table/quickContext/issueContext.tsx

@@ -159,7 +159,7 @@ function IssueContext(props: BaseContextProps) {
     issue && (
       <SuspectCommitsContainer data-test-id="quick-context-suspect-commits-container">
         <SuspectCommits
-          project={issue.project}
+          projectSlug={issue.project.slug}
           eventId={event.eventID}
           commitRow={QuickContextCommitRow}
         />

+ 5 - 3
static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx

@@ -159,7 +159,7 @@ export function EventDetailsContent({
         {!hasStreamlinedUI && <TraceDataSection event={event} />}
         {!hasStreamlinedUI && (
           <SuspectCommits
-            project={project}
+            projectSlug={project.slug}
             eventId={event.id}
             group={group}
             commitRow={CommitRow}
@@ -254,7 +254,8 @@ export function EventDetailsContent({
           <Exception
             event={event}
             data={eventEntries[EntryType.EXCEPTION].data}
-            projectSlug={projectSlug}
+            projectSlug={project.slug}
+            group={group}
             groupingCurrentLevel={groupingCurrentLevel}
           />
         </EntryErrorBoundary>
@@ -274,8 +275,9 @@ export function EventDetailsContent({
           <Threads
             event={event}
             data={eventEntries[EntryType.THREADS].data}
-            projectSlug={projectSlug}
+            projectSlug={project.slug}
             groupingCurrentLevel={groupingCurrentLevel}
+            group={group}
           />
         </EntryErrorBoundary>
       )}

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