diff --git a/assets/js/components/DashboardMainApp.js b/assets/js/components/DashboardMainApp.js index 19656b31d8d..b4f5df010be 100644 --- a/assets/js/components/DashboardMainApp.js +++ b/assets/js/components/DashboardMainApp.js @@ -72,8 +72,11 @@ import { CORE_FORMS } from '../googlesitekit/datastore/forms/constants'; import OfflineNotification from './notifications/OfflineNotification'; import OverlayNotificationsRenderer from './OverlayNotification/OverlayNotificationsRenderer'; import ModuleDashboardEffects from './ModuleDashboardEffects'; -import { useMonitorInternetConnection } from '../hooks/useMonitorInternetConnection'; +import { useBreakpoint } from '../hooks/useBreakpoint'; import { useFeature } from '../hooks/useFeature'; +import { useMonitorInternetConnection } from '../hooks/useMonitorInternetConnection'; +import useQueryArg from '../hooks/useQueryArg'; +import { getContextScrollTop } from '../util/scroll'; export default function DashboardMainApp() { const audienceSegmentationEnabled = useFeature( 'audienceSegmentation' ); @@ -82,6 +85,10 @@ export default function DashboardMainApp() { const viewOnlyDashboard = useViewOnly(); + const breakpoint = useBreakpoint(); + + const [ widgetArea, setWidgetArea ] = useQueryArg( 'widgetArea' ); + const { setValues } = useDispatch( CORE_FORMS ); const grantedScopes = useSelect( ( select ) => @@ -100,10 +107,24 @@ export default function DashboardMainApp() { ); useMount( () => { + // Render the current survey portal in 5 seconds after the initial rendering. if ( ! viewOnlyDashboard ) { - // Render the current survey portal in 5 seconds after the initial rendering. setTimeout( () => setShowSurveyPortal( true ), 5000 ); } + + // Scroll to a widget area if specified in the URL. + if ( widgetArea ) { + const widgetClass = `.googlesitekit-widget-area--${ widgetArea }`; + + setTimeout( () => { + global.scrollTo( { + top: getContextScrollTop( widgetClass, breakpoint ), + behavior: 'smooth', + } ); + + setWidgetArea( undefined ); + }, 100 ); + } } ); useEffect( () => { diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTAWidget.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTAWidget.js index 5cf5c56b943..aa096163688 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTAWidget.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTAWidget.js @@ -25,9 +25,8 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { compose } from '@wordpress/compose'; -import { addQueryArgs } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; -import { Fragment, useCallback, useState, useEffect } from '@wordpress/element'; +import { Fragment, useCallback, useState } from '@wordpress/element'; /** * Internal dependencies @@ -42,9 +41,9 @@ import { CORE_USER } from '../../../../../googlesitekit/datastore/user/constants import { CORE_SITE } from '../../../../../googlesitekit/datastore/site/constants'; import { MODULES_ANALYTICS_4, - EDIT_SCOPE, AUDIENCE_SEGMENTATION_SETUP_FORM, } from '../../../datastore/constants'; +import { SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION } from '../settings/SettingsCardVisitorGroups/SetupSuccess'; import { Button, SpinnerButton } from 'googlesitekit-components'; import { Cell, Grid, Row } from '../../../../../material-components'; import { @@ -52,7 +51,6 @@ import { BREAKPOINT_TABLET, useBreakpoint, } from '../../../../../hooks/useBreakpoint'; -import { ERROR_CODE_MISSING_REQUIRED_SCOPE } from '../../../../../util/errors'; import { AdminMenuTooltip, useShowTooltip, @@ -60,19 +58,18 @@ import { } from '../../../../../components/AdminMenuTooltip'; import { withWidgetComponentProps } from '../../../../../googlesitekit/widgets/util'; import { WEEK_IN_SECONDS } from '../../../../../util'; +import useEnableAudienceGroup from '../../../hooks/useEnableAudienceGroup'; import AudienceErrorModal from './AudienceErrorModal'; export const AUDIENCE_SEGMENTATION_SETUP_CTA_NOTIFICATION = 'audience_segmentation_setup_cta-notification'; function AudienceSegmentationSetupCTAWidget( { Widget, WidgetNull } ) { - const [ isSaving, setIsSaving ] = useState( false ); const breakpoint = useBreakpoint(); const isMobileBreakpoint = breakpoint === BREAKPOINT_SMALL; const isTabletBreakpoint = breakpoint === BREAKPOINT_TABLET; const { setValues } = useDispatch( CORE_FORMS ); - const { setPermissionScopeError } = useDispatch( CORE_USER ); const showTooltip = useShowTooltip( AUDIENCE_SEGMENTATION_SETUP_CTA_NOTIFICATION ); @@ -91,16 +88,10 @@ function AudienceSegmentationSetupCTAWidget( { Widget, WidgetNull } ) { ) ); - const { enableAudienceGroup } = useDispatch( MODULES_ANALYTICS_4 ); - const configuredAudiences = useSelect( ( select ) => select( MODULES_ANALYTICS_4 ).getConfiguredAudiences() ); - const hasAnalytics4EditScope = useSelect( ( select ) => - select( CORE_USER ).hasScope( EDIT_SCOPE ) - ); - const autoSubmit = useSelect( ( select ) => select( CORE_FORMS ).getValue( AUDIENCE_SEGMENTATION_SETUP_FORM, @@ -108,80 +99,23 @@ function AudienceSegmentationSetupCTAWidget( { Widget, WidgetNull } ) { ) ); - const redirectURL = addQueryArgs( global.location.href, { - notification: 'audience_segmentation', - } ); - - const [ apiErrors, setApiErrors ] = useState( [] ); - const [ failedAudiences, setFailedAudiences ] = useState( [] ); const [ showErrorModal, setShowErrorModal ] = useState( false ); - const onEnableGroups = useCallback( async () => { - setIsSaving( true ); - - // If scope not granted, trigger scope error right away. These are - // typically handled automatically based on API responses, but - // this particular case has some special handling to improve UX. - if ( ! hasAnalytics4EditScope ) { - setValues( AUDIENCE_SEGMENTATION_SETUP_FORM, { - autoSubmit: true, - } ); - - setPermissionScopeError( { - code: ERROR_CODE_MISSING_REQUIRED_SCOPE, - message: __( - 'Additional permissions are required to create new audiences in Analytics.', - 'google-site-kit' - ), - data: { - status: 403, - scopes: [ EDIT_SCOPE ], - skipModal: true, - skipDefaultErrorNotifications: true, - redirectURL, - }, - } ); - - return; - } - - setValues( AUDIENCE_SEGMENTATION_SETUP_FORM, { - autoSubmit: false, + const { dismissItem, dismissPrompt } = useDispatch( CORE_USER ); + + const { apiErrors, failedAudiences, isSaving, onEnableGroups } = + useEnableAudienceGroup( { + onSuccess: () => { + // Dismiss success notification in settings. + dismissItem( + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION + ); + }, + onError: () => { + setShowErrorModal( true ); + }, } ); - const { error, failedSiteKitAudienceSlugs } = - ( await enableAudienceGroup( failedAudiences ) ) || {}; - - if ( error ) { - setApiErrors( [ error ] ); - setFailedAudiences( [] ); - } else if ( Array.isArray( failedSiteKitAudienceSlugs ) ) { - setFailedAudiences( failedSiteKitAudienceSlugs ); - setApiErrors( [] ); - } else { - setApiErrors( [] ); - setFailedAudiences( [] ); - } - - setShowErrorModal( !! error || !! failedSiteKitAudienceSlugs ); - setIsSaving( false ); - }, [ - hasAnalytics4EditScope, - setValues, - enableAudienceGroup, - failedAudiences, - setPermissionScopeError, - redirectURL, - ] ); - - // If the user ends up back on this component with the required scope granted, - // and already submitted the form, trigger the submit again. - useEffect( () => { - if ( hasAnalytics4EditScope && autoSubmit ) { - onEnableGroups(); - } - }, [ hasAnalytics4EditScope, autoSubmit, onEnableGroups ] ); - const analyticsIsDataAvailableOnLoad = useSelect( ( select ) => { // We should call isGatheringData() within this component for completeness // as we do not want to rely on it being called in other components. @@ -192,7 +126,6 @@ function AudienceSegmentationSetupCTAWidget( { Widget, WidgetNull } ) { return select( MODULES_ANALYTICS_4 ).isDataAvailableOnLoad(); } ); - const { dismissPrompt } = useDispatch( CORE_USER ); const handleDismissClick = async () => { showTooltip(); diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupCTA.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupCTA.js new file mode 100644 index 00000000000..8226a8bb18c --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupCTA.js @@ -0,0 +1,77 @@ +/** + * SettingsCardVisitorGroups SetupCTA component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { AUDIENCE_SEGMENTATION_SETUP_CTA_NOTIFICATION } from '../../dashboard/AudienceSegmentationSetupCTAWidget'; +import { AUDIENCE_SEGMENTATION_SETUP_SUCCESS_NOTIFICATION } from '../../dashboard/AudienceSegmentationSetupSuccessSubtleNotification'; +import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import { useDispatch, useSelect } from 'googlesitekit-data'; +import useEnableAudienceGroup from '../../../../hooks/useEnableAudienceGroup'; +import { ProgressBar } from 'googlesitekit-components'; +import Link from '../../../../../../components/Link'; + +export default function SetupCTA() { + const { dismissItem } = useDispatch( CORE_USER ); + + const { isSaving, onEnableGroups } = useEnableAudienceGroup( { + redirectURL: global.location.href, + onSuccess: () => { + // Dismiss success notification in dashboard. + dismissItem( AUDIENCE_SEGMENTATION_SETUP_SUCCESS_NOTIFICATION ); + }, + } ); + + const isDismissed = useSelect( ( select ) => + select( CORE_USER ).isPromptDismissed( + AUDIENCE_SEGMENTATION_SETUP_CTA_NOTIFICATION + ) + ); + + if ( isDismissed === undefined || isDismissed ) { + return null; + } + + return ( +
+

+ { __( + 'To set up new visitor groups for your site, Site Kit needs to update your Google Analytics property.', + 'google-site-kit' + ) } +

+ { isSaving && ( +
+

{ __( 'Enabling groups', 'google-site-kit' ) }

+ +
+ ) } + { ! isSaving && ( + + { __( 'Enable groups', 'google-site-kit' ) } + + ) } +
+ ); +} diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupCTA.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupCTA.test.js new file mode 100644 index 00000000000..d6a9c9cb499 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupCTA.test.js @@ -0,0 +1,159 @@ +/** + * SettingsCardVisitorGroups SetupCTA component tests. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { availableAudiences as audiencesFixture } from '../../../../datastore/__fixtures__'; +import { + createTestRegistry, + fireEvent, + freezeFetch, + muteFetch, + provideModules, + provideUserAuthentication, + render, +} from '../../../../../../../../tests/js/test-utils'; +import { AUDIENCE_SEGMENTATION_SETUP_CTA_NOTIFICATION } from '../../dashboard/AudienceSegmentationSetupCTAWidget'; +import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import { + EDIT_SCOPE, + MODULES_ANALYTICS_4, +} from '../../../../datastore/constants'; +import SetupCTA from './SetupCTA'; + +describe( 'SettingsCardVisitorGroups SetupCTA', () => { + let registry; + + const audienceSettingsEndpoint = new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/audience-settings' + ); + const reportEndpoint = new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/report' + ); + const syncAvailableAudiencesEndpoint = new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/sync-audiences' + ); + + beforeEach( () => { + registry = createTestRegistry(); + + provideUserAuthentication( registry, { + grantedScopes: [ EDIT_SCOPE ], + } ); + + provideModules( registry, [ + { + slug: 'analytics-4', + active: true, + connected: true, + }, + ] ); + + registry.dispatch( CORE_USER ).receiveGetDismissedPrompts( [] ); + + registry.dispatch( MODULES_ANALYTICS_4 ).setSettings( { + availableAudiences: null, + availableCustomDimensions: [ 'googlesitekit_post_type' ], + propertyID: '123456789', + } ); + + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetAudienceSettings( { + configuredAudiences: null, + isAudienceSegmentationWidgetHidden: false, + } ); + } ); + + it( 'should render the setup CTA', () => { + const { getByText, getByRole } = render( , { registry } ); + + expect( + getByText( + 'To set up new visitor groups for your site, Site Kit needs to update your Google Analytics property.' + ) + ).toBeInTheDocument(); + + expect( + getByRole( 'button', { name: /Enable groups/i } ) + ).toBeInTheDocument(); + } ); + + it( 'should not render the setup CTA if dismissed', () => { + registry.dispatch( CORE_USER ).receiveGetDismissedPrompts( { + [ AUDIENCE_SEGMENTATION_SETUP_CTA_NOTIFICATION ]: { + expires: 0, + count: 1, + }, + } ); + + const { queryByText, queryByRole } = render( , { + registry, + } ); + + expect( + queryByText( + 'To set up new visitor groups for your site, Site Kit needs to update your Google Analytics property.' + ) + ).not.toBeInTheDocument(); + + expect( + queryByRole( 'button', { name: /Enable groups/i } ) + ).not.toBeInTheDocument(); + } ); + + it( 'should show in progress state when enabling groups', () => { + freezeFetch( syncAvailableAudiencesEndpoint ); + + const { getByText, getByRole } = render( , { registry } ); + + fireEvent.click( getByRole( 'button', { name: /Enable groups/i } ) ); + + expect( getByText( 'Enabling groups' ) ).toBeInTheDocument(); + } ); + + it( 'should initialize the list of configured audiences when the CTA is clicked', () => { + fetchMock.postOnce( syncAvailableAudiencesEndpoint, { + status: 200, + body: audiencesFixture, + } ); + + fetchMock.postOnce( audienceSettingsEndpoint, { + status: 200, + body: { + configuredAudiences: [ + audiencesFixture[ 3 ].name, + audiencesFixture[ 4 ].name, + ], + isAudienceSegmentationWidgetHidden: false, + }, + } ); + + muteFetch( reportEndpoint ); + + const { getByText, getByRole } = render( , { registry } ); + + expect( + getByRole( 'button', { name: /Enable groups/i } ) + ).toBeInTheDocument(); + + fireEvent.click( getByRole( 'button', { name: /Enable groups/i } ) ); + + expect( getByText( 'Enabling groups' ) ).toBeInTheDocument(); + expect( fetchMock ).toHaveFetched( syncAvailableAudiencesEndpoint ); + } ); +} ); diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupSuccess.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupSuccess.js new file mode 100644 index 00000000000..5cde9517e82 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupSuccess.js @@ -0,0 +1,96 @@ +/** + * SettingsCardVisitorGroups SetupSuccess component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION } from '../../../../../../googlesitekit/widgets/default-areas'; +import { CORE_LOCATION } from '../../../../../../googlesitekit/datastore/location/constants'; +import { CORE_SITE } from '../../../../../../googlesitekit/datastore/site/constants'; +import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import { useDispatch, useSelect } from 'googlesitekit-data'; +import { Button } from 'googlesitekit-components'; +import CheckFill from '../../../../../../../svg/icons/check-fill.svg'; + +export const SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION = + 'settings_visitor_groups_setup_success_notification'; + +export default function SetupSuccess() { + const isDismissed = useSelect( ( select ) => + select( CORE_USER ).isItemDismissed( + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION + ) + ); + const dashboardURL = useSelect( ( select ) => { + const url = select( CORE_SITE ).getAdminURL( + 'googlesitekit-dashboard' + ); + + return addQueryArgs( url, { + widgetArea: AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION, + } ); + } ); + + const { navigateTo } = useDispatch( CORE_LOCATION ); + const { dismissItem } = useDispatch( CORE_USER ); + + function dismissNotificationForUser() { + return dismissItem( + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION + ); + } + + async function scrollToWidgetArea() { + await dismissNotificationForUser(); + navigateTo( dashboardURL ); + } + + if ( isDismissed === undefined || isDismissed ) { + return null; + } + + return ( +
+
+ +
+
+

+ { __( + 'We’ve added the audiences section to your dashboard!', + 'google-site-kit' + ) } +

+
+
+ + +
+
+ ); +} diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupSuccess.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupSuccess.test.js new file mode 100644 index 00000000000..016f4649139 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/SetupSuccess.test.js @@ -0,0 +1,163 @@ +/** + * SettingsCardVisitorGroups SetupSuccess component tests. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * WordPress dependencies + */ +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { + createTestRegistry, + fireEvent, + provideSiteInfo, + render, + waitFor, +} from '../../../../../../../../tests/js/test-utils'; +import { mockLocation } from '../../../../../../../../tests/js/mock-browser-utils'; +import { AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION } from '../../../../../../googlesitekit/widgets/default-areas'; +import { CORE_SITE } from '../../../../../../googlesitekit/datastore/site/constants'; +import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import SetupSuccess, { + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION, +} from './SetupSuccess'; + +describe( 'SettingsCardVisitorGroups SetupSuccess', () => { + let registry; + let dismissItemSpy; + + mockLocation(); + + beforeEach( () => { + registry = createTestRegistry(); + + provideSiteInfo( registry ); + + registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] ); + + dismissItemSpy = jest.spyOn( + registry.dispatch( CORE_USER ), + 'dismissItem' + ); + + dismissItemSpy.mockImplementation( () => Promise.resolve() ); + } ); + + afterEach( () => { + dismissItemSpy.mockReset(); + } ); + + it( 'should render the setup success notification', () => { + const { getByText, getByRole } = render( , { + registry, + } ); + + expect( + getByText( 'We’ve added the audiences section to your dashboard!' ) + ).toBeInTheDocument(); + + expect( + getByRole( 'button', { name: /Got it/i } ) + ).toBeInTheDocument(); + + expect( + getByRole( 'button', { name: /Show me/i } ) + ).toBeInTheDocument(); + } ); + + it( 'should not render the setup success notification if dismissed', () => { + registry + .dispatch( CORE_USER ) + .receiveGetDismissedItems( [ + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION, + ] ); + + const { queryByText, queryByRole } = render( , { + registry, + } ); + + expect( + queryByText( + 'We’ve added the audiences section to your dashboard!' + ) + ).not.toBeInTheDocument(); + + expect( + queryByRole( 'button', { name: /Got it/i } ) + ).not.toBeInTheDocument(); + + expect( + queryByRole( 'button', { name: /Show me/i } ) + ).not.toBeInTheDocument(); + } ); + + it( 'should dismiss the notification if "Got it" is clicked on', () => { + const { queryByText, getByRole } = render( , { + registry, + } ); + + expect( + queryByText( + 'We’ve added the audiences section to your dashboard!' + ) + ).toBeInTheDocument(); + + fireEvent.click( getByRole( 'button', { name: /Got it/i } ) ); + + expect( dismissItemSpy ).toHaveBeenCalledTimes( 1 ); + expect( dismissItemSpy ).toHaveBeenCalledWith( + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION + ); + } ); + + it( 'should dismiss the notification and navigate to dashboard if "Show me" is clicked on', async () => { + const { queryByText, getByRole } = render( , { + registry, + } ); + + expect( + queryByText( + 'We’ve added the audiences section to your dashboard!' + ) + ).toBeInTheDocument(); + + fireEvent.click( getByRole( 'button', { name: /Show me/i } ) ); + + expect( dismissItemSpy ).toHaveBeenCalledTimes( 1 ); + expect( dismissItemSpy ).toHaveBeenCalledWith( + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION + ); + + const expectedURL = addQueryArgs( + registry + .select( CORE_SITE ) + .getAdminURL( 'googlesitekit-dashboard' ), + { + widgetArea: AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION, + } + ); + + await waitFor( () => { + expect( global.location.assign ).toHaveBeenCalledWith( + expectedURL + ); + } ); + } ); +} ); diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.js similarity index 64% rename from assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.js rename to assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.js index eac3d66dfe5..872d114b727 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.js @@ -19,22 +19,27 @@ /** * WordPress dependencies */ +import { Fragment, useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ import { useSelect, useDispatch } from 'googlesitekit-data'; +import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; import { Switch } from 'googlesitekit-components'; -import { Cell, Grid, Row } from '../../../../../material-components'; -import Layout from '../../../../../components/layout/Layout'; -import { MODULES_ANALYTICS_4 } from '../../../datastore/constants'; +import { Cell, Grid, Row } from '../../../../../../material-components'; +import Layout from '../../../../../../components/layout/Layout'; +import SetupCTA from './SetupCTA'; +import SetupSuccess from './SetupSuccess'; export default function SettingsCardVisitorGroups() { const audienceSegmentationWidgetHidden = useSelect( ( select ) => select( MODULES_ANALYTICS_4 ).isAudienceSegmentationWidgetHidden() ); + const configuredAudiences = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).getConfiguredAudiences() + ); const { setAudienceSegmentationWidgetHidden, saveAudienceSettings } = useDispatch( MODULES_ANALYTICS_4 ); @@ -50,6 +55,10 @@ export default function SettingsCardVisitorGroups() { setAudienceSegmentationWidgetHidden, ] ); + if ( configuredAudiences === undefined ) { + return null; + } + return ( - + { ! configuredAudiences?.length && } + { !! configuredAudiences?.length && ( + + + + + ) } diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.stories.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.stories.js similarity index 57% rename from assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.stories.js rename to assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.stories.js index dc2236e5f77..acaff376c8e 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.stories.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.stories.js @@ -24,9 +24,11 @@ import fetchMock from 'fetch-mock'; /** * Internal dependencies */ -import { MODULES_ANALYTICS_4 } from '../../../datastore/constants'; -import WithRegistrySetup from '../../../../../../../tests/js/WithRegistrySetup'; -import SettingsCardVisitorGroups from './SettingsCardVisitorGroups'; +import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; +import { SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION } from './SetupSuccess'; +import WithRegistrySetup from '../../../../../../../../tests/js/WithRegistrySetup'; +import SettingsCardVisitorGroups from './'; function Template() { return ; @@ -38,11 +40,42 @@ Default.scenario = { label: 'Modules/Analytics4/Components/AudienceSegmentation/Settings/SettingsCardVisitorGroups/Default', }; +export const WithSetupCTA = Template.bind( {} ); +WithSetupCTA.storyName = 'With setup CTA'; +WithSetupCTA.args = { + setupRegistry: ( registry ) => { + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetAudienceSettings( { + configuredAudiences: [], + isAudienceSegmentationWidgetHidden: false, + } ); + }, +}; +WithSetupCTA.scenario = { + label: 'Modules/Analytics4/Components/AudienceSegmentation/Settings/SettingsCardVisitorGroups/WithSetupCTA', +}; + +export const WithSetupSuccessNotification = Template.bind( {} ); +WithSetupSuccessNotification.storyName = 'With setup success notification'; +WithSetupSuccessNotification.args = { + setupRegistry: ( registry ) => { + registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] ); + }, +}; +WithSetupSuccessNotification.scenario = { + label: 'Modules/Analytics4/Components/AudienceSegmentation/Settings/SettingsCardVisitorGroups/WithSetupSuccessNotification', +}; + export default { title: 'Modules/Analytics4/Components/AudienceSegmentation/Settings/SettingsCardVisitorGroups', decorators: [ ( Story, { args } ) => { const setupRegistry = ( registry ) => { + registry + .dispatch( CORE_USER ) + .receiveGetDismissedItems( [ + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION, + ] ); + registry .dispatch( MODULES_ANALYTICS_4 ) .receiveGetAudienceSettings( { diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.test.js similarity index 64% rename from assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.test.js rename to assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.test.js index 72279be1cd8..631f366de7d 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.test.js @@ -16,16 +16,62 @@ * limitations under the License. */ -import { render, waitFor } from '../../../../../../../tests/js/test-utils'; -import { createTestRegistry } from '../../../../../../../tests/js/utils'; -import { MODULES_ANALYTICS_4 } from '../../../datastore/constants'; -import SettingsCardVisitorGroups from './SettingsCardVisitorGroups'; +import { render, waitFor } from '../../../../../../../../tests/js/test-utils'; +import { + createTestRegistry, + provideUserAuthentication, +} from '../../../../../../../../tests/js/utils'; +import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; +import { SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION } from './SetupSuccess'; +import SettingsCardVisitorGroups from './'; describe( 'SettingsCardVisitorGroups', () => { let registry; beforeEach( () => { registry = createTestRegistry(); + + provideUserAuthentication( registry ); + + registry + .dispatch( CORE_USER ) + .receiveGetDismissedItems( [ + SETTINGS_VISITOR_GROUPS_SETUP_SUCCESS_NOTIFICATION, + ] ); + + registry.dispatch( CORE_USER ).receiveGetDismissedPrompts( [] ); + } ); + + it( 'should render the setup CTA if groups are not configured', () => { + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetAudienceSettings( { + configuredAudiences: [], + isAudienceSegmentationWidgetHidden: false, + } ); + + const { getByRole } = render( , { + registry, + } ); + + expect( + getByRole( 'button', { name: /Enable groups/i } ) + ).toBeInTheDocument(); + } ); + + it( 'should render the setup success notification once groups are configured', () => { + registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] ); + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetAudienceSettings( { + configuredAudiences: [ 'audienceA', 'audienceB' ], + isAudienceSegmentationWidgetHidden: false, + } ); + + const { getByText } = render( , { + registry, + } ); + + expect( + getByText( 'We’ve added the audiences section to your dashboard!' ) + ).toBeInTheDocument(); } ); it( 'should render the visitor groups switch correctly', async () => { diff --git a/assets/js/modules/analytics-4/hooks/useEnableAudienceGroup.js b/assets/js/modules/analytics-4/hooks/useEnableAudienceGroup.js new file mode 100644 index 00000000000..4a6fbc950c1 --- /dev/null +++ b/assets/js/modules/analytics-4/hooks/useEnableAudienceGroup.js @@ -0,0 +1,155 @@ +/** + * Analytics useEnableAudienceGroup custom hook. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useMountedState } from 'react-use'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useCallback, useEffect, useState } from '@wordpress/element'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { useDispatch, useSelect } from 'googlesitekit-data'; +import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants'; +import { CORE_USER } from '../../../googlesitekit/datastore/user/constants'; +import { ERROR_CODE_MISSING_REQUIRED_SCOPE } from '../../../util/errors'; +import { + AUDIENCE_SEGMENTATION_SETUP_FORM, + EDIT_SCOPE, + MODULES_ANALYTICS_4, +} from '../datastore/constants'; + +export default function useEnableAudienceGroup( { + redirectURL, + onSuccess, + onError, +} = {} ) { + const isMounted = useMountedState(); + + const [ apiErrors, setApiErrors ] = useState( [] ); + const [ failedAudiences, setFailedAudiences ] = useState( [] ); + const [ isSaving, setIsSaving ] = useState( false ); + + const hasAnalytics4EditScope = useSelect( ( select ) => + select( CORE_USER ).hasScope( EDIT_SCOPE ) + ); + const autoSubmit = useSelect( ( select ) => + select( CORE_FORMS ).getValue( + AUDIENCE_SEGMENTATION_SETUP_FORM, + 'autoSubmit' + ) + ); + + const { setValues } = useDispatch( CORE_FORMS ); + const { setPermissionScopeError } = useDispatch( CORE_USER ); + const { enableAudienceGroup } = useDispatch( MODULES_ANALYTICS_4 ); + + if ( ! redirectURL ) { + redirectURL = addQueryArgs( global.location.href, { + notification: 'audience_segmentation', + } ); + } + + const onEnableGroups = useCallback( async () => { + setIsSaving( true ); + + // If scope is not granted, trigger scope error right away. These are + // typically handled automatically based on API responses, but + // this particular case has some special handling to improve UX. + if ( ! hasAnalytics4EditScope ) { + setValues( AUDIENCE_SEGMENTATION_SETUP_FORM, { + autoSubmit: true, + } ); + + setPermissionScopeError( { + code: ERROR_CODE_MISSING_REQUIRED_SCOPE, + message: __( + 'Additional permissions are required to create new audiences in Analytics.', + 'google-site-kit' + ), + data: { + status: 403, + scopes: [ EDIT_SCOPE ], + skipModal: true, + skipDefaultErrorNotifications: true, + redirectURL, + }, + } ); + + return; + } + + setValues( AUDIENCE_SEGMENTATION_SETUP_FORM, { autoSubmit: false } ); + + const { error, failedSiteKitAudienceSlugs } = + ( await enableAudienceGroup( failedAudiences ) ) || {}; + + if ( !! error || !! failedSiteKitAudienceSlugs ) { + onError?.(); + } else { + onSuccess?.(); + } + + if ( isMounted() ) { + if ( error ) { + setApiErrors( [ error ] ); + setFailedAudiences( [] ); + } else if ( Array.isArray( failedSiteKitAudienceSlugs ) ) { + setFailedAudiences( failedSiteKitAudienceSlugs ); + setApiErrors( [] ); + } else { + setApiErrors( [] ); + setFailedAudiences( [] ); + } + + setIsSaving( false ); + } + }, [ + hasAnalytics4EditScope, + setValues, + enableAudienceGroup, + failedAudiences, + isMounted, + setPermissionScopeError, + redirectURL, + onError, + onSuccess, + ] ); + + // If the user returns to the component using this hook with the required + // scope granted, and already submitted the form, trigger the submit again. + useEffect( () => { + if ( hasAnalytics4EditScope && autoSubmit ) { + onEnableGroups(); + } + }, [ hasAnalytics4EditScope, autoSubmit, onEnableGroups ] ); + + return { + apiErrors, + failedAudiences, + isSaving, + onEnableGroups, + }; +} diff --git a/assets/js/modules/analytics-4/hooks/useEnableAudienceGroup.test.js b/assets/js/modules/analytics-4/hooks/useEnableAudienceGroup.test.js new file mode 100644 index 00000000000..c280ee14b5d --- /dev/null +++ b/assets/js/modules/analytics-4/hooks/useEnableAudienceGroup.test.js @@ -0,0 +1,224 @@ +/** + * Analytics useEnableAudienceGroup hook tests. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { + AUDIENCE_SEGMENTATION_SETUP_FORM, + EDIT_SCOPE, + MODULES_ANALYTICS_4, +} from '../datastore/constants'; +import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants'; +import { CORE_USER } from '../../../googlesitekit/datastore/user/constants'; +import { availableAudiences as audiencesFixture } from '../datastore/__fixtures__'; +import { actHook, renderHook } from '../../../../../tests/js/test-utils'; +import { + createTestRegistry, + freezeFetch, + muteFetch, + provideModules, + provideUserAuthentication, + waitForTimeouts, +} from '../../../../../tests/js/utils'; +import useEnableAudienceGroup from './useEnableAudienceGroup'; + +describe( 'useEnableAudienceGroup', () => { + let registry; + let enableAudienceGroupSpy; + + const audienceSettingsEndpoint = new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/audience-settings' + ); + const reportEndpoint = new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/report' + ); + const syncAvailableAudiencesEndpoint = new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/sync-audiences' + ); + + beforeEach( () => { + registry = createTestRegistry(); + + enableAudienceGroupSpy = jest.spyOn( + registry.dispatch( MODULES_ANALYTICS_4 ), + 'enableAudienceGroup' + ); + + provideUserAuthentication( registry, { + grantedScopes: [ EDIT_SCOPE ], + } ); + + provideModules( registry, [ + { + slug: 'analytics-4', + active: true, + connected: true, + }, + ] ); + + registry.dispatch( MODULES_ANALYTICS_4 ).setSettings( { + availableAudiences: null, + availableCustomDimensions: [ 'googlesitekit_post_type' ], + propertyID: '123456789', + } ); + + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetAudienceSettings( { + configuredAudiences: null, + isAudienceSegmentationWidgetHidden: false, + } ); + } ); + + afterEach( () => { + enableAudienceGroupSpy.mockRestore(); + } ); + + it( 'should return an object with appropriate properties', () => { + const { result } = renderHook( () => useEnableAudienceGroup(), { + registry, + } ); + + expect( Object.keys( result.current ) ).toEqual( [ + 'apiErrors', + 'failedAudiences', + 'isSaving', + 'onEnableGroups', + ] ); + + const { isSaving, onEnableGroups } = result.current; + + expect( typeof isSaving ).toBe( 'boolean' ); + expect( typeof onEnableGroups ).toBe( 'function' ); + } ); + + it( 'should set `isSaving` to true when `onEnableGroups` is called', () => { + freezeFetch( syncAvailableAudiencesEndpoint ); + + const { result } = renderHook( () => useEnableAudienceGroup(), { + registry, + } ); + + const { isSaving, onEnableGroups } = result.current; + + expect( isSaving ).toBe( false ); + + actHook( () => { + onEnableGroups(); + } ); + + expect( result.current.isSaving ).toBe( true ); + } ); + + it( 'should set permission scope error when `onEnableGroups` is called but the user does not have the required scope', () => { + provideUserAuthentication( registry, { + grantedScopes: [], + } ); + + const { result } = renderHook( () => useEnableAudienceGroup(), { + registry, + } ); + + const { onEnableGroups } = result.current; + + actHook( () => { + onEnableGroups(); + } ); + + const { message } = registry + .select( CORE_USER ) + .getPermissionScopeError(); + + expect( message ).toBe( + 'Additional permissions are required to create new audiences in Analytics.' + ); + + expect( enableAudienceGroupSpy ).not.toHaveBeenCalled(); + } ); + + it( 'should automatically call `onEnableGroups` function when user returns from the OAuth screen', async () => { + fetchMock.postOnce( syncAvailableAudiencesEndpoint, { + status: 200, + body: audiencesFixture, + } ); + + fetchMock.postOnce( audienceSettingsEndpoint, { + status: 200, + body: { + configuredAudiences: [ + audiencesFixture[ 3 ].name, + audiencesFixture[ 4 ].name, + ], + isAudienceSegmentationWidgetHidden: false, + }, + } ); + + muteFetch( reportEndpoint ); + + // Set autoSubmit to true. + registry + .dispatch( CORE_FORMS ) + .setValues( AUDIENCE_SEGMENTATION_SETUP_FORM, { + autoSubmit: true, + } ); + + // eslint-disable-next-line require-await + await actHook( async () => { + renderHook( () => useEnableAudienceGroup(), { + registry, + } ); + } ); + + expect( enableAudienceGroupSpy ).toHaveBeenCalledTimes( 1 ); + + await actHook( () => waitForTimeouts( 30 ) ); + } ); + + it( 'should dispatch the `enableAudienceGroup` action when `onEnableGroups` is called', async () => { + fetchMock.postOnce( syncAvailableAudiencesEndpoint, { + status: 200, + body: audiencesFixture, + } ); + + fetchMock.postOnce( audienceSettingsEndpoint, { + status: 200, + body: { + configuredAudiences: [ + audiencesFixture[ 3 ].name, + audiencesFixture[ 4 ].name, + ], + isAudienceSegmentationWidgetHidden: false, + }, + } ); + + muteFetch( reportEndpoint ); + + const { result } = renderHook( () => useEnableAudienceGroup(), { + registry, + } ); + + const { onEnableGroups } = result.current; + + await actHook( async () => { + await onEnableGroups(); + } ); + + expect( enableAudienceGroupSpy ).toHaveBeenCalledTimes( 1 ); + + await actHook( () => waitForTimeouts( 30 ) ); + } ); +} ); diff --git a/assets/sass/admin.scss b/assets/sass/admin.scss index dc14f79e308..36b269b93e0 100644 --- a/assets/sass/admin.scss +++ b/assets/sass/admin.scss @@ -182,6 +182,7 @@ @import "components/settings/googlesitekit-settings-module"; @import "components/settings/googlesitekit-settings-notice"; @import "components/settings/googlesitekit-settings-user-input"; +@import "components/settings/googlesitekit-settings-visitor-groups"; // User Input @import "components/user-input/googlesitekit-user-input-module"; diff --git a/assets/sass/components/settings/_googlesitekit-settings-visitor-groups.scss b/assets/sass/components/settings/_googlesitekit-settings-visitor-groups.scss new file mode 100644 index 00000000000..f239942e968 --- /dev/null +++ b/assets/sass/components/settings/_googlesitekit-settings-visitor-groups.scss @@ -0,0 +1,47 @@ +/** + * Settings Visitor Groups styles. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.googlesitekit-plugin { + + .googlesitekit-settings-visitor-groups__setup { + + p { + font-size: $fs-body-sm; + letter-spacing: $ls-xs; + line-height: $lh-body-sm; + margin-top: 0; + } + + .googlesitekit-cta-link { + font-weight: $fw-medium; + margin-top: 6px; + } + } + + .googlesitekit-settings-visitor-groups__setup-progress { + margin-top: $grid-gap-phone; + + p { + margin-bottom: 10px; + } + } + + .googlesitekit-settings-visitor-groups__setup-success { + margin-bottom: 30px; + } +} diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_0_small.png new file mode 100644 index 00000000000..91b1ead4eb2 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_1_medium.png new file mode 100644 index 00000000000..0192952091c Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_2_large.png new file mode 100644 index 00000000000..52cfc537e39 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupCTA_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_0_small.png new file mode 100644 index 00000000000..00d664922cf Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_1_medium.png new file mode 100644 index 00000000000..62b91f2b0f2 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_2_large.png new file mode 100644 index 00000000000..21a850cc89e Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Settings_SettingsCardVisitorGroups_WithSetupSuccessNotification_0_document_2_large.png differ