utils.tsx 5.8 KB

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