diff --git a/assets/js/components/GoogleChart/index.js b/assets/js/components/GoogleChart/index.js index cea36eda427..c3136c7a86f 100644 --- a/assets/js/components/GoogleChart/index.js +++ b/assets/js/components/GoogleChart/index.js @@ -97,7 +97,7 @@ export default function GoogleChart( props ) { const breakpoint = useBreakpoint(); const { startDate, endDate } = useSelect( ( select ) => - select( CORE_USER ).getDateRangeDates() + select( CORE_USER ).getDateRangeDates( { offsetDays: 0 } ) ); const viewContext = useViewContext(); diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/CustomDimensionsNotice.js b/assets/js/components/KeyMetrics/MetricsSelectionPanel/CustomDimensionsNotice.js index 9a6dac10642..1fbb590fc2d 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/CustomDimensionsNotice.js +++ b/assets/js/components/KeyMetrics/MetricsSelectionPanel/CustomDimensionsNotice.js @@ -89,7 +89,7 @@ function CustomDimensionsNotice() { if ( currentFocusedElement && currentFocusedElement.closest( - '.googlesitekit-km-selection-panel-metrics__metric-item' + '.googlesitekit-selection-panel-item' ) && elementsOverlap( noticeRef.current, currentFocusedElement ) ) { @@ -113,10 +113,7 @@ function CustomDimensionsNotice() { ); return ( -
+

{ customDimensionMessage }

); diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/Footer.js b/assets/js/components/KeyMetrics/MetricsSelectionPanel/Footer.js index 9b61d7f82b0..6e8beafbb85 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/Footer.js +++ b/assets/js/components/KeyMetrics/MetricsSelectionPanel/Footer.js @@ -19,34 +19,24 @@ /** * External dependencies */ -import { isEqual } from 'lodash'; import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { - useCallback, - useEffect, - useState, - useMemo, - createInterpolateElement, -} from '@wordpress/element'; +import { useCallback } from '@wordpress/element'; import { addQueryArgs } from '@wordpress/url'; import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ -import { Button, SpinnerButton } from 'googlesitekit-components'; import Data from 'googlesitekit-data'; import { CORE_USER } from '../../../googlesitekit/datastore/user/constants'; import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants'; import { CORE_LOCATION } from '../../../googlesitekit/datastore/location/constants'; import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants'; -import { CORE_UI } from '../../../googlesitekit/datastore/ui/constants'; import { - KEY_METRICS_SELECTION_PANEL_OPENED_KEY, KEY_METRICS_SELECTED, KEY_METRICS_SELECTION_FORM, MIN_SELECTED_METRICS_COUNT, @@ -59,13 +49,14 @@ import { } from '../../../modules/analytics-4/datastore/constants'; import { KEY_METRICS_WIDGETS } from '../key-metrics-widgets'; import { ERROR_CODE_MISSING_REQUIRED_SCOPE } from '../../../util/errors'; -import ErrorNotice from '../../ErrorNotice'; -import { safelySort } from './utils'; import useViewContext from '../../../hooks/useViewContext'; import { trackEvent } from '../../../util'; +import { SelectionPanelFooter } from '../../SelectionPanel'; const { useSelect, useDispatch } = Data; export default function Footer( { + isOpen, + closePanel, savedMetrics, onNavigationToOAuthURL = () => {}, } ) { @@ -85,14 +76,6 @@ export default function Footer( { ); const trackingCategory = `${ viewContext }_kmw-sidebar`; - const haveSettingsChanged = useMemo( () => { - // Arrays need to be sorted to match in `isEqual`. - return ! isEqual( - safelySort( selectedMetrics ), - safelySort( savedMetrics ) - ); - }, [ savedMetrics, selectedMetrics ] ); - const requiredCustomDimensions = selectedMetrics?.flatMap( ( tileName ) => { const tile = KEY_METRICS_WIDGETS[ tileName ]; return tile?.requiredCustomDimensions || []; @@ -147,109 +130,70 @@ export default function Footer( { return select( CORE_LOCATION ).isNavigatingTo( OAuthURL ); } ); - const isOpen = useSelect( ( select ) => - select( CORE_UI ).getValue( KEY_METRICS_SELECTION_PANEL_OPENED_KEY ) - ); - const { saveKeyMetricsSettings, setPermissionScopeError } = useDispatch( CORE_USER ); - const { setValue } = useDispatch( CORE_UI ); const { setValues } = useDispatch( CORE_FORMS ); - const [ finalButtonText, setFinalButtonText ] = useState( null ); - const [ wasSaved, setWasSaved ] = useState( false ); - - const currentButtonText = - savedMetrics?.length > 0 && haveSettingsChanged - ? __( 'Apply changes', 'google-site-kit' ) - : __( 'Save selection', 'google-site-kit' ); - - const onSaveClick = useCallback( async () => { - const { error } = await saveKeyMetricsSettings( { - widgetSlugs: selectedMetrics, - } ); - - if ( ! error ) { - trackEvent( trackingCategory, 'metrics_sidebar_save' ); + const saveSettings = useCallback( + async ( widgetSlugs ) => { + // We could simply return the value of `saveKeyMetricsSettings()` here, + // but this makes the expected return value more explicit. + const { error } = await saveKeyMetricsSettings( { + widgetSlugs, + } ); + + return { error }; + }, + [ saveKeyMetricsSettings ] + ); - if ( isGA4Connected && hasMissingCustomDimensions ) { - setValues( FORM_CUSTOM_DIMENSIONS_CREATE, { - autoSubmit: true, + const onSaveSuccess = useCallback( () => { + trackEvent( trackingCategory, 'metrics_sidebar_save' ); + + if ( isGA4Connected && hasMissingCustomDimensions ) { + setValues( FORM_CUSTOM_DIMENSIONS_CREATE, { + autoSubmit: true, + } ); + + if ( ! hasAnalytics4EditScope ) { + // Let parent component know that the user is navigating to OAuth URL + // so that the panel is kept open. + onNavigationToOAuthURL(); + + // Ensure the panel is closed, just in case the user navigates to + // the OAuth URL before the function is fully executed. + closePanel(); + + setPermissionScopeError( { + code: ERROR_CODE_MISSING_REQUIRED_SCOPE, + message: __( + 'Additional permissions are required to create new Analytics custom dimensions', + 'google-site-kit' + ), + data: { + status: 403, + scopes: [ EDIT_SCOPE ], + skipModal: true, + redirectURL, + }, } ); - - if ( ! hasAnalytics4EditScope ) { - // Let parent component know that the user is navigating to OAuth URL - // so that the panel is kept open. - onNavigationToOAuthURL(); - - // Ensure the state is set, just in case the user navigates to the - // OAuth URL before the function is fully executed. - setValue( KEY_METRICS_SELECTION_PANEL_OPENED_KEY, false ); - - setPermissionScopeError( { - code: ERROR_CODE_MISSING_REQUIRED_SCOPE, - message: __( - 'Additional permissions are required to create new Analytics custom dimensions', - 'google-site-kit' - ), - data: { - status: 403, - scopes: [ EDIT_SCOPE ], - skipModal: true, - redirectURL, - }, - } ); - } } - - // If the state has not been set to `false` yet, set it now. - if ( isOpen ) { - setValue( KEY_METRICS_SELECTION_PANEL_OPENED_KEY, false ); - } - - // lock the button label while panel is closing - setFinalButtonText( currentButtonText ); - setWasSaved( true ); } }, [ - saveKeyMetricsSettings, - selectedMetrics, trackingCategory, isGA4Connected, hasMissingCustomDimensions, - isOpen, - currentButtonText, setValues, hasAnalytics4EditScope, onNavigationToOAuthURL, - setValue, + closePanel, setPermissionScopeError, redirectURL, ] ); - const onCancelClick = useCallback( () => { - setValue( KEY_METRICS_SELECTION_PANEL_OPENED_KEY, false ); + const onCancel = useCallback( () => { trackEvent( trackingCategory, 'metrics_sidebar_cancel' ); - }, [ setValue, trackingCategory ] ); - - const [ prevIsOpen, setPrevIsOpen ] = useState( null ); - - useEffect( () => { - if ( prevIsOpen !== null ) { - // if current isOpen is true, and different from prevIsOpen - // meaning it transitioned from false to true and it is not - // in closing transition, we should reset the button label - // locked when save button was clicked - if ( prevIsOpen !== isOpen ) { - if ( isOpen ) { - setFinalButtonText( null ); - setWasSaved( false ); - } - } - } - - setPrevIsOpen( isOpen ); - }, [ isOpen, prevIsOpen ] ); + }, [ trackingCategory ] ); const selectedMetricsCount = selectedMetrics?.length || 0; let metricsLimitError; @@ -277,67 +221,26 @@ export default function Footer( { } return ( - + ); } Footer.propTypes = { + isOpen: PropTypes.bool, + closePanel: PropTypes.func.isRequired, savedMetrics: PropTypes.array, onNavigationToOAuthURL: PropTypes.func, }; diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/Header.js b/assets/js/components/KeyMetrics/MetricsSelectionPanel/Header.js index 3229cc4aecc..75bf76f7971 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/Header.js +++ b/assets/js/components/KeyMetrics/MetricsSelectionPanel/Header.js @@ -16,6 +16,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + /** * WordPress dependencies */ @@ -28,15 +33,13 @@ import { __ } from '@wordpress/i18n'; import Data from 'googlesitekit-data'; import { CORE_LOCATION } from '../../../googlesitekit/datastore/location/constants'; import { CORE_SITE } from '../../../googlesitekit/datastore/site/constants'; -import { CORE_UI } from '../../../googlesitekit/datastore/ui/constants'; import { CORE_USER } from '../../../googlesitekit/datastore/user/constants'; -import { KEY_METRICS_SELECTION_PANEL_OPENED_KEY } from '../constants'; import Link from '../../Link'; -import CloseIcon from '../../../../svg/icons/close.svg'; +import { SelectionPanelHeader } from '../../SelectionPanel'; import useViewOnly from '../../../hooks/useViewOnly'; const { useSelect, useDispatch } = Data; -export default function Header() { +export default function Header( { closePanel } ) { const isViewOnly = useViewOnly(); const settingsURL = useSelect( ( select ) => @@ -46,30 +49,18 @@ export default function Header() { select( CORE_USER ).isSavingKeyMetricsSettings() ); - const { setValue } = useDispatch( CORE_UI ); const { navigateTo } = useDispatch( CORE_LOCATION ); - const onCloseClick = useCallback( () => { - setValue( KEY_METRICS_SELECTION_PANEL_OPENED_KEY, false ); - }, [ setValue ] ); - const onSettingsClick = useCallback( () => navigateTo( `${ settingsURL }#/admin-settings` ), [ navigateTo, settingsURL ] ); return ( -
-
-

{ __( 'Select your metrics', 'google-site-kit' ) }

- - - -
+ { ! isViewOnly && (

{ createInterpolateElement( @@ -90,6 +81,10 @@ export default function Header() { ) }

) } -
+ ); } + +Header.propTypes = { + closePanel: PropTypes.func.isRequired, +}; diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItem.js b/assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItem.js index fb27efb353d..6242fc9bbba 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItem.js +++ b/assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItem.js @@ -32,18 +32,32 @@ import { __, _n, sprintf } from '@wordpress/i18n'; */ import Data from 'googlesitekit-data'; import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants'; +import { CORE_WIDGETS } from '../../../googlesitekit/widgets/datastore/constants'; +import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants'; import { KEY_METRICS_SELECTED, KEY_METRICS_SELECTION_FORM } from '../constants'; -import SelectionBox from '../../SelectionBox'; +import { SelectionPanelItem } from '../../SelectionPanel'; const { useSelect, useDispatch } = Data; export default function MetricItem( { - id, slug, title, description, - disconnectedModules, - savedMetrics = [], + savedItemSlugs = [], } ) { + const disconnectedModules = useSelect( ( select ) => { + const { getModule } = select( CORE_MODULES ); + const widget = select( CORE_WIDGETS ).getWidget( slug ); + + return widget?.modules.reduce( ( modulesAcc, widgetSlug ) => { + const module = getModule( widgetSlug ); + if ( module?.connected || ! module?.name ) { + return modulesAcc; + } + + return [ ...modulesAcc, module.name ]; + }, [] ); + } ); + const selectedMetrics = useSelect( ( select ) => select( CORE_FORMS ).getValue( KEY_METRICS_SELECTION_FORM, @@ -74,45 +88,43 @@ export default function MetricItem( { const isMetricSelected = selectedMetrics?.includes( slug ); const isMetricDisabled = - ! savedMetrics.includes( slug ) && disconnectedModules.length > 0; + ! savedItemSlugs.includes( slug ) && disconnectedModules.length > 0; + + const id = `key-metric-selection-checkbox-${ slug }`; return ( -
- - { description } - { disconnectedModules.length > 0 && ( -
- { sprintf( - /* translators: %s: module names. */ - _n( - '%s is disconnected, no data to show', - '%s are disconnected, no data to show', - disconnectedModules.length, - 'google-site-kit' - ), - disconnectedModules.join( - __( ' and ', 'google-site-kit' ) - ) - ) } -
- ) } -
-
+ + { disconnectedModules.length > 0 && ( +
+ { sprintf( + /* translators: %s: module names. */ + _n( + '%s is disconnected, no data to show', + '%s are disconnected, no data to show', + disconnectedModules.length, + 'google-site-kit' + ), + disconnectedModules.join( + __( ' and ', 'google-site-kit' ) + ) + ) } +
+ ) } +
); } MetricItem.propTypes = { - id: PropTypes.string.isRequired, slug: PropTypes.string.isRequired, title: PropTypes.string.isRequired, description: PropTypes.string.isRequired, - disconnectedModules: PropTypes.array, - savedMetrics: PropTypes.array, + savedItemSlugs: PropTypes.array, }; diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/Metrics.js b/assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItems.js similarity index 51% rename from assets/js/components/KeyMetrics/MetricsSelectionPanel/Metrics.js rename to assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItems.js index 439d06799ff..4e2928c1944 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/Metrics.js +++ b/assets/js/components/KeyMetrics/MetricsSelectionPanel/MetricItems.js @@ -33,22 +33,19 @@ import Data from 'googlesitekit-data'; import { AREA_MAIN_DASHBOARD_KEY_METRICS_PRIMARY } from '../../../googlesitekit/widgets/default-areas'; import { CORE_USER } from '../../../googlesitekit/datastore/user/constants'; import { CORE_WIDGETS } from '../../../googlesitekit/widgets/datastore/constants'; -import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants'; import { KEY_METRICS_WIDGETS } from '../key-metrics-widgets'; import MetricItem from './MetricItem'; +import { SelectionPanelItems } from '../../SelectionPanel'; import useViewOnly from '../../../hooks/useViewOnly'; -import { Fragment } from '@wordpress/element'; const { useSelect } = Data; -export default function Metrics( { savedMetrics } ) { +export default function MetricItems( { savedMetrics } ) { const isViewOnlyDashboard = useViewOnly(); const { isKeyMetricAvailable } = useSelect( ( select ) => select( CORE_USER ) ); - const { getModule } = useSelect( ( select ) => select( CORE_MODULES ) ); - const displayInList = useSelect( ( select ) => ( metric ) => KEY_METRICS_WIDGETS[ metric ].displayInList( @@ -57,41 +54,26 @@ export default function Metrics( { savedMetrics } ) { ) ); - const getWidget = useSelect( - ( select ) => ( metric ) => select( CORE_WIDGETS ).getWidget( metric ) - ); - - const metricsListReducer = ( acc, metric ) => { - if ( ! isKeyMetricAvailable( metric ) ) { + const metricsListReducer = ( acc, metricSlug ) => { + if ( ! isKeyMetricAvailable( metricSlug ) ) { return acc; } if ( - typeof KEY_METRICS_WIDGETS[ metric ].displayInList === 'function' && - ! displayInList( metric ) + typeof KEY_METRICS_WIDGETS[ metricSlug ].displayInList === + 'function' && + ! displayInList( metricSlug ) ) { return acc; } - const widget = getWidget( metric ); - - const disconnectedModules = widget.modules.reduce( - ( modulesAcc, slug ) => { - const module = getModule( slug ); - if ( module?.connected || ! module?.name ) { - return modulesAcc; - } - - return [ ...modulesAcc, module.name ]; - }, - [] - ); + const { title, description } = KEY_METRICS_WIDGETS[ metricSlug ]; return { ...acc, - [ metric ]: { - ...KEY_METRICS_WIDGETS[ metric ], - disconnectedModules, + [ metricSlug ]: { + title, + description, }, }; }; @@ -119,53 +101,20 @@ export default function Metrics( { savedMetrics } ) { } ) .reduce( metricsListReducer, {} ); - const renderMetricItems = ( metricSlugs ) => { - return Object.keys( metricSlugs ).map( ( slug ) => { - const { title, description, disconnectedModules } = - metricSlugs[ slug ]; - - const id = `key-metric-selection-checkbox-${ slug }`; - - return ( - - ); - } ); - }; - return ( -
- { - // Split list into two sections with sub-headings for current selection and - // additional metrics if there are already saved metrics. - savedMetrics.length !== 0 && ( - -

- { __( 'Current selection', 'google-site-kit' ) } -

-
- { renderMetricItems( availableSavedMetrics ) } -
-

- { __( 'Additional metrics', 'google-site-kit' ) } -

-
- ) - } -
- { renderMetricItems( availableUnsavedMetrics ) } -
-
+ ); } -Metrics.propTypes = { +MetricItems.propTypes = { savedMetrics: PropTypes.array, }; diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/index.js b/assets/js/components/KeyMetrics/MetricsSelectionPanel/index.js index 49ce12c9bd6..30e0f02bc73 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/index.js +++ b/assets/js/components/KeyMetrics/MetricsSelectionPanel/index.js @@ -33,11 +33,11 @@ import { KEY_METRICS_SELECTION_FORM, KEY_METRICS_SELECTION_PANEL_OPENED_KEY, } from '../constants'; -import SideSheet from '../../SideSheet'; +import CustomDimensionsNotice from './CustomDimensionsNotice'; import Header from './Header'; import Footer from './Footer'; -import Metrics from './Metrics'; -import CustomDimensionsNotice from './CustomDimensionsNotice'; +import MetricItems from './MetricItems'; +import SelectionPanel from '../../SelectionPanel'; import useViewContext from '../../../hooks/useViewContext'; import { trackEvent } from '../../../util'; const { useSelect, useDispatch } = Data; @@ -69,7 +69,7 @@ export default function MetricsSelectionPanel() { trackEvent( `${ viewContext }_kmw-sidebar`, 'metrics_sidebar_view' ); }, [ savedViewableMetrics, setValues, viewContext ] ); - const sideSheetCloseFn = useCallback( () => { + const closePanel = useCallback( () => { if ( isOpen ) { setValue( KEY_METRICS_SELECTION_PANEL_OPENED_KEY, false ); } @@ -79,25 +79,23 @@ export default function MetricsSelectionPanel() { useState( false ); return ( - -
- +
+
- { ! adsModuleEnabled && useSnippet && ( -
-
-
- { __( 'Ads Conversion ID', 'google-site-kit' ) } -
-

- { !! adsConversionID && ( - - ) } - { ! adsConversionID && - __( 'None', 'google-site-kit' ) } -

+ { /* Prevent the Ads Conversion ID setting displaying after this field has been + migrated to the Ads module, even after resetting the Analytics module. */ } + { useSnippet && + ! adsConversionIDMigratedAtMs && + !! adsConversionID && ( +
+
+
+ { __( 'Ads Conversion ID', 'google-site-kit' ) } +
+

+ { !! adsConversionID && ( + + ) } + { ! adsConversionID && + __( 'None', 'google-site-kit' ) } +

+
-
- ) } + ) } - { adsModuleEnabled && } + ); } diff --git a/assets/js/modules/analytics-4/components/settings/SettingsForm.js b/assets/js/modules/analytics-4/components/settings/SettingsForm.js index 57e4e6a0e5d..34b0f776c11 100644 --- a/assets/js/modules/analytics-4/components/settings/SettingsForm.js +++ b/assets/js/modules/analytics-4/components/settings/SettingsForm.js @@ -30,18 +30,15 @@ import { Fragment } from '@wordpress/element'; * Internal dependencies */ import Data from 'googlesitekit-data'; -import { AdsConversionIDTextField, TrackingExclusionSwitches } from '../common'; +import { TrackingExclusionSwitches } from '../common'; import { MODULES_ANALYTICS_4 } from '../../datastore/constants'; import SettingsControls from './SettingsControls'; import AdsConversionIDSettingsNotice from './AdsConversionIDSettingsNotice'; import EntityOwnershipChangeNotice from '../../../../components/settings/EntityOwnershipChangeNotice'; import { isValidAccountID } from '../../utils/validation'; -import { useFeature } from '../../../../hooks/useFeature'; const { useSelect } = Data; export default function SettingsForm( { hasModuleAccess } ) { - const adsModuleEnabled = useFeature( 'adsModule' ); - const accountID = useSelect( ( select ) => select( MODULES_ANALYTICS_4 ).getAccountID() ); @@ -53,8 +50,7 @@ export default function SettingsForm( { hasModuleAccess } ) { { isValidAccountID( accountID ) && ( - { ! adsModuleEnabled && } - { adsModuleEnabled && } + ) } diff --git a/assets/js/modules/analytics-4/components/widgets/PopularProductsWidget.js b/assets/js/modules/analytics-4/components/widgets/PopularProductsWidget.js index d8e95b4d0a6..3f1fbc52af4 100644 --- a/assets/js/modules/analytics-4/components/widgets/PopularProductsWidget.js +++ b/assets/js/modules/analytics-4/components/widgets/PopularProductsWidget.js @@ -62,7 +62,7 @@ const { useSelect, useInViewSelect, useDispatch } = Data; /** * Gets the report options for the Popular Products widget. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Function} select Data store 'select' function. * @return {Object} The report options. diff --git a/assets/js/modules/analytics-4/datastore/audiences.js b/assets/js/modules/analytics-4/datastore/audiences.js index da77ca953b8..97dd2cce856 100644 --- a/assets/js/modules/analytics-4/datastore/audiences.js +++ b/assets/js/modules/analytics-4/datastore/audiences.js @@ -136,7 +136,7 @@ const baseSelectors = { /** * Checks if the given audience is a default audience. * - * @since n.e.x.t + * @since 1.127.0 * * @param {string} audienceResourceName The audience resource name. * @param {Object} state Data store's state. @@ -162,7 +162,7 @@ const baseSelectors = { /** * Checks if the given audience is a Site Kit-created audience. * - * @since n.e.x.t + * @since 1.127.0 * * @param {string} audienceResourceName The audience resource name. * @param {Object} state Data store's state. @@ -188,7 +188,7 @@ const baseSelectors = { /** * Checks if the given audience is a user-defined audience. * - * @since n.e.x.t + * @since 1.127.0 * * @param {string} audienceResourceName The audience resource name. * @param {Object} state Data store's state. @@ -214,7 +214,7 @@ const baseSelectors = { /** * Checks whether the provided audiences are available. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string|Array} audienceResourceNames The audience resource names to check. diff --git a/assets/js/modules/analytics-4/datastore/partial-data.js b/assets/js/modules/analytics-4/datastore/partial-data.js index c0cd9bc0f5d..360d6f6ab90 100644 --- a/assets/js/modules/analytics-4/datastore/partial-data.js +++ b/assets/js/modules/analytics-4/datastore/partial-data.js @@ -91,7 +91,7 @@ const baseActions = { /** * Receives the data availability dates for all resources. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} resourceAvailabilityDates Resource data availability dates. * @return {Object} Redux-style action. @@ -111,7 +111,7 @@ const baseActions = { /** * Sets the data availability date for a specific resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param {string} resourceSlug Resource slug. * @param {string} resourceType Resource type. @@ -352,7 +352,7 @@ const baseSelectors = { /** * Gets the data availability date for all resources. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @return {Object} Resource data availability dates. Undefined if not loaded. @@ -364,7 +364,7 @@ const baseSelectors = { /** * Gets the data availability date for a specific resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string} resourceSlug Resource slug. @@ -380,7 +380,7 @@ const baseSelectors = { /** * Gets whether the given resource is in partial data state. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string} resourceSlug Resource slug. @@ -437,7 +437,7 @@ const baseSelectors = { /** * Gets whether the given audience is in partial data state. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string} audienceSlug Audience slug. @@ -454,7 +454,7 @@ const baseSelectors = { /** * Gets whether the given custom dimension is in partial data state. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string} customDimensionSlug Custom dimension slug. @@ -471,7 +471,7 @@ const baseSelectors = { /** * Gets whether the given property is in partial data state. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string} propertyID Property ID. @@ -488,7 +488,7 @@ const baseSelectors = { /** * Gets report options for determining data availability date for a given resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param {Object} state Data store's state. * @param {string} resourceSlug Resource slug. diff --git a/assets/js/modules/analytics-4/datastore/settings.js b/assets/js/modules/analytics-4/datastore/settings.js index eacc4118d21..3258c780052 100644 --- a/assets/js/modules/analytics-4/datastore/settings.js +++ b/assets/js/modules/analytics-4/datastore/settings.js @@ -50,6 +50,7 @@ import { WEBDATASTREAM_CREATE, } from './constants'; import { isValidConversionID } from '../../ads/utils/validation'; +import { CORE_SITE } from '../../../googlesitekit/datastore/site/constants'; // Invariant error messages. export const INVARIANT_INVALID_PROPERTY_SELECTION = @@ -139,57 +140,95 @@ export async function submitChanges( { select, dispatch } ) { // Only make the API request to enable the Enhanced Measurement setting, not to disable it. if ( isEnhancedMeasurementEnabled ) { - await dispatch( - MODULES_ANALYTICS_4 - ).setEnhancedMeasurementStreamEnabled( + const { error } = await updateEnhancedMeasurementSettings( { + select, + dispatch, propertyID, webDataStreamID, - isEnhancedMeasurementEnabled - ); + isEnhancedMeasurementEnabled, + } ); - if ( - select( - MODULES_ANALYTICS_4 - ).haveEnhancedMeasurementSettingsChanged( - propertyID, - webDataStreamID - ) - ) { - const { error } = await dispatch( - MODULES_ANALYTICS_4 - ).updateEnhancedMeasurementSettings( - propertyID, - webDataStreamID - ); - - if ( error ) { - return { error }; - } - - const shouldDismissActivationBanner = select( - CORE_FORMS - ).getValue( - ENHANCED_MEASUREMENT_FORM, - ENHANCED_MEASUREMENT_SHOULD_DISMISS_ACTIVATION_BANNER - ); - - if ( shouldDismissActivationBanner ) { - await dispatch( CORE_USER ).dismissItem( - ENHANCED_MEASUREMENT_ACTIVATION_BANNER_DISMISSED_ITEM_KEY - ); - } + if ( error ) { + return { error }; } } } - if ( select( MODULES_ANALYTICS_4 ).haveSettingsChanged() ) { + const { error } = await saveSettings( select, dispatch ); + + if ( error ) { + return error; + } + + await API.invalidateCache( 'modules', 'analytics-4' ); + + return {}; +} + +async function saveSettings( select, dispatch ) { + const haveSettingsChanged = + select( MODULES_ANALYTICS_4 ).haveSettingsChanged(); + + if ( haveSettingsChanged ) { const { error } = await dispatch( MODULES_ANALYTICS_4 ).saveSettings(); if ( error ) { return { error }; } } - await API.invalidateCache( 'modules', 'analytics-4' ); + const haveConversionTrackingSettingsChanged = + select( CORE_SITE ).haveConversionTrackingSettingsChanged(); + if ( haveConversionTrackingSettingsChanged ) { + const { error } = await dispatch( + CORE_SITE + ).saveConversionTrackingSettings(); + + if ( error ) { + return { error }; + } + } + + return {}; +} + +async function updateEnhancedMeasurementSettings( { + select, + dispatch, + propertyID, + webDataStreamID, + isEnhancedMeasurementEnabled, +} ) { + await dispatch( MODULES_ANALYTICS_4 ).setEnhancedMeasurementStreamEnabled( + propertyID, + webDataStreamID, + isEnhancedMeasurementEnabled + ); + + if ( + select( MODULES_ANALYTICS_4 ).haveEnhancedMeasurementSettingsChanged( + propertyID, + webDataStreamID + ) + ) { + const { error } = await dispatch( + MODULES_ANALYTICS_4 + ).updateEnhancedMeasurementSettings( propertyID, webDataStreamID ); + + if ( error ) { + return { error }; + } + + const shouldDismissActivationBanner = select( CORE_FORMS ).getValue( + ENHANCED_MEASUREMENT_FORM, + ENHANCED_MEASUREMENT_SHOULD_DISMISS_ACTIVATION_BANNER + ); + + if ( shouldDismissActivationBanner ) { + await dispatch( CORE_USER ).dismissItem( + ENHANCED_MEASUREMENT_ACTIVATION_BANNER_DISMISSED_ITEM_KEY + ); + } + } return {}; } diff --git a/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.js b/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.js index 2e03422d7ba..925f42026f1 100644 --- a/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.js +++ b/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.js @@ -28,7 +28,6 @@ import Data from 'googlesitekit-data'; import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants'; import { MODULES_ANALYTICS_4 } from '../datastore/constants'; import { MODULES_ADS } from '../../ads/datastore/constants'; -import { useFeature } from '../../../hooks/useFeature'; const { useSelect, useDispatch } = Data; @@ -42,8 +41,6 @@ const { useSelect, useDispatch } = Data; export default function useMigrateAdsConversionID() { const [ loading, setLoading ] = useState( false ); - const adsModuleEnabled = useFeature( 'adsModule' ); - const legacyAdsConversionID = useSelect( ( select ) => select( MODULES_ANALYTICS_4 ).getAdsConversionID() ); @@ -60,7 +57,7 @@ export default function useMigrateAdsConversionID() { select( CORE_MODULES ).isModuleConnected( 'ads' ) ); const adsConversionID = useSelect( ( select ) => { - if ( ! adsModuleEnabled || ! adsModuleAvailable ) { + if ( ! adsModuleAvailable ) { return null; } @@ -74,12 +71,10 @@ export default function useMigrateAdsConversionID() { submitChanges: submitAnalyticsChanges, } = useDispatch( MODULES_ANALYTICS_4 ); - // TODO: Destructure actions here when the `adsModule` feature flag is removed. - const dispatch = useDispatch( MODULES_ADS ); + const { setConversionID, submitChanges } = useDispatch( MODULES_ADS ); useEffect( () => { if ( - ! adsModuleEnabled || isDoingSubmitChanges || loading || ! adsModuleAvailable || @@ -93,8 +88,8 @@ export default function useMigrateAdsConversionID() { const migrate = async () => { setLoading( true ); - await dispatch.setConversionID( legacyAdsConversionID ); - await dispatch.submitChanges(); + await setConversionID( legacyAdsConversionID ); + await submitChanges(); await setLegacyAdsConversionID( '' ); await setAdsConversionIDMigratedAtMs( Date.now() ); @@ -120,15 +115,15 @@ export default function useMigrateAdsConversionID() { adsModuleActive, adsModuleAvailable, adsModuleConnected, - adsModuleEnabled, - dispatch, fetchGetModules, isDoingSubmitChanges, legacyAdsConversionID, loading, setAdsConversionIDMigratedAtMs, + setConversionID, setLegacyAdsConversionID, submitAnalyticsChanges, + submitChanges, ] ); return loading; diff --git a/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.test.js b/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.test.js index 59ccc0fd66f..a7a8ef1ad4c 100644 --- a/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.test.js +++ b/assets/js/modules/analytics-4/hooks/useMigrateAdsConversionID.test.js @@ -61,7 +61,6 @@ describe( 'useMigrateAdsConversionID', () => { renderHook( () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); expect( @@ -83,7 +82,6 @@ describe( 'useMigrateAdsConversionID', () => { renderHook( () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); // Verify that the value has not changed. @@ -104,7 +102,6 @@ describe( 'useMigrateAdsConversionID', () => { renderHook( () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); expect( fetchMock ).not.toHaveFetched(); @@ -124,7 +121,6 @@ describe( 'useMigrateAdsConversionID', () => { renderHook( () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); expect( fetchMock ).not.toHaveFetched(); @@ -144,7 +140,6 @@ describe( 'useMigrateAdsConversionID', () => { () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); @@ -178,7 +173,6 @@ describe( 'useMigrateAdsConversionID', () => { () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); @@ -237,7 +231,6 @@ describe( 'useMigrateAdsConversionID', () => { () => useMigrateAdsConversionID(), { registry, - features: [ 'adsModule' ], } ); diff --git a/assets/js/modules/tagmanager/datastore/versions.js b/assets/js/modules/tagmanager/datastore/versions.js index 0da7a4c6e20..81485ff4afb 100644 --- a/assets/js/modules/tagmanager/datastore/versions.js +++ b/assets/js/modules/tagmanager/datastore/versions.js @@ -60,11 +60,18 @@ const fetchGetLiveContainerVersionStore = createFetchStore( { { useCache: false } ); } catch ( err ) { - // If the container has no published version, it will error with a 404. - if ( 404 === err.code ) { + // If the container has no published version, it will error with a 404 + // and the message will be "Published container version not found". + // If the user has no permission to access the container, the error is also a 404 + // with a different message. In this case or any other case, we want to display + // the error message along with the option to retry, so we allow it to be thrown + // but filter out the former case. + if ( + 404 === err.code && + err.message.includes( 'container version not found' ) + ) { return null; } - // Otherwise rethrow the error to be handled as usual. throw err; } }, diff --git a/assets/js/modules/tagmanager/datastore/versions.test.js b/assets/js/modules/tagmanager/datastore/versions.test.js index c0c8c1d3872..5b5f8e04ade 100644 --- a/assets/js/modules/tagmanager/datastore/versions.test.js +++ b/assets/js/modules/tagmanager/datastore/versions.test.js @@ -298,6 +298,53 @@ describe( 'modules/tagmanager versions', () => { expect( console ).toHaveErrored(); } ); + it( 'dispatches an error if the request fails when access is denied', async () => { + const { accountID, internalContainerID } = parseIDs( + factories.buildLiveContainerVersionWeb() + ); + const permissionDeniedResponse = { + code: 404, + message: 'Not found or permission denied.', + data: { + status: 404, + reason: 'notFound', + }, + }; + fetchMock.getOnce( + new RegExp( + '^/google-site-kit/v1/modules/tagmanager/data/live-container-version' + ), + { body: permissionDeniedResponse, status: 404 } + ); + + registry + .select( MODULES_TAGMANAGER ) + .getLiveContainerVersion( accountID, internalContainerID ); + await untilResolved( + registry, + MODULES_TAGMANAGER + ).getLiveContainerVersion( accountID, internalContainerID ); + + expect( fetchMock ).toHaveFetchedTimes( 1 ); + expect( + registry + .select( MODULES_TAGMANAGER ) + .getErrorForSelector( 'getLiveContainerVersion', [ + accountID, + internalContainerID, + ] ) + ).toEqual( permissionDeniedResponse ); + expect( + registry + .select( MODULES_TAGMANAGER ) + .getLiveContainerVersion( + accountID, + internalContainerID + ) + ).toEqual( undefined ); + expect( console ).toHaveErrored(); + } ); + it( 'receives null if the container has no published version', async () => { const { accountID, internalContainerID } = parseIDs( factories.buildLiveContainerVersionWeb() diff --git a/assets/js/util/date-range/string-to-date.test.js b/assets/js/util/date-range/string-to-date.test.js index f395a41261b..69833ee4f6c 100644 --- a/assets/js/util/date-range/string-to-date.test.js +++ b/assets/js/util/date-range/string-to-date.test.js @@ -32,6 +32,14 @@ describe( 'stringToDate', () => { } ); + it( 'uses a zero-indexed month', () => { + const date = stringToDate( '2019-01-31' ); + + expect( date.getFullYear() ).toBe( 2019 ); + expect( date.getMonth() ).toBe( 0 ); + expect( date.getDate() ).toBe( 31 ); + } ); + it( 'returns a valid date instance for the given date string', () => { const date = stringToDate( '2019-10-31' ); diff --git a/assets/js/util/index.js b/assets/js/util/index.js index ce04d48950f..cbee2aac599 100644 --- a/assets/js/util/index.js +++ b/assets/js/util/index.js @@ -37,6 +37,7 @@ export * from './chart'; export * from './urls'; export * from './is-valid-numeric-id'; export * from './isnumeric'; +export * from './safely-sort'; global._gsktag = trackEvent; /** diff --git a/assets/js/components/KeyMetrics/MetricsSelectionPanel/utils.js b/assets/js/util/safely-sort.js similarity index 84% rename from assets/js/components/KeyMetrics/MetricsSelectionPanel/utils.js rename to assets/js/util/safely-sort.js index 2d1d872fff0..912ceb9639d 100644 --- a/assets/js/components/KeyMetrics/MetricsSelectionPanel/utils.js +++ b/assets/js/util/safely-sort.js @@ -1,7 +1,7 @@ /** - * Metric Selection Panel utils. + * `safelySort` utility function. * - * Site Kit by Google, Copyright 2023 Google LLC + * 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. @@ -21,6 +21,7 @@ * If the parameter is not an array, it returns the parameter as is. * * @since 1.110.0 + * @since n.e.x.t Moved to the common utility directory from the key metrics directory. * * @param {Array|*} arr Param to be sorted. * @return {Array|*} Safely sorted array without mutation. diff --git a/assets/js/util/whenScopesGranted.js b/assets/js/util/whenScopesGranted.js new file mode 100644 index 00000000000..6728d0a76ee --- /dev/null +++ b/assets/js/util/whenScopesGranted.js @@ -0,0 +1,96 @@ +/** + * `whenScopesGranted` HOC. + * + * 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 Data from 'googlesitekit-data'; +import { CORE_USER } from '../googlesitekit/datastore/user/constants'; +const { useSelect } = Data; + +/** + * Higher-Order Component to render wrapped components when specified scopes + * are available to this user. + * + * If the scopes are not available, a fallback component is rendered instead. + * + * A higher-order component is used here instead of hooks because there is + * potential for related selectors in components this HOC wraps to call out to + * resolvers that call endpoints for modules that aren't active. This would + * cause 404s at best and possibly errors, so it's better to wrap them in HOCs + * and "return early". + * + * @since 1.127.0 + * + * @param {Object} options Options for enhancing function. + * @param {string} options.scopes Array of scopes to check. + * @param {WPComponent} [options.FallbackComponent] Optional. Fallback component to render when the module is not active. + * @return {Function} Enhancing function. + */ +export default function whenScopesGranted( { + scopes = [], + FallbackComponent, +} ) { + return ( WrappedComponent ) => { + function WhenScopesGranted( props ) { + const allScopeResults = useSelect( + ( select ) => { + return scopes.map( ( scope ) => { + return select( CORE_USER ).hasScope( scope ); + } ); + }, + [ scopes ] + ); + + // Return null if any scopes aren't yet loaded. + if ( + allScopeResults.some( ( hasScope ) => { + return hasScope === undefined; + } ) + ) { + return null; + } + + // This component isn't widget-specific but widgets need to use + // `WidgetNull` from props when rendering "null" output. + const DefaultFallbackComponent = + FallbackComponent || props.WidgetNull || null; + + // Return a fallback (by default, ``) if any scopes are + // not present for this user. + if ( + allScopeResults.some( ( hasScope ) => { + return hasScope === false; + } ) + ) { + return ( + DefaultFallbackComponent && ( + + ) + ); + } + + // Return the component if the user has all scopes specified. + return ; + } + + WhenScopesGranted.displayName = 'WhenScopesGranted'; + + return WhenScopesGranted; + }; +} diff --git a/assets/js/util/whenScopesGranted.test.js b/assets/js/util/whenScopesGranted.test.js new file mode 100644 index 00000000000..bfa22955b8d --- /dev/null +++ b/assets/js/util/whenScopesGranted.test.js @@ -0,0 +1,158 @@ +/** + * HOC whenScopesGranted 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 { render } from '../../../tests/js/test-utils'; +import { createTestRegistry, subscribeUntil } from '../../../tests/js/utils'; +import { CORE_USER } from '../googlesitekit/datastore/user/constants'; +import whenScopesGranted from './whenScopesGranted'; + +describe( 'whenScopesGranted', () => { + let registry; + + function TestComponent() { + return
; + } + function TestFallbackComponent() { + return
; + } + function FakeWidgetNull() { + return
; + } + + async function loadScopes( registryReference, scopes = [] ) { + const coreUserDataEndpointRegExp = new RegExp( + '^/google-site-kit/v1/core/user/data/authentication' + ); + + fetchMock.getOnce( coreUserDataEndpointRegExp, { + body: { + authenticated: true, + requiredScopes: [], + grantedScopes: scopes, + unsatisfiedScopes: [], + }, + status: 200, + } ); + + registryReference.select( CORE_USER ).hasScope( scopes ); + await subscribeUntil( registryReference, () => + registryReference + .select( CORE_USER ) + .hasFinishedResolution( 'getAuthentication' ) + ); + } + + beforeEach( () => { + registry = createTestRegistry(); + } ); + + it( 'renders nothing (`null`) when scopes are loading', async () => { + await loadScopes( registry ); + + const WhenScopesGrantedComponent = whenScopesGranted( { + scopes: [ 'loading' ], + } )( TestComponent ); + + const { container, queryByTestID } = render( + , + { + registry, + } + ); + + expect( queryByTestID( 'component' ) ).not.toBeInTheDocument(); + expect( container ).toBeEmptyDOMElement(); + } ); + + it( 'renders the given component when all scopes are present', async () => { + await loadScopes( registry, [ 'https://test.net/testing' ] ); + + const WhenScopesGrantedComponent = whenScopesGranted( { + scopes: [ 'https://test.net/testing' ], + FallbackComponent: TestFallbackComponent, + } )( TestComponent ); + + const { queryByTestID } = render( , { + registry, + } ); + + expect( queryByTestID( 'component' ) ).toBeInTheDocument(); + } ); + + it( 'renders the fallback component when some, but not all, scopes are present', async () => { + await loadScopes( registry, [ 'https://test.net/testing' ] ); + + const WhenScopesGrantedComponent = whenScopesGranted( { + scopes: [ + 'https://test.net/testing', + 'https://otherscope.com/i-am-not-here', + ], + FallbackComponent: TestFallbackComponent, + } )( TestComponent ); + + const { queryByTestID } = render( , { + registry, + } ); + + expect( queryByTestID( 'component' ) ).not.toBeInTheDocument(); + expect( queryByTestID( 'fallback-component' ) ).toBeInTheDocument(); + } ); + + it( 'renders the fallback component when none of the scopes are present', async () => { + await loadScopes( registry, [] ); + + const WhenScopesGrantedComponent = whenScopesGranted( { + scopes: [ + 'https://test.net/testing', + 'https://otherscope.com/i-am-not-here', + ], + FallbackComponent: TestFallbackComponent, + } )( TestComponent ); + + const { queryByTestID } = render( , { + registry, + } ); + + expect( queryByTestID( 'component' ) ).not.toBeInTheDocument(); + expect( queryByTestID( 'fallback-component' ) ).toBeInTheDocument(); + } ); + + it( 'renders `WidgetNull` from the components ownProps for the default `FallbackComponent`', async () => { + await loadScopes( registry, [] ); + + const WhenScopesGrantedComponent = whenScopesGranted( { + scopes: [ + 'https://test.net/testing', + 'https://otherscope.com/i-am-not-here', + ], + } )( TestComponent ); + + const { queryByTestID } = render( + , + { + registry, + } + ); + + expect( queryByTestID( 'component' ) ).not.toBeInTheDocument(); + expect( queryByTestID( 'widget-null' ) ).toBeInTheDocument(); + } ); +} ); diff --git a/assets/sass/admin.scss b/assets/sass/admin.scss index 76fa0c050e8..8c8264af5c1 100644 --- a/assets/sass/admin.scss +++ b/assets/sass/admin.scss @@ -129,6 +129,7 @@ @import "components/global/googlesitekit-preview-table"; @import "components/global/googlesitekit-publisher-wins"; @import "components/global/googlesitekit-selection-box"; +@import "components/global/googlesitekit-selection-panel"; @import "components/global/googlesitekit-sharing-settings"; @import "components/global/googlesitekit-side-sheet"; @import "components/global/googlesitekit-source-link"; @@ -166,7 +167,6 @@ // Key Metrics @import "components/key-metrics/googlesitekit-key-metrics-setup-cta"; -@import "components/key-metrics/googlesitekit-km-selection-panel"; @import "components/key-metrics/googlesitekit-key-metrics-widgets"; // Setup diff --git a/assets/sass/components/analytics-4/audience-segmentation/_googlesitekit-audience-segmentation-tile.scss b/assets/sass/components/analytics-4/audience-segmentation/_googlesitekit-audience-segmentation-tile.scss index 091af5579e6..628210c0c4b 100644 --- a/assets/sass/components/analytics-4/audience-segmentation/_googlesitekit-audience-segmentation-tile.scss +++ b/assets/sass/components/analytics-4/audience-segmentation/_googlesitekit-audience-segmentation-tile.scss @@ -144,13 +144,29 @@ // Top content custom metric. &.googlesitekit-audience-segmentation-tile-metric--top-content { - .googlesitekit-audience-segmentation-tile-metric__title { height: 20px; line-height: 20px; margin-bottom: 10px; } + .googlesitekit-cta-link, + .googlesitekit-audience-segmentation-tile__top-content-metric-name { + margin-right: 1rem; + } + + .googlesitekit-audience-segmentation-tile-metric__container, + .googlesitekit-cta-link { + min-width: 0; + + .googlesitekit-cta-link__contents, + .googlesitekit-audience-segmentation-tile__top-content-metric-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + .googlesitekit-audience-segmentation-tile-metric__page-metric-container { display: flex; font-size: $fs-body-sm; diff --git a/assets/sass/components/dashboard/_googlesitekit-subtle-notification.scss b/assets/sass/components/dashboard/_googlesitekit-subtle-notification.scss index c9fe32aa17a..f10dd294aca 100644 --- a/assets/sass/components/dashboard/_googlesitekit-subtle-notification.scss +++ b/assets/sass/components/dashboard/_googlesitekit-subtle-notification.scss @@ -62,6 +62,10 @@ .mdc-button { margin: 0 auto; + &.mdc-button--tertiary:not(:disabled) { + color: $c-site-kit-sk-600; + } + @media (min-width: $bp-tablet) { margin: 0; } diff --git a/assets/sass/components/key-metrics/_googlesitekit-km-selection-panel.scss b/assets/sass/components/global/_googlesitekit-selection-panel.scss similarity index 75% rename from assets/sass/components/key-metrics/_googlesitekit-km-selection-panel.scss rename to assets/sass/components/global/_googlesitekit-selection-panel.scss index 4c7f772e2d7..e32569462c8 100644 --- a/assets/sass/components/key-metrics/_googlesitekit-km-selection-panel.scss +++ b/assets/sass/components/global/_googlesitekit-selection-panel.scss @@ -1,7 +1,7 @@ /** - * Key Metrics Selection Panel styles. + * Selection Panel component styles. * - * Site Kit by Google, Copyright 2023 Google LLC + * 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. @@ -18,12 +18,12 @@ .googlesitekit-plugin { - .googlesitekit-km-selection-panel { + .googlesitekit-selection-panel { display: flex; flex-direction: column; } - .googlesitekit-km-selection-panel-header { + .googlesitekit-selection-panel-header { padding: $grid-gap-phone; @media (min-width: $bp-tablet) { @@ -43,22 +43,22 @@ } } - .googlesitekit-km-selection-panel-header__row { + .googlesitekit-selection-panel-header__row { align-items: center; display: flex; justify-content: space-between; } - .googlesitekit-km-selection-panel-header__close { + .googlesitekit-selection-panel-header__close { color: $c-surfaces-on-surface-variant; display: flex; padding: 8px; // Tweak the padding to ensure the link is aligned in its focus state. } - .googlesitekit-km-selection-panel-metrics { + .googlesitekit-selection-panel-items { overflow: auto; - .googlesitekit-km-selection-panel-metrics__subheading { + .googlesitekit-selection-panel-items__subheading { color: $c-surfaces-on-surface-variant; font-size: $fs-body-sm; font-weight: $fw-medium; @@ -70,7 +70,7 @@ } } - .googlesitekit-km-selection-panel-metrics__subheading:first-child { + .googlesitekit-selection-panel-items__subheading:first-child { margin: 0 0 0 $grid-gap-phone; @media (min-width: $bp-tablet) { @@ -79,10 +79,10 @@ } } - .googlesitekit-km-selection-panel-metrics__metric-item { + .googlesitekit-selection-panel-item { padding: 0 $grid-gap-desktop; - .googlesitekit-km-selection-panel-metrics__metric-item-error { + .googlesitekit-selection-panel-item-error { color: $c-red-600; padding-top: 10px; } @@ -92,7 +92,7 @@ } } - .googlesitekit-km-selection-panel-notice { + .googlesitekit-selection-panel-notice { background-color: $c-utility-warning-container; padding: $grid-gap-phone $grid-gap-desktop; @@ -104,7 +104,7 @@ } } - .googlesitekit-km-selection-panel-footer { + .googlesitekit-selection-panel-footer { background-color: $c-surfaces-background; margin-top: auto; padding: $grid-gap-desktop / 2 $grid-gap-desktop $grid-gap-desktop; @@ -118,7 +118,7 @@ } } - .googlesitekit-km-selection-panel-footer__content { + .googlesitekit-selection-panel-footer__content { @media (min-width: $bp-tablet) { align-items: center; @@ -130,16 +130,16 @@ } } - .googlesitekit-km-selection-panel-footer__metric-count { + .googlesitekit-selection-panel-footer__item-count { font-weight: $fw-medium; - .googlesitekit-km-selection-panel-footer__metric-count--max-count { + .googlesitekit-selection-panel-footer__item-count--max-count { color: $c-surfaces-on-surface-variant; font-weight: $fw-normal; } } - .googlesitekit-km-selection-panel-footer__actions { + .googlesitekit-selection-panel-footer__actions { align-items: center; column-gap: $grid-gap-phone / 2; display: flex; diff --git a/changelog.txt b/changelog.txt index e61cd582e10..ed59476b956 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,42 @@ == Changelog == += 1.127.0 = + +**Enhanced** + +* Update PAX conversion tracking service code to improve compatibility with the new PAX version 1 API. See [#8693](https://github.com/google/site-kit-wp/issues/8693). +* Add the partner authentication service to the PAX app. See [#8686](https://github.com/google/site-kit-wp/issues/8686). +* Add support for the Partner Ads Experience `reportingStyle` config. See [#8637](https://github.com/google/site-kit-wp/issues/8637). +* Add `ConversionTrackingService` to PAX resolver/selectors. See [#8620](https://github.com/google/site-kit-wp/issues/8620). +* Add support for `supportedConversionEvents` in Ads datastore. See [#8619](https://github.com/google/site-kit-wp/issues/8619). +* Add REST API routes to the Conversion Tracking class. See [#8613](https://github.com/google/site-kit-wp/issues/8613). +* Add settings infrastructure for conversion tracking. See [#8612](https://github.com/google/site-kit-wp/issues/8612). +* Add conversion infrastructure for Contact Form 7. See [#8574](https://github.com/google/site-kit-wp/issues/8574). +* Add conversion infrastructure for WPForms. See [#8572](https://github.com/google/site-kit-wp/issues/8572). +* Add conversion infrastructure for Mailchimp. See [#8571](https://github.com/google/site-kit-wp/issues/8571). +* Add conversion infrastructure for Popup Maker. See [#8570](https://github.com/google/site-kit-wp/issues/8570). +* Update Ads settings edit view with PAX-specific alternate when connected via PAX. See [#8564](https://github.com/google/site-kit-wp/issues/8564). +* Provide conversion tracking service to PAX. See [#8561](https://github.com/google/site-kit-wp/issues/8561). +* Create Ads placeholder reporting widget. See [#8559](https://github.com/google/site-kit-wp/issues/8559). +* Introduce initial setup experience for Ads via PAX. See [#8558](https://github.com/google/site-kit-wp/issues/8558). +* Implement the PAX component to display the embedded app. See [#8557](https://github.com/google/site-kit-wp/issues/8557). +* Add conversion infrastructure for OptinMonster. See [#8554](https://github.com/google/site-kit-wp/issues/8554). +* Add support for Analytics events when WooCommerce is connected. See [#8553](https://github.com/google/site-kit-wp/issues/8553). +* Remove the `adsModule` feature flag. See [#8541](https://github.com/google/site-kit-wp/issues/8541). +* Add conversion event providers information to the site debug data. See [#8530](https://github.com/google/site-kit-wp/issues/8530). +* Update the settings view for Ads to display "None" in conversion tracking and external customer ID only when those settings are actually empty. See [#8516](https://github.com/google/site-kit-wp/issues/8516). +* Update the CTA link color in the post Ads module setup success banner. See [#8514](https://github.com/google/site-kit-wp/issues/8514). +* Fix typo in the "Visitor groups" admin setting. See [#8496](https://github.com/google/site-kit-wp/issues/8496). +* Add partial data states infrastructure for Analytics resources. See [#8141](https://github.com/google/site-kit-wp/issues/8141). +* Add datastore API for determining audience type. See [#8129](https://github.com/google/site-kit-wp/issues/8129). +* Add date range support to PAX app. See [#8687](https://github.com/google/site-kit-wp/issues/8687). + +**Fixed** + +* Fix the GTM edit screen stuck issue when the user doesn't have access to the connected property. See [#8596](https://github.com/google/site-kit-wp/issues/8596). +* Fix bug that caused "00%" to appear instead of "0%" when there was no change in data in Analytics widget. See [#8416](https://github.com/google/site-kit-wp/issues/8416). +* Ensure the "Most popular products" Key Metric widget supports the case where the required custom dimension does not exist. See [#8402](https://github.com/google/site-kit-wp/issues/8402). + = 1.126.0 = **Enhanced** diff --git a/feature-flags.json b/feature-flags.json index 612d3760696..f65e9fb5e5e 100644 --- a/feature-flags.json +++ b/feature-flags.json @@ -1 +1 @@ -[ "adsModule", "adsPax", "audienceSegmentation", "conversionInfra", "gm3Components" ] +[ "adsPax", "audienceSegmentation", "consentModeSwitzerland", "conversionInfra", "gm3Components" ] diff --git a/google-site-kit.php b/google-site-kit.php index 11b8e0b2c2e..5bab9497ab0 100644 --- a/google-site-kit.php +++ b/google-site-kit.php @@ -11,7 +11,7 @@ * Plugin Name: Site Kit by Google * Plugin URI: https://sitekit.withgoogle.com * Description: Site Kit is a one-stop solution for WordPress users to use everything Google has to offer to make them successful on the web. - * Version: 1.126.0 + * Version: 1.127.0 * Requires at least: 5.2 * Requires PHP: 7.4 * Author: Google @@ -26,7 +26,7 @@ } // Define most essential constants. -define( 'GOOGLESITEKIT_VERSION', '1.126.0' ); +define( 'GOOGLESITEKIT_VERSION', '1.127.0' ); define( 'GOOGLESITEKIT_PLUGIN_MAIN_FILE', __FILE__ ); define( 'GOOGLESITEKIT_PHP_MINIMUM', '7.4.0' ); define( 'GOOGLESITEKIT_WP_MINIMUM', '5.2.0' ); diff --git a/includes/Core/Consent_Mode/Consent_Mode.php b/includes/Core/Consent_Mode/Consent_Mode.php index 04e3266afa8..362ec82f355 100644 --- a/includes/Core/Consent_Mode/Consent_Mode.php +++ b/includes/Core/Consent_Mode/Consent_Mode.php @@ -84,6 +84,8 @@ function () use ( $consent_mode_enabled ) { return $consent_mode_enabled ? 'enabled' : 'disabled'; } ); + + add_filter( 'googlesitekit_inline_base_data', $this->get_method_proxy( 'inline_js_base_data' ) ); } /** @@ -111,7 +113,7 @@ protected function render_gtag_consent_snippet() { // TODO: The value for `region` should be retrieved from $this->consent_mode_settings->get_regions(), // but we'll need to migrate/clean up the incorrect values that were set from the initial release. // See https://github.com/google/site-kit-wp/issues/8444. - 'region' => Regions::EU_USER_CONSENT_POLICY, + 'region' => Regions::get_regions(), 'wait_for_update' => 500, // Allow 500ms for Consent Management Platforms (CMPs) to update the consent status. ) ); @@ -199,4 +201,18 @@ function updateGrantedConsent() { false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ); } diff --git a/includes/Core/Consent_Mode/Regions.php b/includes/Core/Consent_Mode/Regions.php index b0a986182c4..23ac0c065a8 100644 --- a/includes/Core/Consent_Mode/Regions.php +++ b/includes/Core/Consent_Mode/Regions.php @@ -10,6 +10,8 @@ namespace Google\Site_Kit\Core\Consent_Mode; +use Google\Site_Kit\Core\Util\Feature_Flags; + /** * Class containing Consent Mode Regions. * @@ -56,4 +58,24 @@ class Regions { 'SI', 'SK', ); + + /** + * Returns the list of regions that Google's EU user consent policy applies to. + * + * @since n.e.x.t + * + * @return array List of regions. + */ + public static function get_regions() { + // Include Switzerland (CH) in the consent mode regions if the current date + // is on or after 31 July 2024. + if ( + time() >= strtotime( '2024-07-31' ) || + Feature_Flags::enabled( 'consentModeSwitzerland' ) + ) { + return array_merge( self::EU_USER_CONSENT_POLICY, array( 'CH' ) ); + } + + return self::EU_USER_CONSENT_POLICY; + } } diff --git a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Contact_Form_7.php b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Contact_Form_7.php index 964bdcd94e3..b7d1ec4b7e3 100644 --- a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Contact_Form_7.php +++ b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Contact_Form_7.php @@ -16,7 +16,7 @@ /** * Class for handling Contact Form 7 conversion events. * - * @since n.e.x.t + * @since 1.127.0 * @access private * @ignore */ @@ -27,7 +27,7 @@ class Contact_Form_7 extends Conversion_Events_Provider { /** * Checks if the Contact Form 7 plugin is active. * - * @since n.e.x.t + * @since 1.127.0 * * @return bool True if Contact Form 7 is active, false otherwise. */ @@ -38,7 +38,7 @@ public function is_active() { /** * Gets the conversion event names that are tracked by this provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return array List of event names. */ @@ -49,7 +49,7 @@ public function get_event_names() { /** * Registers the script for the provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return Script Script instance. */ diff --git a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Mailchimp.php b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Mailchimp.php new file mode 100644 index 00000000000..6831bfe38e2 --- /dev/null +++ b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/Mailchimp.php @@ -0,0 +1,71 @@ + $this->context->url( 'dist/assets/js/mailchimp.js' ), + 'execution' => 'async', + 'dependencies' => array( 'mc4wp-forms-api' ), + ) + ); + + $script->register( $this->context ); + + return $script; + } + +} diff --git a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/OptinMonster.php b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/OptinMonster.php index b17325ad018..9804a8ae8a9 100644 --- a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/OptinMonster.php +++ b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/OptinMonster.php @@ -16,7 +16,7 @@ /** * Class for handling OptinMonster conversion events. * - * @since n.e.x.t + * @since 1.127.0 * @access private * @ignore */ @@ -27,7 +27,7 @@ class OptinMonster extends Conversion_Events_Provider { /** * Checks if the OptinMonster plugin is active. * - * @since n.e.x.t + * @since 1.127.0 * * @return bool True if OptinMonster is active, false otherwise. */ @@ -38,7 +38,7 @@ public function is_active() { /** * Gets the conversion event names that are tracked by this provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return array List of event names. */ @@ -49,7 +49,7 @@ public function get_event_names() { /** * Registers the script for the provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return Script Script instance. */ diff --git a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/PopupMaker.php b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/PopupMaker.php new file mode 100644 index 00000000000..576c4d0e7ff --- /dev/null +++ b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/PopupMaker.php @@ -0,0 +1,71 @@ + $this->context->url( 'dist/assets/js/popup-maker.js' ), + 'dependencies' => array( 'popup-maker-site' ), + 'execution' => 'defer', + ) + ); + + $script->register( $this->context ); + + return $script; + } + +} diff --git a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WPForms.php b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WPForms.php index cf8630de5b8..52871d46351 100644 --- a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WPForms.php +++ b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WPForms.php @@ -16,7 +16,7 @@ /** * Class for handling WPForms conversion events. * - * @since n.e.x.t + * @since 1.127.0 * @access private * @ignore */ @@ -27,7 +27,7 @@ class WPForms extends Conversion_Events_Provider { /** * Checks if the WPForms plugin is active. * - * @since n.e.x.t + * @since 1.127.0 * * @return bool True if WPForms is active, false otherwise. */ @@ -38,7 +38,7 @@ public function is_active() { /** * Gets the conversion event names that are tracked by this provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return array List of event names. */ @@ -49,7 +49,7 @@ public function get_event_names() { /** * Registers the script for the provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return Script Script instance. */ diff --git a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WooCommerce.php b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WooCommerce.php index 22c689f52a8..8bf2da02b6c 100644 --- a/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WooCommerce.php +++ b/includes/Core/Conversion_Tracking/Conversion_Event_Providers/WooCommerce.php @@ -16,7 +16,7 @@ /** * Class for handling WooCommerce conversion events. * - * @since n.e.x.t + * @since 1.127.0 * @access private * @ignore */ @@ -27,7 +27,7 @@ class WooCommerce extends Conversion_Events_Provider { /** * Checks if the WooCommerce plugin is active. * - * @since n.e.x.t + * @since 1.127.0 * * @return bool True if WooCommerce is active, false otherwise. */ @@ -38,7 +38,7 @@ public function is_active() { /** * Gets the conversion event names that are tracked by this provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return array List of event names. */ @@ -49,7 +49,7 @@ public function get_event_names() { /** * Registers the script for the provider. * - * @since n.e.x.t + * @since 1.127.0 * * @return Script Script instance. */ diff --git a/includes/Core/Conversion_Tracking/Conversion_Tracking.php b/includes/Core/Conversion_Tracking/Conversion_Tracking.php index 38704efecb2..c7adf581ffb 100644 --- a/includes/Core/Conversion_Tracking/Conversion_Tracking.php +++ b/includes/Core/Conversion_Tracking/Conversion_Tracking.php @@ -11,11 +11,14 @@ namespace Google\Site_Kit\Core\Conversion_Tracking; use Google\Site_Kit\Context; -use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Contact_Form_7; +use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Mailchimp; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\OptinMonster; +use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\PopupMaker; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\WooCommerce; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\WPForms; +use Google\Site_Kit\Core\Storage\Options; +use Google\Site_Kit\Core\Tags\GTag; use LogicException; /** @@ -37,11 +40,19 @@ class Conversion_Tracking { /** * Conversion_Tracking_Settings instance. * - * @since n.e.x.t + * @since 1.127.0 * @var Conversion_Tracking_Settings */ protected $conversion_tracking_settings; + /** + * REST_Conversion_Tracking_Controller instance. + * + * @since 1.127.0 + * @var REST_Conversion_Tracking_Controller + */ + protected $rest_conversion_tracking_controller; + /** * Supported conversion event providers. * @@ -50,7 +61,9 @@ class Conversion_Tracking { */ public static $providers = array( Contact_Form_7::CONVERSION_EVENT_PROVIDER_SLUG => Contact_Form_7::class, + Mailchimp::CONVERSION_EVENT_PROVIDER_SLUG => Mailchimp::class, OptinMonster::CONVERSION_EVENT_PROVIDER_SLUG => OptinMonster::class, + PopupMaker::CONVERSION_EVENT_PROVIDER_SLUG => PopupMaker::class, WooCommerce::CONVERSION_EVENT_PROVIDER_SLUG => WooCommerce::class, WPForms::CONVERSION_EVENT_PROVIDER_SLUG => WPForms::class, ); @@ -64,9 +77,10 @@ class Conversion_Tracking { * @param Options $options Optional. Option API instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null ) { - $this->context = $context; - $options = $options ?: new Options( $context ); - $this->conversion_tracking_settings = new Conversion_Tracking_Settings( $options ); + $this->context = $context; + $options = $options ?: new Options( $context ); + $this->conversion_tracking_settings = new Conversion_Tracking_Settings( $options ); + $this->rest_conversion_tracking_controller = new REST_Conversion_Tracking_Controller( $this->conversion_tracking_settings ); } /** @@ -76,6 +90,7 @@ public function __construct( Context $context, Options $options = null ) { */ public function register() { $this->conversion_tracking_settings->register(); + $this->rest_conversion_tracking_controller->register(); add_action( 'wp_enqueue_scripts', @@ -94,7 +109,11 @@ function( Conversion_Events_Provider $active_provider ) { $script_asset->enqueue(); } ); - } + + wp_add_inline_script( GTag::HANDLE, 'window._googlesitekit = window._googlesitekit || {};' ); + wp_add_inline_script( GTag::HANDLE, 'window._googlesitekit.trackEvent = (name, data) => gtag("event", name, {...data, _source: "site-kit" });' ); + }, + 30 ); } diff --git a/includes/Core/Conversion_Tracking/Conversion_Tracking_Settings.php b/includes/Core/Conversion_Tracking/Conversion_Tracking_Settings.php index f130d500462..a28b2ff4410 100644 --- a/includes/Core/Conversion_Tracking/Conversion_Tracking_Settings.php +++ b/includes/Core/Conversion_Tracking/Conversion_Tracking_Settings.php @@ -15,7 +15,7 @@ /** * Class to store conversion tracking settings. * - * @since n.e.x.t + * @since 1.127.0 * @access private * @ignore */ @@ -29,7 +29,7 @@ class Conversion_Tracking_Settings extends Setting { /** * Gets the expected value type. * - * @since n.e.x.t + * @since 1.127.0 * * @return string The expected type of the setting option. */ @@ -40,7 +40,7 @@ protected function get_type() { /** * Gets the default value. * - * @since n.e.x.t + * @since 1.127.0 * * @return array The default value. */ @@ -53,7 +53,7 @@ protected function get_default() { /** * Gets the callback for sanitizing the setting's value before saving. * - * @since n.e.x.t + * @since 1.127.0 * * @return callable Sanitize callback. */ @@ -73,7 +73,7 @@ protected function get_sanitize_callback() { /** * Accessor for the `enabled` setting. * - * @since n.e.x.t + * @since 1.127.0 * * @return bool TRUE if conversion tracking is enabled, otherwise FALSE. */ diff --git a/includes/Core/Conversion_Tracking/REST_Conversion_Tracking_Controller.php b/includes/Core/Conversion_Tracking/REST_Conversion_Tracking_Controller.php new file mode 100644 index 00000000000..53ea49f5cbb --- /dev/null +++ b/includes/Core/Conversion_Tracking/REST_Conversion_Tracking_Controller.php @@ -0,0 +1,131 @@ +settings = $settings; + } + + /** + * Registers functionality through WordPress hooks. + * + * @since 1.127.0 + */ + 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 ) { + return array_merge( + $paths, + array( + '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking', + ) + ); + } + ); + } + + /** + * Gets REST route instances. + * + * @since 1.127.0 + * + * @return REST_Route[] List of REST_Route objects. + */ + protected function get_rest_routes() { + $has_capabilities = function() { + return current_user_can( Permissions::VIEW_DASHBOARD ); + }; + + return array( + new REST_Route( + 'core/site/data/conversion-tracking', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => function () { + return new WP_REST_Response( $this->settings->get() ); + }, + 'permission_callback' => $has_capabilities, + ) + ), + new REST_Route( + 'core/site/data/conversion-tracking', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => function ( WP_REST_Request $request ) { + $this->settings->set( + $request['data']['settings'] + ); + + return new WP_REST_Response( $this->settings->get() ); + }, + 'permission_callback' => $has_capabilities, + 'args' => array( + 'data' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'settings' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'enabled' => array( + 'type' => 'boolean', + 'required' => true, + ), + ), + ), + ), + ), + ), + ) + ), + ); + } +} diff --git a/includes/Core/Expirables/Expirable_Items.php b/includes/Core/Expirables/Expirable_Items.php new file mode 100644 index 00000000000..9552ed670fb --- /dev/null +++ b/includes/Core/Expirables/Expirable_Items.php @@ -0,0 +1,134 @@ +get(); + $items[ $item ] = time() + $expires_in_seconds; + + $this->set( $items ); + } + + /** + * Removes one or more items from the list of expirable items. + * + * @since n.e.x.t + * + * @param string $item Item to remove. + */ + public function remove( $item ) { + $items = $this->get(); + + // If the item is not in expirable items, there's nothing to do. + if ( ! array_key_exists( $item, $items ) ) { + return; + } + + unset( $items[ $item ] ); + + $this->set( $items ); + } + + /** + * Gets the value of the setting. + * + * @since n.e.x.t + * + * @return array Value set for the option, or default if not set. + */ + public function get() { + $value = parent::get(); + return is_array( $value ) ? $value : $this->get_default(); + } + + /** + * Gets the expected value type. + * + * @since n.e.x.t + * + * @return string The type name. + */ + protected function get_type() { + return 'array'; + } + + /** + * Gets the default value. + * + * @since n.e.x.t + * + * @return array The default value. + */ + protected function get_default() { + return array(); + } + + /** + * 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 ( $items ) { + return $this->filter_expirable_items( $items ); + }; + } + + /** + * Filters expirable items. + * + * @since n.e.x.t + * + * @param array $items Expirable items list. + * @return array Filtered expirable items. + */ + private function filter_expirable_items( $items ) { + $expirables = array(); + + if ( is_array( $items ) ) { + foreach ( $items as $item => $ttl ) { + if ( is_integer( $ttl ) ) { + $expirables[ $item ] = $ttl; + } + } + } + + return $expirables; + } + +} diff --git a/includes/Core/Expirables/Expirables.php b/includes/Core/Expirables/Expirables.php new file mode 100644 index 00000000000..7e4127c6842 --- /dev/null +++ b/includes/Core/Expirables/Expirables.php @@ -0,0 +1,76 @@ +expirable_items = new Expirable_Items( $user_options ?: new User_Options( $context ) ); + $this->rest_controller = new REST_Expirable_Items_Controller( $this->expirable_items ); + } + + /** + * Gets the reference to the Expirable_Items instance. + * + * @since n.e.x.t + * + * @return Expirable_Items An instance of the Expirable_Items class. + */ + public function get_expirable_items() { + return $this->expirable_items; + } + + /** + * Registers functionality through WordPress hooks. + * + * @since n.e.x.t + */ + public function register() { + $this->expirable_items->register(); + $this->rest_controller->register(); + } +} diff --git a/includes/Core/Expirables/REST_Expirable_Items_Controller.php b/includes/Core/Expirables/REST_Expirable_Items_Controller.php new file mode 100644 index 00000000000..a54645d5cc9 --- /dev/null +++ b/includes/Core/Expirables/REST_Expirable_Items_Controller.php @@ -0,0 +1,169 @@ +expirable_items = $expirable_items; + } + + /** + * 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 ) { + return array_merge( + $paths, + array( + '/' . REST_Routes::REST_ROOT . '/core/user/data/expirable-items', + ) + ); + } + ); + } + + /** + * Gets REST route instances. + * + * @since n.e.x.t + * + * @return REST_Route[] List of REST_Route objects. + */ + protected function get_rest_routes() { + $can_manage_expirable_item = function() { + return current_user_can( Permissions::VIEW_DASHBOARD ); + }; + + return array( + new REST_Route( + 'core/user/data/expirable-items', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => function () { + return new WP_REST_Response( $this->expirable_items->get() ); + }, + 'permission_callback' => $can_manage_expirable_item, + ) + ), + new REST_Route( + 'core/user/data/set-expirable-item-timers', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => function ( WP_REST_Request $request ) { + $data = $request['data']; + + if ( empty( $data ) || ! is_array( $data ) ) { + return new WP_Error( + 'missing_required_param', + /* translators: %s: Missing parameter name */ + sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'items' ), + array( 'status' => 400 ) + ); + } + + foreach ( $data as $datum ) { + if ( empty( $datum['slug'] ) ) { + return new WP_Error( + 'missing_required_param', + /* translators: %s: Missing parameter name */ + sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'slug' ), + array( 'status' => 400 ) + ); + } + + $expiration = null; + if ( isset( $datum['expiration'] ) && intval( $datum['expiration'] ) > 0 ) { + $expiration = $datum['expiration']; + } + + if ( ! $expiration ) { + return new WP_Error( + 'missing_required_param', + /* translators: %s: Missing parameter name */ + sprintf( __( 'Request parameter is invalid: %s.', 'google-site-kit' ), 'expiration' ), + array( 'status' => 400 ) + ); + } + + $this->expirable_items->add( $datum['slug'], $expiration ); + } + + return new WP_REST_Response( $this->expirable_items->get() ); + }, + 'permission_callback' => $can_manage_expirable_item, + 'args' => array( + 'data' => array( + 'type' => 'array', + 'required' => true, + 'items' => array( + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'slug' => array( + 'type' => 'string', + 'required' => true, + ), + 'expiration' => array( + 'type' => 'integer', + 'required' => true, + ), + ), + ), + ), + ), + ) + ), + ); + } +} diff --git a/includes/Core/Modules/Modules.php b/includes/Core/Modules/Modules.php index b9d911a2101..834d686adc2 100644 --- a/includes/Core/Modules/Modules.php +++ b/includes/Core/Modules/Modules.php @@ -25,7 +25,6 @@ use Google\Site_Kit\Modules\Site_Verification; use Google\Site_Kit\Modules\Tag_Manager; use Google\Site_Kit\Modules\Ads; -use Google\Site_Kit\Core\Util\Feature_Flags; use Exception; /** @@ -146,6 +145,7 @@ final class Modules { private $core_modules = array( Site_Verification::MODULE_SLUG => Site_Verification::class, Search_Console::MODULE_SLUG => Search_Console::class, + Ads::MODULE_SLUG => Ads::class, Analytics_4::MODULE_SLUG => Analytics_4::class, Tag_Manager::MODULE_SLUG => Tag_Manager::class, AdSense::MODULE_SLUG => AdSense::class, @@ -177,10 +177,6 @@ public function __construct( $this->authentication = $authentication ?: new Authentication( $this->context, $this->options, $this->user_options ); $this->assets = $assets ?: new Assets( $this->context ); - if ( Feature_Flags::enabled( 'adsModule' ) ) { - $this->core_modules[ Ads::MODULE_SLUG ] = Ads::class; - } - $this->rest_controller = new REST_Modules_Controller( $this ); $this->dashboard_sharing_controller = new REST_Dashboard_Sharing_Controller( $this ); } diff --git a/includes/Core/Site_Health/Debug_Data.php b/includes/Core/Site_Health/Debug_Data.php index c4f4e9460cf..48d32bf93e5 100644 --- a/includes/Core/Site_Health/Debug_Data.php +++ b/includes/Core/Site_Health/Debug_Data.php @@ -623,7 +623,7 @@ private function get_consent_mode_fields() { * Gets the conversion event names registered by the currently supported * active plugins. * - * @since n.e.x.t + * @since 1.127.0 * * @return array */ diff --git a/includes/Modules/Ads.php b/includes/Modules/Ads.php index 0e34a5dcf34..7b630b839d3 100644 --- a/includes/Modules/Ads.php +++ b/includes/Modules/Ads.php @@ -151,7 +151,7 @@ protected function setup_assets() { $assets[] = new Script( 'googlesitekit-ads-pax-integrator', array( - 'src' => 'https://www.gstatic.com/pax/dev/pax_integrator.js', + 'src' => 'https://www.gstatic.com/pax/latest/pax_integrator.js', 'execution' => 'async', 'dependencies' => array( 'googlesitekit-ads-pax-config', @@ -239,7 +239,7 @@ protected function setup_settings() { * A module being connected means that all steps required as part of its activation are completed. * * @since 1.122.0 - * @since n.e.x.t Add additional check to account for paxConversionID and extCustomerID as well when feature flag is enabled. + * @since 1.127.0 Add additional check to account for paxConversionID and extCustomerID as well when feature flag is enabled. * * @return bool True if module is connected, false otherwise. */ diff --git a/includes/Modules/Analytics_4.php b/includes/Modules/Analytics_4.php index 5c542fbf430..03e72e36d0d 100644 --- a/includes/Modules/Analytics_4.php +++ b/includes/Modules/Analytics_4.php @@ -150,7 +150,7 @@ final class Analytics_4 extends Module /** * Resource_Data_Availability_Date instance. * - * @since n.e.x.t + * @since 1.127.0 * @var Resource_Data_Availability_Date */ protected $resource_data_availability_date; @@ -2213,7 +2213,7 @@ private function inline_custom_dimensions_data( $modules_data ) { /** * Populates resource availability dates data to pass to JS via _googlesitekitModulesData. * - * @since n.e.x.t + * @since 1.127.0 * * @param array $modules_data Inline modules data. * @return array Inline modules data. diff --git a/includes/Modules/Analytics_4/Resource_Data_Availability_Date.php b/includes/Modules/Analytics_4/Resource_Data_Availability_Date.php index 93685b0af15..a6d67bdd022 100644 --- a/includes/Modules/Analytics_4/Resource_Data_Availability_Date.php +++ b/includes/Modules/Analytics_4/Resource_Data_Availability_Date.php @@ -16,7 +16,7 @@ /** * Class for managing Analytics 4 resource data availability date. * - * @since n.e.x.t + * @since 1.127.0 * @access private * @ignore */ @@ -25,7 +25,7 @@ class Resource_Data_Availability_Date { /** * List of valid custom dimension slugs. * - * @since n.e.x.t + * @since 1.127.0 * @var array */ const CUSTOM_DIMENSION_SLUGS = array( @@ -39,7 +39,7 @@ class Resource_Data_Availability_Date { /** * Transients instance. * - * @since n.e.x.t + * @since 1.127.0 * @var Transients */ protected $transients; @@ -47,7 +47,7 @@ class Resource_Data_Availability_Date { /** * Module settings. * - * @since n.e.x.t + * @since 1.127.0 * @var Module_Settings */ protected $settings; @@ -55,7 +55,7 @@ class Resource_Data_Availability_Date { /** * Constructor. * - * @since n.e.x.t + * @since 1.127.0 * * @param Transients $transients Transients instance. * @param Module_Settings $settings Module settings instance. @@ -68,7 +68,7 @@ public function __construct( Transients $transients, Module_Settings $settings ) /** * Gets the data availability date for the given resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param string $resource_slug Resource slug. * @param string $resource_type Resource type. @@ -81,7 +81,7 @@ public function get_resource_date( $resource_slug, $resource_type ) { /** * Sets the data availability date for the given resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param string $resource_slug Resource slug. * @param string $resource_type Resource type. @@ -95,7 +95,7 @@ public function set_resource_date( $resource_slug, $resource_type, $date ) { /** * Resets the data availability date for the given resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param string $resource_slug Resource slug. * @param string $resource_type Resource type. @@ -108,7 +108,7 @@ public function reset_resource_date( $resource_slug, $resource_type ) { /** * Gets data availability dates for all resources. * - * @since n.e.x.t + * @since 1.127.0 * * @return array Associative array of resource names and their data availability date. */ @@ -152,7 +152,7 @@ function ( $custom_dimension_data_availability_dates, $custom_dimension ) { /** * Resets the data availability date for all resources. * - * @since n.e.x.t + * @since 1.127.0 * * @param array/null $available_audience_names Optional. List of available audience resource names. If not provided, it will be fetched from settings. * @param string/null $property_id Optional. Property ID. If not provided, it will be fetched from settings. @@ -177,7 +177,7 @@ public function reset_all_resource_dates( $available_audience_names = null, $pro /** * Checks whether the given resource type is valid. * - * @since n.e.x.t + * @since 1.127.0 * * @param string $resource_type Resource type. * @return bool True if valid, false otherwise. @@ -189,7 +189,7 @@ public function is_valid_resource_type( $resource_type ) { /** * Checks whether the given resource slug is valid. * - * @since n.e.x.t + * @since 1.127.0 * * @param string $resource_slug Resource slug. * @param string $resource_type Resource type. @@ -211,7 +211,7 @@ public function is_valid_resource_slug( $resource_slug, $resource_type ) { /** * Gets data available date transient name for the given resource. * - * @since n.e.x.t + * @since 1.127.0 * * @param string $resource_slug Resource slug. * @param string $resource_type Resource type. @@ -224,7 +224,7 @@ protected function get_resource_transient_name( $resource_slug, $resource_type ) /** * Gets available audience resource names. * - * @since n.e.x.t + * @since 1.127.0 * * @return array List of available audience resource names. */ @@ -243,7 +243,7 @@ function ( $audience ) { /** * Gets the property ID from settings instance. * - * @since n.e.x.t + * @since 1.127.0 * * @return string Property ID. */ diff --git a/includes/Modules/Tag_Manager.php b/includes/Modules/Tag_Manager.php index 94f445620a2..1bfef36bc7d 100644 --- a/includes/Modules/Tag_Manager.php +++ b/includes/Modules/Tag_Manager.php @@ -594,7 +594,7 @@ public function check_service_entity_access() { try { $containers = $this->get_tagmanager_service()->accounts_containers->listAccountsContainers( "accounts/{$account_id}" ); } catch ( Exception $e ) { - if ( $e->getCode() === 403 ) { + if ( $e->getCode() === 404 ) { return false; } return $this->exception_to_error( $e ); diff --git a/includes/Plugin.php b/includes/Plugin.php index cc62e311959..0b423fe674e 100644 --- a/includes/Plugin.php +++ b/includes/Plugin.php @@ -174,6 +174,9 @@ function() use ( $options, $activation_flag ) { $dismissed_items = $dismissals->get_dismissed_items(); + $expirables = new Core\Expirables\Expirables( $this->context, $user_options ); + $expirables->register(); + $permissions = new Core\Permissions\Permissions( $this->context, $authentication, $modules, $user_options, $dismissed_items ); $permissions->register(); diff --git a/readme.txt b/readme.txt index 16bcd822a91..10f0d9a24f2 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Contributors: google Requires at least: 5.2 Tested up to: 6.5 Requires PHP: 7.4 -Stable tag: 1.126.0 +Stable tag: 1.127.0 License: Apache License 2.0 License URI: https://www.apache.org/licenses/LICENSE-2.0 Tags: google, search-console, analytics, adsense, pagespeed-insights @@ -109,32 +109,41 @@ Please create a new topic on our [WordPress.org support forum](https://wordpress == Changelog == -= 1.126.0 = += 1.127.0 = **Enhanced** -* Support PAX-supplied Ads Conversion ID in tag output. See [#8580](https://github.com/google/site-kit-wp/issues/8580). -* Add feature that requests AdsWords scope when required. See [#8565](https://github.com/google/site-kit-wp/issues/8565). -* Add PAX settings to Ads settings. See [#8563](https://github.com/google/site-kit-wp/issues/8563). -* Add Ads settings for PAX. See [#8562](https://github.com/google/site-kit-wp/issues/8562). -* Scaffold dependencies for launching PAX. See [#8556](https://github.com/google/site-kit-wp/issues/8556). -* Add the main `Conversion_Tracking` class. See [#8528](https://github.com/google/site-kit-wp/issues/8528). -* Use a Google brand color for the WordPress link in the footer of the Authorize Application screen when authorizing a Google application. See [#8524](https://github.com/google/site-kit-wp/issues/8524). -* Add "Powered by Site Kit" to the Authorize Application screen footer when authorizing a Google application. See [#8510](https://github.com/google/site-kit-wp/issues/8510). -* Update WordPress Authorize Application Screen with enhanced design for Site Kit users. See [#8505](https://github.com/google/site-kit-wp/issues/8505). -* Enqueue stylesheet specific to the Authorize Application screen. See [#8504](https://github.com/google/site-kit-wp/issues/8504). -* Add stylesheet for Authorize Application screen. See [#8503](https://github.com/google/site-kit-wp/issues/8503). -* Replace direct calls to retrieve audiences with use of the cached list of audiences. See [#8487](https://github.com/google/site-kit-wp/issues/8487). -* Add REST and datastore APIs for audience caching. See [#8486](https://github.com/google/site-kit-wp/issues/8486). -* Add the `googlesitekit_consent_defaults` filter to allow customisation of Consent Mode defaults. See [#8383](https://github.com/google/site-kit-wp/issues/8383). -* Remove warning about Ad campaigns in the Consent Mode deactivation modal if Google Ads is not connected. See [#8381](https://github.com/google/site-kit-wp/issues/8381). -* Improve the deprecation warning for the `googlesitekit_analytics-4_tag_block_on_consent` filter. See [#8362](https://github.com/google/site-kit-wp/issues/8362). -* Add the Full Width Error Banner for the Audience Segmentation feature as a component in Storybook. See [#8230](https://github.com/google/site-kit-wp/issues/8230). -* Add introductory popup for Audience Segmentation. See [#8171](https://github.com/google/site-kit-wp/issues/8171). -* Add the Audience Tiles widget as a component in Storybook. See [#8136](https://github.com/google/site-kit-wp/issues/8136). +* Update PAX conversion tracking service code to improve compatibility with the new PAX version 1 API. See [#8693](https://github.com/google/site-kit-wp/issues/8693). +* Add the partner authentication service to the PAX app. See [#8686](https://github.com/google/site-kit-wp/issues/8686). +* Add support for the Partner Ads Experience `reportingStyle` config. See [#8637](https://github.com/google/site-kit-wp/issues/8637). +* Add `ConversionTrackingService` to PAX resolver/selectors. See [#8620](https://github.com/google/site-kit-wp/issues/8620). +* Add support for `supportedConversionEvents` in Ads datastore. See [#8619](https://github.com/google/site-kit-wp/issues/8619). +* Add REST API routes to the Conversion Tracking class. See [#8613](https://github.com/google/site-kit-wp/issues/8613). +* Add settings infrastructure for conversion tracking. See [#8612](https://github.com/google/site-kit-wp/issues/8612). +* Add conversion infrastructure for Contact Form 7. See [#8574](https://github.com/google/site-kit-wp/issues/8574). +* Add conversion infrastructure for WPForms. See [#8572](https://github.com/google/site-kit-wp/issues/8572). +* Add conversion infrastructure for Mailchimp. See [#8571](https://github.com/google/site-kit-wp/issues/8571). +* Add conversion infrastructure for Popup Maker. See [#8570](https://github.com/google/site-kit-wp/issues/8570). +* Update Ads settings edit view with PAX-specific alternate when connected via PAX. See [#8564](https://github.com/google/site-kit-wp/issues/8564). +* Provide conversion tracking service to PAX. See [#8561](https://github.com/google/site-kit-wp/issues/8561). +* Create Ads placeholder reporting widget. See [#8559](https://github.com/google/site-kit-wp/issues/8559). +* Introduce initial setup experience for Ads via PAX. See [#8558](https://github.com/google/site-kit-wp/issues/8558). +* Implement the PAX component to display the embedded app. See [#8557](https://github.com/google/site-kit-wp/issues/8557). +* Add conversion infrastructure for OptinMonster. See [#8554](https://github.com/google/site-kit-wp/issues/8554). +* Add support for Analytics events when WooCommerce is connected. See [#8553](https://github.com/google/site-kit-wp/issues/8553). +* Remove the `adsModule` feature flag. See [#8541](https://github.com/google/site-kit-wp/issues/8541). +* Add conversion event providers information to the site debug data. See [#8530](https://github.com/google/site-kit-wp/issues/8530). +* Update the settings view for Ads to display "None" in conversion tracking and external customer ID only when those settings are actually empty. See [#8516](https://github.com/google/site-kit-wp/issues/8516). +* Update the CTA link color in the post Ads module setup success banner. See [#8514](https://github.com/google/site-kit-wp/issues/8514). +* Fix typo in the "Visitor groups" admin setting. See [#8496](https://github.com/google/site-kit-wp/issues/8496). +* Add partial data states infrastructure for Analytics resources. See [#8141](https://github.com/google/site-kit-wp/issues/8141). +* Add datastore API for determining audience type. See [#8129](https://github.com/google/site-kit-wp/issues/8129). +* Add date range support to PAX app. See [#8687](https://github.com/google/site-kit-wp/issues/8687). **Fixed** -* Fix bug that could cause the Ads Module's Settings screen not to appear for admin users who did not connect the Ads Module. See [#8598](https://github.com/google/site-kit-wp/issues/8598). +* Fix the GTM edit screen stuck issue when the user doesn't have access to the connected property. See [#8596](https://github.com/google/site-kit-wp/issues/8596). +* Fix bug that caused "00%" to appear instead of "0%" when there was no change in data in Analytics widget. See [#8416](https://github.com/google/site-kit-wp/issues/8416). +* Ensure the "Most popular products" Key Metric widget supports the case where the required custom dimension does not exist. See [#8402](https://github.com/google/site-kit-wp/issues/8402). [See changelog for all versions](https://raw.githubusercontent.com/google/site-kit-wp/main/changelog.txt). diff --git a/stories/module-adsense-components.stories.js b/stories/module-adsense-components.stories.js index 7caee787f7b..38f4ad7f404 100644 --- a/stories/module-adsense-components.stories.js +++ b/stories/module-adsense-components.stories.js @@ -110,8 +110,8 @@ generateReportBasedWidgetStories( { 'IMPRESSIONS', 'PAGE_VIEWS_CTR', ], - startDate: '2020-10-29', - endDate: '2020-11-25', + startDate: '2020-10-28', + endDate: '2020-11-24', }, { dimensions: [ 'DATE' ], @@ -121,8 +121,8 @@ generateReportBasedWidgetStories( { 'IMPRESSIONS', 'PAGE_VIEWS_CTR', ], - startDate: '2020-10-29', - endDate: '2020-11-25', + startDate: '2020-10-28', + endDate: '2020-11-24', }, { metrics: [ @@ -131,8 +131,8 @@ generateReportBasedWidgetStories( { 'IMPRESSIONS', 'PAGE_VIEWS_CTR', ], - startDate: '2020-10-01', - endDate: '2020-10-28', + startDate: '2020-09-30', + endDate: '2020-10-27', }, { dimensions: [ 'DATE' ], @@ -142,8 +142,8 @@ generateReportBasedWidgetStories( { 'IMPRESSIONS', 'PAGE_VIEWS_CTR', ], - startDate: '2020-10-01', - endDate: '2020-10-28', + startDate: '2020-09-30', + endDate: '2020-10-27', }, ] ), additionalVariants: { diff --git a/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_0_small.png b/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_0_small.png index fb51b7b6728..5a9e0102013 100644 Binary files a/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_1_medium.png index e945dafda4f..5abb5e1122d 100644 Binary files a/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_2_large.png b/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_2_large.png index 975e7c5184a..bed44d59cb6 100644 Binary files a/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_AdSense_Module_Overview_Widget_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_0_small.png new file mode 100644 index 00000000000..8ca4bc9ac10 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_1_medium.png new file mode 100644 index 00000000000..0c6ffc71dff Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_2_large.png new file mode 100644 index 00000000000..c323f4eb294 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_Default_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_0_small.png new file mode 100644 index 00000000000..6a68422d665 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_1_medium.png new file mode 100644 index 00000000000..8cb5ce362f2 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_2_large.png new file mode 100644 index 00000000000..6328a7fb0a9 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Components_SelectionPanel_WithSavedItems_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_0_small.png index e04f144dbd5..3fd5c39b298 100644 Binary files a/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_1_medium.png index 8ba1467efb3..908994b7f1f 100644 Binary files a/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_2_large.png index c02300a56ac..c25d90df253 100644 Binary files a/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Global_SetupSuccessSubtleNotification_Ads_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_0_small.png index 4ad2c891334..0c805cf6a20 100644 Binary files a/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_1_medium.png index e9fcd6fa227..90fc69fb1ab 100644 Binary files a/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_2_large.png index d8393ca0745..2454b79d084 100644 Binary files a/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Global_SubtleNotifications_GA4AdSenseLinkedNotification_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_0_small.png index 90aa2d1c25b..fca885d1955 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_1_medium.png index a57db2928f5..691e2be11c6 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_2_large.png index 66c5ea6dc81..9472a4b15dc 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyLongCityNames_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_0_small.png index f48c9dcc2b6..77777b72d07 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_1_medium.png index df5f9db314d..d10a731c472 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_2_large.png index b777e3075af..8118309748f 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyViewOnly_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_0_small.png index ed38ea7a204..7a4c872df75 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_1_medium.png index d3e850b55eb..6b63ed056fe 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_2_large.png index 163802f1dad..4c227c57bfe 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_ReadyWithToolTip_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_0_small.png index ed38ea7a204..7a4c872df75 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_1_medium.png index d3e850b55eb..6b63ed056fe 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_2_large.png index 2d1e2725a14..dae27a3f0d6 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Components_AudienceSegmentation_Dashboard_AudienceTile_Ready_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_0_small.png index 5add0c10037..31ab82026fa 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_1_medium.png index 8efd132f200..a3354b66650 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_2_large.png index 824c9bd70e4..930d8316189 100644 Binary files a/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Modules_Analytics4_Settings_SettingsEdit_Default_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_0_small.png index cc31b93c6f8..d14e2bc06da 100644 Binary files a/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_1_medium.png index dc08f65b1c0..348aab1de1c 100644 Binary files a/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_2_large.png index 6d8231a6a0d..fb7c5d44021 100644 Binary files a/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Settings_Connected_Services_0_document_2_large.png differ diff --git a/tests/e2e/specs/front-end/consent-mode.test.js b/tests/e2e/specs/front-end/consent-mode.test.js index c236e79723d..5d47b56cade 100644 --- a/tests/e2e/specs/front-end/consent-mode.test.js +++ b/tests/e2e/specs/front-end/consent-mode.test.js @@ -28,6 +28,7 @@ import { setSiteVerification, setSearchConsoleProperty, wpApiFetch, + enableFeature, } from '../../utils'; const euUserConsentPolicyRegions = [ @@ -198,4 +199,27 @@ describe( 'Consent Mode snippet', () => { }, ] ); } ); + + it( 'includes Switzerland (CH) in the list of regions when the consentModeSwitzerland feature flag is enabled', async () => { + await enableFeature( 'consentModeSwitzerland' ); + + await page.reload(); + + const dataLayer = await page.evaluate( () => window.dataLayer ); + + expect( dataLayer ).toEqual( [ + { + 0: 'consent', + 1: 'default', + 2: { + ad_personalization: 'denied', + ad_storage: 'denied', + ad_user_data: 'denied', + analytics_storage: 'denied', + region: [ ...euUserConsentPolicyRegions, 'CH' ], + wait_for_update: 500, + }, + }, + ] ); + } ); } ); diff --git a/tests/e2e/specs/modules/ads/setup-no-previous-ads-conversion-id.test.js b/tests/e2e/specs/modules/ads/setup-no-previous-ads-conversion-id.test.js index 21b9e83c28b..1a542e78ee2 100644 --- a/tests/e2e/specs/modules/ads/setup-no-previous-ads-conversion-id.test.js +++ b/tests/e2e/specs/modules/ads/setup-no-previous-ads-conversion-id.test.js @@ -26,7 +26,6 @@ import { visitAdminPage } from '@wordpress/e2e-test-utils'; */ import { deactivateUtilityPlugins, - enableFeature, resetSiteKit, setupSiteKit, step, @@ -77,7 +76,6 @@ describe( 'Ads setup (with no Conversion Tracking ID present)', () => { } ); beforeEach( async () => { - await enableFeature( 'adsModule' ); await setupSiteKit(); } ); diff --git a/tests/e2e/specs/modules/ads/setup-with-previous-ads-conversion-id.test.js b/tests/e2e/specs/modules/ads/setup-with-previous-ads-conversion-id.test.js index 1b0554b94b3..ae67ae2ddfb 100644 --- a/tests/e2e/specs/modules/ads/setup-with-previous-ads-conversion-id.test.js +++ b/tests/e2e/specs/modules/ads/setup-with-previous-ads-conversion-id.test.js @@ -29,7 +29,6 @@ import { resetSiteKit, setupSiteKit, setupAnalytics4, - enableFeature, useRequestInterception, step, } from '../../../utils'; @@ -72,7 +71,6 @@ describe( 'Ads setup with Conversion Tracking ID present', () => { } ); beforeEach( async () => { - await enableFeature( 'adsModule' ); await setupSiteKit(); await setupAnalytics4( { adsConversionID: 'AW-12345', diff --git a/tests/phpunit/includes/Core/Modules/Module_With_Service_Entity_ContractTests.php b/tests/phpunit/includes/Core/Modules/Module_With_Service_Entity_ContractTests.php index 8967849c457..d48af1ad2c7 100644 --- a/tests/phpunit/includes/Core/Modules/Module_With_Service_Entity_ContractTests.php +++ b/tests/phpunit/includes/Core/Modules/Module_With_Service_Entity_ContractTests.php @@ -25,6 +25,14 @@ trait Module_With_Service_Entity_ContractTests { */ abstract protected function get_module_with_service_entity(); + /** + * All service entities return 403 for the permission denied error, + * except for Tag Manager which returns a 404. + */ + protected function get_service_entity_no_access_error_code() { + return 403; + } + /** * @group Module_With_Service_Entity */ @@ -48,7 +56,8 @@ public function test_check_service_entity_access_no_access() { $testcase = $this->get_testcase(); $module = $this->get_module_with_service_entity(); - $this->mock_service_entity_access( $module, 403 ); + $no_access_error_code = $this->get_service_entity_no_access_error_code(); + $this->mock_service_entity_access( $module, $no_access_error_code ); $this->set_up_check_service_entity_access( $module ); $access = $module->check_service_entity_access(); diff --git a/tests/phpunit/integration/Core/Consent_Mode/Consent_Mode_SettingsTest.php b/tests/phpunit/integration/Core/Consent_Mode/Consent_Mode_SettingsTest.php index 633a8047729..daec8b0a07b 100644 --- a/tests/phpunit/integration/Core/Consent_Mode/Consent_Mode_SettingsTest.php +++ b/tests/phpunit/integration/Core/Consent_Mode/Consent_Mode_SettingsTest.php @@ -50,7 +50,7 @@ public function test_get_default() { $this->assertEqualSetsWithIndex( array( 'enabled' => false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), $default_settings ); @@ -64,14 +64,14 @@ public function data_consent_mode_settings() { ), array( 'enabled' => false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), 'enabled empty' => array( array(), array( 'enabled' => false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), 'enabled true' => array( @@ -80,7 +80,7 @@ public function data_consent_mode_settings() { ), array( 'enabled' => true, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), 'enabled non-empty' => array( @@ -89,7 +89,7 @@ public function data_consent_mode_settings() { ), array( 'enabled' => true, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), 'valid regions' => array( @@ -116,7 +116,7 @@ public function data_consent_mode_settings() { ), array( 'enabled' => false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), 'empty regions' => array( @@ -125,7 +125,7 @@ public function data_consent_mode_settings() { ), array( 'enabled' => false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), 'non-array regions' => array( @@ -134,7 +134,7 @@ public function data_consent_mode_settings() { ), array( 'enabled' => false, - 'regions' => Regions::EU_USER_CONSENT_POLICY, + 'regions' => Regions::get_regions(), ), ), ); @@ -162,7 +162,7 @@ public function test_is_consent_mode_enabled() { } public function test_get_regions() { - $this->assertEquals( Regions::EU_USER_CONSENT_POLICY, $this->settings->get_regions() ); + $this->assertEquals( Regions::get_regions(), $this->settings->get_regions() ); $regions = array( 'SG', 'UA-AS' ); $this->settings->set( array( 'regions' => $regions ) ); diff --git a/tests/phpunit/integration/Core/Conversion_Tracking/Conversion_Event_Providers/MailchimpTest.php b/tests/phpunit/integration/Core/Conversion_Tracking/Conversion_Event_Providers/MailchimpTest.php new file mode 100644 index 00000000000..7403a0cfdd5 --- /dev/null +++ b/tests/phpunit/integration/Core/Conversion_Tracking/Conversion_Event_Providers/MailchimpTest.php @@ -0,0 +1,61 @@ +mailchimp = new Mailchimp( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ); + } + + public static function tear_down_after_class() { + parent::tear_down_after_class(); + + if ( function_exists( 'runkit7_constant_remove' ) ) { + runkit7_constant_remove( 'MC4WP_VERSION' ); + } elseif ( function_exists( 'runkit_constant_remove' ) ) { + runkit_constant_remove( 'MC4WP_VERSION' ); + } + } + + public function test_is_active() { + $this->assertFalse( $this->mailchimp->is_active() ); + define( 'MC4WP_VERSION', 1 ); + $this->assertTrue( $this->mailchimp->is_active() ); + } + + public function test_get_event_names() { + $events = $this->mailchimp->get_event_names(); + $this->assertCount( 1, $events ); + $this->assertEquals( 'submit_lead_form', $events[0] ); + } + + public function test_register_script() { + $handle = 'gsk-cep-' . Mailchimp::CONVERSION_EVENT_PROVIDER_SLUG; + $this->assertFalse( wp_script_is( $handle, 'registered' ) ); + + $script = $this->mailchimp->register_script(); + $this->assertInstanceOf( Script::class, $script ); + $this->assertTrue( wp_script_is( $handle, 'registered' ) ); + } + +} diff --git a/tests/phpunit/integration/Core/Conversion_Tracking/Conversion_Event_Providers/PopupMakerTest.php b/tests/phpunit/integration/Core/Conversion_Tracking/Conversion_Event_Providers/PopupMakerTest.php new file mode 100644 index 00000000000..fb8e50b69db --- /dev/null +++ b/tests/phpunit/integration/Core/Conversion_Tracking/Conversion_Event_Providers/PopupMakerTest.php @@ -0,0 +1,62 @@ +popupmaker = new PopupMaker( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ); + } + + public static function tear_down_after_class() { + parent::tear_down_after_class(); + + if ( function_exists( 'runkit7_constant_remove' ) ) { + runkit7_constant_remove( 'POPMAKE_VERSION' ); + } elseif ( function_exists( 'runkit_constant_remove' ) ) { + runkit_constant_remove( 'POPMAKE_VERSION' ); + } + } + + public function test_is_active() { + $this->assertFalse( $this->popupmaker->is_active() ); + define( 'POPMAKE_VERSION', 1 ); + $this->assertTrue( $this->popupmaker->is_active() ); + } + + public function test_get_event_names() { + $events = $this->popupmaker->get_event_names(); + $this->assertCount( 1, $events ); + $this->assertEquals( 'submit_lead_form', $events[0] ); + } + + public function test_register_script() { + $handle = 'gsk-cep-' . PopupMaker::CONVERSION_EVENT_PROVIDER_SLUG; + $this->assertFalse( wp_script_is( $handle, 'registered' ) ); + + $script = $this->popupmaker->register_script(); + + $this->assertInstanceOf( Script::class, $script ); + $this->assertTrue( wp_script_is( $handle, 'registered' ) ); + } + +} diff --git a/tests/phpunit/integration/Core/Conversion_Tracking/REST_Conversion_Tracking_ControllerTest.php b/tests/phpunit/integration/Core/Conversion_Tracking/REST_Conversion_Tracking_ControllerTest.php new file mode 100644 index 00000000000..d1adfd157de --- /dev/null +++ b/tests/phpunit/integration/Core/Conversion_Tracking/REST_Conversion_Tracking_ControllerTest.php @@ -0,0 +1,249 @@ +factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user_id ); + + $this->context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + $options = new Options( $this->context ); + + $this->settings = new Conversion_Tracking_Settings( $options ); + $this->controller = new REST_Conversion_Tracking_Controller( $this->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_settings() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + // Set up the site and admin user to make a successful REST request. + $this->grant_manage_options_permission(); + + $original_settings = array( + 'enabled' => false, + ); + + $this->settings->register(); + $this->settings->set( $original_settings ); + + $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEqualSetsWithIndex( $original_settings, $response->get_data() ); + } + + public function test_get_settings__requires_authenticated_admin() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + + $original_settings = array( + 'enabled' => false, + ); + + $this->settings->register(); + $this->settings->set( $original_settings ); + + $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking' ); + $response = rest_get_server()->dispatch( $request ); + + // This request is made by a user who is not authenticated with dashboard + // view permissions and is therefore forbidden. + $this->assertEquals( 'rest_forbidden', $response->get_data()['code'] ); + } + + public function test_set_settings() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + // Set up the site and admin user to make a successful REST request. + $this->grant_manage_options_permission(); + + $original_settings = array( + 'enabled' => false, + ); + + $changed_settings = array( + 'enabled' => true, + ); + + $this->settings->register(); + $this->settings->set( $original_settings ); + + $request = new WP_REST_Request( 'POST', '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking' ); + $request->set_body_params( + array( + 'data' => array( + 'settings' => $changed_settings, + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEqualSetsWithIndex( $changed_settings, $response->get_data() ); + } + + public function test_set_settings__requires_authenticated_admin() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + + $original_settings = array( + 'enabled' => false, + ); + + $changed_settings = array( + 'enabled' => true, + ); + + $this->settings->register(); + $this->settings->set( $original_settings ); + + $request = new WP_REST_Request( 'POST', '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking' ); + $request->set_body_params( + array( + 'data' => array( + 'settings' => $changed_settings, + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + + // This request is made by a user who is not authenticated with dashboard + // view permissions and is therefore forbidden. + $this->assertEquals( 'rest_forbidden', $response->get_data()['code'] ); + } + + /** + * @dataProvider provider_wrong_settings_data + */ + public function test_set_settings__wrong_data( $settings ) { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + + $request = new WP_REST_Request( 'POST', '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking' ); + $request->set_body_params( + array( + 'data' => array( + 'settings' => $settings, + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( 'rest_invalid_param', $response->get_data()['code'] ); + } + + public function provider_wrong_settings_data() { + return array( + 'wrong data type' => array( + '{}', + ), + 'invalid property' => array( + array( 'some-invalid-property' => 'value' ), + ), + 'non-boolean enabled property' => array( + array( 'enabled' => 123 ), + ), + 'regions property array containing non-string' => array( + array( 'regions' => array( 123 ) ), + ), + ); + } + + public function test_get_api_settings__requires_authenticated_admin() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + + $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking' ); + $response = rest_get_server()->dispatch( $request ); + + // This request is made by a user who is not authenticated with dashboard + // view permissions and is therefore forbidden. + $this->assertEquals( 'rest_forbidden', $response->get_data()['code'] ); + } + + private function grant_manage_options_permission() { + // Setup SiteKit. + $this->fake_proxy_site_connection(); + // Override any existing filter to make sure the setup is marked as complete all the time. + add_filter( 'googlesitekit_setup_complete', '__return_true', 100 ); + + // Verify and authenticate the current user. + $authentication = new Authentication( $this->context ); + $authentication->verification()->set( true ); + $authentication->get_oauth_client()->set_token( + array( + 'access_token' => 'valid-auth-token', + ) + ); + } +} diff --git a/tests/phpunit/integration/Core/Expirables/Expirable_ItemsTest.php b/tests/phpunit/integration/Core/Expirables/Expirable_ItemsTest.php new file mode 100644 index 00000000000..a0c7c38d250 --- /dev/null +++ b/tests/phpunit/integration/Core/Expirables/Expirable_ItemsTest.php @@ -0,0 +1,81 @@ +factory()->user->create(); + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + + $this->user_options = new User_Options( $context, $user_id ); + $this->expirable_items = new Expirable_Items( $this->user_options ); + $this->expirable_items->register(); + } + + public function test_add() { + $this->assertEmpty( $this->user_options->get( Expirable_Items::OPTION ) ); + + $current_time = time(); + $expected_time_foo = $current_time + 1000; + $this->expirable_items->add( 'foo', 1000 ); + + $this->expirable_items->add( 'bar', 100 ); + $user_options = $this->user_options->get( Expirable_Items::OPTION ); + $expected_time_foo_bar = $current_time + 100; + + $this->assertArrayHasKey( 'foo', $user_options ); + $this->assertEqualsWithDelta( $expected_time_foo, $user_options['foo'], 2 ); + $this->assertArrayHasKey( 'bar', $user_options ); + $this->assertEqualsWithDelta( $expected_time_foo_bar, $user_options['bar'], 2 ); + } + + public function test_remove() { + $this->user_options->set( + Expirable_Items::OPTION, + array( + 'foo' => time() + 50, + 'bar' => time() + 100, + 'baz' => time() + 500, + ) + ); + + $user_options = $this->user_options->get( Expirable_Items::OPTION ); + + $this->assertEqualsWithDelta( time() + 50, $user_options['foo'], 2 ); + $this->assertEqualsWithDelta( time() + 100, $user_options['bar'], 2 ); + $this->assertEqualsWithDelta( time() + 500, $user_options['baz'], 2 ); + + $this->expirable_items->remove( 'bar' ); + + $user_options_updated = $this->user_options->get( Expirable_Items::OPTION ); + + $this->assertEqualsWithDelta( time() + 50, $user_options_updated['foo'], 2 ); + $this->assertEqualsWithDelta( time() + 500, $user_options_updated['baz'], 2 ); + } +} diff --git a/tests/phpunit/integration/Core/Expirables/REST_Expirable_Items_ControllerTest.php b/tests/phpunit/integration/Core/Expirables/REST_Expirable_Items_ControllerTest.php new file mode 100644 index 00000000000..6b51f7d694c --- /dev/null +++ b/tests/phpunit/integration/Core/Expirables/REST_Expirable_Items_ControllerTest.php @@ -0,0 +1,143 @@ +factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user_id ); + + $this->context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + $user_options = new User_Options( $this->context, $user_id ); + $this->expirable_items = new Expirable_Items( $user_options ); + $this->controller = new REST_Expirable_Items_Controller( $this->expirable_items ); + } + + 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_expirable_items() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + + $this->grant_manage_options_permission(); + + $this->expirable_items->add( 'foo', 100 ); + $this->expirable_items->add( 'bar', 100 ); + $this->expirable_items->add( 'baz', -10 ); + + $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/user/data/expirable-items' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEqualsWithDelta( time() + 100, $data['foo'], 2 ); + $this->assertEqualsWithDelta( time() + 100, $data['bar'], 2 ); + $this->assertEqualsWithDelta( time() - 10, $data['baz'], 2 ); + } + + public function test_set_expirable_item_timers() { + remove_all_filters( 'googlesitekit_rest_routes' ); + $this->controller->register(); + $this->register_rest_routes(); + + $this->grant_manage_options_permission(); + + $this->expirable_items->add( 'foo', 100 ); + $this->expirable_items->add( 'baz', -10 ); + + $request = new WP_REST_Request( 'POST', '/' . REST_Routes::REST_ROOT . '/core/user/data/set-expirable-item-timers' ); + $request->set_body_params( + array( + 'data' => array( + array( + 'slug' => 'bar', + 'expiration' => 100, + ), + ), + ) + ); + + $data = rest_get_server()->dispatch( $request )->get_data(); + + $this->assertEqualsWithDelta( time() + 100, $data['foo'], 2 ); + $this->assertEqualsWithDelta( time() - 10, $data['baz'], 2 ); + } + + private function grant_manage_options_permission() { + // Setup SiteKit. + $this->fake_proxy_site_connection(); + // Override any existing filter to make sure the setup is marked as complete all the time. + add_filter( 'googlesitekit_setup_complete', '__return_true', 100 ); + + // Verify and authenticate the current user. + $authentication = new Authentication( $this->context ); + $authentication->verification()->set( true ); + $authentication->get_oauth_client()->set_token( + array( + 'access_token' => 'valid-auth-token', + ) + ); + } + +} diff --git a/tests/phpunit/integration/Core/Modules/ModulesTest.php b/tests/phpunit/integration/Core/Modules/ModulesTest.php index 5aa8fa355b2..6ff6cf50a81 100644 --- a/tests/phpunit/integration/Core/Modules/ModulesTest.php +++ b/tests/phpunit/integration/Core/Modules/ModulesTest.php @@ -16,6 +16,7 @@ use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; +use Google\Site_Kit\Modules\Ads; use Google\Site_Kit\Modules\AdSense; use Google\Site_Kit\Modules\Analytics_4; use Google\Site_Kit\Modules\PageSpeed_Insights; @@ -42,6 +43,7 @@ function ( $instance ) { $this->assertEqualSetsWithIndex( array( + 'ads' => 'Google\\Site_Kit\\Modules\\Ads', 'adsense' => 'Google\\Site_Kit\\Modules\\AdSense', 'analytics-4' => 'Google\\Site_Kit\\Modules\\Analytics_4', 'pagespeed-insights' => 'Google\\Site_Kit\\Modules\\PageSpeed_Insights', @@ -356,6 +358,7 @@ public function provider_googlesitekit_available_modules_filter() { $default_modules = array( Site_Verification::MODULE_SLUG, Search_Console::MODULE_SLUG, + Ads::MODULE_SLUG, AdSense::MODULE_SLUG, Analytics_4::MODULE_SLUG, PageSpeed_Insights::MODULE_SLUG, diff --git a/tests/phpunit/integration/Modules/Tag_ManagerTest.php b/tests/phpunit/integration/Modules/Tag_ManagerTest.php index 22d1ab56691..7a21d2c77f5 100644 --- a/tests/phpunit/integration/Modules/Tag_ManagerTest.php +++ b/tests/phpunit/integration/Modules/Tag_ManagerTest.php @@ -488,6 +488,14 @@ protected function get_module_with_service_entity() { return new Tag_Manager( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ); } + /** + * @return int The error code returned by the listAccountsContainers( "accounts/{account_id}" ) + * endpoint when permission is denied. + */ + protected function get_service_entity_no_access_error_code() { + return 404; + } + protected function set_up_check_service_entity_access( Module $module ) { $module->get_settings()->merge( array( diff --git a/webpack/conversionEventProviders.config.js b/webpack/conversionEventProviders.config.js index 2983529fa79..106384cba98 100644 --- a/webpack/conversionEventProviders.config.js +++ b/webpack/conversionEventProviders.config.js @@ -36,7 +36,9 @@ const { module.exports = ( mode ) => ( { entry: { 'contact-form-7': './assets/js/event-providers/contact-form-7.js', + mailchimp: './assets/js/event-providers/mailchimp.js', 'optin-monster': './assets/js/event-providers/optin-monster.js', + 'popup-maker': './assets/js/event-providers/popup-maker.js', woocommerce: './assets/js/event-providers/woocommerce.js', wpforms: './assets/js/event-providers/wpforms.js', },