Browse Source

feat(ddm): Add amplitude events (#64177)

Extend basic analytics on metrics page.

- closes https://github.com/getsentry/sentry/issues/63584
ArthurKnaus 1 year ago
parent
commit
105775eca3

+ 18 - 0
static/app/utils/analytics/ddmAnalyticsEvents.tsx

@@ -1,9 +1,21 @@
 export type DDMEventParameters = {
 export type DDMEventParameters = {
+  'ddm.add-to-dashboard': {
+    source: 'global' | 'widget';
+  };
+  'ddm.code-location': {};
+  'ddm.create-alert': {
+    source: 'global' | 'widget';
+  };
   'ddm.page-view': {};
   'ddm.page-view': {};
+  'ddm.sample-table-interaction': {
+    target: 'event-id' | 'transaction' | 'trace-id' | 'profile';
+  };
   'ddm.scratchpad.remove': {};
   'ddm.scratchpad.remove': {};
   'ddm.scratchpad.save': {};
   'ddm.scratchpad.save': {};
   'ddm.scratchpad.set-default': {};
   'ddm.scratchpad.set-default': {};
   'ddm.widget.add': {};
   'ddm.widget.add': {};
+  'ddm.widget.duplicate': {};
+  'ddm.widget.metric-settings': {};
   'ddm.widget.sort': {
   'ddm.widget.sort': {
     by: string;
     by: string;
     order: string;
     order: string;
@@ -17,4 +29,10 @@ export const ddmEventMap: Record<keyof DDMEventParameters, string> = {
   'ddm.scratchpad.set-default': 'DDM: Scratchpad Set as Default',
   'ddm.scratchpad.set-default': 'DDM: Scratchpad Set as Default',
   'ddm.widget.add': 'DDM: Widget Added',
   'ddm.widget.add': 'DDM: Widget Added',
   'ddm.widget.sort': 'DDM: Group By Sort Changed',
   'ddm.widget.sort': 'DDM: Group By Sort Changed',
+  'ddm.widget.duplicate': 'DDM: Widget Duplicated',
+  'ddm.widget.metric-settings': 'DDM: Widget Metric Settings',
+  'ddm.create-alert': 'DDM: Create Alert',
+  'ddm.add-to-dashboard': 'DDM: Add to Dashboard',
+  'ddm.code-location': 'DDM: Code Location',
+  'ddm.sample-table-interaction': 'DDM: Sample Table Interaction',
 };
 };

+ 19 - 3
static/app/views/ddm/contextMenu.tsx

@@ -15,6 +15,7 @@ import {
 } from 'sentry/icons';
 } from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import type {Organization} from 'sentry/types';
 import type {Organization} from 'sentry/types';
+import {trackAnalytics} from 'sentry/utils/analytics';
 import {isCustomMeasurement, isCustomMetric} from 'sentry/utils/metrics';
 import {isCustomMeasurement, isCustomMetric} from 'sentry/utils/metrics';
 import {
 import {
   convertToDashboardWidget,
   convertToDashboardWidget,
@@ -63,6 +64,9 @@ export function MetricQueryContextMenu({
         key: 'duplicate',
         key: 'duplicate',
         label: t('Duplicate'),
         label: t('Duplicate'),
         onAction: () => {
         onAction: () => {
+          trackAnalytics('ddm.widget.duplicate', {
+            organization,
+          });
           Sentry.metrics.increment('ddm.widget.duplicate');
           Sentry.metrics.increment('ddm.widget.duplicate');
           duplicateWidget(widgetIndex);
           duplicateWidget(widgetIndex);
         },
         },
@@ -73,6 +77,10 @@ export function MetricQueryContextMenu({
         label: t('Create Alert'),
         label: t('Create Alert'),
         disabled: !createAlert,
         disabled: !createAlert,
         onAction: () => {
         onAction: () => {
+          trackAnalytics('ddm.create-alert', {
+            organization,
+            source: 'widget',
+          });
           Sentry.metrics.increment('ddm.widget.alert');
           Sentry.metrics.increment('ddm.widget.alert');
           createAlert?.();
           createAlert?.();
         },
         },
@@ -83,6 +91,10 @@ export function MetricQueryContextMenu({
         label: t('Add to Dashboard'),
         label: t('Add to Dashboard'),
         disabled: !createDashboardWidget,
         disabled: !createDashboardWidget,
         onAction: () => {
         onAction: () => {
+          trackAnalytics('ddm.add-to-dashboard', {
+            organization,
+            source: 'widget',
+          });
           Sentry.metrics.increment('ddm.widget.dashboard');
           Sentry.metrics.increment('ddm.widget.dashboard');
           createDashboardWidget?.();
           createDashboardWidget?.();
         },
         },
@@ -93,6 +105,9 @@ export function MetricQueryContextMenu({
         label: t('Metric Settings'),
         label: t('Metric Settings'),
         disabled: !isCustomMetric({mri: metricsQuery.mri}),
         disabled: !isCustomMetric({mri: metricsQuery.mri}),
         onAction: () => {
         onAction: () => {
+          trackAnalytics('ddm.widget.settings', {
+            organization,
+          });
           Sentry.metrics.increment('ddm.widget.settings');
           Sentry.metrics.increment('ddm.widget.settings');
           navigateTo(
           navigateTo(
             `/settings/projects/:projectId/metrics/${encodeURIComponent(
             `/settings/projects/:projectId/metrics/${encodeURIComponent(
@@ -116,12 +131,13 @@ export function MetricQueryContextMenu({
     [
     [
       createAlert,
       createAlert,
       createDashboardWidget,
       createDashboardWidget,
+      metricsQuery.mri,
+      canDelete,
+      organization,
       duplicateWidget,
       duplicateWidget,
-      removeWidget,
       widgetIndex,
       widgetIndex,
-      canDelete,
-      metricsQuery.mri,
       router,
       router,
+      removeWidget,
     ]
     ]
   );
   );
 
 

+ 14 - 2
static/app/views/ddm/pageHeaderActions.tsx

@@ -66,7 +66,13 @@ export function PageHeaderActions({showCustomMetricButton, addCustomMetric}: Pro
         leadingItems: [<IconDashboard key="icon" />],
         leadingItems: [<IconDashboard key="icon" />],
         key: 'add-dashboard',
         key: 'add-dashboard',
         label: t('Add to Dashboard'),
         label: t('Add to Dashboard'),
-        onAction: createDashboard,
+        onAction: () => {
+          trackAnalytics('ddm.add-to-dashboard', {
+            organization,
+            source: 'global',
+          });
+          createDashboard();
+        },
       },
       },
       {
       {
         leadingItems: [<IconSettings key="icon" />],
         leadingItems: [<IconSettings key="icon" />],
@@ -108,7 +114,13 @@ export function PageHeaderActions({showCustomMetricButton, addCustomMetric}: Pro
             ? t('Custom measurements cannot be used to create alerts')
             ? t('Custom measurements cannot be used to create alerts')
             : undefined,
             : undefined,
           disabled: !createAlert,
           disabled: !createAlert,
-          onAction: createAlert,
+          onAction: () => {
+            trackAnalytics('ddm.create-alert', {
+              organization,
+              source: 'global',
+            });
+            createAlert?.();
+          },
         };
         };
       }),
       }),
     [
     [

+ 12 - 0
static/app/views/ddm/sampleTable.tsx

@@ -17,6 +17,7 @@ import {IconArrow, IconProfiling} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
 import type {MRI} from 'sentry/types';
 import type {MRI} from 'sentry/types';
+import {trackAnalytics} from 'sentry/utils/analytics';
 import {getDuration} from 'sentry/utils/formatters';
 import {getDuration} from 'sentry/utils/formatters';
 import {getMetricsCorrelationSpanUrl} from 'sentry/utils/metrics';
 import {getMetricsCorrelationSpanUrl} from 'sentry/utils/metrics';
 import type {MetricCorrelation, SelectionRange} from 'sentry/utils/metrics/types';
 import type {MetricCorrelation, SelectionRange} from 'sentry/utils/metrics/types';
@@ -86,6 +87,13 @@ export function SampleTable({
     // We only want to show the first 10 correlations
     // We only want to show the first 10 correlations
     .slice(0, 10) as MetricCorrelation[];
     .slice(0, 10) as MetricCorrelation[];
 
 
+  function trackClick(target: 'event-id' | 'transaction' | 'trace-id' | 'profile') {
+    trackAnalytics('ddm.sample-table-interaction', {
+      organization,
+      target,
+    });
+  }
+
   function renderHeadCell(col: Column) {
   function renderHeadCell(col: Column) {
     if (col.key === 'profileId') {
     if (col.key === 'profileId') {
       return <AlignCenter>{col.name}</AlignCenter>;
       return <AlignCenter>{col.name}</AlignCenter>;
@@ -125,6 +133,7 @@ export function SampleTable({
               row.transactionId,
               row.transactionId,
               row.transactionSpanId
               row.transactionSpanId
             )}
             )}
+            onClick={() => trackClick('event-id')}
             target="_blank"
             target="_blank"
           >
           >
             {row.transactionId.slice(0, 8)}
             {row.transactionId.slice(0, 8)}
@@ -152,6 +161,7 @@ export function SampleTable({
                   referrer: 'metrics',
                   referrer: 'metrics',
                 })}`
                 })}`
               )}
               )}
+              onClick={() => trackClick('transaction')}
             >
             >
               {row.segmentName}
               {row.segmentName}
             </Link>
             </Link>
@@ -182,6 +192,7 @@ export function SampleTable({
             to={normalizeUrl(
             to={normalizeUrl(
               `/organizations/${organization.slug}/performance/trace/${row.traceId}/`
               `/organizations/${organization.slug}/performance/trace/${row.traceId}/`
             )}
             )}
+            onClick={() => trackClick('trace-id')}
           >
           >
             {row.traceId.slice(0, 8)}
             {row.traceId.slice(0, 8)}
           </Link>
           </Link>
@@ -251,6 +262,7 @@ export function SampleTable({
               to={normalizeUrl(
               to={normalizeUrl(
                 `/organizations/${organization.slug}/profiling/profile/${project?.slug}/${row.profileId}/flamegraph/`
                 `/organizations/${organization.slug}/profiling/profile/${project?.slug}/${row.profileId}/flamegraph/`
               )}
               )}
+              onClick={() => trackClick('profile')}
               size="xs"
               size="xs"
             >
             >
               <IconProfiling size="xs" />
               <IconProfiling size="xs" />

+ 17 - 2
static/app/views/ddm/widgetDetails.tsx

@@ -1,12 +1,14 @@
-import {useState} from 'react';
+import {useCallback, useState} from 'react';
 import styled from '@emotion/styled';
 import styled from '@emotion/styled';
 
 
 import {TabList, TabPanels, Tabs} from 'sentry/components/tabs';
 import {TabList, TabPanels, Tabs} from 'sentry/components/tabs';
 import {Tooltip} from 'sentry/components/tooltip';
 import {Tooltip} from 'sentry/components/tooltip';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
 import {isCustomMetric} from 'sentry/utils/metrics';
 import {isCustomMetric} from 'sentry/utils/metrics';
 import type {MetricWidgetQueryParams} from 'sentry/utils/metrics/types';
 import type {MetricWidgetQueryParams} from 'sentry/utils/metrics/types';
+import useOrganization from 'sentry/utils/useOrganization';
 import {CodeLocations} from 'sentry/views/ddm/codeLocations';
 import {CodeLocations} from 'sentry/views/ddm/codeLocations';
 import {useDDMContext} from 'sentry/views/ddm/context';
 import {useDDMContext} from 'sentry/views/ddm/context';
 import {SampleTable} from 'sentry/views/ddm/sampleTable';
 import {SampleTable} from 'sentry/views/ddm/sampleTable';
@@ -23,6 +25,7 @@ const constructQueryString = (queryObject: Record<string, string>) => {
 };
 };
 
 
 export function WidgetDetails() {
 export function WidgetDetails() {
+  const organization = useOrganization();
   const {
   const {
     selectedWidgetIndex,
     selectedWidgetIndex,
     widgets,
     widgets,
@@ -46,9 +49,21 @@ export function WidgetDetails() {
     setHighlightedSampleId(sampleId);
     setHighlightedSampleId(sampleId);
   };
   };
 
 
+  const handleTabChange = useCallback(
+    (tab: Tab) => {
+      if (tab === Tab.CODE_LOCATIONS) {
+        trackAnalytics('ddm.code-locations', {
+          organization,
+        });
+      }
+      setSelectedTab(tab);
+    },
+    [organization]
+  );
+
   return (
   return (
     <TrayWrapper>
     <TrayWrapper>
-      <Tabs value={selectedTab} onChange={setSelectedTab}>
+      <Tabs value={selectedTab} onChange={handleTabChange}>
         <TabList>
         <TabList>
           <TabList.Item key={Tab.SAMPLES}>{t('Samples')}</TabList.Item>
           <TabList.Item key={Tab.SAMPLES}>{t('Samples')}</TabList.Item>
           <TabList.Item
           <TabList.Item