index.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import {Fragment, useEffect} from 'react';
  2. import {Location} from 'history';
  3. import Feature from 'sentry/components/acl/feature';
  4. import Alert from 'sentry/components/alert';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import {t} from 'sentry/locale';
  8. import {PageContent} from 'sentry/styles/organization';
  9. import type {Organization, Project} from 'sentry/types';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import {
  12. SPAN_OP_BREAKDOWN_FIELDS,
  13. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  14. } from 'sentry/utils/discover/fields';
  15. import {decodeScalar} from 'sentry/utils/queryString';
  16. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  17. import withOrganization from 'sentry/utils/withOrganization';
  18. import withProjects from 'sentry/utils/withProjects';
  19. import type {ReplayListLocationQuery} from 'sentry/views/replays/types';
  20. import {decodeFilterFromLocation, SpanOperationBreakdownFilter} from '../filter';
  21. import PageLayout, {ChildProps} from '../pageLayout';
  22. import Tab from '../tabs';
  23. import {decodeEventsDisplayFilterFromLocation} from '../transactionEvents/utils';
  24. import ReplaysContent from './content';
  25. import useReplaysFromTransaction from './useReplaysFromTransaction';
  26. type Props = {
  27. location: Location<ReplayListLocationQuery>;
  28. organization: Organization;
  29. projects: Project[];
  30. };
  31. function renderNoAccess() {
  32. return (
  33. <PageContent>
  34. <Alert type="warning">{t("You don't have access to this feature")}</Alert>
  35. </PageContent>
  36. );
  37. }
  38. function TransactionReplays(props: Props) {
  39. const {location, organization, projects} = props;
  40. return (
  41. <Feature
  42. features={['session-replay-ui']}
  43. organization={organization}
  44. renderDisabled={renderNoAccess}
  45. >
  46. <PageLayout
  47. location={location}
  48. organization={organization}
  49. projects={projects}
  50. tab={Tab.Replays}
  51. getDocumentTitle={getDocumentTitle}
  52. generateEventView={generateEventView}
  53. childComponent={ReplaysContentWrapper}
  54. />
  55. </Feature>
  56. );
  57. }
  58. function ReplaysContentWrapper({
  59. eventView: eventsWithReplaysView,
  60. location,
  61. organization,
  62. setError,
  63. }: ChildProps) {
  64. const eventsDisplayFilterName = decodeEventsDisplayFilterFromLocation(location);
  65. const spanOperationBreakdownFilter = decodeFilterFromLocation(location);
  66. const {eventView, replays, pageLinks, isFetching, fetchError} =
  67. useReplaysFromTransaction({
  68. eventsWithReplaysView,
  69. location,
  70. organization,
  71. eventsDisplayFilterName,
  72. spanOperationBreakdownFilter,
  73. });
  74. useEffect(() => {
  75. setError(fetchError?.message);
  76. }, [setError, fetchError]);
  77. if (isFetching || !eventView) {
  78. return (
  79. <Layout.Main fullWidth>
  80. <LoadingIndicator />
  81. </Layout.Main>
  82. );
  83. }
  84. return replays ? (
  85. <ReplaysContent
  86. eventView={eventView}
  87. isFetching={isFetching}
  88. location={location}
  89. organization={organization}
  90. pageLinks={pageLinks}
  91. replays={replays}
  92. eventsDisplayFilterName={eventsDisplayFilterName}
  93. spanOperationBreakdownFilter={spanOperationBreakdownFilter}
  94. />
  95. ) : (
  96. <Fragment>{null}</Fragment>
  97. );
  98. }
  99. function getDocumentTitle(transactionName: string): string {
  100. const hasTransactionName =
  101. typeof transactionName === 'string' && String(transactionName).trim().length > 0;
  102. if (hasTransactionName) {
  103. return [String(transactionName).trim(), t('Replays')].join(' \u2014 ');
  104. }
  105. return [t('Summary'), t('Replays')].join(' \u2014 ');
  106. }
  107. function generateEventView({
  108. location,
  109. transactionName,
  110. }: {
  111. location: Location;
  112. transactionName: string;
  113. }) {
  114. const fields = ['replayId', 'count()', 'transaction.duration', 'trace', 'timestamp'];
  115. const breakdown = decodeFilterFromLocation(location);
  116. if (breakdown !== SpanOperationBreakdownFilter.None) {
  117. fields.push(`spans.${breakdown}`);
  118. } else {
  119. fields.push(...SPAN_OP_BREAKDOWN_FIELDS, SPAN_OP_RELATIVE_BREAKDOWN_FIELD);
  120. }
  121. const query = decodeScalar(location.query.query, '');
  122. const conditions = new MutableSearch(query);
  123. conditions.setFilterValues('event.type', ['transaction']);
  124. conditions.addFilterValues('transaction', [transactionName]);
  125. conditions.addFilterValues('!replayId', ['']);
  126. return EventView.fromNewQueryWithLocation(
  127. {
  128. id: '',
  129. name: `Replay events within a transaction`,
  130. version: 2,
  131. fields,
  132. query: conditions.formatString(),
  133. projects: [],
  134. orderby: decodeScalar(location.query.sort, '-timestamp'),
  135. },
  136. location
  137. );
  138. }
  139. export default withProjects(withOrganization(TransactionReplays));