useTraceTimelineEvents.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. id: string;
  9. 'issue.id': number;
  10. message: string;
  11. project: string;
  12. 'project.name': string;
  13. timestamp: string;
  14. title: string;
  15. transaction: string;
  16. }
  17. interface TimelineDiscoverEvent extends BaseEvent {}
  18. interface TimelineIssuePlatformEvent extends BaseEvent {
  19. 'event.type': string;
  20. 'stack.function': string[];
  21. }
  22. export type TimelineEvent = TimelineDiscoverEvent | TimelineIssuePlatformEvent;
  23. export interface TraceEventResponse {
  24. data: TimelineEvent[];
  25. meta: unknown;
  26. }
  27. interface UseTraceTimelineEventsOptions {
  28. event: Event;
  29. }
  30. export function useTraceTimelineEvents({event}: UseTraceTimelineEventsOptions): {
  31. endTimestamp: number;
  32. isError: boolean;
  33. isLoading: boolean;
  34. oneOtherIssueEvent: TimelineEvent | undefined;
  35. startTimestamp: number;
  36. traceEvents: TimelineEvent[];
  37. } {
  38. const organization = useOrganization();
  39. const {start, end} = getTraceTimeRangeFromEvent(event);
  40. const traceId = event.contexts?.trace?.trace_id ?? '';
  41. const enabled = !!traceId;
  42. const {
  43. data: issuePlatformData,
  44. isLoading: isLoadingIssuePlatform,
  45. isError: isErrorIssuePlatform,
  46. } = useApiQuery<TraceEventResponse>(
  47. [
  48. `/organizations/${organization.slug}/events/`,
  49. {
  50. query: {
  51. // Get performance issues
  52. dataset: DiscoverDatasets.ISSUE_PLATFORM,
  53. field: ['message', 'title', 'project', 'timestamp', 'issue.id', 'transaction'],
  54. per_page: 100,
  55. query: `trace:${traceId}`,
  56. referrer: 'api.issues.issue_events',
  57. sort: '-timestamp',
  58. start,
  59. end,
  60. },
  61. },
  62. ],
  63. {staleTime: Infinity, retry: false, enabled}
  64. );
  65. const {
  66. data: discoverData,
  67. isLoading: isLoadingDiscover,
  68. isError: isErrorDiscover,
  69. } = useApiQuery<{
  70. data: TimelineEvent[];
  71. meta: unknown;
  72. }>(
  73. [
  74. `/organizations/${organization.slug}/events/`,
  75. {
  76. query: {
  77. // Other events
  78. dataset: DiscoverDatasets.DISCOVER,
  79. field: [
  80. 'message',
  81. 'title',
  82. 'project',
  83. 'timestamp',
  84. 'issue.id',
  85. 'transaction',
  86. 'event.type',
  87. 'stack.function',
  88. ],
  89. per_page: 100,
  90. query: `trace:${traceId}`,
  91. referrer: 'api.issues.issue_events',
  92. sort: '-timestamp',
  93. start,
  94. end,
  95. },
  96. },
  97. ],
  98. {staleTime: Infinity, retry: false, enabled}
  99. );
  100. const eventData = useMemo(() => {
  101. if (
  102. isLoadingIssuePlatform ||
  103. isLoadingDiscover ||
  104. isErrorIssuePlatform ||
  105. isErrorDiscover
  106. ) {
  107. return {
  108. data: [],
  109. startTimestamp: 0,
  110. endTimestamp: 0,
  111. };
  112. }
  113. // Events is unsorted since they're grouped by date later
  114. const events = [...issuePlatformData.data, ...discoverData.data];
  115. const oneOtherIssueEvent = getOneOtherIssueEvent(event, events);
  116. // The current event might be missing when there is a large number of issues
  117. const hasCurrentEvent = events.some(e => e.id === event.id);
  118. if (!hasCurrentEvent) {
  119. events.push({
  120. id: event.id,
  121. 'issue.id': Number(event.groupID),
  122. message: event.message,
  123. project: event.projectID,
  124. // The project name for current event is not used
  125. 'project.name': '',
  126. timestamp: event.dateCreated!,
  127. title: event.title,
  128. transaction: '',
  129. });
  130. }
  131. const timestamps = events.map(e => new Date(e.timestamp).getTime());
  132. const startTimestamp = Math.min(...timestamps);
  133. const endTimestamp = Math.max(...timestamps);
  134. return {
  135. data: events,
  136. startTimestamp,
  137. endTimestamp,
  138. oneOtherIssueEvent,
  139. };
  140. }, [
  141. event,
  142. issuePlatformData,
  143. discoverData,
  144. isLoadingIssuePlatform,
  145. isLoadingDiscover,
  146. isErrorIssuePlatform,
  147. isErrorDiscover,
  148. ]);
  149. return {
  150. traceEvents: eventData.data,
  151. startTimestamp: eventData.startTimestamp,
  152. endTimestamp: eventData.endTimestamp,
  153. isLoading: isLoadingIssuePlatform || isLoadingDiscover,
  154. isError: isErrorIssuePlatform || isErrorDiscover,
  155. oneOtherIssueEvent: eventData.oneOtherIssueEvent,
  156. };
  157. }
  158. function getOneOtherIssueEvent(
  159. event: Event,
  160. allTraceEvents: TimelineEvent[]
  161. ): TimelineEvent | undefined {
  162. const groupId = event.groupID;
  163. if (!groupId) {
  164. return undefined;
  165. }
  166. const otherIssues = allTraceEvents.filter(
  167. (_event, index, self) =>
  168. _event['issue.id'] !== undefined &&
  169. // Exclude the current issue
  170. _event['issue.id'] !== Number(groupId) &&
  171. self.findIndex(e => e['issue.id'] === _event['issue.id']) === index
  172. );
  173. return otherIssues.length === 1 ? otherIssues[0] : undefined;
  174. }