utils.tsx 5.7 KB

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