utils.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import ExternalLink from 'sentry/components/links/externalLink';
  2. import {DEFAULT_QUERY, NEW_DEFAULT_QUERY} from 'sentry/constants';
  3. import {t, tct} from 'sentry/locale';
  4. import type {Organization} from 'sentry/types/organization';
  5. export enum Query {
  6. FOR_REVIEW = 'is:unresolved is:for_review assigned_or_suggested:[me, my_teams, none]',
  7. // biome-ignore lint/style/useLiteralEnumMembers: Disable for maintenance cost.
  8. PRIORITIZED = NEW_DEFAULT_QUERY,
  9. UNRESOLVED = 'is:unresolved',
  10. IGNORED = 'is:ignored',
  11. NEW = 'is:new',
  12. ARCHIVED = 'is:archived',
  13. ESCALATING = 'is:escalating',
  14. REGRESSED = 'is:regressed',
  15. REPROCESSING = 'is:reprocessing',
  16. }
  17. export const CUSTOM_TAB_VALUE = '__custom__';
  18. type OverviewTab = {
  19. /**
  20. * Emitted analytics event tab name
  21. */
  22. analyticsName: string;
  23. /**
  24. * Will fetch a count to display on this tab
  25. */
  26. count: boolean;
  27. /**
  28. * Tabs can be disabled via flag
  29. */
  30. enabled: boolean;
  31. name: string;
  32. hidden?: boolean;
  33. /**
  34. * Tooltip text to be hoverable when text has links
  35. */
  36. tooltipHoverable?: boolean;
  37. /**
  38. * Tooltip text for each tab
  39. */
  40. tooltipTitle?: React.ReactNode;
  41. };
  42. /**
  43. * Get a list of currently active tabs
  44. */
  45. export function getTabs(organization: Organization) {
  46. const hasIssuePriority = organization.features.includes('issue-priority-ui');
  47. const tabs: Array<[string, OverviewTab]> = [
  48. [
  49. Query.PRIORITIZED,
  50. {
  51. name: t('Prioritized'),
  52. analyticsName: 'prioritized',
  53. count: true,
  54. enabled: hasIssuePriority,
  55. },
  56. ],
  57. [
  58. Query.UNRESOLVED,
  59. {
  60. name: t('Unresolved'),
  61. analyticsName: 'unresolved',
  62. count: true,
  63. enabled: !hasIssuePriority,
  64. },
  65. ],
  66. [
  67. Query.FOR_REVIEW,
  68. {
  69. name: t('For Review'),
  70. analyticsName: 'needs_review',
  71. count: true,
  72. enabled: true,
  73. tooltipTitle: t(
  74. 'Issues are marked for review if they are new or escalating, and have not been resolved or archived. Issues are automatically marked reviewed in 7 days.'
  75. ),
  76. },
  77. ],
  78. [
  79. Query.REGRESSED,
  80. {
  81. name: t('Regressed'),
  82. analyticsName: 'regressed',
  83. count: true,
  84. enabled: true,
  85. },
  86. ],
  87. [
  88. Query.ESCALATING,
  89. {
  90. name: t('Escalating'),
  91. analyticsName: 'escalating',
  92. count: true,
  93. enabled: true,
  94. },
  95. ],
  96. [
  97. Query.ARCHIVED,
  98. {
  99. name: t('Archived'),
  100. analyticsName: 'archived',
  101. count: true,
  102. enabled: true,
  103. },
  104. ],
  105. [
  106. Query.IGNORED,
  107. {
  108. name: t('Ignored'),
  109. analyticsName: 'ignored',
  110. count: true,
  111. enabled: false,
  112. tooltipTitle: t(`Ignored issues don’t trigger alerts. When their ignore
  113. conditions are met they become Unresolved and are flagged for review.`),
  114. },
  115. ],
  116. [
  117. Query.REPROCESSING,
  118. {
  119. name: t('Reprocessing'),
  120. analyticsName: 'reprocessing',
  121. count: true,
  122. enabled: organization.features.includes('reprocessing-v2'),
  123. tooltipTitle: tct(
  124. `These [link:reprocessing issues] will take some time to complete.
  125. Any new issues that are created during reprocessing will be flagged for review.`,
  126. {
  127. link: (
  128. <ExternalLink href="https://docs.sentry.io/product/error-monitoring/reprocessing/" />
  129. ),
  130. }
  131. ),
  132. tooltipHoverable: true,
  133. },
  134. ],
  135. [
  136. // Hidden tab to account for custom queries that don't match any of the queries
  137. // above. It's necessary because if Tabs's value doesn't match that of any tab item
  138. // then Tabs will fall back to a default value, causing unexpected behaviors.
  139. CUSTOM_TAB_VALUE,
  140. {
  141. name: t('Custom'),
  142. analyticsName: 'custom',
  143. hidden: true,
  144. count: false,
  145. enabled: true,
  146. },
  147. ],
  148. ];
  149. return tabs.filter(([_query, tab]) => tab.enabled);
  150. }
  151. /**
  152. * @returns queries that should have counts fetched
  153. */
  154. export function getTabsWithCounts(organization: Organization) {
  155. const tabs = getTabs(organization);
  156. return tabs.filter(([_query, tab]) => tab.count).map(([query]) => query);
  157. }
  158. export function isForReviewQuery(query: string | undefined) {
  159. return !!query && /\bis:for_review\b/.test(query);
  160. }
  161. // the tab counts will look like 99+
  162. export const TAB_MAX_COUNT = 99;
  163. type QueryCount = {
  164. count: number;
  165. hasMore: boolean;
  166. };
  167. export type QueryCounts = Partial<Record<Query, QueryCount>>;
  168. export enum IssueSortOptions {
  169. DATE = 'date',
  170. NEW = 'new',
  171. TRENDS = 'trends',
  172. FREQ = 'freq',
  173. USER = 'user',
  174. INBOX = 'inbox',
  175. }
  176. export const DEFAULT_ISSUE_STREAM_SORT = IssueSortOptions.DATE;
  177. export function isDefaultIssueStreamSearch(
  178. {query, sort}: {query: string; sort: string},
  179. {organization}: {organization: Organization}
  180. ) {
  181. const defaultQuery = organization.features.includes('issue-priority-ui')
  182. ? NEW_DEFAULT_QUERY
  183. : DEFAULT_QUERY;
  184. return query === defaultQuery && sort === DEFAULT_ISSUE_STREAM_SORT;
  185. }
  186. export function getSortLabel(key: string) {
  187. switch (key) {
  188. case IssueSortOptions.NEW:
  189. return t('First Seen');
  190. case IssueSortOptions.TRENDS:
  191. return t('Trends');
  192. case IssueSortOptions.FREQ:
  193. return t('Events');
  194. case IssueSortOptions.USER:
  195. return t('Users');
  196. case IssueSortOptions.INBOX:
  197. return t('Date Added');
  198. case IssueSortOptions.DATE:
  199. default:
  200. return t('Last Seen');
  201. }
  202. }
  203. export const DISCOVER_EXCLUSION_FIELDS: string[] = [
  204. 'query',
  205. 'status',
  206. 'bookmarked_by',
  207. 'assigned',
  208. 'assigned_to',
  209. 'unassigned',
  210. 'subscribed_by',
  211. 'active_at',
  212. 'first_release',
  213. 'first_seen',
  214. 'is',
  215. '__text',
  216. ];
  217. export const FOR_REVIEW_QUERIES: string[] = [Query.FOR_REVIEW];
  218. export const SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY =
  219. 'issue-stream-saved-searches-sidebar-open';