trace.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {useEffect, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import EmptyMessage from 'sentry/components/emptyMessage';
  4. import LoadingError from 'sentry/components/loadingError';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {t} from 'sentry/locale';
  7. import {Organization} from 'sentry/types';
  8. import {getUtcDateString} from 'sentry/utils/dates';
  9. import {TableData} from 'sentry/utils/discover/discoverQuery';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  12. import {TraceFullDetailed} from 'sentry/utils/performance/quickTrace/types';
  13. import {
  14. getTraceRequestPayload,
  15. makeEventView,
  16. } from 'sentry/utils/performance/quickTrace/utils';
  17. import useApi from 'sentry/utils/useApi';
  18. import {useLocation} from 'sentry/utils/useLocation';
  19. import TraceView from 'sentry/views/performance/traceDetails/traceView';
  20. import type {ReplayListLocationQuery, ReplayRecord} from 'sentry/views/replays/types';
  21. type State = {
  22. /**
  23. * Error, if not null.
  24. */
  25. // error: QueryError | null;
  26. error: any | null;
  27. /**
  28. * Loading state of this query.
  29. */
  30. isLoading: boolean;
  31. /**
  32. * Pagelinks, if applicable. Can be provided to the Pagination component.
  33. */
  34. pageLinks: string | null;
  35. /**
  36. * EventView that generates API payload
  37. */
  38. traceEventView: EventView | null;
  39. /**
  40. * Data / result.
  41. */
  42. traces: TraceFullDetailed[] | null;
  43. };
  44. interface Props {
  45. organization: Organization;
  46. replayRecord: ReplayRecord;
  47. }
  48. const INITIAL_STATE = Object.freeze({
  49. error: null,
  50. isLoading: true,
  51. pageLinks: null,
  52. traceEventView: null,
  53. traces: null,
  54. });
  55. export default function Trace({replayRecord, organization}: Props) {
  56. const [state, setState] = useState<State>(INITIAL_STATE);
  57. const api = useApi();
  58. const location = useLocation<ReplayListLocationQuery>();
  59. const replayId = replayRecord.id;
  60. const projectId = replayRecord.project_id;
  61. const orgSlug = organization.slug;
  62. const start = getUtcDateString(replayRecord.started_at.getTime());
  63. const end = getUtcDateString(replayRecord.finished_at.getTime());
  64. useEffect(() => {
  65. async function loadTraces() {
  66. const eventView = EventView.fromSavedQuery({
  67. id: undefined,
  68. name: `Traces in replay ${replayId}`,
  69. fields: ['trace', 'count(trace)', 'min(timestamp)'],
  70. orderby: 'min_timestamp',
  71. query: `replayId:${replayId}`,
  72. projects: [Number(projectId)],
  73. version: 2,
  74. start,
  75. end,
  76. });
  77. try {
  78. const [data, , resp] = await doDiscoverQuery<TableData>(
  79. api,
  80. `/organizations/${orgSlug}/events/`,
  81. eventView.getEventsAPIPayload(location)
  82. );
  83. const traceIds = data.data.map(({trace}) => trace).filter(trace => trace);
  84. // TODO(replays): Potential performance concerns here if number of traceIds is large
  85. const traceDetails = await Promise.allSettled(
  86. traceIds.map(traceId =>
  87. doDiscoverQuery(
  88. api,
  89. `/organizations/${orgSlug}/events-trace/${traceId}/`,
  90. getTraceRequestPayload({
  91. eventView: makeEventView({start, end}),
  92. location,
  93. })
  94. )
  95. )
  96. );
  97. const successfulTraceDetails = traceDetails
  98. .map(settled => (settled.status === 'fulfilled' ? settled.value[0] : undefined))
  99. .filter(Boolean);
  100. if (successfulTraceDetails.length !== traceDetails.length) {
  101. traceDetails.forEach(trace => {
  102. if (trace.status === 'rejected') {
  103. Sentry.captureMessage(trace.reason);
  104. }
  105. });
  106. }
  107. setState(prevState => ({
  108. isLoading: false,
  109. error: null,
  110. traceEventView: eventView,
  111. pageLinks: resp?.getResponseHeader('Link') ?? prevState.pageLinks,
  112. traces:
  113. successfulTraceDetails.flatMap(trace => trace as TraceFullDetailed[]) || [],
  114. }));
  115. } catch (err) {
  116. setState({
  117. isLoading: false,
  118. error: err,
  119. pageLinks: null,
  120. traceEventView: null,
  121. traces: null,
  122. });
  123. }
  124. }
  125. loadTraces();
  126. return () => {};
  127. }, [api, replayId, projectId, orgSlug, location, start, end]);
  128. if (state.isLoading) {
  129. return <LoadingIndicator />;
  130. }
  131. if (state.error || !state.traceEventView) {
  132. return <LoadingError />;
  133. }
  134. if (!state.traces?.length) {
  135. return <EmptyMessage title={t('No traces found')} />;
  136. }
  137. return (
  138. <TraceView
  139. meta={null}
  140. traces={state.traces}
  141. location={location}
  142. organization={organization}
  143. traceEventView={state.traceEventView}
  144. traceSlug="Replay"
  145. />
  146. );
  147. // TODO(replays): pagination
  148. }