transactionReplays.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {Fragment, useEffect, useMemo} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import type {Location} from 'history';
  4. import * as Layout from 'sentry/components/layouts/thirds';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {t} from 'sentry/locale';
  7. import type {Organization} from 'sentry/types/organization';
  8. import EventView from 'sentry/utils/discover/eventView';
  9. import {
  10. SPAN_OP_BREAKDOWN_FIELDS,
  11. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  12. } from 'sentry/utils/discover/fields';
  13. import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import useMedia from 'sentry/utils/useMedia';
  16. import useOrganization from 'sentry/utils/useOrganization';
  17. import useProjects from 'sentry/utils/useProjects';
  18. import type {ChildProps} from 'sentry/views/performance/transactionSummary/pageLayout';
  19. import PageLayout from 'sentry/views/performance/transactionSummary/pageLayout';
  20. import Tab from 'sentry/views/performance/transactionSummary/tabs';
  21. import useAllMobileProj from 'sentry/views/replays/detail/useAllMobileProj';
  22. import ReplayTable from 'sentry/views/replays/replayTable';
  23. import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
  24. import type {ReplayListLocationQuery} from 'sentry/views/replays/types';
  25. import type {EventSpanData} from './useReplaysFromTransaction';
  26. import useReplaysFromTransaction from './useReplaysFromTransaction';
  27. import useReplaysWithTxData from './useReplaysWithTxData';
  28. function TransactionReplays() {
  29. const location = useLocation<ReplayListLocationQuery>();
  30. const organization = useOrganization();
  31. const {projects} = useProjects();
  32. return (
  33. <PageLayout
  34. location={{
  35. ...location,
  36. query: {
  37. ...location.query,
  38. statsPeriod: '90d',
  39. },
  40. }}
  41. organization={organization}
  42. projects={projects}
  43. tab={Tab.REPLAYS}
  44. getDocumentTitle={getDocumentTitle}
  45. generateEventView={generateEventView}
  46. childComponent={ReplaysContentWrapper}
  47. />
  48. );
  49. }
  50. function getDocumentTitle(transactionName: string): string {
  51. const hasTransactionName =
  52. typeof transactionName === 'string' && String(transactionName).trim().length > 0;
  53. if (hasTransactionName) {
  54. return [String(transactionName).trim(), t('Replays')].join(' \u2014 ');
  55. }
  56. return [t('Summary'), t('Replays')].join(' \u2014 ');
  57. }
  58. function generateEventView({
  59. location,
  60. transactionName,
  61. }: {
  62. location: Location;
  63. transactionName: string;
  64. }) {
  65. const fields = [
  66. 'replayId',
  67. 'count()',
  68. 'transaction.duration',
  69. 'trace',
  70. 'timestamp',
  71. ...SPAN_OP_BREAKDOWN_FIELDS,
  72. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  73. ];
  74. return EventView.fromSavedQuery({
  75. id: '',
  76. name: `Replay events within a transaction`,
  77. version: 2,
  78. fields,
  79. query: `event.type:transaction transaction:"${transactionName}" !replayId:""`,
  80. projects: [Number(location.query.project)],
  81. });
  82. }
  83. function ReplaysContentWrapper({
  84. eventView: replayIdsEventView,
  85. location,
  86. organization,
  87. setError,
  88. }: ChildProps) {
  89. const {data, fetchError, isFetching, pageLinks} = useReplaysFromTransaction({
  90. replayIdsEventView,
  91. location,
  92. organization,
  93. });
  94. useEffect(() => {
  95. setError(fetchError?.message ?? fetchError);
  96. }, [setError, fetchError]);
  97. if (!data) {
  98. return isFetching ? (
  99. <Layout.Main fullWidth>
  100. <LoadingIndicator />
  101. </Layout.Main>
  102. ) : (
  103. <Fragment>{null}</Fragment>
  104. );
  105. }
  106. const {events, replayRecordsEventView} = data;
  107. return (
  108. <ReplaysContent
  109. eventView={replayRecordsEventView}
  110. events={events}
  111. organization={organization}
  112. pageLinks={pageLinks}
  113. />
  114. );
  115. }
  116. function ReplaysContent({
  117. eventView,
  118. events,
  119. organization,
  120. }: {
  121. eventView: EventView;
  122. events: EventSpanData[];
  123. organization: Organization;
  124. pageLinks: string | null;
  125. }) {
  126. const location = useMemo(() => ({query: {}}) as Location<ReplayListLocationQuery>, []);
  127. const theme = useTheme();
  128. const hasRoomForColumns = useMedia(`(min-width: ${theme.breakpoints.small})`);
  129. const {replays, isFetching, fetchError} = useReplayList({
  130. eventView,
  131. location,
  132. organization,
  133. queryReferrer: 'transactionReplays',
  134. });
  135. const replaysWithTx = useReplaysWithTxData({
  136. replays,
  137. events,
  138. });
  139. const {allMobileProj} = useAllMobileProj();
  140. return (
  141. <Layout.Main fullWidth>
  142. <ReplayTable
  143. fetchError={fetchError}
  144. isFetching={isFetching}
  145. replays={replaysWithTx}
  146. sort={undefined}
  147. visibleColumns={[
  148. ReplayColumn.REPLAY,
  149. ...(hasRoomForColumns ? [ReplayColumn.SLOWEST_TRANSACTION] : []),
  150. ReplayColumn.OS,
  151. ...(allMobileProj ? [] : [ReplayColumn.BROWSER]),
  152. ReplayColumn.DURATION,
  153. ReplayColumn.COUNT_ERRORS,
  154. ReplayColumn.ACTIVITY,
  155. ]}
  156. showDropdownFilters={false}
  157. />
  158. </Layout.Main>
  159. );
  160. }
  161. export default TransactionReplays;