From 56f49f0ede2b49bc0b8f87946a1f73e9988b3e4b Mon Sep 17 00:00:00 2001 From: Ben Bowler Date: Mon, 20 May 2024 15:33:08 +0100 Subject: [PATCH 1/2] Add reusable Widget area, New badge feature. --- .../widgets/components/WidgetAreaRenderer.js | 54 ++++++++++++++++++- .../googlesitekit/widgets/datastore/areas.js | 4 ++ .../widgets/register-defaults.js | 2 + 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.js b/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.js index efd0d5b7b4f..55e847be828 100644 --- a/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.js +++ b/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.js @@ -49,6 +49,9 @@ import WidgetCellWrapper from './WidgetCellWrapper'; import useViewOnly from '../../../hooks/useViewOnly'; import { CORE_USER } from '../../datastore/user/constants'; import useLatestIntersection from '../../../hooks/useLatestIntersection'; +import NewBadge from '../../../components/NewBadge'; +import { WEEK_IN_SECONDS } from '../../../util'; +import { useDispatch } from '@wordpress/data'; const { useSelect } = Data; /** @@ -95,6 +98,10 @@ export default function WidgetAreaRenderer( { slug, contextID } ) { const widgetArea = useSelect( ( select ) => select( CORE_WIDGETS ).getWidgetArea( slug ) ); + + const { Icon, title, style, subtitle, hasNewBadge, CTA, Footer } = + widgetArea; + const widgets = useSelect( ( select ) => select( CORE_WIDGETS ).getWidgets( slug, { modules: viewableModules ? viewableModules : undefined, @@ -129,6 +136,47 @@ export default function WidgetAreaRenderer( { slug, contextID } ) { } ); }, [ intersectionEntry, slug, activeContextID, contextID ] ); + // NewBadge Expirable Item + const expirableBadgeSlug = `widget-area-expirable-new-badge-${ slug }`; + + const hasBadgeBeenSeen = useSelect( ( select ) => + select( CORE_USER ).hasExpirableItem( expirableBadgeSlug ) + ); + const isExpiredBadgeActive = useSelect( ( select ) => + select( CORE_USER ).isExpirableItemActive( expirableBadgeSlug ) + ); + + // Show the new badge if this widget area allows new badges, it's new badge + // has not been seen yet, or the badge has been seen and is still active. + const showNewBadge = + hasNewBadge && ( hasBadgeBeenSeen === false || isExpiredBadgeActive ); + + const { setExpirableItemTimers } = useDispatch( CORE_USER ); + + useEffect( () => { + // Wait until the selectors have resolved. + if ( + hasBadgeBeenSeen !== undefined && + isExpiredBadgeActive !== undefined + ) { + // Only set the expirable item if the badge is new and the user is viewing it for the first time. + if ( hasNewBadge && ! hasBadgeBeenSeen ) { + setExpirableItemTimers( [ + { + slug: expirableBadgeSlug, + expiresInSeconds: WEEK_IN_SECONDS * 4, + }, + ] ); + } + } + }, [ + hasNewBadge, + expirableBadgeSlug, + hasBadgeBeenSeen, + isExpiredBadgeActive, + setExpirableItemTimers, + ] ); + if ( viewableModules === undefined ) { return null; } @@ -175,8 +223,6 @@ export default function WidgetAreaRenderer( { slug, contextID } ) { ) ); - const { Icon, title, style, subtitle, CTA, Footer } = widgetArea; - return ( { !! isActive && ( @@ -198,6 +244,7 @@ export default function WidgetAreaRenderer( { slug, contextID } ) { { title && (

{ title } + { showNewBadge && }

) } @@ -206,6 +253,9 @@ export default function WidgetAreaRenderer( { slug, contextID } ) { { subtitle && (

{ subtitle } + { showNewBadge && ! title && ( + + ) }

) } diff --git a/assets/js/googlesitekit/widgets/datastore/areas.js b/assets/js/googlesitekit/widgets/datastore/areas.js index 194dd57194b..fe39e24ae1c 100644 --- a/assets/js/googlesitekit/widgets/datastore/areas.js +++ b/assets/js/googlesitekit/widgets/datastore/areas.js @@ -83,6 +83,7 @@ export const actions = { * @since 1.107.0 Extended to support an optional CTA component. * @since 1.110.0 Extended to support an optional filterActiveWidgets function. * @since n.e.x.t Extended to make title optional and support an optional Footer component. + * @since n.e.x.t Extended to support an optional hasNewBadge parameter. * * @param {string} slug Widget Area's slug. * @param {Object} settings Widget Area's settings. @@ -91,6 +92,7 @@ export const actions = { * @param {WPComponent} [settings.Icon] Optional. React component to render icon for this widget area. * @param {string} [settings.style] Optional. Widget area style (one of "boxes", "composite"). Default: "boxes". * @param {number} [settings.priority] Optional. Priority for this widget area. Default: 10. + * @param {boolean} [settings.hasNewBadge] Optional. Whether this widget area should display a new badge. * @param {WPComponent} [settings.CTA] Optional. React component used as CTA appearing beside the subtitle. * @param {WPComponent} [settings.Footer] Optional. React component used as footer for the widget area. * @param {Function} [settings.filterActiveWidgets] Optional. Function used to filter active widgets. @@ -104,6 +106,7 @@ export const actions = { title, subtitle, Icon, + hasNewBadge = false, CTA, Footer, filterActiveWidgets, @@ -124,6 +127,7 @@ export const actions = { title, subtitle, Icon, + hasNewBadge, CTA, Footer, filterActiveWidgets, diff --git a/assets/js/googlesitekit/widgets/register-defaults.js b/assets/js/googlesitekit/widgets/register-defaults.js index 980dacdaee6..abf744d4b49 100644 --- a/assets/js/googlesitekit/widgets/register-defaults.js +++ b/assets/js/googlesitekit/widgets/register-defaults.js @@ -135,6 +135,7 @@ export function registerDefaults( widgetsAPI ) { 'Track your site’s traffic over time', 'google-site-kit' ), + hasNewBadge: true, // TODO: remove this as this is only for testing the new badge with a title. style: WIDGET_AREA_STYLES.BOXES, priority: 1, }, @@ -149,6 +150,7 @@ export function registerDefaults( widgetsAPI ) { 'Understand how different visitor groups interact with your site', 'google-site-kit' ), + hasNewBadge: true, style: WIDGET_AREA_STYLES.BOXES, priority: 2, Footer: AudienceAreaFooter, From e99a2ae8690434d37bcf53a352338bd0d7eb8367 Mon Sep 17 00:00:00 2001 From: Ben Bowler Date: Tue, 28 May 2024 11:09:49 +0100 Subject: [PATCH 2/2] Update widget area tests to mute the expirable items endpoint. --- .../widgets/components/WidgetAreaRenderer.test.js | 6 ++++++ .../widgets/components/WidgetContextRenderer.test.js | 6 ++++++ .../js/googlesitekit/widgets/datastore/areas.test.js | 10 +++++++--- assets/js/googlesitekit/widgets/register-defaults.js | 1 - 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.test.js b/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.test.js index dfb457a2434..d2595629ec6 100644 --- a/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.test.js +++ b/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.test.js @@ -38,6 +38,7 @@ import { provideModules, provideUserCapabilities, unsubscribeFromAll, + muteFetch, } from '../../../../../tests/js/test-utils'; import { VIEW_CONTEXT_MAIN_DASHBOARD, @@ -100,6 +101,11 @@ describe( 'WidgetAreaRenderer', () => { const connection = { connected: true }; await registry.dispatch( CORE_SITE ).receiveGetConnection( connection ); + + const fetchGetExpiredItems = new RegExp( + '^/google-site-kit/v1/core/user/data/expirable-items' + ); + muteFetch( fetchGetExpiredItems ); } ); afterEach( () => { diff --git a/assets/js/googlesitekit/widgets/components/WidgetContextRenderer.test.js b/assets/js/googlesitekit/widgets/components/WidgetContextRenderer.test.js index 5381e3d5429..0ba1cce902e 100644 --- a/assets/js/googlesitekit/widgets/components/WidgetContextRenderer.test.js +++ b/assets/js/googlesitekit/widgets/components/WidgetContextRenderer.test.js @@ -23,6 +23,7 @@ import WidgetContextRenderer from './WidgetContextRenderer'; import { CORE_WIDGETS } from '../datastore/constants'; import { createTestRegistry, + muteFetch, provideModules, render, waitFor, @@ -54,6 +55,11 @@ describe( 'WidgetContextRenderer', () => { registry .dispatch( CORE_WIDGETS ) .assignWidgetArea( 'TestArea1', 'TestContext' ); + + const fetchGetExpiredItems = new RegExp( + '^/google-site-kit/v1/core/user/data/expirable-items' + ); + muteFetch( fetchGetExpiredItems ); } ); it( 'should render the registered widget areas', async () => { diff --git a/assets/js/googlesitekit/widgets/datastore/areas.test.js b/assets/js/googlesitekit/widgets/datastore/areas.test.js index cf7575e2b5d..ff697477f7d 100644 --- a/assets/js/googlesitekit/widgets/datastore/areas.test.js +++ b/assets/js/googlesitekit/widgets/datastore/areas.test.js @@ -492,12 +492,16 @@ describe( 'core/widgets Widget areas', () => { expect( registry.select( CORE_WIDGETS ).getWidgetArea( 'TestArea' ) ).toEqual( { - Icon: undefined, - priority: 10, + slug: 'TestArea', title: 'Test Header', subtitle: 'Cool stuff for yoursite.com', + Icon: undefined, style: 'composite', - slug: 'TestArea', + priority: 10, + hasNewBadge: false, + CTA: undefined, + Footer: undefined, + filterActiveWidgets: undefined, } ); } ); diff --git a/assets/js/googlesitekit/widgets/register-defaults.js b/assets/js/googlesitekit/widgets/register-defaults.js index abf744d4b49..1247dcdc862 100644 --- a/assets/js/googlesitekit/widgets/register-defaults.js +++ b/assets/js/googlesitekit/widgets/register-defaults.js @@ -135,7 +135,6 @@ export function registerDefaults( widgetsAPI ) { 'Track your site’s traffic over time', 'google-site-kit' ), - hasNewBadge: true, // TODO: remove this as this is only for testing the new badge with a title. style: WIDGET_AREA_STYLES.BOXES, priority: 1, },