trace.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {useEffect, useState} from 'react';
  2. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  3. import LoadingError from 'sentry/components/loadingError';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
  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 {useRouteContext} from 'sentry/utils/useRouteContext';
  19. import TraceView from 'sentry/views/performance/traceDetails/traceView';
  20. import type {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 {
  59. location,
  60. params: {replaySlug, orgSlug},
  61. } = useRouteContext();
  62. const [, eventId] = replaySlug.split(':');
  63. const start = getUtcDateString(replayRecord.startedAt.getTime());
  64. const end = getUtcDateString(replayRecord.finishedAt.getTime());
  65. useEffect(() => {
  66. async function loadTraces() {
  67. const eventView = EventView.fromSavedQuery({
  68. id: undefined,
  69. name: `Traces in replay ${eventId}`,
  70. fields: ['trace', 'count(trace)', 'min(timestamp)'],
  71. orderby: 'min_timestamp',
  72. query: `replayId:${eventId} !title:"sentry-replay-event*"`,
  73. projects: [ALL_ACCESS_PROJECTS],
  74. version: 2,
  75. start,
  76. end,
  77. });
  78. try {
  79. const [data, , resp] = await doDiscoverQuery<TableData>(
  80. api,
  81. `/organizations/${orgSlug}/events/`,
  82. eventView.getEventsAPIPayload(location)
  83. );
  84. const traceIds = data.data.map(({trace}) => trace).filter(trace => trace);
  85. // TODO(replays): Potential performance concerns here if number of traceIds is large
  86. const traceDetails = await Promise.all(
  87. traceIds.map(traceId =>
  88. doDiscoverQuery(
  89. api,
  90. `/organizations/${orgSlug}/events-trace/${traceId}/`,
  91. getTraceRequestPayload({
  92. eventView: makeEventView({start, end}),
  93. location,
  94. })
  95. )
  96. )
  97. );
  98. setState(prevState => ({
  99. isLoading: false,
  100. error: null,
  101. traceEventView: eventView,
  102. pageLinks: resp?.getResponseHeader('Link') ?? prevState.pageLinks,
  103. traces: traceDetails.flatMap(([trace]) => trace as TraceFullDetailed[]) || [],
  104. }));
  105. } catch (err) {
  106. setState({
  107. isLoading: false,
  108. error: err,
  109. pageLinks: null,
  110. traceEventView: null,
  111. traces: null,
  112. });
  113. }
  114. }
  115. loadTraces();
  116. return () => {};
  117. }, [api, eventId, orgSlug, location, start, end]);
  118. if (state.isLoading) {
  119. return <LoadingIndicator />;
  120. }
  121. if (state.error || !state.traceEventView) {
  122. return <LoadingError />;
  123. }
  124. if (!state.traces?.length) {
  125. return (
  126. <EmptyStateWarning withIcon={false} small>
  127. {t('No traces found')}
  128. </EmptyStateWarning>
  129. );
  130. }
  131. return (
  132. <TraceView
  133. meta={null}
  134. traces={state.traces}
  135. location={location}
  136. organization={organization}
  137. traceEventView={state.traceEventView}
  138. traceSlug="Replay"
  139. />
  140. );
  141. // TODO(replays): pagination
  142. }