From eeefec44674e91c1e2976be339a268aa762b8145 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 09:54:49 +0100 Subject: [PATCH 01/31] Refactor AudienceTilesWidget to be a JS module. --- .../index.js} | 8 ++++---- .../index.stories.js} | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) rename assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/{AudienceTileError.js => AudienceTileError/index.js} (89%) rename assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/{AudienceTileError.stories.js => AudienceTileError/index.stories.js} (92%) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js similarity index 89% rename from assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError.js rename to assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js index 42806a5b8de..ae045266a72 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js @@ -29,10 +29,10 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { isInsufficientPermissionsError } from '../../../../../../../util/errors'; -import AudienceTileErrorImage from '../../../../../../../../svg/graphics/analytics-audience-segmentation-tile-error.svg'; -import ReportErrorActions from '../../../../../../../components/ReportErrorActions'; -import GetHelpLink from '../../GetHelpLink'; +import { isInsufficientPermissionsError } from '../../../../../../../../util/errors'; +import AudienceTileErrorImage from '../../../../../../../../../svg/graphics/analytics-audience-segmentation-tile-error.svg'; +import ReportErrorActions from '../../../../../../../../components/ReportErrorActions'; +import GetHelpLink from '../../../GetHelpLink'; export default function AudienceTileError( { errors } ) { const hasInsufficientPermissionsError = errors.some( ( err ) => diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError.stories.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.stories.js similarity index 92% rename from assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError.stories.js rename to assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.stories.js index 959d8be6144..a24f75af551 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError.stories.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.stories.js @@ -20,16 +20,16 @@ * Internal dependencies */ import { useSelect } from 'googlesitekit-data'; -import AudienceTileError from './AudienceTileError'; -import { MODULES_ANALYTICS_4 } from '../../../../../datastore/constants'; +import AudienceTileError from '.'; +import { MODULES_ANALYTICS_4 } from '../../../../../../datastore/constants'; import { WithTestRegistry, createTestRegistry, provideModuleRegistrations, provideModules, -} from '../../../../../../../../../tests/js/utils'; -import WithRegistrySetup from '../../../../../../../../../tests/js/WithRegistrySetup'; -import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../../../util/errors'; +} from '../../../../../../../../../../tests/js/utils'; +import WithRegistrySetup from '../../../../../../../../../../tests/js/WithRegistrySetup'; +import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../../../../util/errors'; function AudienceTileErrorWrapper( { ...args } ) { const errors = useSelect( ( select ) => From cd944418ab789e9016ca128802a07194bed94e72 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 10:12:47 +0100 Subject: [PATCH 02/31] Add `insufficient_permissions_error`, `insufficient_permissions_error_request_access`, `data_loading_error` and `data_loading_error_retry` GA actions to AudienceTileError. --- .../AudienceTileError/TileErrorContent.js | 85 +++++++++++++++++++ .../AudienceTile/AudienceTileError/index.js | 79 ++++++++--------- 2 files changed, 120 insertions(+), 44 deletions(-) create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js new file mode 100644 index 00000000000..3c943ac7005 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js @@ -0,0 +1,85 @@ +/** + * TileErrorContent component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { isInsufficientPermissionsError } from '../../../../../../../../util/errors'; +import AudienceTileErrorImage from '../../../../../../../../../svg/graphics/analytics-audience-segmentation-tile-error.svg'; +import ReportErrorActions from '../../../../../../../../components/ReportErrorActions'; +import GetHelpLink from '../../../GetHelpLink'; + +export default function TileErrorContent( { errors, onRetry } ) { + const hasInsufficientPermissionsError = errors.some( ( err ) => + isInsufficientPermissionsError( err ) + ); + + return ( +
+
+ +
+
+

+ { hasInsufficientPermissionsError + ? __( + 'Insufficient permissions', + 'google-site-kit' + ) + : __( + 'Data loading failed', + 'google-site-kit' + ) } +

+
+
+ +
+
+
+
+ ); +} + +TileErrorContent.propTypes = { + errors: PropTypes.array.isRequired, + onRetry: PropTypes.func.isRequired, +}; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js index ae045266a72..e410fcf714e 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js @@ -21,63 +21,54 @@ */ import PropTypes from 'prop-types'; -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - /** * Internal dependencies */ import { isInsufficientPermissionsError } from '../../../../../../../../util/errors'; -import AudienceTileErrorImage from '../../../../../../../../../svg/graphics/analytics-audience-segmentation-tile-error.svg'; -import ReportErrorActions from '../../../../../../../../components/ReportErrorActions'; -import GetHelpLink from '../../../GetHelpLink'; +import TileErrorContent from './TileErrorContent'; +import withIntersectionObserver from '../../../../../../../../util/withIntersectionObserver'; +import useViewContext from '../../../../../../../../hooks/useViewContext'; +import { trackEvent } from '../../../../../../../../util'; -export default function AudienceTileError( { errors } ) { +const TileErrorContentWithIntersectionObserver = + withIntersectionObserver( TileErrorContent ); + +export default function AudienceTileError( { audienceSlug, errors } ) { + const viewContext = useViewContext(); const hasInsufficientPermissionsError = errors.some( ( err ) => isInsufficientPermissionsError( err ) ); return ( -
-
- -
-
-

- { hasInsufficientPermissionsError - ? __( - 'Insufficient permissions', - 'google-site-kit' - ) - : __( - 'Data loading failed', - 'google-site-kit' - ) } -

-
-
- -
-
-
-
+ { + const action = hasInsufficientPermissionsError + ? 'insufficient_permissions_error' + : 'data_loading_error'; + + trackEvent( + `${ viewContext }_audiences-tile`, + action, + audienceSlug + ); + } } + onRetry={ () => { + const action = hasInsufficientPermissionsError + ? 'insufficient_permissions_error_request_access' + : 'data_loading_error_retry'; + + trackEvent( + `${ viewContext }_audiences-tile`, + action, + audienceSlug + ); + } } + /> ); } AudienceTileError.propTypes = { + audienceSlug: PropTypes.string.isRequired, errors: PropTypes.array.isRequired, }; From 4f1743bcf1639eacb0f0534254af4ca3333c4049 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 10:25:12 +0100 Subject: [PATCH 03/31] Add `view_tile_tooltip` and `view_partial_data_tile_tooltip` GA events to AudienceTile. --- assets/js/components/BadgeWithTooltip.js | 6 +++- assets/js/components/InfoTooltip.js | 4 ++- .../AudienceTilesWidget/AudienceTile/index.js | 28 ++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/assets/js/components/BadgeWithTooltip.js b/assets/js/components/BadgeWithTooltip.js index be4f99e8d3d..431181a46f4 100644 --- a/assets/js/components/BadgeWithTooltip.js +++ b/assets/js/components/BadgeWithTooltip.js @@ -30,6 +30,7 @@ import InfoTooltip from './InfoTooltip'; export default function BadgeWithTooltip( { className = '', label, + onTooltipOpen, tooltipTitle, } ) { return ( @@ -41,12 +42,15 @@ export default function BadgeWithTooltip( { ) } > { label } - { tooltipTitle && } + { tooltipTitle && ( + + ) } ); } BadgeWithTooltip.propTypes = { + onTooltipOpen: PropTypes.func, tooltipTitle: PropTypes.node, className: PropTypes.string, label: PropTypes.node.isRequired, diff --git a/assets/js/components/InfoTooltip.js b/assets/js/components/InfoTooltip.js index f3dff1b99f2..2cecfc2f8b4 100644 --- a/assets/js/components/InfoTooltip.js +++ b/assets/js/components/InfoTooltip.js @@ -28,7 +28,7 @@ import PropTypes from 'prop-types'; import { Tooltip } from 'googlesitekit-components'; import InfoIcon from '../../svg/icons/info-green.svg'; -export default function InfoTooltip( { title, tooltipClassName } ) { +export default function InfoTooltip( { onOpen, title, tooltipClassName } ) { if ( ! title ) { return null; } @@ -45,6 +45,7 @@ export default function InfoTooltip( { title, tooltipClassName } ) { enterTouchDelay={ 0 } leaveTouchDelay={ 5000 } interactive + onOpen={ onOpen } > @@ -54,6 +55,7 @@ export default function InfoTooltip( { title, tooltipClassName } ) { } InfoTooltip.propTypes = { + onOpen: PropTypes.func, title: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ), tooltipClassName: PropTypes.string, }; 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 3379cdb5c07..99c86cf5200 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 @@ -50,10 +50,11 @@ import AudienceTilePagesMetric from './AudienceTilePagesMetric'; import ChangeBadge from '../../../../../../../components/ChangeBadge'; import InfoTooltip from '../../../../../../../components/InfoTooltip'; import PartialDataNotice from './PartialDataNotice'; -import { numFmt } from '../../../../../../../util'; +import { numFmt, trackEvent } from '../../../../../../../util'; import AudienceTileCollectingData from './AudienceTileCollectingData'; import AudienceTileCollectingDataHideable from './AudienceTileCollectingDataHideable'; import BadgeWithTooltip from '../../../../../../../components/BadgeWithTooltip'; +import useViewContext from '../../../../../../../hooks/useViewContext'; // TODO: as part of #8484 the report props should be updated to expect // the full report rows for the current tile to reduce data manipulation @@ -63,6 +64,7 @@ export default function AudienceTile( { // 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, visitors, @@ -82,6 +84,7 @@ export default function AudienceTile( { onHideTile, } ) { const breakpoint = useBreakpoint(); + const viewContext = useViewContext(); const isViewOnly = useViewOnly(); const isPropertyPartialData = useInViewSelect( ( select ) => { @@ -153,6 +156,13 @@ export default function AudienceTile( { + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_tile_tooltip', + audienceSlug + ) + } /> ) } @@ -191,6 +201,13 @@ export default function AudienceTile( { + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_tile_tooltip', + audienceSlug + ) + } /> ) } @@ -205,6 +222,13 @@ export default function AudienceTile( { 'Still collecting full data for this timeframe, partial data is displayed for this group', 'google-site-kit' ) } + onTooltipOpen={ () => + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_partial_data_tile_tooltip', + audienceSlug + ) + } /> ) } @@ -291,6 +315,7 @@ export default function AudienceTile( { ! hasInvalidCustomDimensionError ) ) && ( Date: Fri, 25 Oct 2024 11:05:17 +0100 Subject: [PATCH 04/31] Add `view_top_content_partial_data_tooltip` GA event to AudienceTilePagesMetric. --- .../AudienceTile/AudienceTilePagesMetric.js | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 f4ab4b417d8..a4b212f9931 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 @@ -52,12 +52,14 @@ import AudienceTilePagesMetricContent from './AudienceTilePagesMetricContent'; import AudienceErrorModal from '../../AudienceErrorModal'; import { AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION } from '../../../../../../../googlesitekit/widgets/default-areas'; import useViewContext from '../../../../../../../hooks/useViewContext'; +import { trackEvent } from '../../../../../../../util'; 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, topContent, @@ -215,6 +217,13 @@ export default function AudienceTilePagesMetric( { { + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_top_content_partial_data_tooltip', + audienceSlug + ); + } } tooltipTitle={ __( 'Still collecting full data for this timeframe, partial data is displayed for this metric', 'google-site-kit' @@ -266,6 +275,7 @@ export default function AudienceTilePagesMetric( { AudienceTilePagesMetric.propTypes = { audienceTileNumber: PropTypes.number, + audienceSlug: PropTypes.string, TileIcon: PropTypes.elementType.isRequired, title: PropTypes.string.isRequired, topContent: PropTypes.object, From ebd087d3088096b4097d1e0665e8ba66270aa3b3 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 11:21:40 +0100 Subject: [PATCH 05/31] Add `view_tile_collecting_data` and `temporarily_hide` GA events to AudienceTileZeroData. --- .../AudienceTile/AudienceTilePagesMetric.js | 2 +- .../TileZeroDataContent.js | 102 ++++++++++++++++++ .../AudienceTileZeroData/index.js | 82 ++++++++++++++ .../AudienceTilesWidget/AudienceTile/index.js | 48 ++------- .../AudienceTilesWidget/AudienceTiles.js | 2 + 5 files changed, 198 insertions(+), 38 deletions(-) create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/TileZeroDataContent.js create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.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 a4b212f9931..5f1b9f7bd55 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 @@ -275,7 +275,7 @@ export default function AudienceTilePagesMetric( { AudienceTilePagesMetric.propTypes = { audienceTileNumber: PropTypes.number, - audienceSlug: PropTypes.string, + audienceSlug: PropTypes.string.isRequired, TileIcon: PropTypes.elementType.isRequired, title: PropTypes.string.isRequired, topContent: PropTypes.object, diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/TileZeroDataContent.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/TileZeroDataContent.js new file mode 100644 index 00000000000..f99899d0132 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/TileZeroDataContent.js @@ -0,0 +1,102 @@ +/** + * ZeroDataContent component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useViewContext from '../../../../../../../../hooks/useViewContext'; +import { trackEvent } from '../../../../../../../../util'; +import InfoTooltip from '../../../../../../../../components/InfoTooltip'; +import AudienceTileCollectingData from '../AudienceTileCollectingData'; +import AudienceTileCollectingDataHideable from '../AudienceTileCollectingDataHideable'; + +const TileZeroDataContent = forwardRef( + ( + { + Widget, + audienceSlug, + title, + infoTooltip, + isMobileBreakpoint, + isTileHideable, + onHideTile, + }, + ref + ) => { + const viewContext = useViewContext(); + + return ( + +
+
+ { ! isMobileBreakpoint && ( +
+
+ { title } + { infoTooltip && ( + + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_tile_tooltip', + audienceSlug + ) + } + /> + ) } +
+
+ ) } +
+ + { isTileHideable && ( + + ) } +
+
+
+
+ ); + } +); + +TileZeroDataContent.propTypes = { + Widget: PropTypes.elementType.isRequired, + audienceSlug: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + infoTooltip: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ), + isMobileBreakpoint: PropTypes.bool, + isTileHideable: PropTypes.bool, + onHideTile: PropTypes.func, +}; + +export default TileZeroDataContent; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js new file mode 100644 index 00000000000..fa00ed90061 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js @@ -0,0 +1,82 @@ +/** + * AudienceTileZeroData component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * Internal dependencies + */ +import useViewContext from '../../../../../../../../hooks/useViewContext'; +import withIntersectionObserver from '../../../../../../../../util/withIntersectionObserver'; +import { trackEvent } from '../../../../../../../../util'; +import TileZeroDataContent from './TileZeroDataContent'; + +const TileZeroDataContentWithIntersectionObserver = + withIntersectionObserver( TileZeroDataContent ); + +export default function AudienceTileZeroData( { + Widget, + audienceSlug, + title, + infoTooltip, + isMobileBreakpoint, + isTileHideable, + onHideTile, +} ) { + const viewContext = useViewContext(); + + function handleHideTile() { + trackEvent( + `${ viewContext }_audiences-tile`, + 'temporarily_hide', + audienceSlug + ).finally( onHideTile ); + } + + return ( + { + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_tile_zero_data', + audienceSlug + ); + } } + /> + ); +} + +AudienceTileZeroData.propTypes = { + Widget: PropTypes.elementType.isRequired, + audienceSlug: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + infoTooltip: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ), + isMobileBreakpoint: PropTypes.bool, + isTileHideable: PropTypes.bool, + onHideTile: PropTypes.func, +}; 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 99c86cf5200..ab4dc47a644 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 @@ -51,10 +51,9 @@ import ChangeBadge from '../../../../../../../components/ChangeBadge'; import InfoTooltip from '../../../../../../../components/InfoTooltip'; import PartialDataNotice from './PartialDataNotice'; import { numFmt, trackEvent } from '../../../../../../../util'; -import AudienceTileCollectingData from './AudienceTileCollectingData'; -import AudienceTileCollectingDataHideable from './AudienceTileCollectingDataHideable'; import BadgeWithTooltip from '../../../../../../../components/BadgeWithTooltip'; import useViewContext from '../../../../../../../hooks/useViewContext'; +import AudienceTileZeroData from './AudienceTileZeroData'; // TODO: as part of #8484 the report props should be updated to expect // the full report rows for the current tile to reduce data manipulation @@ -145,40 +144,15 @@ export default function AudienceTile( { if ( isPartialData && isZeroData ) { return ( - -
-
- { ! isMobileBreakpoint && ( -
-
- { title } - { infoTooltip && ( - - trackEvent( - `${ viewContext }_audiences-tile`, - 'view_tile_tooltip', - audienceSlug - ) - } - /> - ) } -
-
- ) } -
- - { isTileHideable && ( - - ) } -
-
-
-
+ ); } @@ -334,7 +308,7 @@ export default function AudienceTile( { AudienceTile.propTypes = { audienceTileNumber: PropTypes.number, - audienceSlug: PropTypes.string, + audienceSlug: PropTypes.string.isRequired, title: PropTypes.string.isRequired, infoTooltip: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ), visitors: PropTypes.object, diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js index 04c41e10d14..90048d27672 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js @@ -577,6 +577,7 @@ export default function AudienceTiles( { Widget, widgetLoading } ) { return ( Date: Fri, 25 Oct 2024 11:43:50 +0100 Subject: [PATCH 06/31] Add `insufficient_permissions_error`, `insufficient_permissions_error_request_access`, `data_loading_error` and `data_loading_error_retry` GA actions to AudienceSegmentationErrorWidget. --- .../AudienceSegmentationErrorWidget.js | 165 ------------------ .../ErrorWidgetContent.js | 142 +++++++++++++++ .../AudienceSegmentationErrorWidget/index.js | 106 +++++++++++ .../index.stories.js} | 12 +- .../index.test.js} | 10 +- 5 files changed, 259 insertions(+), 176 deletions(-) delete mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.js create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/ErrorWidgetContent.js create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js rename assets/js/modules/analytics-4/components/audience-segmentation/dashboard/{AudienceSegmentationErrorWidget.stories.js => AudienceSegmentationErrorWidget/index.stories.js} (90%) rename assets/js/modules/analytics-4/components/audience-segmentation/dashboard/{AudienceSegmentationErrorWidget.test.js => AudienceSegmentationErrorWidget/index.test.js} (94%) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.js deleted file mode 100644 index 0769ac8fa46..00000000000 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * AudienceSegmentationErrorWidget component. - * - * Site Kit by Google, Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * External dependencies - */ -import { castArray } from 'lodash'; -import PropTypes from 'prop-types'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useEffect } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { Button } from 'googlesitekit-components'; -import whenActive from '../../../../../util/when-active'; -import { useDispatch } from 'googlesitekit-data'; -import { Cell, Grid, Row } from '../../../../../material-components'; -import { - BREAKPOINT_SMALL, - BREAKPOINT_TABLET, - useBreakpoint, -} from '../../../../../hooks/useBreakpoint'; -import AudienceSegmentationErrorSVG from '../../../../../../svg/graphics/audience-segmentation-error-full-width.svg'; -import { isInsufficientPermissionsError } from '../../../../../util/errors'; -import ReportErrorActions from '../../../../../components/ReportErrorActions'; -import GetHelpLink from './GetHelpLink'; -import { CORE_UI } from '../../../../../googlesitekit/datastore/ui/constants'; -import { AUDIENCE_INFO_NOTICE_HIDE_UI } from './InfoNoticeWidget/constants'; - -function AudienceSegmentationErrorWidget( { - Widget, - errors, - onRetry, - showRetryButton, -} ) { - const breakpoint = useBreakpoint(); - const isMobileBreakpoint = breakpoint === BREAKPOINT_SMALL; - const isTabletBreakpoint = breakpoint === BREAKPOINT_TABLET; - const { setValue } = useDispatch( CORE_UI ); - - const hasInsufficientPermissionsError = errors - ? castArray( errors ).some( isInsufficientPermissionsError ) - : false; - - const handleRetry = () => { - setValue( AUDIENCE_INFO_NOTICE_HIDE_UI, false ); - onRetry?.(); - }; - - useEffect( () => { - // Set UI key to hide the info notice. - setValue( AUDIENCE_INFO_NOTICE_HIDE_UI, true ); - }, [ setValue ] ); - - return ( - - - - -

- { hasInsufficientPermissionsError - ? __( - 'Insufficient permissions', - 'google-site-kit' - ) - : __( - 'Your visitor groups data loading failed', - 'google-site-kit' - ) } -

-
- { showRetryButton && onRetry ? ( - - ) : ( - - ) } -
-
- { ! isMobileBreakpoint && ! isTabletBreakpoint && ( - - - - ) } - { isTabletBreakpoint && ( - - - - ) } - { isMobileBreakpoint && ( - - - - ) } -
-
-
- ); -} - -AudienceSegmentationErrorWidget.propTypes = { - Widget: PropTypes.elementType.isRequired, - errors: PropTypes.oneOfType( [ - PropTypes.object, - PropTypes.arrayOf( PropTypes.object ), - ] ).isRequired, - onRetry: PropTypes.func, - showRetryButton: PropTypes.bool, -}; - -export default whenActive( { moduleName: 'analytics-4' } )( - AudienceSegmentationErrorWidget -); diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/ErrorWidgetContent.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/ErrorWidgetContent.js new file mode 100644 index 00000000000..2e0e1a30ab7 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/ErrorWidgetContent.js @@ -0,0 +1,142 @@ +/** + * ErrorWidgetContent component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { Button } from 'googlesitekit-components'; +import { Cell, Grid, Row } from '../../../../../../material-components'; +import { + BREAKPOINT_SMALL, + BREAKPOINT_TABLET, + useBreakpoint, +} from '../../../../../../hooks/useBreakpoint'; +import AudienceSegmentationErrorSVG from '../../../../../../../svg/graphics/audience-segmentation-error-full-width.svg'; +import { isInsufficientPermissionsError } from '../../../../../../util/errors'; +import ReportErrorActions from '../../../../../../components/ReportErrorActions'; +import GetHelpLink from '../GetHelpLink'; + +const ErrorWidgetContent = forwardRef( + ( { Widget, errors, onRetry, showRetryButton }, ref ) => { + const breakpoint = useBreakpoint(); + const isMobileBreakpoint = breakpoint === BREAKPOINT_SMALL; + const isTabletBreakpoint = breakpoint === BREAKPOINT_TABLET; + + const hasInsufficientPermissionsError = errors.some( + isInsufficientPermissionsError + ); + + return ( + + + + +

+ { hasInsufficientPermissionsError + ? __( + 'Insufficient permissions', + 'google-site-kit' + ) + : __( + 'Your visitor groups data loading failed', + 'google-site-kit' + ) } +

+
+ { showRetryButton && onRetry ? ( + + ) : ( + + ) } +
+
+ { ! isMobileBreakpoint && ! isTabletBreakpoint && ( + + + + ) } + { isTabletBreakpoint && ( + + + + ) } + { isMobileBreakpoint && ( + + + + ) } +
+
+
+ ); + } +); + +ErrorWidgetContent.propTypes = { + Widget: PropTypes.elementType.isRequired, + errors: PropTypes.arrayOf( PropTypes.object ).isRequired, + onRetry: PropTypes.func, + showRetryButton: PropTypes.bool, +}; + +export default ErrorWidgetContent; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js new file mode 100644 index 00000000000..329292f7aed --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js @@ -0,0 +1,106 @@ +/** + * AudienceSegmentationErrorWidget component. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { castArray } from 'lodash'; +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; + +import whenActive from '../../../../../../util/when-active'; +import { useDispatch } from 'googlesitekit-data'; +import { isInsufficientPermissionsError } from '../../../../../../util/errors'; +import { CORE_UI } from '../../../../../../googlesitekit/datastore/ui/constants'; +import { AUDIENCE_INFO_NOTICE_HIDE_UI } from '../InfoNoticeWidget/constants'; +import ErrorWidgetContent from './ErrorWidgetContent'; +import withIntersectionObserver from '../../../../../../util/withIntersectionObserver'; +import { trackEvent } from '../../../../../../util'; +import useViewContext from '../../../../../../hooks/useViewContext'; + +const ErrorWidgetContentWithIntersectionObserver = + withIntersectionObserver( ErrorWidgetContent ); + +function AudienceSegmentationErrorWidget( { + Widget, + errors, + onRetry, + showRetryButton, +} ) { + const viewContext = useViewContext(); + + const { setValue } = useDispatch( CORE_UI ); + + const errorsArray = errors ? castArray( errors ) : []; + + const hasInsufficientPermissionsError = errorsArray.some( + isInsufficientPermissionsError + ); + + const handleRetry = () => { + const action = hasInsufficientPermissionsError + ? 'insufficient_permissions_error_request_access' + : 'data_loading_error_retry'; + + trackEvent( `${ viewContext }_audiences-all-tiles`, action ).finally( + () => { + setValue( AUDIENCE_INFO_NOTICE_HIDE_UI, false ); + onRetry?.(); + } + ); + }; + + useEffect( () => { + // Set UI key to hide the info notice. + setValue( AUDIENCE_INFO_NOTICE_HIDE_UI, true ); + }, [ setValue ] ); + + return ( + { + const action = hasInsufficientPermissionsError + ? 'insufficient_permissions_error' + : 'data_loading_error'; + + trackEvent( `${ viewContext }_audiences-all-tiles`, action ); + } } + /> + ); +} + +AudienceSegmentationErrorWidget.propTypes = { + Widget: PropTypes.elementType.isRequired, + errors: PropTypes.oneOfType( [ + PropTypes.object, + PropTypes.arrayOf( PropTypes.object ), + ] ).isRequired, + onRetry: PropTypes.func, + showRetryButton: PropTypes.bool, +}; + +export default whenActive( { moduleName: 'analytics-4' } )( + AudienceSegmentationErrorWidget +); diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.stories.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.stories.js similarity index 90% rename from assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.stories.js rename to assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.stories.js index 7be14862812..79fc8ab5242 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.stories.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.stories.js @@ -26,12 +26,12 @@ import { provideModules, provideModuleRegistrations, provideUserInfo, -} from '../../../../../../../tests/js/test-utils'; -import WithRegistrySetup from '../../../../../../../tests/js/WithRegistrySetup'; -import { withWidgetComponentProps } from '../../../../../googlesitekit/widgets/util'; -import { MODULES_ANALYTICS_4 } from '../../../datastore/constants'; -import AudienceSegmentationErrorWidget from './AudienceSegmentationErrorWidget'; -import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../util/errors'; +} from '../../../../../../../../tests/js/test-utils'; +import WithRegistrySetup from '../../../../../../../../tests/js/WithRegistrySetup'; +import { withWidgetComponentProps } from '../../../../../../googlesitekit/widgets/util'; +import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; +import AudienceSegmentationErrorWidget from '.'; +import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../../util/errors'; const WidgetWithComponentProps = withWidgetComponentProps( 'audienceSegmentationErrorWidget' diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js similarity index 94% rename from assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.test.js rename to assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js index ff281d27ca3..cb3a3afeae9 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js @@ -26,11 +26,11 @@ import { provideModuleRegistrations, provideUserInfo, fireEvent, -} from '../../../../../../../tests/js/test-utils'; -import { withWidgetComponentProps } from '../../../../../googlesitekit/widgets/util'; -import { MODULES_ANALYTICS_4 } from '../../../datastore/constants'; -import AudienceSegmentationErrorWidget from './AudienceSegmentationErrorWidget'; -import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../util/errors'; +} from '../../../../../../../../tests/js/test-utils'; +import { withWidgetComponentProps } from '../../../../../../googlesitekit/widgets/util'; +import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; +import AudienceSegmentationErrorWidget from '.'; +import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../../util/errors'; describe( 'AudienceSegmentationErrorWidget', () => { let registry; From 66dd91120cf79f49f4086f159cc1509c00e0286b Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 12:05:13 +0100 Subject: [PATCH 07/31] Fix tests. --- .../__snapshots__/index.test.js.snap} | 0 .../dashboard/AudienceSegmentationErrorWidget/index.test.js | 4 ++++ .../dashboard/AudienceTilesWidget/AudienceTile/index.test.js | 1 + 3 files changed, 5 insertions(+) rename assets/js/modules/analytics-4/components/audience-segmentation/dashboard/{__snapshots__/AudienceSegmentationErrorWidget.test.js.snap => AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap} (100%) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/__snapshots__/AudienceSegmentationErrorWidget.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap similarity index 100% rename from assets/js/modules/analytics-4/components/audience-segmentation/dashboard/__snapshots__/AudienceSegmentationErrorWidget.test.js.snap rename to assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js index cb3a3afeae9..9a928872b61 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js @@ -26,6 +26,7 @@ import { provideModuleRegistrations, provideUserInfo, fireEvent, + waitForDefaultTimeouts, } from '../../../../../../../../tests/js/test-utils'; import { withWidgetComponentProps } from '../../../../../../googlesitekit/widgets/util'; import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; @@ -210,6 +211,9 @@ describe( 'AudienceSegmentationErrorWidget', () => { fireEvent.click( getByRole( 'button', { name: /retry/i } ) ); + // Allow the `trackEvent()` promise to resolve. + await waitForDefaultTimeouts(); + expect( handleRetrySpy ).toHaveBeenCalledTimes( 1 ); } ); } ); diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js index 068316415e1..5e66310269a 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js @@ -68,6 +68,7 @@ describe( 'AudienceTile', () => { const audienceResourceName = 'properties/12345/audiences/12345'; const props = { audienceResourceName, + audienceSlug: 'new-visitors', title: 'New visitors', toolTip: 'This is a tooltip', loaded: true, From 196b674f949b1cc7f0c2aabe1b0cace772e086fd Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 12:22:00 +0100 Subject: [PATCH 08/31] Add `view_tile_tooltip` GA event to AudienceTiles. --- .../dashboard/AudienceTilesWidget/AudienceTiles.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js index 90048d27672..ffd36e37070 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js @@ -53,7 +53,9 @@ import AudienceTileLoading from './AudienceTile/AudienceTileLoading'; import MaybePlaceholderTile from './MaybePlaceholderTile'; import useAudienceTilesReports from '../../../../hooks/useAudienceTilesReports'; import { isInvalidCustomDimensionError } from '../../../../utils/custom-dimensions'; +import useViewContext from '../../../../../../hooks/useViewContext'; import useViewOnly from '../../../../../../hooks/useViewOnly'; +import { trackEvent } from '../../../../../../util'; const hasZeroDataForAudience = ( report, dimensionName ) => { const audienceData = report?.rows?.find( @@ -64,6 +66,7 @@ const hasZeroDataForAudience = ( report, dimensionName ) => { }; export default function AudienceTiles( { Widget, widgetLoading } ) { + const viewContext = useViewContext(); const isViewOnly = useViewOnly(); const breakpoint = useBreakpoint(); const isTabbedBreakpoint = @@ -513,6 +516,13 @@ export default function AudienceTiles( { Widget, widgetLoading } ) { + trackEvent( + `${ viewContext }_audiences-tile`, + 'view_tile_tooltip', + audienceSlug + ) + } /> ); From 307b94fea56a5c20a1ace324ad19d0965472a860 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 13:09:04 +0100 Subject: [PATCH 09/31] Wrap `TileErrorContent` in `forwardRef()`. --- .../AudienceTileError/TileErrorContent.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js index 3c943ac7005..0982b22750b 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js @@ -24,6 +24,7 @@ import PropTypes from 'prop-types'; /** * WordPress dependencies */ +import { forwardRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -34,13 +35,16 @@ import AudienceTileErrorImage from '../../../../../../../../../svg/graphics/anal import ReportErrorActions from '../../../../../../../../components/ReportErrorActions'; import GetHelpLink from '../../../GetHelpLink'; -export default function TileErrorContent( { errors, onRetry } ) { +const TileErrorContent = forwardRef( ( { errors, onRetry }, ref ) => { const hasInsufficientPermissionsError = errors.some( ( err ) => isInsufficientPermissionsError( err ) ); return ( -
+
@@ -77,9 +81,11 @@ export default function TileErrorContent( { errors, onRetry } ) {
); -} +} ); TileErrorContent.propTypes = { errors: PropTypes.array.isRequired, onRetry: PropTypes.func.isRequired, }; + +export default TileErrorContent; From f3f460f3a2ff9b28d5a92b8918b49dc3c0e07f6d Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 13:38:38 +0100 Subject: [PATCH 10/31] Fix `insufficient_permissions_error_request_access` GA event in AudienceTileError and AudienceSegmentationErrorWidget. --- assets/js/components/ReportErrorActions.js | 3 + .../ErrorWidgetContent.js | 6 +- .../AudienceSegmentationErrorWidget/index.js | 23 ++--- .../AudienceTileError/TileErrorContent.js | 90 ++++++++++--------- .../AudienceTile/AudienceTileError/index.js | 13 +-- 5 files changed, 75 insertions(+), 60 deletions(-) diff --git a/assets/js/components/ReportErrorActions.js b/assets/js/components/ReportErrorActions.js index 4786bcbd6a4..1dff91d98bf 100644 --- a/assets/js/components/ReportErrorActions.js +++ b/assets/js/components/ReportErrorActions.js @@ -53,6 +53,7 @@ export default function ReportErrorActions( props ) { hideGetHelpLink, buttonVariant, onRetry, + onRequestAccess, getHelpClassName, RequestAccessButton, RetryButton, @@ -132,6 +133,7 @@ export default function ReportErrorActions( props ) { /> ) : (
@@ -135,7 +136,8 @@ const ErrorWidgetContent = forwardRef( ErrorWidgetContent.propTypes = { Widget: PropTypes.elementType.isRequired, errors: PropTypes.arrayOf( PropTypes.object ).isRequired, - onRetry: PropTypes.func, + onRetry: PropTypes.func.isRequired, + onRequestAccess: PropTypes.func.isRequired, showRetryButton: PropTypes.bool, }; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js index 329292f7aed..00b9371e474 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js @@ -57,16 +57,13 @@ function AudienceSegmentationErrorWidget( { ); const handleRetry = () => { - const action = hasInsufficientPermissionsError - ? 'insufficient_permissions_error_request_access' - : 'data_loading_error_retry'; - - trackEvent( `${ viewContext }_audiences-all-tiles`, action ).finally( - () => { - setValue( AUDIENCE_INFO_NOTICE_HIDE_UI, false ); - onRetry?.(); - } - ); + trackEvent( + `${ viewContext }_audiences-all-tiles`, + 'data_loading_error_retry' + ).finally( () => { + setValue( AUDIENCE_INFO_NOTICE_HIDE_UI, false ); + onRetry?.(); + } ); }; useEffect( () => { @@ -79,6 +76,12 @@ function AudienceSegmentationErrorWidget( { Widget={ Widget } errors={ errorsArray } onRetry={ handleRetry } + onRequestAccess={ () => { + trackEvent( + `${ viewContext }_audiences-all-tiles`, + 'insufficient_permissions_error_request_access' + ); + } } showRetryButton={ showRetryButton } onInView={ () => { const action = hasInsufficientPermissionsError diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js index 0982b22750b..3fc745c1290 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/TileErrorContent.js @@ -35,57 +35,61 @@ import AudienceTileErrorImage from '../../../../../../../../../svg/graphics/anal import ReportErrorActions from '../../../../../../../../components/ReportErrorActions'; import GetHelpLink from '../../../GetHelpLink'; -const TileErrorContent = forwardRef( ( { errors, onRetry }, ref ) => { - const hasInsufficientPermissionsError = errors.some( ( err ) => - isInsufficientPermissionsError( err ) - ); +const TileErrorContent = forwardRef( + ( { errors, onRetry, onRequestAccess }, ref ) => { + const hasInsufficientPermissionsError = errors.some( ( err ) => + isInsufficientPermissionsError( err ) + ); - return ( -
-
- -
-
-

- { hasInsufficientPermissionsError - ? __( - 'Insufficient permissions', - 'google-site-kit' - ) - : __( - 'Data loading failed', - 'google-site-kit' - ) } -

-
-
- + return ( +
+
+ +
+
+

+ { hasInsufficientPermissionsError + ? __( + 'Insufficient permissions', + 'google-site-kit' + ) + : __( + 'Data loading failed', + 'google-site-kit' + ) } +

+
+
+ +
-
- ); -} ); + ); + } +); TileErrorContent.propTypes = { errors: PropTypes.array.isRequired, onRetry: PropTypes.func.isRequired, + onRequestAccess: PropTypes.func.isRequired, }; export default TileErrorContent; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js index e410fcf714e..bdb991584db 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.js @@ -54,13 +54,16 @@ export default function AudienceTileError( { audienceSlug, errors } ) { ); } } onRetry={ () => { - const action = hasInsufficientPermissionsError - ? 'insufficient_permissions_error_request_access' - : 'data_loading_error_retry'; - trackEvent( `${ viewContext }_audiences-tile`, - action, + 'data_loading_error_retry', + audienceSlug + ); + } } + onRequestAccess={ () => { + trackEvent( + `${ viewContext }_audiences-tile`, + 'insufficient_permissions_error_request_access', audienceSlug ); } } From 4ba47f851a7fd40ba8acee495a84227cf55adb33 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 13:53:56 +0100 Subject: [PATCH 11/31] Prevent Tooltip's `onOpen` prop from being called while the tooltip is open. --- .../googlesitekit/components-gm2/Tooltip.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/assets/js/googlesitekit/components-gm2/Tooltip.js b/assets/js/googlesitekit/components-gm2/Tooltip.js index 16201a80bcb..3b5be1fc46b 100644 --- a/assets/js/googlesitekit/components-gm2/Tooltip.js +++ b/assets/js/googlesitekit/components-gm2/Tooltip.js @@ -23,12 +23,40 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Tooltip as MuiTooltip } from '@material-ui/core'; +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; + export default function Tooltip( { children, popperClassName, tooltipClassName, + onOpen, + onClose, ...props } ) { + const isOpen = useRef( false ); + + const handleOpen = onOpen + ? () => { + // This fixes a bug where the `onOpen` callback is called when the tooltip is already open. + if ( isOpen.current ) { + return; + } + + isOpen.current = true; + onOpen?.(); + } + : undefined; + + const handleClose = onOpen + ? () => { + isOpen.current = false; + onClose?.(); + } + : onClose; + return ( { children } From 87428d682478b6a18ae34670161984bca84565b7 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Fri, 25 Oct 2024 14:05:46 +0100 Subject: [PATCH 12/31] Fix GA event name, rename `view_partial_data_tile_tooltip` to `view_tile_partial_data_tooltip`. --- .../dashboard/AudienceTilesWidget/AudienceTile/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 ab4dc47a644..7718341cec0 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 @@ -196,13 +196,13 @@ export default function AudienceTile( { 'Still collecting full data for this timeframe, partial data is displayed for this group', 'google-site-kit' ) } - onTooltipOpen={ () => + onTooltipOpen={ () => { trackEvent( `${ viewContext }_audiences-tile`, - 'view_partial_data_tile_tooltip', + 'view_tile_partial_data_tooltip', audienceSlug - ) - } + ); + } } /> ) }
From c0731ce19e9e95a4897c103116104aeb37f01339 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 12:00:57 +0000 Subject: [PATCH 13/31] Add AudienceTileError tests for the `insufficient_permissions_error`, `insufficient_permissions_error_request_access`, `data_loading_error` and `data_loading_error_retry` GA actions. --- .../__snapshots__/index.test.js.snap | 110 +++++++ .../AudienceTileError/index.test.js | 311 ++++++++++++++++++ 2 files changed, 421 insertions(+) create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/__snapshots__/index.test.js.snap create mode 100644 assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..0845a1b64a9 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/__snapshots__/index.test.js.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AudienceTileError should correctly render the generic error variant 1`] = ` +
+
+
+ +
+
+

+ Data loading failed +

+
+
+
+ +
+
+
+
+
+
+`; + +exports[`AudienceTileError should correctly render the insufficient permissions variant 1`] = ` +
+
+
+ +
+
+

+ Insufficient permissions +

+
+
+
+ + + Request access + + +
+ Contact your administrator. Trouble getting access? + + + Get help + + +
+
+
+
+
+
+
+`; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js new file mode 100644 index 00000000000..aaa3e2ef2d2 --- /dev/null +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js @@ -0,0 +1,311 @@ +/** + * AudienceTileError component tests. + * + * Site Kit by Google, Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useIntersection as mockUseIntersection } from 'react-use'; + +/** + * Internal dependencies + */ +import AudienceTileError from '.'; +import { + act, + fireEvent, + render, +} from '../../../../../../../../../../tests/js/test-utils'; +import { + createTestRegistry, + provideModuleRegistrations, + provideModules, + provideSiteInfo, + provideUserInfo, + waitForDefaultTimeouts, +} from '../../../../../../../../../../tests/js/utils'; +import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../../../../../googlesitekit/constants'; +import { MODULES_ANALYTICS_4 } from '../../../../../../datastore/constants'; +import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../../../../util/errors'; +import * as tracking from '../../../../../../../../util/tracking'; + +jest.mock( 'react-use', () => ( { + ...jest.requireActual( 'react-use' ), + useIntersection: jest.fn(), +} ) ); + +const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); +mockTrackEvent.mockImplementation( () => Promise.resolve() ); + +describe( 'AudienceTileError', () => { + let registry; + + const audienceSlug = 'new-visitors'; + + const insufficientPermissionsError = { + code: 'test_error', + message: 'Error message.', + data: { + status: 403, + reason: ERROR_REASON_INSUFFICIENT_PERMISSIONS, + }, + }; + + const notFoundError = { + code: 404, + message: 'Not found or permission denied.', + data: { status: 404, reason: 'notFound' }, + }; + + const reportOptions = { + metrics: [ + { + name: 'totalUsers', + }, + ], + dimensions: [ + { + name: 'date', + }, + ], + startDate: '2020-08-11', + endDate: '2020-09-07', + }; + + beforeEach( () => { + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: false, + intersectionRatio: 0, + } ) ); + + registry = createTestRegistry(); + + provideModules( registry, [ + { + active: true, + connected: true, + slug: 'analytics-4', + }, + ] ); + provideModuleRegistrations( registry ); + provideSiteInfo( registry ); + provideUserInfo( registry ); + + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetSettings( { + accountID: '12345', + propertyID: '34567', + measurementID: '56789', + webDataStreamID: '78901', + } ); + } ); + + afterEach( () => { + mockTrackEvent.mockClear(); + } ); + + it( 'should correctly render the insufficient permissions variant', async () => { + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .receiveError( insufficientPermissionsError, 'getReport', [ + reportOptions, + ] ); + + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + const { container, getByText } = render( + , + { + registry, + } + ); + + expect( getByText( 'Insufficient permissions' ) ).toBeInTheDocument(); + + expect( container ).toMatchSnapshot(); + } ); + + it( 'should correctly render the generic error variant', async () => { + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .receiveError( notFoundError, 'getReport', [ reportOptions ] ); + + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + const { container, getByText, waitForRegistry } = render( + , + { + registry, + } + ); + + await act( waitForRegistry ); + + expect( getByText( 'Data loading failed' ) ).toBeInTheDocument(); + + expect( container ).toMatchSnapshot(); + } ); + + it( 'should track an event when the insufficient permissions error variant is viewed', async () => { + const { rerender, waitForRegistry } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + // Simulate the CTA becoming visible. + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: true, + intersectionRatio: 1, + } ) ); + + rerender( + + ); + + await act( waitForRegistry ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'insufficient_permissions_error', + audienceSlug + ); + } ); + + it( 'should track an event when "Request access" is clicked on the insufficient permissions error variant', async () => { + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .receiveError( insufficientPermissionsError, 'getReport', [ + reportOptions, + ] ); + + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + const { getByRole } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + await act( async () => { + fireEvent.click( + getByRole( 'button', { name: /Request access/ } ) + ); + + // Allow the `trackEvent()` promise to resolve. + await waitForDefaultTimeouts(); + } ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'insufficient_permissions_error_request_access', + audienceSlug + ); + } ); + + it( 'should track an event when the generic error variant is viewed', async () => { + const { rerender, waitForRegistry } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + // Simulate the CTA becoming visible. + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: true, + intersectionRatio: 1, + } ) ); + + rerender( + + ); + + await act( waitForRegistry ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'data_loading_error', + audienceSlug + ); + } ); + + it( 'should track an event when "Retry" is clicked on the generic error variant', async () => { + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .receiveError( notFoundError, 'getReport', [ reportOptions ] ); + + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + const { getByRole } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + await act( async () => { + fireEvent.click( getByRole( 'button', { name: /Retry/ } ) ); + + // Allow the `trackEvent()` promise to resolve. + await waitForDefaultTimeouts(); + } ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'data_loading_error_retry', + audienceSlug + ); + } ); +} ); From abd282335c96a7a8f590c7aad3e2c08663173595 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 13:30:32 +0000 Subject: [PATCH 14/31] Fix missing report for AudienceTilesWidget tests. --- .../AudienceTilesWidget/index.test.js | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js index 2182f4d3850..3e65ea37c37 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js @@ -28,6 +28,7 @@ import { import { createTestRegistry, freezeFetch, + muteFetch, provideModuleRegistrations, provideModules, provideUserAuthentication, @@ -70,6 +71,27 @@ function provideAudienceTilesMockReport( const { startDate, endDate } = dates; + const userCountReportOptions = { + startDate, + endDate, + dimensions: [ 'date' ], + metrics: [ { name: 'totalUsers' } ], + }; + + const userCountReportData = getAnalytics4MockResponse( + userCountReportOptions + ); + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .receiveGetReport( userCountReportData, { + options: userCountReportOptions, + } ); + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .finishResolution( 'getReport', [ userCountReportOptions ] ); + const reportOptions = { dimensions: isSiteKitAudiencePartialData ? [ { name: 'newVsReturning' } ] @@ -244,6 +266,12 @@ describe( 'AudienceTilesWidget', () => { registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetSettings( { propertyID: '12345', } ); + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetProperty( + { + createTime: '2014-10-02T15:01:23Z', + }, + { propertyID: '12345' } + ); registry .dispatch( MODULES_ANALYTICS_4 ) .receiveResourceDataAvailabilityDates( { @@ -251,11 +279,19 @@ describe( 'AudienceTilesWidget', () => { acc[ name ] = 20201220; return acc; }, {} ), - customDimension: {}, + customDimension: { + googlesitekit_post_type: 20201220, + }, property: { 12345: 20201218, }, } ); + + muteFetch( + new RegExp( + '^/google-site-kit/v1/modules/analytics-4/data/data-available' + ) + ); } ); afterEach( () => { @@ -320,6 +356,9 @@ describe( 'AudienceTilesWidget', () => { await waitFor( () => { expect( container ).toMatchSnapshot(); } ); + + // expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + // expect( mockTrackEvent ).toHaveBeenCalledWith( {} ); } ); it( 'should render when all configured audiences are matching available audiences', async () => { From 019ee91c73d60dc66f05c30c6e19e9acb97a8db6 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 13:52:31 +0000 Subject: [PATCH 15/31] Fix call to `view_tile_tooltip` in AudienceTiles. --- .../dashboard/AudienceTilesWidget/AudienceTiles.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js index ffd36e37070..980f152540c 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTiles.js @@ -516,13 +516,13 @@ export default function AudienceTiles( { Widget, widgetLoading } ) { + onOpen={ () => { trackEvent( `${ viewContext }_audiences-tile`, 'view_tile_tooltip', audienceSlug - ) - } + ); + } } /> ); From e8c2dd0f9285838fd030124c776dcb363a5a2481 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 15:30:49 +0000 Subject: [PATCH 16/31] Fix `view_tile_collecting_data` GA event name. --- .../AudienceTile/AudienceTileZeroData/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js index fa00ed90061..d2244abdf61 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileZeroData/index.js @@ -63,7 +63,7 @@ export default function AudienceTileZeroData( { onInView={ () => { trackEvent( `${ viewContext }_audiences-tile`, - 'view_tile_zero_data', + 'view_tile_collecting_data', audienceSlug ); } } From a3ce229c1e1be828a24da72c2e5a65392b85a474 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 16:16:26 +0000 Subject: [PATCH 17/31] Add tests for `view_tile_collecting_data` and `temporarily_hide` GA events in AudienceTileZeroData. --- .../__snapshots__/index.test.js.snap | 51 ++++++++++ .../AudienceTile/index.test.js | 94 +++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap index 5e2f54cecae..87fded27736 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap @@ -252,3 +252,54 @@ exports[`AudienceTile should render the AudienceTile component 1`] = `
`; + +exports[`AudienceTile with zero data, in the partial data state should render the zero data tile 1`] = ` +
+
+
+
+
+
+ +

+ Site Kit is collecting data for this group. +

+

+ You can hide this group until data is available. +

+ +
+
+
+
+
+
+`; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js index 5e66310269a..e22b69ef35e 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js @@ -308,6 +308,100 @@ describe( 'AudienceTile', () => { } ); } ); + describe( 'with zero data, in the partial data state', () => { + it( 'should render the zero data tile', () => { + const { container, getByText } = render( + , + { + registry, + } + ); + + expect( + getByText( 'Site Kit is collecting data for this group.' ) + ).toBeInTheDocument(); + + expect( container ).toMatchSnapshot(); + } ); + + it( 'should track an event when the tile is viewed', () => { + const { rerender } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + // Simulate the CTA becoming visible. + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: true, + intersectionRatio: 1, + } ) ); + + rerender( + + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 1 ); + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'view_tile_collecting_data', + 'new-visitors' + ); + } ); + + it( 'should track an event when the "Temporarily hide" button is clicked', async () => { + const { getByRole } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + await act( async () => { + fireEvent.click( + getByRole( 'button', { name: /temporarily hide/i } ) + ); + + // Allow the `trackEvent()` promise to resolve. + await waitForDefaultTimeouts(); + } ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 1 ); + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'temporarily_hide', + 'new-visitors' + ); + } ); + } ); + describe( 'AudienceErrorModal', () => { let getByRole, getByText; From 719df73a7b4853f384e015bb623d09f3453f6840 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 16:18:31 +0000 Subject: [PATCH 18/31] Update tests for consistency. --- .../AudienceTile/AudienceTileError/index.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js index aaa3e2ef2d2..3ec4a1f39f0 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js @@ -195,7 +195,7 @@ describe( 'AudienceTileError', () => { await act( waitForRegistry ); expect( mockTrackEvent ).toHaveBeenCalledWith( - `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'mainDashboard_audiences-tile', 'insufficient_permissions_error', audienceSlug ); @@ -233,7 +233,7 @@ describe( 'AudienceTileError', () => { } ); expect( mockTrackEvent ).toHaveBeenCalledWith( - `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'mainDashboard_audiences-tile', 'insufficient_permissions_error_request_access', audienceSlug ); From da4d6337fba9f47b498e5ba8951cf02a005e60d7 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 16:26:38 +0000 Subject: [PATCH 19/31] Reorganise tests. --- .../__snapshots__/index.test.js.snap | 100 ++++---- .../index.test.js | 229 ++++++++++-------- 2 files changed, 176 insertions(+), 153 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap index fd6a957fbf8..66b88456598 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AudienceSegmentationErrorWidget should render a retry button when \`onRetry\` and \`showRetryButton\` props are passed 1`] = ` +exports[`AudienceSegmentationErrorWidget default error state should render correctly 1`] = `
- + + Retry + + +
`; -exports[`AudienceSegmentationErrorWidget should render the default error state 1`] = ` +exports[`AudienceSegmentationErrorWidget insufficient permissions error state should render correctly 1`] = `
- Your visitor groups data loading failed + Insufficient permissions
- + +
+ Contact your administrator. Trouble getting access? + + + Get help + + +
@@ -102,7 +127,7 @@ exports[`AudienceSegmentationErrorWidget should render the default error state 1
`; -exports[`AudienceSegmentationErrorWidget should render the insufficient permissions error state 1`] = ` +exports[`AudienceSegmentationErrorWidget should render a retry button when \`onRetry\` and \`showRetryButton\` props are passed 1`] = `
- Insufficient permissions + Your visitor groups data loading failed
-
- - - Request access - - -
- Contact your administrator. Trouble getting access? - - - Get help - - -
-
+ Retry + +
( { + ...jest.requireActual( 'react-use' ), + useIntersection: jest.fn(), +} ) ); + +const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); +mockTrackEvent.mockImplementation( () => Promise.resolve() ); describe( 'AudienceSegmentationErrorWidget', () => { let registry; @@ -50,129 +59,143 @@ describe( 'AudienceSegmentationErrorWidget', () => { registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetSettings( {} ); } ); + afterEach( () => { + mockTrackEvent.mockClear(); + } ); + const WidgetWithComponentProps = withWidgetComponentProps( 'audienceSegmentationErrorWidget' )( AudienceSegmentationErrorWidget ); - it( 'should render the default error state', async () => { - await registry.dispatch( MODULES_ANALYTICS_4 ).receiveError( - { - code: 'test-error-code', - message: 'Test error message', - data: { - reason: '', - }, - }, - 'getReport', - [ + describe( 'default error state', () => { + it( 'should render correctly', async () => { + await registry.dispatch( MODULES_ANALYTICS_4 ).receiveError( { - metrics: [ - { - name: 'totalUsers', - }, - ], - dimensions: [ - { - name: 'date', - }, - ], - startDate: '2020-08-11', - endDate: '2020-09-07', + code: 'test-error-code', + message: 'Test error message', + data: { + reason: '', + }, }, - ] - ); - - const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + 'getReport', + [ + { + metrics: [ + { + name: 'totalUsers', + }, + ], + dimensions: [ + { + name: 'date', + }, + ], + startDate: '2020-08-11', + endDate: '2020-09-07', + }, + ] + ); + + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + const { + container, + getByText, + getByRole, + queryByText, + waitForRegistry, + } = render( , { + registry, + } ); - const { - container, - getByText, - getByRole, - queryByText, - waitForRegistry, - } = render( , { - registry, - } ); + await waitForRegistry(); - await waitForRegistry(); + expect( container ).toMatchSnapshot(); - expect( container ).toMatchSnapshot(); + expect( + getByText( 'Your visitor groups data loading failed' ) + ).toBeInTheDocument(); - expect( - getByText( 'Your visitor groups data loading failed' ) - ).toBeInTheDocument(); + expect( + getByRole( 'button', { name: /retry/i } ) + ).toBeInTheDocument(); - expect( getByRole( 'button', { name: /retry/i } ) ).toBeInTheDocument(); - - // Verify that it's not an "Insufficient permissions" error. - expect( - queryByText( 'Insufficient permissions' ) - ).not.toBeInTheDocument(); - expect( queryByText( /request access/i ) ).not.toBeInTheDocument(); + // Verify that it's not an "Insufficient permissions" error. + expect( + queryByText( 'Insufficient permissions' ) + ).not.toBeInTheDocument(); + expect( queryByText( /request access/i ) ).not.toBeInTheDocument(); + } ); } ); - it( 'should render the insufficient permissions error state', async () => { - const [ accountID, propertyID, measurementID, webDataStreamID ] = [ - '12345', - '34567', - '56789', - '78901', - ]; - - await registry - .dispatch( MODULES_ANALYTICS_4 ) - .setAccountID( accountID ); - await registry - .dispatch( MODULES_ANALYTICS_4 ) - .setPropertyID( propertyID ); - await registry - .dispatch( MODULES_ANALYTICS_4 ) - .setMeasurementID( measurementID ); - await registry - .dispatch( MODULES_ANALYTICS_4 ) - .setWebDataStreamID( webDataStreamID ); - await registry.dispatch( MODULES_ANALYTICS_4 ).receiveError( - { - code: 'test-error-code', - message: 'Test error message', - data: { - reason: ERROR_REASON_INSUFFICIENT_PERMISSIONS, + describe( 'insufficient permissions error state', () => { + it( 'should render correctly', async () => { + const [ accountID, propertyID, measurementID, webDataStreamID ] = [ + '12345', + '34567', + '56789', + '78901', + ]; + + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .setAccountID( accountID ); + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .setPropertyID( propertyID ); + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .setMeasurementID( measurementID ); + await registry + .dispatch( MODULES_ANALYTICS_4 ) + .setWebDataStreamID( webDataStreamID ); + await registry.dispatch( MODULES_ANALYTICS_4 ).receiveError( + { + code: 'test-error-code', + message: 'Test error message', + data: { + reason: ERROR_REASON_INSUFFICIENT_PERMISSIONS, + }, }, - }, - 'getAccountID' - ); - - const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + 'getAccountID' + ); + + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + const { + container, + getByText, + getByRole, + queryByText, + waitForRegistry, + } = render( , { + registry, + } ); - const { - container, - getByText, - getByRole, - queryByText, - waitForRegistry, - } = render( , { - registry, - } ); + await waitForRegistry(); - await waitForRegistry(); + expect( container ).toMatchSnapshot(); - expect( container ).toMatchSnapshot(); + expect( + getByText( 'Insufficient permissions' ) + ).toBeInTheDocument(); - expect( getByText( 'Insufficient permissions' ) ).toBeInTheDocument(); + expect( + getByText( + 'Contact your administrator. Trouble getting access?' + ) + ).toBeInTheDocument(); - expect( - getByText( 'Contact your administrator. Trouble getting access?' ) - ).toBeInTheDocument(); + expect( + getByRole( 'button', { name: /request access/i } ) + ).toBeInTheDocument(); - expect( - getByRole( 'button', { name: /request access/i } ) - ).toBeInTheDocument(); - - // Verify that it's not a default error. - expect( - queryByText( 'Your visitor groups data loading failed' ) - ).not.toBeInTheDocument(); - expect( queryByText( /retry/i ) ).not.toBeInTheDocument(); + // Verify that it's not a default error. + expect( + queryByText( 'Your visitor groups data loading failed' ) + ).not.toBeInTheDocument(); + expect( queryByText( /retry/i ) ).not.toBeInTheDocument(); + } ); } ); it( 'should render a retry button when `onRetry` and `showRetryButton` props are passed', async () => { From 933e8b9868bd742fab53b4deae8cee11a082b50f Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 16:43:00 +0000 Subject: [PATCH 20/31] Update tests for consistency. --- .../AudienceTile/AudienceTileError/index.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js index 3ec4a1f39f0..ec663addd5c 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError/index.test.js @@ -269,7 +269,7 @@ describe( 'AudienceTileError', () => { await act( waitForRegistry ); expect( mockTrackEvent ).toHaveBeenCalledWith( - `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'mainDashboard_audiences-tile', 'data_loading_error', audienceSlug ); @@ -303,7 +303,7 @@ describe( 'AudienceTileError', () => { } ); expect( mockTrackEvent ).toHaveBeenCalledWith( - `${ VIEW_CONTEXT_MAIN_DASHBOARD }_audiences-tile`, + 'mainDashboard_audiences-tile', 'data_loading_error_retry', audienceSlug ); From 9ad495c07e877fb9be6b1ec71a03a0101fbe65ee Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 16:43:16 +0000 Subject: [PATCH 21/31] Add tests for `insufficient_permissions_error`, `insufficient_permissions_error_request_access`, `data_loading_error` and `data_loading_error_retry` GA actions in AudienceSegmentationErrorWidget. --- .../index.test.js | 114 ++++++++++++++++-- 1 file changed, 107 insertions(+), 7 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js index 054082976dd..3b2f347d9e9 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js @@ -15,6 +15,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * External dependencies + */ +import { useIntersection as mockUseIntersection } from 'react-use'; /** * Internal dependencies @@ -27,8 +31,10 @@ import { provideUserInfo, fireEvent, waitForDefaultTimeouts, + act, } from '../../../../../../../../tests/js/test-utils'; import { withWidgetComponentProps } from '../../../../../../googlesitekit/widgets/util'; +import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../../../googlesitekit/constants'; import { MODULES_ANALYTICS_4 } from '../../../../datastore/constants'; import AudienceSegmentationErrorWidget from '.'; import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../../../util/errors'; @@ -46,6 +52,11 @@ describe( 'AudienceSegmentationErrorWidget', () => { let registry; beforeEach( () => { + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: false, + intersectionRatio: 0, + } ) ); + registry = createTestRegistry(); provideModules( registry, [ { @@ -68,7 +79,14 @@ describe( 'AudienceSegmentationErrorWidget', () => { )( AudienceSegmentationErrorWidget ); describe( 'default error state', () => { - it( 'should render correctly', async () => { + let container, + getByText, + getByRole, + queryByText, + rerender, + waitForRegistry; + + beforeEach( async () => { await registry.dispatch( MODULES_ANALYTICS_4 ).receiveError( { code: 'test-error-code', @@ -98,18 +116,22 @@ describe( 'AudienceSegmentationErrorWidget', () => { const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); - const { + ( { container, getByText, getByRole, queryByText, + rerender, waitForRegistry, } = render( , { registry, - } ); + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } ) ); await waitForRegistry(); + } ); + it( 'should render correctly', () => { expect( container ).toMatchSnapshot(); expect( @@ -126,10 +148,50 @@ describe( 'AudienceSegmentationErrorWidget', () => { ).not.toBeInTheDocument(); expect( queryByText( /request access/i ) ).not.toBeInTheDocument(); } ); + + it( 'should track an event when the widget is viewed', () => { + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + // Simulate the CTA becoming visible. + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: true, + intersectionRatio: 1, + } ) ); + + rerender( ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-all-tiles', + 'data_loading_error' + ); + } ); + + it( 'should track an event when the "Retry" button is clicked', async () => { + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + fireEvent.click( getByRole( 'button', { name: /retry/i } ) ); + + // Allow the `trackEvent()` promise to resolve. + await act( waitForDefaultTimeouts ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-all-tiles', + 'data_loading_error_retry' + ); + } ); } ); describe( 'insufficient permissions error state', () => { - it( 'should render correctly', async () => { + let container, + getByText, + getByRole, + queryByText, + rerender, + waitForRegistry; + + beforeEach( async () => { const [ accountID, propertyID, measurementID, webDataStreamID ] = [ '12345', '34567', @@ -162,18 +224,21 @@ describe( 'AudienceSegmentationErrorWidget', () => { const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); - const { + ( { container, getByText, getByRole, queryByText, + rerender, waitForRegistry, } = render( , { registry, - } ); + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } ) ); await waitForRegistry(); - + } ); + it( 'should render correctly', () => { expect( container ).toMatchSnapshot(); expect( @@ -196,6 +261,41 @@ describe( 'AudienceSegmentationErrorWidget', () => { ).not.toBeInTheDocument(); expect( queryByText( /retry/i ) ).not.toBeInTheDocument(); } ); + + it( 'should track an event when the widget is viewed', () => { + const errors = registry.select( MODULES_ANALYTICS_4 ).getErrors(); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + // Simulate the CTA becoming visible. + mockUseIntersection.mockImplementation( () => ( { + isIntersecting: true, + intersectionRatio: 1, + } ) ); + + rerender( ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-all-tiles', + 'insufficient_permissions_error' + ); + } ); + + it( 'should track an event when the "Request access" button is clicked', async () => { + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + fireEvent.click( + getByRole( 'button', { name: /request access/i } ) + ); + + // Allow the `trackEvent()` promise to resolve. + await act( waitForDefaultTimeouts ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-all-tiles', + 'insufficient_permissions_error_request_access' + ); + } ); } ); it( 'should render a retry button when `onRetry` and `showRetryButton` props are passed', async () => { From 0688a996ddf5879f05b9364681b72cadafdab814 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 16:48:28 +0000 Subject: [PATCH 22/31] Restore snapshot order. --- .../__snapshots__/index.test.js.snap | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap index 66b88456598..44cfeb62358 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/__snapshots__/index.test.js.snap @@ -1,5 +1,54 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`AudienceSegmentationErrorWidget should render a retry button when \`onRetry\` and \`showRetryButton\` props are passed 1`] = ` +
+
+
+
+
+
+

+ Your visitor groups data loading failed +

+
+ +
+
+
+ +
+
+
+
+
+
+`; + exports[`AudienceSegmentationErrorWidget default error state should render correctly 1`] = `
`; - -exports[`AudienceSegmentationErrorWidget should render a retry button when \`onRetry\` and \`showRetryButton\` props are passed 1`] = ` -
-
-
-
-
-
-

- Your visitor groups data loading failed -

-
- -
-
-
- -
-
-
-
-
-
-`; From a743d48a436b59fc59ed486143b5069af4ca59f1 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 17:08:56 +0000 Subject: [PATCH 23/31] Add test for `view_tile_tooltip` GA event in AudienceTilesWidget. --- .../AudienceTilesWidget/index.test.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js index 3e65ea37c37..bb329bc16cf 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js @@ -22,6 +22,7 @@ import AudienceTilesWidget from '.'; import { act, + fireEvent, render, waitFor, } from '../../../../../../../../tests/js/test-utils'; @@ -36,6 +37,7 @@ import { waitForTimeouts, } from '../../../../../../../../tests/js/utils'; import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants'; +import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../../../googlesitekit/constants'; import { withWidgetComponentProps } from '../../../../../../googlesitekit/widgets/util'; import { getPreviousDate } from '../../../../../../util'; import { @@ -47,8 +49,12 @@ import { DATE_RANGE_OFFSET, MODULES_ANALYTICS_4, } from '../../../../datastore/constants'; +import * as tracking from '../../../../../../util/tracking'; import { getAnalytics4MockResponse } from '../../../../utils/data-mock'; +const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); +mockTrackEvent.mockImplementation( () => Promise.resolve() ); + /** * Generates mock response for audience tiles component. * @@ -540,6 +546,50 @@ describe( 'AudienceTilesWidget', () => { await act( () => waitForTimeouts( 100 ) ); } ); + it( 'should track an event when the tooltip for an audience tab is viewed', async () => { + const configuredAudiences = [ 'properties/12345/audiences/1' ]; + + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetSettings( { + availableAudiencesLastSyncedAt: ( Date.now() - 1000 ) / 1000, + } ); + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setAvailableAudiences( availableAudiences ); + + registry.dispatch( CORE_USER ).receiveGetAudienceSettings( { + configuredAudiences, + isAudienceSegmentationWidgetHidden: false, + } ); + + provideAudienceTilesMockReport( registry, configuredAudiences ); + + const { container, waitForRegistry } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + await waitForRegistry(); + + expect( mockTrackEvent ).not.toHaveBeenCalled(); + + fireEvent.mouseOver( + container.querySelector( '.googlesitekit-info-tooltip' ) + ); + + // Wait for the tooltip to appear, its delay is 100ms. + await waitForTimeouts( 100 ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'view_tile_tooltip', + 'all-users' + ); + } ); + it( 'should show the "no audiences" banner when there is no matching audience', async () => { fetchMock.postOnce( syncAvailableAudiencesEndpoint, { body: availableAudiences, From 098b8c136602acbe0b9070f68693ed68228b7d42 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 17:22:48 +0000 Subject: [PATCH 24/31] Add test for `view_tile_tooltip` GA event in AudienceTile (zero data state). --- .../__snapshots__/index.test.js.snap | 15 ++++ .../AudienceTile/index.test.js | 69 +++++++++++-------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap index 87fded27736..48639168ea8 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap @@ -267,6 +267,21 @@ exports[`AudienceTile with zero data, in the partial data state should render th
+
+
+ New visitors + + + +
+
diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js index e22b69ef35e..c1c5a481b8d 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js @@ -37,6 +37,7 @@ import { provideSiteInfo, provideUserAuthentication, waitForDefaultTimeouts, + waitForTimeouts, } from '../../../../../../../../../tests/js/utils'; import { VIEW_CONTEXT_MAIN_DASHBOARD } from '../../../../../../../googlesitekit/constants'; import { CORE_SITE } from '../../../../../../../googlesitekit/datastore/site/constants'; @@ -50,6 +51,10 @@ import { } from '../../../../../datastore/constants'; import { provideCustomDimensionError } from '../../../../../utils/custom-dimensions'; import { getAnalytics4MockResponse } from '../../../../../utils/data-mock'; +import { + getViewportWidth, + setViewportWidth, +} from '../../../../../../../../../tests/js/viewport-width-utils'; jest.mock( 'react-use', () => ( { ...jest.requireActual( 'react-use' ), @@ -309,19 +314,34 @@ describe( 'AudienceTile', () => { } ); describe( 'with zero data, in the partial data state', () => { - it( 'should render the zero data tile', () => { - const { container, getByText } = render( + let originalViewportWidth, container, getByRole, getByText, rerender; + + beforeEach( () => { + originalViewportWidth = getViewportWidth(); + + // Ensure the viewport is wide enough to render the tooltip. + setViewportWidth( 1024 ); + + ( { container, getByRole, getByText, rerender } = render( , { registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, } - ); + ) ); + } ); + + afterEach( () => { + setViewportWidth( originalViewportWidth ); + } ); + it( 'should render the zero data tile', () => { expect( getByText( 'Site Kit is collecting data for this group.' ) ).toBeInTheDocument(); @@ -330,19 +350,6 @@ describe( 'AudienceTile', () => { } ); it( 'should track an event when the tile is viewed', () => { - const { rerender } = render( - , - { - registry, - viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, - } - ); - expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); // Simulate the CTA becoming visible. @@ -369,19 +376,6 @@ describe( 'AudienceTile', () => { } ); it( 'should track an event when the "Temporarily hide" button is clicked', async () => { - const { getByRole } = render( - , - { - registry, - viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, - } - ); - expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); await act( async () => { @@ -400,6 +394,23 @@ describe( 'AudienceTile', () => { 'new-visitors' ); } ); + + it( 'should track an event when the tooltip is viewed', async () => { + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + fireEvent.mouseOver( + container.querySelector( '.googlesitekit-info-tooltip' ) + ); + + // Wait for the tooltip to appear, its delay is 100ms. + await act( () => waitForTimeouts( 100 ) ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'view_tile_tooltip', + 'new-visitors' + ); + } ); } ); describe( 'AudienceErrorModal', () => { From b2294206b825007931bf7ff74cf39387010b003d Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 17:30:26 +0000 Subject: [PATCH 25/31] Add test for `view_tile_tooltip` GA event in AudienceTile. --- .../__snapshots__/index.test.js.snap | 15 ++++++ .../AudienceTile/index.test.js | 47 ++++++++++++++----- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap index 48639168ea8..6442afb2fd2 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap @@ -11,6 +11,21 @@ exports[`AudienceTile should render the AudienceTile component 1`] = `
+
+
+ New visitors + + + +
+
diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js index c1c5a481b8d..b7af871690c 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/index.test.js @@ -65,7 +65,7 @@ const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); mockTrackEvent.mockImplementation( () => Promise.resolve() ); describe( 'AudienceTile', () => { - let registry; + let registry, originalViewportWidth; const WidgetWithComponentProps = withWidgetComponentProps( 'audienceTile' )( AudienceTile ); @@ -75,7 +75,7 @@ describe( 'AudienceTile', () => { audienceResourceName, audienceSlug: 'new-visitors', title: 'New visitors', - toolTip: 'This is a tooltip', + infoTooltip: 'This is a tooltip', loaded: true, visitors: { metricValue: 24200, @@ -156,6 +156,11 @@ describe( 'AudienceTile', () => { }; beforeEach( () => { + originalViewportWidth = getViewportWidth(); + + // Ensure the viewport is wide enough to render the tooltips. + setViewportWidth( 1024 ); + mockUseIntersection.mockImplementation( () => ( { isIntersecting: false, intersectionRatio: 0, @@ -222,6 +227,7 @@ describe( 'AudienceTile', () => { afterEach( () => { mockTrackEvent.mockClear(); + setViewportWidth( originalViewportWidth ); } ); it( 'should render the AudienceTile component', () => { @@ -235,6 +241,31 @@ describe( 'AudienceTile', () => { expect( container ).toMatchSnapshot(); } ); + it( 'should track an event when the tooltip is viewed', async () => { + const { container } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + fireEvent.mouseOver( + container.querySelector( '.googlesitekit-info-tooltip' ) + ); + + // Wait for the tooltip to appear, its delay is 100ms. + await act( () => waitForTimeouts( 100 ) ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'view_tile_tooltip', + 'new-visitors' + ); + } ); + describe( 'Top content metric', () => { it( 'should track an event when the create custom dimension CTA is viewed', () => { const { getByRole, rerender } = render( @@ -314,18 +345,12 @@ describe( 'AudienceTile', () => { } ); describe( 'with zero data, in the partial data state', () => { - let originalViewportWidth, container, getByRole, getByText, rerender; + let container, getByRole, getByText, rerender; beforeEach( () => { - originalViewportWidth = getViewportWidth(); - - // Ensure the viewport is wide enough to render the tooltip. - setViewportWidth( 1024 ); - ( { container, getByRole, getByText, rerender } = render( { ) ); } ); - afterEach( () => { - setViewportWidth( originalViewportWidth ); - } ); - it( 'should render the zero data tile', () => { expect( getByText( 'Site Kit is collecting data for this group.' ) From d207a0014b64a9473d9ac31e7a057a8032458320 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 17:49:37 +0000 Subject: [PATCH 26/31] Add test for `view_tile_partial_data_tooltip` GA event in AudienceTile. --- .../__snapshots__/index.test.js.snap | 279 ++++++++++++++++++ .../AudienceTile/index.test.js | 61 +++- 2 files changed, 339 insertions(+), 1 deletion(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap index 6442afb2fd2..8d7c502af41 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap @@ -1,5 +1,284 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`AudienceTile Partial data badge should render a partial data badge 1`] = ` +
+
+
+
+
+
+ New visitors + + + +
+ + Partial data + + + + +
+
+
+
+ +
+
+
+ 24K +
+
+ Visitors +
+
+
+
+ +18.5% +
+
+
+
+
+ +
+
+
+ 3 +
+
+ Visits per visitor +
+
+
+
+ +50% +
+
+
+
+
+ +
+
+
+ 2 +
+
+ Pages per visit +
+
+
+
+ -33.3% +
+
+
+
+
+ +
+
+
+ 1.6K +
+
+ 33.3% of total pageviews +
+
+
+
+ +4.1% +
+
+
+
+
+ +
+
+
+ Cities with the most visitors +
+
+
+
+ Dublin +
+
+ 63.8% +
+
+
+
+ London +
+
+ 20.7% +
+
+
+
+ New York +
+
+ 15.5% +
+
+
+
+
+
+
+ +
+
+
+ Top content by pageviews +
+
+
+ No data to show +

+ Update Analytics to track metric +

+ +
+
+
+
+
+
+
+
+
+`; + exports[`AudienceTile should render the AudienceTile component 1`] = `
( { ...jest.requireActual( 'react-use' ), @@ -324,13 +325,71 @@ describe( 'AudienceTile', () => { } ); describe( 'Partial data badge', () => { + beforeEach( () => { + const referenceDate = registry + .select( CORE_USER ) + .getReferenceDate(); + + const dataAvailabilityDate = Number( + getPreviousDate( referenceDate, 1 ).replace( /-/g, '' ) + ); + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setResourceDataAvailabilityDate( + audienceResourceName, + 'audience', + dataAvailabilityDate + ); + } ); + + it( 'should render a partial data badge', () => { + const { container, getByText } = render( + , + { + registry, + } + ); + + expect( getByText( 'Partial data' ) ).toBeInTheDocument(); + + expect( container ).toMatchSnapshot(); + } ); + + it( 'should track an event when the partial data badge tooltip is viewed', async () => { + const { container } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + fireEvent.mouseOver( + container.querySelector( + '.googlesitekit-audience-segmentation-partial-data-badge .googlesitekit-info-tooltip' + ) + ); + + // Wait for the tooltip to appear, its delay is 100ms. + await act( () => waitForTimeouts( 100 ) ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'view_tile_partial_data_tooltip', + 'new-visitors' + ); + } ); + it( 'should not display partial data badge for tile or top content metrics when property is in partial state', () => { registry .dispatch( MODULES_ANALYTICS_4 ) .receiveIsGatheringData( true ); const { container } = render( - , + , { registry, } From 55cbf01989faea3dfdbc7c9cd5fe025e9c1f17f2 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 18:03:26 +0000 Subject: [PATCH 27/31] Add tests for the `view_top_content_partial_data_tooltip` and `view_top_content_partial_data_tooltip` GA events in AudienceTile. --- .../__snapshots__/index.test.js.snap | 281 +++++++++++++++++- .../AudienceTile/index.test.js | 76 ++++- 2 files changed, 354 insertions(+), 3 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap index 8d7c502af41..b9ecd4443d2 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/__snapshots__/index.test.js.snap @@ -1,6 +1,285 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AudienceTile Partial data badge should render a partial data badge 1`] = ` +exports[`AudienceTile Partial data badge should render a partial data badge for the "Top content" metric area when the custom dimension is in the partial data state and the audience is not 1`] = ` +
+
+
+
+
+
+ New visitors + + + +
+
+
+
+
+ +
+
+
+ 24K +
+
+ Visitors +
+
+
+
+ +18.5% +
+
+
+
+
+ +
+
+
+ 3 +
+
+ Visits per visitor +
+
+
+
+ +50% +
+
+
+
+
+ +
+
+
+ 2 +
+
+ Pages per visit +
+
+
+
+ -33.3% +
+
+
+
+
+ +
+
+
+ 1.6K +
+
+ 33.3% of total pageviews +
+
+
+
+ +4.1% +
+
+
+
+
+ +
+
+
+ Cities with the most visitors +
+
+
+
+ Dublin +
+
+ 63.8% +
+
+
+
+ London +
+
+ 20.7% +
+
+
+
+ New York +
+
+ 15.5% +
+
+
+
+
+
+
+ +
+
+
+ Top content by pageviews + + Partial data + + + + +
+
+
+ No data to show +

+ Update Analytics to track metric +

+ +
+
+
+
+
+
+
+
+
+`; + +exports[`AudienceTile Partial data badge should render a partial data badge for the audience when the audience is in the partial data state 1`] = `
{ 'audience', dataAvailabilityDate ); + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setResourceDataAvailabilityDate( + 'googlesitekit_post_type', + 'customDimension', + dataAvailabilityDate + ); } ); - it( 'should render a partial data badge', () => { + it( 'should render a partial data badge for the audience when the audience is in the partial data state', () => { const { container, getByText } = render( , { @@ -356,7 +365,35 @@ describe( 'AudienceTile', () => { expect( container ).toMatchSnapshot(); } ); - it( 'should track an event when the partial data badge tooltip is viewed', async () => { + it( 'should render a partial data badge for the "Top content" metric area when the custom dimension is in the partial data state and the audience is not', () => { + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setResourceDataAvailabilityDate( + audienceResourceName, + 'audience', + 20201220 + ); + + const { container } = render( + , + { + registry, + } + ); + + expect( + domGetByText( + container.querySelector( + '.googlesitekit-audience-segmentation-tile-metric--top-content' + ), + 'Partial data' + ) + ).toBeInTheDocument(); + + expect( container ).toMatchSnapshot(); + } ); + + it( "should track an event when the partial data badge for the audience's tooltip is viewed", async () => { const { container } = render( , { @@ -383,6 +420,41 @@ describe( 'AudienceTile', () => { ); } ); + it( "should track an event when the partial data badge for the 'Top content' metric area's tooltip is viewed", async () => { + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setResourceDataAvailabilityDate( + audienceResourceName, + 'audience', + 20201220 + ); + + const { container } = render( + , + { + registry, + viewContext: VIEW_CONTEXT_MAIN_DASHBOARD, + } + ); + + expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); + + fireEvent.mouseOver( + container.querySelector( + '.googlesitekit-audience-segmentation-tile-metric--top-content .googlesitekit-info-tooltip' + ) + ); + + // Wait for the tooltip to appear, its delay is 100ms. + await act( () => waitForTimeouts( 100 ) ); + + expect( mockTrackEvent ).toHaveBeenCalledWith( + 'mainDashboard_audiences-tile', + 'view_top_content_partial_data_tooltip', + 'new-visitors' + ); + } ); + it( 'should not display partial data badge for tile or top content metrics when property is in partial state', () => { registry .dispatch( MODULES_ANALYTICS_4 ) From f215a8c4997374083fb3cc60580ecc2f4aa2b580 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 18:23:39 +0000 Subject: [PATCH 28/31] Remove commented out lines. --- .../dashboard/AudienceTilesWidget/index.test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js index bb329bc16cf..b9b05824233 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js @@ -362,9 +362,6 @@ describe( 'AudienceTilesWidget', () => { await waitFor( () => { expect( container ).toMatchSnapshot(); } ); - - // expect( mockTrackEvent ).toHaveBeenCalledTimes( 0 ); - // expect( mockTrackEvent ).toHaveBeenCalledWith( {} ); } ); it( 'should render when all configured audiences are matching available audiences', async () => { From 3e24cb6d33918905320c1b777b5fe4f53a8460e1 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 19:17:16 +0000 Subject: [PATCH 29/31] Improve test stability. --- .../dashboard/AudienceTilesWidget/index.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js index b9b05824233..679878f5bac 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js @@ -467,7 +467,9 @@ describe( 'AudienceTilesWidget', () => { 'properties/12345/audiences/3': dataAvailabilityDate, 'properties/12345/audiences/4': dataAvailabilityDate, }, - customDimension: {}, + customDimension: { + googlesitekit_post_type: 20201220, + }, property: { 12345: 20201218, }, From 42f62707a3aba8fa459f113bdd2364e798504dd7 Mon Sep 17 00:00:00 2001 From: Tom Rees-Herdman Date: Mon, 28 Oct 2024 19:39:25 +0000 Subject: [PATCH 30/31] Improve test stability. --- .../AudienceTilesWidget/index.test.js | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js index 679878f5bac..2a1097b48f5 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/index.test.js @@ -289,7 +289,7 @@ describe( 'AudienceTilesWidget', () => { googlesitekit_post_type: 20201220, }, property: { - 12345: 20201218, + 12345: 20201220, }, } ); @@ -462,18 +462,19 @@ describe( 'AudienceTilesWidget', () => { registry .dispatch( MODULES_ANALYTICS_4 ) - .receiveResourceDataAvailabilityDates( { - audience: { - 'properties/12345/audiences/3': dataAvailabilityDate, - 'properties/12345/audiences/4': dataAvailabilityDate, - }, - customDimension: { - googlesitekit_post_type: 20201220, - }, - property: { - 12345: 20201218, - }, - } ); + .setResourceDataAvailabilityDate( + 'properties/12345/audiences/3', + 'audience', + dataAvailabilityDate + ); + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setResourceDataAvailabilityDate( + 'properties/12345/audiences/4', + 'audience', + dataAvailabilityDate + ); registry .dispatch( MODULES_ANALYTICS_4 ) @@ -542,7 +543,7 @@ describe( 'AudienceTilesWidget', () => { ) ).toBeInTheDocument(); - await act( () => waitForTimeouts( 100 ) ); + await act( () => waitForTimeouts( 150 ) ); } ); it( 'should track an event when the tooltip for an audience tab is viewed', async () => { From 3013ac378139ea1279dcf4bedeb561b68d26bb0f Mon Sep 17 00:00:00 2001 From: nfmohit Date: Thu, 7 Nov 2024 12:40:18 +0600 Subject: [PATCH 31/31] Address minor CR feedback. --- assets/js/googlesitekit/components-gm2/Tooltip.js | 2 ++ .../dashboard/AudienceSegmentationErrorWidget/index.js | 3 +++ .../dashboard/AudienceSegmentationErrorWidget/index.test.js | 2 ++ 3 files changed, 7 insertions(+) diff --git a/assets/js/googlesitekit/components-gm2/Tooltip.js b/assets/js/googlesitekit/components-gm2/Tooltip.js index 3b5be1fc46b..c6d510ea68c 100644 --- a/assets/js/googlesitekit/components-gm2/Tooltip.js +++ b/assets/js/googlesitekit/components-gm2/Tooltip.js @@ -83,4 +83,6 @@ Tooltip.propTypes = { children: PropTypes.node, popperClassName: PropTypes.string, tooltipClassName: PropTypes.string, + onOpen: PropTypes.func, + onClose: PropTypes.func, }; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js index 00b9371e474..9fa064aea3b 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.js @@ -27,6 +27,9 @@ import PropTypes from 'prop-types'; */ import { useEffect } from '@wordpress/element'; +/** + * Internal dependencies + */ import whenActive from '../../../../../../util/when-active'; import { useDispatch } from 'googlesitekit-data'; import { isInsufficientPermissionsError } from '../../../../../../util/errors'; diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js index 3b2f347d9e9..f1299a25b19 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationErrorWidget/index.test.js @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /** * External dependencies */ @@ -238,6 +239,7 @@ describe( 'AudienceSegmentationErrorWidget', () => { await waitForRegistry(); } ); + it( 'should render correctly', () => { expect( container ).toMatchSnapshot();