utils.tsx 6.7 KB

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