Browse Source

ref(onboarding): Move loader code to javascript getStartedDoc file (#77310)

Priscila Oliveira 5 months ago
parent
commit
009b6eac26

+ 12 - 2
static/app/components/onboarding/gettingStartedDoc/sdkDocumentation.spec.tsx

@@ -72,7 +72,7 @@ describe('Renders SDK Documentation corretly based on platform id and language',
   });
 
   it('JavaScript', async function () {
-    const {organization, project} = initializeOrg({
+    const {organization, project, router} = initializeOrg({
       projects: [
         {
           ...initializeOrg().project,
@@ -80,6 +80,13 @@ describe('Renders SDK Documentation corretly based on platform id and language',
           platform: 'javascript',
         },
       ],
+      router: {
+        location: {
+          query: {
+            installationMode: 'manual',
+          },
+        },
+      },
     });
 
     renderMockRequests({project, orgSlug: organization.slug});
@@ -99,7 +106,10 @@ describe('Renders SDK Documentation corretly based on platform id and language',
           organization={organization}
           activeProductSelection={[]}
         />
-      </OnboardingContextProvider>
+      </OnboardingContextProvider>,
+      {
+        router,
+      }
     );
 
     // Renders main headings

+ 0 - 51
static/app/components/onboarding/productSelection.spec.tsx

@@ -106,57 +106,6 @@ describe('Onboarding Product Selection', function () {
     ).toBeInTheDocument();
   });
 
-  it('renders for Loader Script', async function () {
-    const {router, project} = initializeOrg({
-      router: {
-        location: {
-          query: {
-            showLoader: 'true',
-            product: [
-              ProductSolution.PERFORMANCE_MONITORING,
-              ProductSolution.SESSION_REPLAY,
-            ],
-          },
-        },
-        params: {},
-      },
-    });
-
-    render(
-      <ProductSelection
-        organization={organization}
-        platform="javascript"
-        projectId={project.id}
-      />,
-      {
-        router,
-      }
-    );
-
-    // Introduction
-    expect(
-      screen.getByText(
-        textWithMarkupMatcher(/In this quick guide you’ll use our Loader Script/)
-      )
-    ).toBeInTheDocument();
-    expect(
-      screen.getByText(
-        textWithMarkupMatcher(/Prefer to set up Sentry using npm or yarn\?/)
-      )
-    ).toBeInTheDocument();
-
-    await userEvent.click(screen.getByText('View npm/yarn instructions'));
-
-    expect(router.replace).toHaveBeenCalledWith(
-      expect.objectContaining({
-        query: {
-          product: ['performance-monitoring', 'session-replay'],
-          showLoader: false,
-        },
-      })
-    );
-  });
-
   it('renders disabled product', async function () {
     const {router, project} = initializeOrg({
       router: {

+ 6 - 73
static/app/components/onboarding/productSelection.tsx

@@ -5,7 +5,6 @@ import styled from '@emotion/styled';
 
 import {openModal} from 'sentry/actionCreators/modal';
 import {FeatureDisabledModal} from 'sentry/components/acl/featureDisabledModal';
-import {Alert} from 'sentry/components/alert';
 import {Button} from 'sentry/components/button';
 import Checkbox from 'sentry/components/checkbox';
 import ExternalLink from 'sentry/components/links/externalLink';
@@ -17,7 +16,6 @@ import HookStore from 'sentry/stores/hookStore';
 import {space} from 'sentry/styles/space';
 import type {Organization} from 'sentry/types/organization';
 import type {PlatformKey} from 'sentry/types/project';
-import {trackAnalytics} from 'sentry/utils/analytics';
 import {useOnboardingQueryParams} from 'sentry/views/onboarding/components/useOnboardingQueryParams';
 import TextBlock from 'sentry/views/settings/components/text/textBlock';
 
@@ -317,13 +315,11 @@ export function ProductSelection({
   organization,
   platform,
   productsPerPlatform = platformProductAvailability,
-  projectId,
   onChange,
   onLoad,
 }: ProductSelectionProps) {
   const [params, setParams] = useOnboardingQueryParams();
   const urlProducts = useMemo(() => params.product ?? [], [params.product]);
-  const supportLoader = platform === 'javascript';
 
   const products: ProductSolution[] | undefined = platform
     ? productsPerPlatform[platform]
@@ -340,8 +336,6 @@ export function ProductSelection({
   useEffect(() => {
     onLoad?.(defaultProducts);
     setParams({
-      showLoader:
-        supportLoader && params.showLoader === undefined ? true : params.showLoader,
       product: defaultProducts,
     });
     // Adding defaultProducts to the dependency array causes an max-depth error
@@ -385,18 +379,6 @@ export function ProductSelection({
     [defaultProducts, organization, setParams, urlProducts, onChange]
   );
 
-  const handleToggleLoader = useCallback(() => {
-    if (!params.showLoader === false && platform) {
-      trackAnalytics('onboarding.js_loader_npm_docs_shown', {
-        organization,
-        platform,
-        project_id: projectId,
-      });
-    }
-
-    setParams({showLoader: !params.showLoader});
-  }, [setParams, platform, projectId, organization, params.showLoader]);
-
   if (!products) {
     // if the platform does not support any product, we don't render anything
     return null;
@@ -407,20 +389,17 @@ export function ProductSelection({
   // until we improve multi snippet suppport
   const showPackageManagerInfo =
     (platform?.indexOf('javascript') === 0 || platform?.indexOf('node') === 0) &&
-    platform !== 'javascript-astro';
+    platform !== 'javascript-astro' &&
+    platform !== 'javascript';
 
   return (
     <Fragment>
       {showPackageManagerInfo && (
         <TextBlock noMargin>
-          {supportLoader
-            ? tct('In this quick guide you’ll use our [loaderScript] to set up:', {
-                loaderScript: <strong>Loader Script</strong>,
-              })
-            : tct('In this quick guide you’ll use [npm] or [yarn] to set up:', {
-                npm: <strong>npm</strong>,
-                yarn: <strong>yarn</strong>,
-              })}
+          {tct('In this quick guide you’ll use [npm] or [yarn] to set up:', {
+            npm: <strong>npm</strong>,
+            yarn: <strong>yarn</strong>,
+          })}
         </TextBlock>
       )}
       <Products>
@@ -470,44 +449,10 @@ export function ProductSelection({
           />
         )}
       </Products>
-      {showPackageManagerInfo && supportLoader && (
-        <AlternativeInstallationAlert type="info" showIcon>
-          {params.showLoader
-            ? tct('Prefer to set up Sentry using [npm:npm] or [yarn:yarn]? [goHere]', {
-                npm: <strong />,
-                yarn: <strong />,
-                goHere: (
-                  <SkipLazyLoaderButton
-                    onClick={handleToggleLoader}
-                    size="xs"
-                    priority="default"
-                  >
-                    {t('View npm/yarn instructions')}
-                  </SkipLazyLoaderButton>
-                ),
-              })
-            : tct('Prefer to set up Sentry using [bold:Loader Script]? [goHere]', {
-                bold: <strong />,
-                goHere: (
-                  <SkipLazyLoaderButton
-                    onClick={handleToggleLoader}
-                    size="xs"
-                    priority="default"
-                  >
-                    {t('View loader instructions')}
-                  </SkipLazyLoaderButton>
-                ),
-              })}
-        </AlternativeInstallationAlert>
-      )}
     </Fragment>
   );
 }
 
-const SkipLazyLoaderButton = styled(Button)`
-  margin-left: ${space(1)};
-`;
-
 const Products = styled('div')`
   display: flex;
   flex-wrap: wrap;
@@ -564,15 +509,3 @@ const TooltipDescription = styled('div')`
   gap: ${space(0.5)};
   justify-content: flex-start;
 `;
-
-const AlternativeInstallationAlert = styled(Alert)`
-  margin-bottom: 0px;
-  /*
-   * The first child is the icon.
-   * We render a button within the message, so to ensure proper alignment,
-   * the height of the first child (icon) needs to be set to 'auto'.
-   */
-  > *:first-child {
-    height: auto;
-  }
-`;

+ 40 - 2
static/app/gettingStartedDocs/javascript/javascript.spec.tsx

@@ -4,11 +4,15 @@ import {textWithMarkupMatcher} from 'sentry-test/utils';
 
 import {ProductSolution} from 'sentry/components/onboarding/productSelection';
 
-import docs from './javascript';
+import docs, {InstallationMode} from './javascript';
 
 describe('javascript onboarding docs', function () {
   it('renders onboarding docs correctly', () => {
-    renderWithOnboardingLayout(docs);
+    renderWithOnboardingLayout(docs, {
+      selectedOptions: {
+        installationMode: InstallationMode.MANUAL,
+      },
+    });
 
     // Renders main headings
     expect(screen.getByRole('heading', {name: 'Install'})).toBeInTheDocument();
@@ -31,6 +35,9 @@ describe('javascript onboarding docs', function () {
         ProductSolution.PERFORMANCE_MONITORING,
         ProductSolution.SESSION_REPLAY,
       ],
+      selectedOptions: {
+        installationMode: InstallationMode.MANUAL,
+      },
     });
 
     expect(
@@ -50,6 +57,9 @@ describe('javascript onboarding docs', function () {
         ProductSolution.ERROR_MONITORING,
         ProductSolution.PERFORMANCE_MONITORING,
       ],
+      selectedOptions: {
+        installationMode: InstallationMode.MANUAL,
+      },
     });
 
     expect(
@@ -63,6 +73,9 @@ describe('javascript onboarding docs', function () {
         ProductSolution.ERROR_MONITORING,
         ProductSolution.SESSION_REPLAY,
       ],
+      selectedOptions: {
+        installationMode: InstallationMode.MANUAL,
+      },
     });
 
     expect(
@@ -76,6 +89,9 @@ describe('javascript onboarding docs', function () {
   it('enables profiling by setting profiling sample rates', () => {
     renderWithOnboardingLayout(docs, {
       selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.PROFILING],
+      selectedOptions: {
+        installationMode: InstallationMode.MANUAL,
+      },
     });
 
     expect(
@@ -85,4 +101,26 @@ describe('javascript onboarding docs', function () {
       screen.getByText(textWithMarkupMatcher(/profilesSampleRate: 1\.0/))
     ).toBeInTheDocument();
   });
+
+  it('renders Loader Script by default', function () {
+    renderWithOnboardingLayout(docs);
+
+    expect(screen.getByRole('radio', {name: 'Loader Script'})).toBeChecked();
+
+    expect(
+      screen.getByRole('heading', {name: 'Configure SDK (Optional)'})
+    ).toBeInTheDocument();
+  });
+
+  it('renders package manager installation', function () {
+    renderWithOnboardingLayout(docs, {
+      selectedOptions: {
+        installationMode: InstallationMode.MANUAL,
+      },
+    });
+
+    expect(screen.getByRole('radio', {name: 'Npm/Yarn'})).toBeChecked();
+
+    expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument();
+  });
 });

+ 296 - 29
static/app/gettingStartedDocs/javascript/javascript.tsx

@@ -1,9 +1,12 @@
+import {css} from '@emotion/react';
+
 import ExternalLink from 'sentry/components/links/externalLink';
 import crashReportCallout from 'sentry/components/onboarding/gettingStartedDoc/feedback/crashReportCallout';
 import widgetCallout from 'sentry/components/onboarding/gettingStartedDoc/feedback/widgetCallout';
 import TracePropagationMessage from 'sentry/components/onboarding/gettingStartedDoc/replay/tracePropagationMessage';
 import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
 import type {
+  BasePlatformOptions,
   Docs,
   DocsParams,
   OnboardingConfig,
@@ -28,8 +31,39 @@ import {
 } from 'sentry/components/onboarding/gettingStartedDoc/utils/replayOnboarding';
 import replayOnboardingJsLoader from 'sentry/gettingStartedDocs/javascript/jsLoader/jsLoader';
 import {t, tct} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import TextBlock from 'sentry/views/settings/components/text/textBlock';
+
+import {updateDynamicSdkLoaderOptions} from './jsLoader/updateDynamicSdkLoaderOptions';
+
+export enum InstallationMode {
+  AUTO = 'auto',
+  MANUAL = 'manual',
+}
+
+const platformOptions = {
+  installationMode: {
+    label: t('Installation Mode'),
+    items: [
+      {
+        label: t('Loader Script'),
+        value: InstallationMode.AUTO,
+      },
+      {
+        label: t('Npm/Yarn'),
+        value: InstallationMode.MANUAL,
+      },
+    ],
+    defaultValue: InstallationMode.AUTO,
+  },
+} satisfies BasePlatformOptions;
 
-type Params = DocsParams;
+type PlatformOptions = typeof platformOptions;
+type Params = DocsParams<PlatformOptions>;
+
+const isAutoInstall = (params: Params) =>
+  params.platformOptions.installationMode === InstallationMode.AUTO;
 
 const getSdkSetupSnippet = (params: Params) => `
 import * as Sentry from "@sentry/browser";
@@ -112,64 +146,227 @@ const getInstallConfig = () => [
   },
 ];
 
-const onboarding: OnboardingConfig = {
-  introduction: MaybeBrowserProfilingBetaWarning,
-  install: () => [
+const getVerifyConfig = () => [
+  {
+    type: StepType.VERIFY,
+    description: t(
+      "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected."
+    ),
+    configurations: [
+      {
+        code: [
+          {
+            label: 'Javascript',
+            value: 'javascript',
+            language: 'javascript',
+            code: getVerifyJSSnippet(),
+          },
+        ],
+      },
+    ],
+  },
+];
+
+const loaderScriptOnboarding: OnboardingConfig<PlatformOptions> = {
+  introduction: () =>
+    tct('In this quick guide you’ll use our [strong: Loader Script] to set up:', {
+      strong: <strong />,
+    }),
+  install: params => [
     {
       type: StepType.INSTALL,
-      description: t(
-        'Sentry captures data by using an SDK within your application’s runtime.'
-      ),
-      configurations: getInstallConfig(),
+      description: t('Add this script tag to the top of the page:'),
+      configurations: [
+        {
+          language: 'html',
+          code: [
+            {
+              label: 'HTML',
+              value: 'html',
+              language: 'html',
+              code: `
+<script
+  src="${params.dsn.cdn}"
+  crossorigin="anonymous"
+></script>`,
+            },
+          ],
+        },
+      ],
     },
   ],
-  configure: (params: Params) => [
+  configure: params => [
     {
       type: StepType.CONFIGURE,
       description: t(
         "Initialize Sentry as early as possible in your application's lifecycle."
       ),
+      isOptional: true,
       configurations: [
         {
+          language: 'html',
           code: [
             {
-              label: 'JavaScript',
-              value: 'javascript',
-              language: 'javascript',
-              code: getSdkSetupSnippet(params),
+              label: 'HTML',
+              value: 'html',
+              language: 'html',
+              code: `
+<script>
+  Sentry.onLoad(function() {
+    Sentry.init({${
+      !(params.isPerformanceSelected || params.isReplaySelected)
+        ? `
+        // You can add any additional configuration here`
+        : ''
+    }${
+      params.isPerformanceSelected
+        ? `
+        // Tracing
+        tracesSampleRate: 1.0, // Capture 100% of the transactions`
+        : ''
+    }${
+      params.isReplaySelected
+        ? `
+        // 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.`
+        : ''
+    }
+      });
+  });
+</script>`,
             },
           ],
         },
-        ...(params.isProfilingSelected
-          ? [getProfilingDocumentHeaderConfigurationStep()]
-          : []),
       ],
+      onOptionalToggleClick: showOptionalConfig => {
+        if (showOptionalConfig) {
+          trackAnalytics('onboarding.js_loader_npm_docs_optional_shown', {
+            organization: params.organization,
+            platform: params.platformKey,
+            project_id: params.projectId,
+          });
+        }
+      },
     },
-    getUploadSourceMapsStep({
-      guideLink: 'https://docs.sentry.io/platforms/javascript/sourcemaps/',
-      ...params,
+  ],
+  verify: getVerifyConfig,
+  nextSteps: () => [
+    {
+      id: 'performance-monitoring',
+      name: t('Tracing'),
+      description: t(
+        'Track down transactions to connect the dots between 10-second page loads and poor-performing API calls or slow database queries.'
+      ),
+      link: 'https://docs.sentry.io/platforms/javascript/tracing/',
+    },
+    {
+      id: 'session-replay',
+      name: t('Session Replay'),
+      description: t(
+        'Get to the root cause of an error or latency issue faster by seeing all the technical details related to that issue in one visual replay on your web application.'
+      ),
+      link: 'https://docs.sentry.io/platforms/javascript/session-replay/',
+    },
+    {
+      id: 'source-maps',
+      name: t('Source Maps'),
+      description: t('Learn how to enable readable stack traces in your Sentry errors.'),
+      link: 'https://docs.sentry.io/platforms/javascript/sourcemaps/',
+    },
+    {
+      id: 'sdk-configuration',
+      name: t('SDK Configuration'),
+      description: t(
+        'Learn about additional configuration options for the Javascript SDK.'
+      ),
+      link: 'https://docs.sentry.io/platforms/javascript/configuration/',
+    },
+  ],
+  onPageLoad: params => {
+    return () => {
+      trackAnalytics('onboarding.setup_loader_docs_rendered', {
+        organization: params.organization,
+        platform: params.platformKey,
+        project_id: params.projectId,
+      });
+    };
+  },
+  onPlatformOptionsChange: params => {
+    return () => {
+      trackAnalytics('onboarding.js_loader_npm_docs_shown', {
+        organization: params.organization,
+        platform: params.platformKey,
+        project_id: params.projectId,
+      });
+    };
+  },
+  onProductSelectionChange: params => {
+    return products => {
+      updateDynamicSdkLoaderOptions({
+        orgSlug: params.organization.slug,
+        projectSlug: params.projectSlug,
+        products,
+        projectKey: params.projectKeyId,
+        api: params.api,
+      });
+    };
+  },
+  onProductSelectionLoad: params => {
+    return products => {
+      updateDynamicSdkLoaderOptions({
+        orgSlug: params.organization.slug,
+        projectSlug: params.projectSlug,
+        products,
+        projectKey: params.projectKeyId,
+        api: params.api,
+      });
+    };
+  },
+};
+
+const packageManagerOnboarding: OnboardingConfig<PlatformOptions> = {
+  introduction: () =>
+    tct('In this quick guide you’ll use [strong:npm] or [strong:yarn] to set up:', {
+      strong: <strong />,
     }),
+  install: () => [
+    {
+      type: StepType.INSTALL,
+      description: t(
+        'Sentry captures data by using an SDK within your application’s runtime.'
+      ),
+      configurations: getInstallConfig(),
+    },
   ],
-  verify: () => [
+  configure: params => [
     {
-      type: StepType.VERIFY,
+      type: StepType.CONFIGURE,
       description: t(
-        "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected."
+        "Initialize Sentry as early as possible in your application's lifecycle."
       ),
       configurations: [
         {
           code: [
             {
-              label: 'Javascript',
+              label: 'JavaScript',
               value: 'javascript',
               language: 'javascript',
-              code: getVerifyJSSnippet(),
+              code: getSdkSetupSnippet(params),
             },
           ],
         },
+        ...(params.isProfilingSelected
+          ? [getProfilingDocumentHeaderConfigurationStep()]
+          : []),
       ],
     },
+    getUploadSourceMapsStep({
+      guideLink: 'https://docs.sentry.io/platforms/javascript/sourcemaps/',
+      ...params,
+    }),
   ],
+  verify: getVerifyConfig,
   nextSteps: () => [
     {
       id: 'performance-monitoring',
@@ -188,9 +385,78 @@ const onboarding: OnboardingConfig = {
       link: 'https://docs.sentry.io/platforms/javascript/session-replay/',
     },
   ],
+  onPageLoad: params => {
+    return () => {
+      trackAnalytics('onboarding.js_loader_npm_docs_shown', {
+        organization: params.organization,
+        platform: params.platformKey,
+        project_id: params.projectId,
+      });
+    };
+  },
+  onPlatformOptionsChange: params => {
+    return () => {
+      trackAnalytics('onboarding.setup_loader_docs_rendered', {
+        organization: params.organization,
+        platform: params.platformKey,
+        project_id: params.projectId,
+      });
+    };
+  },
+};
+
+const onboarding: OnboardingConfig<PlatformOptions> = {
+  introduction: params => (
+    <div
+      css={css`
+        display: flex;
+        flex-direction: column;
+        gap: ${space(1)};
+      `}
+    >
+      <MaybeBrowserProfilingBetaWarning {...params} />
+      <TextBlock noMargin>
+        {isAutoInstall(params)
+          ? loaderScriptOnboarding.introduction?.(params)
+          : packageManagerOnboarding.introduction?.(params)}
+      </TextBlock>
+    </div>
+  ),
+  install: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.install(params)
+      : packageManagerOnboarding.install(params),
+  configure: (params: Params) =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.configure(params)
+      : packageManagerOnboarding.configure(params),
+  verify: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.verify(params)
+      : packageManagerOnboarding.verify(params),
+  nextSteps: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.nextSteps?.(params)
+      : packageManagerOnboarding.nextSteps?.(params),
+  onPageLoad: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.onPageLoad?.(params)
+      : packageManagerOnboarding.onPageLoad?.(params),
+  onProductSelectionChange: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.onProductSelectionChange?.(params)
+      : packageManagerOnboarding.onProductSelectionChange?.(params),
+  onPlatformOptionsChange: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.onPlatformOptionsChange?.(params)
+      : packageManagerOnboarding.onPlatformOptionsChange?.(params),
+  onProductSelectionLoad: params =>
+    isAutoInstall(params)
+      ? loaderScriptOnboarding.onProductSelectionLoad?.(params)
+      : packageManagerOnboarding.onProductSelectionLoad?.(params),
 };
 
-const replayOnboarding: OnboardingConfig = {
+const replayOnboarding: OnboardingConfig<PlatformOptions> = {
   install: () => [
     {
       type: StepType.INSTALL,
@@ -228,7 +494,7 @@ const replayOnboarding: OnboardingConfig = {
   nextSteps: () => [],
 };
 
-const feedbackOnboarding: OnboardingConfig = {
+const feedbackOnboarding: OnboardingConfig<PlatformOptions> = {
   install: () => [
     {
       type: StepType.INSTALL,
@@ -271,7 +537,7 @@ const feedbackOnboarding: OnboardingConfig = {
   nextSteps: () => [],
 };
 
-const crashReportOnboarding: OnboardingConfig = {
+const crashReportOnboarding: OnboardingConfig<PlatformOptions> = {
   introduction: () => getCrashReportModalIntroduction(),
   install: (params: Params) => getCrashReportJavaScriptInstallStep(params),
   configure: () => [
@@ -289,7 +555,7 @@ const crashReportOnboarding: OnboardingConfig = {
   nextSteps: () => [],
 };
 
-const performanceOnboarding: OnboardingConfig = {
+const performanceOnboarding: OnboardingConfig<PlatformOptions> = {
   introduction: () =>
     t(
       "Adding Performance to your Browser JavaScript project is simple. Make sure you've got these basics down."
@@ -382,7 +648,7 @@ transaction.finish(); // Finishing the transaction will send it to Sentry`,
   nextSteps: () => [],
 };
 
-const docs: Docs = {
+const docs: Docs<PlatformOptions> = {
   onboarding,
   feedbackOnboardingNpm: feedbackOnboarding,
   replayOnboarding,
@@ -390,6 +656,7 @@ const docs: Docs = {
   performanceOnboarding,
   customMetricsOnboarding: getJSMetricsOnboarding({getInstallConfig}),
   crashReportOnboarding,
+  platformOptions,
 };
 
 export default docs;

+ 50 - 0
static/app/gettingStartedDocs/javascript/jsLoader/updateDynamicSdkLoaderOptions.ts

@@ -0,0 +1,50 @@
+import type {Client} from 'sentry/api';
+import {ProductSolution} from 'sentry/components/onboarding/productSelection';
+import {t} from 'sentry/locale';
+import type {Organization} from 'sentry/types/organization';
+import type {Project, ProjectKey} from 'sentry/types/project';
+import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
+
+export async function updateDynamicSdkLoaderOptions({
+  orgSlug,
+  projectSlug,
+  products,
+  api,
+  projectKey,
+}: {
+  api: Client;
+  orgSlug: Organization['slug'];
+  projectKey: ProjectKey['id'];
+  projectSlug: Project['slug'];
+  products?: ProductSolution[];
+}) {
+  const newDynamicSdkLoaderOptions: ProjectKey['dynamicSdkLoaderOptions'] = {
+    hasPerformance: false,
+    hasReplay: false,
+    hasDebug: false,
+  };
+
+  (products ?? []).forEach(product => {
+    // eslint-disable-next-line default-case
+    switch (product) {
+      case ProductSolution.PERFORMANCE_MONITORING:
+        newDynamicSdkLoaderOptions.hasPerformance = true;
+        break;
+      case ProductSolution.SESSION_REPLAY:
+        newDynamicSdkLoaderOptions.hasReplay = true;
+        break;
+    }
+  });
+
+  try {
+    await api.requestPromise(`/projects/${orgSlug}/${projectSlug}/keys/${projectKey}/`, {
+      method: 'PUT',
+      data: {
+        dynamicSdkLoaderOptions: newDynamicSdkLoaderOptions,
+      },
+    });
+  } catch (error) {
+    const message = t('Unable to dynamically update the SDK loader configuration');
+    handleXhrErrorResponse(message, error);
+  }
+}

+ 18 - 29
static/app/views/onboarding/setupDocs.spec.tsx

@@ -1,7 +1,13 @@
+import {OrganizationFixture} from 'sentry-fixture/organization';
 import {ProjectKeysFixture} from 'sentry-fixture/projectKeys';
 
 import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary';
+import {
+  render,
+  screen,
+  userEvent,
+  waitForElementToBeRemoved,
+} from 'sentry-test/reactTestingLibrary';
 
 import {OnboardingContextProvider} from 'sentry/components/onboarding/onboardingContext';
 import {ProductSolution} from 'sentry/components/onboarding/productSelection';
@@ -367,11 +373,11 @@ describe('Onboarding Setup Docs', function () {
         router: {
           location: {
             query: {
-              showLoader: 'true',
               product: [
                 ProductSolution.PERFORMANCE_MONITORING,
                 ProductSolution.SESSION_REPLAY,
               ],
+              installationMode: 'auto',
             },
           },
         },
@@ -382,6 +388,9 @@ describe('Onboarding Setup Docs', function () {
             platform: 'javascript',
           },
         ],
+        organization: OrganizationFixture({
+          features: ['session-replay', 'performance-view'],
+        }),
       });
 
       const updateLoaderMock = MockApiClient.addMockResponse({
@@ -398,7 +407,7 @@ describe('Onboarding Setup Docs', function () {
         orgSlug: organization.slug,
       });
 
-      const {rerender} = render(
+      render(
         <OnboardingContextProvider>
           <SetupDocs
             active
@@ -420,7 +429,7 @@ describe('Onboarding Setup Docs', function () {
       );
 
       expect(
-        await screen.findByRole('heading', {name: 'Configure Browser JavaScript SDK'})
+        await screen.findByRole('radio', {name: 'Loader Script'})
       ).toBeInTheDocument();
 
       expect(updateLoaderMock).toHaveBeenCalledTimes(1);
@@ -440,41 +449,21 @@ describe('Onboarding Setup Docs', function () {
         }
       );
 
-      // update query in URL
-      router.location.query = {
-        showLoader: 'true',
-        product: [ProductSolution.SESSION_REPLAY],
-      };
-      rerender(
-        <OnboardingContextProvider>
-          <SetupDocs
-            active
-            onComplete={() => {}}
-            stepIndex={2}
-            router={router}
-            route={{}}
-            location={router.location}
-            genSkipOnboardingLink={() => ''}
-            orgId={organization.slug}
-            search=""
-            recentCreatedProject={project as OnboardingRecentCreatedProject}
-          />
-        </OnboardingContextProvider>
-      );
-
       expect(
-        await screen.findByRole('heading', {name: 'Configure Browser JavaScript SDK'})
+        await screen.findByRole('radio', {name: 'Loader Script'})
       ).toBeInTheDocument();
 
+      await userEvent.click(screen.getByRole('checkbox', {name: 'Session Replay'}));
       expect(updateLoaderMock).toHaveBeenCalledTimes(2);
+
       expect(updateLoaderMock).toHaveBeenLastCalledWith(
         expect.any(String), // The URL
         {
           data: {
             dynamicSdkLoaderOptions: {
               hasDebug: false,
-              hasPerformance: false,
-              hasReplay: true,
+              hasPerformance: true,
+              hasReplay: false,
             },
           },
           error: expect.any(Function),

+ 0 - 8
static/app/views/onboarding/setupDocs.tsx

@@ -14,7 +14,6 @@ import {decodeList} from 'sentry/utils/queryString';
 import useOrganization from 'sentry/utils/useOrganization';
 import SetupIntroduction from 'sentry/views/onboarding/components/setupIntroduction';
 import {useOnboardingQueryParams} from 'sentry/views/onboarding/components/useOnboardingQueryParams';
-import {SetupDocsLoader} from 'sentry/views/onboarding/setupDocsLoader';
 import {OtherPlatformsInfo} from 'sentry/views/projectInstall/otherPlatformsInfo';
 
 import FirstEventFooter from './components/firstEventFooter';
@@ -66,13 +65,6 @@ function SetupDocs({location, recentCreatedProject: project}: StepProps) {
                   projectSlug={project.slug}
                   platform={currentPlatform.name}
                 />
-              ) : params.showLoader ? (
-                <SetupDocsLoader
-                  organization={organization}
-                  project={project}
-                  location={location}
-                  platform={currentPlatform.id}
-                />
               ) : (
                 <SdkDocumentation
                   platform={currentPlatform}

+ 0 - 366
static/app/views/onboarding/setupDocsLoader.tsx

@@ -1,366 +0,0 @@
-import {Fragment, useCallback, useEffect, useState} from 'react';
-import styled from '@emotion/styled';
-import {motion} from 'framer-motion';
-import type {Location} from 'history';
-import beautify from 'js-beautify';
-
-import {Button} from 'sentry/components/button';
-import {CodeSnippet} from 'sentry/components/codeSnippet';
-import HookOrDefault from 'sentry/components/hookOrDefault';
-import ExternalLink from 'sentry/components/links/externalLink';
-import LoadingError from 'sentry/components/loadingError';
-import {DocumentationWrapper} from 'sentry/components/onboarding/documentationWrapper';
-import {
-  ProductSelection,
-  ProductSolution,
-} from 'sentry/components/onboarding/productSelection';
-import {IconChevron} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-import type {Organization} from 'sentry/types/organization';
-import type {PlatformKey, Project, ProjectKey} from 'sentry/types/project';
-import {trackAnalytics} from 'sentry/utils/analytics';
-import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
-import {decodeList} from 'sentry/utils/queryString';
-import useApi from 'sentry/utils/useApi';
-
-const ProductSelectionAvailabilityHook = HookOrDefault({
-  hookName: 'component:product-selection-availability',
-  defaultComponent: ProductSelection,
-});
-
-export function SetupDocsLoader({
-  organization,
-  location,
-  project,
-  platform,
-}: {
-  location: Location;
-  organization: Organization;
-  platform: PlatformKey | null;
-  project: Project;
-}) {
-  const api = useApi();
-  const currentPlatform = platform ?? project?.platform ?? 'other';
-  const [projectKey, setProjectKey] = useState<ProjectKey | null>(null);
-  const [hasLoadingError, setHasLoadingError] = useState(false);
-  const [projectKeyUpdateError, setProjectKeyUpdateError] = useState(false);
-
-  const productsQuery =
-    (location.query.product as ProductSolution | ProductSolution[] | undefined) ?? [];
-  const products = decodeList(productsQuery) as ProductSolution[];
-
-  const fetchData = useCallback(async () => {
-    const keysApiUrl = `/projects/${organization.slug}/${project.slug}/keys/`;
-
-    try {
-      const loadedKeys = await api.requestPromise(keysApiUrl);
-
-      if (loadedKeys.length === 0) {
-        setHasLoadingError(true);
-        return;
-      }
-
-      setProjectKey(loadedKeys[0]);
-      setHasLoadingError(false);
-    } catch (error) {
-      setHasLoadingError(error);
-      throw error;
-    }
-  }, [api, organization.slug, project.slug]);
-
-  // Automatically update the products on the project key when the user changes the product selection
-  // Note that on initial visit, this will also update the project key with the default products (=all products)
-  // This DOES NOT take into account any initial products that may already be set on the project key - they will always be overwritten!
-  const handleUpdateSelectedProducts = useCallback(async () => {
-    const keyId = projectKey?.id;
-
-    if (!keyId) {
-      return;
-    }
-
-    const newDynamicSdkLoaderOptions: ProjectKey['dynamicSdkLoaderOptions'] = {
-      hasPerformance: false,
-      hasReplay: false,
-      hasDebug: false,
-    };
-
-    products.forEach(product => {
-      // eslint-disable-next-line default-case
-      switch (product) {
-        case ProductSolution.PERFORMANCE_MONITORING:
-          newDynamicSdkLoaderOptions.hasPerformance = true;
-          break;
-        case ProductSolution.SESSION_REPLAY:
-          newDynamicSdkLoaderOptions.hasReplay = true;
-          break;
-      }
-    });
-
-    try {
-      await api.requestPromise(
-        `/projects/${organization.slug}/${project.slug}/keys/${keyId}/`,
-        {
-          method: 'PUT',
-          data: {
-            dynamicSdkLoaderOptions: newDynamicSdkLoaderOptions,
-          },
-        }
-      );
-      setProjectKeyUpdateError(false);
-    } catch (error) {
-      const message = t('Unable to updated dynamic SDK loader configuration');
-      handleXhrErrorResponse(message, error);
-      setProjectKeyUpdateError(true);
-    }
-  }, [api, organization.slug, project.slug, projectKey?.id, products]);
-
-  const track = useCallback(() => {
-    if (!project?.id) {
-      return;
-    }
-
-    trackAnalytics('onboarding.setup_loader_docs_rendered', {
-      organization,
-      platform: currentPlatform,
-      project_id: project?.id,
-    });
-  }, [organization, currentPlatform, project?.id]);
-
-  useEffect(() => {
-    fetchData();
-  }, [fetchData, organization.slug, project.slug]);
-
-  useEffect(() => {
-    handleUpdateSelectedProducts();
-  }, [handleUpdateSelectedProducts, location.query.product]);
-
-  useEffect(() => {
-    track();
-  }, [track]);
-
-  return (
-    <Fragment>
-      <Header>
-        <ProductSelectionAvailabilityHook
-          organization={organization}
-          platform={currentPlatform}
-          projectId={project.id}
-        />
-      </Header>
-      <Divider />
-      {projectKeyUpdateError && (
-        <LoadingError
-          message={t('Failed to update the project key with the selected products.')}
-          onRetry={handleUpdateSelectedProducts}
-        />
-      )}
-
-      {!hasLoadingError ? (
-        projectKey !== null && (
-          <ProjectKeyInfo
-            projectKey={projectKey}
-            platform={platform}
-            organization={organization}
-            project={project}
-            products={products}
-          />
-        )
-      ) : (
-        <LoadingError
-          message={t('Failed to load Client Keys for the project.')}
-          onRetry={fetchData}
-        />
-      )}
-    </Fragment>
-  );
-}
-
-function ProjectKeyInfo({
-  projectKey,
-  platform,
-  organization,
-  project,
-  products,
-}: {
-  organization: Organization;
-  platform: PlatformKey | null;
-  products: ProductSolution[];
-  project: Project;
-  projectKey: ProjectKey;
-}) {
-  const [showOptionalConfig, setShowOptionalConfig] = useState(false);
-
-  const loaderLink = projectKey.dsn.cdn;
-  const currentPlatform = platform ?? project?.platform ?? 'other';
-  const hasPerformance = products.includes(ProductSolution.PERFORMANCE_MONITORING);
-  const hasSessionReplay = products.includes(ProductSolution.SESSION_REPLAY);
-
-  const configCodeSnippet = beautify.html(
-    `<script>
-Sentry.onLoad(function() {
-  Sentry.init({${
-    !(hasPerformance || hasSessionReplay)
-      ? `
-    // You can add any additional configuration here`
-      : ''
-  }${
-    hasPerformance
-      ? `
-    // Tracing
-    tracesSampleRate: 1.0, // Capture 100% of the transactions`
-      : ''
-  }${
-    hasSessionReplay
-      ? `
-    // 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.`
-      : ''
-  }
-  });
-});
-</script>`,
-    {indent_size: 2}
-  );
-
-  const verifyCodeSnippet = beautify.html(
-    `<script>
-  myUndefinedFunction();
-</script>`,
-    {indent_size: 2}
-  );
-
-  const toggleOptionalConfiguration = useCallback(() => {
-    const show = !showOptionalConfig;
-
-    setShowOptionalConfig(show);
-
-    if (show) {
-      trackAnalytics('onboarding.js_loader_optional_configuration_shown', {
-        organization,
-        platform: currentPlatform,
-        project_id: project.id,
-      });
-    }
-  }, [organization, project.id, currentPlatform, showOptionalConfig]);
-
-  return (
-    <DocsWrapper>
-      <DocumentationWrapper>
-        <h2>{t('Install')}</h2>
-        <p>{t('Add this script tag to the top of the page:')}</p>
-
-        <CodeSnippet dark language="html">
-          {beautify.html(
-            `<script src="${loaderLink}" crossorigin="anonymous"></script>`,
-            {indent_size: 2, wrap_attributes: 'force-expand-multiline'}
-          )}
-        </CodeSnippet>
-
-        <OptionalConfigWrapper>
-          <ToggleButton
-            priority="link"
-            borderless
-            size="zero"
-            icon={<IconChevron direction={showOptionalConfig ? 'down' : 'right'} />}
-            aria-label={t('Toggle optional configuration')}
-            onClick={toggleOptionalConfiguration}
-          />
-          <h2 onClick={toggleOptionalConfiguration}>{t('Configuration (Optional)')}</h2>
-        </OptionalConfigWrapper>
-        {showOptionalConfig && (
-          <div>
-            <p>
-              {t(
-                "Initialize Sentry as early as possible in your application's lifecycle."
-              )}
-            </p>
-            <CodeSnippet dark language="html">
-              {configCodeSnippet}
-            </CodeSnippet>
-          </div>
-        )}
-
-        <h2>{t('Verify')}</h2>
-        <p>
-          {t(
-            "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected."
-          )}
-        </p>
-        <CodeSnippet dark language="html">
-          {verifyCodeSnippet}
-        </CodeSnippet>
-
-        <hr />
-
-        <h2>{t('Next Steps')}</h2>
-        <ul>
-          <li>
-            <ExternalLink href="https://docs.sentry.io/platforms/javascript/sourcemaps/">
-              {t('Source Maps')}
-            </ExternalLink>
-            {': '}
-            {t('Learn how to enable readable stack traces in your Sentry errors.')}
-          </li>
-          <li>
-            <ExternalLink href="https://docs.sentry.io/platforms/javascript/configuration/">
-              {t('SDK Configuration')}
-            </ExternalLink>
-            {': '}
-            {t('Learn how to configure your SDK using our Loader Script')}
-          </li>
-          {!products.includes(ProductSolution.PERFORMANCE_MONITORING) && (
-            <li>
-              <ExternalLink href="https://docs.sentry.io/platforms/javascript/tracing/">
-                {t('Tracing')}
-              </ExternalLink>
-              {': '}
-              {t(
-                'Track down transactions to connect the dots between 10-second page loads and poor-performing API calls or slow database queries.'
-              )}
-            </li>
-          )}
-          {!products.includes(ProductSolution.SESSION_REPLAY) && (
-            <li>
-              <ExternalLink href="https://docs.sentry.io/platforms/javascript/session-replay/">
-                {t('Session Replay')}
-              </ExternalLink>
-              {': '}
-              {t(
-                'Get to the root cause of an error or latency issue faster by seeing all the technical details related to that issue in one visual replay on your web application.'
-              )}
-            </li>
-          )}
-        </ul>
-      </DocumentationWrapper>
-    </DocsWrapper>
-  );
-}
-
-const DocsWrapper = styled(motion.div)``;
-
-const Header = styled('div')`
-  display: flex;
-  flex-direction: column;
-  gap: ${space(2)};
-`;
-
-const OptionalConfigWrapper = styled('div')`
-  display: flex;
-  cursor: pointer;
-`;
-
-const ToggleButton = styled(Button)`
-  &,
-  :hover {
-    color: ${p => p.theme.gray500};
-  }
-`;
-
-const Divider = styled('hr')<{withBottomMargin?: boolean}>`
-  height: 1px;
-  width: 100%;
-  background: ${p => p.theme.border};
-  border: none;
-`;

+ 0 - 9
static/app/views/projectInstall/platform.tsx

@@ -29,7 +29,6 @@ import {decodeList} from 'sentry/utils/queryString';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useNavigate} from 'sentry/utils/useNavigate';
 import useOrganization from 'sentry/utils/useOrganization';
-import {SetupDocsLoader} from 'sentry/views/onboarding/setupDocsLoader';
 import {GettingStartedWithProjectContext} from 'sentry/views/projects/gettingStartedWithProjectContext';
 
 import {OtherPlatformsInfo} from './otherPlatformsInfo';
@@ -58,7 +57,6 @@ export function ProjectInstallPlatform({
   const gettingStartedWithProjectContext = useContext(GettingStartedWithProjectContext);
 
   const isSelfHosted = ConfigStore.get('isSelfHosted');
-  const showLoaderOnboarding = location.query.showLoader === 'true';
 
   const products = useMemo(
     () => decodeList(location.query.product ?? []) as ProductSolution[],
@@ -172,13 +170,6 @@ export function ProjectInstallPlatform({
           projectSlug={project.slug}
           platform={platform.name ?? 'other'}
         />
-      ) : showLoaderOnboarding ? (
-        <SetupDocsLoader
-          organization={organization}
-          project={project}
-          location={location}
-          platform={currentPlatform.id}
-        />
       ) : (
         <SdkDocumentation
           platform={currentPlatform}

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