index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import {Fragment} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {LinkButton} from 'sentry/components/button';
  5. import GroupList from 'sentry/components/issues/groupList';
  6. import Link from 'sentry/components/links/link';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {useApiQuery} from 'sentry/utils/queryClient';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. type RouteParams = {
  14. groupId: string;
  15. };
  16. type Props = RouteComponentProps<RouteParams, {}>;
  17. type RelatedIssuesResponse = {
  18. data: [
  19. {
  20. data: number[];
  21. meta: {
  22. event_id: string;
  23. trace_id: string;
  24. };
  25. type: string;
  26. },
  27. ];
  28. };
  29. function GroupRelatedIssues({params}: Props) {
  30. const {groupId} = params;
  31. const organization = useOrganization();
  32. const orgSlug = organization.slug;
  33. // Fetch the list of related issues
  34. const {
  35. isLoading,
  36. isError,
  37. data: relatedIssues,
  38. refetch,
  39. } = useApiQuery<RelatedIssuesResponse>([`/issues/${groupId}/related-issues/`], {
  40. staleTime: 0,
  41. });
  42. let traceMeta = {
  43. trace_id: '',
  44. event_id: '',
  45. };
  46. const {
  47. same_root_cause: sameRootCauseIssues = [],
  48. trace_connected: traceConnectedIssues = [],
  49. } = (relatedIssues?.data ?? []).reduce(
  50. (mapping, item) => {
  51. if (item.type === 'trace_connected') {
  52. traceMeta = {...item.meta};
  53. }
  54. const issuesList = item.data;
  55. mapping[item.type] = issuesList;
  56. return mapping;
  57. },
  58. {same_root_cause: [], trace_connected: []}
  59. );
  60. // project=-1 allows ensuring that the query will show issues from any projects for the org
  61. // This is important for traces since issues can be for any project in the org
  62. const baseUrl = `/organizations/${orgSlug}/issues/?project=-1`;
  63. return (
  64. <Fragment>
  65. {isLoading ? (
  66. <LoadingIndicator />
  67. ) : isError ? (
  68. <LoadingError
  69. message={t('Unable to load related issues, please try again later')}
  70. onRetry={refetch}
  71. />
  72. ) : (
  73. <Fragment>
  74. <div>
  75. <HeaderWrapper>
  76. <Title>{t('Issues caused by the same root cause')}</Title>
  77. {sameRootCauseIssues.length > 0 ? (
  78. <div>
  79. <TextButtonWrapper>
  80. <div />
  81. <LinkButton
  82. to={`${baseUrl}&query=issue.id:[${groupId},${sameRootCauseIssues}]`}
  83. size="xs"
  84. analyticsEventName="Clicked Open Issues from same-root related issues"
  85. analyticsEventKey="similar_issues.same_root_cause_clicked_open_issues"
  86. >
  87. {t('Open in Issues')}
  88. </LinkButton>
  89. </TextButtonWrapper>
  90. <GroupList
  91. orgSlug={orgSlug}
  92. queryParams={{query: `issue.id:[${sameRootCauseIssues}]`}}
  93. source="similar-issues-tab"
  94. canSelectGroups={false}
  95. withChart={false}
  96. />
  97. </div>
  98. ) : (
  99. <small>{t('No same-root-cause related issues were found.')}</small>
  100. )}
  101. </HeaderWrapper>
  102. </div>
  103. <div>
  104. <HeaderWrapper>
  105. <Title>{t('Issues in the same trace')}</Title>
  106. {traceConnectedIssues.length > 0 ? (
  107. <div>
  108. <TextButtonWrapper>
  109. <small>
  110. {t('These issues were all found within ')}
  111. <Link
  112. to={`/organizations/${orgSlug}/performance/trace/${traceMeta.trace_id}/?node=error-${traceMeta.event_id}`}
  113. >
  114. {t('this trace')}
  115. </Link>
  116. .
  117. </small>
  118. <LinkButton
  119. to={`${baseUrl}&query=trace:${traceMeta.trace_id}`}
  120. size="xs"
  121. analyticsEventName="Clicked Open Issues from trace-connected related issues"
  122. analyticsEventKey="similar_issues.trace_connected_issues_clicked_open_issues"
  123. >
  124. {t('Open in Issues')}
  125. </LinkButton>
  126. </TextButtonWrapper>
  127. <GroupList
  128. orgSlug={orgSlug}
  129. queryParams={{query: `issue.id:[${traceConnectedIssues}]`}}
  130. source="similar-issues-tab"
  131. canSelectGroups={false}
  132. withChart={false}
  133. />
  134. </div>
  135. ) : (
  136. <small>{t('No trace-connected related issues were found.')}</small>
  137. )}
  138. </HeaderWrapper>
  139. </div>
  140. </Fragment>
  141. )}
  142. </Fragment>
  143. );
  144. }
  145. // Export the component without feature flag controls
  146. export {GroupRelatedIssues};
  147. const Title = styled('h4')`
  148. margin-bottom: ${space(0.75)};
  149. `;
  150. const HeaderWrapper = styled('div')`
  151. margin-bottom: ${space(2)};
  152. small {
  153. color: ${p => p.theme.subText};
  154. }
  155. `;
  156. const TextButtonWrapper = styled('div')`
  157. display: flex;
  158. justify-content: space-between;
  159. margin-bottom: ${space(1)};
  160. `;