utils.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {Fragment} from 'react';
  2. import {Alert} from 'sentry/components/alert';
  3. import ExternalLink from 'sentry/components/links/externalLink';
  4. import {t, tct, tn} from 'sentry/locale';
  5. import type {IgnoredStatusDetails} from 'sentry/types/group';
  6. import {capitalize} from 'sentry/utils/string/capitalize';
  7. import commonTheme from 'sentry/utils/theme';
  8. import ExtraDescription from './extraDescription';
  9. export const BULK_LIMIT = 1000;
  10. export const BULK_LIMIT_STR = BULK_LIMIT.toLocaleString();
  11. export enum ConfirmAction {
  12. RESOLVE = 'resolve',
  13. UNRESOLVE = 'unresolve',
  14. ARCHIVE = 'archive',
  15. BOOKMARK = 'bookmark',
  16. UNBOOKMARK = 'unbookmark',
  17. MERGE = 'merge',
  18. DELETE = 'delete',
  19. SET_PRIORITY = 'reprioritize',
  20. }
  21. function getBulkConfirmMessage(action: string, queryCount: number) {
  22. if (queryCount > BULK_LIMIT) {
  23. return tct(
  24. 'Are you sure you want to [action] the first [bulkNumber] issues that match the search?',
  25. {
  26. action,
  27. bulkNumber: BULK_LIMIT_STR,
  28. }
  29. );
  30. }
  31. return tct(
  32. 'Are you sure you want to [action] all [bulkNumber] issues that match the search?',
  33. {
  34. action,
  35. bulkNumber: queryCount,
  36. }
  37. );
  38. }
  39. function PerformanceIssueAlert({
  40. allInQuerySelected,
  41. children,
  42. }: {
  43. allInQuerySelected: boolean;
  44. children: string;
  45. }) {
  46. if (!allInQuerySelected) {
  47. return null;
  48. }
  49. return (
  50. <Alert type="info" showIcon>
  51. {children}
  52. </Alert>
  53. );
  54. }
  55. export function getConfirm({
  56. numIssues,
  57. allInQuerySelected,
  58. query,
  59. queryCount,
  60. }: {
  61. allInQuerySelected: boolean;
  62. numIssues: number;
  63. query: string;
  64. queryCount: number;
  65. }) {
  66. return function ({
  67. action,
  68. canBeUndone,
  69. append = '',
  70. }: {
  71. action: ConfirmAction;
  72. canBeUndone: boolean;
  73. append?: string;
  74. }) {
  75. const question = allInQuerySelected
  76. ? getBulkConfirmMessage(`${action}${append}`, queryCount)
  77. : tn(
  78. // Use sprintf argument swapping since the number value must come
  79. // first. See https://github.com/alexei/sprintf.js#argument-swapping
  80. `Are you sure you want to %2$s this %s issue%3$s?`,
  81. `Are you sure you want to %2$s these %s issues%3$s?`,
  82. numIssues,
  83. action,
  84. append
  85. );
  86. let message: React.ReactNode;
  87. switch (action) {
  88. case ConfirmAction.DELETE:
  89. message = (
  90. <Fragment>
  91. <p>
  92. {tct(
  93. 'Bulk deletion is only recommended for junk data. To clear your stream, consider resolving or ignoring. [link:When should I delete events?]',
  94. {
  95. link: (
  96. <ExternalLink href="https://sentry.zendesk.com/hc/en-us/articles/23813143627675-When-should-I-delete-events" />
  97. ),
  98. }
  99. )}
  100. </p>
  101. <PerformanceIssueAlert allInQuerySelected={allInQuerySelected}>
  102. {t('Deleting performance issues is not yet supported and will be skipped.')}
  103. </PerformanceIssueAlert>
  104. </Fragment>
  105. );
  106. break;
  107. case ConfirmAction.MERGE:
  108. message = (
  109. <Fragment>
  110. <p>{t('Note that unmerging is currently an experimental feature.')}</p>
  111. <PerformanceIssueAlert allInQuerySelected={allInQuerySelected}>
  112. {t('Merging performance issues is not yet supported and will be skipped.')}
  113. </PerformanceIssueAlert>
  114. </Fragment>
  115. );
  116. break;
  117. default:
  118. message = !canBeUndone ? <p>{t('This action cannot be undone.')}</p> : null;
  119. }
  120. return (
  121. <div>
  122. <p style={{marginBottom: '20px'}}>
  123. <strong>{question}</strong>
  124. </p>
  125. <ExtraDescription
  126. all={allInQuerySelected}
  127. query={query}
  128. queryCount={queryCount}
  129. />
  130. {message}
  131. </div>
  132. );
  133. };
  134. }
  135. export function getLabel(numIssues: number, allInQuerySelected: boolean) {
  136. return function (action: string, append = '') {
  137. const capitalized = capitalize(action);
  138. const text = allInQuerySelected
  139. ? t('Bulk %s issues', action)
  140. : // Use sprintf argument swapping to put the capitalized string first. See
  141. // https://github.com/alexei/sprintf.js#argument-swapping
  142. tn(`%2$s %s selected issue`, `%2$s %s selected issues`, numIssues, capitalized);
  143. return text + append;
  144. };
  145. }
  146. export function performanceIssuesSupportsIgnoreAction(
  147. statusDetails: IgnoredStatusDetails
  148. ) {
  149. return !(statusDetails.ignoreWindow || statusDetails.ignoreUserWindow);
  150. }
  151. // A mapping of which screen sizes will trigger the column to disappear
  152. // e.g. 'Trend': screen.small => 'Trend' column will disappear on screen.small widths
  153. export const COLUMN_BREAKPOINTS = {
  154. ISSUE: undefined, // Issue column is always visible
  155. TREND: commonTheme.breakpoints.small,
  156. AGE: commonTheme.breakpoints.xlarge,
  157. SEEN: commonTheme.breakpoints.xlarge,
  158. EVENTS: commonTheme.breakpoints.medium,
  159. USERS: commonTheme.breakpoints.medium,
  160. PRIORITY: commonTheme.breakpoints.large,
  161. ASSIGNEE: commonTheme.breakpoints.xsmall,
  162. };