From 518f16e5714f1548998fef0c73e7f7f0ab8c1065 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Wed, 12 Nov 2025 16:12:53 -0800 Subject: [PATCH 1/5] feat(aci): Add monitor created/updated analytics Adds some basic tracking of type, dataset, aggregate. I think a lot of other fields are already normalized in the database and can be queried at any time. --- .../analytics/monitorsAnalyticsEvents.tsx | 20 +++++++++++++++++++ .../hooks/useCreateDetectorFormSubmit.tsx | 17 +++++++++++++++- .../hooks/useEditDetectorFormSubmit.tsx | 17 +++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/static/app/utils/analytics/monitorsAnalyticsEvents.tsx b/static/app/utils/analytics/monitorsAnalyticsEvents.tsx index 2da41d8934b1a0..33997a7fb18ef4 100644 --- a/static/app/utils/analytics/monitorsAnalyticsEvents.tsx +++ b/static/app/utils/analytics/monitorsAnalyticsEvents.tsx @@ -3,10 +3,30 @@ export type MonitorsEventParameters = { guide: string; platform: string; }; + 'monitor.created': { + detector_type: + | 'error' + | 'metric_issue' + | 'uptime_domain_failure' + | 'monitor_check_in_failure'; + aggregate?: string; + dataset?: string; + }; + 'monitor.updated': { + detector_type: + | 'error' + | 'metric_issue' + | 'uptime_domain_failure' + | 'monitor_check_in_failure'; + aggregate?: string; + dataset?: string; + }; }; type MonitorsAnalyticsKey = keyof MonitorsEventParameters; export const monitorsEventMap: Record = { 'landing_page.platform_guide.viewed': 'Crons Landing Page: Viewed Platform Guide', + 'monitor.created': 'Detectors: Created', + 'monitor.updated': 'Detectors: Updated', }; diff --git a/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx b/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx index c8110fd571991a..1d8f1fe4bedb51 100644 --- a/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx +++ b/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx @@ -7,6 +7,7 @@ import type { BaseDetectorUpdatePayload, Detector, } from 'sentry/types/workflowEngine/detectors'; +import {trackAnalytics} from 'sentry/utils/analytics'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {useCreateDetector} from 'sentry/views/detectors/hooks'; @@ -44,6 +45,20 @@ export function useCreateDetectorFormSubmit< const payload = formDataToEndpointPayload(data as TFormData); const resultDetector = await createDetector(payload); + let dataset: string | undefined; + let aggregate: string | undefined; + if (resultDetector.type === 'metric_issue') { + dataset = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.dataset; + aggregate = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.aggregate; + } + + trackAnalytics('monitor.created', { + organization, + detector_type: resultDetector.type, + dataset, + aggregate, + }); + addSuccessMessage(t('Monitor created successfully')); if (onSuccess) { @@ -66,7 +81,7 @@ export function useCreateDetectorFormSubmit< [ formDataToEndpointPayload, createDetector, - organization.slug, + organization, navigate, onSuccess, onError, diff --git a/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx b/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx index 42131fed22edef..814adb604b5f12 100644 --- a/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx +++ b/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx @@ -7,6 +7,7 @@ import type { BaseDetectorUpdatePayload, Detector, } from 'sentry/types/workflowEngine/detectors'; +import {trackAnalytics} from 'sentry/utils/analytics'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {useUpdateDetector} from 'sentry/views/detectors/hooks'; @@ -52,6 +53,20 @@ export function useEditDetectorFormSubmit< const resultDetector = await updateDetector(updatedData); + let dataset: string | undefined; + let aggregate: string | undefined; + if (resultDetector.type === 'metric_issue') { + dataset = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.dataset; + aggregate = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.aggregate; + } + + trackAnalytics('monitor.updated', { + organization, + detector_type: resultDetector.type, + dataset, + aggregate, + }); + addSuccessMessage(t('Monitor updated successfully')); if (onSuccess) { @@ -75,7 +90,7 @@ export function useEditDetectorFormSubmit< detector, formDataToEndpointPayload, updateDetector, - organization.slug, + organization, navigate, onSuccess, onError, From 0c74774e2e7a07f7cc0f57420935ed663027cc9d Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Wed, 12 Nov 2025 16:16:20 -0800 Subject: [PATCH 2/5] make it easier for more use of snubaQuery --- .../hooks/useCreateDetectorFormSubmit.tsx | 15 +++++++-------- .../detectors/hooks/useEditDetectorFormSubmit.tsx | 15 +++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx b/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx index 1d8f1fe4bedb51..0dc8ca7025d686 100644 --- a/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx +++ b/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx @@ -6,6 +6,7 @@ import {t} from 'sentry/locale'; import type { BaseDetectorUpdatePayload, Detector, + SnubaQuery, } from 'sentry/types/workflowEngine/detectors'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useNavigate} from 'sentry/utils/useNavigate'; @@ -45,18 +46,16 @@ export function useCreateDetectorFormSubmit< const payload = formDataToEndpointPayload(data as TFormData); const resultDetector = await createDetector(payload); - let dataset: string | undefined; - let aggregate: string | undefined; - if (resultDetector.type === 'metric_issue') { - dataset = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.dataset; - aggregate = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.aggregate; - } + const snubaQuery: SnubaQuery | undefined = + resultDetector.type === 'metric_issue' + ? resultDetector.dataSources[0]?.queryObj?.snubaQuery + : undefined; trackAnalytics('monitor.created', { organization, detector_type: resultDetector.type, - dataset, - aggregate, + dataset: snubaQuery?.dataset, + aggregate: snubaQuery?.aggregate, }); addSuccessMessage(t('Monitor created successfully')); diff --git a/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx b/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx index 814adb604b5f12..3b2acf92a42d19 100644 --- a/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx +++ b/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx @@ -6,6 +6,7 @@ import {t} from 'sentry/locale'; import type { BaseDetectorUpdatePayload, Detector, + SnubaQuery, } from 'sentry/types/workflowEngine/detectors'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useNavigate} from 'sentry/utils/useNavigate'; @@ -53,18 +54,16 @@ export function useEditDetectorFormSubmit< const resultDetector = await updateDetector(updatedData); - let dataset: string | undefined; - let aggregate: string | undefined; - if (resultDetector.type === 'metric_issue') { - dataset = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.dataset; - aggregate = resultDetector.dataSources[0]?.queryObj?.snubaQuery?.aggregate; - } + const snubaQuery: SnubaQuery | undefined = + resultDetector.type === 'metric_issue' + ? resultDetector.dataSources[0]?.queryObj?.snubaQuery + : undefined; trackAnalytics('monitor.updated', { organization, detector_type: resultDetector.type, - dataset, - aggregate, + dataset: snubaQuery?.dataset, + aggregate: snubaQuery?.aggregate, }); addSuccessMessage(t('Monitor updated successfully')); From 40c658e15b8b64206a5029328ee4c2c1fd4418a5 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Thu, 13 Nov 2025 14:08:10 -0800 Subject: [PATCH 3/5] make utility function --- .../analytics/monitorsAnalyticsEvents.tsx | 24 ++----- .../common/getDetectorAnalyticsPayload.ts | 65 +++++++++++++++++++ .../hooks/useCreateDetectorFormSubmit.tsx | 11 +--- .../hooks/useEditDetectorFormSubmit.tsx | 11 +--- 4 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts diff --git a/static/app/utils/analytics/monitorsAnalyticsEvents.tsx b/static/app/utils/analytics/monitorsAnalyticsEvents.tsx index 33997a7fb18ef4..f2eace60b10d70 100644 --- a/static/app/utils/analytics/monitorsAnalyticsEvents.tsx +++ b/static/app/utils/analytics/monitorsAnalyticsEvents.tsx @@ -1,26 +1,14 @@ +import type {getDetectorAnalyticsPayload} from 'sentry/views/detectors/components/forms/common/getDetectorAnalyticsPayload'; + +type DetectorAnalyticsEventPayload = ReturnType; + export type MonitorsEventParameters = { 'landing_page.platform_guide.viewed': { guide: string; platform: string; }; - 'monitor.created': { - detector_type: - | 'error' - | 'metric_issue' - | 'uptime_domain_failure' - | 'monitor_check_in_failure'; - aggregate?: string; - dataset?: string; - }; - 'monitor.updated': { - detector_type: - | 'error' - | 'metric_issue' - | 'uptime_domain_failure' - | 'monitor_check_in_failure'; - aggregate?: string; - dataset?: string; - }; + 'monitor.created': DetectorAnalyticsEventPayload; + 'monitor.updated': DetectorAnalyticsEventPayload; }; type MonitorsAnalyticsKey = keyof MonitorsEventParameters; diff --git a/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts b/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts new file mode 100644 index 00000000000000..4e10d70cdc4f95 --- /dev/null +++ b/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts @@ -0,0 +1,65 @@ +import type { + CronDetector, + Detector, + MetricDetector, + SnubaQuery, + UptimeDetector, +} from 'sentry/types/workflowEngine/detectors'; +import type {MonitorConfig} from 'sentry/views/insights/crons/types'; + +type MetricDetectorAnalytics = { + detector_type: MetricDetector['type']; + aggregate?: SnubaQuery['aggregate']; + dataset?: SnubaQuery['dataset']; +}; + +type UptimeDetectorAnalytics = { + detector_type: UptimeDetector['type']; + environment: UptimeDetector['config']['environment']; + mode: UptimeDetector['config']['mode']; +}; + +type MonitorDetectorAnalytics = { + detector_type: CronDetector['type']; + schedule_type: MonitorConfig['schedule_type'] | undefined; +}; + +type ErrorDetectorAnalytics = { + detector_type: Extract; +}; + +export type DetectorAnalyticsPayload = + | MetricDetectorAnalytics + | UptimeDetectorAnalytics + | MonitorDetectorAnalytics + | ErrorDetectorAnalytics; + +export function getDetectorAnalyticsPayload( + detector: Detector +): DetectorAnalyticsPayload { + switch (detector.type) { + case 'metric_issue': { + const snubaQuery = detector.dataSources[0]?.queryObj?.snubaQuery; + return { + detector_type: detector.type, + aggregate: snubaQuery?.aggregate, + dataset: snubaQuery?.dataset, + }; + } + case 'uptime_domain_failure': + return { + detector_type: detector.type, + environment: detector.config.environment, + mode: detector.config.mode, + }; + case 'monitor_check_in_failure': { + const monitorConfig = detector.dataSources[0]?.queryObj?.config; + return { + detector_type: detector.type, + schedule_type: monitorConfig?.schedule_type, + }; + } + default: + return {detector_type: detector.type}; + } +} diff --git a/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx b/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx index 0dc8ca7025d686..b335ca7459937a 100644 --- a/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx +++ b/static/app/views/detectors/hooks/useCreateDetectorFormSubmit.tsx @@ -6,11 +6,11 @@ import {t} from 'sentry/locale'; import type { BaseDetectorUpdatePayload, Detector, - SnubaQuery, } from 'sentry/types/workflowEngine/detectors'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; +import {getDetectorAnalyticsPayload} from 'sentry/views/detectors/components/forms/common/getDetectorAnalyticsPayload'; import {useCreateDetector} from 'sentry/views/detectors/hooks'; import {makeMonitorDetailsPathname} from 'sentry/views/detectors/pathnames'; @@ -46,16 +46,9 @@ export function useCreateDetectorFormSubmit< const payload = formDataToEndpointPayload(data as TFormData); const resultDetector = await createDetector(payload); - const snubaQuery: SnubaQuery | undefined = - resultDetector.type === 'metric_issue' - ? resultDetector.dataSources[0]?.queryObj?.snubaQuery - : undefined; - trackAnalytics('monitor.created', { organization, - detector_type: resultDetector.type, - dataset: snubaQuery?.dataset, - aggregate: snubaQuery?.aggregate, + ...getDetectorAnalyticsPayload(resultDetector), }); addSuccessMessage(t('Monitor created successfully')); diff --git a/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx b/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx index 3b2acf92a42d19..c91cd4a54d5839 100644 --- a/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx +++ b/static/app/views/detectors/hooks/useEditDetectorFormSubmit.tsx @@ -6,11 +6,11 @@ import {t} from 'sentry/locale'; import type { BaseDetectorUpdatePayload, Detector, - SnubaQuery, } from 'sentry/types/workflowEngine/detectors'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; +import {getDetectorAnalyticsPayload} from 'sentry/views/detectors/components/forms/common/getDetectorAnalyticsPayload'; import {useUpdateDetector} from 'sentry/views/detectors/hooks'; import {makeMonitorDetailsPathname} from 'sentry/views/detectors/pathnames'; @@ -54,16 +54,9 @@ export function useEditDetectorFormSubmit< const resultDetector = await updateDetector(updatedData); - const snubaQuery: SnubaQuery | undefined = - resultDetector.type === 'metric_issue' - ? resultDetector.dataSources[0]?.queryObj?.snubaQuery - : undefined; - trackAnalytics('monitor.updated', { organization, - detector_type: resultDetector.type, - dataset: snubaQuery?.dataset, - aggregate: snubaQuery?.aggregate, + ...getDetectorAnalyticsPayload(resultDetector), }); addSuccessMessage(t('Monitor updated successfully')); From 889a70b48423485123a222f3295164cf22a6a0ad Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Thu, 13 Nov 2025 14:29:58 -0800 Subject: [PATCH 4/5] friendlier names --- .../forms/common/getDetectorAnalyticsPayload.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts b/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts index 4e10d70cdc4f95..568f5bb1c8b245 100644 --- a/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts +++ b/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts @@ -15,13 +15,12 @@ type MetricDetectorAnalytics = { type UptimeDetectorAnalytics = { detector_type: UptimeDetector['type']; - environment: UptimeDetector['config']['environment']; - mode: UptimeDetector['config']['mode']; + uptime_mode: UptimeDetector['config']['mode']; }; type MonitorDetectorAnalytics = { + cron_schedule_type: MonitorConfig['schedule_type'] | undefined; detector_type: CronDetector['type']; - schedule_type: MonitorConfig['schedule_type'] | undefined; }; type ErrorDetectorAnalytics = { @@ -49,14 +48,13 @@ export function getDetectorAnalyticsPayload( case 'uptime_domain_failure': return { detector_type: detector.type, - environment: detector.config.environment, - mode: detector.config.mode, + uptime_mode: detector.config.mode, }; case 'monitor_check_in_failure': { const monitorConfig = detector.dataSources[0]?.queryObj?.config; return { + cron_schedule_type: monitorConfig?.schedule_type, detector_type: detector.type, - schedule_type: monitorConfig?.schedule_type, }; } default: From 564f332d89a6c8a0fc559b2f4968d3bda2a54e8a Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Fri, 14 Nov 2025 12:45:33 -0800 Subject: [PATCH 5/5] remove export --- .../components/forms/common/getDetectorAnalyticsPayload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts b/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts index 568f5bb1c8b245..36feb6b4ac48ee 100644 --- a/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts +++ b/static/app/views/detectors/components/forms/common/getDetectorAnalyticsPayload.ts @@ -27,7 +27,7 @@ type ErrorDetectorAnalytics = { detector_type: Extract; }; -export type DetectorAnalyticsPayload = +type DetectorAnalyticsPayload = | MetricDetectorAnalytics | UptimeDetectorAnalytics | MonitorDetectorAnalytics