Repository: getsentry/sentry
Pull request: ref(replay): Extract the ReplaySection to its own file and update logic
The logic inside the ternary's was getting a little difficult to follow. So I broke it out into it's own file with return statements to make it easier to read.

The basic text output looks like this when rendered "No replay captured" in this case:
![SCR-20240118-nvpu](https://github.com/getsentry/sentry/assets/187460/bab85498-ec58-41fc-91d5-c992fda4fab9)

And the CTA version still looking good:
![SCR-20240118-nwdy](https://github.com/getsentry/sentry/assets/187460/2d9254f2-0a71-475d-b2a7-b28e666bfab7)


Also, i made a truth-table to track down everything that needs to happen. The column on the right matches with each `return` statement in the code (counting from top of function), and is 1:1 with the outcome we want to see. Blank spaces means use the same value as the cell above, and `-` means the value doesn't matter to the return value. Three return values have `<-` in there... these states should be impossible, but that's the value that the code will return (in the other cases, i decided first and then wrote code to match).

| platformSupported | replayId | hasReplayId | isFetchingSentOneReplay | hasSentOneReplay | RETURN VALUE | Step |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| false | - | - | - | - | Sorry / Upsell | 1 |
| true | falsey | undefined | true | - | Loading | 3 |
|  |  |  | false | false | CTA | 4 |
|  |  |  | false | true | *None captured | 6 |
|  |  | false | true | - | Loading | 3 |
|  |  |  | false | false | CTA | 4 |
|  |  |  | false | true | *None Captured | 6 |
|  |  | true | true | - | Loading <- | 3 |
|  |  |  | false | false | CTA <- | 4 |
|  |  |  | false | true | *None Captured | 6 |
|  | truthy | undefined | - | - | Loading | 3 |
|  |  |  | - | - | Loading | 3 |
|  |  |  | - | - | Loading | 3 |
|  |  | false | true | - | Loading | 3 |
|  |  |  | false | false | CTA | 4 |
|  |  |  | false | true | *Not Found | 5 |
|  |  | true | - | - | Show preview | 2 |
|  |  |  | - | - | Show preview | 2 |
|  |  |  | - | - | Show preview | 2 |

Fixes https://github.com/getsentry/sentry/issues/61426

Problem statement:
[User Feedback] Replay can take a few seconds to load and it's not clear until then if there's a replay or not (large CLS)
@billyvg 

> it's a bit jarring to have Linked Replay section appear after a few seconds -- we should probably always show the header and give it a loading state (as well as all the other states) instead of having it appear only if there is a linked replay

Hints from issue discussion:
Routing to @getsentry/product-owners-user-feedback for [triage](https://develop.sentry.dev/processing-tickets/#3-triage) ⏲️

Commit message: ref(replay): Extract the ReplaySection to its own file and update logic

Patch to review:
diff --git a/static/app/components/feedback/feedbackItem/feedbackItem.tsx b/static/app/components/feedback/feedbackItem/feedbackItem.tsx
index 58175ccad0d9aa..07e0a93283a955 100644
--- a/static/app/components/feedback/feedbackItem/feedbackItem.tsx
+++ b/static/app/components/feedback/feedbackItem/feedbackItem.tsx
@@ -6,21 +6,17 @@ import CrashReportSection from 'sentry/components/feedback/feedbackItem/crashRep
 import FeedbackActivitySection from 'sentry/components/feedback/feedbackItem/feedbackActivitySection';
 import FeedbackItemHeader from 'sentry/components/feedback/feedbackItem/feedbackItemHeader';
 import Section from 'sentry/components/feedback/feedbackItem/feedbackItemSection';
+import FeedbackReplay from 'sentry/components/feedback/feedbackItem/feedbackReplay';
 import FeedbackViewers from 'sentry/components/feedback/feedbackItem/feedbackViewers';
-import ReplayInlineCTAPanel from 'sentry/components/feedback/feedbackItem/replayInlineCTAPanel';
-import ReplaySection from 'sentry/components/feedback/feedbackItem/replaySection';
 import TagsSection from 'sentry/components/feedback/feedbackItem/tagsSection';
 import PanelItem from 'sentry/components/panels/panelItem';
 import {Flex} from 'sentry/components/profiling/flex';
 import TextCopyInput from 'sentry/components/textCopyInput';
-import {replayPlatforms} from 'sentry/data/platformCategories';
 import {IconChat, IconFire, IconLink, IconPlay, IconTag} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Event} from 'sentry/types';
 import type {FeedbackIssue} from 'sentry/utils/feedback/types';
-import useReplayCountForFeedbacks from 'sentry/utils/replayCount/useReplayCountForFeedbacks';
-import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
 import useOrganization from 'sentry/utils/useOrganization';
 
 interface Props {
@@ -31,16 +27,9 @@ interface Props {
 
 export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
   const organization = useOrganization();
-  const {feedbackHasReplay} = useReplayCountForFeedbacks();
-  const hasReplayId = feedbackHasReplay(feedbackItem.id);
-
   const url = eventData?.tags.find(tag => tag.key === 'url');
-  const replayId = eventData?.contexts?.feedback?.replay_id;
   const crashReportId = eventData?.contexts?.feedback?.associated_event_id;
 
-  const {hasSentOneReplay} = useHaveSelectedProjectsSentAnyReplayEvents();
-  const platformSupported = replayPlatforms.includes(feedbackItem.platform);
-
   return (
     <Fragment>
       <FeedbackItemHeader eventData={eventData} feedbackItem={feedbackItem} />
@@ -77,21 +66,13 @@ export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
           </Section>
         )}
 
-        {hasReplayId && replayId ? (
-          <Section icon={<IconPlay size="xs" />} title={t('Linked Replay')}>
-            <ErrorBoundary mini>
-              <ReplaySection
-                eventTimestampMs={new Date(feedbackItem.firstSeen).getTime()}
-                organization={organization}
-                replayId={replayId}
-              />
-            </ErrorBoundary>
-          </Section>
-        ) : hasSentOneReplay || !platformSupported ? null : (
-          <Section icon={<IconPlay size="xs" />} title={t('Linked Replay')}>
-            <ReplayInlineCTAPanel />
-          </Section>
-        )}
+        <Section icon={<IconPlay size="xs" />} title={t('Linked Replay')}>
+          <FeedbackReplay
+            eventData={eventData}
+            feedbackItem={feedbackItem}
+            organization={organization}
+          />
+        </Section>
 
         <Section icon={<IconTag size="xs" />} title={t('Tags')}>
           <TagsSection tags={tags} />
diff --git a/static/app/components/feedback/feedbackItem/feedbackReplay.tsx b/static/app/components/feedback/feedbackItem/feedbackReplay.tsx
new file mode 100644
index 00000000000000..6b8787ba0419d4
--- /dev/null
+++ b/static/app/components/feedback/feedbackItem/feedbackReplay.tsx
@@ -0,0 +1,52 @@
+import {Fragment} from 'react';
+
+import ErrorBoundary from 'sentry/components/errorBoundary';
+import ReplayInlineCTAPanel from 'sentry/components/feedback/feedbackItem/replayInlineCTAPanel';
+import ReplaySection from 'sentry/components/feedback/feedbackItem/replaySection';
+import {replayPlatforms} from 'sentry/data/platformCategories';
+import type {Event, Organization} from 'sentry/types';
+import type {FeedbackIssue} from 'sentry/utils/feedback/types';
+import useReplayCountForFeedbacks from 'sentry/utils/replayCount/useReplayCountForFeedbacks';
+import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
+
+interface Props {
+  eventData: Event | undefined;
+  feedbackItem: FeedbackIssue;
+  organization: Organization;
+}
+
+export default function FeedbackReplay({eventData, feedbackItem, organization}: Props) {
+  const {feedbackHasReplay} = useReplayCountForFeedbacks();
+  const hasReplayId = feedbackHasReplay(feedbackItem.id);
+
+  const replayId = eventData?.contexts?.feedback?.replay_id;
+  const {hasSentOneReplay, fetching: isFetchingSentOneReplay} =
+    useHaveSelectedProjectsSentAnyReplayEvents();
+  const platformSupported = replayPlatforms.includes(feedbackItem.platform);
+
+  if (!platformSupported) {
+    return <Fragment>This platform isn't supported.</Fragment>;
+  }
+
+  if (replayId && hasReplayId) {
+    return (
+      <ErrorBoundary mini>
+        <ReplaySection
+          eventTimestampMs={new Date(feedbackItem.firstSeen).getTime()}
+          organization={organization}
+          replayId={replayId}
+        />
+      </ErrorBoundary>
+    );
+  }
+
+  if ((replayId && hasReplayId === undefined) || isFetchingSentOneReplay) {
+    return <Fragment>Checking things out...</Fragment>;
+  }
+
+  if (!hasSentOneReplay) {
+    return <ReplayInlineCTAPanel />;
+  }
+
+  return <Fragment>No replay captured</Fragment>;
+}


Code review comments:
diff_hunk: @@ -0,0 +1,52 @@
+import {Fragment} from 'react';
+
+import ErrorBoundary from 'sentry/components/errorBoundary';
+import ReplayInlineCTAPanel from 'sentry/components/feedback/feedbackItem/replayInlineCTAPanel';
+import ReplaySection from 'sentry/components/feedback/feedbackItem/replaySection';
+import {replayPlatforms} from 'sentry/data/platformCategories';
+import type {Event, Organization} from 'sentry/types';
+import type {FeedbackIssue} from 'sentry/utils/feedback/types';
+import useReplayCountForFeedbacks from 'sentry/utils/replayCount/useReplayCountForFeedbacks';
+import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
+
+interface Props {
+  eventData: Event | undefined;
+  feedbackItem: FeedbackIssue;
+  organization: Organization;
+}
+
+export default function FeedbackReplay({eventData, feedbackItem, organization}: Props) {
+  const {feedbackHasReplay} = useReplayCountForFeedbacks();
+  const hasReplayId = feedbackHasReplay(feedbackItem.id);
+
+  const replayId = eventData?.contexts?.feedback?.replay_id;
+  const {hasSentOneReplay, fetching: isFetchingSentOneReplay} =
+    useHaveSelectedProjectsSentAnyReplayEvents();
+  const platformSupported = replayPlatforms.includes(feedbackItem.platform);
+
+  if (!platformSupported) {
+    return <Fragment>This platform isn't supported.</Fragment>;
+  }
+
+  if (replayId && hasReplayId) {
+    return (
+      <ErrorBoundary mini>
+        <ReplaySection
+          eventTimestampMs={new Date(feedbackItem.firstSeen).getTime()}
+          organization={organization}
+          replayId={replayId}
+        />
+      </ErrorBoundary>
+    );
+  }
+
+  if ((replayId && hasReplayId === undefined) || isFetchingSentOneReplay) {
+    return <Fragment>Checking things out...</Fragment>;
path: static/app/components/feedback/feedbackItem/feedbackReplay.tsx
text: @user1:
should this be a loading placeholder instead?

@author:
d'oh, it should at least be translated


@user1:
true... the other spots too, i also missed those!

@author:
fixed!

diff_hunk: @@ -0,0 +1,52 @@
+import {Fragment} from 'react';
+
+import ErrorBoundary from 'sentry/components/errorBoundary';
+import ReplayInlineCTAPanel from 'sentry/components/feedback/feedbackItem/replayInlineCTAPanel';
+import ReplaySection from 'sentry/components/feedback/feedbackItem/replaySection';
+import {replayPlatforms} from 'sentry/data/platformCategories';
+import type {Event, Organization} from 'sentry/types';
+import type {FeedbackIssue} from 'sentry/utils/feedback/types';
+import useReplayCountForFeedbacks from 'sentry/utils/replayCount/useReplayCountForFeedbacks';
+import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
+
+interface Props {
+  eventData: Event | undefined;
+  feedbackItem: FeedbackIssue;
+  organization: Organization;
+}
+
+export default function FeedbackReplay({eventData, feedbackItem, organization}: Props) {
+  const {feedbackHasReplay} = useReplayCountForFeedbacks();
+  const hasReplayId = feedbackHasReplay(feedbackItem.id);
+
+  const replayId = eventData?.contexts?.feedback?.replay_id;
+  const {hasSentOneReplay, fetching: isFetchingSentOneReplay} =
+    useHaveSelectedProjectsSentAnyReplayEvents();
+  const platformSupported = replayPlatforms.includes(feedbackItem.platform);
+
+  if (!platformSupported) {
+    return <Fragment>This platform isn't supported.</Fragment>;
+  }
+
+  if (replayId && hasReplayId) {
+    return (
+      <ErrorBoundary mini>
+        <ReplaySection
+          eventTimestampMs={new Date(feedbackItem.firstSeen).getTime()}
+          organization={organization}
+          replayId={replayId}
+        />
+      </ErrorBoundary>
+    );
+  }
+
+  if ((replayId && hasReplayId === undefined) || isFetchingSentOneReplay) {
+    return <Fragment>Checking things out...</Fragment>;
+  }
+
+  if (!hasSentOneReplay) {
+    return <ReplayInlineCTAPanel />;
+  }
+
+  return <Fragment>No replay captured</Fragment>;
path: static/app/components/feedback/feedbackItem/feedbackReplay.tsx
text: @user1:
in the future we could even turn this into something similar to what we have on issue details 
![image](https://github.com/getsentry/sentry/assets/56095982/b6aaaa1f-b573-4aa0-b5e1-d89ad69bdb30)


@author:
oh yeah, that would be better. There's the set of cases where `replayId` is truthy, but `hasReplayId` is `false`, this component fits that case. I'll update to pull that in

@author:
pulled it in!

diff_hunk: @@ -0,0 +1,52 @@
+import {Fragment} from 'react';
+
+import ErrorBoundary from 'sentry/components/errorBoundary';
+import ReplayInlineCTAPanel from 'sentry/components/feedback/feedbackItem/replayInlineCTAPanel';
+import ReplaySection from 'sentry/components/feedback/feedbackItem/replaySection';
+import {replayPlatforms} from 'sentry/data/platformCategories';
+import type {Event, Organization} from 'sentry/types';
+import type {FeedbackIssue} from 'sentry/utils/feedback/types';
+import useReplayCountForFeedbacks from 'sentry/utils/replayCount/useReplayCountForFeedbacks';
+import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
+
+interface Props {
+  eventData: Event | undefined;
+  feedbackItem: FeedbackIssue;
+  organization: Organization;
+}
+
+export default function FeedbackReplay({eventData, feedbackItem, organization}: Props) {
+  const {feedbackHasReplay} = useReplayCountForFeedbacks();
+  const hasReplayId = feedbackHasReplay(feedbackItem.id);
+
+  const replayId = eventData?.contexts?.feedback?.replay_id;
+  const {hasSentOneReplay, fetching: isFetchingSentOneReplay} =
+    useHaveSelectedProjectsSentAnyReplayEvents();
+  const platformSupported = replayPlatforms.includes(feedbackItem.platform);
+
+  if (!platformSupported) {
+    return <Fragment>This platform isn't supported.</Fragment>;
path: static/app/components/feedback/feedbackItem/feedbackReplay.tsx
text: @user1:
maybe we could clarify that the platform doesn't support _replay_ specifically? small nit though

@author:
I pulled in the existing alert from the replay onboarding sidebar