Browse Source

feat(replays): Include a replayId column in the Performance>All Events tab (#40003)

Building from #39798 I've added the same 'replay id' column into the
Performance > All Events tab.

<img width="1240" alt="Screen Shot 2022-10-13 at 2 57 19 PM"
src="https://user-images.githubusercontent.com/187460/195719071-b411b811-39d6-42a2-bf30-f092be6d6a6b.png">

Fixes #39736
Ryan Albrecht 2 years ago
parent
commit
705c27aa41

+ 3 - 1
static/app/views/organizationGroupDetails/allEventsTable.tsx

@@ -5,6 +5,7 @@ import LoadingError from 'sentry/components/loadingError';
 import {t} from 'sentry/locale';
 import {Organization} from 'sentry/types';
 import EventView, {decodeSorts} from 'sentry/utils/discover/eventView';
+import {useRoutes} from 'sentry/utils/useRoutes';
 import EventsTable from 'sentry/views/performance/transactionSummary/transactionEvents/eventsTable';
 
 export interface Props {
@@ -28,7 +29,7 @@ const AllEventsTable = (props: Props) => {
     totalEventCount,
   } = props;
   const [error, setError] = useState<string>('');
-
+  const routes = useRoutes();
   const fields: string[] = [
     'id',
     'transaction',
@@ -77,6 +78,7 @@ const AllEventsTable = (props: Props) => {
       location={location}
       issueId={issueId}
       organization={organization}
+      routes={routes}
       excludedTags={excludedTags}
       projectId={projectId}
       totalEventCount={totalEventCount}

+ 109 - 84
static/app/views/organizationGroupDetails/groupEvents.spec.jsx

@@ -9,6 +9,7 @@ import {
 } from 'sentry-test/reactTestingLibrary';
 
 import {GroupEvents} from 'sentry/views/organizationGroupDetails/groupEvents';
+import {RouteContext} from 'sentry/views/routeContext';
 
 describe('groupEvents', function () {
   let request;
@@ -115,13 +116,15 @@ describe('groupEvents', function () {
 
   it('renders', function () {
     const wrapper = render(
-      <GroupEvents
-        organization={organization}
-        api={new MockApiClient()}
-        group={TestStubs.Group()}
-        params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-        location={{query: {}}}
-      />,
+      <RouteContext.Provider value={routerContext}>
+        <GroupEvents
+          organization={organization}
+          api={new MockApiClient()}
+          group={TestStubs.Group()}
+          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+          location={{query: {}}}
+        />
+      </RouteContext.Provider>,
       {context: routerContext, organization}
     );
 
@@ -130,13 +133,15 @@ describe('groupEvents', function () {
 
   it('handles search', function () {
     render(
-      <GroupEvents
-        organization={organization}
-        api={new MockApiClient()}
-        params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-        group={TestStubs.Group()}
-        location={{query: {}}}
-      />,
+      <RouteContext.Provider value={routerContext}>
+        <GroupEvents
+          organization={organization}
+          api={new MockApiClient()}
+          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+          group={TestStubs.Group()}
+          location={{query: {}}}
+        />
+      </RouteContext.Provider>,
       {context: routerContext, organization}
     );
 
@@ -162,13 +167,15 @@ describe('groupEvents', function () {
 
   it('handles environment filtering', function () {
     render(
-      <GroupEvents
-        organization={organization}
-        api={new MockApiClient()}
-        params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-        group={TestStubs.Group()}
-        location={{query: {environment: ['prod', 'staging']}}}
-      />,
+      <RouteContext.Provider value={routerContext}>
+        <GroupEvents
+          organization={organization}
+          api={new MockApiClient()}
+          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+          group={TestStubs.Group()}
+          location={{query: {environment: ['prod', 'staging']}}}
+        />
+      </RouteContext.Provider>,
       {context: routerContext, organization}
     );
     expect(request).toHaveBeenCalledWith(
@@ -194,13 +201,15 @@ describe('groupEvents', function () {
       group.issueCategory = 'performance';
 
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       expect(discoverRequest).toHaveBeenCalledWith(
@@ -218,13 +227,15 @@ describe('groupEvents', function () {
       group.issueCategory = 'performance';
 
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       await waitForElementToBeRemoved(document.querySelector('div.loading-indicator'));
@@ -242,13 +253,15 @@ describe('groupEvents', function () {
 
     it('does not display attachments column with no attachments', async () => {
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       await waitForElementToBeRemoved(document.querySelector('div.loading-indicator'));
@@ -259,13 +272,15 @@ describe('groupEvents', function () {
 
     it('does not display minidump column with no minidumps', async () => {
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       await waitForElementToBeRemoved(document.querySelector('div.loading-indicator'));
@@ -294,13 +309,15 @@ describe('groupEvents', function () {
       });
 
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       await waitForElementToBeRemoved(document.querySelector('div.loading-indicator'));
@@ -329,13 +346,15 @@ describe('groupEvents', function () {
       });
 
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       await waitForElementToBeRemoved(document.querySelector('div.loading-indicator'));
@@ -348,13 +367,15 @@ describe('groupEvents', function () {
 
     it('renders new events table if error', function () {
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging']}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging']}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       expect(discoverRequest).toHaveBeenCalledWith(
@@ -373,13 +394,15 @@ describe('groupEvents', function () {
 
     it('removes sort if unsupported by the events table', function () {
       render(
-        <GroupEvents
-          organization={org.organization}
-          api={new MockApiClient()}
-          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-          group={group}
-          location={{query: {environment: ['prod', 'staging'], sort: 'user'}}}
-        />,
+        <RouteContext.Provider value={routerContext}>
+          <GroupEvents
+            organization={org.organization}
+            api={new MockApiClient()}
+            params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+            group={group}
+            location={{query: {environment: ['prod', 'staging'], sort: 'user'}}}
+          />
+        </RouteContext.Provider>,
         {context: routerContext, organization}
       );
       expect(discoverRequest).toHaveBeenCalledWith(
@@ -394,13 +417,15 @@ describe('groupEvents', function () {
     const group = TestStubs.Group();
 
     render(
-      <GroupEvents
-        organization={org.organization}
-        api={new MockApiClient()}
-        params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
-        group={group}
-        location={{query: {environment: ['prod', 'staging']}}}
-      />,
+      <RouteContext.Provider value={routerContext}>
+        <GroupEvents
+          organization={org.organization}
+          api={new MockApiClient()}
+          params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
+          group={group}
+          location={{query: {environment: ['prod', 'staging']}}}
+        />
+      </RouteContext.Provider>,
       {context: routerContext, organization}
     );
 

+ 24 - 8
static/app/views/performance/transactionEvents.spec.tsx

@@ -8,6 +8,7 @@ import {Organization} from 'sentry/types';
 import {WebVital} from 'sentry/utils/fields';
 import {OrganizationContext} from 'sentry/views/organizationContext';
 import TransactionEvents from 'sentry/views/performance/transactionSummary/transactionEvents';
+import {RouteContext} from 'sentry/views/routeContext';
 
 // XXX(epurkhiser): This appears to also be tested by ./transactionSummary/transactionEvents/index.spec.tsx
 
@@ -46,14 +47,29 @@ function initializeData({features: additionalFeatures = [], query = {}}: Data =
 
 const WrappedComponent = ({
   organization,
+  router,
   ...props
-}: Omit<React.ComponentProps<typeof TransactionEvents>, 'organization'> & {
+}: Omit<React.ComponentProps<typeof TransactionEvents>, 'organization' | 'location'> & {
   organization: Organization;
+  router: any;
 }) => {
   return (
-    <OrganizationContext.Provider value={organization}>
-      <TransactionEvents organization={organization} {...props} />
-    </OrganizationContext.Provider>
+    <RouteContext.Provider
+      value={{
+        router,
+        location: router.location,
+        params: {},
+        routes: [],
+      }}
+    >
+      <OrganizationContext.Provider value={organization}>
+        <TransactionEvents
+          organization={organization}
+          location={router.location}
+          {...props}
+        />
+      </OrganizationContext.Provider>
+    </RouteContext.Provider>
   );
 };
 
@@ -182,7 +198,7 @@ describe('Performance > TransactionSummary', function () {
     const wrapper = mountWithTheme(
       <WrappedComponent
         organization={initialData.organization}
-        location={initialData.router.location}
+        router={initialData.router}
       />,
       initialData.routerContext
     );
@@ -203,7 +219,7 @@ describe('Performance > TransactionSummary', function () {
     const wrapper = mountWithTheme(
       <WrappedComponent
         organization={initialData.organization}
-        location={initialData.router.location}
+        router={initialData.router}
       />,
       initialData.routerContext
     );
@@ -221,7 +237,7 @@ describe('Performance > TransactionSummary', function () {
     const wrapper = mountWithTheme(
       <WrappedComponent
         organization={initialData.organization}
-        location={initialData.router.location}
+        router={initialData.router}
       />,
       initialData.routerContext
     );
@@ -258,7 +274,7 @@ describe('Performance > TransactionSummary', function () {
     const wrapper = mountWithTheme(
       <WrappedComponent
         organization={initialData.organization}
-        location={initialData.router.location}
+        router={initialData.router}
       />,
       initialData.routerContext
     );

+ 42 - 29
static/app/views/performance/transactionSummary/transactionEvents/content.spec.tsx

@@ -14,6 +14,7 @@ import {OrganizationContext} from 'sentry/views/organizationContext';
 import {SpanOperationBreakdownFilter} from 'sentry/views/performance/transactionSummary/filter';
 import EventsPageContent from 'sentry/views/performance/transactionSummary/transactionEvents/content';
 import {EventsDisplayFilterName} from 'sentry/views/performance/transactionSummary/transactionEvents/utils';
+import {RouteContext} from 'sentry/views/routeContext';
 
 type Data = {
   features?: string[];
@@ -52,6 +53,7 @@ describe('Performance Transaction Events Content', function () {
   let eventView;
   let totalEventCount;
   let initialData;
+  let routeContext;
   const query =
     'transaction.duration:<15m event.type:transaction transaction:/api/0/organizations/{organization_slug}/events/';
   beforeEach(function () {
@@ -153,6 +155,13 @@ describe('Performance Transaction Events Content', function () {
       },
       initialData.router.location
     );
+
+    routeContext = {
+      router: initialData.router,
+      location: initialData.router.location,
+      params: {},
+      routes: [],
+    };
   });
 
   afterEach(function () {
@@ -163,20 +172,22 @@ describe('Performance Transaction Events Content', function () {
 
   it('basic rendering', async function () {
     const wrapper = mountWithTheme(
-      <OrganizationContext.Provider value={organization}>
-        <EventsPageContent
-          totalEventCount={totalEventCount}
-          eventView={eventView}
-          organization={organization}
-          location={initialData.router.location}
-          transactionName={transactionName}
-          spanOperationBreakdownFilter={SpanOperationBreakdownFilter.None}
-          onChangeSpanOperationBreakdownFilter={() => {}}
-          eventsDisplayFilterName={EventsDisplayFilterName.p100}
-          onChangeEventsDisplayFilter={() => {}}
-          setError={() => {}}
-        />
-      </OrganizationContext.Provider>,
+      <RouteContext.Provider value={routeContext}>
+        <OrganizationContext.Provider value={organization}>
+          <EventsPageContent
+            totalEventCount={totalEventCount}
+            eventView={eventView}
+            organization={organization}
+            location={initialData.router.location}
+            transactionName={transactionName}
+            spanOperationBreakdownFilter={SpanOperationBreakdownFilter.None}
+            onChangeSpanOperationBreakdownFilter={() => {}}
+            eventsDisplayFilterName={EventsDisplayFilterName.p100}
+            onChangeEventsDisplayFilter={() => {}}
+            setError={() => {}}
+          />
+        </OrganizationContext.Provider>
+      </RouteContext.Provider>,
       initialData.routerContext
     );
     await tick();
@@ -200,21 +211,23 @@ describe('Performance Transaction Events Content', function () {
 
   it('rendering with webvital selected', async function () {
     const wrapper = mountWithTheme(
-      <OrganizationContext.Provider value={organization}>
-        <EventsPageContent
-          totalEventCount={totalEventCount}
-          eventView={eventView}
-          organization={organization}
-          location={initialData.router.location}
-          transactionName={transactionName}
-          spanOperationBreakdownFilter={SpanOperationBreakdownFilter.None}
-          onChangeSpanOperationBreakdownFilter={() => {}}
-          eventsDisplayFilterName={EventsDisplayFilterName.p100}
-          onChangeEventsDisplayFilter={() => {}}
-          webVital={WebVital.LCP}
-          setError={() => {}}
-        />
-      </OrganizationContext.Provider>,
+      <RouteContext.Provider value={routeContext}>
+        <OrganizationContext.Provider value={organization}>
+          <EventsPageContent
+            totalEventCount={totalEventCount}
+            eventView={eventView}
+            organization={organization}
+            location={initialData.router.location}
+            transactionName={transactionName}
+            spanOperationBreakdownFilter={SpanOperationBreakdownFilter.None}
+            onChangeSpanOperationBreakdownFilter={() => {}}
+            eventsDisplayFilterName={EventsDisplayFilterName.p100}
+            onChangeEventsDisplayFilter={() => {}}
+            webVital={WebVital.LCP}
+            setError={() => {}}
+          />
+        </OrganizationContext.Provider>
+      </RouteContext.Provider>,
       initialData.routerContext
     );
     await tick();

+ 7 - 1
static/app/views/performance/transactionSummary/transactionEvents/content.tsx

@@ -18,6 +18,7 @@ import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAna
 import EventView from 'sentry/utils/discover/eventView';
 import {WebVital} from 'sentry/utils/fields';
 import {decodeScalar} from 'sentry/utils/queryString';
+import {useRoutes} from 'sentry/utils/useRoutes';
 
 import Filter, {filterToSearchConditions, SpanOperationBreakdownFilter} from '../filter';
 import {SetStateAction} from '../types';
@@ -60,7 +61,7 @@ function EventsContent(props: Props) {
     setError,
     totalEventCount,
   } = props;
-
+  const routes = useRoutes();
   const eventView = originalEventView.clone();
   const transactionsListTitles = TRANSACTIONS_LIST_TITLES.slice();
 
@@ -78,6 +79,10 @@ function EventsContent(props: Props) {
     transactionsListTitles.splice(2, 1, t(`${spanOperationBreakdownFilter} duration`));
   }
 
+  if (organization.features.includes('session-replay-ui')) {
+    transactionsListTitles.push(t('replay id'));
+  }
+
   return (
     <Layout.Main fullWidth>
       <Search {...props} />
@@ -85,6 +90,7 @@ function EventsContent(props: Props) {
         totalEventCount={totalEventCount}
         eventView={eventView}
         organization={organization}
+        routes={routes}
         location={location}
         setError={setError}
         columnTitles={transactionsListTitles}

+ 3 - 0
static/app/views/performance/transactionSummary/transactionEvents/eventsTable.spec.tsx

@@ -169,6 +169,7 @@ describe('Performance GridEditable Table', function () {
         totalEventCount={totalEventCount}
         eventView={eventView}
         organization={organization}
+        routes={initialData.router.routes}
         location={initialData.router.location}
         setError={() => {}}
         columnTitles={transactionsListTitles}
@@ -219,6 +220,7 @@ describe('Performance GridEditable Table', function () {
         totalEventCount={totalEventCount}
         eventView={eventView}
         organization={organization}
+        routes={initialData.router.routes}
         location={initialData.router.location}
         setError={() => {}}
         columnTitles={transactionsListTitles}
@@ -254,6 +256,7 @@ describe('Performance GridEditable Table', function () {
         totalEventCount={totalEventCount}
         eventView={eventView}
         organization={organization}
+        routes={initialData.router.routes}
         location={initialData.router.location}
         setError={() => {}}
         columnTitles={transactionsListTitles}

+ 23 - 2
static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx

@@ -1,5 +1,5 @@
 import {Component, Fragment} from 'react';
-import {browserHistory} from 'react-router';
+import {browserHistory, RouteContextInterface} from 'react-router';
 import styled from '@emotion/styled';
 import {Location, LocationDescriptor, LocationDescriptorObject} from 'history';
 
@@ -35,6 +35,7 @@ import {TableColumn} from 'sentry/views/eventsV2/table/types';
 
 import {COLUMN_TITLES} from '../../data';
 import {
+  generateReplayLink,
   generateTraceLink,
   generateTransactionLink,
   normalizeSearchConditions,
@@ -83,6 +84,7 @@ type Props = {
   eventView: EventView;
   location: Location;
   organization: Organization;
+  routes: RouteContextInterface['routes'];
   setError: (msg: string | undefined) => void;
   transactionName: string;
   columnTitles?: string[];
@@ -110,6 +112,7 @@ class EventsTable extends Component<Props, State> {
   };
 
   api = new Client();
+  replayLinkGenerator = generateReplayLink(this.props.routes);
 
   handleCellAction = (column: TableColumn<keyof TableDataRow>) => {
     return (action: Actions, value: React.ReactText) => {
@@ -197,6 +200,23 @@ class EventsTable extends Component<Props, State> {
       );
     }
 
+    if (field === 'replayId') {
+      const target: LocationDescriptor | null = dataRow.replayId
+        ? this.replayLinkGenerator(organization, dataRow, undefined)
+        : null;
+
+      return (
+        <CellAction
+          column={column}
+          dataRow={dataRow}
+          handleCellAction={this.handleCellAction(column)}
+          allowActions={allowActions}
+        >
+          {target ? <Link to={target}>{rendered}</Link> : rendered}
+        </CellAction>
+      );
+    }
+
     const fieldName = getAggregateAlias(field);
     const value = dataRow[fieldName];
     if (tableMeta[fieldName] === 'integer' && defined(value) && value > 999) {
@@ -272,10 +292,11 @@ class EventsTable extends Component<Props, State> {
       };
     }
     const currentSort = eventView.sortForField(field, tableMeta);
-    // Event id and Trace id are technically sortable but we don't want to sort them here since sorting by a uuid value doesn't make sense
+    // EventId, TraceId, and ReplayId are technically sortable but we don't want to sort them here since sorting by a uuid value doesn't make sense
     const canSort =
       field.field !== 'id' &&
       field.field !== 'trace' &&
+      field.field !== 'replayid' &&
       field.field !== SPAN_OP_RELATIVE_BREAKDOWN_FIELD &&
       isFieldSortable(field, tableMeta);
 

+ 20 - 8
static/app/views/performance/transactionSummary/transactionEvents/index.spec.tsx

@@ -1,5 +1,6 @@
 import {browserHistory} from 'react-router';
 
+import {initializeOrg} from 'sentry-test/initializeOrg';
 import {
   initializeData as _initializeData,
   initializeDataSettings,
@@ -10,19 +11,30 @@ import ProjectsStore from 'sentry/stores/projectsStore';
 import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
 import {OrganizationContext} from 'sentry/views/organizationContext';
 import TransactionEvents from 'sentry/views/performance/transactionSummary/transactionEvents';
+import {RouteContext} from 'sentry/views/routeContext';
 
 import {EVENTS_TABLE_RESPONSE_FIELDS, MOCK_EVENTS_TABLE_DATA} from './eventsTable.spec';
 
 const WrappedComponent = ({data}) => {
+  const {router} = initializeOrg();
   return (
-    <OrganizationContext.Provider value={data.organization}>
-      <MEPSettingProvider>
-        <TransactionEvents
-          organization={data.organization}
-          location={data.router.location}
-        />
-      </MEPSettingProvider>
-    </OrganizationContext.Provider>
+    <RouteContext.Provider
+      value={{
+        router,
+        location: router.location,
+        params: {},
+        routes: [],
+      }}
+    >
+      <OrganizationContext.Provider value={data.organization}>
+        <MEPSettingProvider>
+          <TransactionEvents
+            organization={data.organization}
+            location={data.router.location}
+          />
+        </MEPSettingProvider>
+      </OrganizationContext.Provider>
+    </RouteContext.Provider>
   );
 };
 

+ 5 - 0
static/app/views/performance/transactionSummary/transactionEvents/index.tsx

@@ -237,9 +237,11 @@ function getWebVital(location: Location): WebVital | undefined {
 
 function generateEventView({
   location,
+  organization,
   transactionName,
 }: {
   location: Location;
+  organization: Organization;
   transactionName: string;
 }): EventView {
   const query = decodeScalar(location.query.query, '');
@@ -263,6 +265,9 @@ function generateEventView({
     'trace',
     'timestamp',
   ];
+  if (organization.features.includes('session-replay-ui')) {
+    fields.push('replayId');
+  }
   const breakdown = decodeFilterFromLocation(location);
   if (breakdown !== SpanOperationBreakdownFilter.None) {
     fields.splice(2, 1, `spans.${breakdown}`);

+ 1 - 1
static/app/views/performance/transactionSummary/utils.tsx

@@ -139,7 +139,7 @@ export function generateReplayLink(routes: PlainRoute<any>[]) {
   return (
     organization: Organization,
     tableRow: TableDataRow,
-    _query: Query
+    _query: Query | undefined
   ): LocationDescriptor => {
     const replayId = tableRow.replayId;
     if (!replayId) {