useReplayTraces.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import {useCallback, useEffect, useMemo, useState} from 'react';
  2. import type {Location} from 'history';
  3. import {getTimeStampFromTableDateField, getUtcDateString} from 'sentry/utils/dates';
  4. import type {TableData} from 'sentry/utils/discover/discoverQuery';
  5. import EventView from 'sentry/utils/discover/eventView';
  6. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  7. import type {ParsedHeader} from 'sentry/utils/parseLinkHeader';
  8. import parseLinkHeader from 'sentry/utils/parseLinkHeader';
  9. import useApi from 'sentry/utils/useApi';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import type {ReplayRecord} from 'sentry/views/replays/types';
  12. export type ReplayTrace = {
  13. timestamp: number | undefined;
  14. traceSlug: string;
  15. };
  16. type ReplayTraceDataResults = {
  17. eventView: EventView | undefined;
  18. indexComplete: boolean;
  19. indexError: undefined | Error;
  20. replayTraces: ReplayTrace[] | undefined;
  21. };
  22. // This hook fetches the traceIds and the min(timestamp) associated with each id, for a replay record.
  23. export function useReplayTraces({
  24. replayRecord,
  25. }: {
  26. replayRecord: ReplayRecord | undefined;
  27. }) {
  28. const api = useApi();
  29. const organization = useOrganization();
  30. const [state, setState] = useState<ReplayTraceDataResults>({
  31. indexComplete: false,
  32. indexError: undefined,
  33. replayTraces: undefined,
  34. eventView: undefined,
  35. });
  36. const orgSlug = organization.slug;
  37. const listEventView = useMemo(() => {
  38. if (!replayRecord) {
  39. return null;
  40. }
  41. const replayId = replayRecord?.id;
  42. const projectId = replayRecord?.project_id;
  43. const start = getUtcDateString(replayRecord?.started_at.getTime());
  44. const end = getUtcDateString(replayRecord?.finished_at.getTime());
  45. return EventView.fromSavedQuery({
  46. id: undefined,
  47. name: `Traces in replay ${replayId}`,
  48. fields: ['trace', 'count(trace)', 'min(timestamp)'],
  49. orderby: 'min_timestamp',
  50. query: `replayId:${replayId}`,
  51. projects: [Number(projectId)],
  52. version: 2,
  53. start,
  54. end,
  55. });
  56. }, [replayRecord]);
  57. const fetchTransactionData = useCallback(async () => {
  58. if (!listEventView) {
  59. return;
  60. }
  61. const start = getUtcDateString(replayRecord?.started_at.getTime());
  62. const end = getUtcDateString(replayRecord?.finished_at.getTime());
  63. setState({
  64. indexComplete: false,
  65. indexError: undefined,
  66. replayTraces: undefined,
  67. eventView: listEventView,
  68. });
  69. let cursor = {
  70. cursor: '0:0:0',
  71. results: true,
  72. href: '',
  73. } as ParsedHeader;
  74. while (cursor.results) {
  75. const payload = {
  76. ...listEventView.getEventsAPIPayload({
  77. start,
  78. end,
  79. limit: 10,
  80. } as unknown as Location),
  81. sort: ['min_timestamp', 'trace'],
  82. cursor: cursor.cursor,
  83. };
  84. try {
  85. const [{data}, , listResp] = await doDiscoverQuery<TableData>(
  86. api,
  87. `/organizations/${orgSlug}/events/`,
  88. payload
  89. );
  90. const parsedData = data
  91. .filter(row => row.trace) // Filter out items where trace is not truthy
  92. .map(row => ({
  93. traceSlug: row.trace.toString(),
  94. timestamp: getTimeStampFromTableDateField(row['min(timestamp)']),
  95. }));
  96. const pageLinks = listResp?.getResponseHeader('Link') ?? null;
  97. cursor = parseLinkHeader(pageLinks)?.next;
  98. const indexComplete = !cursor.results;
  99. setState(prev => ({
  100. ...prev,
  101. replayTraces: prev.replayTraces
  102. ? [...prev.replayTraces, ...parsedData]
  103. : parsedData,
  104. indexComplete,
  105. }));
  106. } catch (indexError) {
  107. setState(prev => ({...prev, indexError, indexComplete: true}));
  108. cursor = {cursor: '', results: false, href: ''} as ParsedHeader;
  109. }
  110. }
  111. }, [api, listEventView, orgSlug, replayRecord]);
  112. useEffect(() => {
  113. if (state.indexComplete === false) {
  114. fetchTransactionData();
  115. }
  116. }, [fetchTransactionData, state.indexComplete]);
  117. return state;
  118. }