Browse Source

feat(related_issues): Integrate related issues into Similar Issues tab (#70150)

This change integrated the trace-connected errors and same-root-cause
issues into the Similar Issues tab. It also drops the Related Issues tab
(introduced in #67079).

Both sections of this screenshot are feature-flagged.

<img width="942" alt="image"
src="https://github.com/getsentry/sentry/assets/44410/c9a3ed87-9943-4550-b7a0-4a2914add734">
Armen Zambrano G 10 months ago
parent
commit
d8c2db6e2c

+ 0 - 6
static/app/routes.tsx

@@ -1815,12 +1815,6 @@ function buildRoutes() {
             make(() => import('sentry/views/issueDetails/groupSimilarIssues'))
           )}
         />
-        <Route
-          path={TabPaths[Tab.RELATED_ISSUES]}
-          component={hoc(
-            make(() => import('sentry/views/issueDetails/groupRelatedIssues'))
-          )}
-        />
         <Route
           path={TabPaths[Tab.MERGED]}
           component={hoc(make(() => import('sentry/views/issueDetails/groupMerged')))}

+ 77 - 97
static/app/views/issueDetails/groupRelatedIssues/index.tsx

@@ -1,12 +1,9 @@
-// XXX: A lot of the UI for this file will be changed once we use IssueListActions
-// We're using GroupList to help us iterate quickly
+import {Fragment} from 'react';
 import type {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 
-import Feature from 'sentry/components/acl/feature';
 import {LinkButton} from 'sentry/components/button';
 import GroupList from 'sentry/components/issues/groupList';
-import * as Layout from 'sentry/components/layouts/thirds';
 import Link from 'sentry/components/links/link';
 import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
@@ -70,107 +67,90 @@ function GroupRelatedIssues({params}: Props) {
   );
 
   return (
-    <Layout.Body>
-      <Layout.Main fullWidth>
-        <HeaderWrapper>
-          <small>
-            {t(
-              'Related Issues are issues that are related in some way and can be acted on together.'
-            )}
-          </small>
-        </HeaderWrapper>
-        {isLoading ? (
-          <LoadingIndicator />
-        ) : isError ? (
-          <LoadingError
-            message={t('Unable to load related issues, please try again later')}
-            onRetry={refetch}
-          />
-        ) : (
+    <Fragment>
+      {isLoading ? (
+        <LoadingIndicator />
+      ) : isError ? (
+        <LoadingError
+          message={t('Unable to load related issues, please try again later')}
+          onRetry={refetch}
+        />
+      ) : (
+        <Fragment>
           <div>
-            <div>
-              <HeaderWrapper>
-                <Title>{t('Issues caused by the same root cause')}</Title>
-                {sameRootCauseIssues.length > 0 ? (
-                  <div>
-                    <TextButtonWrapper>
-                      <div />
-                      <LinkButton
-                        to={`/organizations/${orgSlug}/issues/?query=issue.id:[${groupId},${sameRootCauseIssues}]`}
-                        size="xs"
-                      >
-                        {t('Open in Issues')}
-                      </LinkButton>
-                    </TextButtonWrapper>
-                    <GroupList
-                      endpointPath={`/organizations/${orgSlug}/issues/`}
-                      orgSlug={orgSlug}
-                      queryParams={{query: `issue.id:[${sameRootCauseIssues}]`}}
-                      query=""
-                      source="related-issues-tab"
-                      canSelectGroups={false}
-                      withChart={false}
-                    />
-                  </div>
-                ) : (
-                  <small>{t('No same-root-cause related issues were found.')}</small>
-                )}
-              </HeaderWrapper>
-            </div>
-            <div>
-              <HeaderWrapper>
-                <Title>{t('Trace connected issues')}</Title>
-                {traceConnectedIssues.length > 0 ? (
-                  <div>
-                    <TextButtonWrapper>
-                      <small>
-                        {t('These are the issues belonging to ')}
-                        <Link
-                          to={`/organizations/${orgSlug}/performance/trace/${traceMeta.trace_id}/?node=error-${traceMeta.event_id}`}
-                        >
-                          {t('this trace')}
-                        </Link>
-                      </small>
-                      <LinkButton
-                        to={`/organizations/${orgSlug}/issues/?query=trace:${traceMeta.trace_id}`}
-                        size="xs"
+            <HeaderWrapper>
+              <Title>{t('Issues caused by the same root cause')}</Title>
+              {sameRootCauseIssues.length > 0 ? (
+                <div>
+                  <TextButtonWrapper>
+                    <div />
+                    <LinkButton
+                      to={`/organizations/${orgSlug}/issues/?query=issue.id:[${groupId},${sameRootCauseIssues}]`}
+                      size="xs"
+                    >
+                      {t('Open in Issues')}
+                    </LinkButton>
+                  </TextButtonWrapper>
+                  <GroupList
+                    endpointPath={`/organizations/${orgSlug}/issues/`}
+                    orgSlug={orgSlug}
+                    queryParams={{query: `issue.id:[${sameRootCauseIssues}]`}}
+                    query=""
+                    source="related-issues-tab"
+                    canSelectGroups={false}
+                    withChart={false}
+                  />
+                </div>
+              ) : (
+                <small>{t('No same-root-cause related issues were found.')}</small>
+              )}
+            </HeaderWrapper>
+          </div>
+          <div>
+            <HeaderWrapper>
+              <Title>{t('Issues in the same trace')}</Title>
+              {traceConnectedIssues.length > 0 ? (
+                <div>
+                  <TextButtonWrapper>
+                    <small>
+                      {t('These issues were all found within ')}
+                      <Link
+                        to={`/organizations/${orgSlug}/performance/trace/${traceMeta.trace_id}/?node=error-${traceMeta.event_id}`}
                       >
-                        {t('Open in Issues')}
-                      </LinkButton>
-                    </TextButtonWrapper>
-                    <GroupList
-                      endpointPath={`/organizations/${orgSlug}/issues/`}
-                      orgSlug={orgSlug}
-                      queryParams={{query: `issue.id:[${traceConnectedIssues}]`}}
-                      query=""
-                      source="related-issues-tab"
-                      canSelectGroups={false}
-                      withChart={false}
-                    />
-                  </div>
-                ) : (
-                  <small>{t('No trace-connected related issues were found.')}</small>
-                )}
-              </HeaderWrapper>
-            </div>
+                        {t('this trace')}
+                      </Link>
+                      .
+                    </small>
+                    <LinkButton
+                      to={`/organizations/${orgSlug}/issues/?query=trace:${traceMeta.trace_id}`}
+                      size="xs"
+                    >
+                      {t('Open in Issues')}
+                    </LinkButton>
+                  </TextButtonWrapper>
+                  <GroupList
+                    endpointPath={`/organizations/${orgSlug}/issues/`}
+                    orgSlug={orgSlug}
+                    queryParams={{query: `issue.id:[${traceConnectedIssues}]`}}
+                    query=""
+                    source="related-issues-tab"
+                    canSelectGroups={false}
+                    withChart={false}
+                  />
+                </div>
+              ) : (
+                <small>{t('No trace-connected related issues were found.')}</small>
+              )}
+            </HeaderWrapper>
           </div>
-        )}
-      </Layout.Main>
-    </Layout.Body>
-  );
-}
-
-function GroupRelatedIssuesWrapper(props: Props) {
-  return (
-    <Feature features={['related-issues']}>
-      <GroupRelatedIssues {...props} />
-    </Feature>
+        </Fragment>
+      )}
+    </Fragment>
   );
 }
 
 // Export the component without feature flag controls
 export {GroupRelatedIssues};
-export default GroupRelatedIssuesWrapper;
 
 const Title = styled('h4')`
   margin-bottom: ${space(0.75)};

+ 12 - 3
static/app/views/issueDetails/groupSimilarIssues/index.tsx

@@ -1,4 +1,6 @@
 import Feature from 'sentry/components/acl/feature';
+import * as Layout from 'sentry/components/layouts/thirds';
+import {GroupRelatedIssues} from 'sentry/views/issueDetails/groupRelatedIssues';
 
 import SimilarStackTrace from './similarStackTrace';
 
@@ -6,9 +8,16 @@ type Props = React.ComponentProps<typeof SimilarStackTrace>;
 
 function GroupSimilarIssues({project, ...props}: Props) {
   return (
-    <Feature features="similarity-view" project={project}>
-      <SimilarStackTrace project={project} {...props} />
-    </Feature>
+    <Layout.Body>
+      <Layout.Main fullWidth>
+        <Feature features="related-issues">
+          <GroupRelatedIssues {...props} />
+        </Feature>
+        <Feature features="similarity-view" project={project}>
+          <SimilarStackTrace project={project} {...props} />
+        </Feature>
+      </Layout.Main>
+    </Layout.Body>
   );
 }
 

+ 54 - 59
static/app/views/issueDetails/groupSimilarIssues/similarStackTrace/index.tsx

@@ -7,7 +7,6 @@ import * as qs from 'query-string';
 import Alert from 'sentry/components/alert';
 import EmptyStateWarning from 'sentry/components/emptyStateWarning';
 import HookOrDefault from 'sentry/components/hookOrDefault';
-import * as Layout from 'sentry/components/layouts/thirds';
 import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import Panel from 'sentry/components/panels/panel';
@@ -176,66 +175,62 @@ function SimilarStackTrace({params, location, project}: Props) {
           which refers to whether or not we'd group the similar issue into the main issue.
         </Alert>
       )}
-      <Layout.Body>
-        <Layout.Main fullWidth>
-          <HeaderWrapper>
-            <Title>{t('Issues with a similar stack trace')}</Title>
-            <small>
+      <HeaderWrapper>
+        <Title>{t('Issues with a similar stack trace')}</Title>
+        <small>
+          {t(
+            'This is an experimental feature. Data may not be immediately available while we process merges.'
+          )}
+        </small>
+      </HeaderWrapper>
+      {status === 'loading' && <LoadingIndicator />}
+      {status === 'error' && (
+        <LoadingError
+          message={t('Unable to load similar issues, please try again later')}
+          onRetry={fetchData}
+        />
+      )}
+      {status === 'ready' && !hasSimilarItems && !hasSimilarityEmbeddingsFeature && (
+        <Panel>
+          <EmptyStateWarning>
+            <p>{t("There don't seem to be any similar issues.")}</p>
+          </EmptyStateWarning>
+        </Panel>
+      )}
+      {status === 'ready' && !hasSimilarItems && hasSimilarityEmbeddingsFeature && (
+        <Panel>
+          <EmptyStateWarning>
+            <p>
               {t(
-                'This is an experimental feature. Data may not be immediately available while we process merges.'
+                "There don't seem to be any similar issues. This can occur when the issue has no stacktrace or in-app frames."
               )}
-            </small>
-          </HeaderWrapper>
-          {status === 'loading' && <LoadingIndicator />}
-          {status === 'error' && (
-            <LoadingError
-              message={t('Unable to load similar issues, please try again later')}
-              onRetry={fetchData}
-            />
-          )}
-          {status === 'ready' && !hasSimilarItems && !hasSimilarityEmbeddingsFeature && (
-            <Panel>
-              <EmptyStateWarning>
-                <p>{t("There don't seem to be any similar issues.")}</p>
-              </EmptyStateWarning>
-            </Panel>
-          )}
-          {status === 'ready' && !hasSimilarItems && hasSimilarityEmbeddingsFeature && (
-            <Panel>
-              <EmptyStateWarning>
-                <p>
-                  {t(
-                    "There don't seem to be any similar issues. This can occur when the issue has no stacktrace or in-app frames."
-                  )}
-                </p>
-              </EmptyStateWarning>
-            </Panel>
-          )}
-          {status === 'ready' && hasSimilarItems && !hasSimilarityEmbeddingsFeature && (
-            <List
-              items={items.similar}
-              filteredItems={items.filtered}
-              onMerge={handleMerge}
-              orgId={orgId}
-              project={project}
-              groupId={groupId}
-              pageLinks={items.pageLinks}
-            />
-          )}
-          {status === 'ready' && hasSimilarItems && hasSimilarityEmbeddingsFeature && (
-            <List
-              items={items.similar.concat(items.filtered)}
-              filteredItems={[]}
-              onMerge={handleMerge}
-              orgId={orgId}
-              project={project}
-              groupId={groupId}
-              pageLinks={items.pageLinks}
-            />
-          )}
-          <DataConsentBanner source="grouping" />
-        </Layout.Main>
-      </Layout.Body>
+            </p>
+          </EmptyStateWarning>
+        </Panel>
+      )}
+      {status === 'ready' && hasSimilarItems && !hasSimilarityEmbeddingsFeature && (
+        <List
+          items={items.similar}
+          filteredItems={items.filtered}
+          onMerge={handleMerge}
+          orgId={orgId}
+          project={project}
+          groupId={groupId}
+          pageLinks={items.pageLinks}
+        />
+      )}
+      {status === 'ready' && hasSimilarItems && hasSimilarityEmbeddingsFeature && (
+        <List
+          items={items.similar.concat(items.filtered)}
+          filteredItems={[]}
+          onMerge={handleMerge}
+          orgId={orgId}
+          project={project}
+          groupId={groupId}
+          pageLinks={items.pageLinks}
+        />
+      )}
+      <DataConsentBanner source="grouping" />
     </Fragment>
   );
 }

+ 0 - 7
static/app/views/issueDetails/header.tsx

@@ -141,13 +141,6 @@ function GroupHeaderTabs({
       >
         {t('Merged Issues')}
       </TabList.Item>
-      <TabList.Item
-        key={Tab.RELATED_ISSUES}
-        hidden={!organizationFeatures.has('related-issues')}
-        to={`${baseUrl}related/${location.search}`}
-      >
-        {t('Related Issues')}
-      </TabList.Item>
       <TabList.Item
         key={Tab.SIMILAR_ISSUES}
         hidden={!hasSimilarView || !issueTypeConfig.similarIssues.enabled}

+ 0 - 2
static/app/views/issueDetails/types.tsx

@@ -7,7 +7,6 @@ export enum Tab {
   EVENTS = 'events',
   MERGED = 'merged',
   SIMILAR_ISSUES = 'similar-issues',
-  RELATED_ISSUES = 'related-issues',
   REPLAYS = 'Replays',
 }
 
@@ -20,6 +19,5 @@ export const TabPaths: Record<Tab, string> = {
   [Tab.EVENTS]: 'events/',
   [Tab.MERGED]: 'merged/',
   [Tab.SIMILAR_ISSUES]: 'similar/',
-  [Tab.RELATED_ISSUES]: 'related/',
   [Tab.REPLAYS]: 'replays/',
 };