useReplaysFromTransaction.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import {useCallback, useEffect, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {Location} from 'history';
  4. import type {Client} from 'sentry/api';
  5. import type {Organization} from 'sentry/types';
  6. import {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
  7. import EventView, {fromSorts} from 'sentry/utils/discover/eventView';
  8. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  9. import {decodeScalar} from 'sentry/utils/queryString';
  10. import fetchReplayList, {
  11. DEFAULT_SORT,
  12. REPLAY_LIST_FIELDS,
  13. } from 'sentry/utils/replays/fetchReplayList';
  14. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  15. import useApi from 'sentry/utils/useApi';
  16. import {ReplayListRecord} from 'sentry/views/replays/types';
  17. export type ReplayListRecordWithTx = ReplayListRecord & {
  18. txEvent: {[x: string]: any};
  19. };
  20. import {SpanOperationBreakdownFilter} from '../filter';
  21. import {
  22. EventsDisplayFilterName,
  23. getEventsFilterOptions,
  24. getPercentilesEventView,
  25. mapPercentileValues,
  26. PercentileValues,
  27. } from '../transactionEvents/utils';
  28. type State = Awaited<ReturnType<typeof fetchReplayList>> & {
  29. eventView: undefined | EventView;
  30. replays?: ReplayListRecordWithTx[];
  31. };
  32. type Options = {
  33. eventsDisplayFilterName: EventsDisplayFilterName;
  34. eventsWithReplaysView: EventView;
  35. location: Location;
  36. organization: Organization;
  37. spanOperationBreakdownFilter: SpanOperationBreakdownFilter;
  38. };
  39. function useReplaysFromTransaction({
  40. eventsWithReplaysView,
  41. location,
  42. organization,
  43. spanOperationBreakdownFilter,
  44. eventsDisplayFilterName,
  45. }: Options) {
  46. const api = useApi();
  47. const [data, setData] = useState<State>({
  48. fetchError: undefined,
  49. isFetching: true,
  50. pageLinks: null,
  51. replays: [],
  52. eventView: undefined,
  53. });
  54. const load = useCallback(async () => {
  55. const percentilesView = getPercentilesEventView(eventsWithReplaysView.clone());
  56. const percentileData = await fetchPercentiles({
  57. api,
  58. eventView: percentilesView,
  59. location,
  60. organization,
  61. });
  62. const percentiles = mapPercentileValues(percentileData);
  63. const filteredEventView = getFilteredEventView({
  64. eventView: eventsWithReplaysView,
  65. percentiles,
  66. spanOperationBreakdownFilter,
  67. eventsDisplayFilterName,
  68. });
  69. const eventsData = await fetchEventsWithReplay({
  70. api,
  71. eventView: filteredEventView ?? eventsWithReplaysView,
  72. location,
  73. organization,
  74. });
  75. const replayIds = eventsData?.map(row => row.replayId);
  76. const eventView = EventView.fromNewQueryWithLocation(
  77. {
  78. id: '',
  79. name: 'Replays within a transaction',
  80. version: 2,
  81. fields: REPLAY_LIST_FIELDS,
  82. projects: [],
  83. query: `id:[${String(replayIds)}]`,
  84. },
  85. location
  86. );
  87. eventView.sorts = fromSorts(decodeScalar(location.query.sort, DEFAULT_SORT));
  88. const listData = await fetchReplayList({
  89. api,
  90. eventView,
  91. location,
  92. organization,
  93. });
  94. const replays: ReplayListRecordWithTx[] | undefined = listData.replays?.map(
  95. replay => {
  96. let slowestEvent: TableDataRow | undefined;
  97. for (const event of eventsData ?? []) {
  98. // attach the slowest tx event to the replay
  99. if (
  100. event.replayId === replay.id &&
  101. (!slowestEvent ||
  102. event['transaction.duration'] > slowestEvent['transaction.duration'])
  103. ) {
  104. slowestEvent = event;
  105. }
  106. }
  107. return {
  108. ...replay,
  109. txEvent: slowestEvent ?? {},
  110. };
  111. }
  112. );
  113. setData({
  114. ...listData,
  115. eventView,
  116. replays,
  117. });
  118. }, [
  119. api,
  120. eventsWithReplaysView,
  121. location,
  122. organization,
  123. spanOperationBreakdownFilter,
  124. eventsDisplayFilterName,
  125. ]);
  126. useEffect(() => {
  127. load();
  128. }, [load]);
  129. return data;
  130. }
  131. async function fetchEventsWithReplay({
  132. api,
  133. eventView,
  134. location,
  135. organization,
  136. }: {
  137. api: Client;
  138. eventView: EventView;
  139. location: Location;
  140. organization: Organization;
  141. }) {
  142. try {
  143. const [data] = await doDiscoverQuery<TableData>(
  144. api,
  145. `/organizations/${organization.slug}/events/`,
  146. eventView.getEventsAPIPayload(location)
  147. );
  148. return data.data;
  149. } catch (err) {
  150. Sentry.captureException(err);
  151. return null;
  152. }
  153. }
  154. async function fetchPercentiles({
  155. api,
  156. eventView,
  157. location,
  158. organization,
  159. }: {
  160. api: Client;
  161. eventView: EventView;
  162. location: Location;
  163. organization: Organization;
  164. }) {
  165. try {
  166. const [data] = await doDiscoverQuery<TableData>(
  167. api,
  168. `/organizations/${organization.slug}/events/`,
  169. eventView.getEventsAPIPayload(location)
  170. );
  171. return data.data[0];
  172. } catch (err) {
  173. Sentry.captureException(err);
  174. return null;
  175. }
  176. }
  177. function getFilteredEventView({
  178. percentiles,
  179. spanOperationBreakdownFilter,
  180. eventsDisplayFilterName,
  181. eventView,
  182. }: {
  183. eventView: EventView;
  184. eventsDisplayFilterName: EventsDisplayFilterName;
  185. percentiles: PercentileValues;
  186. spanOperationBreakdownFilter: SpanOperationBreakdownFilter;
  187. }) {
  188. const filter = getEventsFilterOptions(spanOperationBreakdownFilter, percentiles)[
  189. eventsDisplayFilterName
  190. ];
  191. const filteredEventView = eventView.clone();
  192. if (filteredEventView && filter?.query) {
  193. const query = new MutableSearch(filteredEventView.query);
  194. filter.query.forEach(item => query.setFilterValues(item[0], [item[1]]));
  195. filteredEventView.query = query.formatString();
  196. }
  197. return filteredEventView;
  198. }
  199. export default useReplaysFromTransaction;