Browse Source

ref(replays): refactor replay onboarding docs to be separate (#62128)

The replay onboarding content is now fairly separate from the normal
onboarding docs -- the only big thing we're reusing is the `Step`
component.

- Gets rid of `SdkDocumentation` usage in our sidebar file
- Instead, use a new hook `useLoadOnboardingDoc` to load the new
onboarding content we want
- No behavior change (I hope)
- Create our own replay-specific `ReplayOnboardingLayout` component
instead of re-using the docs one
- The boolean `isReplayConfigStep` is gone; we don't need it anymore
- Moved the 'Mask All Text' and 'Block All Media' toggles to right above
the code snippet



https://github.com/getsentry/sentry/assets/56095982/fb1a2d80-6d15-440e-97c6-da95c226d29d
Michelle Zhang 1 year ago
parent
commit
499a77b101

+ 5 - 43
static/app/components/onboarding/gettingStartedDoc/onboardingLayout.tsx

@@ -1,4 +1,4 @@
-import {Fragment, useMemo, useState} from 'react';
+import {Fragment, useMemo} from 'react';
 import styled from '@emotion/styled';
 
 import HookOrDefault from 'sentry/components/hookOrDefault';
@@ -6,7 +6,6 @@ import ExternalLink from 'sentry/components/links/externalLink';
 import List from 'sentry/components/list';
 import ListItem from 'sentry/components/list/listItem';
 import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
-import ReplayConfigToggle from 'sentry/components/onboarding/gettingStartedDoc/replayConfigToggle';
 import {Step} from 'sentry/components/onboarding/gettingStartedDoc/step';
 import {
   ConfigType,
@@ -61,9 +60,6 @@ export function OnboardingLayout({
   const {isLoading: isLoadingRegistry, data: registryData} =
     useSourcePackageRegistries(organization);
   const selectedOptions = useUrlPlatformOptions(docsConfig.platformOptions);
-  const [mask, setMask] = useState(true);
-  const [block, setBlock] = useState(true);
-
   const {platformOptions} = docsConfig;
 
   const {introduction, steps, nextSteps} = useMemo(() => {
@@ -87,8 +83,6 @@ export function OnboardingLayout({
       },
       platformOptions: selectedOptions,
       newOrg,
-      mask,
-      block,
     };
 
     return {
@@ -114,8 +108,6 @@ export function OnboardingLayout({
     registryData,
     selectedOptions,
     configType,
-    mask,
-    block,
   ]);
 
   return (
@@ -129,45 +121,15 @@ export function OnboardingLayout({
               platform={platformKey}
             />
           )}
-          {platformOptions &&
-          !['replayOnboardingJsLoader', 'customMetricsOnboarding'].includes(
-            configType
-          ) ? (
+          {platformOptions && !['customMetricsOnboarding'].includes(configType) ? (
             <PlatformOptionsControl platformOptions={platformOptions} />
           ) : null}
         </Header>
         <Divider withBottomMargin />
         <Steps>
-          {steps.map(step =>
-            step.isOptional && step.isReplayConfigStep ? (
-              <Step
-                key={step.title ?? step.type}
-                {...{
-                  ...step,
-                  additionalInfo: (
-                    <ReplayConfigToggle
-                      blockToggle={block}
-                      maskToggle={mask}
-                      onBlockToggle={() => setBlock(!block)}
-                      onMaskToggle={() => setMask(!mask)}
-                    />
-                  ),
-                }}
-              />
-            ) : (
-              <Fragment key={step.title ?? step.type}>
-                <Step key={step.title ?? step.type} {...step} />
-                {step.isReplayConfigStep ? (
-                  <ReplayConfigToggle
-                    blockToggle={block}
-                    maskToggle={mask}
-                    onBlockToggle={() => setBlock(!block)}
-                    onMaskToggle={() => setMask(!mask)}
-                  />
-                ) : null}
-              </Fragment>
-            )
-          )}
+          {steps.map(step => (
+            <Step key={step.title ?? step.type} {...step} />
+          ))}
         </Steps>
         {nextSteps.length > 0 && (
           <Fragment>

+ 16 - 2
static/app/components/onboarding/gettingStartedDoc/step.tsx

@@ -120,6 +120,10 @@ interface BaseStepProps {
    * Additional information to be displayed below the configurations
    */
   additionalInfo?: React.ReactNode;
+  /**
+   * Content that goes directly above the code snippet
+   */
+  codeHeader?: React.ReactNode;
   configurations?: ConfigurationType[];
   /**
    * A brief description of the step
@@ -129,7 +133,6 @@ interface BaseStepProps {
    * Whether the step is optional
    */
   isOptional?: boolean;
-  isReplayConfigStep?: boolean;
 }
 interface StepPropsWithTitle extends BaseStepProps {
   title: string;
@@ -195,12 +198,14 @@ export function Step({
   additionalInfo,
   description,
   isOptional = false,
+  codeHeader,
 }: StepProps) {
   const [showOptionalConfig, setShowOptionalConfig] = useState(false);
 
   const config = (
     <Fragment>
       {description && <Description>{description}</Description>}
+
       {!!configurations?.length && (
         <Configurations>
           {configurations.map((configuration, index) => {
@@ -211,6 +216,10 @@ export function Step({
                   {configuration.configurations.map(
                     (nestedConfiguration, nestedConfigurationIndex) => (
                       <Fragment key={nestedConfigurationIndex}>
+                        {nestedConfigurationIndex ===
+                        (configuration.configurations?.length ?? 1) - 1
+                          ? codeHeader
+                          : null}
                         {getConfiguration(nestedConfiguration)}
                       </Fragment>
                     )
@@ -218,7 +227,12 @@ export function Step({
                 </Fragment>
               );
             }
-            return <Fragment key={index}>{getConfiguration(configuration)}</Fragment>;
+            return (
+              <Fragment key={index}>
+                {index === configurations.length - 1 ? codeHeader : null}
+                {getConfiguration(configuration)}
+              </Fragment>
+            );
           })}
         </Configurations>
       )}

+ 1 - 0
static/app/components/onboarding/gettingStartedDoc/replayConfigToggle.tsx → static/app/components/replaysOnboarding/replayConfigToggle.tsx

@@ -39,6 +39,7 @@ const SwitchWrapper = styled('div')`
   display: flex;
   align-items: center;
   gap: ${space(2)};
+  padding-top: ${space(0.5)};
 `;
 
 export default ReplayConfigToggle;

+ 152 - 0
static/app/components/replaysOnboarding/replayOnboardingLayout.tsx

@@ -0,0 +1,152 @@
+import {useMemo, useState} from 'react';
+import styled from '@emotion/styled';
+
+import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
+import {OnboardingLayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/onboardingLayout';
+import {Step, StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
+import {DocsParams} from 'sentry/components/onboarding/gettingStartedDoc/types';
+import {useSourcePackageRegistries} from 'sentry/components/onboarding/gettingStartedDoc/useSourcePackageRegistries';
+import {
+  PlatformOptionsControl,
+  useUrlPlatformOptions,
+} from 'sentry/components/onboarding/platformOptionsControl';
+import ReplayConfigToggle from 'sentry/components/replaysOnboarding/replayConfigToggle';
+import {space} from 'sentry/styles/space';
+import useOrganization from 'sentry/utils/useOrganization';
+
+export function ReplayOnboardingLayout({
+  cdn,
+  docsConfig,
+  dsn,
+  platformKey,
+  projectId,
+  projectSlug,
+  newOrg,
+  configType = 'onboarding',
+}: OnboardingLayoutProps) {
+  const organization = useOrganization();
+  const {isLoading: isLoadingRegistry, data: registryData} =
+    useSourcePackageRegistries(organization);
+  const selectedOptions = useUrlPlatformOptions(docsConfig.platformOptions);
+  const [mask, setMask] = useState(true);
+  const [block, setBlock] = useState(true);
+  const {platformOptions} = docsConfig;
+  const {introduction, steps} = useMemo(() => {
+    const doc = docsConfig[configType] ?? docsConfig.onboarding;
+
+    const docParams: DocsParams<any> = {
+      cdn,
+      dsn,
+      organization,
+      platformKey,
+      projectId,
+      projectSlug,
+      isPerformanceSelected: false,
+      isProfilingSelected: false,
+      isReplaySelected: true,
+      sourcePackageRegistries: {
+        isLoading: isLoadingRegistry,
+        data: registryData,
+      },
+      platformOptions: selectedOptions,
+      newOrg,
+      mask,
+      block,
+    };
+
+    return {
+      introduction: doc.introduction?.(docParams),
+      steps: [
+        ...doc.install(docParams),
+        ...doc.configure(docParams),
+        ...doc.verify(docParams),
+      ],
+      nextSteps: doc.nextSteps?.(docParams) || [],
+    };
+  }, [
+    cdn,
+    docsConfig,
+    dsn,
+    isLoadingRegistry,
+    newOrg,
+    organization,
+    platformKey,
+    projectId,
+    projectSlug,
+    registryData,
+    selectedOptions,
+    configType,
+    mask,
+    block,
+  ]);
+
+  return (
+    <AuthTokenGeneratorProvider projectSlug={projectSlug}>
+      <Wrapper>
+        <Header>
+          {introduction && <div>{introduction}</div>}
+          {platformOptions && !['replayOnboardingJsLoader'].includes(configType) ? (
+            <PlatformOptionsControl platformOptions={platformOptions} />
+          ) : null}
+        </Header>
+        <Divider withBottomMargin />
+        <Steps>
+          {steps.map(step =>
+            step.type === StepType.CONFIGURE ? (
+              <Step
+                key={step.title ?? step.type}
+                {...{
+                  ...step,
+                  codeHeader: (
+                    <ReplayConfigToggle
+                      blockToggle={block}
+                      maskToggle={mask}
+                      onBlockToggle={() => setBlock(!block)}
+                      onMaskToggle={() => setMask(!mask)}
+                    />
+                  ),
+                }}
+              />
+            ) : (
+              <Step key={step.title ?? step.type} {...step} />
+            )
+          )}
+        </Steps>
+      </Wrapper>
+    </AuthTokenGeneratorProvider>
+  );
+}
+
+const Header = styled('div')`
+  display: flex;
+  flex-direction: column;
+  gap: ${space(2)};
+`;
+
+const Divider = styled('hr')<{withBottomMargin?: boolean}>`
+  height: 1px;
+  width: 100%;
+  background: ${p => p.theme.border};
+  border: none;
+  ${p => p.withBottomMargin && `margin-bottom: ${space(3)}`}
+`;
+
+const Steps = styled('div')`
+  display: flex;
+  flex-direction: column;
+  gap: 1.5rem;
+`;
+
+const Wrapper = styled('div')`
+  h4 {
+    margin-bottom: 0.5em;
+  }
+  && {
+    p {
+      margin-bottom: 0;
+    }
+    h5 {
+      margin-bottom: 0;
+    }
+  }
+`;

+ 46 - 28
static/app/components/replaysOnboarding/sidebar.tsx

@@ -8,9 +8,10 @@ import {Button} from 'sentry/components/button';
 import {CompactSelect} from 'sentry/components/compactSelect';
 import IdBadge from 'sentry/components/idBadge';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
-import {SdkDocumentation} from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation';
 import useOnboardingDocs from 'sentry/components/onboardingWizard/useOnboardingDocs';
+import {ReplayOnboardingLayout} from 'sentry/components/replaysOnboarding/replayOnboardingLayout';
 import useCurrentProjectState from 'sentry/components/replaysOnboarding/useCurrentProjectState';
+import useLoadOnboardingDoc from 'sentry/components/replaysOnboarding/useLoadOnboardingDoc';
 import {
   generateDocKeys,
   isPlatformSupported,
@@ -28,7 +29,7 @@ import {
   replayJsLoaderInstructionsPlatformList,
   replayPlatforms,
 } from 'sentry/data/platformCategories';
-import platforms from 'sentry/data/platforms';
+import platforms, {otherPlatform} from 'sentry/data/platforms';
 import {t, tct} from 'sentry/locale';
 import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
 import {space} from 'sentry/styles/space';
@@ -224,6 +225,18 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
     backend.includes(currentProject.platform) &&
     setupMode() === 'npm';
 
+  const defaultTab =
+    currentProject && currentProject.platform && backend.includes(currentProject.platform)
+      ? 'jsLoader'
+      : 'npm';
+
+  const npmOnlyFramework =
+    currentProject &&
+    currentProject.platform &&
+    replayFrontendPlatforms
+      .filter(p => p !== 'javascript')
+      .includes(currentProject.platform);
+
   useEffect(() => {
     if (previousProject.id !== currentProject.id) {
       setReceived(false);
@@ -231,32 +244,36 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
   }, [previousProject.id, currentProject.id]);
 
   const currentPlatform = currentProject.platform
-    ? platforms.find(p => p.id === currentProject.platform)
-    : undefined;
+    ? platforms.find(p => p.id === currentProject.platform) ?? otherPlatform
+    : otherPlatform;
 
   const docKeys = useMemo(() => {
     return currentPlatform && !newOnboarding ? generateDocKeys(currentPlatform.id) : [];
   }, [currentPlatform, newOnboarding]);
 
+  // Old onboarding docs
   const {docContents, isLoading, hasOnboardingContents} = useOnboardingDocs({
     project: currentProject,
     docKeys,
     isPlatformSupported: isPlatformSupported(currentPlatform),
   });
 
-  const defaultTab =
-    currentProject && currentProject.platform && backend.includes(currentProject.platform)
-      ? 'jsLoader'
-      : 'npm';
-
-  const npmOnlyFramework =
-    currentProject &&
-    currentProject.platform &&
-    replayFrontendPlatforms
-      .filter(p => p !== 'javascript')
-      .includes(currentProject.platform);
+  // New onboarding docs
+  const {
+    docs: newDocs,
+    dsn,
+    cdn,
+    isProjKeysLoading,
+  } = useLoadOnboardingDoc({
+    platform: showJsFrameworkInstructions
+      ? replayJsFrameworkOptions.find(p => p.id === jsFramework.value) ??
+        replayJsFrameworkOptions[0]
+      : currentPlatform,
+    organization,
+    projectSlug: currentProject.slug,
+  });
 
-  if (isLoading) {
+  if (isLoading || isProjKeysLoading) {
     return <LoadingIndicator />;
   }
 
@@ -286,7 +303,11 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
     );
   }
 
-  if (!currentPlatform || (!hasOnboardingContents && !newOnboarding)) {
+  if (
+    !currentPlatform ||
+    (!newOnboarding && !hasOnboardingContents) ||
+    (newOnboarding && !newDocs)
+  ) {
     return (
       <Fragment>
         <div>
@@ -330,18 +351,15 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
           </PlatformSelect>
         ) : null}
       </IntroText>
-      {newOnboarding ? (
-        <SdkDocumentation
-          platform={
-            showJsFrameworkInstructions
-              ? replayJsFrameworkOptions.find(p => p.id === jsFramework.value) ??
-                replayJsFrameworkOptions[0]
-              : currentPlatform
-          }
-          organization={organization}
-          projectSlug={currentProject.slug}
-          projectId={currentProject.id}
+      {newOnboarding && newDocs ? (
+        <ReplayOnboardingLayout
+          docsConfig={newDocs}
+          dsn={dsn}
+          cdn={cdn}
           activeProductSelection={[]}
+          platformKey={currentPlatform.id}
+          projectId={currentProject.id}
+          projectSlug={currentProject.slug}
           configType={
             setupMode() === 'npm' || // switched to NPM tab
             (!setupMode() && defaultTab === 'npm') || // default value for FE frameworks when ?mode={...} in URL is not set yet

+ 70 - 0
static/app/components/replaysOnboarding/useLoadOnboardingDoc.tsx

@@ -0,0 +1,70 @@
+import {useEffect, useState} from 'react';
+
+import {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types';
+import {Organization, PlatformIntegration, ProjectKey} from 'sentry/types';
+import {useApiQuery} from 'sentry/utils/queryClient';
+
+function useLoadOnboardingDoc({
+  platform,
+  organization,
+  projectSlug,
+}: {
+  organization: Organization;
+  platform: PlatformIntegration;
+  projectSlug: string;
+}) {
+  const [module, setModule] = useState<null | {
+    default: Docs<any>;
+  }>(null);
+
+  const platformPath =
+    platform?.type === 'framework'
+      ? platform?.id === 'capacitor'
+        ? `capacitor/capacitor`
+        : platform?.id.replace(`${platform.language}-`, `${platform.language}/`)
+      : `${platform?.language}/${platform?.id}`;
+
+  const {
+    data: projectKeys,
+    isError: projectKeysIsError,
+    isLoading: projectKeysIsLoading,
+  } = useApiQuery<ProjectKey[]>([`/projects/${organization.slug}/${projectSlug}/keys/`], {
+    staleTime: Infinity,
+  });
+
+  useEffect(() => {
+    async function getGettingStartedDoc() {
+      const mod = await import(
+        /* webpackExclude: /.spec/ */
+        `sentry/gettingStartedDocs/${platformPath}`
+      );
+      setModule(mod);
+    }
+    getGettingStartedDoc();
+    return () => {
+      setModule(null);
+    };
+  }, [platformPath]);
+
+  if (!module || projectKeysIsLoading) {
+    return {
+      isProjKeysLoading: true,
+    };
+  }
+
+  if (projectKeysIsError) {
+    return {
+      isProjKeysError: true,
+    };
+  }
+
+  const {default: docs} = module;
+
+  return {
+    docs,
+    dsn: projectKeys[0].dsn.public,
+    cdn: projectKeys[0].dsn.cdn,
+  };
+}
+
+export default useLoadOnboardingDoc;

+ 19 - 19
static/app/gettingStartedDocs/capacitor/capacitor.tsx

@@ -301,22 +301,26 @@ function getVueConstSetup(siblingOption: string): string {
 function getSetupConfiguration({
   params,
   showExtraStep,
+  showDescription,
 }: {
   params: Params;
   showExtraStep: boolean;
+  showDescription?: boolean;
 }) {
   const siblingOption = params.platformOptions.siblingOption;
   const sentryInitLayout = getSentryInitLayout(params, siblingOption);
 
   const configuration = [
     {
-      description: tct(
-        `You should init the Sentry capacitor SDK in your [code:main.ts] file as soon as possible during application load up, before initializing Sentry [siblingName:]:`,
-        {
-          siblingName: getSiblingName(siblingOption),
-          code: <code />,
-        }
-      ),
+      description: showDescription
+        ? tct(
+            `You should init the Sentry capacitor SDK in your [code:main.ts] file as soon as possible during application load up, before initializing Sentry [siblingName:]:`,
+            {
+              siblingName: getSiblingName(siblingOption),
+              code: <code />,
+            }
+          )
+        : null,
       language: 'javascript',
       code: `${getSiblingImportsSetupConfiguration(siblingOption)}
           import * as Sentry from '@sentry/capacitor';
@@ -408,18 +412,14 @@ const replayOnboarding: OnboardingConfig<PlatformOptions> = {
   configure: params => [
     {
       type: StepType.CONFIGURE,
-      isReplayConfigStep: true,
-      configurations: [
-        {
-          description: getReplayConfigureDescription({
-            link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/session-replay/',
-          }),
-          configurations: getSetupConfiguration({
-            params: {...params, isReplaySelected: true},
-            showExtraStep: false,
-          }),
-        },
-      ],
+      description: getReplayConfigureDescription({
+        link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/session-replay/',
+      }),
+      configurations: getSetupConfiguration({
+        params: {...params, isReplaySelected: true},
+        showExtraStep: false,
+        showDescription: false,
+      }),
     },
   ],
   verify: () => [],

+ 0 - 1
static/app/gettingStartedDocs/electron/electron.tsx

@@ -122,7 +122,6 @@ const replayOnboarding: OnboardingConfig = {
   configure: (params: Params) => [
     {
       type: StepType.CONFIGURE,
-      isReplayConfigStep: true,
       description: getReplayConfigureDescription({
         link: 'https://docs.sentry.io/platforms/javascript/guides/electron/session-replay/',
       }),

+ 3 - 4
static/app/gettingStartedDocs/javascript/angular.tsx

@@ -274,12 +274,11 @@ const replayOnboarding: OnboardingConfig<PlatformOptions> = {
   configure: params => [
     {
       type: StepType.CONFIGURE,
-      isReplayConfigStep: true,
+      description: getReplayConfigureDescription({
+        link: 'https://docs.sentry.io/platforms/javascript/guides/angular/session-replay/',
+      }),
       configurations: [
         {
-          description: getReplayConfigureDescription({
-            link: 'https://docs.sentry.io/platforms/javascript/guides/angular/session-replay/',
-          }),
           code: [
             {
               label: 'JavaScript',

+ 0 - 1
static/app/gettingStartedDocs/javascript/astro.tsx

@@ -200,7 +200,6 @@ const replayOnboarding: OnboardingConfig = {
   configure: (params: Params) => [
     {
       type: StepType.CONFIGURE,
-      isReplayConfigStep: true,
       description: getReplayConfigureDescription({
         link: 'https://docs.sentry.io/platforms/javascript/guides/astro/session-replay/',
       }),

Some files were not shown because too many files changed in this diff