errorMessage.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {Fragment} from 'react';
  2. import {Alert} from 'sentry/components/alert';
  3. import {Button} from 'sentry/components/button';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import EmptyMessage from 'sentry/components/emptyMessage';
  6. import FeatureBadge from 'sentry/components/featureBadge';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import {Panel} from 'sentry/components/panels';
  9. import {t, tct} from 'sentry/locale';
  10. import {Group, Organization, Project} from 'sentry/types';
  11. type ErrorCode =
  12. | 'issue_not_hierarchical'
  13. | 'project_not_hierarchical'
  14. | 'no_events'
  15. | 'merged_issues'
  16. | 'missing_feature';
  17. type Error = {
  18. status: number;
  19. responseJSON?: {
  20. detail: {
  21. code: ErrorCode;
  22. extra: Record<string, any>;
  23. message: string;
  24. };
  25. };
  26. };
  27. type Props = {
  28. error: Error | string;
  29. groupId: Group['id'];
  30. hasProjectWriteAccess: boolean;
  31. onRetry: () => void;
  32. orgSlug: Organization['slug'];
  33. projSlug: Project['slug'];
  34. className?: string;
  35. };
  36. function ErrorMessage({
  37. error,
  38. groupId,
  39. onRetry,
  40. orgSlug,
  41. projSlug,
  42. hasProjectWriteAccess,
  43. className,
  44. }: Props) {
  45. function getErrorDetails(errorCode: ErrorCode) {
  46. switch (errorCode) {
  47. case 'merged_issues':
  48. return {
  49. title: t('Grouping breakdown is not available in this issue'),
  50. subTitle: t(
  51. 'This issue needs to be fully unmerged before grouping breakdown is available'
  52. ),
  53. action: (
  54. <Button
  55. priority="primary"
  56. to={`/organizations/${orgSlug}/issues/${groupId}/merged/?${location.search}`}
  57. >
  58. {t('Unmerge issue')}
  59. </Button>
  60. ),
  61. };
  62. case 'missing_feature':
  63. return {
  64. title: t(
  65. 'This project does not have the grouping breakdown available. Is your organization still an early adopter?'
  66. ),
  67. };
  68. case 'no_events':
  69. return {
  70. title: t('This issue has no events'),
  71. };
  72. case 'issue_not_hierarchical':
  73. return {
  74. title: t('Grouping breakdown is not available in this issue'),
  75. subTitle: t(
  76. 'Only new issues with the latest grouping strategy have this feature available'
  77. ),
  78. };
  79. case 'project_not_hierarchical':
  80. return {
  81. title: (
  82. <Fragment>
  83. {t('Update your Grouping Config')}
  84. <FeatureBadge type="beta" />
  85. </Fragment>
  86. ),
  87. subTitle: (
  88. <Fragment>
  89. <p>
  90. {t(
  91. 'Enable advanced grouping insights and functionality by updating this project to the latest Grouping Config:'
  92. )}
  93. </p>
  94. <ul>
  95. <li>
  96. {tct(
  97. '[strong:Breakdowns:] Explore events in this issue by call hierarchy.',
  98. {strong: <strong />}
  99. )}
  100. </li>
  101. <li>
  102. {tct(
  103. '[strong:Stack trace annotations:] See important frames Sentry uses to group issues directly in the stack trace.',
  104. {strong: <strong />}
  105. )}
  106. </li>
  107. </ul>
  108. </Fragment>
  109. ),
  110. leftAligned: true,
  111. action: (
  112. <ButtonBar gap={1}>
  113. <Button
  114. priority="primary"
  115. to={`/settings/${orgSlug}/projects/${projSlug}/issue-grouping/#upgrade-grouping`}
  116. disabled={!hasProjectWriteAccess}
  117. title={
  118. !hasProjectWriteAccess
  119. ? t('You do not have permission to update this project')
  120. : undefined
  121. }
  122. >
  123. {t('Upgrade Grouping Strategy')}
  124. </Button>
  125. <Button href="https://docs.sentry.io/product/data-management-settings/event-grouping/grouping-breakdown/">
  126. {t('Read the docs')}
  127. </Button>
  128. </ButtonBar>
  129. ),
  130. };
  131. default:
  132. return {};
  133. }
  134. }
  135. if (typeof error === 'string') {
  136. return (
  137. <Alert type="warning" className={className}>
  138. {error}
  139. </Alert>
  140. );
  141. }
  142. if (error.status === 403 && error.responseJSON?.detail) {
  143. const {code, message} = error.responseJSON.detail;
  144. const {action, title, subTitle, leftAligned} = getErrorDetails(code);
  145. return (
  146. <Panel className={className}>
  147. <EmptyMessage
  148. size="large"
  149. title={title ?? message}
  150. description={subTitle}
  151. action={action}
  152. leftAligned={leftAligned}
  153. />
  154. </Panel>
  155. );
  156. }
  157. return (
  158. <LoadingError
  159. message={t('Unable to load grouping levels, please try again later')}
  160. onRetry={onRetry}
  161. className={className}
  162. />
  163. );
  164. }
  165. export default ErrorMessage;