Browse Source

feat(growth): sandbox walkthrough start guide (#40689)

this PR adds the start guide for the sandbox walkthrough. this is where
the guided tours begin and show you the different guided tours that are
available. changes to guideAnchors for guides have been made for the
sidebar/issue tours do not start immediately on arriving in the sandbox.
some copy has been changed so the same components can be used for
onboarding and the sandbox walkthrough.
<img width="399" alt="Screen Shot 2022-10-27 at 3 02 27 PM"
src="https://user-images.githubusercontent.com/46740234/198406757-f7fc5dff-f2e7-47cb-9cd4-d01fe69b404a.png">
Richard Roggenkemper 2 years ago
parent
commit
2f17e6bd66

+ 2 - 1
static/app/components/assistant/getGuidesContent.tsx

@@ -3,10 +3,11 @@ import ExternalLink from 'sentry/components/links/externalLink';
 import Link from 'sentry/components/links/link';
 import {t, tct} from 'sentry/locale';
 import ConfigStore from 'sentry/stores/configStore';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 
 export default function getGuidesContent(orgSlug: string | null): GuidesContent {
   if (ConfigStore.get('demoMode')) {
-    if (localStorage.getItem('new-walkthrough') === '1') {
+    if (isDemoWalkthrough()) {
       return getDemoModeGuidesV2();
     }
     return getDemoModeGuides();

+ 2 - 3
static/app/components/demo/demoHeader.tsx

@@ -11,6 +11,7 @@ import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAna
 import {
   extraQueryParameter,
   extraQueryParameterWithEmail,
+  isDemoWalkthrough,
   urlAttachQueryParams,
 } from 'sentry/utils/demoMode';
 
@@ -19,13 +20,11 @@ export default function DemoHeader() {
   // if the user came from a SaaS org, we should send them back to upgrade when they leave the sandbox
   const extraSearchParams = extraQueryParameter();
 
-  const walkthrough = localStorage.getItem('new-walkthrough');
-
   const collapsed = !!useLegacyStore(PreferencesStore).collapsed;
 
   let docsBtn, reqDemoBtn, signUpBtn;
 
-  if (walkthrough === '1') {
+  if (isDemoWalkthrough()) {
     docsBtn = (
       <DocsDemoBtn
         onClick={() =>

+ 12 - 4
static/app/components/onboardingWizard/progressHeader.tsx

@@ -5,6 +5,7 @@ import ProgressRing from 'sentry/components/progressRing';
 import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {OnboardingTaskDescriptor, OnboardingTaskStatus} from 'sentry/types';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 
 type Props = {
   allTasks: OnboardingTaskDescriptor[];
@@ -14,6 +15,15 @@ type Props = {
 function ProgressHeader({allTasks, completedTasks}: Props) {
   const theme = useTheme();
 
+  let title: string, description: string;
+  if (isDemoWalkthrough()) {
+    title = t('Guided Tours');
+    description = t('Take a guided tour to see what Sentry can do for you');
+  } else {
+    title = t('Quick Start');
+    description = t('Walk through this guide to get the most out of Sentry right away.');
+  }
+
   return (
     <Container>
       <StyledProgressRing
@@ -29,10 +39,8 @@ function ProgressHeader({allTasks, completedTasks}: Props) {
           color: ${theme.textColor};
         `}
       />
-      <HeaderTitle>{t('Quick Start')}</HeaderTitle>
-      <Description>
-        {t('Walk through this guide to get the most out of Sentry right away.')}
-      </Description>
+      <HeaderTitle>{title}</HeaderTitle>
+      <Description>{description}</Description>
     </Container>
   );
 }

+ 4 - 1
static/app/components/onboardingWizard/sidebar.tsx

@@ -11,6 +11,7 @@ import Tooltip from 'sentry/components/tooltip';
 import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {OnboardingTask, OnboardingTaskKey, Project} from 'sentry/types';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import testableTransition from 'sentry/utils/testableTransition';
 import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -52,8 +53,10 @@ Heading.defaultProps = {
   transition: testableTransition(),
 };
 
+const completeNowText = isDemoWalkthrough() ? t('Sentry Basics') : t('Next Steps');
+
 const customizedTasksHeading = <Heading key="customized">{t('The Basics')}</Heading>;
-const completeNowHeading = <Heading key="now">{t('Next Steps')}</Heading>;
+const completeNowHeading = <Heading key="now">{completeNowText}</Heading>;
 const upcomingTasksHeading = (
   <Heading key="upcoming">
     <Tooltip

+ 6 - 0
static/app/components/onboardingWizard/task.tsx

@@ -11,9 +11,11 @@ import LetterAvatar from 'sentry/components/letterAvatar';
 import Tooltip from 'sentry/components/tooltip';
 import {IconCheckmark, IconClose, IconLock, IconSync} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
+import DemoWalkthroughStore from 'sentry/stores/demoWalkthroughStore';
 import space from 'sentry/styles/space';
 import {AvatarUser, OnboardingTask, OnboardingTaskKey, Organization} from 'sentry/types';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import testableTransition from 'sentry/utils/testableTransition';
 import {useRouteContext} from 'sentry/utils/useRouteContext';
 import withOrganization from 'sentry/utils/withOrganization';
@@ -65,6 +67,10 @@ function Task(props: Props) {
     recordAnalytics(task, organization, 'clickthrough');
     e.stopPropagation();
 
+    if (isDemoWalkthrough()) {
+      DemoWalkthroughStore.activateGuideAnchor(task.task);
+    }
+
     if (task.actionType === 'external') {
       window.open(task.location, '_blank');
     }

+ 51 - 0
static/app/components/onboardingWizard/taskConfig.tsx

@@ -17,6 +17,7 @@ import {
   Organization,
   Project,
 } from 'sentry/types';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import EventWaiter from 'sentry/utils/eventWaiter';
 import withApi from 'sentry/utils/withApi';
 import {OnboardingState} from 'sentry/views/onboarding/types';
@@ -74,6 +75,56 @@ export function getOnboardingTasks({
   projects,
   onboardingState,
 }: Options): OnboardingTaskDescriptor[] {
+  if (isDemoWalkthrough()) {
+    return [
+      {
+        task: OnboardingTaskKey.SIDEBAR_GUIDE,
+        title: t('Check out the different tabs'),
+        description: t('Press the start button for a guided tour through each tab.'),
+        skippable: false,
+        requisites: [],
+        actionType: 'app',
+        location: `/organizations/${organization.slug}/projects/`,
+        display: true,
+      },
+      {
+        task: OnboardingTaskKey.ISSUE_GUIDE,
+        title: t('Issues'),
+        description: t(
+          'Here’s a list of errors and performance problems. And everything you need to know to fix it.'
+        ),
+        skippable: false,
+        requisites: [],
+        actionType: 'app',
+        location: `/organizations/${organization.slug}/issues/`,
+        display: true,
+      },
+      {
+        task: OnboardingTaskKey.PERFORMANCE_GUIDE,
+        title: t('Performance'),
+        description: t(
+          'See slow fast. Trace slow-loading pages back to their API calls as well as all related errors'
+        ),
+        skippable: false,
+        requisites: [],
+        actionType: 'app',
+        location: `/organizations/${organization.slug}/performance/`,
+        display: true,
+      },
+      {
+        task: OnboardingTaskKey.RELEASE_GUIDE,
+        title: t('Releases'),
+        description: t(
+          'Track the health of every release. See differences between releases from crash analytics to adoption rates.'
+        ),
+        skippable: false,
+        requisites: [],
+        actionType: 'app',
+        location: `/organizations/${organization.slug}/releases/`,
+        display: true,
+      },
+    ];
+  }
   return [
     {
       task: OnboardingTaskKey.FIRST_PROJECT,

+ 11 - 1
static/app/components/sidebar/index.tsx

@@ -27,12 +27,14 @@ import {
 } from 'sentry/icons';
 import {t} from 'sentry/locale';
 import ConfigStore from 'sentry/stores/configStore';
+import DemoWalkthroughStore from 'sentry/stores/demoWalkthroughStore';
 import HookStore from 'sentry/stores/hookStore';
 import PreferencesStore from 'sentry/stores/preferencesStore';
 import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
 import {useLegacyStore} from 'sentry/stores/useLegacyStore';
 import space from 'sentry/styles/space';
 import {Organization} from 'sentry/types';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import {getDiscoverLandingUrl} from 'sentry/utils/discover/urls';
 import theme from 'sentry/utils/theme';
 import useMedia from 'sentry/utils/useMedia';
@@ -128,12 +130,20 @@ function Sidebar({location, organization}: Props) {
     organization,
   };
 
+  const sidebarAnchor = isDemoWalkthrough() ? (
+    <GuideAnchor target="projects" disabled={!DemoWalkthroughStore.get('sidebar')}>
+      {t('Projects')}
+    </GuideAnchor>
+  ) : (
+    <GuideAnchor target="projects">{t('Projects')}</GuideAnchor>
+  );
+
   const projects = hasOrganization && (
     <SidebarItem
       {...sidebarItemProps}
       index
       icon={<IconProject size="md" />}
-      label={<GuideAnchor target="projects">{t('Projects')}</GuideAnchor>}
+      label={sidebarAnchor}
       to={`/organizations/${organization.slug}/projects/`}
       id="projects"
     />

+ 5 - 2
static/app/components/sidebar/onboardingStatus.tsx

@@ -14,6 +14,7 @@ import HookStore from 'sentry/stores/hookStore';
 import space from 'sentry/styles/space';
 import {OnboardingTaskStatus, Organization, Project} from 'sentry/types';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import theme, {Theme} from 'sentry/utils/theme';
 import withProjects from 'sentry/utils/withProjects';
 import {usePersistedOnboardingState} from 'sentry/views/onboarding/utils';
@@ -88,7 +89,9 @@ function OnboardingStatus({
     return null;
   }
 
-  const label = t('Quick Start');
+  const walkthrough = isDemoWalkthrough();
+  const label = walkthrough ? t('Guided Tours') : t('Quick Start');
+  const task = walkthrough ? 'tours' : 'tasks';
 
   return (
     <Fragment>
@@ -112,7 +115,7 @@ function OnboardingStatus({
           <div>
             <Heading>{label}</Heading>
             <Remaining>
-              {tct('[numberRemaining] Remaining tasks', {numberRemaining})}
+              {tct('[numberRemaining] Remaining [task]', {numberRemaining, task})}
               {pendingCompletionSeen && <PendingSeenIndicator />}
             </Remaining>
           </div>

+ 10 - 1
static/app/components/stream/group.tsx

@@ -22,6 +22,7 @@ import GroupChart from 'sentry/components/stream/groupChart';
 import TimeSince from 'sentry/components/timeSince';
 import {DEFAULT_STATS_PERIOD} from 'sentry/constants';
 import {t} from 'sentry/locale';
+import DemoWalkthroughStore from 'sentry/stores/demoWalkthroughStore';
 import GroupStore from 'sentry/stores/groupStore';
 import SelectedGroupStore from 'sentry/stores/selectedGroupStore';
 import {useLegacyStore} from 'sentry/stores/useLegacyStore';
@@ -36,6 +37,7 @@ import {
 } from 'sentry/types';
 import {defined, percent} from 'sentry/utils';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
+import {isDemoWalkthrough} from 'sentry/utils/demoMode';
 import EventView from 'sentry/utils/discover/eventView';
 import usePageFilters from 'sentry/utils/usePageFilters';
 import withOrganization from 'sentry/utils/withOrganization';
@@ -413,6 +415,12 @@ function BaseGroupRow({
     </DeprecatedDropdownMenu>
   );
 
+  const issueStreamAnchor = isDemoWalkthrough() ? (
+    <GuideAnchor target="issue_stream" disabled={!DemoWalkthroughStore.get('issue')} />
+  ) : (
+    <GuideAnchor target="issue_stream" />
+  );
+
   return (
     <Wrapper
       data-test-id="group"
@@ -445,7 +453,8 @@ function BaseGroupRow({
         />
         <EventOrGroupExtraDetails data={group} showInboxTime={showInboxTime} />
       </GroupSummary>
-      {hasGuideAnchor && <GuideAnchor target="issue_stream" />}
+      {hasGuideAnchor && issueStreamAnchor}
+
       {withChart && !displayReprocessingLayout && (
         <ChartWrapper
           className={`hidden-xs hidden-sm ${narrowGroups ? 'hidden-md' : ''}`}

+ 49 - 0
static/app/stores/demoWalkthroughStore.tsx

@@ -0,0 +1,49 @@
+import {createStore, StoreDefinition} from 'reflux';
+
+import {OnboardingTaskKey} from 'sentry/types';
+
+interface DemoWalkthroughStoreDefinition extends StoreDefinition {
+  activateGuideAnchor(guide: string): void;
+  get(guide: string): boolean;
+}
+
+const storeConfig: DemoWalkthroughStoreDefinition = {
+  issueGuideAnchor: false,
+  sidebarGuideAnchor: false,
+  init() {
+    // XXX: Do not use `this.listenTo` in this store. We avoid usage of reflux
+    // listeners due to their leaky nature in tests.
+  },
+
+  activateGuideAnchor(task: OnboardingTaskKey) {
+    switch (task) {
+      case OnboardingTaskKey.ISSUE_GUIDE:
+        this.issueGuideAnchor = true;
+        this.trigger(this.issueGuideAnchor);
+        break;
+      case OnboardingTaskKey.SIDEBAR_GUIDE:
+        this.sidebarGuideAnchor = true;
+        this.trigger(this.sidebarGuideAnchor);
+        break;
+      default:
+    }
+  },
+
+  get(guide: string) {
+    switch (guide) {
+      case 'issue':
+        return this.issueGuideAnchor;
+      case 'sidebar':
+        return this.sidebarGuideAnchor;
+      default:
+        return false;
+    }
+  },
+};
+
+/**
+ * This store is used to hold local user preferences
+ * Side-effects (like reading/writing to cookies) are done in associated actionCreators
+ */
+const DemoWalkthroughStore = createStore(storeConfig);
+export default DemoWalkthroughStore;

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