Browse Source

feat(replays): add backend NPM instructions for onboarding (#62026)

This PR adds the NPM instructions for all backend platforms in the
replay onboarding sidebar. When a backend platform is selected, there's
a dropdown with a list of supported JS framework options, and the
respective NPM install instructions display depending on what framework
is selected. See video below:


https://github.com/getsentry/sentry/assets/56095982/402f7f68-28cd-4d78-a427-232e41937a43

Relates to https://github.com/getsentry/sentry/issues/61069
Michelle Zhang 1 year ago
parent
commit
5630d5f841

+ 6 - 8
static/app/components/onboarding/gettingStartedDoc/utils/index.tsx

@@ -101,12 +101,10 @@ export const getReplaySDKSetupSnippet = ({
   Sentry.init({
     dsn: "${dsn}",
 
-    // This sets the sample rate at 10%. You may want this to be 100% while
-    // in development, then 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,
-
-    integrations: [new Sentry.Replay()],
+    integrations: [
+      new Sentry.Replay(),
+    ],
+    // Session Replay
+    replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
+    replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
   });`;

+ 68 - 7
static/app/components/replaysOnboarding/sidebar.tsx

@@ -1,5 +1,6 @@
-import {Fragment, useEffect, useMemo, useState} from 'react';
+import {Fragment, ReactNode, useEffect, useMemo, useState} from 'react';
 import styled from '@emotion/styled';
+import {PlatformIcon} from 'platformicons';
 
 import HighlightTopRightPattern from 'sentry-images/pattern/highlight-top-right.svg';
 
@@ -13,11 +14,13 @@ import useCurrentProjectState from 'sentry/components/replaysOnboarding/useCurre
 import {
   generateDocKeys,
   isPlatformSupported,
+  replayJsFrameworkOptions,
 } from 'sentry/components/replaysOnboarding/utils';
 import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {DocumentationWrapper} from 'sentry/components/sidebar/onboardingStep';
 import SidebarPanel from 'sentry/components/sidebar/sidebarPanel';
 import {CommonSidebarProps, SidebarPanelKey} from 'sentry/components/sidebar/types';
+import TextOverflow from 'sentry/components/textOverflow';
 import {Tooltip} from 'sentry/components/tooltip';
 import {
   backend,
@@ -28,7 +31,7 @@ import platforms from 'sentry/data/platforms';
 import {t, tct} from 'sentry/locale';
 import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
 import {space} from 'sentry/styles/space';
-import {Project, SelectValue} from 'sentry/types';
+import {PlatformKey, Project, SelectValue} from 'sentry/types';
 import EventWaiter from 'sentry/utils/eventWaiter';
 import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -188,11 +191,37 @@ function ReplaysOnboardingSidebar(props: CommonSidebarProps) {
 }
 
 function OnboardingContent({currentProject}: {currentProject: Project}) {
+  const jsFrameworkSelectOptions = replayJsFrameworkOptions.map(platform => {
+    return {
+      value: platform.id,
+      textValue: platform.name,
+      label: (
+        <PlatformLabel>
+          <PlatformIcon platform={platform.id} size={16} />
+          <TextOverflow>{platform.name}</TextOverflow>
+        </PlatformLabel>
+      ),
+    };
+  });
+
   const api = useApi();
   const organization = useOrganization();
   const previousProject = usePrevious(currentProject);
   const [received, setReceived] = useState<boolean>(false);
   const {getParamValue: setupMode} = useUrlParams('mode');
+  const [jsFramework, setJsFramework] = useState<{
+    value: PlatformKey;
+    label?: ReactNode;
+    textValue?: string;
+  }>(jsFrameworkSelectOptions[0]);
+
+  const newOnboarding = organization.features.includes('session-replay-new-zero-state');
+  const showJsFrameworkInstructions =
+    newOnboarding &&
+    currentProject &&
+    currentProject.platform &&
+    backend.includes(currentProject.platform) &&
+    setupMode() === 'npm';
 
   useEffect(() => {
     if (previousProject.id !== currentProject.id) {
@@ -205,8 +234,8 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
     : undefined;
 
   const docKeys = useMemo(() => {
-    return currentPlatform ? generateDocKeys(currentPlatform.id) : [];
-  }, [currentPlatform]);
+    return currentPlatform && !newOnboarding ? generateDocKeys(currentPlatform.id) : [];
+  }, [currentPlatform, newOnboarding]);
 
   const {docContents, isLoading, hasOnboardingContents} = useOnboardingDocs({
     project: currentProject,
@@ -244,8 +273,6 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
     );
   }
 
-  const newOnboarding = organization.features.includes('session-replay-new-zero-state');
-
   if (!currentPlatform || (!hasOnboardingContents && !newOnboarding)) {
     return (
       <Fragment>
@@ -275,10 +302,29 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
           `Adding Session Replay to your [platform] project is simple. Make sure you've got these basics down.`,
           {platform: currentPlatform?.name || currentProject.slug}
         )}
+        {showJsFrameworkInstructions ? (
+          <PlatformSelect>
+            {t('Select your JS Framework: ')}
+            <CompactSelect
+              triggerLabel={jsFramework.label}
+              value={jsFramework.value}
+              onChange={v => {
+                setJsFramework(v);
+              }}
+              options={jsFrameworkSelectOptions}
+              position="bottom-end"
+            />
+          </PlatformSelect>
+        ) : null}
       </IntroText>
       {newOnboarding ? (
         <SdkDocumentation
-          platform={currentPlatform}
+          platform={
+            showJsFrameworkInstructions
+              ? replayJsFrameworkOptions.find(p => p.id === jsFramework.value) ??
+                replayJsFrameworkOptions[0]
+              : currentPlatform
+          }
           organization={organization}
           projectSlug={currentProject.slug}
           projectId={currentProject.id}
@@ -343,6 +389,8 @@ function OnboardingStepV2({step, content}: OnboardingStepV2Props) {
 
 const IntroText = styled('div')`
   padding-top: ${space(3)};
+  display: grid;
+  gap: ${space(1)};
 `;
 
 const OnboardingStepContainer = styled('div')`
@@ -443,4 +491,17 @@ const StyledTooltip = styled(Tooltip)`
   ${p => p.theme.overflowEllipsis};
 `;
 
+const PlatformLabel = styled('div')`
+  display: flex;
+  gap: ${space(1)};
+  align-items: center;
+`;
+
+const PlatformSelect = styled('div')`
+  display: flex;
+  gap: ${space(1)};
+  align-items: center;
+  padding-bottom: ${space(1)};
+`;
+
 export default ReplaysOnboardingSidebar;

+ 6 - 1
static/app/components/replaysOnboarding/utils.tsx

@@ -1,6 +1,7 @@
 import partition from 'lodash/partition';
 
-import {replayPlatforms} from 'sentry/data/platformCategories';
+import {replayFrontendPlatforms, replayPlatforms} from 'sentry/data/platformCategories';
+import platforms from 'sentry/data/platforms';
 import {PlatformIntegration, PlatformKey, Project} from 'sentry/types';
 
 export function generateDocKeys(platform: PlatformKey): string[] {
@@ -25,3 +26,7 @@ export function splitProjectsByReplaySupport(projects: Project[]) {
     unsupported,
   };
 }
+
+export const replayJsFrameworkOptions: PlatformIntegration[] = platforms.filter(p =>
+  replayFrontendPlatforms.includes(p.id)
+);

+ 1 - 1
static/app/data/platformCategories.tsx

@@ -331,7 +331,7 @@ const replayBackendPlatforms: readonly PlatformKey[] = [
 ];
 
 // These are the frontend platforms that can set up replay.
-const replayFrontendPlatforms: readonly PlatformKey[] = [
+export const replayFrontendPlatforms: readonly PlatformKey[] = [
   'capacitor',
   'electron',
   'javascript-angular',

+ 1 - 1
static/app/data/platforms.tsx

@@ -3,7 +3,7 @@ import {PlatformIntegration} from 'sentry/types';
 // If you update items of this list, please remember to update the "GETTING_STARTED_DOCS_PLATFORMS" list
 // in the 'src/sentry/models/project.py' file. This way, they'll work together correctly.
 // Otherwise, creating a project will cause an error in the backend, saying "Invalid platform".
-const platforms: PlatformIntegration[] = [
+export const platforms: PlatformIntegration[] = [
   {
     id: 'android',
     name: 'Android',