useTraceTimelineEvents.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import {useMemo} from 'react';
  2. import type {Event} from 'sentry/types/event';
  3. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  4. import {getTraceTimeRangeFromEvent} from 'sentry/utils/performance/quickTrace/utils';
  5. import {useApiQuery} from 'sentry/utils/queryClient';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. interface BaseEvent {
  8. culprit: string; // Used for default events & subtitles
  9. id: string;
  10. 'issue.id': number;
  11. project: string;
  12. 'project.name': string;
  13. timestamp: string;
  14. title: string;
  15. transaction: string;
  16. }
  17. interface TimelineIssuePlatformEvent extends BaseEvent {
  18. 'event.type': '';
  19. message: string; // Used for message for issue platform events
  20. }
  21. interface TimelineDefaultEvent extends BaseEvent {
  22. 'event.type': 'default';
  23. }
  24. export interface TimelineErrorEvent extends BaseEvent {
  25. 'error.value': string[]; // Used for message for error events
  26. 'event.type': 'error';
  27. 'stack.function': string[];
  28. }
  29. export type TimelineEvent =
  30. | TimelineDefaultEvent
  31. | TimelineErrorEvent
  32. | TimelineIssuePlatformEvent;
  33. export interface TraceEventResponse {
  34. data: TimelineEvent[];
  35. meta: unknown;
  36. }
  37. interface UseTraceTimelineEventsOptions {
  38. event: Event;
  39. }
  40. export function useTraceTimelineEvents({event}: UseTraceTimelineEventsOptions): {
  41. endTimestamp: number;
  42. isError: boolean;
  43. isLoading: boolean;
  44. oneOtherIssueEvent: TimelineEvent | undefined;
  45. startTimestamp: number;
  46. traceEvents: TimelineEvent[];
  47. } {
  48. const organization = useOrganization();
  49. // If the org has global views, we want to look across all projects,
  50. // otherwise, just look at the current project.
  51. const hasGlobalViews = organization.features.includes('global-views');
  52. const project = hasGlobalViews ? -1 : event.projectID;
  53. const {start, end} = getTraceTimeRangeFromEvent(event);
  54. const traceId = event.contexts?.trace?.trace_id ?? '';
  55. const enabled = !!traceId;
  56. const {
  57. data: issuePlatformData,
  58. isPending: isLoadingIssuePlatform,
  59. isError: isErrorIssuePlatform,
  60. } = useApiQuery<TraceEventResponse>(
  61. [
  62. `/organizations/${organization.slug}/events/`,
  63. {
  64. query: {
  65. // Get issue platform issues
  66. dataset: DiscoverDatasets.ISSUE_PLATFORM,
  67. field: [
  68. 'message',
  69. 'title',
  70. 'project',
  71. 'timestamp',
  72. 'issue.id',
  73. 'transaction',
  74. 'culprit', // Used for the subtitle
  75. 'event.type', // This is useful for typing TimelineEvent
  76. ],
  77. per_page: 100,
  78. query: `trace:${traceId}`,
  79. referrer: 'api.issues.issue_events',
  80. sort: '-timestamp',
  81. start,
  82. end,
  83. project: project,
  84. },
  85. },
  86. ],
  87. {staleTime: Infinity, retry: false, enabled}
  88. );
  89. const {
  90. data: discoverData,
  91. isPending: isLoadingDiscover,
  92. isError: isErrorDiscover,
  93. } = useApiQuery<{
  94. data: TimelineEvent[];
  95. meta: unknown;
  96. }>(
  97. [
  98. `/organizations/${organization.slug}/events/`,
  99. {
  100. query: {
  101. // Other events
  102. dataset: DiscoverDatasets.DISCOVER,
  103. field: [
  104. 'title',
  105. 'project',
  106. 'timestamp',
  107. 'issue.id',
  108. 'transaction',
  109. 'event.type',
  110. 'stack.function',
  111. 'culprit', // Used for default events and subtitles
  112. 'error.value', // Used for message for error events
  113. ],
  114. per_page: 100,
  115. query: `trace:${traceId}`,
  116. referrer: 'api.issues.issue_events',
  117. sort: '-timestamp',
  118. start,
  119. end,
  120. project: project,
  121. },
  122. },
  123. ],
  124. {staleTime: Infinity, retry: false, enabled}
  125. );
  126. const eventData = useMemo(() => {
  127. if (
  128. isLoadingIssuePlatform ||
  129. isLoadingDiscover ||
  130. isErrorIssuePlatform ||
  131. isErrorDiscover
  132. ) {
  133. return {
  134. data: [],
  135. startTimestamp: 0,
  136. endTimestamp: 0,
  137. };
  138. }
  139. // Events is unsorted since they're grouped by date later
  140. const events = [...issuePlatformData.data, ...discoverData.data];
  141. const oneOtherIssueEvent = getOneOtherIssueEvent(event, events);
  142. // The current event might be missing when there is a large number of issues
  143. const hasCurrentEvent = events.some(e => e.id === event.id);
  144. if (!hasCurrentEvent) {
  145. events.push({
  146. culprit: event.culprit,
  147. id: event.id,
  148. 'issue.id': Number(event.groupID),
  149. message: event.message,
  150. project: event.projectID,
  151. // The project name for current event is not used
  152. 'project.name': '',
  153. timestamp: event.dateCreated!,
  154. title: event.title,
  155. transaction: '',
  156. 'event.type': event['event.type'],
  157. });
  158. }
  159. const timestamps = events.map(e => new Date(e.timestamp).getTime());
  160. const startTimestamp = Math.min(...timestamps);
  161. const endTimestamp = Math.max(...timestamps);
  162. return {
  163. data: events,
  164. startTimestamp,
  165. endTimestamp,
  166. oneOtherIssueEvent,
  167. };
  168. }, [
  169. event,
  170. issuePlatformData,
  171. discoverData,
  172. isLoadingIssuePlatform,
  173. isLoadingDiscover,
  174. isErrorIssuePlatform,
  175. isErrorDiscover,
  176. ]);
  177. return {
  178. traceEvents: eventData.data,
  179. startTimestamp: eventData.startTimestamp,
  180. endTimestamp: eventData.endTimestamp,
  181. isLoading: isLoadingIssuePlatform || isLoadingDiscover,
  182. isError: isErrorIssuePlatform || isErrorDiscover,
  183. oneOtherIssueEvent: eventData.oneOtherIssueEvent,
  184. };
  185. }
  186. function getOneOtherIssueEvent(
  187. event: Event,
  188. allTraceEvents: TimelineEvent[]
  189. ): TimelineEvent | undefined {
  190. const groupId = event.groupID;
  191. if (!groupId) {
  192. return undefined;
  193. }
  194. const otherIssues = allTraceEvents.filter(
  195. (_event, index, self) =>
  196. _event['issue.id'] !== undefined &&
  197. // Exclude the current issue
  198. _event['issue.id'] !== Number(groupId) &&
  199. self.findIndex(e => e['issue.id'] === _event['issue.id']) === index
  200. );
  201. return otherIssues.length === 1 ? otherIssues[0] : undefined;
  202. }