index.tsx 4.4 KB

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