From 4d3fc0cdfb864d41040c31400784abf88b2b2c7b Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 25 Nov 2025 09:43:29 +0100 Subject: [PATCH 1/3] Add analytics for primary and secondary release selection --- .../utils/analytics/insightAnalyticEvents.tsx | 10 +++ .../components/releaseSelector.spec.tsx | 66 +++++++++++++++++++ .../common/components/releaseSelector.tsx | 18 +++++ .../appStarts/views/screenSummaryPage.tsx | 2 +- .../screenload/views/screenLoadSpansPage.tsx | 7 +- .../screens/views/screensLandingPage.tsx | 2 +- .../mobile/ui/views/screenSummaryPage.tsx | 2 +- 7 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 static/app/views/insights/common/components/releaseSelector.spec.tsx diff --git a/static/app/utils/analytics/insightAnalyticEvents.tsx b/static/app/utils/analytics/insightAnalyticEvents.tsx index 1ee2374829f1dc..63d7e23f25fd9a 100644 --- a/static/app/utils/analytics/insightAnalyticEvents.tsx +++ b/static/app/utils/analytics/insightAnalyticEvents.tsx @@ -54,6 +54,14 @@ export type InsightEventParameters = { }; 'insights.open_in_explore': {referrer: string}; 'insights.page_loads.overview': {domain: DomainView | undefined; platforms: string[]}; + 'insights.release.select_primary_release': { + moduleName: ModuleName; + release: string; + }; + 'insights.release.select_secondary_release': { + moduleName: ModuleName; + release: string; + }; 'insights.session_health_tour.dismissed': Record; }; @@ -110,4 +118,6 @@ export const insightEventMap: Record = { 'insights.eap.toggle': 'Insights: EAP Toggle', 'insights.open_in_explore': 'Insights: Open in Explore', 'insights.create_alert': 'Insights: Create Alert', + 'insights.release.select_primary_release': 'Insights: Select Primary Release', + 'insights.release.select_secondary_release': 'Insights: Select Secondary Release', }; diff --git a/static/app/views/insights/common/components/releaseSelector.spec.tsx b/static/app/views/insights/common/components/releaseSelector.spec.tsx new file mode 100644 index 00000000000000..b63530f8c5cfa0 --- /dev/null +++ b/static/app/views/insights/common/components/releaseSelector.spec.tsx @@ -0,0 +1,66 @@ +import {OrganizationFixture} from 'sentry-fixture/organization'; + +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; + +import {trackAnalytics} from 'sentry/utils/analytics'; +import {ReleaseComparisonSelector} from 'sentry/views/insights/common/components/releaseSelector'; +import {ModuleName} from 'sentry/views/insights/types'; + +jest.mock('sentry/utils/analytics', () => ({ + trackAnalytics: jest.fn(), +})); + +jest.mock('sentry/views/insights/common/queries/useReleases', () => ({ + useReleases: () => ({ + data: [ + {dateCreated: '2024-01-01T00:00:00Z', version: 'v1.0.0', count: 10}, + {dateCreated: '2024-01-02T00:00:00Z', version: 'v2.0.0', count: 20}, + ], + isLoading: false, + }), + useReleaseSelection: () => ({ + primaryRelease: 'v1.0.0', + secondaryRelease: undefined, + isLoading: false, + }), +})); + +describe('ReleaseComparisonSelector analytics', () => { + it('tracks primary release selection with module name and release', async () => { + const organization = OrganizationFixture(); + + render(, { + organization, + }); + + const triggers = screen.getAllByRole('button'); + expect(triggers.length).toBeGreaterThan(0); + const primaryTrigger = triggers[0]!; + await userEvent.click(primaryTrigger); + + const option = await screen.findByRole('option', {name: 'v2.0.0'}); + await userEvent.click(option); + + expect(trackAnalytics).toHaveBeenCalledWith( + 'insights.release.select_primary_release', + expect.objectContaining({ + organization, + moduleName: ModuleName.MOBILE_VITALS, + release: 'v2.0.0', + }) + ); + + await userEvent.click(primaryTrigger); + const allOption = await screen.findByRole('option', {name: 'All'}); + await userEvent.click(allOption); + + expect(trackAnalytics).toHaveBeenCalledWith( + 'insights.release.select_primary_release', + expect.objectContaining({ + organization, + moduleName: ModuleName.MOBILE_VITALS, + release: '', + }) + ); + }); +}); diff --git a/static/app/views/insights/common/components/releaseSelector.tsx b/static/app/views/insights/common/components/releaseSelector.tsx index b43dd332a76590..7567e19a84c524 100644 --- a/static/app/views/insights/common/components/releaseSelector.tsx +++ b/static/app/views/insights/common/components/releaseSelector.tsx @@ -12,10 +12,12 @@ import {IconReleases} from 'sentry/icons/iconReleases'; import {t, tct, tn} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {defined} from 'sentry/utils'; +import {trackAnalytics} from 'sentry/utils/analytics'; import {decodeScalar} from 'sentry/utils/queryString'; import {useLocalStorageState} from 'sentry/utils/useLocalStorageState'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; +import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import { ReleasesSort, @@ -27,6 +29,7 @@ import { useReleaseSelection, } from 'sentry/views/insights/common/queries/useReleases'; import {formatVersionAndCenterTruncate} from 'sentry/views/insights/common/utils/formatVersionAndCenterTruncate'; +import type {ModuleName} from 'sentry/views/insights/types'; export const PRIMARY_RELEASE_ALIAS = 'R1'; export const SECONDARY_RELEASE_ALIAS = 'R2'; @@ -187,16 +190,19 @@ function getReleasesSortBy( } type ReleaseComparisonSelectorProps = { + moduleName: ModuleName; primaryOnly?: boolean; }; export function ReleaseComparisonSelector({ primaryOnly = false, + moduleName, }: ReleaseComparisonSelectorProps) { const {primaryRelease, secondaryRelease} = useReleaseSelection(); const location = useLocation(); const navigate = useNavigate(); const {selection} = usePageFilters(); + const organization = useOrganization(); const [localStoragedReleaseBy, setLocalStoragedReleaseBy] = useLocalStorageState( @@ -257,6 +263,12 @@ export function ReleaseComparisonSelector({ allOptionDescription={t('Show data from all releases.')} allOptionTitle={t('All')} onChange={newValue => { + trackAnalytics('insights.release.select_primary_release', { + organization, + release: (newValue.value as string) ?? '', + moduleName, + }); + const updatedQuery: Record = { ...location.query, primaryRelease: newValue.value as string, @@ -288,6 +300,12 @@ export function ReleaseComparisonSelector({ allOptionDescription={t('No comparison.')} allOptionTitle={t('None')} onChange={newValue => { + trackAnalytics('insights.release.select_secondary_release', { + organization, + release: (newValue.value as string) ?? '', + moduleName, + }); + const updatedQuery: Record = { ...location.query, secondaryRelease: newValue.value as string, diff --git a/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx b/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx index 54697f50c15d10..f3374c8ad7c56d 100644 --- a/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx +++ b/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx @@ -115,7 +115,7 @@ export function ScreenSummaryContentPage() { - + - - + + diff --git a/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx b/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx index ae6d52c19ce919..6ad66c1af1df4b 100644 --- a/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx +++ b/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx @@ -265,7 +265,7 @@ function ScreensLandingPage() { - + diff --git a/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx b/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx index 4b597cd17b9aa2..80941d17fee04a 100644 --- a/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx +++ b/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx @@ -58,7 +58,7 @@ export function ScreenSummaryContent() { moduleName={ModuleName.SCREEN_RENDERING} disableProjectFilter /> - + From 5cde8a348a015999ccd8d4984e3eff5b11ff4b6a Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 25 Nov 2025 10:39:17 +0100 Subject: [PATCH 2/3] Address PR feedback --- .../insights/common/components/releaseSelector.spec.tsx | 6 ++---- .../views/insights/common/components/releaseSelector.tsx | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/static/app/views/insights/common/components/releaseSelector.spec.tsx b/static/app/views/insights/common/components/releaseSelector.spec.tsx index b63530f8c5cfa0..b07eba596903a0 100644 --- a/static/app/views/insights/common/components/releaseSelector.spec.tsx +++ b/static/app/views/insights/common/components/releaseSelector.spec.tsx @@ -19,7 +19,7 @@ jest.mock('sentry/views/insights/common/queries/useReleases', () => ({ isLoading: false, }), useReleaseSelection: () => ({ - primaryRelease: 'v1.0.0', + primaryRelease: undefined, secondaryRelease: undefined, isLoading: false, }), @@ -33,9 +33,7 @@ describe('ReleaseComparisonSelector analytics', () => { organization, }); - const triggers = screen.getAllByRole('button'); - expect(triggers.length).toBeGreaterThan(0); - const primaryTrigger = triggers[0]!; + const primaryTrigger = screen.getByRole('button', {name: 'Filter Release'}); await userEvent.click(primaryTrigger); const option = await screen.findByRole('option', {name: 'v2.0.0'}); diff --git a/static/app/views/insights/common/components/releaseSelector.tsx b/static/app/views/insights/common/components/releaseSelector.tsx index 7567e19a84c524..e56a999e42cc23 100644 --- a/static/app/views/insights/common/components/releaseSelector.tsx +++ b/static/app/views/insights/common/components/releaseSelector.tsx @@ -111,6 +111,7 @@ function ReleaseSelector({ title: selectorValue, prefix: triggerLabelPrefix, children: triggerLabel, + 'aria-label': t('Filter Release'), }} menuTitle={t('Filter Release')} loading={isLoading} From 949443f72ca56350b08931d0580f66e3365cc1cf Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Wed, 26 Nov 2025 20:33:23 +0100 Subject: [PATCH 3/3] Address PR feedback --- static/app/utils/analytics/insightAnalyticEvents.tsx | 12 ++++-------- .../common/components/releaseSelector.spec.tsx | 10 ++++++---- .../insights/common/components/releaseSelector.tsx | 10 ++++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/static/app/utils/analytics/insightAnalyticEvents.tsx b/static/app/utils/analytics/insightAnalyticEvents.tsx index 63d7e23f25fd9a..fe6278b12794cb 100644 --- a/static/app/utils/analytics/insightAnalyticEvents.tsx +++ b/static/app/utils/analytics/insightAnalyticEvents.tsx @@ -54,13 +54,10 @@ export type InsightEventParameters = { }; 'insights.open_in_explore': {referrer: string}; 'insights.page_loads.overview': {domain: DomainView | undefined; platforms: string[]}; - 'insights.release.select_primary_release': { + 'insights.release.select_release': { + filtered: boolean; moduleName: ModuleName; - release: string; - }; - 'insights.release.select_secondary_release': { - moduleName: ModuleName; - release: string; + type: 'primary' | 'secondary'; }; 'insights.session_health_tour.dismissed': Record; }; @@ -118,6 +115,5 @@ export const insightEventMap: Record = { 'insights.eap.toggle': 'Insights: EAP Toggle', 'insights.open_in_explore': 'Insights: Open in Explore', 'insights.create_alert': 'Insights: Create Alert', - 'insights.release.select_primary_release': 'Insights: Select Primary Release', - 'insights.release.select_secondary_release': 'Insights: Select Secondary Release', + 'insights.release.select_release': 'Insights: Select Release', }; diff --git a/static/app/views/insights/common/components/releaseSelector.spec.tsx b/static/app/views/insights/common/components/releaseSelector.spec.tsx index b07eba596903a0..dac18edc22f2c0 100644 --- a/static/app/views/insights/common/components/releaseSelector.spec.tsx +++ b/static/app/views/insights/common/components/releaseSelector.spec.tsx @@ -40,11 +40,12 @@ describe('ReleaseComparisonSelector analytics', () => { await userEvent.click(option); expect(trackAnalytics).toHaveBeenCalledWith( - 'insights.release.select_primary_release', + 'insights.release.select_release', expect.objectContaining({ organization, moduleName: ModuleName.MOBILE_VITALS, - release: 'v2.0.0', + filtered: true, + type: 'primary', }) ); @@ -53,11 +54,12 @@ describe('ReleaseComparisonSelector analytics', () => { await userEvent.click(allOption); expect(trackAnalytics).toHaveBeenCalledWith( - 'insights.release.select_primary_release', + 'insights.release.select_release', expect.objectContaining({ organization, moduleName: ModuleName.MOBILE_VITALS, - release: '', + filtered: false, + type: 'primary', }) ); }); diff --git a/static/app/views/insights/common/components/releaseSelector.tsx b/static/app/views/insights/common/components/releaseSelector.tsx index e56a999e42cc23..b9726b74cc7c4a 100644 --- a/static/app/views/insights/common/components/releaseSelector.tsx +++ b/static/app/views/insights/common/components/releaseSelector.tsx @@ -264,9 +264,10 @@ export function ReleaseComparisonSelector({ allOptionDescription={t('Show data from all releases.')} allOptionTitle={t('All')} onChange={newValue => { - trackAnalytics('insights.release.select_primary_release', { + trackAnalytics('insights.release.select_release', { organization, - release: (newValue.value as string) ?? '', + filtered: defined(newValue.value) && newValue.value !== '', + type: 'primary', moduleName, }); @@ -301,9 +302,10 @@ export function ReleaseComparisonSelector({ allOptionDescription={t('No comparison.')} allOptionTitle={t('None')} onChange={newValue => { - trackAnalytics('insights.release.select_secondary_release', { + trackAnalytics('insights.release.select_release', { organization, - release: (newValue.value as string) ?? '', + filtered: defined(newValue.value) && newValue.value !== '', + type: 'secondary', moduleName, });