Browse Source

feat(targeted-onboardinig): analytics and button bar (#33374)

This PR does the following (only impacts the multi-platform onboarding flow and not the existing onboarding)

* Adds some analytics events related to onboarding
* Switches the primary and secondary CTAs on the setup docs page
* Makes the order of platforms in the multi-platform picker match the order in which they appear in the config instead of alphabetical order
Stephen Cefali 2 years ago
parent
commit
f1c66039ed

+ 12 - 2
static/app/components/multiPlatformPicker.tsx

@@ -29,6 +29,11 @@ const isPopular = (platform: PlatformIntegration) =>
     platform.id as typeof popularPlatformCategories[number]
   );
 
+const popularIndex = (platform: PlatformIntegration) =>
+  popularPlatformCategories.indexOf(
+    platform.id as typeof popularPlatformCategories[number]
+  );
+
 const PlatformList = styled('div')`
   display: grid;
   gap: ${space(1)};
@@ -75,13 +80,18 @@ function PlatformPicker(props: PlatformPickerProps) {
       (currentCategory?.platforms as undefined | string[])?.includes(platform.id);
 
     const popularTopOfAllCompare = (a: PlatformIntegration, b: PlatformIntegration) => {
-      // for the all category, put popular ones at the top
+      // for the all category, put popular ones at the top in the order they appear in the popular list
       if (category === 'all') {
+        if (isPopular(a) && isPopular(b)) {
+          // if both popular, maintain ordering from popular list
+          return popularIndex(a) - popularIndex(b);
+        }
         if (isPopular(a) !== isPopular(b)) {
           return isPopular(a) ? -1 : 1;
         }
       }
-      return a.id.localeCompare(b.id);
+      // maintain ordering otherwise
+      return 0;
     };
 
     const filtered = platforms

+ 7 - 0
static/app/utils/analytics/growthAnalyticsEvents.tsx

@@ -52,6 +52,10 @@ export type GrowthEventParameters = {
   'growth.demo_modal_clicked_signup': {};
   'growth.issue_open_in_discover_btn_clicked': {};
   'growth.onboarding_clicked_instrument_app': {source?: string};
+  'growth.onboarding_clicked_project_in_sidebar': {platform: string};
+  'growth.onboarding_clicked_setup_platform_later': PlatformParam & {
+    project_index: number;
+  };
   'growth.onboarding_clicked_skip': {source?: string};
   'growth.onboarding_load_choose_platform': {};
   'growth.onboarding_set_up_your_project': PlatformParam;
@@ -118,11 +122,14 @@ export const growthEventMap: Record<GrowthAnalyticsKey, string> = {
   'growth.onboarding_view_full_docs': 'Growth: Onboarding View Full Docs',
   'growth.onboarding_view_sample_event': 'Growth: Onboarding View Sample Event',
   'growth.onboarding_clicked_instrument_app': 'Growth: Onboarding Clicked Instrument App',
+  'growth.onboarding_clicked_setup_platform_later':
+    'Growth: Onboarding Clicked Setup Platform Later',
   'invite_request.approved': 'Invite Request Approved',
   'invite_request.denied': 'Invite Request Denied',
   'growth.demo_modal_clicked_signup': 'Growth: Demo Modal Clicked Signup',
   'growth.demo_modal_clicked_continue': 'Growth: Demo Modal Clicked Continue',
   'growth.clicked_enter_sandbox': 'Growth: Clicked Enter Sandbox',
+  'growth.onboarding_clicked_project_in_sidebar': 'Growth: Clicked Project Sidebar',
   'growth.sample_transaction_docs_link_clicked':
     'Growth: Sample Transaction Docs Link Clicked',
   'growth.sample_error_onboarding_link_clicked':

+ 17 - 35
static/app/views/onboarding/targetedOnboarding/components/firstEventFooter.tsx

@@ -36,62 +36,44 @@ export default function FirstEventFooter({
 }: FirstEventFooterProps) {
   const source = 'targeted_onboarding_first_event_footer';
 
-  const getSecondaryCta = ({firstIssue}: {firstIssue: null | true | Group}) => {
+  const getSecondaryCta = () => {
+    // if hasn't sent first event, allow skiping
+    if (!hasFirstEvent) {
+      return <Button onClick={onClickSetupLater}>{t('Setup Later')}</Button>;
+    }
+    // if last, no secondary cta
+    if (isLast) {
+      return null;
+    }
+    return <Button onClick={onClickSetupLater}>{t('Next Platform')}</Button>;
+  };
+
+  const getPrimaryCta = ({firstIssue}: {firstIssue: null | true | Group}) => {
     // if hasn't sent first event, allow creation of sample error
     if (!hasFirstEvent) {
       return (
         <CreateSampleEventButton
           project={project}
           source="targted-onboarding"
-          priority="default"
+          priority="primary"
         >
           {t('View Sample Error')}
         </CreateSampleEventButton>
       );
     }
-    // if last, no secondary cta
-    if (isLast) {
-      return null;
-    }
+
     return (
       <Button
         to={`/organizations/${organization.slug}/issues/${
           firstIssue !== true && firstIssue !== null ? `${firstIssue.id}/` : ''
         }`}
+        priority="primary"
       >
         {t('Take me to my error')}
       </Button>
     );
   };
 
-  const getPrimaryCta = ({firstIssue}: {firstIssue: null | true | Group}) => {
-    // if hasn't sent first event, allow skiping
-    if (!hasFirstEvent) {
-      return (
-        <Button priority="primary" onClick={onClickSetupLater}>
-          {t('Setup Later')}
-        </Button>
-      );
-    }
-    if (isLast) {
-      return (
-        <Button
-          to={`/organizations/${organization.slug}/issues/${
-            firstIssue !== true && firstIssue !== null ? `${firstIssue.id}/` : ''
-          }`}
-          priority="primary"
-        >
-          {t('Take me to my error')}
-        </Button>
-      );
-    }
-    return (
-      <Button priority="primary" onClick={onClickSetupLater}>
-        {t('Next Platform')}
-      </Button>
-    );
-  };
-
   return (
     <GridFooter>
       <SkipOnboardingLink
@@ -123,7 +105,7 @@ export default function FirstEventFooter({
               </AnimatedText>
             </StatusWrapper>
             <OnboardingButtonBar gap={2}>
-              {getSecondaryCta({firstIssue})}
+              {getSecondaryCta()}
               {getPrimaryCta({firstIssue})}
             </OnboardingButtonBar>
           </Fragment>

+ 3 - 3
static/app/views/onboarding/targetedOnboarding/components/sidebar.tsx

@@ -14,15 +14,15 @@ import testableTransition from 'sentry/utils/testableTransition';
 type Props = {
   checkProjectHasFirstEvent: (project: Project) => boolean;
   projects: Project[];
+  selectProject: (newProjectId: string) => void;
   // A map from selected platform keys to the projects created by onboarding.
   selectedPlatformToProjectIdMap: {[key in PlatformKey]?: string};
-  setNewProject: (newProjectId: string) => void;
   activeProject?: Project;
 };
 function Sidebar({
   projects,
   activeProject,
-  setNewProject,
+  selectProject,
   checkProjectHasFirstEvent,
   selectedPlatformToProjectIdMap,
 }: Props) {
@@ -36,7 +36,7 @@ function Sidebar({
       <ProjectWrapper
         key={projectSlug}
         isActive={isActive}
-        onClick={() => project && setNewProject(project.id)}
+        onClick={() => project && selectProject(project.id)}
         disabled={!project}
       >
         <StyledPlatformIcon platform={platform} size={36} />

+ 19 - 2
static/app/views/onboarding/targetedOnboarding/setupDocs.tsx

@@ -15,6 +15,7 @@ import platforms from 'sentry/data/platforms';
 import {t, tct} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Project} from 'sentry/types';
+import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
 import getDynamicText from 'sentry/utils/getDynamicText';
 import {Theme} from 'sentry/utils/theme';
 import useApi from 'sentry/utils/useApi';
@@ -122,6 +123,15 @@ function SetupDocs({organization, projects, search}: Props) {
     browserHistory.push(`${window.location.pathname}?${searchParams}`);
   };
 
+  const selectProject = (newProjectId: string) => {
+    const matchedProject = projects.find(p => p.id === newProjectId);
+    trackAdvancedAnalyticsEvent('growth.onboarding_clicked_project_in_sidebar', {
+      organization,
+      platform: matchedProject?.platform || 'unknown',
+    });
+    setNewProject(newProjectId);
+  };
+
   const missingExampleWarning = () => {
     const missingExample =
       platformDocs && platformDocs.html.includes(INCOMPLETE_DOC_FLAG);
@@ -182,7 +192,7 @@ function SetupDocs({organization, projects, search}: Props) {
               : {}
           }
           activeProject={project}
-          {...{checkProjectHasFirstEvent, setNewProject}}
+          {...{checkProjectHasFirstEvent, selectProject}}
         />
         <MainContent>
           <FullIntroduction currentPlatform={currentPlatform} />
@@ -200,7 +210,14 @@ function SetupDocs({organization, projects, search}: Props) {
           isLast={projectIndex === projects.length - 1}
           hasFirstEvent={checkProjectHasFirstEvent(project)}
           onClickSetupLater={() => {
-            // TODO: analytics
+            trackAdvancedAnalyticsEvent(
+              'growth.onboarding_clicked_setup_platform_later',
+              {
+                organization,
+                platform: currentPlatform,
+                project_index: projectIndex,
+              }
+            );
             const nextProject = projects.find(
               (p, index) => !p.firstEvent && index > projectIndex
             );