From fc4b6b27803a1ca43a910db2b9c576df66b97336 Mon Sep 17 00:00:00 2001 From: Ankit Gade Date: Fri, 9 Aug 2024 19:57:38 +0530 Subject: [PATCH 1/9] Add tracking for ReaderRevenueManagerSetupCTABanner component. --- .../ReaderRevenueManagerSetupCTABanner.js | 42 ++++++++++++-- ...ReaderRevenueManagerSetupCTABanner.test.js | 55 +++++++++++++++++-- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.js b/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.js index bfb5c82198f..af794a73ecb 100644 --- a/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.js +++ b/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.js @@ -25,7 +25,11 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { compose } from '@wordpress/compose'; -import { createInterpolateElement, useCallback } from '@wordpress/element'; +import { + createInterpolateElement, + useCallback, + useEffect, +} from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -52,16 +56,28 @@ import SetupSVG from '../../../../../svg/graphics/reader-revenue-manager-setup.s import SetupTabletSVG from '../../../../../svg/graphics/reader-revenue-manager-setup-tablet.svg'; import SetupMobileSVG from '../../../../../svg/graphics/reader-revenue-manager-setup-mobile.svg'; import Link from '../../../../components/Link'; +import { trackEvent } from '../../../../util'; +import useViewContext from '../../../../hooks/useViewContext'; function ReaderRevenueManagerSetupCTABanner( { Widget, WidgetNull } ) { + const viewContext = useViewContext(); const breakpoint = useBreakpoint(); const isMobileBreakpoint = breakpoint === BREAKPOINT_SMALL; const isTabletBreakpoint = breakpoint === BREAKPOINT_TABLET; - const onSetupCallback = useActivateModuleCallback( + const onSetupActivate = useActivateModuleCallback( READER_REVENUE_MANAGER_MODULE_SLUG ); + const onSetupCallback = useCallback( () => { + trackEvent( + `${ viewContext }_rrm-setup-notification`, + 'confirm_notification' + ).finally( () => { + onSetupActivate(); + } ); + }, [ onSetupActivate, viewContext ] ); + const isDismissed = useSelect( ( select ) => select( CORE_USER ).isItemDismissed( READER_REVENUE_MANAGER_SETUP_BANNER_DISMISSED_KEY @@ -70,9 +86,16 @@ function ReaderRevenueManagerSetupCTABanner( { Widget, WidgetNull } ) { const { dismissItem } = useDispatch( CORE_USER ); - const onDismiss = useCallback( async () => { - await dismissItem( READER_REVENUE_MANAGER_SETUP_BANNER_DISMISSED_KEY ); - }, [ dismissItem ] ); + const onDismiss = useCallback( () => { + trackEvent( + `${ viewContext }_rrm-setup-notification`, + 'dismiss_notification' + ).finally( async () => { + await dismissItem( + READER_REVENUE_MANAGER_SETUP_BANNER_DISMISSED_KEY + ); + } ); + }, [ dismissItem, viewContext ] ); const readerRevenueManagerDocumentationURL = 'https://readerrevenue.withgoogle.com'; @@ -83,6 +106,15 @@ function ReaderRevenueManagerSetupCTABanner( { Widget, WidgetNull } ) { ) ); + useEffect( () => { + if ( isDismissed === false && canActivateRRMModule ) { + trackEvent( + `${ viewContext }_rrm-setup-notification`, + 'view_notification' + ); + } + }, [ canActivateRRMModule, isDismissed, viewContext ] ); + if ( isDismissed || isDismissed === undefined || ! canActivateRRMModule ) { return ; } diff --git a/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js b/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js index e137b91a265..370c68488ca 100644 --- a/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js +++ b/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js @@ -30,7 +30,6 @@ import { createTestRegistry, fireEvent, provideModules, - waitFor, } from '../../../../../../tests/js/test-utils'; import { getWidgetComponentProps } from '../../../../googlesitekit/widgets/util'; import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants'; @@ -40,7 +39,13 @@ import { READER_REVENUE_MANAGER_MODULE_SLUG, READER_REVENUE_MANAGER_SETUP_BANNER_DISMISSED_KEY, } from '../../datastore/constants'; +import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../googlesitekit/constants'; +import * as tracking from '../../../../util/tracking'; import useActivateModuleCallback from '../../../../hooks/useActivateModuleCallback'; +import { act } from 'react-dom/test-utils'; + +const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); +mockTrackEvent.mockImplementation( () => Promise.resolve() ); jest.mock( '../../../../hooks/useActivateModuleCallback' ); @@ -53,8 +58,9 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { ); beforeEach( () => { + mockTrackEvent.mockClear(); registry = createTestRegistry(); - activateModuleMock = jest.fn(); + activateModuleMock = jest.fn( () => jest.fn() ); registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] ); @@ -76,11 +82,17 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { />, { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } ); await waitForRegistry(); + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-setup-notification`, + 'view_notification' + ); + expect( getByText( /Grow your revenue and deepen reader engagement/ ) ).toBeInTheDocument(); @@ -99,12 +111,14 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { />, { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } ); await waitForRegistry(); expect( container ).toBeEmptyDOMElement(); + expect( mockTrackEvent ).not.toHaveBeenCalled(); } ); it( 'should call the "useActivateModuleCallback" hook when the setup CTA is clicked', async () => { @@ -121,6 +135,7 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { />, { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } ); @@ -135,6 +150,18 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { ); expect( activateModuleMock ).toHaveBeenCalledTimes( 1 ); + + expect( mockTrackEvent ).toHaveBeenNthCalledWith( + 1, + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-setup-notification`, + 'view_notification' + ); + + expect( mockTrackEvent ).toHaveBeenNthCalledWith( + 2, + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-setup-notification`, + 'confirm_notification' + ); } ); it( 'should call the dismiss item endpoint when the banner is dismissed', async () => { @@ -155,16 +182,30 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { />, { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } ); await waitForRegistry(); - fireEvent.click( getByRole( 'button', { name: /Maybe later/i } ) ); - - await waitFor( () => { - expect( fetchMock ).toHaveFetchedTimes( 1 ); + // eslint-disable-next-line require-await + await act( async () => { + fireEvent.click( getByRole( 'button', { name: /Maybe later/i } ) ); } ); + + expect( fetchMock ).toHaveFetchedTimes( 1 ); + + expect( mockTrackEvent ).toHaveBeenNthCalledWith( + 1, + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-setup-notification`, + 'view_notification' + ); + + expect( mockTrackEvent ).toHaveBeenNthCalledWith( + 2, + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-setup-notification`, + 'dismiss_notification' + ); } ); it( 'should not render the Reader Revenue Manager setup CTA banner when the module requirements do not meet', async () => { @@ -191,11 +232,13 @@ describe( 'ReaderRevenueManagerSetupCTABanner', () => { />, { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } ); await waitForRegistry(); expect( container ).toBeEmptyDOMElement(); + expect( mockTrackEvent ).not.toHaveBeenCalled(); } ); } ); From 61118af1dcf61a795ff9bb3ac74af35dbf504e6f Mon Sep 17 00:00:00 2001 From: Ankit Gade Date: Fri, 9 Aug 2024 21:30:14 +0530 Subject: [PATCH 2/9] Add tracking for PublicationOnboardingStateNotice component. --- .../PublicationOnboardingStateNotice.js | 36 ++++++++++++++++--- .../PublicationOnboardingStateNotice.test.js | 30 +++++++++++++++- ...ReaderRevenueManagerSetupCTABanner.test.js | 2 +- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.js b/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.js index 75890effba5..dab60bcdd24 100644 --- a/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.js +++ b/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.js @@ -32,11 +32,15 @@ import { PUBLICATION_ONBOARDING_STATES, } from '../../datastore/constants'; import SettingsNotice from '../../../../components/SettingsNotice'; +import { trackEvent } from '../../../../util'; +import useViewContext from '../../../../hooks/useViewContext'; +import { useEffect } from 'react'; const { PENDING_VERIFICATION, ONBOARDING_ACTION_REQUIRED } = PUBLICATION_ONBOARDING_STATES; export default function PublicationOnboardingStateNotice() { + const viewContext = useViewContext(); const onboardingState = useSelect( ( select ) => select( MODULES_READER_REVENUE_MANAGER ).getPublicationOnboardingState() ); @@ -57,10 +61,23 @@ export default function PublicationOnboardingStateNotice() { } ) ); - if ( + const skipDisplay = ! onboardingState || - ! actionableOnboardingStates.includes( onboardingState ) - ) { + ! actionableOnboardingStates.includes( onboardingState ); + + useEffect( () => { + if ( skipDisplay ) { + return; + } + + trackEvent( + `${ viewContext }_rrm-onboarding-state-notification`, + 'view_notification', + onboardingState + ); + }, [ onboardingState, skipDisplay, viewContext ] ); + + if ( skipDisplay ) { return null; } @@ -82,7 +99,18 @@ export default function PublicationOnboardingStateNotice() { const noticeCTA = () => { return ( - + { + trackEvent( + `${ viewContext }_rrm-onboarding-state-notification`, + 'confirm_notification', + onboardingState + ); + } } + href={ serviceURL } + external + inverse + > { buttonText } ); diff --git a/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.test.js b/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.test.js index 5d1044298cc..7e9faa84d02 100644 --- a/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.test.js +++ b/assets/js/modules/reader-revenue-manager/components/common/PublicationOnboardingStateNotice.test.js @@ -20,18 +20,24 @@ * Internal dependencies. */ import { + act, createTestRegistry, + fireEvent, provideUserAuthentication, provideUserInfo, render, } from '../../../../../../tests/js/test-utils'; - +import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../googlesitekit/constants'; +import * as tracking from '../../../../util/tracking'; import { MODULES_READER_REVENUE_MANAGER, PUBLICATION_ONBOARDING_STATES, } from '../../datastore/constants'; import PublicationOnboardingStateNotice from './PublicationOnboardingStateNotice'; +const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); +mockTrackEvent.mockImplementation( () => Promise.resolve() ); + describe( 'PublicationOnboardingStateNotice', () => { let registry; @@ -42,6 +48,7 @@ describe( 'PublicationOnboardingStateNotice', () => { } = PUBLICATION_ONBOARDING_STATES; beforeEach( () => { + mockTrackEvent.mockClear(); registry = createTestRegistry(); provideUserAuthentication( registry ); provideUserInfo( registry ); @@ -61,6 +68,7 @@ describe( 'PublicationOnboardingStateNotice', () => { } ); expect( container ).toBeEmptyDOMElement(); + expect( mockTrackEvent ).not.toHaveBeenCalled(); } ); it.each( [ @@ -89,11 +97,18 @@ describe( 'PublicationOnboardingStateNotice', () => { , { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } ); await waitForRegistry(); + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-onboarding-state-notification`, + 'view_notification', + publicationState + ); + expect( getByText( expectedText ) ).toBeInTheDocument(); const expectedServiceURL = registry @@ -120,6 +135,19 @@ describe( 'PublicationOnboardingStateNotice', () => { expect( container.firstChild ).toHaveClass( 'googlesitekit-publication-onboarding-state-notice' ); + + // eslint-disable-next-line require-await + await act( async () => { + fireEvent.click( + container.querySelector( '.googlesitekit-cta-link' ) + ); + } ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_rrm-onboarding-state-notification`, + 'confirm_notification', + publicationState + ); } ); } ); diff --git a/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js b/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js index 370c68488ca..1d21365334d 100644 --- a/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js +++ b/assets/js/modules/reader-revenue-manager/components/dashboard/ReaderRevenueManagerSetupCTABanner.test.js @@ -26,6 +26,7 @@ import fetchMock from 'fetch-mock'; */ import ReaderRevenueManagerSetupCTABanner from './ReaderRevenueManagerSetupCTABanner'; import { + act, render, createTestRegistry, fireEvent, @@ -42,7 +43,6 @@ import { import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../googlesitekit/constants'; import * as tracking from '../../../../util/tracking'; import useActivateModuleCallback from '../../../../hooks/useActivateModuleCallback'; -import { act } from 'react-dom/test-utils'; const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); mockTrackEvent.mockImplementation( () => Promise.resolve() ); From 0de103b91a516b5a0af83e9bf54f6007d9481157 Mon Sep 17 00:00:00 2001 From: Ankit Gade Date: Fri, 9 Aug 2024 22:18:23 +0530 Subject: [PATCH 3/9] Add tracking for PublicationApprovedOverlayNotification component. --- .../PublicationApprovedOverlayNotification.js | 33 ++++++++++--- ...icationApprovedOverlayNotification.test.js | 47 +++++++++++++++---- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.js b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.js index 3029453e67d..4bdc94f02df 100644 --- a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.js +++ b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.js @@ -28,8 +28,10 @@ import OverlayNotification from '../../../../components/OverlayNotification/Over import ReaderRevenueManagerIntroductoryGraphicDesktop from '../../../../../svg/graphics/reader-revenue-manager-introductory-graphic-desktop.svg'; import ReaderRevenueManagerIntroductoryGraphicMobile from '../../../../../svg/graphics/reader-revenue-manager-introductory-graphic-mobile.svg'; import useViewOnly from '../../../../hooks/useViewOnly'; +import useViewContext from '../../../../hooks/useViewContext'; import useDashboardType from '../../../../hooks/useDashboardType'; import ExternalIcon from '../../../../../svg/icons/external.svg'; +import { trackEvent } from '../../../../util'; import { Button } from 'googlesitekit-components'; import { useSelect, useDispatch } from 'googlesitekit-data'; import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants'; @@ -44,6 +46,7 @@ export const RRM_PUBLICATION_APPROVED_OVERLAY_NOTIFICATION = 'rrmPublicationApprovedOverlayNotification'; export default function PublicationApprovedOverlayNotification() { + const viewContext = useViewContext(); const isViewOnly = useViewOnly(); const dashboardType = useDashboardType(); @@ -84,11 +87,16 @@ export default function PublicationApprovedOverlayNotification() { ); const dismissNotification = () => { - // Dismiss the notification, which also dismisses it from - // the current user's profile with the `dismissItem` action. - dismissOverlayNotification( - RRM_PUBLICATION_APPROVED_OVERLAY_NOTIFICATION - ); + trackEvent( + `${ viewContext }_rrm-publication-approved-notification`, + 'dismiss_notification' + ).finally( () => { + // Dismiss the notification, which also dismisses it from + // the current user's profile with the `dismissItem` action. + dismissOverlayNotification( + RRM_PUBLICATION_APPROVED_OVERLAY_NOTIFICATION + ); + } ); }; return ( @@ -96,6 +104,12 @@ export default function PublicationApprovedOverlayNotification() { className="googlesitekit-reader-revenue-manager-publication-approved-notification" GraphicDesktop={ ReaderRevenueManagerIntroductoryGraphicDesktop } GraphicMobile={ ReaderRevenueManagerIntroductoryGraphicMobile } + onShow={ () => { + trackEvent( + `${ viewContext }_rrm-publication-approved-notification`, + 'view_notification' + ); + } } shouldShowNotification={ shouldShowNotification } notificationID={ RRM_PUBLICATION_APPROVED_OVERLAY_NOTIFICATION } > @@ -125,7 +139,14 @@ export default function PublicationApprovedOverlayNotification() {