- { Icon &&
}
- { ! Icon && variant === VARIANTS.SUCCESS && (
-
+const SubtleNotification = forwardRef(
+ (
+ {
+ title,
+ description,
+ Icon,
+ ctaLink,
+ ctaLabel,
+ className,
+ onCTAClick,
+ isCTALinkExternal,
+ dismissLabel,
+ onDismiss,
+ variant = VARIANTS.SUCCESS,
+ hideIcon = false,
+ },
+ ref
+ ) => {
+ return (
+
+ { ! hideIcon && (
+
+ { Icon && }
+ { ! Icon && variant === VARIANTS.SUCCESS && (
+
+ ) }
+ { ! Icon && variant === VARIANTS.WARNING && (
+
+ ) }
+
+ ) }
+
+
{ title }
+ { description && (
+
+ { description }
+
) }
- { ! Icon && variant === VARIANTS.WARNING && (
-
+
+
+ { dismissLabel && (
+
+ ) }
+ { ctaLabel && (
+
+ ) : undefined
+ }
+ >
+ { ctaLabel }
+
) }
- ) }
-
-
{ title }
- { description && (
-
- { description }
-
- ) }
-
-
- { dismissLabel && (
-
- ) }
- { ctaLabel && (
-
- ) : undefined
- }
- >
- { ctaLabel }
-
- ) }
-
- );
-}
+ );
+ }
+);
SubtleNotification.propTypes = {
title: PropTypes.node.isRequired,
diff --git a/assets/js/components/settings/SettingsActiveModule/Footer.js b/assets/js/components/settings/SettingsActiveModule/Footer.js
index 9bc29c5dfdd..ff9ac93535d 100644
--- a/assets/js/components/settings/SettingsActiveModule/Footer.js
+++ b/assets/js/components/settings/SettingsActiveModule/Footer.js
@@ -58,6 +58,9 @@ export default function Footer( props ) {
const dialogActiveKey = `module-${ slug }-dialogActive`;
const isSavingKey = `module-${ slug }-isSaving`;
+ const areSettingsEditDependenciesLoaded = useSelect( ( select ) =>
+ select( CORE_MODULES ).areSettingsEditDependenciesLoaded( slug )
+ );
const canSubmitChanges = useSelect( ( select ) =>
select( CORE_MODULES ).canSubmitChanges( slug )
);
@@ -147,30 +150,6 @@ export default function Footer( props ) {
);
}, [ slug, viewContext ] );
- // Check if the resolution for the specified selector has finished.
- // This allows us to determine if the data needed by the module is still being loaded.
- // The primary reason for this loading check is to disable the submit button
- // while the necessary data for the settings is still being loaded, preventing
- // premature interactions by the user.
- const isLoading = useSelect( ( select ) => {
- const resolutionMapping = {
- 'analytics-4': 'getAccountSummaries',
- tagmanager: 'getAccounts',
- 'search-console': 'getMatchedProperties',
- };
- const resolutionSelector = resolutionMapping[ slug ];
-
- if ( ! module || ! resolutionSelector ) {
- return false;
- }
-
- const storeName = module.storeName;
-
- return ! select( storeName ).hasFinishedResolution(
- resolutionSelector
- );
- } );
-
let buttonText = __( 'Save', 'google-site-kit' );
if ( haveSettingsChanged ) {
@@ -195,7 +174,7 @@ export default function Footer( props ) {
{
+ if ( ! isViewed && inView ) {
+ // Handle internal tracking.
+ trackEvent(
+ `${ viewContext }_kmw-settings-suggested-site-purpose-edit-notification`,
+ 'view_notification',
+ 'conversion_reporting'
+ );
+
+ setIsViewed( true );
+ }
+ }, [ isViewed, inView, viewContext ] );
const isDismissed = useSelect( ( select ) =>
select( CORE_USER ).isItemDismissed(
@@ -43,7 +72,13 @@ export default function KeyMetricsSettingsSellProductsSubtleNotification() {
const onDismiss = useCallback( async () => {
await dismissItem( USER_INPUT_LEGACY_SITE_PURPOSE_DISMISSED_ITEM_KEY );
- }, [ dismissItem ] );
+ // Handle internal tracking.
+ trackEvent(
+ `${ viewContext }_kmw-settings-suggested-site-purpose-edit-notification`,
+ 'confirm_notification',
+ 'conversion_reporting'
+ );
+ }, [ dismissItem, viewContext ] );
if ( isDismissed ) {
return null;
@@ -51,6 +86,7 @@ export default function KeyMetricsSettingsSellProductsSubtleNotification() {
return (
- select( CORE_USER ).getUserPickedMetrics()
- );
- const { saveUserInputSettings, resetKeyMetricsSelection } =
- useDispatch( CORE_USER );
+ const { saveUserInputSettings } = useDispatch( CORE_USER );
const { navigateTo } = useDispatch( CORE_LOCATION );
const dashboardURL = useSelect( ( select ) =>
@@ -191,9 +187,6 @@ export default function UserInputQuestionnaire() {
const response = await saveUserInputSettings();
if ( ! response.error ) {
- if ( !! userPickedMetrics ) {
- await resetKeyMetricsSelection();
- }
const url = new URL( dashboardURL );
navigateTo( url.toString() );
}
@@ -205,8 +198,6 @@ export default function UserInputQuestionnaire() {
setUserInputSetting,
navigateTo,
isConversionReportingEnabled,
- userPickedMetrics,
- resetKeyMetricsSelection,
] );
const settings = useSelect( ( select ) =>
diff --git a/assets/js/googlesitekit/api/middleware/preloading.js b/assets/js/googlesitekit/api/middleware/preloading.js
index 03105d25ff6..7c18e5acd92 100644
--- a/assets/js/googlesitekit/api/middleware/preloading.js
+++ b/assets/js/googlesitekit/api/middleware/preloading.js
@@ -45,7 +45,7 @@ function createPreloadingMiddleware( preloadedData ) {
}
setTimeout( () => {
cacheHasExpired = true;
- }, 1000 );
+ }, 3000 );
const { parse = true } = options;
const uri = options.path;
diff --git a/assets/js/googlesitekit/datastore/user/conversion-reporting-settings.js b/assets/js/googlesitekit/datastore/user/conversion-reporting-settings.js
new file mode 100644
index 00000000000..42568ce1932
--- /dev/null
+++ b/assets/js/googlesitekit/datastore/user/conversion-reporting-settings.js
@@ -0,0 +1,228 @@
+/**
+ * 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 invariant from 'invariant';
+import { isPlainObject } from 'lodash';
+
+/**
+ * Internal dependencies
+ */
+import API from 'googlesitekit-api';
+import {
+ createRegistrySelector,
+ commonActions,
+ combineStores,
+} from 'googlesitekit-data';
+import { CORE_USER } from './constants';
+import { createFetchStore } from '../../data/create-fetch-store';
+import { createValidatedAction } from '../../data/utils';
+
+const baseInitialState = {
+ conversionReportingSettings: undefined,
+};
+
+const fetchGetConversionReportingSettingsStore = createFetchStore( {
+ baseName: 'getConversionReportingSettings',
+ controlCallback: () =>
+ API.get( 'core', 'user', 'conversion-reporting-settings', undefined, {
+ // Never cache conversion reporting settings requests, we want them to be
+ // up-to-date with what's in settings, and they don't
+ // make requests to Google APIs so it's not a slow request.
+ useCache: false,
+ } ),
+ reducerCallback: ( state, conversionReportingSettings ) => ( {
+ ...state,
+ conversionReportingSettings,
+ } ),
+} );
+
+const fetchSaveConversionReportingSettingsStore = createFetchStore( {
+ baseName: 'saveConversionReportingSettings',
+ controlCallback: ( settings ) =>
+ API.set( 'core', 'user', 'conversion-reporting-settings', {
+ settings,
+ } ),
+ reducerCallback: ( state, conversionReportingSettings ) => ( {
+ ...state,
+ conversionReportingSettings,
+ } ),
+ argsToParams: ( settings ) => settings,
+ validateParams: ( settings ) => {
+ invariant(
+ isPlainObject( settings ),
+ 'Conversion reporting settings should be an object.'
+ );
+ if ( settings.newEventsCalloutDismissedAt ) {
+ invariant(
+ Number.isInteger( settings.newEventsCalloutDismissedAt ),
+ 'newEventsCalloutDismissedAt should be a timestamp.'
+ );
+ }
+ if ( settings.lostEventsCalloutDismissedAt ) {
+ invariant(
+ Number.isInteger( settings.lostEventsCalloutDismissedAt ),
+ 'lostEventsCalloutDismissedAt should be an integer.'
+ );
+ }
+ },
+} );
+
+const baseActions = {
+ /**
+ * Saves the conversion reporting settings.
+ *
+ * @since n.e.x.t
+ *
+ * @param {Object} settings Optional. By default, this saves whatever there is in the store. Use this object to save additional settings.
+ * @return {Object} Object with `response` and `error`.
+ */
+ saveConversionReportingSettings: createValidatedAction(
+ ( settings = {} ) => {
+ invariant(
+ isPlainObject( settings ),
+ 'Conversion reporting settings should be an object to save.'
+ );
+ },
+ function* ( settings = {} ) {
+ return yield fetchSaveConversionReportingSettingsStore.actions.fetchSaveConversionReportingSettings(
+ settings
+ );
+ }
+ ),
+};
+
+const baseResolvers = {
+ *getConversionReportingSettings() {
+ const registry = yield commonActions.getRegistry();
+
+ const conversionReportingSettings = registry
+ .select( CORE_USER )
+ .getConversionReportingSettings();
+
+ if ( conversionReportingSettings === undefined ) {
+ yield fetchGetConversionReportingSettingsStore.actions.fetchGetConversionReportingSettings();
+ }
+ },
+};
+
+const baseSelectors = {
+ /**
+ * Gets the conversion reporting settings.
+ *
+ * @since n.e.x.t
+ *
+ * @param {Object} state Data store's state.
+ * @return {(Object|undefined)} Conversion reporting settings; `undefined` if not loaded.
+ */
+ getConversionReportingSettings( state ) {
+ return state.conversionReportingSettings;
+ },
+
+ /**
+ * Determines whether the conversion reporting settings are being saved or not.
+ *
+ * @since n.e.x.t
+ *
+ * @param {Object} state Data store's state.
+ * @return {boolean} TRUE if the key metrics settings are being saved, otherwise FALSE.
+ */
+ isSavingConversionReportingSettings( state ) {
+ // Since isFetchingSaveConversionReportingSettings holds information based on specific values but we only need
+ // generic information here, we need to check whether ANY such request is in progress.
+ return Object.values(
+ state.isFetchingSaveConversionReportingSettings
+ ).some( Boolean );
+ },
+
+ /**
+ * Determines whether the new events callout should be shown or not.
+ *
+ * @since n.e.x.t
+ *
+ * @return {boolean} TRUE if the there were new events detected after the callout was dismissed, otherwise FALSE.
+ */
+ haveNewConversionEventsAfterDismiss: createRegistrySelector(
+ ( select ) => ( state, newEventsLastSyncedAt ) => {
+ const { getConversionReportingSettings } = select( CORE_USER );
+ const conversionReportingSettings =
+ getConversionReportingSettings();
+
+ if ( ! conversionReportingSettings ) {
+ return false;
+ }
+
+ if (
+ newEventsLastSyncedAt >
+ conversionReportingSettings.newEventsCalloutDismissedAt
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+
+ /**
+ * Determines whether the lost events callout should be shown or not.
+ *
+ * @since n.e.x.t
+ *
+ * @return {boolean} TRUE if the there were lost events detected after the callout was dismissed, otherwise FALSE.
+ */
+ haveLostConversionEventsAfterDismiss: createRegistrySelector(
+ ( select ) => ( state, lostEventsLastSyncedAt ) => {
+ const { getConversionReportingSettings } = select( CORE_USER );
+ const conversionReportingSettings =
+ getConversionReportingSettings();
+
+ if ( ! conversionReportingSettings ) {
+ return false;
+ }
+
+ if (
+ lostEventsLastSyncedAt >
+ conversionReportingSettings.lostEventsCalloutDismissedAt
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+};
+
+const store = combineStores(
+ fetchGetConversionReportingSettingsStore,
+ fetchSaveConversionReportingSettingsStore,
+ {
+ initialState: baseInitialState,
+ actions: baseActions,
+ resolvers: baseResolvers,
+ selectors: baseSelectors,
+ }
+);
+
+export const initialState = store.initialState;
+export const actions = store.actions;
+export const controls = store.controls;
+export const reducer = store.reducer;
+export const resolvers = store.resolvers;
+export const selectors = store.selectors;
+
+export default store;
diff --git a/assets/js/googlesitekit/datastore/user/conversion-reporting-settings.test.js b/assets/js/googlesitekit/datastore/user/conversion-reporting-settings.test.js
new file mode 100644
index 00000000000..92b2fdc031e
--- /dev/null
+++ b/assets/js/googlesitekit/datastore/user/conversion-reporting-settings.test.js
@@ -0,0 +1,271 @@
+/**
+ * `modules/analytics-4` data store: conversion reporting settings 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 API from 'googlesitekit-api';
+import { CORE_USER } from './constants';
+import {
+ createTestRegistry,
+ freezeFetch,
+ muteFetch,
+ provideModules,
+ untilResolved,
+ waitForDefaultTimeouts,
+} from '../../../../../tests/js/utils';
+
+describe( 'core/user conversion reporting settings', () => {
+ let registry;
+ let store;
+
+ const conversionReportingSettingsEndpoint = new RegExp(
+ '^/google-site-kit/v1/core/user/data/conversion-reporting-settings'
+ );
+
+ let conversionReportingSettingsResponse;
+
+ beforeAll( () => {
+ API.setUsingCache( false );
+ } );
+
+ beforeEach( () => {
+ registry = createTestRegistry();
+ provideModules( registry );
+ store = registry.stores[ CORE_USER ].store;
+
+ conversionReportingSettingsResponse = {
+ newEventsCalloutDismissedAt: 0,
+ lostEventsCalloutDismissedAt: 0,
+ };
+ } );
+
+ afterAll( () => {
+ API.setUsingCache( true );
+ } );
+
+ describe( 'actions', () => {
+ describe( 'saveConversionReportingSettings', () => {
+ it( 'should save settings and add it to the store', async () => {
+ const newEventsCalloutDismissedAt = 1734470013;
+
+ fetchMock.postOnce( conversionReportingSettingsEndpoint, {
+ body: {
+ ...conversionReportingSettingsResponse,
+ newEventsCalloutDismissedAt,
+ },
+ } );
+
+ await registry
+ .dispatch( CORE_USER )
+ .saveConversionReportingSettings( {
+ newEventsCalloutDismissedAt,
+ } );
+
+ // Ensure the proper body parameters were sent.
+ expect( fetchMock ).toHaveFetched(
+ conversionReportingSettingsEndpoint,
+ {
+ body: {
+ data: {
+ settings: { newEventsCalloutDismissedAt },
+ },
+ },
+ }
+ );
+
+ const conversionReportingSettings =
+ store.getState().conversionReportingSettings;
+
+ expect( conversionReportingSettings ).toEqual( {
+ ...conversionReportingSettingsResponse,
+ newEventsCalloutDismissedAt,
+ } );
+ expect( fetchMock ).toHaveFetchedTimes( 1 );
+ } );
+
+ it( 'dispatches an error if the request fails', async () => {
+ const response = {
+ code: 'internal_server_error',
+ message: 'Internal server error',
+ data: { status: 500 },
+ };
+
+ fetchMock.post( conversionReportingSettingsEndpoint, {
+ body: response,
+ status: 500,
+ } );
+
+ const settingsPartial = {
+ newEventsCalloutDismissedAt: 1734466109,
+ };
+
+ await registry
+ .dispatch( CORE_USER )
+ .saveConversionReportingSettings( settingsPartial );
+
+ expect(
+ registry
+ .select( CORE_USER )
+ .getErrorForAction( 'saveConversionReportingSettings', [
+ settingsPartial,
+ ] )
+ ).toMatchObject( response );
+
+ expect( console ).toHaveErrored();
+ } );
+ } );
+ } );
+
+ describe( 'selectors', () => {
+ describe( 'getConversionReportingSettings', () => {
+ it( 'should return undefined while conversion reporting settings are loading', async () => {
+ freezeFetch( conversionReportingSettingsEndpoint );
+
+ expect(
+ registry
+ .select( CORE_USER )
+ .getConversionReportingSettings()
+ ).toBeUndefined();
+
+ await waitForDefaultTimeouts();
+ } );
+
+ it( 'should not make a network request if conversion reporting settings exist', async () => {
+ registry
+ .dispatch( CORE_USER )
+ .receiveGetConversionReportingSettings(
+ conversionReportingSettingsResponse
+ );
+
+ registry.select( CORE_USER ).getConversionReportingSettings();
+
+ await untilResolved(
+ registry,
+ CORE_USER
+ ).getConversionReportingSettings();
+
+ expect( fetchMock ).not.toHaveFetched(
+ conversionReportingSettingsEndpoint
+ );
+ } );
+
+ it( 'should use a resolver to make a network request if data is not available', async () => {
+ fetchMock.getOnce( conversionReportingSettingsEndpoint, {
+ body: conversionReportingSettingsResponse,
+ status: 200,
+ } );
+
+ registry.select( CORE_USER ).getConversionReportingSettings();
+
+ await untilResolved(
+ registry,
+ CORE_USER
+ ).getConversionReportingSettings();
+
+ expect( fetchMock ).toHaveFetched(
+ conversionReportingSettingsEndpoint,
+ {
+ body: {
+ settings: conversionReportingSettingsResponse,
+ },
+ }
+ );
+
+ expect(
+ registry
+ .select( CORE_USER )
+ .getConversionReportingSettings()
+ ).toMatchObject( conversionReportingSettingsResponse );
+
+ expect( fetchMock ).toHaveFetchedTimes( 1 );
+ } );
+ } );
+
+ describe( 'isSavingConversionReportingSettings', () => {
+ it( 'should return false if conversion reporting settings are not being saved', () => {
+ expect(
+ registry
+ .select( CORE_USER )
+ .isSavingConversionReportingSettings()
+ ).toBe( false );
+ } );
+
+ it( 'should return true if conversion reporting settings are being saved', async () => {
+ muteFetch( conversionReportingSettingsEndpoint );
+
+ const promise = registry
+ .dispatch( CORE_USER )
+ .fetchSaveConversionReportingSettings(
+ conversionReportingSettingsResponse
+ );
+
+ expect(
+ registry
+ .select( CORE_USER )
+ .isSavingConversionReportingSettings()
+ ).toBe( true );
+
+ await promise;
+
+ expect(
+ registry
+ .select( CORE_USER )
+ .isSavingConversionReportingSettings()
+ ).toBe( false );
+ } );
+ } );
+
+ describe.each( [
+ [ 'haveNewConversionEventsAfterDismiss' ],
+ [ 'haveLostConversionEventsAfterDismiss' ],
+ ] )( '%s', ( selector ) => {
+ it.each( [
+ [
+ true,
+ 'after the callout was dismissed',
+ 1734512930,
+ 1734253730,
+ ],
+ [ true, 'and the callout was never dismissed', 1734512930, 0 ],
+ [
+ false,
+ 'before the callout was dismissed',
+ 1734253730,
+ 1734512930,
+ ],
+ ] )(
+ 'should return %s when most recent new events sync happened %s',
+ ( expected, _, eventsLastSyncedAt, calloutDismissedAt ) => {
+ registry
+ .dispatch( CORE_USER )
+ .receiveGetConversionReportingSettings( {
+ newEventsCalloutDismissedAt: calloutDismissedAt,
+ lostEventsCalloutDismissedAt: calloutDismissedAt,
+ } );
+
+ const result = registry
+ .select( CORE_USER )
+ [ selector ]( eventsLastSyncedAt );
+
+ expect( result ).toBe( expected );
+ }
+ );
+ } );
+ } );
+} );
diff --git a/assets/js/googlesitekit/datastore/user/index.js b/assets/js/googlesitekit/datastore/user/index.js
index 931b696e509..0708d694b49 100644
--- a/assets/js/googlesitekit/datastore/user/index.js
+++ b/assets/js/googlesitekit/datastore/user/index.js
@@ -39,6 +39,7 @@ import surveys from './surveys';
import tracking from './tracking';
import userInfo from './user-info';
import userInputSettings from './user-input-settings';
+import conversionReportingSettings from './conversion-reporting-settings';
const store = combineStores(
commonStore,
@@ -59,7 +60,8 @@ const store = combineStores(
surveys,
tracking,
userInfo,
- userInputSettings
+ userInputSettings,
+ conversionReportingSettings
);
export const {
diff --git a/assets/js/googlesitekit/datastore/user/key-metrics.js b/assets/js/googlesitekit/datastore/user/key-metrics.js
index 3b24e8c6244..febc00df131 100644
--- a/assets/js/googlesitekit/datastore/user/key-metrics.js
+++ b/assets/js/googlesitekit/datastore/user/key-metrics.js
@@ -104,16 +104,6 @@ const fetchSaveKeyMetricsSettingsStore = createFetchStore( {
},
} );
-const fetchResetKeyMetricsSelectionStore = createFetchStore( {
- baseName: 'resetKeyMetricsSelection',
- controlCallback: () =>
- API.set( 'core', 'user', 'reset-key-metrics-selection' ),
- reducerCallback: ( state, keyMetricsSettings ) => ( {
- ...state,
- keyMetricsSettings,
- } ),
-} );
-
const baseActions = {
/**
* Sets key metrics setting.
@@ -179,33 +169,6 @@ const baseActions = {
return { response, error };
},
-
- /**
- * Resets key metrics selecton.
- *
- * @since 1.141.0
- *
- * @param {Object} settings Optional. By default, this saves whatever there is in the store. Use this object to save additional settings.
- * @return {Object} Object with `response` and `error`.
- */
- *resetKeyMetricsSelection( settings = {} ) {
- invariant(
- isPlainObject( settings ),
- 'key metric settings should be an object to save.'
- );
-
- yield clearError( 'resetKeyMetricsSelection', [] );
-
- const { response, error } =
- yield fetchResetKeyMetricsSelectionStore.actions.fetchResetKeyMetricsSelection();
-
- if ( error ) {
- // Store error manually since resetKeyMetricsSelection signature differs from fetchResetKeyMetricsSelectionStore.
- yield receiveError( error, 'resetKeyMetricsSelection', [] );
- }
-
- return { response, error };
- },
};
const baseControls = {};
@@ -722,7 +685,6 @@ const baseSelectors = {
const store = combineStores(
fetchGetKeyMetricsSettingsStore,
fetchSaveKeyMetricsSettingsStore,
- fetchResetKeyMetricsSelectionStore,
{
initialState: baseInitialState,
actions: baseActions,
diff --git a/assets/js/googlesitekit/datastore/user/key-metrics.test.js b/assets/js/googlesitekit/datastore/user/key-metrics.test.js
index 5d2d0e94280..af2642be4e2 100644
--- a/assets/js/googlesitekit/datastore/user/key-metrics.test.js
+++ b/assets/js/googlesitekit/datastore/user/key-metrics.test.js
@@ -91,14 +91,6 @@ describe( 'core/user key metrics', () => {
},
};
- const coreKeyMetricsResetMetricSelectionEndpointRegExp = new RegExp(
- '^/google-site-kit/v1/core/user/data/reset-key-metrics-selection'
- );
- const coreKeyMetricsResetMetricSelectionExpectedResponse = {
- widgetSlugs: [],
- isWidgetHidden: false,
- };
-
beforeAll( () => {
API.setUsingCache( false );
} );
@@ -824,53 +816,6 @@ describe( 'core/user key metrics', () => {
expect( console ).not.toHaveErrored();
} );
} );
-
- describe( 'resetKeyMetricsSelection', () => {
- it( 'should clear widgetSlugs on resetKeyMetricsSelection', async () => {
- const userID = 123;
- provideUserInfo( registry, { id: userID } );
- provideUserAuthentication( registry );
-
- fetchMock.postOnce( coreKeyMetricsEndpointRegExp, {
- body: coreKeyMetricsExpectedResponse,
- status: 200,
- } );
-
- fetchMock.postOnce(
- coreKeyMetricsResetMetricSelectionEndpointRegExp,
- {
- body: coreKeyMetricsResetMetricSelectionExpectedResponse,
- status: 200,
- }
- );
-
- await registry
- .dispatch( CORE_USER )
- .receiveGetKeyMetricsSettings( {
- widgetSlugs: [
- KM_ANALYTICS_NEW_VISITORS,
- KM_ANALYTICS_PAGES_PER_VISIT,
- ],
- isWidgetHidden: false,
- } );
-
- expect( store.getState().keyMetricsSettings ).toMatchObject( {
- widgetSlugs: [
- KM_ANALYTICS_NEW_VISITORS,
- KM_ANALYTICS_PAGES_PER_VISIT,
- ],
- isWidgetHidden: false,
- } );
-
- await registry.dispatch( CORE_USER ).resetKeyMetricsSelection();
-
- expect( store.getState().keyMetricsSettings ).toMatchObject( {
- widgetSlugs: [],
- } );
-
- expect( console ).not.toHaveErrored();
- } );
- } );
} );
describe( 'selectors', () => {
diff --git a/assets/js/googlesitekit/modules/datastore/__fixtures__/list.json b/assets/js/googlesitekit/modules/datastore/__fixtures__/list.json
index 76c144df16c..f773b636ed7 100644
--- a/assets/js/googlesitekit/modules/datastore/__fixtures__/list.json
+++ b/assets/js/googlesitekit/modules/datastore/__fixtures__/list.json
@@ -37,7 +37,7 @@
"description": "Track conversions for your existing Google Ads campaigns",
"homepage": "https://google.com/ads",
"internal": false,
- "order": 1,
+ "order": 10,
"forceActive": false,
"recoverable": false,
"shareable": false,
@@ -53,7 +53,7 @@
"description": "Earn money by placing ads on your website. It’s free and easy.",
"homepage": "https://adsense.google.com/start?source=site-kit&url=https://example.com",
"internal": false,
- "order": 2,
+ "order": 10,
"forceActive": false,
"recoverable": false,
"shareable": false,
@@ -69,7 +69,7 @@
"description": "Get a deeper understanding of your customers. Google Analytics gives you the free tools you need to analyze data for your business in one place.",
"homepage": "https://analytics.google.com/analytics/web",
"internal": false,
- "order": 3,
+ "order": 10,
"forceActive": false,
"recoverable": false,
"shareable": false,
@@ -85,7 +85,7 @@
"description": "Google PageSpeed Insights gives you metrics about performance, accessibility, SEO and PWA",
"homepage": "https://pagespeed.web.dev",
"internal": false,
- "order": 4,
+ "order": 10,
"forceActive": false,
"recoverable": false,
"shareable": true,
@@ -101,7 +101,7 @@
"description": "Reader Revenue Manager helps publishers grow, retain, and engage their audiences, creating new revenue opportunities",
"homepage": "https://readerrevenue.withgoogle.com/",
"internal": false,
- "order": 5,
+ "order": 10,
"forceActive": false,
"recoverable": false,
"shareable": false,
@@ -117,7 +117,7 @@
"description": "Tag Manager creates an easy to manage way to create tags on your site without updating code",
"homepage": "https://tagmanager.google.com/",
"internal": false,
- "order": 6,
+ "order": 10,
"forceActive": false,
"recoverable": false,
"shareable": false,
diff --git a/assets/js/googlesitekit/modules/datastore/modules.js b/assets/js/googlesitekit/modules/datastore/modules.js
index d65d059d38e..dc73df01675 100644
--- a/assets/js/googlesitekit/modules/datastore/modules.js
+++ b/assets/js/googlesitekit/modules/datastore/modules.js
@@ -99,7 +99,9 @@ const normalizeModules = memize( ( serverDefinitions, clientDefinitions ) => {
return module;
} )
- .sort( ( a, b ) => a.order - b.order )
+ .sort(
+ ( a, b ) => a.order - b.order || a.name?.localeCompare( b.name )
+ )
.reduce( ( acc, module ) => {
return { ...acc, [ module.slug ]: module };
}, {} );
diff --git a/assets/js/googlesitekit/modules/datastore/settings.js b/assets/js/googlesitekit/modules/datastore/settings.js
index 5ae428e2192..b4a5303295c 100644
--- a/assets/js/googlesitekit/modules/datastore/settings.js
+++ b/assets/js/googlesitekit/modules/datastore/settings.js
@@ -123,6 +123,33 @@ export const controls = {
};
export const selectors = {
+ /**
+ * Checks whether settings edit dependencies are currently loading for a module.
+ *
+ * @since n.e.x.t
+ *
+ * @param {string} slug Module slug.
+ * @return {boolean?} Whether or not settings edit dependencies are currently loading for the module,
+ * or `undefined` if the store doesn't exist.
+ */
+ areSettingsEditDependenciesLoaded: createRegistrySelector(
+ ( select ) => ( state, slug ) => {
+ invariant( slug, 'slug is required.' );
+ const storeName = select( CORE_MODULES ).getModuleStoreName( slug );
+ const moduleSelectors = select( storeName );
+ if ( ! moduleSelectors ) {
+ return undefined;
+ }
+
+ return (
+ // If the module doesn't implement the selector, consider dependencies loaded,
+ ! moduleSelectors.areSettingsEditDependenciesLoaded ||
+ // otherwise defer to the result of the selector.
+ !! moduleSelectors.areSettingsEditDependenciesLoaded()
+ );
+ }
+ ),
+
/**
* Checks whether changes are currently being submitted for a module.
*
diff --git a/assets/js/googlesitekit/modules/datastore/settings.test.js b/assets/js/googlesitekit/modules/datastore/settings.test.js
index e00c0b023f2..9c9f35ba94a 100644
--- a/assets/js/googlesitekit/modules/datastore/settings.test.js
+++ b/assets/js/googlesitekit/modules/datastore/settings.test.js
@@ -20,6 +20,7 @@
* Internal dependencies
*/
import Modules from 'googlesitekit-modules';
+import { combineStores } from 'googlesitekit-data';
import { CORE_MODULES } from './constants';
import {
createTestRegistry,
@@ -29,6 +30,7 @@ import {
describe( 'core/modules settings', () => {
let registry;
+ let areSettingsEditDependenciesLoaded;
let submitChanges;
const slug = 'test-module';
const nonExistentModuleSlug = 'not-module';
@@ -37,22 +39,28 @@ describe( 'core/modules settings', () => {
let validateCanSubmitChangesError = false;
beforeEach( () => {
+ areSettingsEditDependenciesLoaded = jest.fn();
submitChanges = jest.fn();
registry = createTestRegistry();
registry.registerStore(
moduleStoreName,
- Modules.createModuleStore( slug, {
- storeName: moduleStoreName,
- submitChanges,
- validateCanSubmitChanges: () => {
- if ( validateCanSubmitChangesError ) {
- throw new Error( validateCanSubmitChangesError );
- }
- },
- settingSlugs: [ 'testSetting' ],
- } )
+ combineStores(
+ Modules.createModuleStore( slug, {
+ storeName: moduleStoreName,
+ submitChanges,
+ validateCanSubmitChanges: () => {
+ if ( validateCanSubmitChangesError ) {
+ throw new Error( validateCanSubmitChangesError );
+ }
+ },
+ settingSlugs: [ 'testSetting' ],
+ } ),
+ {
+ selectors: { areSettingsEditDependenciesLoaded },
+ }
+ )
);
registry
@@ -64,7 +72,7 @@ describe( 'core/modules settings', () => {
describe( 'actions', () => {
describe( 'submitChanges', () => {
- it( 'should return an error if a module doesnt exist', async () => {
+ it( "should return an error if a module doesn't exist", async () => {
const expectedError = {
error: `The module '${ nonExistentModuleSlug }' does not have a store.`,
};
@@ -93,8 +101,66 @@ describe( 'core/modules settings', () => {
} );
describe( 'selectors', () => {
+ describe( 'areSettingsEditDependenciesLoaded', () => {
+ it( 'should return undefined if the module store has not been registered', () => {
+ expect(
+ registry
+ .select( CORE_MODULES )
+ .areSettingsEditDependenciesLoaded(
+ nonExistentModuleSlug
+ )
+ ).toBe( undefined );
+ } );
+
+ it( 'should return true for module which does not implement areSettingsEditDependenciesLoaded', () => {
+ const notImplementedSlug = 'not-implemented-module';
+ const notImplementedModuleStoreName = `test/${ notImplementedSlug }`;
+
+ registry.registerStore(
+ notImplementedModuleStoreName,
+ Modules.createModuleStore( notImplementedSlug, {
+ storeName: notImplementedModuleStoreName,
+ } )
+ );
+
+ registry
+ .dispatch( CORE_MODULES )
+ .registerModule( notImplementedSlug, {
+ storeName: notImplementedModuleStoreName,
+ } );
+
+ provideModules( registry );
+
+ expect(
+ registry
+ .select( CORE_MODULES )
+ .areSettingsEditDependenciesLoaded( notImplementedSlug )
+ ).toBe( true );
+ } );
+
+ it( 'should proxy the selector call to the module with the given slug', () => {
+ areSettingsEditDependenciesLoaded.mockImplementation(
+ () => false
+ );
+ expect(
+ registry
+ .select( CORE_MODULES )
+ .areSettingsEditDependenciesLoaded( slug )
+ ).toBe( false );
+
+ areSettingsEditDependenciesLoaded.mockImplementation(
+ () => true
+ );
+ expect(
+ registry
+ .select( CORE_MODULES )
+ .areSettingsEditDependenciesLoaded( slug )
+ ).toBe( true );
+ } );
+ } );
+
describe( 'isDoingSubmitChanges', () => {
- it( 'should return FALSE for non existing module', () => {
+ it( 'should return FALSE for non existent module', () => {
expect(
registry
.select( CORE_MODULES )
diff --git a/assets/js/googlesitekit/notifications/components/layout/SubtleNotification.js b/assets/js/googlesitekit/notifications/components/layout/SubtleNotification.js
index d8fa827c76b..f97a1943ca6 100644
--- a/assets/js/googlesitekit/notifications/components/layout/SubtleNotification.js
+++ b/assets/js/googlesitekit/notifications/components/layout/SubtleNotification.js
@@ -22,6 +22,11 @@
import PropTypes from 'prop-types';
import classnames from 'classnames';
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
/**
* Internal dependencies
*/
@@ -29,58 +34,63 @@ import CheckFill from '../../../../../svg/icons/check-fill.svg';
import WarningSVG from '../../../../../svg/icons/warning.svg';
import { Grid, Cell, Row } from '../../../../material-components';
-export default function SubtleNotification( {
- className,
- title,
- description,
- dismissCTA,
- additionalCTA,
- type = 'success',
- icon,
-} ) {
- return (
-
-
-
-
- { icon }
- { type === 'success' && ! icon && (
-
+const SubtleNotification = forwardRef(
+ (
+ {
+ className,
+ title,
+ description,
+ dismissCTA,
+ additionalCTA,
+ type = 'success',
+ icon,
+ },
+ ref
+ ) => {
+ return (
+
+
+ |
- ) }
-
+ >
+
+ { icon }
+ { type === 'success' && ! icon && (
+
+ ) }
+ { type === 'warning' && ! icon && (
+
+ ) }
+
-
- { title }
-
- { description }
-
-
-
- { dismissCTA }
+
+ { title }
+
+ { description }
+
+
+
+ { dismissCTA }
- { additionalCTA }
-
-
-
-
- );
-}
+ { additionalCTA }
+
+ |
+
+
+ );
+ }
+);
SubtleNotification.propTypes = {
className: PropTypes.string,
@@ -91,3 +101,5 @@ SubtleNotification.propTypes = {
type: PropTypes.string,
icon: PropTypes.object,
};
+
+export default SubtleNotification;
diff --git a/assets/js/googlesitekit/notifications/constants.js b/assets/js/googlesitekit/notifications/constants.js
index f7fed08ea66..b7fa4d5a440 100644
--- a/assets/js/googlesitekit/notifications/constants.js
+++ b/assets/js/googlesitekit/notifications/constants.js
@@ -16,5 +16,7 @@
* limitations under the License.
*/
-export const FPM_SETUP_CTA_BANNER_NOTIFICATION =
- 'first-party-mode-setup-cta-banner';
+export const FPM_HEALTH_CHECK_WARNING_NOTIFICATION_ID =
+ 'warning-notification-fpm';
+
+export const FPM_SETUP_CTA_BANNER_NOTIFICATION = 'fpm-setup-cta';
diff --git a/assets/js/googlesitekit/notifications/datastore/constants.js b/assets/js/googlesitekit/notifications/datastore/constants.js
index c8fff22ed85..a60ab84f9a3 100644
--- a/assets/js/googlesitekit/notifications/datastore/constants.js
+++ b/assets/js/googlesitekit/notifications/datastore/constants.js
@@ -42,6 +42,3 @@ export const NOTIFICATION_VIEW_CONTEXTS = [
VIEW_CONTEXT_MAIN_DASHBOARD_VIEW_ONLY,
VIEW_CONTEXT_ENTITY_DASHBOARD_VIEW_ONLY,
];
-
-export const FPM_HEALTH_CHECK_WARNING_NOTIFICATION_ID =
- 'fpm-warning-notification';
diff --git a/assets/js/googlesitekit/notifications/register-defaults.js b/assets/js/googlesitekit/notifications/register-defaults.js
index 3732408482a..6d8beb20da1 100644
--- a/assets/js/googlesitekit/notifications/register-defaults.js
+++ b/assets/js/googlesitekit/notifications/register-defaults.js
@@ -29,8 +29,11 @@ import {
CORE_NOTIFICATIONS,
NOTIFICATION_AREAS,
NOTIFICATION_GROUPS,
- FPM_HEALTH_CHECK_WARNING_NOTIFICATION_ID,
} from './datastore/constants';
+import {
+ FPM_HEALTH_CHECK_WARNING_NOTIFICATION_ID,
+ FPM_SETUP_CTA_BANNER_NOTIFICATION,
+} from './constants';
import { CORE_FORMS } from '../datastore/forms/constants';
import { CORE_SITE } from '../datastore/site/constants';
import {
@@ -58,7 +61,6 @@ import FirstPartyModeSetupBanner, {
FPM_SHOW_SETUP_SUCCESS_NOTIFICATION,
} from '../../components/notifications/FirstPartyModeSetupBanner';
import FirstPartyModeSetupSuccessSubtleNotification from '../../components/notifications/FirstPartyModeSetupSuccessSubtleNotification';
-import { FPM_SETUP_CTA_BANNER_NOTIFICATION } from './constants';
import { isFeatureEnabled } from '../../features';
export const DEFAULT_NOTIFICATIONS = {
@@ -237,30 +239,24 @@ export const DEFAULT_NOTIFICATIONS = {
VIEW_CONTEXT_ENTITY_DASHBOARD,
],
checkRequirements: async ( { select, resolveSelect, dispatch } ) => {
- await Promise.all( [
- // The getAdSenseLinked selector relies on the resolution
- // of the getSettings() resolver.
- resolveSelect( MODULES_ANALYTICS_4 ).getSettings(),
- // The isModuleConnected() selector relies on the resolution
- // of the getModules() resolver.
- resolveSelect( CORE_MODULES ).getModules(),
- ] );
+ const adSenseModuleConnected = await resolveSelect(
+ CORE_MODULES
+ ).isModuleConnected( 'adsense' );
- const adSenseModuleConnected =
- select( CORE_MODULES ).isModuleConnected( 'adsense' );
+ const analyticsModuleConnected = await resolveSelect(
+ CORE_MODULES
+ ).isModuleConnected( 'analytics-4' );
- const analyticsModuleConnected =
- select( CORE_MODULES ).isModuleConnected( 'analytics-4' );
+ if ( ! ( adSenseModuleConnected && analyticsModuleConnected ) ) {
+ return false;
+ }
+
+ await resolveSelect( MODULES_ANALYTICS_4 ).getSettings();
const isAdSenseLinked =
select( MODULES_ANALYTICS_4 ).getAdSenseLinked();
- const analyticsAndAdsenseConnectedAndLinked =
- adSenseModuleConnected &&
- analyticsModuleConnected &&
- isAdSenseLinked;
-
- if ( ! analyticsAndAdsenseConnectedAndLinked ) {
+ if ( ! isAdSenseLinked ) {
return false;
}
@@ -297,10 +293,7 @@ export const DEFAULT_NOTIFICATIONS = {
// we show them a different notification and should not show this one. Check
// to see if the user already has data and dismiss this notification without
// showing it.
- if (
- isZeroReport( report ) === false &&
- analyticsAndAdsenseConnectedAndLinked
- ) {
+ if ( isZeroReport( report ) === false ) {
await dispatch( CORE_NOTIFICATIONS ).dismissNotification(
'top-earning-pages-success-notification'
);
diff --git a/assets/js/modules/ads/pax/services.js b/assets/js/modules/ads/pax/services.js
index ed5f24dd70b..c58442fd7d9 100644
--- a/assets/js/modules/ads/pax/services.js
+++ b/assets/js/modules/ads/pax/services.js
@@ -167,8 +167,7 @@ export function createPaxServices( registry, options = {} ) {
getSupportedConversionTrackingTypes: async () => {
return {
conversionTrackingTypes: [
- // @TODO: Include TYPE_CONVERSION_EVENT in a future update.
- // 'TYPE_CONVERSION_EVENT',
+ 'TYPE_CONVERSION_EVENT',
'TYPE_PAGE_VIEW',
],
};
diff --git a/assets/js/modules/ads/pax/services.test.js b/assets/js/modules/ads/pax/services.test.js
index 3f75b5973aa..52abf55a25e 100644
--- a/assets/js/modules/ads/pax/services.test.js
+++ b/assets/js/modules/ads/pax/services.test.js
@@ -301,7 +301,10 @@ describe( 'PAX partner services', () => {
);
expect( supportedTypes ).toMatchObject( {
- conversionTrackingTypes: [ 'TYPE_PAGE_VIEW' ],
+ conversionTrackingTypes: [
+ 'TYPE_CONVERSION_EVENT',
+ 'TYPE_PAGE_VIEW',
+ ],
} );
} );
} );
diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.js
index 6bf857e204c..868e908d536 100644
--- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.js
+++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.js
@@ -41,11 +41,12 @@ import { MODULES_ANALYTICS_4 } from '../../../datastore/constants';
import useDashboardType, {
DASHBOARD_TYPE_MAIN,
} from '../../../../../hooks/useDashboardType';
+import whenActive from '../../../../../util/when-active';
export const AUDIENCE_SEGMENTATION_INTRODUCTORY_OVERLAY_NOTIFICATION =
'audienceSegmentationIntroductoryOverlayNotification';
-export default function AudienceSegmentationIntroductoryOverlayNotification() {
+function AudienceSegmentationIntroductoryOverlayNotification() {
const viewContext = useViewContext();
const isViewOnly = useViewOnly();
const breakpoint = useBreakpoint();
@@ -178,3 +179,7 @@ export default function AudienceSegmentationIntroductoryOverlayNotification() {
);
}
+
+export default whenActive( { moduleName: 'analytics-4' } )(
+ AudienceSegmentationIntroductoryOverlayNotification
+);
diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.stories.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.stories.js
index a0c963c1b7e..29a2c813e3c 100644
--- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.stories.js
+++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.stories.js
@@ -51,6 +51,7 @@ export default {
{
slug: 'analytics-4',
active: true,
+ connected: true,
setupComplete: true,
},
] );
diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.test.js
index 1354f3e0297..3f7681d830e 100644
--- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.test.js
+++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationIntroductoryOverlayNotification.test.js
@@ -68,6 +68,7 @@ describe( 'AudienceSegmentationIntroductoryOverlayNotification', () => {
{
slug: 'analytics-4',
active: true,
+ connected: true,
setupComplete: true,
},
] );
diff --git a/assets/js/modules/analytics-4/components/setup/SetupEnhancedMeasurementSwitch.js b/assets/js/modules/analytics-4/components/setup/SetupEnhancedMeasurementSwitch.js
index 9a4631106b4..64fc2ff228c 100644
--- a/assets/js/modules/analytics-4/components/setup/SetupEnhancedMeasurementSwitch.js
+++ b/assets/js/modules/analytics-4/components/setup/SetupEnhancedMeasurementSwitch.js
@@ -15,10 +15,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
- * External dependencies
+ * WordPress dependencies
*/
-import { useMount } from 'react-use';
+import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
@@ -108,17 +109,25 @@ export default function SetupEnhancedMeasurementSwitch() {
);
} );
- const { setValues } = useDispatch( CORE_FORMS );
- const { getValue } = useSelect( ( select ) => select( CORE_FORMS ) );
+ const isAutoSubmit = useSelect( ( select ) =>
+ select( CORE_FORMS ).getValue( FORM_SETUP, 'autoSubmit' )
+ );
- useMount( () => {
- const autoSubmit = getValue( FORM_SETUP, 'autoSubmit' );
- if ( ! autoSubmit ) {
+ const isEnhancedMeasurementEnabled = useSelect( ( select ) =>
+ select( CORE_FORMS ).getValue(
+ ENHANCED_MEASUREMENT_FORM,
+ ENHANCED_MEASUREMENT_ENABLED
+ )
+ );
+
+ const { setValues } = useDispatch( CORE_FORMS );
+ useEffect( () => {
+ if ( ! isAutoSubmit && isEnhancedMeasurementEnabled === undefined ) {
setValues( ENHANCED_MEASUREMENT_FORM, {
[ ENHANCED_MEASUREMENT_ENABLED ]: true,
} );
}
- } );
+ }, [ isAutoSubmit, isEnhancedMeasurementEnabled, setValues ] );
if ( ! isValidAccountID( accountID ) ) {
return null;
diff --git a/assets/js/modules/analytics-4/datastore/base.js b/assets/js/modules/analytics-4/datastore/base.js
index 18472cd07b0..a0aa982c3d5 100644
--- a/assets/js/modules/analytics-4/datastore/base.js
+++ b/assets/js/modules/analytics-4/datastore/base.js
@@ -65,6 +65,8 @@ const baseModuleStore = Modules.createModuleStore( 'analytics-4', {
'availableAudiencesLastSyncedAt',
'audienceSegmentationSetupCompletedBy',
'detectedEvents',
+ 'newConversionEventsLastUpdateAt',
+ 'lostConversionEventsLastUpdateAt',
],
submitChanges,
rollbackChanges,
diff --git a/assets/js/modules/analytics-4/datastore/conversion-reporting.js b/assets/js/modules/analytics-4/datastore/conversion-reporting.js
index d8c9ee77ee6..cbbd1a353f8 100644
--- a/assets/js/modules/analytics-4/datastore/conversion-reporting.js
+++ b/assets/js/modules/analytics-4/datastore/conversion-reporting.js
@@ -25,10 +25,8 @@ import { isEqual } from 'lodash';
/**
* Internal dependencies
*/
-import API from 'googlesitekit-api';
import {
commonActions,
- combineStores,
createRegistrySelector,
createReducer,
} from 'googlesitekit-data';
@@ -49,7 +47,6 @@ import {
MODULES_ANALYTICS_4,
} from './constants';
import { USER_INPUT_PURPOSE_TO_CONVERSION_EVENTS_MAPPING } from '../../../components/user-input/util/constants';
-import { createFetchStore } from '../../../googlesitekit/data/create-fetch-store';
import { negateDefined } from '../../../util/negate';
import { safelySort } from '../../../util';
@@ -65,54 +62,6 @@ function hasConversionReportingEventsOfType( propName ) {
} );
}
-const dismissNewConversionReportingEventsStore = createFetchStore( {
- baseName: 'dismissNewConversionReportingEvents',
- controlCallback: () => {
- return API.set(
- 'modules',
- 'analytics-4',
- 'clear-conversion-reporting-new-events'
- );
- },
- reducerCallback: ( state, values ) => {
- if ( values === false ) {
- return state;
- }
-
- return {
- ...state,
- detectedEventsChange: {
- ...state.detectedEventsChange,
- newEvents: [],
- },
- };
- },
-} );
-
-const dismissLostConversionReportingEventsStore = createFetchStore( {
- baseName: 'dismissLostConversionReportingEvents',
- controlCallback: () => {
- return API.set(
- 'modules',
- 'analytics-4',
- 'clear-conversion-reporting-lost-events'
- );
- },
- reducerCallback: ( state, values ) => {
- if ( values === false ) {
- return state;
- }
-
- return {
- ...state,
- detectedEventsChange: {
- ...state.detectedEventsChange,
- lostEvents: [],
- },
- };
- },
-} );
-
// Actions.
const RECEIVE_CONVERSION_REPORTING_INLINE_DATA =
'RECEIVE_CONVERSION_REPORTING_INLINE_DATA';
@@ -150,28 +99,6 @@ export const resolvers = {
};
export const actions = {
- /**
- * Dismiss new conversion reporting events.
- *
- * @since 1.138.0
- *
- * @return {boolean} Transient deletion response.
- */
- dismissNewConversionReportingEvents() {
- return dismissNewConversionReportingEventsStore.actions.fetchDismissNewConversionReportingEvents();
- },
-
- /**
- * Dismiss lost conversion reporting events.
- *
- * @since 1.138.0
- *
- * @return {boolean} Transient deletion response.
- */
- dismissLostConversionReportingEvents() {
- return dismissLostConversionReportingEventsStore.actions.fetchDismissLostConversionReportingEvents();
- },
-
/**
* Stores conversion reporting inline data in the datastore.
*
@@ -554,14 +481,12 @@ export const selectors = {
),
};
-export default combineStores(
- dismissNewConversionReportingEventsStore,
- dismissLostConversionReportingEventsStore,
- {
- initialState,
- actions,
- resolvers,
- selectors,
- reducer,
- }
-);
+const store = {
+ initialState,
+ actions,
+ resolvers,
+ selectors,
+ reducer,
+};
+
+export default store;
diff --git a/assets/js/modules/analytics-4/datastore/conversion-reporting.test.js b/assets/js/modules/analytics-4/datastore/conversion-reporting.test.js
index 07846bc9842..e376a8b211d 100644
--- a/assets/js/modules/analytics-4/datastore/conversion-reporting.test.js
+++ b/assets/js/modules/analytics-4/datastore/conversion-reporting.test.js
@@ -80,51 +80,6 @@ describe( 'modules/analytics-4 conversion-reporting', () => {
);
} );
} );
- describe( 'dismissNewConversionReportingEvents', () => {
- it( 'fetches clear new events endpoint', async () => {
- fetchMock.postOnce(
- new RegExp(
- '^/google-site-kit/v1/modules/analytics-4/data/clear-conversion-reporting-new-events'
- ),
- true
- );
-
- const { response } = await registry
- .dispatch( MODULES_ANALYTICS_4 )
- .dismissNewConversionReportingEvents();
-
- expect( fetchMock ).toHaveFetchedTimes( 1 );
- expect( fetchMock ).toHaveFetched(
- new RegExp(
- '^/google-site-kit/v1/modules/analytics-4/data/clear-conversion-reporting-new-events'
- )
- );
- expect( response ).toEqual( true );
- } );
- } );
-
- describe( 'dismissLostConversionReportingEvents', () => {
- it( 'fetches clear lost events endpoint', async () => {
- fetchMock.postOnce(
- new RegExp(
- '^/google-site-kit/v1/modules/analytics-4/data/clear-conversion-reporting-lost-events'
- ),
- true
- );
-
- const { response } = await registry
- .dispatch( MODULES_ANALYTICS_4 )
- .dismissLostConversionReportingEvents();
-
- expect( fetchMock ).toHaveFetchedTimes( 1 );
- expect( fetchMock ).toHaveFetched(
- new RegExp(
- '^/google-site-kit/v1/modules/analytics-4/data/clear-conversion-reporting-lost-events'
- )
- );
- expect( response ).toEqual( true );
- } );
- } );
} );
describe( 'selectors', () => {
diff --git a/assets/js/modules/analytics-4/datastore/index.js b/assets/js/modules/analytics-4/datastore/index.js
index 2ed957d9a78..f95fd78875a 100644
--- a/assets/js/modules/analytics-4/datastore/index.js
+++ b/assets/js/modules/analytics-4/datastore/index.js
@@ -35,6 +35,7 @@ import properties from './properties';
import report from './report';
import pivotReport from './pivot-report';
import service from './service';
+import settings from './settings';
import tags from './tags';
import webdatastreams from './webdatastreams';
import { createSnapshotStore } from '../../../googlesitekit/data/create-snapshot-store';
@@ -51,9 +52,10 @@ const store = combineStores(
customDimensionsGatheringData,
enhancedMeasurement,
partialData,
+ pivotReport,
properties,
report,
- pivotReport,
+ settings,
service,
tags,
webdatastreams
diff --git a/assets/js/modules/analytics-4/datastore/settings.js b/assets/js/modules/analytics-4/datastore/settings.js
index e1986ac3833..0bc18dd8450 100644
--- a/assets/js/modules/analytics-4/datastore/settings.js
+++ b/assets/js/modules/analytics-4/datastore/settings.js
@@ -26,6 +26,7 @@ import { isEqual, pick } from 'lodash';
* Internal dependencies
*/
import API from 'googlesitekit-api';
+import { createRegistrySelector } from 'googlesitekit-data';
import { createStrictSelect } from '../../../googlesitekit/data/utils';
import {
isValidPropertyID,
@@ -67,6 +68,18 @@ export const INVARIANT_WEBDATASTREAM_ALREADY_EXISTS =
export const INVARIANT_INVALID_ADS_CONVERSION_ID =
'a valid ads adsConversionID is required to submit changes';
+const store = {
+ selectors: {
+ areSettingsEditDependenciesLoaded: createRegistrySelector(
+ ( select ) => () =>
+ select( MODULES_ANALYTICS_4 ).hasFinishedResolution(
+ 'getAccountSummaries'
+ )
+ ),
+ },
+};
+export default store;
+
export async function submitChanges( { select, dispatch } ) {
let propertyID = select( MODULES_ANALYTICS_4 ).getPropertyID();
if ( propertyID === PROPERTY_CREATE ) {
diff --git a/assets/js/modules/analytics-4/datastore/settings.test.js b/assets/js/modules/analytics-4/datastore/settings.test.js
index 6578325a45a..7a1d8d665a7 100644
--- a/assets/js/modules/analytics-4/datastore/settings.test.js
+++ b/assets/js/modules/analytics-4/datastore/settings.test.js
@@ -972,6 +972,32 @@ describe( 'modules/analytics-4 settings', () => {
} );
describe( 'selectors', () => {
+ describe( 'areSettingsEditDependenciesLoaded', () => {
+ it( 'should return false if getAccountSummaries selector has not resolved', () => {
+ registry
+ .dispatch( MODULES_ANALYTICS_4 )
+ .startResolution( 'getAccountSummaries', [] );
+
+ expect(
+ registry
+ .select( MODULES_ANALYTICS_4 )
+ .areSettingsEditDependenciesLoaded()
+ ).toBe( false );
+ } );
+
+ it( 'should return true if getAccountSummaries selector has resolved', () => {
+ registry
+ .dispatch( MODULES_ANALYTICS_4 )
+ .finishResolution( 'getAccountSummaries', [] );
+
+ expect(
+ registry
+ .select( MODULES_ANALYTICS_4 )
+ .areSettingsEditDependenciesLoaded()
+ ).toBe( true );
+ } );
+ } );
+
describe( 'canSubmitChanges', () => {
const propertyID = '1000';
const webDataStreamID = '2000';
diff --git a/assets/js/modules/analytics-4/index.js b/assets/js/modules/analytics-4/index.js
index 057a3a8954c..2a35f2a9637 100644
--- a/assets/js/modules/analytics-4/index.js
+++ b/assets/js/modules/analytics-4/index.js
@@ -695,6 +695,14 @@ export const registerNotifications = ( notifications ) => {
areaSlug: NOTIFICATION_AREAS.BANNERS_BELOW_NAV,
viewContexts: [ VIEW_CONTEXT_MAIN_DASHBOARD ],
checkRequirements: async ( { select, resolveSelect } ) => {
+ const analyticsConnected = await resolveSelect(
+ CORE_MODULES
+ ).isModuleConnected( 'analytics-4' );
+
+ if ( ! analyticsConnected ) {
+ return false;
+ }
+
await resolveSelect( MODULES_ANALYTICS_4 ).getSettings();
const configuredAudiences =
select( CORE_USER ).getConfiguredAudiences();
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 c415960ace9..60224b9bc68 100644
--- a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.js
+++ b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.js
@@ -40,16 +40,18 @@ import { CORE_UI } from '../../../../googlesitekit/datastore/ui/constants';
import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../googlesitekit/constants';
import {
MODULES_READER_REVENUE_MANAGER,
+ READER_REVENUE_MANAGER_MODULE_SLUG,
UI_KEY_READER_REVENUE_MANAGER_SHOW_PUBLICATION_APPROVED_NOTIFICATION,
PUBLICATION_ONBOARDING_STATES,
} from '../../datastore/constants';
+import whenActive from '../../../../util/when-active';
const { ONBOARDING_COMPLETE } = PUBLICATION_ONBOARDING_STATES;
export const RRM_PUBLICATION_APPROVED_OVERLAY_NOTIFICATION =
'rrmPublicationApprovedOverlayNotification';
-export default function PublicationApprovedOverlayNotification() {
+function PublicationApprovedOverlayNotification() {
const viewContext = useViewContext();
const isViewOnly = useViewOnly();
const dashboardType = useDashboardType();
@@ -205,3 +207,7 @@ export default function PublicationApprovedOverlayNotification() {
);
}
+
+export default whenActive( { moduleName: READER_REVENUE_MANAGER_MODULE_SLUG } )(
+ PublicationApprovedOverlayNotification
+);
diff --git a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.stories.js b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.stories.js
index 5bb4bedf353..f66a551ff4e 100644
--- a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.stories.js
+++ b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.stories.js
@@ -24,10 +24,12 @@ import WithRegistrySetup from '../../../../../../tests/js/WithRegistrySetup';
import { CORE_UI } from '../../../../googlesitekit/datastore/ui/constants';
import {
MODULES_READER_REVENUE_MANAGER,
+ READER_REVENUE_MANAGER_MODULE_SLUG,
UI_KEY_READER_REVENUE_MANAGER_SHOW_PUBLICATION_APPROVED_NOTIFICATION,
} from '../../datastore/constants';
import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../googlesitekit/constants';
import { Provider as ViewContextProvider } from '../../../../components/Root/ViewContextContext';
+import { provideModules } from '../../../../../../tests/js/utils';
function Template() {
return (
@@ -48,6 +50,14 @@ export default {
decorators: [
( Story, { args } ) => {
const setupRegistry = ( registry ) => {
+ provideModules( registry, [
+ {
+ slug: READER_REVENUE_MANAGER_MODULE_SLUG,
+ active: true,
+ connected: true,
+ },
+ ] );
+
registry
.dispatch( MODULES_READER_REVENUE_MANAGER )
.receiveGetSettings( {
diff --git a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.test.js b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.test.js
index 6900659660f..7891c477b34 100644
--- a/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.test.js
+++ b/assets/js/modules/reader-revenue-manager/components/dashboard/PublicationApprovedOverlayNotification.test.js
@@ -24,7 +24,10 @@ import fetchMock from 'fetch-mock';
/**
* Internal dependencies
*/
-import { createTestRegistry } from '../../../../../../tests/js/utils';
+import {
+ createTestRegistry,
+ provideModules,
+} from '../../../../../../tests/js/utils';
import { act, fireEvent, render } from '../../../../../../tests/js/test-utils';
import PublicationApprovedOverlayNotification, {
RRM_PUBLICATION_APPROVED_OVERLAY_NOTIFICATION,
@@ -38,6 +41,7 @@ import * as tracking from '../../../../util/tracking';
import { Provider as ViewContextProvider } from '../../../../components/Root/ViewContextContext';
import {
MODULES_READER_REVENUE_MANAGER,
+ READER_REVENUE_MANAGER_MODULE_SLUG,
UI_KEY_READER_REVENUE_MANAGER_SHOW_PUBLICATION_APPROVED_NOTIFICATION,
} from '../../datastore/constants';
import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants';
@@ -59,6 +63,15 @@ describe( 'PublicationApprovedOverlayNotification', () => {
beforeEach( () => {
mockTrackEvent.mockClear();
registry = createTestRegistry();
+
+ provideModules( registry, [
+ {
+ slug: READER_REVENUE_MANAGER_MODULE_SLUG,
+ active: true,
+ connected: true,
+ },
+ ] );
+
registry
.dispatch( CORE_UI )
.setValue(
diff --git a/assets/js/modules/reader-revenue-manager/index.js b/assets/js/modules/reader-revenue-manager/index.js
index ddc51e0efa5..9367ad5d4f7 100644
--- a/assets/js/modules/reader-revenue-manager/index.js
+++ b/assets/js/modules/reader-revenue-manager/index.js
@@ -38,6 +38,7 @@ import { isURLUsingHTTPS } from './utils/validation';
import { RRMSetupSuccessSubtleNotification } from './components/dashboard';
import { NOTIFICATION_AREAS } from '../../googlesitekit/notifications/datastore/constants';
import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../googlesitekit/constants';
+import { CORE_MODULES } from '../../googlesitekit/modules/datastore/constants';
export { registerStore } from './datastore';
@@ -82,6 +83,14 @@ export const registerNotifications = ( notifications ) => {
areaSlug: NOTIFICATION_AREAS.BANNERS_BELOW_NAV,
viewContexts: [ VIEW_CONTEXT_MAIN_DASHBOARD ],
checkRequirements: async ( { select, resolveSelect } ) => {
+ const rrmConnected = await resolveSelect(
+ CORE_MODULES
+ ).isModuleConnected( READER_REVENUE_MANAGER_MODULE_SLUG );
+
+ if ( ! rrmConnected ) {
+ return false;
+ }
+
const notification = getQueryArg( location.href, 'notification' );
const slug = getQueryArg( location.href, 'slug' );
diff --git a/assets/js/modules/search-console/datastore/index.js b/assets/js/modules/search-console/datastore/index.js
index ef7c67f77a8..5e78477ed60 100644
--- a/assets/js/modules/search-console/datastore/index.js
+++ b/assets/js/modules/search-console/datastore/index.js
@@ -24,9 +24,16 @@ import { MODULES_SEARCH_CONSOLE } from './constants';
import baseModuleStore from './base';
import report from './report';
import service from './service';
+import settings from './settings';
import properties from './properties';
-const store = combineStores( baseModuleStore, report, service, properties );
+const store = combineStores(
+ baseModuleStore,
+ report,
+ service,
+ settings,
+ properties
+);
export const initialState = store.initialState;
export const actions = store.actions;
diff --git a/assets/js/modules/search-console/datastore/settings.js b/assets/js/modules/search-console/datastore/settings.js
index 72f9e796a8d..0bf18183c14 100644
--- a/assets/js/modules/search-console/datastore/settings.js
+++ b/assets/js/modules/search-console/datastore/settings.js
@@ -25,6 +25,7 @@ import invariant from 'invariant';
* Internal dependencies
*/
import API from 'googlesitekit-api';
+import { createRegistrySelector } from 'googlesitekit-data';
import { createStrictSelect } from '../../../googlesitekit/data/utils';
import { isValidPropertyID } from '../util';
import { INVARIANT_SETTINGS_NOT_CHANGED } from '../../../googlesitekit/data/create-settings-store';
@@ -34,6 +35,18 @@ import { MODULES_SEARCH_CONSOLE } from './constants';
export const INVARIANT_INVALID_PROPERTY_SELECTION =
'a valid propertyID is required to submit changes';
+const store = {
+ selectors: {
+ areSettingsEditDependenciesLoaded: createRegistrySelector(
+ ( select ) => () =>
+ select( MODULES_SEARCH_CONSOLE ).hasFinishedResolution(
+ 'getMatchedProperties'
+ )
+ ),
+ },
+};
+export default store;
+
export async function submitChanges( { select, dispatch } ) {
// This action shouldn't be called if settings haven't changed,
// but this prevents errors in tests.
diff --git a/assets/js/modules/search-console/datastore/settings.test.js b/assets/js/modules/search-console/datastore/settings.test.js
index fe1214d3a9a..2a592d5d3f6 100644
--- a/assets/js/modules/search-console/datastore/settings.test.js
+++ b/assets/js/modules/search-console/datastore/settings.test.js
@@ -125,4 +125,30 @@ describe( 'modules/search-console settings', () => {
).not.toThrow( INVARIANT_SETTINGS_NOT_CHANGED );
} );
} );
+
+ describe( 'areSettingsEditDependenciesLoaded', () => {
+ it( 'should return false if getMatchedProperties selector has not resolved', () => {
+ registry
+ .dispatch( MODULES_SEARCH_CONSOLE )
+ .startResolution( 'getMatchedProperties', [] );
+
+ expect(
+ registry
+ .select( MODULES_SEARCH_CONSOLE )
+ .areSettingsEditDependenciesLoaded()
+ ).toBe( false );
+ } );
+
+ it( 'should return true if getMatchedProperties selector has resolved', () => {
+ registry
+ .dispatch( MODULES_SEARCH_CONSOLE )
+ .finishResolution( 'getMatchedProperties', [] );
+
+ expect(
+ registry
+ .select( MODULES_SEARCH_CONSOLE )
+ .areSettingsEditDependenciesLoaded()
+ ).toBe( true );
+ } );
+ } );
} );
diff --git a/assets/js/modules/tagmanager/datastore/index.js b/assets/js/modules/tagmanager/datastore/index.js
index 4f41e405f21..67e0a41af64 100644
--- a/assets/js/modules/tagmanager/datastore/index.js
+++ b/assets/js/modules/tagmanager/datastore/index.js
@@ -28,6 +28,7 @@ import containers from './containers';
import tags from './tags';
import versions from './versions';
import service from './service';
+import settings from './settings';
const store = combineStores(
baseModuleStore,
@@ -36,6 +37,7 @@ const store = combineStores(
tags,
versions,
createSnapshotStore( MODULES_TAGMANAGER ),
+ settings,
service
);
diff --git a/assets/js/modules/tagmanager/datastore/settings.js b/assets/js/modules/tagmanager/datastore/settings.js
index 139ee74f268..0bfb306c143 100644
--- a/assets/js/modules/tagmanager/datastore/settings.js
+++ b/assets/js/modules/tagmanager/datastore/settings.js
@@ -25,6 +25,7 @@ import invariant from 'invariant';
* Internal dependencies
*/
import API from 'googlesitekit-api';
+import { createRegistrySelector } from 'googlesitekit-data';
import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';
import {
isValidAccountID,
@@ -67,6 +68,18 @@ export const INVARIANT_INVALID_CONTAINER_NAME =
export const INVARIANT_GTM_GA_PROPERTY_ID_MISMATCH =
'single GTM Analytics property ID must match Analytics property ID';
+const store = {
+ selectors: {
+ areSettingsEditDependenciesLoaded: createRegistrySelector(
+ ( select ) => () =>
+ select( MODULES_TAGMANAGER ).hasFinishedResolution(
+ 'getAccounts'
+ )
+ ),
+ },
+};
+export default store;
+
export async function submitChanges( { select, dispatch } ) {
const accountID = select( MODULES_TAGMANAGER ).getAccountID();
const containerID = select( MODULES_TAGMANAGER ).getContainerID();
diff --git a/assets/js/modules/tagmanager/datastore/settings.test.js b/assets/js/modules/tagmanager/datastore/settings.test.js
index bb47ac4d4a2..a661ce3c604 100644
--- a/assets/js/modules/tagmanager/datastore/settings.test.js
+++ b/assets/js/modules/tagmanager/datastore/settings.test.js
@@ -480,6 +480,32 @@ describe( 'modules/tagmanager settings', () => {
} );
describe( 'selectors', () => {
+ describe( 'areSettingsEditDependenciesLoaded', () => {
+ it( 'should return false if getAccounts selector has not resolved', () => {
+ registry
+ .dispatch( MODULES_TAGMANAGER )
+ .startResolution( 'getAccounts', [] );
+
+ expect(
+ registry
+ .select( MODULES_TAGMANAGER )
+ .areSettingsEditDependenciesLoaded()
+ ).toBe( false );
+ } );
+
+ it( 'should return true if getAccounts selector has resolved', () => {
+ registry
+ .dispatch( MODULES_TAGMANAGER )
+ .finishResolution( 'getAccounts', [] );
+
+ expect(
+ registry
+ .select( MODULES_TAGMANAGER )
+ .areSettingsEditDependenciesLoaded()
+ ).toBe( true );
+ } );
+ } );
+
describe( 'isDoingSubmitChanges', () => {
it( 'returns true while submitting changes', async () => {
registry
diff --git a/assets/sass/components/first-party-mode/_googlesitekit-first-party-mode-setup-cta-banner.scss b/assets/sass/components/first-party-mode/_googlesitekit-first-party-mode-setup-cta-banner.scss
index 047244e44ae..391b9edfdef 100644
--- a/assets/sass/components/first-party-mode/_googlesitekit-first-party-mode-setup-cta-banner.scss
+++ b/assets/sass/components/first-party-mode/_googlesitekit-first-party-mode-setup-cta-banner.scss
@@ -18,7 +18,7 @@
.googlesitekit-plugin {
- .googlesitekit-setup-cta-banner--first-party-mode-setup-cta-banner {
+ .googlesitekit-setup-cta-banner--fpm-setup-cta {
.googlesitekit-setup-cta-banner__actions-wrapper {
gap: 4px; // Fix buttons being one on top of another on medium mobile viewports.
@@ -28,7 +28,7 @@
}
}
- .googlesitekit-setup-cta-banner__svg-wrapper--first-party-mode-setup-cta-banner {
+ .googlesitekit-setup-cta-banner__svg-wrapper--fpm-setup-cta {
align-items: flex-end;
display: flex;
justify-content: center;
diff --git a/assets/sass/components/key-metrics/_googlesitekit-chip-tab-group.scss b/assets/sass/components/key-metrics/_googlesitekit-chip-tab-group.scss
index c09deb635e1..fb3b754b5d4 100644
--- a/assets/sass/components/key-metrics/_googlesitekit-chip-tab-group.scss
+++ b/assets/sass/components/key-metrics/_googlesitekit-chip-tab-group.scss
@@ -135,6 +135,12 @@
background-color: $c-site-kit-sk-100;
}
+ .googlesitekit-chip-tab-group__chip-item-svg__suggested {
+ path {
+ fill: $c-neutral-n-900;
+ }
+ }
+
.googlesitekit-chip-tab-group__chip-item-count {
margin-left: 3px;
}
@@ -156,14 +162,6 @@
}
}
- .googlesitekit-chip-tab-group__tab-item-mobile-svg {
- margin-right: 7px;
-
- svg {
- display: block;
- }
- }
-
.googlesitekit-chip-tab-group__graphic {
align-items: center;
display: flex;
@@ -178,5 +176,23 @@
}
}
}
+
+ .googlesitekit-chip-tab-group__tab-item-mobile-svg {
+ margin-right: 7px;
+
+ svg {
+ display: block;
+ }
+
+ &.googlesitekit-chip-tab-group__tab-item-mobile-svg--suggested {
+
+ svg {
+
+ path {
+ fill: $c-neutral-n-900;
+ }
+ }
+ }
+ }
}
}
diff --git a/includes/Core/Authentication/Clients/OAuth_Client.php b/includes/Core/Authentication/Clients/OAuth_Client.php
index b600d844dce..636cd18f076 100644
--- a/includes/Core/Authentication/Clients/OAuth_Client.php
+++ b/includes/Core/Authentication/Clients/OAuth_Client.php
@@ -569,24 +569,6 @@ public function refresh_profile_data( $retry_after = 0 ) {
}
}
- /**
- * Determines whether the authentication proxy is used.
- *
- * In order to streamline the setup and authentication flow, the plugin uses a proxy mechanism based on an external
- * service. This can be overridden by providing actual GCP credentials with the {@see 'googlesitekit_oauth_secret'}
- * filter.
- *
- * @since 1.0.0
- * @deprecated 1.9.0
- *
- * @return bool True if proxy authentication is used, false otherwise.
- */
- public function using_proxy() {
- _deprecated_function( __METHOD__, '1.9.0', Credentials::class . '::using_proxy' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
-
- return $this->credentials->using_proxy();
- }
-
/**
* Determines whether the current owner ID must be changed or not.
*
diff --git a/includes/Core/Key_Metrics/REST_Key_Metrics_Controller.php b/includes/Core/Key_Metrics/REST_Key_Metrics_Controller.php
index e287b8afcfb..aa3e849995e 100644
--- a/includes/Core/Key_Metrics/REST_Key_Metrics_Controller.php
+++ b/includes/Core/Key_Metrics/REST_Key_Metrics_Controller.php
@@ -184,18 +184,6 @@ protected function get_rest_routes() {
),
)
),
- new REST_Route(
- 'core/user/data/reset-key-metrics-selection',
- array(
- 'methods' => WP_REST_Server::CREATABLE,
- 'callback' => function () {
- $this->settings->merge( array( 'widgetSlugs' => array() ) );
-
- return new WP_REST_Response( $this->settings->get() );
- },
- 'permission_callback' => $has_capabilities,
- )
- ),
);
}
}
diff --git a/includes/Core/Tags/First_Party_Mode/First_Party_Mode.php b/includes/Core/Tags/First_Party_Mode/First_Party_Mode.php
index 37776fdc855..376b2567d7e 100644
--- a/includes/Core/Tags/First_Party_Mode/First_Party_Mode.php
+++ b/includes/Core/Tags/First_Party_Mode/First_Party_Mode.php
@@ -186,22 +186,33 @@ public function on_admin_init() {
*
* @since 1.141.0
* @since 1.142.0 Relocated from REST_First_Party_Mode_Controller.
+ * @since n.e.x.t Uses Google\FirstPartyLibrary\RequestHelper to send requests.
*
* @param string $endpoint The endpoint to check.
* @return bool True if the endpoint is healthy, false otherwise.
*/
protected function is_endpoint_healthy( $endpoint ) {
- try {
- // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
- $response = file_get_contents( $endpoint );
- } catch ( \Exception $e ) {
+ if ( ! defined( 'IS_FIRST_PARTY_MODE_TEST' ) ) {
+ // TODO: This is a workaround to allow the measurement.php file to be loaded without making a
+ // request, in order to use the RequestHelper class that it defines. We should find a better
+ // solution in the future, but this will involve changes to the measurement.php file.
+ define( 'IS_FIRST_PARTY_MODE_TEST', true );
+ }
+
+ require_once GOOGLESITEKIT_PLUGIN_DIR_PATH . 'fpm/measurement.php';
+
+ $request_helper = new \Google\FirstPartyLibrary\RequestHelper();
+
+ $response = $request_helper->sendRequest( $endpoint );
+
+ if ( 200 !== $response['statusCode'] ) {
return false;
}
- if ( 'ok' !== $response ) {
+ if ( 'ok' !== $response['body'] ) {
return false;
}
- return strpos( $http_response_header[0], '200 OK' ) !== false;
+ return true;
}
}
diff --git a/includes/Core/User/Conversion_Reporting.php b/includes/Core/User/Conversion_Reporting.php
new file mode 100644
index 00000000000..3a517ebe263
--- /dev/null
+++ b/includes/Core/User/Conversion_Reporting.php
@@ -0,0 +1,61 @@
+conversion_reporting_settings = new Conversion_Reporting_Settings( $user_options );
+ $this->rest_controller = new REST_Conversion_Reporting_Controller( $this->conversion_reporting_settings );
+ }
+
+ /**
+ * Registers functionality through WordPress hooks.
+ *
+ * @since n.e.x.t
+ */
+ public function register() {
+ $this->conversion_reporting_settings->register();
+ $this->rest_controller->register();
+ }
+}
diff --git a/includes/Core/User/Conversion_Reporting_Settings.php b/includes/Core/User/Conversion_Reporting_Settings.php
new file mode 100644
index 00000000000..7e17a00fda1
--- /dev/null
+++ b/includes/Core/User/Conversion_Reporting_Settings.php
@@ -0,0 +1,109 @@
+ 0,
+ 'lostEventsCalloutDismissedAt' => 0,
+ );
+ }
+
+ /**
+ * Merges an array of settings to update.
+ *
+ * @since n.e.x.t
+ *
+ * @param array $partial Partial settings array to save.
+ * @return bool True on success, false on failure.
+ */
+ public function merge( array $partial ) {
+ $settings = $this->get();
+ $partial = array_filter(
+ $partial,
+ function ( $value ) {
+ return null !== $value;
+ }
+ );
+
+ $allowed_settings = array(
+ 'newEventsCalloutDismissedAt' => true,
+ 'lostEventsCalloutDismissedAt' => true,
+ );
+
+ $updated = array_intersect_key( $partial, $allowed_settings );
+
+ return $this->set( array_merge( $settings, $updated ) );
+ }
+
+ /**
+ * Gets the callback for sanitizing the setting's value before saving.
+ *
+ * @since n.e.x.t
+ *
+ * @return callable Sanitize callback.
+ */
+ protected function get_sanitize_callback() {
+ return function ( $settings ) {
+ if ( ! is_array( $settings ) ) {
+ return array();
+ }
+
+ if ( isset( $settings['newEventsCalloutDismissedAt'] ) ) {
+ if ( ! is_int( $settings['newEventsCalloutDismissedAt'] ) ) {
+ $settings['newEventsCalloutDismissedAt'] = 0;
+ }
+ }
+
+ if ( isset( $settings['lostEventsCalloutDismissedAt'] ) ) {
+ if ( ! is_int( $settings['lostEventsCalloutDismissedAt'] ) ) {
+ $settings['lostEventsCalloutDismissedAt'] = 0;
+ }
+ }
+
+ return $settings;
+ };
+ }
+}
diff --git a/includes/Core/User/REST_Conversion_Reporting_Controller.php b/includes/Core/User/REST_Conversion_Reporting_Controller.php
new file mode 100644
index 00000000000..a1819f4b1ab
--- /dev/null
+++ b/includes/Core/User/REST_Conversion_Reporting_Controller.php
@@ -0,0 +1,143 @@
+conversion_reporting_settings = $conversion_reporting_settings;
+ }
+
+ /**
+ * Registers functionality through WordPress hooks.
+ *
+ * @since n.e.x.t
+ */
+ public function register() {
+ add_filter(
+ 'googlesitekit_rest_routes',
+ function ( $routes ) {
+ return array_merge( $routes, $this->get_rest_routes() );
+ }
+ );
+
+ add_filter(
+ 'googlesitekit_apifetch_preload_paths',
+ function ( $paths ) {
+ if ( Feature_Flags::enabled( 'conversionReporting' ) ) {
+ return array_merge(
+ $paths,
+ array(
+ '/' . REST_Routes::REST_ROOT . '/core/user/data/conversion-reporting-settings',
+ )
+ );
+ }
+
+ return $paths;
+ }
+ );
+ }
+
+ /**
+ * Gets REST route instances.
+ *
+ * @since n.e.x.t
+ *
+ * @return REST_Route[] List of REST_Route objects.
+ */
+ protected function get_rest_routes() {
+ $can_view_dashboard = function () {
+ return current_user_can( Permissions::VIEW_DASHBOARD );
+ };
+
+ if ( ! Feature_Flags::enabled( 'conversionReporting' ) ) {
+ return array();
+ }
+
+ return array(
+ new REST_Route(
+ 'core/user/data/conversion-reporting-settings',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => function () {
+ return new WP_REST_Response( $this->conversion_reporting_settings->get() );
+ },
+ 'permission_callback' => $can_view_dashboard,
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => function ( WP_REST_Request $request ) {
+ $settings = $request['data']['settings'];
+
+ $this->conversion_reporting_settings->merge( $settings );
+
+ return new WP_REST_Response( $this->conversion_reporting_settings->get() );
+ },
+ 'permission_callback' => $can_view_dashboard,
+ 'args' => array(
+ 'data' => array(
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'settings' => array(
+ 'type' => 'object',
+ 'required' => true,
+ 'minProperties' => 1,
+ 'additionalProperties' => false,
+ 'properties' => array(
+ 'newEventsCalloutDismissedAt' => array(
+ 'type' => 'integer',
+ ),
+ 'lostEventsCalloutDismissedAt' => array(
+ 'type' => 'integer',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ ),
+ );
+ }
+}
diff --git a/includes/Core/User/User.php b/includes/Core/User/User.php
index b5b4685f95e..cbb83e5a83a 100644
--- a/includes/Core/User/User.php
+++ b/includes/Core/User/User.php
@@ -13,7 +13,7 @@
use Google\Site_Kit\Core\Storage\User_Options;
/**
- * Class for handling audience settings rest routes.
+ * Class for handling user settings rest routes.
*
* @since 1.134.0
* @access private
@@ -29,6 +29,14 @@ class User {
*/
private $audience_segmentation;
+ /**
+ * Conversion_Reporting instance.
+ *
+ * @since n.e.x.t
+ * @var Conversion_Reporting
+ */
+ private $conversion_reporting;
+
/**
* Constructor.
*
@@ -38,6 +46,7 @@ class User {
*/
public function __construct( User_Options $user_options ) {
$this->audience_segmentation = new Audience_Segmentation( $user_options );
+ $this->conversion_reporting = new Conversion_Reporting( $user_options );
}
/**
@@ -47,5 +56,6 @@ public function __construct( User_Options $user_options ) {
*/
public function register() {
$this->audience_segmentation->register();
+ $this->conversion_reporting->register();
}
}
diff --git a/includes/Core/User_Input/User_Input.php b/includes/Core/User_Input/User_Input.php
index 6371b8cf1e5..718ffe0e44b 100644
--- a/includes/Core/User_Input/User_Input.php
+++ b/includes/Core/User_Input/User_Input.php
@@ -203,6 +203,12 @@ public function are_settings_empty( $settings = array() ) {
}
}
+ // Conversion events may be empty during setup if no events have been detected.
+ // Since this setting does not affect whether user input is considered "set up",
+ // we are excluding it from this check. It relates to user input initially being
+ // set up with detected events or events added later.
+ unset( $settings['includeConversionEvents'] );
+
foreach ( $settings as $setting ) {
if ( empty( $setting['values'] ) ) {
return true;
diff --git a/includes/Modules/AdSense.php b/includes/Modules/AdSense.php
index eea8952db2a..6e83dcd2067 100644
--- a/includes/Modules/AdSense.php
+++ b/includes/Modules/AdSense.php
@@ -748,7 +748,6 @@ protected function setup_info() {
'slug' => self::MODULE_SLUG,
'name' => _x( 'AdSense', 'Service name', 'google-site-kit' ),
'description' => __( 'Earn money by placing ads on your website. It’s free and easy.', 'google-site-kit' ),
- 'order' => 2,
'homepage' => add_query_arg( $idenfifier_args, 'https://adsense.google.com/start' ),
);
}
diff --git a/includes/Modules/Ads.php b/includes/Modules/Ads.php
index 279af468092..5bdbc1844a4 100644
--- a/includes/Modules/Ads.php
+++ b/includes/Modules/Ads.php
@@ -208,7 +208,6 @@ protected function setup_info() {
'slug' => 'ads',
'name' => _x( 'Ads', 'Service name', 'google-site-kit' ),
'description' => Feature_Flags::enabled( 'adsPax' ) ? __( 'Grow sales, leads or awareness for your business by advertising with Google Ads', 'google-site-kit' ) : __( 'Track conversions for your existing Google Ads campaigns', 'google-site-kit' ),
- 'order' => 1,
'homepage' => __( 'https://google.com/ads', 'google-site-kit' ),
);
}
diff --git a/includes/Modules/Analytics_4.php b/includes/Modules/Analytics_4.php
index d9deecab838..649378bd037 100644
--- a/includes/Modules/Analytics_4.php
+++ b/includes/Modules/Analytics_4.php
@@ -707,15 +707,6 @@ protected function get_datapoint_definitions() {
);
}
- if ( Feature_Flags::enabled( 'conversionReporting' ) ) {
- $datapoints['POST:clear-conversion-reporting-new-events'] = array(
- 'service' => '',
- );
- $datapoints['POST:clear-conversion-reporting-lost-events'] = array(
- 'service' => '',
- );
- }
-
return $datapoints;
}
@@ -1692,14 +1683,6 @@ protected function create_data_request( Data_Request $data ) {
return function () use ( $data ) {
return $this->transients->set( 'googlesitekit_inline_tag_id_mismatch', $data['hasMismatchedTag'] );
};
- case 'POST:clear-conversion-reporting-new-events':
- return function () {
- return $this->transients->delete( Conversion_Reporting_Events_Sync::DETECTED_EVENTS_TRANSIENT );
- };
- case 'POST:clear-conversion-reporting-lost-events':
- return function () {
- return $this->transients->delete( Conversion_Reporting_Events_Sync::LOST_EVENTS_TRANSIENT );
- };
}
return parent::create_data_request( $data );
@@ -1839,7 +1822,6 @@ protected function setup_info() {
'slug' => self::MODULE_SLUG,
'name' => _x( 'Analytics', 'Service name', 'google-site-kit' ),
'description' => __( 'Get a deeper understanding of your customers. Google Analytics gives you the free tools you need to analyze data for your business in one place.', 'google-site-kit' ),
- 'order' => 3,
'homepage' => __( 'https://analytics.google.com/analytics/web', 'google-site-kit' ),
);
}
diff --git a/includes/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_Sync.php b/includes/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_Sync.php
index e291792ec55..ee72924ef34 100644
--- a/includes/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_Sync.php
+++ b/includes/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_Sync.php
@@ -127,10 +127,15 @@ public function sync_detected_events() {
foreach ( $report->rows as $row ) {
$detected_events[] = $row['dimensionValues'][0]['value'];
}
+ $settings_partial = array( 'detectedEvents' => $detected_events );
- $this->maybe_update_new_and_lost_events( $detected_events, $saved_detected_events );
+ $this->maybe_update_new_and_lost_events(
+ $detected_events,
+ $saved_detected_events,
+ $settings_partial
+ );
- $this->settings->merge( array( 'detectedEvents' => $detected_events ) );
+ $this->settings->merge( $settings_partial );
}
/**
@@ -140,18 +145,21 @@ public function sync_detected_events() {
*
* @param array $detected_events Currently detected events array.
* @param array $saved_detected_events Previously saved detected events array.
+ * @param array $settings_partial Analaytics settings partial.
*/
- protected function maybe_update_new_and_lost_events( $detected_events, $saved_detected_events ) {
+ protected function maybe_update_new_and_lost_events( $detected_events, $saved_detected_events, &$settings_partial ) {
$new_events = array_diff( $detected_events, $saved_detected_events );
$lost_events = array_diff( $saved_detected_events, $detected_events );
if ( ! empty( $new_events ) ) {
$this->transients->set( self::DETECTED_EVENTS_TRANSIENT, array_values( $new_events ) );
$this->new_badge_events_sync->sync_new_badge_events( $new_events );
+ $settings_partial['newConversionEventsLastUpdateAt'] = time();
}
if ( ! empty( $lost_events ) ) {
$this->transients->set( self::LOST_EVENTS_TRANSIENT, array_values( $lost_events ) );
+ $settings_partial['lostConversionEventsLastUpdateAt'] = time();
}
if ( empty( $saved_detected_events ) ) {
diff --git a/includes/Modules/Analytics_4/Settings.php b/includes/Modules/Analytics_4/Settings.php
index 7a5976684e4..a113b452fc0 100644
--- a/includes/Modules/Analytics_4/Settings.php
+++ b/includes/Modules/Analytics_4/Settings.php
@@ -74,6 +74,8 @@ public function get_view_only_keys() {
'availableAudiences',
'audienceSegmentationSetupCompletedBy',
'detectedEvents',
+ 'newConversionEventsLastUpdateAt',
+ 'lostConversionEventsLastUpdateAt',
);
}
@@ -110,6 +112,8 @@ protected function get_default() {
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => null,
'detectedEvents' => array(),
+ 'newConversionEventsLastUpdateAt' => 0,
+ 'lostConversionEventsLastUpdateAt' => 0,
);
}
@@ -213,6 +217,18 @@ function ( $dimension ) {
$option['audienceSegmentationSetupCompletedBy'] = null;
}
}
+
+ if ( isset( $option['newConversionEventsLastUpdateAt'] ) ) {
+ if ( ! is_int( $option['newConversionEventsLastUpdateAt'] ) ) {
+ $option['newConversionEventsLastUpdateAt'] = 0;
+ }
+ }
+
+ if ( isset( $option['lostConversionEventsLastUpdateAt'] ) ) {
+ if ( ! is_int( $option['lostConversionEventsLastUpdateAt'] ) ) {
+ $option['lostConversionEventsLastUpdateAt'] = 0;
+ }
+ }
}
return $option;
diff --git a/includes/Modules/PageSpeed_Insights.php b/includes/Modules/PageSpeed_Insights.php
index c4982f116f3..149d5cb9e51 100644
--- a/includes/Modules/PageSpeed_Insights.php
+++ b/includes/Modules/PageSpeed_Insights.php
@@ -180,7 +180,6 @@ protected function setup_info() {
'slug' => 'pagespeed-insights',
'name' => _x( 'PageSpeed Insights', 'Service name', 'google-site-kit' ),
'description' => __( 'Google PageSpeed Insights gives you metrics about performance, accessibility, SEO and PWA', 'google-site-kit' ),
- 'order' => 4,
'homepage' => __( 'https://pagespeed.web.dev', 'google-site-kit' ),
);
}
diff --git a/includes/Modules/Reader_Revenue_Manager.php b/includes/Modules/Reader_Revenue_Manager.php
index 78492224da5..c525a5892a7 100644
--- a/includes/Modules/Reader_Revenue_Manager.php
+++ b/includes/Modules/Reader_Revenue_Manager.php
@@ -332,7 +332,6 @@ protected function setup_info() {
'slug' => self::MODULE_SLUG,
'name' => _x( 'Reader Revenue Manager', 'Service name', 'google-site-kit' ),
'description' => __( 'Reader Revenue Manager helps publishers grow, retain, and engage their audiences, creating new revenue opportunities', 'google-site-kit' ),
- 'order' => 5,
'homepage' => 'https://publishercenter.google.com',
);
}
diff --git a/includes/Modules/Sign_In_With_Google.php b/includes/Modules/Sign_In_With_Google.php
index 3ecbfb0d9b4..fa2e0cc2a7d 100644
--- a/includes/Modules/Sign_In_With_Google.php
+++ b/includes/Modules/Sign_In_With_Google.php
@@ -226,7 +226,6 @@ protected function setup_info() {
'slug' => self::MODULE_SLUG,
'name' => _x( 'Sign in with Google', 'Service name', 'google-site-kit' ),
'description' => __( 'Improve user engagement, trust and data privacy, while creating a simple, secure and personalized experience for your visitors', 'google-site-kit' ),
- 'order' => 10,
'homepage' => __( 'https://developers.google.com/identity/gsi/web/guides/overview', 'google-site-kit' ),
);
}
@@ -341,7 +340,7 @@ private function render_signin_button() {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams( response )
- });
+ } );
if ( res.ok && res.redirected ) {
location.assign( res.url );
}
@@ -353,6 +352,7 @@ private function render_signin_button() {
google.accounts.id.initialize( {
client_id: '',
callback: handleCredentialResponse,
+ library_name: 'Site-Kit',
} );
google.accounts.id.renderButton( parent, );
@@ -369,7 +369,7 @@ private function render_signin_button() {
} )();
\n", esc_html__( 'End Sign in with Google button added by Site Kit', 'google-site-kit' ) );
+ print( "\n\n" );
}
/**
diff --git a/includes/Modules/Tag_Manager.php b/includes/Modules/Tag_Manager.php
index c973e3b554b..5e3a9a7750c 100644
--- a/includes/Modules/Tag_Manager.php
+++ b/includes/Modules/Tag_Manager.php
@@ -466,7 +466,6 @@ protected function setup_info() {
'slug' => self::MODULE_SLUG,
'name' => _x( 'Tag Manager', 'Service name', 'google-site-kit' ),
'description' => __( 'Tag Manager creates an easy to manage way to create tags on your site without updating code', 'google-site-kit' ),
- 'order' => 6,
'homepage' => __( 'https://tagmanager.google.com/', 'google-site-kit' ),
);
}
diff --git a/package-lock.json b/package-lock.json
index 744a908e7eb..b7c9d500664 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26990,9 +26990,9 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001687",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz",
- "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ=="
+ "version": "1.0.30001688",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz",
+ "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA=="
},
"capture-exit": {
"version": "2.0.0",
diff --git a/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_0_small.png
new file mode 100644
index 00000000000..47f7370baad
Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_0_small.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_1_medium.png
new file mode 100644
index 00000000000..326b00742db
Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_1_medium.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_2_large.png
new file mode 100644
index 00000000000..edf26bf232c
Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_KeyMetrics_ChipTabGroup_WithSuggestedGroup_0_document_2_large.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_0_small.png
index 5ff33ce4a1f..71c88713561 100644
Binary files a/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_0_small.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_1_medium.png
index 239a4cc6700..905cba4be97 100644
Binary files a/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_1_medium.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_2_large.png
index 6cce80b6ce3..362db18fb83 100644
Binary files a/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Components_ReportTable_Basic_0_document_2_large.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_0_small.png
index 146e7e548b0..11c3f52a2e7 100644
Binary files a/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_0_small.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_1_medium.png
index 3ea782a9d5f..06c09698602 100644
Binary files a/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_1_medium.png differ
diff --git a/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_2_large.png
index 160c09558b7..a8675ca1c97 100644
Binary files a/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Components_ReportTable_Tabbed_Layout_0_document_2_large.png differ
diff --git a/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_0_small.png
index 1e0262e06a0..79a95874ec2 100644
Binary files a/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_0_small.png differ
diff --git a/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_1_medium.png
index 54085f6c742..6c20ff648c2 100644
Binary files a/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_1_medium.png differ
diff --git a/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_2_large.png
index fd5fa438c0d..5d790a530e5 100644
Binary files a/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Settings_Connect_More_Services_0_document_2_large.png differ
diff --git a/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php b/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php
index f265b53c30a..29778b7be9f 100644
--- a/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php
+++ b/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php
@@ -709,23 +709,6 @@ function ( Request $request ) {
$this->assertTrue( $google_client_mock->shouldDefer() );
}
- public function test_using_proxy() {
- $this->setExpectedDeprecated( OAuth_Client::class . '::using_proxy' );
- $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE );
- $client = new OAuth_Client( $context );
-
- // Use proxy by default.
- $this->assertTrue( $client->using_proxy() );
-
- // Don't use proxy when regular OAuth client ID is used.
- $this->fake_site_connection();
- $this->assertFalse( $client->using_proxy() );
-
- // Use proxy when proxy site ID is used.
- $this->fake_proxy_site_connection();
- $this->assertTrue( $client->using_proxy() );
- }
-
public function test_get_proxy_permissions_url() {
$context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE );
diff --git a/tests/phpunit/integration/Core/Key_Metrics/REST_Key_Metrics_ControllerTest.php b/tests/phpunit/integration/Core/Key_Metrics/REST_Key_Metrics_ControllerTest.php
index 4c4879a2bf3..dbd062363b1 100644
--- a/tests/phpunit/integration/Core/Key_Metrics/REST_Key_Metrics_ControllerTest.php
+++ b/tests/phpunit/integration/Core/Key_Metrics/REST_Key_Metrics_ControllerTest.php
@@ -309,29 +309,4 @@ public function provider_wrong_data() {
),
);
}
-
- public function test_reset_key_metrics_selection() {
- remove_all_filters( 'googlesitekit_rest_routes' );
- $this->controller->register();
- $this->register_rest_routes();
-
- $original_settings = array(
- 'widgetSlugs' => array( 'widgetA' ),
- 'isWidgetHidden' => false,
- );
-
- $expected_change_in_settings = array(
- 'widgetSlugs' => array(),
- 'isWidgetHidden' => false,
- );
-
- $this->settings->register();
- $this->settings->set( $original_settings );
-
- $request = new WP_REST_Request( 'POST', '/' . REST_Routes::REST_ROOT . '/core/user/data/reset-key-metrics-selection' );
- $response = rest_get_server()->dispatch( $request );
-
- $this->assertEqualSetsWithIndex( $expected_change_in_settings, $response->get_data() );
- $this->assertEqualSetsWithIndex( $expected_change_in_settings, $this->settings->get() );
- }
}
diff --git a/tests/phpunit/integration/Core/Site_Health/Tag_PlacementTest.php b/tests/phpunit/integration/Core/Site_Health/Tag_PlacementTest.php
index 5a7afa1ffb5..f63a65c5147 100644
--- a/tests/phpunit/integration/Core/Site_Health/Tag_PlacementTest.php
+++ b/tests/phpunit/integration/Core/Site_Health/Tag_PlacementTest.php
@@ -130,7 +130,7 @@ public function test_get_active_modules_with_tags() {
$result = $get_active_modules->invokeArgs( $site_status, array() );
- $this->assertEquals(
+ $this->assertEqualSets(
array(
'adsense',
'analytics-4',
diff --git a/tests/phpunit/integration/Core/User/Conversion_Reporting_SettingsTest.php b/tests/phpunit/integration/Core/User/Conversion_Reporting_SettingsTest.php
new file mode 100644
index 00000000000..b0a0e74cf84
--- /dev/null
+++ b/tests/phpunit/integration/Core/User/Conversion_Reporting_SettingsTest.php
@@ -0,0 +1,146 @@
+factory()->user->create();
+ $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE );
+ $user_options = new User_Options( $context, $user_id );
+ $meta_key = $user_options->get_meta_key( Conversion_Reporting_Settings::OPTION );
+
+ unregister_meta_key( 'user', $meta_key );
+ // Needed to unregister the instance registered during plugin bootstrap.
+ remove_all_filters( "sanitize_user_meta_{$meta_key}" );
+
+ $this->conversion_reporting_settings = new Conversion_Reporting_Settings( $user_options );
+
+ $this->conversion_reporting_settings->register();
+ }
+
+ public function test_get_default() {
+ $this->assertEquals(
+ array(
+ 'newEventsCalloutDismissedAt' => 0,
+ 'lostEventsCalloutDismissedAt' => 0,
+ ),
+ $this->conversion_reporting_settings->get()
+ );
+ }
+
+ public function data_conversion_reporting_settings() {
+ return array(
+ 'empty by default' => array(
+ null,
+ array(),
+ ),
+ 'non-array - bool' => array(
+ false,
+ array(),
+ ),
+ 'non-array - int' => array(
+ 123,
+ array(),
+ ),
+ 'non integer for newEventsCalloutDismissedAt and null lostEventsCalloutDismissedAt' => array(
+ array(
+ 'newEventsCalloutDismissedAt' => 'string',
+ 'lostEventsCalloutDismissedAt' => null,
+ ),
+ array(
+ 'newEventsCalloutDismissedAt' => 0,
+ 'lostEventsCalloutDismissedAt' => 0,
+ ),
+ ),
+ 'integer for both lostEventsCalloutDismissedAt and newEventsCalloutDismissedAt' => array(
+ array(
+ 'newEventsCalloutDismissedAt' => 1734519924,
+ 'lostEventsCalloutDismissedAt' => 1734519928,
+ ),
+ array(
+ 'newEventsCalloutDismissedAt' => 1734519924,
+ 'lostEventsCalloutDismissedAt' => 1734519928,
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider data_conversion_reporting_settings
+ *
+ * @param mixed $input Values to pass to the `set()` method.
+ * @param array $expected The expected sanitized array.
+ */
+ public function test_get_sanitize_callback( $input, $expected ) {
+ $this->conversion_reporting_settings->set( $input );
+ $this->assertEquals( $expected, $this->conversion_reporting_settings->get() );
+ }
+
+ public function test_merge() {
+ $original_settings = array(
+ 'newEventsCalloutDismissedAt' => 0,
+ 'lostEventsCalloutDismissedAt' => 0,
+ );
+
+ $changed_settings = array(
+ 'newEventsCalloutDismissedAt' => 1734519924,
+ 'lostEventsCalloutDismissedAt' => 0,
+ );
+
+ // Make sure settings can be updated even without having them set initially
+ $this->conversion_reporting_settings->merge( $original_settings );
+ $this->assertEqualSetsWithIndex( $original_settings, $this->conversion_reporting_settings->get() );
+
+ // Make sure invalid keys aren't set
+ $this->conversion_reporting_settings->merge( array( 'test_key' => 'test_value' ) );
+ $this->assertEqualSetsWithIndex( $original_settings, $this->conversion_reporting_settings->get() );
+
+ // Make sure that we can update settings partially
+ $this->conversion_reporting_settings->set( $original_settings );
+ $this->conversion_reporting_settings->merge( array( 'newEventsCalloutDismissedAt' => 1734519924 ) );
+ $this->assertEqualSetsWithIndex(
+ array(
+ 'newEventsCalloutDismissedAt' => 1734519924,
+ 'lostEventsCalloutDismissedAt' => $original_settings['lostEventsCalloutDismissedAt'],
+ ),
+ $this->conversion_reporting_settings->get()
+ );
+
+ // Make sure that we can update all settings at once
+ $this->conversion_reporting_settings->set( $original_settings );
+ $this->conversion_reporting_settings->merge( $changed_settings );
+ $this->assertEqualSetsWithIndex( $changed_settings, $this->conversion_reporting_settings->get() );
+
+ // Make sure that we can't set wrong format for the newEventsCalloutDismissedAt property
+ $this->conversion_reporting_settings->set( $original_settings );
+ $this->conversion_reporting_settings->merge( array( 'newEventsCalloutDismissedAt' => null ) );
+ $this->assertEqualSetsWithIndex( $original_settings, $this->conversion_reporting_settings->get() );
+
+ // Make sure that we can't set wrong format for the lostEventsCalloutDismissedAt property
+ $this->conversion_reporting_settings->set( $original_settings );
+ $this->conversion_reporting_settings->merge( array( 'lostEventsCalloutDismissedAt' => null ) );
+ $this->assertEqualSetsWithIndex( $original_settings, $this->conversion_reporting_settings->get() );
+ }
+}
diff --git a/tests/phpunit/integration/Core/User/REST_Conversion_Reporting_ControllerTest.php b/tests/phpunit/integration/Core/User/REST_Conversion_Reporting_ControllerTest.php
new file mode 100644
index 00000000000..6060e59e145
--- /dev/null
+++ b/tests/phpunit/integration/Core/User/REST_Conversion_Reporting_ControllerTest.php
@@ -0,0 +1,102 @@
+factory()->user->create( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $user_id );
+
+ $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE );
+ $this->user_options = new User_Options( $context );
+ $this->conversion_reporting_settings = new Conversion_Reporting_Settings( $this->user_options );
+ $this->controller = new REST_Conversion_Reporting_Controller(
+ $this->conversion_reporting_settings
+ );
+ }
+
+ public function tear_down() {
+ parent::tear_down();
+ // This ensures the REST server is initialized fresh for each test using it.
+ unset( $GLOBALS['wp_rest_server'] );
+ }
+
+ public function test_register() {
+ remove_all_filters( 'googlesitekit_rest_routes' );
+ remove_all_filters( 'googlesitekit_apifetch_preload_paths' );
+
+ $this->controller->register();
+
+ $this->assertTrue( has_filter( 'googlesitekit_rest_routes' ) );
+ $this->assertTrue( has_filter( 'googlesitekit_apifetch_preload_paths' ) );
+ }
+
+ public function test_get_routes__no_feature_flag() {
+ $this->controller->register();
+
+ $server = rest_get_server();
+ $routes = array(
+ '/' . REST_Routes::REST_ROOT . '/core/user/data/conversion-reporting-settings',
+
+ );
+ $get_routes = array_intersect( $routes, array_keys( $server->get_routes() ) );
+
+ $this->assertTrue( empty( $get_routes ) );
+ }
+
+ public function test_get_routes__with_feature_flag() {
+ $this->enable_feature( 'conversionReporting' );
+ $this->controller->register();
+
+ $server = rest_get_server();
+ $routes = array(
+ '/' . REST_Routes::REST_ROOT . '/core/user/data/conversion-reporting-settings',
+
+ );
+ $get_routes = array_intersect( $routes, array_keys( $server->get_routes() ) );
+
+ $this->assertEqualSets( $routes, $get_routes );
+ }
+}
diff --git a/tests/phpunit/integration/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_SyncTest.php b/tests/phpunit/integration/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_SyncTest.php
index 81656eda6c4..cf7c79ac97b 100644
--- a/tests/phpunit/integration/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_SyncTest.php
+++ b/tests/phpunit/integration/Modules/Analytics_4/Conversion_Reporting/Conversion_Reporting_Events_SyncTest.php
@@ -119,6 +119,70 @@ public function test_sync_detected_events_lost( $initially_saved_events, $lost_e
$this->assertEquals( $transient_lost_events, $lost_events );
}
+ public function test_sync__newConversionEventsLastUpdateAt() {
+ $this->setup_fake_handler_and_analytics(
+ array(
+ array(
+ 'dimensionValues' => array(
+ array(
+ 'value' => 'contact',
+ ),
+ ),
+ ),
+ )
+ );
+
+ $event_check = $this->get_instance();
+ $this->settings->merge(
+ array(
+ 'detectedEvents' => array(),
+ )
+ );
+
+ $this->assertEquals( 0, $this->settings->get()['newConversionEventsLastUpdateAt'] );
+
+ $event_check->sync_detected_events();
+
+ $transient_detected_events = $this->transients->get( Conversion_Reporting_Events_Sync::DETECTED_EVENTS_TRANSIENT );
+
+ $this->assertSame( $transient_detected_events, array( 'contact' ) );
+
+ // Verify that newConversionEventsLastUpdateAt is updated.
+ $this->assertEqualsWithDelta( time(), $this->settings->get()['newConversionEventsLastUpdateAt'], 2 );
+ }
+
+ public function test_sync__lostConversionEventsLastUpdateAt() {
+ $this->setup_fake_handler_and_analytics(
+ array(
+ array(
+ 'dimensionValues' => array(
+ array(
+ 'value' => 'contact',
+ ),
+ ),
+ ),
+ )
+ );
+
+ $event_check = $this->get_instance();
+ $this->settings->merge(
+ array(
+ 'detectedEvents' => array( 'purchase' ),
+ )
+ );
+
+ $this->assertEquals( 0, $this->settings->get()['lostConversionEventsLastUpdateAt'] );
+
+ $event_check->sync_detected_events();
+
+ $transient_lost_events = $this->transients->get( Conversion_Reporting_Events_Sync::LOST_EVENTS_TRANSIENT );
+
+ $this->assertSame( $transient_lost_events, array( 'purchase' ) );
+
+ // Verify that lostConversionEventsLastUpdateAt is updated.
+ $this->assertEqualsWithDelta( time(), $this->settings->get()['lostConversionEventsLastUpdateAt'], 2 );
+ }
+
public function get_instance() {
return new Conversion_Reporting_Events_Sync(
$this->settings,
diff --git a/tests/phpunit/integration/Modules/Analytics_4/SettingsTest.php b/tests/phpunit/integration/Modules/Analytics_4/SettingsTest.php
index 59e832e9f56..ea1669b3de2 100644
--- a/tests/phpunit/integration/Modules/Analytics_4/SettingsTest.php
+++ b/tests/phpunit/integration/Modules/Analytics_4/SettingsTest.php
@@ -88,6 +88,8 @@ public function test_get_default() {
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => null,
'detectedEvents' => array(),
+ 'lostConversionEventsLastUpdateAt' => 0,
+ 'newConversionEventsLastUpdateAt' => 0,
),
get_option( Settings::OPTION )
);
diff --git a/tests/phpunit/integration/Modules/Analytics_4Test.php b/tests/phpunit/integration/Modules/Analytics_4Test.php
index 4db55df868c..59ab52291f5 100644
--- a/tests/phpunit/integration/Modules/Analytics_4Test.php
+++ b/tests/phpunit/integration/Modules/Analytics_4Test.php
@@ -551,6 +551,8 @@ function ( Request $request ) use ( $property_id, $webdatastream_id, $measuremen
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => null,
'detectedEvents' => array(),
+ 'lostConversionEventsLastUpdateAt' => 0,
+ 'newConversionEventsLastUpdateAt' => 0,
),
$options->get( Settings::OPTION )
);
@@ -585,6 +587,8 @@ function ( Request $request ) use ( $property_id, $webdatastream_id, $measuremen
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => false,
'detectedEvents' => array(),
+ 'lostConversionEventsLastUpdateAt' => 0,
+ 'newConversionEventsLastUpdateAt' => 0,
),
$options->get( Settings::OPTION )
);
@@ -712,6 +716,8 @@ function ( Request $request ) use ( $property_id, $webdatastream_id, $measuremen
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => null,
'detectedEvents' => array(),
+ 'lostConversionEventsLastUpdateAt' => 0,
+ 'newConversionEventsLastUpdateAt' => 0,
),
$options->get( Settings::OPTION )
);
@@ -840,6 +846,8 @@ function ( Request $request ) use ( $property_id, $webdatastream_id, $measuremen
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => null,
'detectedEvents' => array(),
+ 'lostConversionEventsLastUpdateAt' => 0,
+ 'newConversionEventsLastUpdateAt' => 0,
),
$options->get( Settings::OPTION )
);
@@ -877,6 +885,8 @@ function ( Request $request ) use ( $property_id, $webdatastream_id, $measuremen
'availableAudiencesLastSyncedAt' => 0,
'audienceSegmentationSetupCompletedBy' => false,
'detectedEvents' => array(),
+ 'lostConversionEventsLastUpdateAt' => 0,
+ 'newConversionEventsLastUpdateAt' => 0,
),
$options->get( Settings::OPTION )
);
@@ -1307,8 +1317,6 @@ public function test_get_datapoints__conversionReporting() {
'sync-custom-dimensions',
'custom-dimension-data-available',
'set-google-tag-id-mismatch',
- 'clear-conversion-reporting-new-events',
- 'clear-conversion-reporting-lost-events',
),
$this->analytics->get_datapoints()
);
diff --git a/tests/phpunit/integration/Modules/Reader_Revenue_ManagerTest.php b/tests/phpunit/integration/Modules/Reader_Revenue_ManagerTest.php
index 6f188e46816..bcf3d83e181 100644
--- a/tests/phpunit/integration/Modules/Reader_Revenue_ManagerTest.php
+++ b/tests/phpunit/integration/Modules/Reader_Revenue_ManagerTest.php
@@ -99,7 +99,7 @@ public function test_magic_methods() {
$this->assertEquals( 'Reader Revenue Manager', $this->reader_revenue_manager->name );
$this->assertEquals( 'https://publishercenter.google.com', $this->reader_revenue_manager->homepage );
$this->assertEquals( 'Reader Revenue Manager helps publishers grow, retain, and engage their audiences, creating new revenue opportunities', $this->reader_revenue_manager->description );
- $this->assertEquals( 5, $this->reader_revenue_manager->order );
+ $this->assertEquals( 10, $this->reader_revenue_manager->order ); // Since order is not set, it uses the default value.
}
public function test_get_scopes() {