group.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  1. import type {LocationDescriptor} from 'history';
  2. import type {SearchGroup} from 'sentry/components/deprecatedSmartSearchBar/types';
  3. import type {TitledPlugin} from 'sentry/components/group/pluginActions';
  4. import type {FieldKind} from 'sentry/utils/fields';
  5. import type {Actor, TimeseriesValue} from './core';
  6. import type {Event, EventMetadata, EventOrGroupType, Level} from './event';
  7. import type {
  8. Commit,
  9. ExternalIssue,
  10. PlatformExternalIssue,
  11. PullRequest,
  12. Repository,
  13. } from './integrations';
  14. import type {Team} from './organization';
  15. import type {PlatformKey, Project} from './project';
  16. import type {AvatarUser, User} from './user';
  17. export type EntryData = Record<string, any | any[]>;
  18. /**
  19. * Saved issues searches
  20. */
  21. export type RecentSearch = {
  22. dateCreated: string;
  23. id: string;
  24. lastSeen: string;
  25. organizationId: string;
  26. query: string;
  27. type: SavedSearchType;
  28. };
  29. // XXX: Deprecated Sentry 9 attributes are not included here.
  30. export type SavedSearch = {
  31. dateCreated: string;
  32. id: string;
  33. isGlobal: boolean;
  34. isPinned: boolean;
  35. name: string;
  36. query: string;
  37. sort: string;
  38. type: SavedSearchType;
  39. visibility: SavedSearchVisibility;
  40. };
  41. export enum SavedSearchVisibility {
  42. ORGANIZATION = 'organization',
  43. OWNER = 'owner',
  44. OWNER_PINNED = 'owner_pinned',
  45. }
  46. export enum SavedSearchType {
  47. ISSUE = 0,
  48. EVENT = 1,
  49. SESSION = 2,
  50. REPLAY = 3,
  51. METRIC = 4,
  52. SPAN = 5,
  53. ERROR = 6,
  54. TRANSACTION = 7,
  55. }
  56. export enum IssueCategory {
  57. PERFORMANCE = 'performance',
  58. ERROR = 'error',
  59. CRON = 'cron',
  60. REPLAY = 'replay',
  61. UPTIME = 'uptime',
  62. METRIC_ALERT = 'metric_alert',
  63. }
  64. export enum IssueType {
  65. // Error
  66. ERROR = 'error',
  67. // Performance
  68. PERFORMANCE_CONSECUTIVE_DB_QUERIES = 'performance_consecutive_db_queries',
  69. PERFORMANCE_CONSECUTIVE_HTTP = 'performance_consecutive_http',
  70. PERFORMANCE_FILE_IO_MAIN_THREAD = 'performance_file_io_main_thread',
  71. PERFORMANCE_DB_MAIN_THREAD = 'performance_db_main_thread',
  72. PERFORMANCE_N_PLUS_ONE_API_CALLS = 'performance_n_plus_one_api_calls',
  73. PERFORMANCE_N_PLUS_ONE_DB_QUERIES = 'performance_n_plus_one_db_queries',
  74. PERFORMANCE_SLOW_DB_QUERY = 'performance_slow_db_query',
  75. PERFORMANCE_RENDER_BLOCKING_ASSET = 'performance_render_blocking_asset_span',
  76. PERFORMANCE_UNCOMPRESSED_ASSET = 'performance_uncompressed_assets',
  77. PERFORMANCE_LARGE_HTTP_PAYLOAD = 'performance_large_http_payload',
  78. PERFORMANCE_HTTP_OVERHEAD = 'performance_http_overhead',
  79. PERFORMANCE_DURATION_REGRESSION = 'performance_duration_regression',
  80. PERFORMANCE_ENDPOINT_REGRESSION = 'performance_p95_endpoint_regression',
  81. // Profile
  82. PROFILE_FILE_IO_MAIN_THREAD = 'profile_file_io_main_thread',
  83. PROFILE_IMAGE_DECODE_MAIN_THREAD = 'profile_image_decode_main_thread',
  84. PROFILE_JSON_DECODE_MAIN_THREAD = 'profile_json_decode_main_thread',
  85. PROFILE_REGEX_MAIN_THREAD = 'profile_regex_main_thread',
  86. PROFILE_FRAME_DROP = 'profile_frame_drop',
  87. PROFILE_FRAME_DROP_EXPERIMENTAL = 'profile_frame_drop_experimental',
  88. PROFILE_FUNCTION_REGRESSION = 'profile_function_regression',
  89. PROFILE_FUNCTION_REGRESSION_EXPERIMENTAL = 'profile_function_regression_exp',
  90. // Replay
  91. REPLAY_RAGE_CLICK = 'replay_click_rage',
  92. REPLAY_HYDRATION_ERROR = 'replay_hydration_error',
  93. }
  94. // Update this if adding an issue type that you don't want to show up in search!
  95. export const VISIBLE_ISSUE_TYPES = Object.values(IssueType).filter(
  96. type =>
  97. ![
  98. IssueType.PROFILE_FRAME_DROP_EXPERIMENTAL,
  99. IssueType.PROFILE_FUNCTION_REGRESSION_EXPERIMENTAL,
  100. ].includes(type)
  101. );
  102. export enum IssueTitle {
  103. ERROR = 'Error',
  104. // Performance
  105. PERFORMANCE_CONSECUTIVE_DB_QUERIES = 'Consecutive DB Queries',
  106. PERFORMANCE_CONSECUTIVE_HTTP = 'Consecutive HTTP',
  107. PERFORMANCE_FILE_IO_MAIN_THREAD = 'File IO on Main Thread',
  108. PERFORMANCE_DB_MAIN_THREAD = 'DB on Main Thread',
  109. PERFORMANCE_N_PLUS_ONE_API_CALLS = 'N+1 API Call',
  110. PERFORMANCE_N_PLUS_ONE_DB_QUERIES = 'N+1 Query',
  111. PERFORMANCE_SLOW_DB_QUERY = 'Slow DB Query',
  112. PERFORMANCE_RENDER_BLOCKING_ASSET = 'Large Render Blocking Asset',
  113. PERFORMANCE_UNCOMPRESSED_ASSET = 'Uncompressed Asset',
  114. PERFORMANCE_LARGE_HTTP_PAYLOAD = 'Large HTTP payload',
  115. PERFORMANCE_HTTP_OVERHEAD = 'HTTP/1.1 Overhead',
  116. PERFORMANCE_DURATION_REGRESSION = 'Duration Regression',
  117. PERFORMANCE_ENDPOINT_REGRESSION = 'Endpoint Regression',
  118. // Profile
  119. PROFILE_FILE_IO_MAIN_THREAD = 'File I/O on Main Thread',
  120. PROFILE_IMAGE_DECODE_MAIN_THREAD = 'Image Decoding on Main Thread',
  121. PROFILE_JSON_DECODE_MAIN_THREAD = 'JSON Decoding on Main Thread',
  122. PROFILE_REGEX_MAIN_THREAD = 'Regex on Main Thread',
  123. PROFILE_FRAME_DROP = 'Frame Drop',
  124. PROFILE_FUNCTION_REGRESSION = 'Function Regression',
  125. PROFILE_FUNCTION_REGRESSION_EXPERIMENTAL = 'Function Duration Regression (Experimental)',
  126. // Replay
  127. REPLAY_RAGE_CLICK = 'Rage Click Detected',
  128. REPLAY_HYDRATION_ERROR = 'Hydration Error Detected',
  129. }
  130. const ISSUE_TYPE_TO_ISSUE_TITLE = {
  131. error: IssueTitle.ERROR,
  132. performance_consecutive_db_queries: IssueTitle.PERFORMANCE_CONSECUTIVE_DB_QUERIES,
  133. performance_consecutive_http: IssueTitle.PERFORMANCE_CONSECUTIVE_HTTP,
  134. performance_file_io_main_thread: IssueTitle.PERFORMANCE_FILE_IO_MAIN_THREAD,
  135. performance_db_main_thread: IssueTitle.PERFORMANCE_DB_MAIN_THREAD,
  136. performance_n_plus_one_api_calls: IssueTitle.PERFORMANCE_N_PLUS_ONE_API_CALLS,
  137. performance_n_plus_one_db_queries: IssueTitle.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
  138. performance_slow_db_query: IssueTitle.PERFORMANCE_SLOW_DB_QUERY,
  139. performance_render_blocking_asset_span: IssueTitle.PERFORMANCE_RENDER_BLOCKING_ASSET,
  140. performance_uncompressed_assets: IssueTitle.PERFORMANCE_UNCOMPRESSED_ASSET,
  141. performance_large_http_payload: IssueTitle.PERFORMANCE_LARGE_HTTP_PAYLOAD,
  142. performance_http_overhead: IssueTitle.PERFORMANCE_HTTP_OVERHEAD,
  143. performance_duration_regression: IssueTitle.PERFORMANCE_DURATION_REGRESSION,
  144. performance_p95_endpoint_regression: IssueTitle.PERFORMANCE_ENDPOINT_REGRESSION,
  145. profile_file_io_main_thread: IssueTitle.PROFILE_FILE_IO_MAIN_THREAD,
  146. profile_image_decode_main_thread: IssueTitle.PROFILE_IMAGE_DECODE_MAIN_THREAD,
  147. profile_json_decode_main_thread: IssueTitle.PROFILE_JSON_DECODE_MAIN_THREAD,
  148. profile_regex_main_thread: IssueTitle.PROFILE_REGEX_MAIN_THREAD,
  149. profile_frame_drop: IssueTitle.PROFILE_FRAME_DROP,
  150. profile_frame_drop_experimental: IssueTitle.PROFILE_FRAME_DROP,
  151. profile_function_regression: IssueTitle.PROFILE_FUNCTION_REGRESSION,
  152. profile_function_regression_exp: IssueTitle.PROFILE_FUNCTION_REGRESSION_EXPERIMENTAL,
  153. replay_click_rage: IssueTitle.REPLAY_RAGE_CLICK,
  154. replay_hydration_error: IssueTitle.REPLAY_HYDRATION_ERROR,
  155. };
  156. export function getIssueTitleFromType(issueType: string): IssueTitle | undefined {
  157. if (issueType in ISSUE_TYPE_TO_ISSUE_TITLE) {
  158. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  159. return ISSUE_TYPE_TO_ISSUE_TITLE[issueType];
  160. }
  161. return undefined;
  162. }
  163. const OCCURRENCE_TYPE_TO_ISSUE_TYPE = {
  164. 1001: IssueType.PERFORMANCE_SLOW_DB_QUERY,
  165. 1004: IssueType.PERFORMANCE_RENDER_BLOCKING_ASSET,
  166. 1006: IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
  167. 1007: IssueType.PERFORMANCE_CONSECUTIVE_DB_QUERIES,
  168. 1008: IssueType.PERFORMANCE_FILE_IO_MAIN_THREAD,
  169. 1009: IssueType.PERFORMANCE_CONSECUTIVE_HTTP,
  170. 1010: IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS,
  171. 1012: IssueType.PERFORMANCE_UNCOMPRESSED_ASSET,
  172. 1013: IssueType.PERFORMANCE_DB_MAIN_THREAD,
  173. 1015: IssueType.PERFORMANCE_LARGE_HTTP_PAYLOAD,
  174. 1016: IssueType.PERFORMANCE_HTTP_OVERHEAD,
  175. 1017: IssueType.PERFORMANCE_DURATION_REGRESSION,
  176. 1018: IssueType.PERFORMANCE_ENDPOINT_REGRESSION,
  177. 2001: IssueType.PROFILE_FILE_IO_MAIN_THREAD,
  178. 2002: IssueType.PROFILE_IMAGE_DECODE_MAIN_THREAD,
  179. 2003: IssueType.PROFILE_JSON_DECODE_MAIN_THREAD,
  180. 2007: IssueType.PROFILE_REGEX_MAIN_THREAD,
  181. 2008: IssueType.PROFILE_FRAME_DROP,
  182. 2009: IssueType.PROFILE_FRAME_DROP_EXPERIMENTAL,
  183. 2010: IssueType.PROFILE_FUNCTION_REGRESSION,
  184. 2011: IssueType.PROFILE_FUNCTION_REGRESSION_EXPERIMENTAL,
  185. };
  186. const PERFORMANCE_REGRESSION_TYPE_IDS = new Set([1017, 1018, 2010, 2011]);
  187. export function getIssueTypeFromOccurrenceType(
  188. typeId: number | undefined
  189. ): IssueType | null {
  190. if (!typeId) {
  191. return null;
  192. }
  193. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  194. return OCCURRENCE_TYPE_TO_ISSUE_TYPE[typeId] ?? null;
  195. }
  196. export function isTransactionBased(typeId: number | undefined): boolean {
  197. if (!typeId) {
  198. return false;
  199. }
  200. // the 1xxx type ids are transaction based performance issues
  201. return typeId >= 1000 && typeId < 2000;
  202. }
  203. export function isOccurrenceBased(typeId: number | undefined): boolean {
  204. if (!typeId) {
  205. return false;
  206. }
  207. // these are regression type performance issues
  208. return !PERFORMANCE_REGRESSION_TYPE_IDS.has(typeId);
  209. }
  210. // endpoint: /api/0/issues/:issueId/attachments/?limit=50
  211. export type IssueAttachment = {
  212. dateCreated: string;
  213. event_id: string;
  214. headers: object;
  215. id: string;
  216. mimetype: string;
  217. name: string;
  218. sha1: string;
  219. size: number;
  220. type: string;
  221. };
  222. // endpoint: /api/0/projects/:orgSlug/:projSlug/events/:eventId/attachments/
  223. export type EventAttachment = IssueAttachment;
  224. /**
  225. * Issue Tags
  226. */
  227. export type Tag = {
  228. key: string;
  229. name: string;
  230. alias?: string;
  231. isInput?: boolean;
  232. kind?: FieldKind;
  233. /**
  234. * How many values should be suggested in autocomplete.
  235. * Overrides SmartSearchBar's `maxSearchItems` prop.
  236. */
  237. maxSuggestedValues?: number;
  238. predefined?: boolean;
  239. totalValues?: number;
  240. uniqueValues?: number;
  241. /**
  242. * Usually values are strings, but a predefined tag can define its SearchGroups
  243. */
  244. values?: string[] | SearchGroup[];
  245. };
  246. export type TagCollection = Record<string, Tag>;
  247. export type TagValue = {
  248. count: number;
  249. firstSeen: string;
  250. lastSeen: string;
  251. name: string;
  252. value: string;
  253. email?: string;
  254. identifier?: string;
  255. ipAddress?: string;
  256. key?: string;
  257. query?: string;
  258. username?: string;
  259. } & AvatarUser;
  260. type Topvalue = {
  261. count: number;
  262. firstSeen: string;
  263. key: string;
  264. lastSeen: string;
  265. name: string;
  266. value: string;
  267. // Might not actually exist.
  268. query?: string;
  269. readable?: string;
  270. };
  271. export type TagWithTopValues = {
  272. key: string;
  273. name: string;
  274. topValues: Topvalue[];
  275. totalValues: number;
  276. uniqueValues: number;
  277. canDelete?: boolean;
  278. };
  279. /**
  280. * Inbox, issue owners and Activity
  281. */
  282. export type Annotation = {
  283. displayName: string;
  284. url: string;
  285. };
  286. export type InboxReasonDetails = {
  287. count?: number | null;
  288. until?: string | null;
  289. user_count?: number | null;
  290. user_window?: number | null;
  291. window?: number | null;
  292. };
  293. export const enum GroupInboxReason {
  294. NEW = 0,
  295. UNIGNORED = 1,
  296. REGRESSION = 2,
  297. MANUAL = 3,
  298. REPROCESSED = 4,
  299. ESCALATING = 5,
  300. ONGOING = 6,
  301. }
  302. export type InboxDetails = {
  303. date_added?: string;
  304. reason?: GroupInboxReason;
  305. reason_details?: InboxReasonDetails | null;
  306. };
  307. export type SuggestedOwnerReason =
  308. | 'suspectCommit'
  309. | 'ownershipRule'
  310. | 'projectOwnership'
  311. // TODO: codeowners may no longer exist
  312. | 'codeowners';
  313. // Received from the backend to denote suggested owners of an issue
  314. export type SuggestedOwner = {
  315. date_added: string;
  316. owner: string;
  317. type: SuggestedOwnerReason;
  318. };
  319. export interface ParsedOwnershipRule {
  320. matcher: {pattern: string; type: string};
  321. owners: Actor[];
  322. }
  323. export type IssueOwnership = {
  324. autoAssignment:
  325. | 'Auto Assign to Suspect Commits'
  326. | 'Auto Assign to Issue Owner'
  327. | 'Turn off Auto-Assignment';
  328. codeownersAutoSync: boolean;
  329. dateCreated: string | null;
  330. fallthrough: boolean;
  331. isActive: boolean;
  332. lastUpdated: string | null;
  333. raw: string | null;
  334. schema?: {rules: ParsedOwnershipRule[]; version: number};
  335. };
  336. export enum GroupActivityType {
  337. NOTE = 'note',
  338. SET_RESOLVED = 'set_resolved',
  339. SET_RESOLVED_BY_AGE = 'set_resolved_by_age',
  340. SET_RESOLVED_IN_RELEASE = 'set_resolved_in_release',
  341. SET_RESOLVED_IN_COMMIT = 'set_resolved_in_commit',
  342. SET_RESOLVED_IN_PULL_REQUEST = 'set_resolved_in_pull_request',
  343. SET_UNRESOLVED = 'set_unresolved',
  344. SET_IGNORED = 'set_ignored',
  345. SET_PUBLIC = 'set_public',
  346. SET_PRIVATE = 'set_private',
  347. SET_REGRESSION = 'set_regression',
  348. CREATE_ISSUE = 'create_issue',
  349. UNMERGE_SOURCE = 'unmerge_source',
  350. UNMERGE_DESTINATION = 'unmerge_destination',
  351. FIRST_SEEN = 'first_seen',
  352. ASSIGNED = 'assigned',
  353. UNASSIGNED = 'unassigned',
  354. MERGE = 'merge',
  355. REPROCESS = 'reprocess',
  356. MARK_REVIEWED = 'mark_reviewed',
  357. AUTO_SET_ONGOING = 'auto_set_ongoing',
  358. SET_ESCALATING = 'set_escalating',
  359. SET_PRIORITY = 'set_priority',
  360. DELETED_ATTACHMENT = 'deleted_attachment',
  361. }
  362. interface GroupActivityBase {
  363. dateCreated: string;
  364. id: string;
  365. project: Project;
  366. assignee?: string;
  367. issue?: Group;
  368. user?: null | User;
  369. }
  370. export interface GroupActivityNote extends GroupActivityBase {
  371. data: {
  372. text: string;
  373. };
  374. type: GroupActivityType.NOTE;
  375. }
  376. interface GroupActivitySetResolved extends GroupActivityBase {
  377. data: {};
  378. type: GroupActivityType.SET_RESOLVED;
  379. }
  380. /**
  381. * An integration marks an issue as resolved
  382. */
  383. interface GroupActivitySetResolvedIntegration extends GroupActivityBase {
  384. data: {
  385. integration_id: number;
  386. /**
  387. * Human readable name of the integration
  388. */
  389. provider: string;
  390. /**
  391. * The key of the integration
  392. */
  393. provider_key: string;
  394. };
  395. type: GroupActivityType.SET_RESOLVED;
  396. }
  397. interface GroupActivitySetUnresolved extends GroupActivityBase {
  398. data: {};
  399. type: GroupActivityType.SET_UNRESOLVED;
  400. }
  401. interface GroupActivitySetUnresolvedForecast extends GroupActivityBase {
  402. data: {
  403. forecast: number;
  404. };
  405. type: GroupActivityType.SET_UNRESOLVED;
  406. }
  407. /**
  408. * An integration marks an issue as unresolved
  409. */
  410. interface GroupActivitySetUnresolvedIntegration extends GroupActivityBase {
  411. data: {
  412. integration_id: number;
  413. /**
  414. * Human readable name of the integration
  415. */
  416. provider: string;
  417. /**
  418. * The key of the integration
  419. */
  420. provider_key: string;
  421. };
  422. type: GroupActivityType.SET_UNRESOLVED;
  423. }
  424. interface GroupActivitySetPublic extends GroupActivityBase {
  425. data: Record<string, any>;
  426. type: GroupActivityType.SET_PUBLIC;
  427. }
  428. interface GroupActivitySetPrivate extends GroupActivityBase {
  429. data: Record<string, any>;
  430. type: GroupActivityType.SET_PRIVATE;
  431. }
  432. interface GroupActivitySetByAge extends GroupActivityBase {
  433. data: Record<string, any>;
  434. type: GroupActivityType.SET_RESOLVED_BY_AGE;
  435. }
  436. interface GroupActivityUnassigned extends GroupActivityBase {
  437. data: Record<string, any>;
  438. type: GroupActivityType.UNASSIGNED;
  439. }
  440. interface GroupActivityFirstSeen extends GroupActivityBase {
  441. data: Record<string, any>;
  442. type: GroupActivityType.FIRST_SEEN;
  443. }
  444. interface GroupActivityMarkReviewed extends GroupActivityBase {
  445. data: Record<string, any>;
  446. type: GroupActivityType.MARK_REVIEWED;
  447. }
  448. interface GroupActivityRegression extends GroupActivityBase {
  449. data: {
  450. /**
  451. * True if the project is using semver to decide if the event is a regression.
  452. * Available when the issue was resolved in a release.
  453. */
  454. follows_semver?: boolean;
  455. /**
  456. * The version that the issue was previously resolved in.
  457. * Available when the issue was resolved in a release.
  458. */
  459. resolved_in_version?: string;
  460. version?: string;
  461. };
  462. type: GroupActivityType.SET_REGRESSION;
  463. }
  464. export interface GroupActivitySetByResolvedInNextSemverRelease extends GroupActivityBase {
  465. data: {
  466. // Set for semver releases
  467. current_release_version: string;
  468. };
  469. type: GroupActivityType.SET_RESOLVED_IN_RELEASE;
  470. }
  471. export interface GroupActivitySetByResolvedInRelease extends GroupActivityBase {
  472. data: {
  473. version?: string;
  474. };
  475. type: GroupActivityType.SET_RESOLVED_IN_RELEASE;
  476. }
  477. interface GroupActivitySetByResolvedInCommit extends GroupActivityBase {
  478. data: {
  479. commit?: Commit;
  480. };
  481. type: GroupActivityType.SET_RESOLVED_IN_COMMIT;
  482. }
  483. interface GroupActivitySetByResolvedInPullRequest extends GroupActivityBase {
  484. data: {
  485. pullRequest?: PullRequest;
  486. };
  487. type: GroupActivityType.SET_RESOLVED_IN_PULL_REQUEST;
  488. }
  489. export interface GroupActivitySetIgnored extends GroupActivityBase {
  490. data: {
  491. ignoreCount?: number;
  492. ignoreDuration?: number;
  493. ignoreUntil?: string;
  494. /** Archived until escalating */
  495. ignoreUntilEscalating?: boolean;
  496. ignoreUserCount?: number;
  497. ignoreUserWindow?: number;
  498. ignoreWindow?: number;
  499. };
  500. type: GroupActivityType.SET_IGNORED;
  501. }
  502. export interface GroupActivityReprocess extends GroupActivityBase {
  503. data: {
  504. eventCount: number;
  505. newGroupId: number;
  506. oldGroupId: number;
  507. };
  508. type: GroupActivityType.REPROCESS;
  509. }
  510. interface GroupActivityUnmergeDestination extends GroupActivityBase {
  511. data: {
  512. fingerprints: string[];
  513. source?: {
  514. id: string;
  515. shortId: string;
  516. };
  517. };
  518. type: GroupActivityType.UNMERGE_DESTINATION;
  519. }
  520. interface GroupActivityUnmergeSource extends GroupActivityBase {
  521. data: {
  522. fingerprints: string[];
  523. destination?: {
  524. id: string;
  525. shortId: string;
  526. };
  527. };
  528. type: GroupActivityType.UNMERGE_SOURCE;
  529. }
  530. interface GroupActivityMerge extends GroupActivityBase {
  531. data: {
  532. issues: any[];
  533. };
  534. type: GroupActivityType.MERGE;
  535. }
  536. interface GroupActivityAutoSetOngoing extends GroupActivityBase {
  537. data: {
  538. afterDays?: number;
  539. };
  540. type: GroupActivityType.AUTO_SET_ONGOING;
  541. }
  542. export interface GroupActivitySetEscalating extends GroupActivityBase {
  543. data: {
  544. expired_snooze?: {
  545. count: number | null;
  546. until: Date | null;
  547. user_count: number | null;
  548. user_window: number | null;
  549. window: number | null;
  550. };
  551. forecast?: number;
  552. };
  553. type: GroupActivityType.SET_ESCALATING;
  554. }
  555. export interface GroupActivitySetPriority extends GroupActivityBase {
  556. data: {
  557. priority: PriorityLevel;
  558. reason: string;
  559. };
  560. type: GroupActivityType.SET_PRIORITY;
  561. }
  562. export interface GroupActivityAssigned extends GroupActivityBase {
  563. data: {
  564. assignee: string;
  565. assigneeType: string;
  566. user: Team | User;
  567. assigneeEmail?: string;
  568. /**
  569. * If the user was assigned via an integration
  570. */
  571. integration?:
  572. | 'projectOwnership'
  573. | 'codeowners'
  574. | 'slack'
  575. | 'msteams'
  576. | 'suspectCommitter';
  577. /** Codeowner or Project owner rule as a string */
  578. rule?: string;
  579. };
  580. type: GroupActivityType.ASSIGNED;
  581. }
  582. export interface GroupActivityCreateIssue extends GroupActivityBase {
  583. data: {
  584. location: string;
  585. provider: string;
  586. title: string;
  587. new?: boolean;
  588. };
  589. type: GroupActivityType.CREATE_ISSUE;
  590. }
  591. interface GroupActivityDeletedAttachment extends GroupActivityBase {
  592. data: {};
  593. type: GroupActivityType.DELETED_ATTACHMENT;
  594. }
  595. export type GroupActivity =
  596. | GroupActivityNote
  597. | GroupActivitySetResolved
  598. | GroupActivitySetResolvedIntegration
  599. | GroupActivitySetUnresolved
  600. | GroupActivitySetUnresolvedForecast
  601. | GroupActivitySetUnresolvedIntegration
  602. | GroupActivitySetIgnored
  603. | GroupActivitySetByAge
  604. | GroupActivitySetByResolvedInRelease
  605. | GroupActivitySetByResolvedInNextSemverRelease
  606. | GroupActivitySetByResolvedInCommit
  607. | GroupActivitySetByResolvedInPullRequest
  608. | GroupActivityFirstSeen
  609. | GroupActivityMerge
  610. | GroupActivityReprocess
  611. | GroupActivityUnassigned
  612. | GroupActivityMarkReviewed
  613. | GroupActivityUnmergeDestination
  614. | GroupActivitySetPublic
  615. | GroupActivitySetPrivate
  616. | GroupActivityRegression
  617. | GroupActivityUnmergeSource
  618. | GroupActivityAssigned
  619. | GroupActivityCreateIssue
  620. | GroupActivityAutoSetOngoing
  621. | GroupActivitySetEscalating
  622. | GroupActivitySetPriority
  623. | GroupActivityDeletedAttachment;
  624. export type Activity = GroupActivity;
  625. interface GroupFiltered {
  626. count: string;
  627. firstSeen: string;
  628. lastSeen: string;
  629. stats: Record<string, TimeseriesValue[]>;
  630. userCount: number;
  631. }
  632. export interface GroupStats extends GroupFiltered {
  633. filtered: GroupFiltered | null;
  634. id: string;
  635. isUnhandled?: boolean;
  636. // for issue alert previews, the last time a group triggered a rule
  637. lastTriggered?: string;
  638. lifetime?: GroupFiltered;
  639. sessionCount?: string | null;
  640. }
  641. export interface IgnoredStatusDetails {
  642. actor?: AvatarUser;
  643. ignoreCount?: number;
  644. // Sent in requests. ignoreUntil is used in responses.
  645. ignoreDuration?: number;
  646. ignoreUntil?: string;
  647. ignoreUntilEscalating?: boolean;
  648. ignoreUserCount?: number;
  649. ignoreUserWindow?: number;
  650. ignoreWindow?: number;
  651. }
  652. export interface ResolvedStatusDetails {
  653. actor?: AvatarUser;
  654. autoResolved?: boolean;
  655. inCommit?: {
  656. commit?: string;
  657. dateCreated?: string;
  658. id?: string;
  659. repository?: string | Repository;
  660. };
  661. inNextRelease?: boolean;
  662. inRelease?: string;
  663. inUpcomingRelease?: boolean;
  664. repository?: string;
  665. }
  666. interface ReprocessingStatusDetails {
  667. info: {
  668. dateCreated: string;
  669. totalEvents: number;
  670. } | null;
  671. pendingEvents: number;
  672. }
  673. export interface UserParticipant extends User {
  674. type: 'user';
  675. }
  676. export interface TeamParticipant extends Team {
  677. type: 'team';
  678. }
  679. /**
  680. * The payload sent when marking reviewed
  681. */
  682. export interface MarkReviewed {
  683. inbox: false;
  684. }
  685. /**
  686. * The payload sent when updating a group's status
  687. */
  688. export interface GroupStatusResolution {
  689. status: GroupStatus.RESOLVED | GroupStatus.UNRESOLVED | GroupStatus.IGNORED;
  690. statusDetails: ResolvedStatusDetails | IgnoredStatusDetails | {};
  691. substatus?: GroupSubstatus | null;
  692. }
  693. export const enum GroupStatus {
  694. RESOLVED = 'resolved',
  695. UNRESOLVED = 'unresolved',
  696. IGNORED = 'ignored',
  697. REPROCESSING = 'reprocessing',
  698. }
  699. export const enum GroupSubstatus {
  700. ARCHIVED_UNTIL_ESCALATING = 'archived_until_escalating',
  701. ARCHIVED_UNTIL_CONDITION_MET = 'archived_until_condition_met',
  702. ARCHIVED_FOREVER = 'archived_forever',
  703. ESCALATING = 'escalating',
  704. ONGOING = 'ongoing',
  705. REGRESSED = 'regressed',
  706. NEW = 'new',
  707. }
  708. export const enum PriorityLevel {
  709. HIGH = 'high',
  710. MEDIUM = 'medium',
  711. LOW = 'low',
  712. }
  713. // TODO(ts): incomplete
  714. export interface BaseGroup {
  715. activity: GroupActivity[];
  716. annotations: Annotation[];
  717. assignedTo: Actor | null;
  718. culprit: string;
  719. firstSeen: string;
  720. hasSeen: boolean;
  721. id: string;
  722. isBookmarked: boolean;
  723. isPublic: boolean;
  724. isSubscribed: boolean;
  725. issueCategory: IssueCategory;
  726. issueType: IssueType;
  727. lastSeen: string;
  728. level: Level;
  729. logger: string | null;
  730. metadata: EventMetadata;
  731. numComments: number;
  732. participants: Array<UserParticipant | TeamParticipant>;
  733. permalink: string;
  734. platform: PlatformKey;
  735. pluginActions: TitledPlugin[];
  736. pluginContexts: any[]; // TODO(ts)
  737. pluginIssues: TitledPlugin[];
  738. priority: PriorityLevel;
  739. priorityLockedAt: string | null;
  740. project: Project;
  741. seenBy: User[];
  742. shareId: string;
  743. shortId: string;
  744. status: GroupStatus;
  745. statusDetails: IgnoredStatusDetails | ResolvedStatusDetails | ReprocessingStatusDetails;
  746. subscriptionDetails: {disabled?: boolean; reason?: string} | null;
  747. title: string;
  748. type: EventOrGroupType;
  749. userReportCount: number;
  750. inbox?: InboxDetails | null | false;
  751. integrationIssues?: ExternalIssue[];
  752. latestEvent?: Event;
  753. latestEventHasAttachments?: boolean;
  754. openPeriods?: GroupOpenPeriod[] | null;
  755. owners?: SuggestedOwner[] | null;
  756. sentryAppIssues?: PlatformExternalIssue[];
  757. substatus?: GroupSubstatus | null;
  758. }
  759. export interface GroupOpenPeriod {
  760. duration: string;
  761. end: string;
  762. isOpen: boolean;
  763. start: string;
  764. lastChecked?: string;
  765. }
  766. export interface GroupReprocessing extends BaseGroup, GroupStats {
  767. status: GroupStatus.REPROCESSING;
  768. statusDetails: ReprocessingStatusDetails;
  769. }
  770. export interface GroupResolved extends BaseGroup, GroupStats {
  771. status: GroupStatus.RESOLVED;
  772. statusDetails: ResolvedStatusDetails;
  773. }
  774. export interface GroupIgnored extends BaseGroup, GroupStats {
  775. status: GroupStatus.IGNORED;
  776. statusDetails: IgnoredStatusDetails;
  777. }
  778. export interface GroupUnresolved extends BaseGroup, GroupStats {
  779. status: GroupStatus.UNRESOLVED;
  780. statusDetails: {};
  781. }
  782. export type Group = GroupUnresolved | GroupResolved | GroupIgnored | GroupReprocessing;
  783. export interface GroupTombstone {
  784. actor: AvatarUser;
  785. culprit: string;
  786. id: string;
  787. level: Level;
  788. metadata: EventMetadata;
  789. type: EventOrGroupType;
  790. title?: string;
  791. }
  792. export interface GroupTombstoneHelper extends GroupTombstone {
  793. isTombstone: true;
  794. }
  795. /**
  796. * Datascrubbing
  797. */
  798. export type Meta = {
  799. chunks: ChunkType[];
  800. err: MetaError[];
  801. len: number;
  802. rem: MetaRemark[];
  803. };
  804. export type MetaError = string | [string, any];
  805. export type MetaRemark = Array<string | number>;
  806. export type ChunkType = {
  807. rule_id: string | number;
  808. text: string;
  809. type: string;
  810. remark?: string | number;
  811. };
  812. /**
  813. * Old User Feedback
  814. */
  815. export type UserReport = {
  816. comments: string;
  817. dateCreated: string;
  818. email: string;
  819. event: {eventID: string; id: string};
  820. eventID: string;
  821. id: string;
  822. issue: Group;
  823. name: string;
  824. user: User;
  825. };
  826. export type KeyValueListDataItem = {
  827. key: string;
  828. subject: string;
  829. action?: {
  830. link?: string | LocationDescriptor;
  831. };
  832. actionButton?: React.ReactNode;
  833. /**
  834. * If true, the action button will always be visible, not just on hover.
  835. */
  836. actionButtonAlwaysVisible?: boolean;
  837. isContextData?: boolean;
  838. isMultiValue?: boolean;
  839. meta?: Meta;
  840. subjectDataTestId?: string;
  841. subjectIcon?: React.ReactNode;
  842. subjectNode?: React.ReactNode;
  843. value?: React.ReactNode | Record<string, string | number>;
  844. };
  845. export type KeyValueListData = KeyValueListDataItem[];
  846. // Response from ShortIdLookupEndpoint
  847. // /organizations/${orgId}/shortids/${query}/
  848. export type ShortIdResponse = {
  849. group: Group;
  850. groupId: string;
  851. organizationSlug: string;
  852. projectSlug: string;
  853. shortId: string;
  854. };
  855. /**
  856. * Note used in Group Activity and Alerts for users to comment
  857. */
  858. export type Note = {
  859. /**
  860. * Array of [id, display string] tuples used for @-mentions
  861. */
  862. mentions: Array<[string, string]>;
  863. /**
  864. * Note contents (markdown allowed)
  865. */
  866. text: string;
  867. };