diff --git a/static/app/views/issueDetails/groupRelatedIssues/index.spec.tsx b/static/app/views/issueDetails/groupRelatedIssues/index.spec.tsx index a78b8edac8a61a..f77f77d0b6cdea 100644 --- a/static/app/views/issueDetails/groupRelatedIssues/index.spec.tsx +++ b/static/app/views/issueDetails/groupRelatedIssues/index.spec.tsx @@ -6,7 +6,8 @@ import {render, screen} from 'sentry-test/reactTestingLibrary'; import {GroupRelatedIssues} from 'sentry/views/issueDetails/groupRelatedIssues'; describe('Related Issues View', function () { - let relatedIssuesMock: jest.Mock; + let sameRootIssuesMock: jest.Mock; + let traceIssuesMock: jest.Mock; let issuesMock: jest.Mock; const router = RouterFixture(); @@ -21,44 +22,20 @@ describe('Related Issues View', function () { const params = {groupId: groupId}; const errorType = 'RuntimeError'; const noData = { - data: [ - { - type: 'same_root_cause', - data: [], - }, - { - type: 'trace_connected', - data: [], - }, - ], + type: 'irrelevant', + data: [], }; const onlySameRootData = { - data: [ - { - type: 'same_root_cause', - data: [group1, group2], - }, - { - type: 'trace_connected', - data: [], - }, - ], + type: 'same_root_cause', + data: [group1, group2], }; const onlyTraceConnectedData = { - data: [ - { - type: 'same_root_cause', - data: [], - }, - { - type: 'trace_connected', - data: [group1, group2], - meta: { - event_id: 'abcd', - trace_id: '1234', - }, - }, - ], + type: 'trace_connected', + data: [group1, group2], + meta: { + event_id: 'abcd', + trace_id: '1234', + }, }; const issuesData = [ { @@ -99,8 +76,12 @@ describe('Related Issues View', function () { }); it('renders with no data', async function () { - relatedIssuesMock = MockApiClient.addMockResponse({ - url: `/issues/${groupId}/related-issues/`, + sameRootIssuesMock = MockApiClient.addMockResponse({ + url: `/issues/${groupId}/related-issues/?type=same_root_cause`, + body: noData, + }); + traceIssuesMock = MockApiClient.addMockResponse({ + url: `/issues/${groupId}/related-issues/?type=trace_connected`, body: noData, }); render( @@ -121,14 +102,19 @@ describe('Related Issues View', function () { await screen.findByText('No trace-connected related issues were found.') ).toBeInTheDocument(); - expect(relatedIssuesMock).toHaveBeenCalled(); + expect(sameRootIssuesMock).toHaveBeenCalled(); + expect(traceIssuesMock).toHaveBeenCalled(); }); it('renders with same root issues', async function () { - relatedIssuesMock = MockApiClient.addMockResponse({ - url: `/issues/${groupId}/related-issues/`, + sameRootIssuesMock = MockApiClient.addMockResponse({ + url: `/issues/${groupId}/related-issues/?type=same_root_cause`, body: onlySameRootData, }); + MockApiClient.addMockResponse({ + url: `/issues/${groupId}/related-issues/?type=trace_connected`, + body: [], + }); issuesMock = MockApiClient.addMockResponse({ url: orgIssuesEndpoint, body: issuesData, @@ -149,7 +135,7 @@ describe('Related Issues View', function () { expect(await screen.findByText(`EARTH-${group1}`)).toBeInTheDocument(); expect(await screen.findByText(`EARTH-${group2}`)).toBeInTheDocument(); - expect(relatedIssuesMock).toHaveBeenCalled(); + expect(sameRootIssuesMock).toHaveBeenCalled(); expect(issuesMock).toHaveBeenCalled(); expect( await screen.findByText('No trace-connected related issues were found.') @@ -163,8 +149,12 @@ describe('Related Issues View', function () { }); it('renders with trace connected issues', async function () { - relatedIssuesMock = MockApiClient.addMockResponse({ - url: `/issues/${groupId}/related-issues/`, + MockApiClient.addMockResponse({ + url: `/issues/${groupId}/related-issues/?type=same_root_cause`, + body: [], + }); + traceIssuesMock = MockApiClient.addMockResponse({ + url: `/issues/${groupId}/related-issues/?type=trace_connected`, body: onlyTraceConnectedData, }); issuesMock = MockApiClient.addMockResponse({ @@ -186,7 +176,7 @@ describe('Related Issues View', function () { expect(await screen.findByText(`EARTH-${group1}`)).toBeInTheDocument(); expect(await screen.findByText(`EARTH-${group2}`)).toBeInTheDocument(); - expect(relatedIssuesMock).toHaveBeenCalled(); + expect(traceIssuesMock).toHaveBeenCalled(); expect(issuesMock).toHaveBeenCalled(); expect( await screen.findByText('No same-root-cause related issues were found.') diff --git a/static/app/views/issueDetails/groupRelatedIssues/index.tsx b/static/app/views/issueDetails/groupRelatedIssues/index.tsx index 0b94bf16281a9c..5364e4b740e119 100644 --- a/static/app/views/issueDetails/groupRelatedIssues/index.tsx +++ b/static/app/views/issueDetails/groupRelatedIssues/index.tsx @@ -19,59 +19,109 @@ type RouteParams = { type Props = RouteComponentProps; type RelatedIssuesResponse = { - data: [ - { - data: number[]; - meta: { - event_id: string; - trace_id: string; - }; - type: string; - }, - ]; + data: number[]; + meta: { + event_id: string; + trace_id: string; + }; + type: string; }; +interface RelatedIssuesSectionProps { + groupId: string; + orgSlug: string; + relationType: string; +} + function GroupRelatedIssues({params}: Props) { const {groupId} = params; const organization = useOrganization(); const orgSlug = organization.slug; + return ( + + + + + ); +} + +function RelatedIssuesSection({ + groupId, + orgSlug, + relationType, +}: RelatedIssuesSectionProps) { // Fetch the list of related issues const { isLoading, isError, data: relatedIssues, refetch, - } = useApiQuery([`/issues/${groupId}/related-issues/`], { - staleTime: 0, - }); - - let traceMeta = { - trace_id: '', - event_id: '', - }; - const { - same_root_cause: sameRootCauseIssues = [], - trace_connected: traceConnectedIssues = [], - } = (relatedIssues?.data ?? []).reduce( - (mapping, item) => { - if (item.type === 'trace_connected') { - traceMeta = {...item.meta}; - } - const issuesList = item.data; - mapping[item.type] = issuesList; - return mapping; - }, - {same_root_cause: [], trace_connected: []} + } = useApiQuery( + [`/issues/${groupId}/related-issues/?type=${relationType}`], + { + staleTime: 0, + } ); + const traceMeta = relationType === 'trace_connected' ? relatedIssues?.meta : undefined; + const issues = relatedIssues?.data ?? []; + const query = `issue.id:[${issues}]`; // project=-1 allows ensuring that the query will show issues from any projects for the org // This is important for traces since issues can be for any project in the org const baseUrl = `/organizations/${orgSlug}/issues/?project=-1`; + let title; + let linkToTrace; + let openIssuesButton; + if (relationType === 'trace_connected' && traceMeta) { + title = t('Issues in the same trace'); + linkToTrace = ( + + {t('These issues were all found within ')} + + {t('this trace')} + + . + + ); + openIssuesButton = ( + + {t('Open in Issues')} + + ); + } else { + title = t('Issues caused by the same root cause'); + openIssuesButton = ( + + {t('Open in Issues')} + + ); + } return ( - + + {title} {isLoading ? ( ) : isError ? ( @@ -79,77 +129,26 @@ function GroupRelatedIssues({params}: Props) { message={t('Unable to load related issues, please try again later')} onRetry={refetch} /> - ) : ( + ) : issues.length > 0 ? ( -
- - {t('Issues caused by the same root cause')} - {sameRootCauseIssues.length > 0 ? ( -
- -
- - {t('Open in Issues')} - - - -
- ) : ( - {t('No same-root-cause related issues were found.')} - )} - -
-
- - {t('Issues in the same trace')} - {traceConnectedIssues.length > 0 ? ( -
- - - {t('These issues were all found within ')} - - {t('this trace')} - - . - - - {t('Open in Issues')} - - - -
- ) : ( - {t('No trace-connected related issues were found.')} - )} -
-
+ + {linkToTrace ?? null} + {openIssuesButton ?? null} + + + ) : relationType === 'trace_connected' ? ( + {t('No trace-connected related issues were found.')} + ) : ( + {t('No same-root-cause related issues were found.')} )} - +
); }