useTraceTimelineEvents.tsx 4.9 KB

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