From 1dd9a40a36ab9c1f1de062ec01fefda79ce67bc2 Mon Sep 17 00:00:00 2001 From: Max Topolsky Date: Thu, 20 Nov 2025 23:21:02 -0500 Subject: [PATCH 01/12] amplitude events for emerge pages --- .../components/preprod/preprodBuildsTable.tsx | 10 ++- static/app/utils/analytics.tsx | 4 ++ .../analytics/preprodBuildAnalyticsEvents.tsx | 63 +++++++++++++++++++ .../main/sizeCompareSelectedBuilds.tsx | 36 ++++++++++- .../main/sizeCompareSelectionContent.tsx | 35 ++++++++++- .../header/buildDetailsHeaderContent.tsx | 24 ++++++- .../main/buildDetailsMetricCards.tsx | 17 ++++- .../main/insights/appSizeInsights.tsx | 11 +++- .../insights/appSizeInsightsSidebarRow.tsx | 22 ++++++- .../detail/commitsAndFiles/preprodBuilds.tsx | 23 ++++++- 10 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx diff --git a/static/app/components/preprod/preprodBuildsTable.tsx b/static/app/components/preprod/preprodBuildsTable.tsx index c7609d6e2b8e82..99da2694b6b0c7 100644 --- a/static/app/components/preprod/preprodBuildsTable.tsx +++ b/static/app/components/preprod/preprodBuildsTable.tsx @@ -28,6 +28,7 @@ interface PreprodBuildsTableProps { projectSlug: string; error?: boolean; hasSearchQuery?: boolean; + onRowClick?: (build: BuildDetailsApiResponse, rowIndex: number) => void; pageLinks?: string | null; } @@ -36,6 +37,7 @@ export function PreprodBuildsTable({ isLoading, error, pageLinks, + onRowClick, organizationSlug, projectSlug, hasSearchQuery, @@ -54,12 +56,12 @@ export function PreprodBuildsTable({ ); - const renderBuildRow = (build: BuildDetailsApiResponse) => { + const renderBuildRow = (build: BuildDetailsApiResponse, rowIndex: number) => { const linkUrl = `/organizations/${organizationSlug}/preprod/${projectSlug}/${build.id}`; return ( - + onRowClick?.(build, rowIndex)}> {build.app_info?.name || build.app_info?.app_id ? ( @@ -176,7 +178,9 @@ export function PreprodBuildsTable({ ); } else { - tableContent = {builds.map(renderBuildRow)}; + tableContent = ( + {builds.map((build, index) => renderBuildRow(build, index))} + ); } return ( diff --git a/static/app/utils/analytics.tsx b/static/app/utils/analytics.tsx index 9cf3b07d6c5fa6..d08930298594c0 100644 --- a/static/app/utils/analytics.tsx +++ b/static/app/utils/analytics.tsx @@ -73,6 +73,8 @@ import type {OnboardingEventParameters} from './analytics/onboardingAnalyticsEve import {onboardingEventMap} from './analytics/onboardingAnalyticsEvents'; import type {PerformanceEventParameters} from './analytics/performanceAnalyticsEvents'; import {performanceEventMap} from './analytics/performanceAnalyticsEvents'; +import type {PreprodBuildEventParameters} from './analytics/preprodBuildAnalyticsEvents'; +import {preprodBuildEventMap} from './analytics/preprodBuildAnalyticsEvents'; import type {ProfilingEventParameters} from './analytics/profilingAnalyticsEvents'; import {profilingEventMap} from './analytics/profilingAnalyticsEvents'; import type {ProjectCreationEventParameters} from './analytics/projectCreationAnalyticsEvents'; @@ -113,6 +115,7 @@ interface EventParameters MonitorsEventParameters, PerformanceEventParameters, ProfilingEventParameters, + PreprodBuildEventParameters, ReleasesEventParameters, ReplayEventParameters, SearchEventParameters, @@ -151,6 +154,7 @@ const allEventMap: Record = { ...monitorsEventMap, ...nextJsInsightsEventMap, ...performanceEventMap, + ...preprodBuildEventMap, ...tracingEventMap, ...profilingEventMap, ...exploreAnalyticsEventMap, diff --git a/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx b/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx new file mode 100644 index 00000000000000..53b4dc7eb1578d --- /dev/null +++ b/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx @@ -0,0 +1,63 @@ +import type {Organization} from 'sentry/types/organization'; + +import makeAnalyticsFunction from './makeAnalyticsFunction'; + +type BasePreprodBuildEvent = { + organization: Organization; + base_id?: string; + build_id?: string; + head_id?: string; + platform?: string | null; + project_slug?: string; + project_type?: string | null; +}; + +export type PreprodBuildEventParameters = { + 'preprod.builds.compare_build_clicked': BasePreprodBuildEvent & { + slot?: 'head' | 'base'; + }; + 'preprod.builds.compare_build_selected': BasePreprodBuildEvent; + 'preprod.builds.compare_selection_viewed': BasePreprodBuildEvent; + 'preprod.builds.compare_state_viewed': BasePreprodBuildEvent; + 'preprod.builds.compare_trigger_clicked': BasePreprodBuildEvent; + 'preprod.builds.details_compare_clicked': BasePreprodBuildEvent; + 'preprod.builds.details_delete_clicked': BasePreprodBuildEvent; + 'preprod.builds.details_insight_action_clicked': BasePreprodBuildEvent & { + insight_key: string; + }; + 'preprod.builds.details_insight_expanded': BasePreprodBuildEvent & { + insight_key: string; + insight_name?: string; + }; + 'preprod.builds.details_insights_opened': BasePreprodBuildEvent & { + insight_count?: number; + source?: string; + }; + 'preprod.builds.details_viewed': BasePreprodBuildEvent; + 'preprod.builds.release_row_clicked': BasePreprodBuildEvent; + 'preprod.builds.release_tab_viewed': BasePreprodBuildEvent & { + has_results?: boolean; + }; +}; + +type PreprodBuildAnalyticsKey = keyof PreprodBuildEventParameters; + +export const preprodBuildEventMap: Record = { + 'preprod.builds.release_tab_viewed': 'Preprod Builds: Release Tab Viewed', + 'preprod.builds.release_row_clicked': 'Preprod Builds: Release Row Clicked', + 'preprod.builds.details_viewed': 'Preprod Build Details: Viewed', + 'preprod.builds.details_insights_opened': 'Preprod Build Details: Insights Opened', + 'preprod.builds.details_insight_expanded': 'Preprod Build Details: Insight Expanded', + 'preprod.builds.details_insight_action_clicked': + 'Preprod Build Details: Insight Action Clicked', + 'preprod.builds.details_compare_clicked': 'Preprod Build Details: Compare Clicked', + 'preprod.builds.details_delete_clicked': 'Preprod Build Details: Delete Clicked', + 'preprod.builds.compare_selection_viewed': 'Preprod Build Comparison: Selection Viewed', + 'preprod.builds.compare_state_viewed': 'Preprod Build Comparison: Compare View Viewed', + 'preprod.builds.compare_build_clicked': 'Preprod Build Comparison: Build Clicked', + 'preprod.builds.compare_build_selected': 'Preprod Build Comparison: Base Selected', + 'preprod.builds.compare_trigger_clicked': 'Preprod Build Comparison: Compare Triggered', +}; + +export const trackPreprodBuildAnalytics = + makeAnalyticsFunction(preprodBuildEventMap); diff --git a/static/app/views/preprod/buildComparison/main/sizeCompareSelectedBuilds.tsx b/static/app/views/preprod/buildComparison/main/sizeCompareSelectedBuilds.tsx index 81f0c4a382469c..7f993965662530 100644 --- a/static/app/views/preprod/buildComparison/main/sizeCompareSelectedBuilds.tsx +++ b/static/app/views/preprod/buildComparison/main/sizeCompareSelectedBuilds.tsx @@ -7,6 +7,7 @@ import {Text} from '@sentry/scraps/text'; import {IconClose, IconCommit, IconFocus, IconLock, IconTelescope} from 'sentry/icons'; import {t} from 'sentry/locale'; +import {trackPreprodBuildAnalytics} from 'sentry/utils/analytics/preprodBuildAnalyticsEvents'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; import type {BuildDetailsApiResponse} from 'sentry/views/preprod/types/buildDetailsTypes'; @@ -15,10 +16,11 @@ interface BuildButtonProps { buildDetails: BuildDetailsApiResponse; icon: React.ReactNode; label: string; + slot: 'head' | 'base'; onRemove?: () => void; } -function BuildButton({buildDetails, icon, label, onRemove}: BuildButtonProps) { +function BuildButton({buildDetails, icon, label, onRemove, slot}: BuildButtonProps) { const organization = useOrganization(); const {projectId} = useParams<{projectId: string}>(); const sha = buildDetails.vcs_info?.head_sha?.substring(0, 7); @@ -26,9 +28,23 @@ function BuildButton({buildDetails, icon, label, onRemove}: BuildButtonProps) { const buildId = buildDetails.id; const buildUrl = `/organizations/${organization.slug}/preprod/${projectId}/${buildId}/`; + const platform = buildDetails.app_info?.platform ?? null; + const projectType = buildDetails.app_info?.platform ?? null; return ( - + + trackPreprodBuildAnalytics('preprod.builds.compare_build_clicked', { + organization, + build_id: buildId, + project_slug: projectId, + project_type: projectType, + platform, + slot, + }) + } + > {icon} @@ -107,12 +123,19 @@ export function SizeCompareSelectedBuilds({ onClearBaseBuild, onTriggerComparison, }: SizeCompareSelectedBuildsProps) { + const organization = useOrganization(); + const {projectId} = useParams<{projectId: string}>(); + const headId = headBuildDetails.id; + const headPlatform = headBuildDetails.app_info?.platform ?? null; + const projectType = headBuildDetails.app_info?.platform ?? null; + return ( } label={t('Head')} + slot="head" /> {t('vs')} @@ -123,6 +146,7 @@ export function SizeCompareSelectedBuilds({ icon={} label={t('Base')} onRemove={onClearBaseBuild} + slot="base" /> ) : ( @@ -134,6 +158,14 @@ export function SizeCompareSelectedBuilds({