useReplaysFromTransaction.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import {useCallback, useEffect, useMemo, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {Location} from 'history';
  4. import type {Organization} from 'sentry/types';
  5. import EventView from 'sentry/utils/discover/eventView';
  6. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  7. import {decodeScalar} from 'sentry/utils/queryString';
  8. import {DEFAULT_SORT, REPLAY_LIST_FIELDS} from 'sentry/utils/replays/fetchReplayList';
  9. import useApi from 'sentry/utils/useApi';
  10. import type {ReplayListLocationQuery} from 'sentry/views/replays/types';
  11. type Options = {
  12. location: Location;
  13. organization: Organization;
  14. replayIdsEventView: EventView;
  15. };
  16. export type EventSpanData = {
  17. 'count()': number;
  18. replayId: string;
  19. 'span_ops_breakdown.relative': string;
  20. 'spans.browser': null | number;
  21. 'spans.db': null | number;
  22. 'spans.http': null | number;
  23. 'spans.resource': null | number;
  24. 'spans.ui': null | number;
  25. timestamp: string;
  26. trace: string;
  27. 'transaction.duration': number;
  28. };
  29. type Return = {
  30. data: null | {
  31. events: EventSpanData[];
  32. replayRecordsEventView: EventView;
  33. };
  34. fetchError: any;
  35. isFetching: boolean;
  36. pageLinks: null | string;
  37. };
  38. function useReplaysFromTransaction({
  39. location,
  40. organization,
  41. replayIdsEventView,
  42. }: Options): Return {
  43. const api = useApi();
  44. const [response, setResponse] = useState<{
  45. events: EventSpanData[];
  46. pageLinks: null | string;
  47. replayIds: undefined | string[];
  48. }>({events: [], pageLinks: null, replayIds: undefined});
  49. const [fetchError, setFetchError] = useState<any>();
  50. const {cursor} = location.query;
  51. const fetchReplayIds = useCallback(async () => {
  52. try {
  53. const [{data}, _textStatus, resp] = await doDiscoverQuery<{data: EventSpanData[]}>(
  54. api,
  55. `/organizations/${organization.slug}/events/`,
  56. replayIdsEventView.getEventsAPIPayload({
  57. query: {cursor},
  58. } as Location<ReplayListLocationQuery>)
  59. );
  60. setResponse({
  61. pageLinks: resp?.getResponseHeader('Link') ?? '',
  62. replayIds: data.map(record => String(record.replayId)),
  63. events: data || [],
  64. });
  65. } catch (err) {
  66. Sentry.captureException(err);
  67. setFetchError(err);
  68. }
  69. }, [api, cursor, organization.slug, replayIdsEventView]);
  70. const replayRecordsEventView = useMemo(() => {
  71. if (!response.replayIds) {
  72. return null;
  73. }
  74. return EventView.fromSavedQuery({
  75. id: '',
  76. name: '',
  77. version: 2,
  78. fields: REPLAY_LIST_FIELDS,
  79. projects: [],
  80. query: `id:[${String(response.replayIds)}]`,
  81. orderby: decodeScalar(location.query.sort, DEFAULT_SORT),
  82. });
  83. }, [location.query.sort, response.replayIds]);
  84. useEffect(() => {
  85. fetchReplayIds();
  86. }, [fetchReplayIds]);
  87. return {
  88. data: replayRecordsEventView
  89. ? {
  90. events: response.events,
  91. replayRecordsEventView,
  92. }
  93. : null,
  94. fetchError,
  95. isFetching: !fetchError && !response.replayIds,
  96. pageLinks: response.pageLinks,
  97. };
  98. }
  99. export default useReplaysFromTransaction;