Browse Source

fix(replay): Normalize urls that link into replay details (#53451)

<!-- Describe your PR here. -->
Ryan Albrecht 1 year ago
parent
commit
38200101f2

+ 1 - 2
static/app/components/events/eventReplay/replayPreview.spec.tsx

@@ -24,8 +24,7 @@ const mockEvent = {
   dateCreated: '2022-09-22T16:59:41.596000Z',
 };
 
-const mockButtonHref =
-  '/replays/761104e184c64d439ee1014b72b4d83b/?referrer=%2Forganizations%2F%3AorgId%2Fissues%2F%3AgroupId%2Freplays%2F&t=62&t_main=console';
+const mockButtonHref = `/organizations/${mockOrgSlug}/replays/761104e184c64d439ee1014b72b4d83b/?referrer=%2Forganizations%2F%3AorgId%2Fissues%2F%3AgroupId%2Freplays%2F&t=62&t_main=console`;
 
 // Mock screenfull library
 jest.mock('screenfull', () => ({

+ 2 - 1
static/app/components/events/eventReplay/replayPreview.tsx

@@ -17,6 +17,7 @@ import {Event} from 'sentry/types/event';
 import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
 import useReplayReader from 'sentry/utils/replays/hooks/useReplayReader';
 import {useRoutes} from 'sentry/utils/useRoutes';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
 
 type Props = {
@@ -100,7 +101,7 @@ function ReplayPreview({orgSlug, replaySlug, event, onClickOpenReplay}: Props) {
   }
 
   const fullReplayUrl = {
-    pathname: `/replays/${replayId}/`,
+    pathname: normalizeUrl(`/organizations/${orgSlug}/replays/${replayId}/`),
     query: {
       referrer: getRouteStringFromRoutes(routes),
       t_main: 'console',

+ 4 - 1
static/app/components/group/issueReplayCount.tsx

@@ -8,6 +8,7 @@ import {IconPlay} from 'sentry/icons';
 import {t, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import useOrganization from 'sentry/utils/useOrganization';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 
 type Props = {
   groupId: string;
@@ -35,7 +36,9 @@ function IssueReplayCount({groupId}: Props) {
   return (
     <Tooltip title={count > 50 ? titleOver50 : title50OrLess}>
       <ReplayCountLink
-        to={`/organizations/${organization.slug}/issues/${groupId}/replays/`}
+        to={normalizeUrl(
+          `/organizations/${organization.slug}/issues/${groupId}/replays/`
+        )}
         aria-label="replay-count"
       >
         <IconPlay size="xs" />

+ 4 - 1
static/app/components/onboardingWizard/taskConfig.tsx

@@ -21,6 +21,7 @@ import {
 import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import EventWaiter from 'sentry/utils/eventWaiter';
 import withApi from 'sentry/utils/withApi';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 
 import OnboardingProjectsCard from './onboardingProjectsCard';
 
@@ -313,7 +314,9 @@ export function getOnboardingTasks({
       skippable: true,
       requisites: [OnboardingTaskKey.FIRST_PROJECT, OnboardingTaskKey.FIRST_EVENT],
       actionType: 'app',
-      location: `/organizations/${organization.slug}/replays/#replay-sidequest`,
+      location: normalizeUrl(
+        `/organizations/${organization.slug}/replays/#replay-sidequest`
+      ),
       display: organization.features?.includes('session-replay'),
       SupplementComponent: withApi(({api, task, onCompleteTask}: FirstEventWaiterProps) =>
         !!projects?.length && task.requisiteTasks.length === 0 && !task.completionSeen ? (

+ 3 - 3
static/app/components/replays/header/deleteButton.tsx

@@ -17,17 +17,17 @@ interface DeleteButtonProps {
 function DeleteButton({projectSlug, replayId}: DeleteButtonProps) {
   const api = useApi();
   const navigate = useNavigate();
-  const orgSlug = useOrganization().slug;
+  const organization = useOrganization();
 
   const handleDelete = async () => {
     try {
       await api.requestPromise(
-        `/projects/${orgSlug}/${projectSlug}/replays/${replayId}/`,
+        `/projects/${organization.slug}/${projectSlug}/replays/${replayId}/`,
         {
           method: 'DELETE',
         }
       );
-      navigate(`/organizations/${orgSlug}/replays/`, {replace: true});
+      navigate(`/organizations/${organization.slug}/replays/`, {replace: true});
     } catch (err) {
       addErrorMessage(t('Failed to delete replay'));
       Sentry.captureException(err);

+ 2 - 1
static/app/components/replays/header/detailsPageBreadcrumbs.tsx

@@ -8,6 +8,7 @@ import EventView from 'sentry/utils/discover/eventView';
 import {getShortEventId} from 'sentry/utils/events';
 import {useLocation} from 'sentry/utils/useLocation';
 import useProjects from 'sentry/utils/useProjects';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import type {ReplayRecord} from 'sentry/views/replays/types';
 
 type Props = {
@@ -33,7 +34,7 @@ function DetailsPageBreadcrumbs({orgSlug, replayRecord}: Props) {
       crumbs={[
         {
           to: {
-            pathname: `/organizations/${orgSlug}/replays/`,
+            pathname: normalizeUrl(`/organizations/${orgSlug}/replays/`),
             query: eventView.generateQueryStringObject(),
           },
           label: t('Session Replay'),

+ 6 - 3
static/app/views/issueDetails/groupReplays/groupReplays.spec.tsx

@@ -293,7 +293,10 @@ describe('GroupReplays', () => {
               finished_at: new Date('2022-09-15T06:54:00+00:00'),
               id: '346789a703f6454384f1de473b8b9fcc',
               started_at: new Date('2022-09-15T06:50:00+00:00'),
-              urls: ['https://dev.getsentry.net:7999/replays/', '/replays/?project=2'],
+              urls: [
+                'https://dev.getsentry.net:7999/replays/',
+                '/organizations/sentry-emerging-tech/replays/?project=2',
+              ],
             },
             {
               ...TestStubs.ReplayList()[0],
@@ -339,13 +342,13 @@ describe('GroupReplays', () => {
       // Expect the first row to have the correct href
       expect(screen.getAllByRole('link', {name: 'testDisplayName'})[0]).toHaveAttribute(
         'href',
-        `/replays/${REPLAY_ID_1}/?${expectedQuery}`
+        `/organizations/org-slug/replays/${REPLAY_ID_1}/?${expectedQuery}`
       );
 
       // Expect the second row to have the correct href
       expect(screen.getAllByRole('link', {name: 'testDisplayName'})[1]).toHaveAttribute(
         'href',
-        `/replays/${REPLAY_ID_2}/?${expectedQuery}`
+        `/organizations/org-slug/replays/${REPLAY_ID_2}/?${expectedQuery}`
       );
 
       // Expect the first row to have the correct duration

+ 2 - 2
static/app/views/performance/transactionSummary/transactionReplays/index.spec.tsx

@@ -247,13 +247,13 @@ describe('TransactionReplays', () => {
     // Expect the first row to have the correct href
     expect(screen.getAllByRole('link', {name: 'testDisplayName'})[0]).toHaveAttribute(
       'href',
-      `/replays/346789a703f6454384f1de473b8b9fcc/?${expectedQuery}`
+      `/organizations/org-slug/replays/346789a703f6454384f1de473b8b9fcc/?${expectedQuery}`
     );
 
     // Expect the second row to have the correct href
     expect(screen.getAllByRole('link', {name: 'testDisplayName'})[1]).toHaveAttribute(
       'href',
-      `/replays/b05dae9b6be54d21a4d5ad9f8f02b780/?${expectedQuery}`
+      `/organizations/org-slug/replays/b05dae9b6be54d21a4d5ad9f8f02b780/?${expectedQuery}`
     );
 
     // Expect the first row to have the correct duration

+ 6 - 3
static/app/views/performance/transactionSummary/utils.tsx

@@ -10,6 +10,7 @@ import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
 import {getTransactionDetailsUrl} from 'sentry/utils/performance/urls';
 import {generateProfileFlamechartRoute} from 'sentry/utils/profiling/routes';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
 
 export enum DisplayModes {
@@ -175,7 +176,7 @@ export function generateReplayLink(routes: PlainRoute<any>[]) {
   const referrer = getRouteStringFromRoutes(routes);
 
   return (
-    _: Organization,
+    organization: Organization,
     tableRow: TableDataRow,
     _query: Query | undefined
   ): LocationDescriptor => {
@@ -186,7 +187,9 @@ export function generateReplayLink(routes: PlainRoute<any>[]) {
 
     if (!tableRow.timestamp) {
       return {
-        pathname: `/replays/${replayId}/`,
+        pathname: normalizeUrl(
+          `/organizations/${organization.slug}/replays/${replayId}/`
+        ),
         query: {
           referrer,
         },
@@ -199,7 +202,7 @@ export function generateReplayLink(routes: PlainRoute<any>[]) {
       : undefined;
 
     return {
-      pathname: `/replays/${replayId}/`,
+      pathname: normalizeUrl(`/organizations/${organization.slug}/replays/${replayId}/`),
       query: {
         event_t: transactionStartTimestamp,
         referrer,

+ 2 - 1
static/app/views/replays/replayTable/tableCell.tsx

@@ -21,6 +21,7 @@ import {getShortEventId} from 'sentry/utils/events';
 import {useLocation} from 'sentry/utils/useLocation';
 import useMedia from 'sentry/utils/useMedia';
 import useProjects from 'sentry/utils/useProjects';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import type {ReplayListRecordWithTx} from 'sentry/views/performance/transactionSummary/transactionReplays/useReplaysWithTxData';
 import type {ReplayListRecord} from 'sentry/views/replays/types';
 
@@ -62,7 +63,7 @@ export function ReplayCell({
   const project = projects.find(p => p.id === replay.project_id);
 
   const replayDetails = {
-    pathname: `/replays/${replay.id}/`,
+    pathname: normalizeUrl(`/organizations/${organization.slug}/replays/${replay.id}/`),
     query: {
       referrer,
       ...eventView.generateQueryStringObject(),