Browse Source

feat(growth): adding create sample transaction button (#27841)

* feat(growth): adding create sample transaction button

* fixes

* added tests
Zhixing Zhang 3 years ago
parent
commit
2c76fcb568

+ 2 - 0
src/sentry/conf/server.py

@@ -967,6 +967,8 @@ SENTRY_FEATURES = {
     "organizations:performance-events-page": False,
     # Enable interpolation of null data points in charts instead of zerofilling in performance
     "organizations:performance-chart-interpolation": False,
+    # Allow the user to create a sample transaction while onboarding
+    "organizations:performance-create-sample-transaction": False,
     # Enable the new Related Events feature
     "organizations:related-events": False,
     # Enable usage of external relays, for use with Relay. See

+ 3 - 0
src/sentry/features/__init__.py

@@ -112,6 +112,9 @@ default_manager.add("organizations:performance-tag-page", OrganizationFeature, T
 default_manager.add("organizations:performance-events-page", OrganizationFeature, True)
 default_manager.add("organizations:performance-chart-interpolation", OrganizationFeature, True)
 default_manager.add("organizations:performance-view", OrganizationFeature)
+default_manager.add(
+    "organizations:performance-create-sample-transaction", OrganizationFeature, True
+)
 default_manager.add("organizations:project-transaction-threshold", OrganizationFeature, True)
 default_manager.add(
     "organizations:project-transaction-threshold-override", OrganizationFeature, True

+ 12 - 2
static/app/utils/advancedAnalytics.tsx

@@ -8,6 +8,10 @@ import {
   IntegrationEventParameters,
 } from 'app/utils/integrationEvents';
 import {issueEventMap, IssueEventParameters} from 'app/utils/issueEvents';
+import {
+  performanceEventMap,
+  PerformanceEventParameters,
+} from 'app/utils/performanceEvents';
 
 const ANALYTICS_SESSION = 'ANALYTICS_SESSION';
 
@@ -28,9 +32,15 @@ const hasAnalyticsDebug = () => window.localStorage.getItem('DEBUG_ANALYTICS') =
 
 export type EventParameters = IntegrationEventParameters &
   GrowthEventParameters &
-  IssueEventParameters;
+  IssueEventParameters &
+  PerformanceEventParameters;
 
-const allEventMap = {...integrationEventMap, ...growthEventMap, ...issueEventMap};
+const allEventMap = {
+  ...integrationEventMap,
+  ...growthEventMap,
+  ...issueEventMap,
+  ...performanceEventMap,
+};
 
 type AnalyticsKey = keyof EventParameters;
 

+ 26 - 0
static/app/utils/performanceEvents.tsx

@@ -0,0 +1,26 @@
+import {PlatformKey} from 'app/data/platformCategories';
+
+type SampleTransactionParam = {
+  platform?: PlatformKey;
+};
+
+type PerformanceTourParams = {
+  step: number;
+  duration: number;
+};
+
+export type PerformanceEventParameters = {
+  'performance_views.create_sample_transaction': SampleTransactionParam;
+  'performance_views.tour.start': {};
+  'performance_views.tour.advance': PerformanceTourParams;
+  'performance_views.tour.close': PerformanceTourParams;
+};
+
+export type PerformanceEventKey = keyof PerformanceEventParameters;
+
+export const performanceEventMap: Record<PerformanceEventKey, string | null> = {
+  'performance_views.create_sample_transaction': 'Growth: Performance Sample Transaction',
+  'performance_views.tour.start': 'Performance Views: Tour Start',
+  'performance_views.tour.advance': 'Performance Views: Tour Advance',
+  'performance_views.tour.close': 'Performance Views: Tour Close',
+};

+ 1 - 1
static/app/views/performance/content.tsx

@@ -224,7 +224,7 @@ class PerformanceContent extends Component<Props, State> {
           <GlobalSdkUpdateAlert />
           {this.renderError()}
           {showOnboarding ? (
-            <Onboarding organization={organization} />
+            <Onboarding organization={organization} project={projects[0]} />
           ) : (
             <LandingContent
               eventView={eventView}

+ 88 - 42
static/app/views/performance/onboarding.tsx

@@ -1,4 +1,6 @@
+import {browserHistory} from 'react-router';
 import styled from '@emotion/styled';
+import * as Sentry from '@sentry/react';
 
 import emptyStateImg from 'sentry-images/spot/performance-empty-state.svg';
 import tourAlert from 'sentry-images/spot/performance-tour-alert.svg';
@@ -6,6 +8,12 @@ import tourCorrelate from 'sentry-images/spot/performance-tour-correlate.svg';
 import tourMetrics from 'sentry-images/spot/performance-tour-metrics.svg';
 import tourTrace from 'sentry-images/spot/performance-tour-trace.svg';
 
+import {
+  addErrorMessage,
+  addLoadingMessage,
+  clearIndicators,
+} from 'app/actionCreators/indicator';
+import {Client} from 'app/api';
 import Button from 'app/components/button';
 import ButtonBar from 'app/components/buttonBar';
 import FeatureTourModal, {
@@ -15,8 +23,9 @@ import FeatureTourModal, {
 } from 'app/components/modals/featureTourModal';
 import OnboardingPanel from 'app/components/onboardingPanel';
 import {t} from 'app/locale';
-import {Organization} from 'app/types';
-import {trackAnalyticsEvent} from 'app/utils/analytics';
+import {Organization, Project} from 'app/types';
+import {trackAdvancedAnalyticsEvent} from 'app/utils/advancedAnalytics';
+import withApi from 'app/utils/withApi';
 
 const performanceSetupUrl =
   'https://docs.sentry.io/performance-monitoring/getting-started/';
@@ -79,28 +88,85 @@ export const PERFORMANCE_TOUR_STEPS: TourStep[] = [
 
 type Props = {
   organization: Organization;
+  api: Client;
+  project: Project;
 };
 
-function Onboarding({organization}: Props) {
+function Onboarding({organization, project, api}: Props) {
   function handleAdvance(step: number, duration: number) {
-    trackAnalyticsEvent({
-      eventKey: 'performance_views.tour.advance',
-      eventName: 'Performance Views: Tour Advance',
-      organization_id: parseInt(organization.id, 10),
-      step,
-      duration,
-    });
+    trackAdvancedAnalyticsEvent(
+      'performance_views.tour.advance',
+      {step, duration},
+      organization
+    );
   }
 
   function handleClose(step: number, duration: number) {
-    trackAnalyticsEvent({
-      eventKey: 'performance_views.tour.close',
-      eventName: 'Performance Views: Tour Close',
-      organization_id: parseInt(organization.id, 10),
-      step,
-      duration,
-    });
+    trackAdvancedAnalyticsEvent(
+      'performance_views.tour.close',
+      {step, duration},
+      organization
+    );
   }
+  const showSampleTransactionBtn = organization.features.includes(
+    'performance-create-sample-transaction'
+  );
+  const featureTourBtn = (
+    <FeatureTourModal
+      steps={PERFORMANCE_TOUR_STEPS}
+      onAdvance={handleAdvance}
+      onCloseModal={handleClose}
+      doneUrl={performanceSetupUrl}
+      doneText={t('Start Setup')}
+    >
+      {({showModal}) => (
+        <Button
+          priority={showSampleTransactionBtn ? 'link' : 'default'}
+          onClick={() => {
+            trackAdvancedAnalyticsEvent('performance_views.tour.start', {}, organization);
+            showModal();
+          }}
+        >
+          {t('Take a Tour')}
+        </Button>
+      )}
+    </FeatureTourModal>
+  );
+  const secondaryBtn = showSampleTransactionBtn ? (
+    <Button
+      data-test-id="create-sample-transaction-btn"
+      onClick={async () => {
+        trackAdvancedAnalyticsEvent(
+          'performance_views.create_sample_transaction',
+          {platform: project.platform},
+          organization
+        );
+        addLoadingMessage(t('Processing sample event...'), {
+          duration: 15000,
+        });
+        const url = `/projects/${organization.slug}/${project.slug}/create-sample-transaction/`;
+        try {
+          const eventData = await api.requestPromise(url, {method: 'POST'});
+          browserHistory.push(
+            `/organizations/${organization.slug}/performance/${project.slug}:${eventData.eventID}/`
+          );
+          clearIndicators();
+        } catch (error) {
+          Sentry.withScope(scope => {
+            scope.setExtra('error', error);
+            Sentry.captureException(new Error('Failed to create sample event'));
+          });
+          clearIndicators();
+          addErrorMessage(t('Failed to create a new sample event'));
+          return;
+        }
+      }}
+    >
+      {t('Create Sample Transaction')}
+    </Button>
+  ) : (
+    featureTourBtn
+  );
 
   return (
     <OnboardingPanel image={<PerfImage src={emptyStateImg} />}>
@@ -118,30 +184,9 @@ function Onboarding({organization}: Props) {
         >
           {t('Start Setup')}
         </Button>
-        <FeatureTourModal
-          steps={PERFORMANCE_TOUR_STEPS}
-          onAdvance={handleAdvance}
-          onCloseModal={handleClose}
-          doneUrl={performanceSetupUrl}
-          doneText={t('Start Setup')}
-        >
-          {({showModal}) => (
-            <Button
-              priority="default"
-              onClick={() => {
-                trackAnalyticsEvent({
-                  eventKey: 'performance_views.tour.start',
-                  eventName: 'Performance Views: Tour Start',
-                  organization_id: parseInt(organization.id, 10),
-                });
-                showModal();
-              }}
-            >
-              {t('Take a Tour')}
-            </Button>
-          )}
-        </FeatureTourModal>
+        {secondaryBtn}
       </ButtonList>
+      {showSampleTransactionBtn && featureTourBtn}
     </OnboardingPanel>
   );
 }
@@ -151,7 +196,7 @@ const PerfImage = styled('img')`
     max-width: unset;
     user-select: none;
     position: absolute;
-    top: 50px;
+    top: 75px;
     bottom: 0;
     width: 450px;
     margin-top: auto;
@@ -169,6 +214,7 @@ const PerfImage = styled('img')`
 
 const ButtonList = styled(ButtonBar)`
   grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
+  margin-bottom: 16px;
 `;
 
-export default Onboarding;
+export default withApi(Onboarding);

+ 42 - 0
tests/js/spec/views/performance/content.spec.jsx

@@ -516,4 +516,46 @@ describe('Performance > Content', function () {
       expect(link).toHaveLength(1);
     }
   });
+
+  it('Display Create Sample Transaction Button with feature flag on', async function () {
+    const projects = [
+      TestStubs.Project({id: 1, firstTransactionEvent: false}),
+      TestStubs.Project({id: 2, firstTransactionEvent: false}),
+    ];
+    const data = initializeData(projects, {view: undefined}, [
+      'performance-create-sample-transaction',
+    ]);
+
+    const wrapper = mountWithTheme(
+      <PerformanceContent
+        organization={data.organization}
+        location={data.router.location}
+      />,
+      data.routerContext
+    );
+
+    expect(
+      wrapper.find('Button[data-test-id="create-sample-transaction-btn"]').exists()
+    ).toBe(true);
+  });
+
+  it('Do not display Create Sample Transaction Button with feature flag turned off', async function () {
+    const projects = [
+      TestStubs.Project({id: 1, firstTransactionEvent: false}),
+      TestStubs.Project({id: 2, firstTransactionEvent: false}),
+    ];
+    const data = initializeData(projects, {view: undefined}, []);
+
+    const wrapper = mountWithTheme(
+      <PerformanceContent
+        organization={data.organization}
+        location={data.router.location}
+      />,
+      data.routerContext
+    );
+
+    expect(
+      wrapper.find('Button[data-test-id="create-sample-transaction-btn"]').exists()
+    ).toBe(false);
+  });
 });