Browse Source

ref(replays): migrate javascript and react onboarding docs (#61182)

Moves two of our onboarding items `javascript` and `javascript-react` to
the `sentry` repo, out of the `sentry-docs` repo. Changes are all under
a feature flag & only visible to the `sentry-emering-tech` org.

Content is mostly the same: [new]
<img width="719" alt="SCR-20231205-lhnv"
src="https://github.com/getsentry/sentry/assets/56095982/3c899a78-fa82-419c-a429-9a55348bb696">

<img width="729" alt="SCR-20231205-lgwu"
src="https://github.com/getsentry/sentry/assets/56095982/e302efc5-c955-4d66-8a46-27a8d9a11563">

[old]
<img width="755" alt="SCR-20231205-lifr"
src="https://github.com/getsentry/sentry/assets/56095982/e60e9a1a-fda9-44ec-a9dd-172d521e9dd6">
<img width="746" alt="SCR-20231205-liee"
src="https://github.com/getsentry/sentry/assets/56095982/c78d4b32-8b26-4292-bba8-21f651164288">


Relates to https://github.com/getsentry/sentry/issues/61001
Michelle Zhang 1 year ago
parent
commit
013421c5e8

+ 14 - 10
static/app/components/onboarding/gettingStartedDoc/layout.tsx

@@ -23,6 +23,7 @@ const ProductSelectionAvailabilityHook = HookOrDefault({
 export type LayoutProps = {
   projectSlug: string;
   steps: StepProps[];
+  hideHeader?: boolean;
   /**
    * An introduction displayed before the steps
    */
@@ -41,22 +42,25 @@ export function Layout({
   platformOptions,
   introduction,
   projectSlug,
+  hideHeader,
 }: LayoutProps) {
   const organization = useOrganization();
 
   return (
     <AuthTokenGeneratorProvider projectSlug={projectSlug}>
       <Wrapper>
-        <Header>
-          {introduction && <Introduction>{introduction}</Introduction>}
-          <ProductSelectionAvailabilityHook
-            organization={organization}
-            platform={platformKey}
-          />
-          {platformOptions ? (
-            <PlatformOptionsControl platformOptions={platformOptions} />
-          ) : null}
-        </Header>
+        {!hideHeader && (
+          <Header>
+            {introduction && <Introduction>{introduction}</Introduction>}
+            <ProductSelectionAvailabilityHook
+              organization={organization}
+              platform={platformKey}
+            />
+            {platformOptions ? (
+              <PlatformOptionsControl platformOptions={platformOptions} />
+            ) : null}
+          </Header>
+        )}
         <Divider withBottomMargin={newOrg} />
         <Steps>
           {steps.map(step => (

+ 13 - 5
static/app/components/onboarding/gettingStartedDoc/sdkDocumentation.tsx

@@ -21,6 +21,7 @@ type SdkDocumentationProps = {
   platform: PlatformIntegration;
   projectId: Project['id'];
   projectSlug: Project['slug'];
+  isReplayOnboarding?: boolean;
   newOrg?: boolean;
 };
 
@@ -28,6 +29,7 @@ export type ModuleProps = {
   dsn: string;
   projectSlug: Project['slug'];
   activeProductSelection?: ProductSolution[];
+  hideHeader?: boolean;
   newOrg?: boolean;
   organization?: Organization;
   platformKey?: PlatformKey;
@@ -48,6 +50,7 @@ export function SdkDocumentation({
   newOrg,
   organization,
   projectId,
+  isReplayOnboarding,
 }: SdkDocumentationProps) {
   const sourcePackageRegistries = useSourcePackageRegistries(organization);
 
@@ -96,17 +99,22 @@ export function SdkDocumentation({
 
   useEffect(() => {
     async function getGettingStartedDoc() {
-      const mod = await import(
-        /* webpackExclude: /.spec/ */
-        `sentry/gettingStartedDocs/${platformPath}`
-      );
+      const mod = isReplayOnboarding
+        ? await import(
+            /* webpackExclude: /.spec/ */
+            `sentry/gettingStartedDocs/replay-onboarding/${platformPath}`
+          )
+        : await import(
+            /* webpackExclude: /.spec/ */
+            `sentry/gettingStartedDocs/${platformPath}`
+          );
       setModule(mod);
     }
     getGettingStartedDoc();
     return () => {
       setModule(null);
     };
-  }, [platformPath]);
+  }, [platformPath, isReplayOnboarding]);
 
   if (!module || projectKeysIsLoading) {
     return <LoadingIndicator />;

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

@@ -7,6 +7,7 @@ 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 useCurrentProjectState from 'sentry/components/replaysOnboarding/useCurrentProjectState';
 import {
@@ -164,6 +165,8 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
     isPlatformSupported: isPlatformSupported(currentPlatform),
   });
 
+  const newOnboarding = organization.features.includes('session-replay-new-zero-state');
+
   if (isLoading) {
     return <LoadingIndicator />;
   }
@@ -218,37 +221,50 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
 
   return (
     <Fragment>
-      <div>
+      <IntroText>
         {tct(
           `Adding Session Replay to your [platform] project is simple. Make sure you've got these basics down.`,
           {platform: currentPlatform?.name || currentProject.slug}
         )}
-      </div>
-      {docKeys.map((docKey, index) => {
-        let footer: React.ReactNode = null;
-
-        if (index === docKeys.length - 1) {
-          footer = (
-            <EventWaiter
-              api={api}
-              organization={organization}
-              project={currentProject}
-              eventType="replay"
-              onIssueReceived={() => {
-                setReceived(true);
-              }}
-            >
-              {() => (received ? <EventReceivedIndicator /> : <EventWaitingIndicator />)}
-            </EventWaiter>
+      </IntroText>
+      {newOnboarding ? (
+        <SdkDocumentation
+          platform={currentPlatform}
+          organization={organization}
+          projectSlug={currentProject.slug}
+          projectId={currentProject.id}
+          activeProductSelection={[]}
+          isReplayOnboarding
+        />
+      ) : (
+        docKeys.map((docKey, index) => {
+          let footer: React.ReactNode = null;
+
+          if (index === docKeys.length - 1) {
+            footer = (
+              <EventWaiter
+                api={api}
+                organization={organization}
+                project={currentProject}
+                eventType="replay"
+                onIssueReceived={() => {
+                  setReceived(true);
+                }}
+              >
+                {() =>
+                  received ? <EventReceivedIndicator /> : <EventWaitingIndicator />
+                }
+              </EventWaiter>
+            );
+          }
+          return (
+            <div key={index}>
+              <OnboardingStepV2 step={index + 1} content={docContents[docKey]} />
+              {footer}
+            </div>
           );
-        }
-        return (
-          <div key={index}>
-            <OnboardingStepV2 step={index + 1} content={docContents[docKey]} />
-            {footer}
-          </div>
-        );
-      })}
+        })
+      )}
     </Fragment>
   );
 }
@@ -272,6 +288,10 @@ function OnboardingStepV2({step, content}: OnboardingStepV2Props) {
   );
 }
 
+const IntroText = styled('div')`
+  padding-top: ${space(3)};
+`;
+
 const OnboardingStepContainer = styled('div')`
   display: flex;
   & > :last-child {

+ 127 - 0
static/app/gettingStartedDocs/replay-onboarding/javascript/javascript.tsx

@@ -0,0 +1,127 @@
+import ExternalLink from 'sentry/components/links/externalLink';
+import {Layout, LayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/layout';
+import {ModuleProps} from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation';
+import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
+import {tct} from 'sentry/locale';
+import type {Organization, PlatformKey} from 'sentry/types';
+
+type StepProps = {
+  newOrg: boolean;
+  organization: Organization;
+  platformKey: PlatformKey;
+  projectId: string;
+  sentryInitContent: string;
+};
+
+// Configuration Start
+const replayIntegration = `
+new Sentry.Replay(),
+`;
+
+const replayOtherConfig = `
+// This sets the sample rate to be 10%. You may want this to be 100% while
+// in development and sample at a lower rate in production.
+replaysSessionSampleRate: 0.1,
+// If the entire session is not sampled, use the below sample rate to sample
+// sessions when an error occurs.
+replaysOnErrorSampleRate: 1.0,
+`;
+
+export const steps = ({
+  sentryInitContent,
+}: Partial<StepProps> = {}): LayoutProps['steps'] => [
+  {
+    type: StepType.INSTALL,
+    description: tct(
+      'For the Session Replay to work, you must have the Sentry browser SDK package, or an equivalent framework SDK (e.g. [code:@sentry/react]) installed, minimum version 7.27.0.',
+      {code: <code />}
+    ),
+    configurations: [
+      {
+        language: 'bash',
+        code: [
+          {
+            label: 'npm',
+            value: 'npm',
+            language: 'bash',
+            code: 'npm install --save @sentry/browser',
+          },
+          {
+            label: 'yarn',
+            value: 'yarn',
+            language: 'bash',
+            code: 'yarn add @sentry/browser',
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: StepType.CONFIGURE,
+    description: tct(
+      'Add the following to your SDK config. There are several privacy and sampling options available, all of which can be set using the [code:integrations] constructor. Learn more about configuring Session Replay by reading the [link:configuration docs].',
+      {
+        code: <code />,
+        link: (
+          <ExternalLink href="https://docs.sentry.io/platforms/javascript/session-replay/" />
+        ),
+      }
+    ),
+    configurations: [
+      {
+        language: 'javascript',
+        code: `
+        import * as Sentry from "@sentry/browser";
+
+        Sentry.init({
+          ${sentryInitContent}
+        });
+        `,
+      },
+    ],
+  },
+];
+
+// Configuration End
+
+export function GettingStartedWithJavaScriptReplay({
+  dsn,
+  organization,
+  newOrg,
+  platformKey,
+  projectId,
+  ...props
+}: ModuleProps) {
+  const integrations: string[] = [];
+  const otherConfigs: string[] = [];
+  integrations.push(replayIntegration.trim());
+  otherConfigs.push(replayOtherConfig.trim());
+
+  let sentryInitContent: string[] = [`dsn: "${dsn}",`];
+
+  if (integrations.length > 0) {
+    sentryInitContent = sentryInitContent.concat('integrations: [', integrations, '],');
+  }
+
+  if (otherConfigs.length > 0) {
+    sentryInitContent = sentryInitContent.concat(otherConfigs);
+  }
+
+  return (
+    <Layout
+      steps={steps({
+        sentryInitContent: sentryInitContent.join('\n'),
+        organization,
+        newOrg,
+        platformKey,
+        projectId,
+      })}
+      platformKey={platformKey}
+      newOrg={newOrg}
+      hideHeader
+      {...props}
+    />
+  );
+}
+
+export default GettingStartedWithJavaScriptReplay;

+ 140 - 0
static/app/gettingStartedDocs/replay-onboarding/javascript/react.tsx

@@ -0,0 +1,140 @@
+import ExternalLink from 'sentry/components/links/externalLink';
+import {Layout, LayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/layout';
+import {ModuleProps} from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation';
+import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
+import {tct} from 'sentry/locale';
+import type {Organization, PlatformKey} from 'sentry/types';
+
+type StepProps = {
+  newOrg: boolean;
+  organization: Organization;
+  platformKey: PlatformKey;
+  projectId: string;
+  sentryInitContent: string;
+};
+
+// Configuration Start
+const replayIntegration = `
+new Sentry.Replay(),
+`;
+
+const replayOtherConfig = `
+// This sets the sample rate to be 10%. You may want this to be 100% while
+// in development and sample at a lower rate in production.
+replaysSessionSampleRate: 0.1,
+// If the entire session is not sampled, use the below sample rate to sample
+// sessions when an error occurs.
+replaysOnErrorSampleRate: 1.0,
+`;
+
+export const steps = ({
+  sentryInitContent,
+}: Partial<StepProps> = {}): LayoutProps['steps'] => [
+  {
+    type: StepType.INSTALL,
+    description: (
+      <p>
+        {tct(
+          'Add the Sentry SDK as a dependency using [codeNpm:npm] or [codeYarn:yarn]. You need a minimum version 7.27.0 of [code:@sentry/react] in order to use Session Replay. You do not need to install any additional packages.',
+          {
+            code: <code />,
+            codeYarn: <code />,
+            codeNpm: <code />,
+          }
+        )}
+      </p>
+    ),
+    configurations: [
+      {
+        language: 'bash',
+        code: [
+          {
+            label: 'npm',
+            value: 'npm',
+            language: 'bash',
+            code: 'npm install --save @sentry/react',
+          },
+          {
+            label: 'yarn',
+            value: 'yarn',
+            language: 'bash',
+            code: 'yarn add @sentry/react',
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: StepType.CONFIGURE,
+    description: tct(
+      'Add the following to your SDK config. There are several privacy and sampling options available, all of which can be set using the [code:integrations] constructor. Learn more about configuring Session Replay by reading the [link:configuration docs].',
+      {
+        code: <code />,
+        link: (
+          <ExternalLink href="https://docs.sentry.io/platforms/javascript/session-replay/" />
+        ),
+      }
+    ),
+    configurations: [
+      {
+        language: 'javascript',
+        code: `
+import * as Sentry from "@sentry/react";
+
+Sentry.init({
+  ${sentryInitContent}
+});
+
+const container = document.getElementById(“app”);
+const root = createRoot(container);
+root.render(<App />);
+        `,
+      },
+    ],
+  },
+];
+
+// Configuration End
+
+export function GettingStartedWithReactReplay({
+  dsn,
+  organization,
+  newOrg,
+  platformKey,
+  projectId,
+  ...props
+}: ModuleProps) {
+  const integrations: string[] = [];
+  const otherConfigs: string[] = [];
+
+  integrations.push(replayIntegration.trim());
+  otherConfigs.push(replayOtherConfig.trim());
+
+  let sentryInitContent: string[] = [`dsn: "${dsn}",`];
+
+  if (integrations.length > 0) {
+    sentryInitContent = sentryInitContent.concat('integrations: [', integrations, '],');
+  }
+
+  if (otherConfigs.length > 0) {
+    sentryInitContent = sentryInitContent.concat(otherConfigs);
+  }
+
+  return (
+    <Layout
+      steps={steps({
+        sentryInitContent: sentryInitContent.join('\n'),
+        organization,
+        newOrg,
+        platformKey,
+        projectId,
+      })}
+      newOrg={newOrg}
+      platformKey={platformKey}
+      hideHeader
+      {...props}
+    />
+  );
+}
+
+export default GettingStartedWithReactReplay;