useReplaysFromTransaction.tsx 3.1 KB

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