utils.tsx 6.3 KB

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