diff --git a/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx b/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx index 90d7e4be3cc0b1..c3ec8df556993a 100644 --- a/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx +++ b/static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx @@ -1,3 +1,4 @@ +import type {PreprodBuildsDisplay} from 'sentry/components/preprod/preprodBuildsDisplay'; import type {Organization} from 'sentry/types/organization'; type BasePreprodBuildEvent = { @@ -8,6 +9,11 @@ type BasePreprodBuildEvent = { project_type?: string | null; }; +export type BuildListPageSource = + | 'preprod_builds_list' + | 'releases_mobile_builds_tab' + | 'releases_details_preprod_builds'; + export type PreprodBuildEventParameters = { 'preprod.builds.compare.go_to_build_details': BasePreprodBuildEvent & { slot?: 'head' | 'base'; @@ -28,6 +34,18 @@ export type PreprodBuildEventParameters = { 'preprod.builds.install_modal.opened': BasePreprodBuildEvent & { source: 'build_details_sidebar' | 'builds_table'; }; + 'preprod.builds.list.metadata': { + build_count_on_page: number; + datetime_selection: string; + display: PreprodBuildsDisplay; + has_search_query: boolean; + is_empty: boolean; + organization: Organization; + page_source: BuildListPageSource; + project_count: number; + query_status: 'success' | 'error'; + cursor?: string | null; + }; 'preprod.builds.release.build_row_clicked': BasePreprodBuildEvent; 'preprod.releases.mobile-builds.tab-clicked': { organization: Organization; @@ -37,6 +55,7 @@ export type PreprodBuildEventParameters = { type PreprodBuildAnalyticsKey = keyof PreprodBuildEventParameters; export const preprodBuildEventMap: Record = { + 'preprod.builds.list.metadata': 'Preprod Builds: List Metadata', 'preprod.builds.release.build_row_clicked': 'Preprod Builds: Release Build Row Clicked', 'preprod.builds.details.open_insights_sidebar': 'Preprod Build Details: Insights Sidebar Opened', diff --git a/static/app/views/preprod/buildList/buildList.tsx b/static/app/views/preprod/buildList/buildList.tsx index e42fdb730dddb1..198714b04f64a9 100644 --- a/static/app/views/preprod/buildList/buildList.tsx +++ b/static/app/views/preprod/buildList/buildList.tsx @@ -3,6 +3,7 @@ import {Flex} from '@sentry/scraps/layout'; import Feature from 'sentry/components/acl/feature'; import {LinkButton} from 'sentry/components/core/button/linkButton'; import * as Layout from 'sentry/components/layouts/thirds'; +import {PreprodBuildsDisplay} from 'sentry/components/preprod/preprodBuildsDisplay'; import {PreprodBuildsTable} from 'sentry/components/preprod/preprodBuildsTable'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {IconSettings} from 'sentry/icons'; @@ -13,6 +14,7 @@ import type RequestError from 'sentry/utils/requestError/requestError'; import useLocationQuery from 'sentry/utils/url/useLocationQuery'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; +import {usePreprodBuildsAnalytics} from 'sentry/views/preprod/hooks/usePreprodBuildsAnalytics'; import type {ListBuildsApiResponse} from 'sentry/views/preprod/types/listBuildsTypes'; export default function BuildList() { @@ -55,6 +57,17 @@ export default function BuildList() { const builds = buildsData?.builds || []; const pageLinks = getResponseHeader?.('Link') || null; + usePreprodBuildsAnalytics({ + builds, + cursor, + display: PreprodBuildsDisplay.SIZE, + enabled: !!projectId, + error: !!error, + isLoading, + pageSource: 'preprod_builds_list', + projectCount: 1, + }); + return ( diff --git a/static/app/views/preprod/hooks/usePreprodBuildsAnalytics.tsx b/static/app/views/preprod/hooks/usePreprodBuildsAnalytics.tsx new file mode 100644 index 00000000000000..6e833faea29126 --- /dev/null +++ b/static/app/views/preprod/hooks/usePreprodBuildsAnalytics.tsx @@ -0,0 +1,70 @@ +import {useEffect} from 'react'; + +import type {PreprodBuildsDisplay} from 'sentry/components/preprod/preprodBuildsDisplay'; +import {trackAnalytics} from 'sentry/utils/analytics'; +import type {BuildListPageSource} from 'sentry/utils/analytics/preprodBuildAnalyticsEvents'; +import useOrganization from 'sentry/utils/useOrganization'; +import usePageFilters from 'sentry/utils/usePageFilters'; +import type {BuildDetailsApiResponse} from 'sentry/views/preprod/types/buildDetailsTypes'; + +interface UsePreprodBuildsAnalyticsProps { + builds: BuildDetailsApiResponse[]; + display: PreprodBuildsDisplay; + isLoading: boolean; + pageSource: BuildListPageSource; + cursor?: string | null; + enabled?: boolean; + error?: boolean; + projectCount?: number; + searchQuery?: string | null; +} + +export function usePreprodBuildsAnalytics({ + builds, + cursor, + display, + enabled = true, + error, + isLoading, + pageSource, + projectCount, + searchQuery, +}: UsePreprodBuildsAnalyticsProps) { + const organization = useOrganization(); + const {selection} = usePageFilters(); + + const datetimeSelection = `${selection.datetime.start || ''}-${selection.datetime.end || ''}-${selection.datetime.period || ''}`; + + useEffect(() => { + if (!enabled || isLoading) { + return; + } + const buildCountOnPage = builds.length; + + trackAnalytics('preprod.builds.list.metadata', { + organization, + build_count_on_page: buildCountOnPage, + query_status: error ? 'error' : 'success', + is_empty: !error && buildCountOnPage === 0, + has_search_query: (searchQuery?.trim() ?? '').length > 0, + page_source: pageSource, + display, + cursor: cursor ?? null, + project_count: projectCount ?? selection.projects.length, + datetime_selection: datetimeSelection, + }); + }, [ + builds.length, + cursor, + datetimeSelection, + display, + enabled, + error, + isLoading, + organization, + pageSource, + projectCount, + searchQuery, + selection.projects.length, + ]); +} diff --git a/static/app/views/releases/detail/commitsAndFiles/preprodBuilds.tsx b/static/app/views/releases/detail/commitsAndFiles/preprodBuilds.tsx index 3abd05e4703afe..3e61498dd4235f 100644 --- a/static/app/views/releases/detail/commitsAndFiles/preprodBuilds.tsx +++ b/static/app/views/releases/detail/commitsAndFiles/preprodBuilds.tsx @@ -22,6 +22,7 @@ import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; import {formatVersion} from 'sentry/utils/versions/formatVersion'; import PreprodBuildsSearchBar from 'sentry/views/preprod/components/preprodBuildsSearchBar'; +import {usePreprodBuildsAnalytics} from 'sentry/views/preprod/hooks/usePreprodBuildsAnalytics'; import type {BuildDetailsApiResponse} from 'sentry/views/preprod/types/buildDetailsTypes'; import type {ListBuildsApiResponse} from 'sentry/views/preprod/types/listBuildsTypes'; import {ReleaseContext} from 'sentry/views/releases/detail'; @@ -132,6 +133,18 @@ export default function PreprodBuilds() { const hasSearchQuery = !!urlSearchQuery?.trim(); const showOnboarding = builds.length === 0 && !hasSearchQuery && !isLoadingBuilds; + usePreprodBuildsAnalytics({ + builds, + cursor, + display: activeDisplay, + enabled: !!projectSlug && !!params.release, + error: !!buildsError, + isLoading: isLoadingBuilds, + pageSource: 'releases_details_preprod_builds', + projectCount: 1, + searchQuery: urlSearchQuery, + }); + const handleBuildRowClick = useCallback( (build: BuildDetailsApiResponse) => { trackAnalytics('preprod.builds.release.build_row_clicked', { diff --git a/static/app/views/releases/list/mobileBuilds.tsx b/static/app/views/releases/list/mobileBuilds.tsx index 58c13241b49242..161d1aaf5aa42e 100644 --- a/static/app/views/releases/list/mobileBuilds.tsx +++ b/static/app/views/releases/list/mobileBuilds.tsx @@ -17,6 +17,7 @@ import type RequestError from 'sentry/utils/requestError/requestError'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import PreprodBuildsSearchBar from 'sentry/views/preprod/components/preprodBuildsSearchBar'; +import {usePreprodBuildsAnalytics} from 'sentry/views/preprod/hooks/usePreprodBuildsAnalytics'; import type {ListBuildsApiResponse} from 'sentry/views/preprod/types/listBuildsTypes'; type Props = { @@ -100,14 +101,26 @@ export default function MobileBuilds({organization, selectedProjectIds}: Props) [location, navigate] ); - if (selectedProjectIds.length === 0) { - return ; - } - const builds = buildsData?.builds ?? []; const pageLinks = getResponseHeader?.('Link') ?? undefined; const hasSearchQuery = !!searchQuery?.trim(); const showProjectColumn = selectedProjectIds.length > 1; + usePreprodBuildsAnalytics({ + builds, + cursor, + display: activeDisplay, + enabled: selectedProjectIds.length > 0, + error: !!buildsError, + isLoading: isLoadingBuilds, + pageSource: 'releases_mobile_builds_tab', + projectCount: selectedProjectIds.length, + searchQuery, + }); + + if (selectedProjectIds.length === 0) { + return ; + } + return (