Browse Source

feat(replays): Create JS hooks for SaaS powered subscription update messaging (#44171)

Creates JS hooks so we can show:
- an "Upgrade to AM2" workflow for all AM1 users
- some "the beta is over" alerts for any replay beta orgs

Relates to: https://github.com/getsentry/getsentry/issues/9452
Ryan Albrecht 2 years ago
parent
commit
8eef3a73b0

+ 6 - 0
static/app/types/hooks.tsx

@@ -77,6 +77,10 @@ type DisabledMemberTooltipProps = {children: React.ReactNode};
 
 
 type DashboardHeadersProps = {organization: Organization};
 type DashboardHeadersProps = {organization: Organization};
 
 
+type ReplayGracePeriodAlertProps = {organization: Organization};
+
+type ReplayOnboardinCTAProps = {children: React.ReactNode; organization: Organization};
+
 type FirstPartyIntegrationAlertProps = {
 type FirstPartyIntegrationAlertProps = {
   integrations: Integration[];
   integrations: Integration[];
   hideCTA?: boolean;
   hideCTA?: boolean;
@@ -105,6 +109,8 @@ export type ComponentHooks = {
   'component:header-selector-items': () => React.ComponentType<SelectorItemsProps>;
   'component:header-selector-items': () => React.ComponentType<SelectorItemsProps>;
   'component:member-list-header': () => React.ComponentType<MemberListHeaderProps>;
   'component:member-list-header': () => React.ComponentType<MemberListHeaderProps>;
   'component:org-stats-banner': () => React.ComponentType<DashboardHeadersProps>;
   'component:org-stats-banner': () => React.ComponentType<DashboardHeadersProps>;
+  'component:replay-beta-grace-period-alert': () => React.ComponentType<ReplayGracePeriodAlertProps>;
+  'component:replay-onboarding-cta': () => React.ComponentType<ReplayOnboardinCTAProps>;
   'component:superuser-access-category': React.FC<any>;
   'component:superuser-access-category': React.FC<any>;
 };
 };
 
 

+ 20 - 8
static/app/views/replays/index.tsx

@@ -2,6 +2,7 @@ import {RouteComponentProps} from 'react-router';
 
 
 import Feature from 'sentry/components/acl/feature';
 import Feature from 'sentry/components/acl/feature';
 import {Alert} from 'sentry/components/alert';
 import {Alert} from 'sentry/components/alert';
+import HookOrDefault from 'sentry/components/hookOrDefault';
 import * as Layout from 'sentry/components/layouts/thirds';
 import * as Layout from 'sentry/components/layouts/thirds';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {Organization} from 'sentry/types';
 import {Organization} from 'sentry/types';
@@ -12,21 +13,32 @@ type Props = RouteComponentProps<{}, {}> & {
   organization: Organization;
   organization: Organization;
 };
 };
 
 
-function ReplaysContainer({organization, children}: Props) {
-  function renderNoAccess() {
-    return (
-      <Layout.Page withPadding>
-        <Alert type="warning">{t("You don't have access to this feature")}</Alert>
-      </Layout.Page>
-    );
-  }
+function renderNoAccess() {
+  return (
+    <Layout.Page withPadding>
+      <Alert type="warning">{t("You don't have access to this feature")}</Alert>
+    </Layout.Page>
+  );
+}
+
+const BetaGracePeriodAlertHook = HookOrDefault({
+  hookName: 'component:replay-beta-grace-period-alert',
+});
 
 
+function ReplaysContainer({organization, children}: Props) {
   return (
   return (
     <Feature
     <Feature
       features={['session-replay-ui']}
       features={['session-replay-ui']}
       organization={organization}
       organization={organization}
       renderDisabled={renderNoAccess}
       renderDisabled={renderNoAccess}
     >
     >
+      <Feature
+        features={['session-replay-beta-grace']}
+        organization={organization}
+        renderDisabled={false}
+      >
+        <BetaGracePeriodAlertHook organization={organization} />
+      </Feature>
       {children}
       {children}
     </Feature>
     </Feature>
   );
   );

+ 45 - 6
static/app/views/replays/list/replayOnboardingPanel.tsx

@@ -1,16 +1,19 @@
+import {Fragment} from 'react';
 import styled from '@emotion/styled';
 import styled from '@emotion/styled';
 
 
 import emptyStateImg from 'sentry-images/spot/replays-empty-state.svg';
 import emptyStateImg from 'sentry-images/spot/replays-empty-state.svg';
 
 
+import Feature from 'sentry/components/acl/feature';
+import {Button} from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import ButtonBar from 'sentry/components/buttonBar';
+import HookOrDefault from 'sentry/components/hookOrDefault';
 import OnboardingPanel from 'sentry/components/onboardingPanel';
 import OnboardingPanel from 'sentry/components/onboardingPanel';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import PreferencesStore from 'sentry/stores/preferencesStore';
 import PreferencesStore from 'sentry/stores/preferencesStore';
 import {useLegacyStore} from 'sentry/stores/useLegacyStore';
 import {useLegacyStore} from 'sentry/stores/useLegacyStore';
+import {useReplayOnboardingSidebarPanel} from 'sentry/utils/replays/hooks/useReplayOnboarding';
+import useOrganization from 'sentry/utils/useOrganization';
 
 
-interface Props {
-  children?: React.ReactNode;
-}
 type Breakpoints = {
 type Breakpoints = {
   large: string;
   large: string;
   medium: string;
   medium: string;
@@ -18,7 +21,12 @@ type Breakpoints = {
   xlarge: string;
   xlarge: string;
 };
 };
 
 
-export default function ReplayOnboardingPanel(props: Props) {
+const OnboardingCTAHook = HookOrDefault({
+  hookName: 'component:replay-onboarding-cta',
+  defaultComponent: ({children}) => <Fragment>{children}</Fragment>,
+});
+
+export default function ReplayOnboardingPanel() {
   const preferences = useLegacyStore(PreferencesStore);
   const preferences = useLegacyStore(PreferencesStore);
 
 
   const breakpoints = preferences.collapsed
   const breakpoints = preferences.collapsed
@@ -35,16 +43,46 @@ export default function ReplayOnboardingPanel(props: Props) {
         xlarge: '1450px',
         xlarge: '1450px',
       };
       };
 
 
+  const organization = useOrganization();
+
   return (
   return (
     <OnboardingPanel image={<HeroImage src={emptyStateImg} breakpoints={breakpoints} />}>
     <OnboardingPanel image={<HeroImage src={emptyStateImg} breakpoints={breakpoints} />}>
+      <Feature
+        features={['session-replay-ga']}
+        organization={organization}
+        renderDisabled={() => <SetupReplaysCTA />}
+      >
+        <OnboardingCTAHook organization={organization}>
+          <SetupReplaysCTA />
+        </OnboardingCTAHook>
+      </Feature>
+    </OnboardingPanel>
+  );
+}
+
+function SetupReplaysCTA() {
+  const {activateSidebar} = useReplayOnboardingSidebarPanel();
+
+  return (
+    <Fragment>
       <h3>{t('Get to the root cause faster')}</h3>
       <h3>{t('Get to the root cause faster')}</h3>
       <p>
       <p>
         {t(
         {t(
           'See a video-like reproduction of your user sessions so you can see what happened before, during, and after an error or latency issue occurred.'
           'See a video-like reproduction of your user sessions so you can see what happened before, during, and after an error or latency issue occurred.'
         )}
         )}
       </p>
       </p>
-      <ButtonList gap={1}>{props.children}</ButtonList>
-    </OnboardingPanel>
+      <ButtonList gap={1}>
+        <Button onClick={activateSidebar} priority="primary">
+          {t('Set Up Replays')}
+        </Button>
+        <Button
+          href="https://docs.sentry.io/platforms/javascript/session-replay/"
+          external
+        >
+          {t('Read Docs')}
+        </Button>
+      </ButtonList>
+    </Fragment>
   );
   );
 }
 }
 
 
@@ -79,6 +117,7 @@ const HeroImage = styled('img')<{breakpoints: Breakpoints}>`
     min-width: 420px;
     min-width: 420px;
   }
   }
 `;
 `;
+
 const ButtonList = styled(ButtonBar)`
 const ButtonList = styled(ButtonBar)`
   grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
   grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
 `;
 `;

+ 2 - 14
static/app/views/replays/list/replays.tsx

@@ -2,10 +2,8 @@ import {Fragment, useMemo} from 'react';
 import {browserHistory} from 'react-router';
 import {browserHistory} from 'react-router';
 import {useTheme} from '@emotion/react';
 import {useTheme} from '@emotion/react';
 
 
-import {Button} from 'sentry/components/button';
 import * as Layout from 'sentry/components/layouts/thirds';
 import * as Layout from 'sentry/components/layouts/thirds';
 import Pagination from 'sentry/components/pagination';
 import Pagination from 'sentry/components/pagination';
-import {t} from 'sentry/locale';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
 import EventView from 'sentry/utils/discover/eventView';
 import EventView from 'sentry/utils/discover/eventView';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {decodeScalar} from 'sentry/utils/queryString';
@@ -52,7 +50,7 @@ function ReplaysList() {
     organization,
     organization,
   });
   });
 
 
-  const {hasSentOneReplay, activateSidebar} = useReplayOnboardingSidebarPanel();
+  const {hasSentOneReplay} = useReplayOnboardingSidebarPanel();
 
 
   return (
   return (
     <Layout.Body>
     <Layout.Body>
@@ -90,17 +88,7 @@ function ReplaysList() {
             />
             />
           </Fragment>
           </Fragment>
         ) : (
         ) : (
-          <ReplayOnboardingPanel>
-            <Button onClick={activateSidebar} priority="primary">
-              {t('Set Up Replays')}
-            </Button>
-            <Button
-              href="https://docs.sentry.io/platforms/javascript/session-replay/"
-              external
-            >
-              {t('Read Docs')}
-            </Button>
-          </ReplayOnboardingPanel>
+          <ReplayOnboardingPanel />
         )}
         )}
       </Layout.Main>
       </Layout.Main>
     </Layout.Body>
     </Layout.Body>