utils.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import {useMemo} from 'react';
  2. import orderBy from 'lodash/orderBy';
  3. import {bulkUpdate, useFetchIssueTags} from 'sentry/actionCreators/group';
  4. import {Client} from 'sentry/api';
  5. import {t} from 'sentry/locale';
  6. import ConfigStore from 'sentry/stores/configStore';
  7. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  8. import type {Group, GroupActivity} from 'sentry/types';
  9. import type {Event} from 'sentry/types/event';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. export function markEventSeen(
  12. api: Client,
  13. orgId: string,
  14. projectId: string,
  15. groupId: string
  16. ) {
  17. bulkUpdate(
  18. api,
  19. {
  20. orgId,
  21. projectId,
  22. itemIds: [groupId],
  23. failSilently: true,
  24. data: {hasSeen: true},
  25. },
  26. {}
  27. );
  28. }
  29. export function fetchGroupUserReports(
  30. orgSlug: string,
  31. groupId: string,
  32. query: Record<string, string>
  33. ) {
  34. const api = new Client();
  35. return api.requestPromise(`/organizations/${orgSlug}/issues/${groupId}/user-reports/`, {
  36. includeAllArgs: true,
  37. query,
  38. });
  39. }
  40. export function useDefaultIssueEvent() {
  41. const user = useLegacyStore(ConfigStore).user;
  42. const options = user ? user.options : null;
  43. return options?.defaultIssueEvent ?? 'recommended';
  44. }
  45. /**
  46. * Returns the environment name for an event or null
  47. *
  48. * @param event
  49. */
  50. export function getEventEnvironment(event: Event) {
  51. const tag = event.tags.find(({key}) => key === 'environment');
  52. return tag ? tag.value : null;
  53. }
  54. const SUBSCRIPTION_REASONS = {
  55. commented: t(
  56. "You're receiving workflow notifications because you have commented on this issue."
  57. ),
  58. assigned: t(
  59. "You're receiving workflow notifications because you were assigned to this issue."
  60. ),
  61. bookmarked: t(
  62. "You're receiving workflow notifications because you have bookmarked this issue."
  63. ),
  64. changed_status: t(
  65. "You're receiving workflow notifications because you have changed the status of this issue."
  66. ),
  67. mentioned: t(
  68. "You're receiving workflow notifications because you have been mentioned in this issue."
  69. ),
  70. };
  71. /**
  72. * @param group
  73. * @param removeLinks add/remove links to subscription reasons text (default: false)
  74. * @returns Reason for subscription
  75. */
  76. export function getSubscriptionReason(group: Group) {
  77. if (group.subscriptionDetails?.disabled) {
  78. return t('You have disabled workflow notifications for this project.');
  79. }
  80. if (!group.isSubscribed) {
  81. return t('Subscribe to workflow notifications for this issue');
  82. }
  83. if (group.subscriptionDetails) {
  84. const {reason} = group.subscriptionDetails;
  85. if (reason === 'unknown') {
  86. return t(
  87. "You're receiving workflow notifications because you are subscribed to this issue."
  88. );
  89. }
  90. if (reason && SUBSCRIPTION_REASONS.hasOwnProperty(reason)) {
  91. return SUBSCRIPTION_REASONS[reason];
  92. }
  93. }
  94. return t(
  95. "You're receiving updates because you are subscribed to workflow notifications for this project."
  96. );
  97. }
  98. export function getGroupMostRecentActivity(
  99. activities: GroupActivity[] | undefined
  100. ): GroupActivity | undefined {
  101. // Most recent activity
  102. return activities
  103. ? orderBy([...activities], ({dateCreated}) => new Date(dateCreated), ['desc'])[0]
  104. : undefined;
  105. }
  106. export enum ReprocessingStatus {
  107. REPROCESSED_AND_HASNT_EVENT = 'reprocessed_and_hasnt_event',
  108. REPROCESSED_AND_HAS_EVENT = 'reprocessed_and_has_event',
  109. REPROCESSING = 'reprocessing',
  110. NO_STATUS = 'no_status',
  111. }
  112. // Reprocessing Checks
  113. export function getGroupReprocessingStatus(
  114. group: Group,
  115. mostRecentActivity?: GroupActivity
  116. ) {
  117. const {status, count, activity: activities} = group;
  118. const groupCount = Number(count);
  119. switch (status) {
  120. case 'reprocessing':
  121. return ReprocessingStatus.REPROCESSING;
  122. case 'unresolved': {
  123. const groupMostRecentActivity =
  124. mostRecentActivity ?? getGroupMostRecentActivity(activities);
  125. if (groupMostRecentActivity?.type === 'reprocess') {
  126. if (groupCount === 0) {
  127. return ReprocessingStatus.REPROCESSED_AND_HASNT_EVENT;
  128. }
  129. return ReprocessingStatus.REPROCESSED_AND_HAS_EVENT;
  130. }
  131. return ReprocessingStatus.NO_STATUS;
  132. }
  133. default:
  134. return ReprocessingStatus.NO_STATUS;
  135. }
  136. }
  137. export const useFetchIssueTagsForDetailsPage = (
  138. {
  139. groupId,
  140. orgSlug,
  141. environment = [],
  142. isStatisticalDetector = false,
  143. statisticalDetectorParameters,
  144. }: {
  145. environment: string[];
  146. orgSlug: string;
  147. groupId?: string;
  148. isStatisticalDetector?: boolean;
  149. statisticalDetectorParameters?: {
  150. durationBaseline: number;
  151. end: string;
  152. start: string;
  153. transaction: string;
  154. };
  155. },
  156. {enabled = true}: {enabled?: boolean} = {}
  157. ) => {
  158. return useFetchIssueTags(
  159. {
  160. groupId,
  161. orgSlug,
  162. environment,
  163. readable: true,
  164. limit: 4,
  165. isStatisticalDetector,
  166. statisticalDetectorParameters,
  167. },
  168. {enabled}
  169. );
  170. };
  171. export function useEnvironmentsFromUrl(): string[] {
  172. const location = useLocation();
  173. const envs = location.query.environment;
  174. const envsArray = useMemo(() => {
  175. return typeof envs === 'string' ? [envs] : envs ?? [];
  176. }, [envs]);
  177. return envsArray;
  178. }
  179. export function getGroupDetailsQueryData({
  180. environments,
  181. }: {
  182. environments?: string[];
  183. } = {}): Record<string, string | string[]> {
  184. // Note, we do not want to include the environment key at all if there are no environments
  185. const query: Record<string, string | string[]> = {
  186. ...(environments && environments.length > 0 ? {environment: environments} : {}),
  187. expand: ['inbox', 'owners'],
  188. collapse: ['release', 'tags'],
  189. };
  190. return query;
  191. }
  192. export function getGroupEventDetailsQueryData({
  193. environments,
  194. query,
  195. stacktraceOnly,
  196. }: {
  197. environments?: string[];
  198. query?: string;
  199. stacktraceOnly?: boolean;
  200. } = {}): Record<string, string | string[]> {
  201. const defaultParams = {
  202. collapse: stacktraceOnly ? ['stacktraceOnly'] : ['fullRelease'],
  203. ...(query ? {query} : {}),
  204. };
  205. if (!environments || environments.length === 0) {
  206. return defaultParams;
  207. }
  208. return {...defaultParams, environment: environments};
  209. }