From 35e5518e7835b2f2b1642c931846ea18e4754018 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Thu, 27 Nov 2025 16:52:48 +0000 Subject: [PATCH 1/2] Avoid rendering multiple instances of `AudienceErrorModal` in `AudienceTiles`. --- .../AudienceTile/AudienceTilePagesMetric.js | 152 ++---------------- .../AudienceTilesWidget/AudienceTile/index.js | 6 - .../AudienceTilesWidget/AudienceTiles/Body.js | 6 - .../AudienceTiles/index.js | 58 ++++--- .../hooks/useCreateCustomDimension.js | 126 +++++++++++++++ .../dashboard/CustomDimensionErrorModal.js | 105 ++++++++++++ 6 files changed, 279 insertions(+), 174 deletions(-) create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/hooks/useCreateCustomDimension.js create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/CustomDimensionErrorModal.js diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js index da0fe52fed4..970a43b5abb 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js @@ -24,42 +24,32 @@ import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { useCallback } from '@wordpress/element'; -import { addQueryArgs } from '@wordpress/url'; +import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { useSelect, useDispatch } from 'googlesitekit-data'; +import { useSelect } from 'googlesitekit-data'; import { BREAKPOINT_SMALL, BREAKPOINT_TABLET, useBreakpoint, } from '@/js/hooks/useBreakpoint'; -import { CORE_FORMS } from '@/js/googlesitekit/datastore/forms/constants'; import { CORE_SITE } from '@/js/googlesitekit/datastore/site/constants'; -import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants'; import { AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, CUSTOM_DIMENSION_DEFINITIONS, - EDIT_SCOPE, MODULES_ANALYTICS_4, } from '@/js/modules/analytics-4/datastore/constants'; -import { ERROR_CODE_MISSING_REQUIRED_SCOPE } from '@/js/util/errors'; import BadgeWithTooltip from '@/js/components/BadgeWithTooltip'; import AudienceTilePagesMetricContent from './AudienceTilePagesMetricContent'; -import AudienceErrorModal from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceErrorModal'; -import { AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION } from '@/js/googlesitekit/widgets/default-areas'; import useViewContext from '@/js/hooks/useViewContext'; import { trackEvent } from '@/js/util'; import useFormValue from '@/js/hooks/useFormValue'; +import useCreateCustomDimension from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/hooks/useCreateCustomDimension'; export default function AudienceTilePagesMetric( { - // TODO: The prop `audienceTileNumber` is part of a temporary workaround to ensure `AudienceErrorModal` is only rendered once - // within `AudienceTilesWidget`. This should be removed once the `AudienceErrorModal` render is extracted - // from `AudienceTilePagesMetric` and it's rendered once at a higher level instead. See https://github.com/google/site-kit-wp/issues/9543. - audienceTileNumber, audienceSlug, TileIcon, title, @@ -80,48 +70,12 @@ export default function AudienceTilePagesMetric( { ) ); - const hasAnalyticsEditScope = useSelect( ( select ) => - select( CORE_USER ).hasScope( EDIT_SCOPE ) - ); - - const redirectURL = addQueryArgs( global.location.href, { - notification: 'audience_segmentation', - widgetArea: AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION, - } ); - const errorRedirectURL = addQueryArgs( global.location.href, { - widgetArea: AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION, - } ); - - const isAutoCreatingCustomDimensionsForAudience = useFormValue( - AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, - 'isAutoCreatingCustomDimensionsForAudience' - ); - - const isCreatingCustomDimension = useSelect( ( select ) => - select( MODULES_ANALYTICS_4 ).isCreatingCustomDimension( - postTypeDimension - ) - ); - - const isSyncingAvailableCustomDimensions = useSelect( ( select ) => - select( MODULES_ANALYTICS_4 ).isFetchingSyncAvailableCustomDimensions() - ); - const customDimensionError = useSelect( ( select ) => select( MODULES_ANALYTICS_4 ).getCreateCustomDimensionError( postTypeDimension ) ); - const propertyID = useSelect( ( select ) => - select( MODULES_ANALYTICS_4 ).getPropertyID() - ); - - const { clearError } = useDispatch( MODULES_ANALYTICS_4 ); - const { setValues } = useDispatch( CORE_FORMS ); - const { setPermissionScopeError, clearPermissionScopeError } = - useDispatch( CORE_USER ); - const isRetryingCustomDimensionCreate = useFormValue( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, 'isRetrying' @@ -135,71 +89,25 @@ export default function AudienceTilePagesMetric( { const setupErrorCode = useSelect( ( select ) => select( CORE_SITE ).getSetupErrorCode() ); - const { setSetupErrorCode } = useDispatch( CORE_SITE ); const hasOAuthError = autoSubmit && setupErrorCode === 'access_denied'; - const onCreateCustomDimension = useCallback( - ( { isRetrying } = {} ) => { - setValues( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, { - autoSubmit: true, - isRetrying, - } ); - - if ( ! hasAnalyticsEditScope ) { - setPermissionScopeError( { - code: ERROR_CODE_MISSING_REQUIRED_SCOPE, - message: __( - 'Additional permissions are required to create new audiences in Analytics.', - 'google-site-kit' - ), - data: { - status: 403, - scopes: [ EDIT_SCOPE ], - skipModal: true, - skipDefaultErrorNotifications: true, - redirectURL, - errorRedirectURL, - }, - } ); - } - }, - [ - hasAnalyticsEditScope, - redirectURL, - errorRedirectURL, - setPermissionScopeError, - setValues, - ] - ); - - const onCancel = useCallback( () => { - setValues( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, { - autoSubmit: false, - isRetrying: false, - } ); - setSetupErrorCode( null ); - clearPermissionScopeError(); - clearError( 'createCustomDimension', [ - propertyID, - CUSTOM_DIMENSION_DEFINITIONS.googlesitekit_post_type, - ] ); - }, [ - clearError, - clearPermissionScopeError, - propertyID, - setSetupErrorCode, - setValues, - ] ); - const isMobileBreakpoint = [ BREAKPOINT_SMALL, BREAKPOINT_TABLET ].includes( breakpoint ); - const isSaving = - isAutoCreatingCustomDimensionsForAudience || - isCreatingCustomDimension || - isSyncingAvailableCustomDimensions; + const { onCreateCustomDimension, isSaving, setShowErrorModal } = + useCreateCustomDimension(); + + const shouldShowErrorModal = + ( customDimensionError && ! isSaving ) || + // I've deliberately removed the check for `! isAutoCreatingCustomDimensionsForAudience` to fix a bug where the error modal would disappear while retrying. + isRetryingCustomDimensionCreate || + hasOAuthError; + + useEffect( () => { + setShowErrorModal( shouldShowErrorModal ); + }, [ shouldShowErrorModal, setShowErrorModal ] ); return (
@@ -235,42 +143,12 @@ export default function AudienceTilePagesMetric( { onCreateCustomDimension={ onCreateCustomDimension } isSaving={ isSaving } /> - { /* - TODO: The `audienceTileNumber` check is part of a temporary workaround to ensure `AudienceErrorModal` is only rendered once - within `AudienceTilesWidget`. This should be removed, and the `AudienceErrorModal` render extracted - from here to be rendered once at a higher level instead. See https://github.com/google/site-kit-wp/issues/9543. - */ } - { audienceTileNumber === 0 && - ( ( customDimensionError && ! isSaving ) || - ( isRetryingCustomDimensionCreate && - ! isAutoCreatingCustomDimensionsForAudience ) || - hasOAuthError ) && ( - - onCreateCustomDimension( { isRetrying: true } ) - } - onCancel={ onCancel } - inProgress={ isSaving } - hasOAuthError={ hasOAuthError } - trackEventCategory={ `${ viewContext }_audiences-top-content-cta` } - /> - ) }
); } AudienceTilePagesMetric.propTypes = { - audienceTileNumber: PropTypes.number, audienceSlug: PropTypes.string.isRequired, TileIcon: PropTypes.elementType.isRequired, title: PropTypes.string.isRequired, diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.js index d78fe5e7497..5522e16fef1 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.js @@ -55,10 +55,6 @@ import BadgeWithTooltip from '@/js/components/BadgeWithTooltip'; import useViewContext from '@/js/hooks/useViewContext'; import AudienceTileZeroData from './AudienceTileZeroData'; export default function AudienceTile( { - // TODO: The prop `audienceTileNumber` is part of a temporary workaround to ensure `AudienceErrorModal` is only rendered once - // within `AudienceTilesWidget`. This should be removed once the `AudienceErrorModal` render is extracted - // from `AudienceTilePagesMetric` and it's rendered once at a higher level instead. See https://github.com/google/site-kit-wp/issues/9543. - audienceTileNumber = 0, audienceSlug, title, infoTooltip, @@ -284,7 +280,6 @@ export default function AudienceTile( { ( postTypeDimensionExists && ! hasInvalidCustomDimensionError ) ) && ( { allTilesError && ! loading && ( @@ -362,7 +357,6 @@ export default function Body( { return ( - { allTilesError === false && - ! loading && - isTabbedBreakpoint && - visibleAudiences.length > 0 && ( -
- ) } - - + + + { allTilesError === false && + ! loading && + isTabbedBreakpoint && + visibleAudiences.length > 0 && ( +
+ ) } + + + { showErrorModal && } + ); } diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/hooks/useCreateCustomDimension.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/hooks/useCreateCustomDimension.js new file mode 100644 index 00000000000..596ffda1b8b --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/hooks/useCreateCustomDimension.js @@ -0,0 +1,126 @@ +/** + * Audience Segmentation useCreateCustomDimension hook. + * + * Site Kit by Google, Copyright 2025 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. + */ + +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; +import { useDispatch, useSelect } from '@/js/googlesitekit-data'; +import { + AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, + CUSTOM_DIMENSION_DEFINITIONS, + EDIT_SCOPE, + MODULES_ANALYTICS_4, +} from '@/js/modules/analytics-4/datastore/constants'; +import { CORE_FORMS } from '@/js/googlesitekit/datastore/forms/constants'; +import { CORE_UI } from '@/js/googlesitekit/datastore/ui/constants'; +import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants'; +import { ERROR_CODE_MISSING_REQUIRED_SCOPE } from '@/js/util/errors'; +import { AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION } from '@/js/googlesitekit/widgets/default-areas'; +import useFormValue from '@/js/hooks/useFormValue'; + +const SHOW_ERROR_MODAL_KEY = 'audience-tiles-show-error-modal'; + +export default function useCreateCustomDimension() { + const showErrorModal = useSelect( + ( select ) => + select( CORE_UI ).getValue( SHOW_ERROR_MODAL_KEY ) || false + ); + const { setValue } = useDispatch( CORE_UI ); + const setShowErrorModal = useCallback( + ( value ) => setValue( SHOW_ERROR_MODAL_KEY, value ), + [ setValue ] + ); + + const { setValues } = useDispatch( CORE_FORMS ); + const hasAnalyticsEditScope = useSelect( ( select ) => + select( CORE_USER ).hasScope( EDIT_SCOPE ) + ); + const { setPermissionScopeError } = useDispatch( CORE_USER ); + + const redirectURL = addQueryArgs( global.location.href, { + notification: 'audience_segmentation', + widgetArea: AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION, + } ); + const errorRedirectURL = addQueryArgs( global.location.href, { + widgetArea: AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION, + } ); + + const onCreateCustomDimension = useCallback( + ( { isRetrying } = {} ) => { + setValues( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, { + autoSubmit: true, + isRetrying, + } ); + + if ( ! hasAnalyticsEditScope ) { + setPermissionScopeError( { + code: ERROR_CODE_MISSING_REQUIRED_SCOPE, + message: __( + 'Additional permissions are required to create new audiences in Analytics.', + 'google-site-kit' + ), + data: { + status: 403, + scopes: [ EDIT_SCOPE ], + skipModal: true, + skipDefaultErrorNotifications: true, + redirectURL, + errorRedirectURL, + }, + } ); + } + }, + [ + hasAnalyticsEditScope, + redirectURL, + errorRedirectURL, + setPermissionScopeError, + setValues, + ] + ); + + const isAutoCreatingCustomDimensionsForAudience = useFormValue( + AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, + 'isAutoCreatingCustomDimensionsForAudience' + ); + + const postTypeDimension = + CUSTOM_DIMENSION_DEFINITIONS.googlesitekit_post_type.parameterName; + + const isCreatingCustomDimension = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).isCreatingCustomDimension( + postTypeDimension + ) + ); + + const isSyncingAvailableCustomDimensions = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).isFetchingSyncAvailableCustomDimensions() + ); + + const isSaving = + isAutoCreatingCustomDimensionsForAudience || + isCreatingCustomDimension || + isSyncingAvailableCustomDimensions; + + return { + onCreateCustomDimension, + isSaving, + showErrorModal, + setShowErrorModal, + }; +} diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/CustomDimensionErrorModal.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/CustomDimensionErrorModal.js new file mode 100644 index 00000000000..f338b9d459f --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/CustomDimensionErrorModal.js @@ -0,0 +1,105 @@ +/** + * Audience Segmentation CustomDimensionErrorModal component. + * + * Site Kit by Google, Copyright 2025 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. + */ + +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@/js/googlesitekit-data'; +import AudienceErrorModal from './AudienceErrorModal'; +import { + AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, + CUSTOM_DIMENSION_DEFINITIONS, + MODULES_ANALYTICS_4, +} from '@/js/modules/analytics-4/datastore/constants'; +import { CORE_SITE } from '@/js/googlesitekit/datastore/site/constants'; +import { CORE_FORMS } from '@/js/googlesitekit/datastore/forms/constants'; +import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants'; +import useFormValue from '@/js/hooks/useFormValue'; +import useViewContext from '@/js/hooks/useViewContext'; +import useCreateCustomDimension from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/hooks/useCreateCustomDimension'; + +export default function CustomDimensionErrorModal( {} ) { + const viewContext = useViewContext(); + + const postTypeDimension = + CUSTOM_DIMENSION_DEFINITIONS.googlesitekit_post_type.parameterName; + + const customDimensionError = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).getCreateCustomDimensionError( + postTypeDimension + ) + ); + + const autoSubmit = useFormValue( + AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, + 'autoSubmit' + ); + + const setupErrorCode = useSelect( ( select ) => + select( CORE_SITE ).getSetupErrorCode() + ); + + const hasOAuthError = autoSubmit && setupErrorCode === 'access_denied'; + + const { setSetupErrorCode } = useDispatch( CORE_SITE ); + const { clearPermissionScopeError } = useDispatch( CORE_USER ); + const { clearError } = useDispatch( MODULES_ANALYTICS_4 ); + const propertyID = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).getPropertyID() + ); + const { setValues } = useDispatch( CORE_FORMS ); + + const { onCreateCustomDimension, isSaving, setShowErrorModal } = + useCreateCustomDimension(); + + const onCancel = useCallback( () => { + setValues( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, { + autoSubmit: false, + isRetrying: false, + } ); + setSetupErrorCode( null ); + clearPermissionScopeError(); + clearError( 'createCustomDimension', [ + propertyID, + CUSTOM_DIMENSION_DEFINITIONS.googlesitekit_post_type, + ] ); + setShowErrorModal( false ); + }, [ + clearError, + clearPermissionScopeError, + propertyID, + setSetupErrorCode, + setShowErrorModal, + setValues, + ] ); + + return ( + onCreateCustomDimension( { isRetrying: true } ) } + onCancel={ onCancel } + inProgress={ isSaving } + hasOAuthError={ hasOAuthError } + trackEventCategory={ `${ viewContext }_audiences-top-content-cta` } + /> + ); +} From ca378f876e6df3ac9aaeb923c70f4ee564c8afff Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Thu, 27 Nov 2025 17:48:06 +0000 Subject: [PATCH 2/2] Remove comment. --- .../AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js index 970a43b5abb..88bc2c2564b 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTilePagesMetric.js @@ -101,7 +101,6 @@ export default function AudienceTilePagesMetric( { const shouldShowErrorModal = ( customDimensionError && ! isSaving ) || - // I've deliberately removed the check for `! isAutoCreatingCustomDimensionsForAudience` to fix a bug where the error modal would disappear while retrying. isRetryingCustomDimensionCreate || hasOAuthError;