Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ModuleRecoveryAlert to new notification system. #10398

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from

Conversation

benbowler
Copy link
Collaborator

@benbowler benbowler commented Mar 10, 2025

Summary

Addresses issue:

Relevant technical choices

Notes:

  • I noticed that the SimpleNotification was scaling strangely and fixed here I believe this is correct for all future and current uses of this notification type because it does not have a support for a right hand column for a graphic.
  • I did keep a null render while checkboxes is being setup as this was simpler than adding inline terneries for all uses of the checkboxes list.
  • I didn't include loading logic in checkRequirements as this already awaits resolution of the getRecoverableModules selector.
  • I ignored sitekit/no-storybook-scenario-label warnings as these will be refactored in Tidy up and remove redundant use of label property in VRT scenarios #9103.
  • E2E and Compressed Size checks are failing due to merge of assets refactor work.

PR Author Checklist

  • My code is tested and passes existing unit tests.
  • My code has an appropriate set of unit tests which all pass.
  • My code is backward-compatible with WordPress 5.2 and PHP 7.4.
  • My code follows the WordPress coding standards.
  • My code has proper inline documentation.
  • I have added a QA Brief on the issue linked above.
  • I have signed the Contributor License Agreement (see https://cla.developers.google.com/).

Do not alter or remove anything below. The following sections will be managed by moderators only.

Code Reviewer Checklist

  • Run the code.
  • Ensure the acceptance criteria are satisfied.
  • Reassess the implementation with the IB.
  • Ensure no unrelated changes are included.
  • Ensure CI checks pass.
  • Check Storybook where applicable.
  • Ensure there is a QA Brief.
  • Ensure there are no unexpected significant changes to file sizes.

Merge Reviewer Checklist

  • Ensure the PR has the correct target branch.
  • Double-check that the PR is okay to be merged.
  • Ensure the corresponding issue has a ZenHub release assigned.
  • Add a changelog message to the issue.

Copy link

github-actions bot commented Mar 10, 2025

Build files for 6ee8815 are ready:

Copy link
Collaborator

@aaemnnosttv aaemnnosttv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @benbowler, this is off to a good start and seems to be working well too! I've left a few points to address throughout but let's try to simplify the component a bit more in the process here. LMK if you have any questions 👍

Comment on lines 573 to 575
'module-recovery-alert': {
Component: ModuleRecoveryAlert,
priority: 320,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All "alerts" such as this should be a higher priority (closer to 0) than informational notifications. Currently the zero data notification shows before this, which is a lower priority. The order should be preserved as it is today as in BannerNotifications (2nd only to AdSenseAlerts).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated it relative to the other notification of this type, I could take it closer to 0 if needed. I also left a note on the AdSenseAlerts to ensure this becomes higher priority and other refactors in this notification area are lower priority.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think something like 145 would be more appropriate (less important than setup errors, but more important than scope alerts).


export default function ModuleRecoveryAlert() {
export default function ModuleRecoveryAlert( { id, Notification } ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most components are simplified during the transition to the new API, however this one has grown substantially, at least in terms of total lines. Perhaps this is mostly due to my previous comment about using Description components instead of defining inline here, but even beyond that, it seems like there is room for some additional refactoring to simplify things further.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've much reduced the size, although it is larger as the props which were once on the BannerNotification component are now duplicated on CTA/Dismiss components.

I've also added additional functionality to disable the Recover button when no modules are selected.

Comment on lines +587 to +588
recoverableModules === undefined ||
recoverableModulesList.length === 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's possible for the resolved recoverableModules to be anything other than an array, but also getRecoverableModules returns an array of slugs, not an object (like getModules).

Suggested change
recoverableModules === undefined ||
recoverableModulesList.length === 0
! recoverableModules.length

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part appears to be unchanged, did you mean to push something?

isSaving={ recoveringModules }
/>
<CTALinkSubtle
id={ id }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we aren't using the standard <Dismiss> component in all of these cases? Then we don't need to pass onCTAClick to dismissNotification.

Actually, we probably can use <ActionsCTALinkDismiss> as they provide a combination of CTALink and Dismiss.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored to use this component. One thing to note is that, because we can't vary the dismissExpires independently between the CTA and Dismiss CTA the the notification won't show again if the error recurs within one day.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using the <ActionsCTALink> component might fix these gap changes between the primary and secondary CTAs in all of these VRTs.

…ismiss logic and disable Recover button when modules are uncheked.
description={ description }
actions={ actions }
/>
</Notification>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored the component significantly, it's longer in terms of lines because props in components such as ActionsCTALinkDismiss and Dismiss those that were passed to the BannerNotification component. The implementation here is clear and now reuses key shared notification components.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @benbowler, I spent a bit looking into what a more substantial refactor might look like for this component as it's quite complex but I think it's probably best saved for its own issue.

I'd love to see a few components and/or hooks extracted here for the various description and actions cases above. We should avoid having large nested if statements as it's quite hard to read and understand. Similarly, the learn more link is the same in all cases where it's present.

Feel free to address any low-hanging fruit you see if time allows, but otherwise, let's open a follow up issue to (probably rewrite) this component.

@@ -108,7 +107,6 @@ export default function BannerNotifications() {
return (
<Fragment>
{ adSenseModuleActive && <AdSenseAlerts /> }
<ModuleRecoveryAlert />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimmymadon shouldn't we be migrating these from the bottom up?

Copy link
Collaborator

@aaemnnosttv aaemnnosttv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @benbowler – I think this is close to ready although I noticed that this is diverging a bit from our approach to the refactoring in terms of the order of components that we're touching. I'll let @jimmymadon confirm, but this may need to be parked for a bit while we address the others between this one and the <Notifications> below it.

dismissLabel={ __( 'Remind me later', 'google-site-kit' ) }
dismissOnCTAClick={ shouldDismissOnCTAClick }
dismissExpires={ DAY_IN_SECONDS }
ctaDismissOptions={ { skipHidingFromQueue: false } }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skipHidingFromQueue: false shouldn't be needed anywhere, is it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this currently because of CTALink which sets the default value to true:

dismissOptions = { skipHidingFromQueue: true },

.. and ActionsCTALinkDismiss uses CTALink. We can remove this default value and update any uses of this component that require skip hiding to pass true explicitly. I think this should be a follow up issue though as it requires and audit of uses of the component and testing to confirm each of these notifications behaviour is retained.

description={ description }
actions={ actions }
/>
</Notification>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @benbowler, I spent a bit looking into what a more substantial refactor might look like for this component as it's quite complex but I think it's probably best saved for its own issue.

I'd love to see a few components and/or hooks extracted here for the various description and actions cases above. We should avoid having large nested if statements as it's quite hard to read and understand. Similarly, the learn more link is the same in all cases where it's present.

Feel free to address any low-hanging fruit you see if time allows, but otherwise, let's open a follow up issue to (probably rewrite) this component.

@benbowler
Copy link
Collaborator Author

@aaemnnosttv I've made a significant refactor and simplification of this component to remove nested if/else and then took it further to split out the actions and description into their own components, let me know what you think.

One effect of removing the loading bar is if you have user recoverable modules the copy and checkboxes change once the module access resolvers resolve:

Screen.Recording.2025-03-12.at.10.59.20.mov

Do you think this is appropriate or should we await resolution of the module access checks before rendering the entire component or parts of it? Or add back a loading bar, what do you think?

Copy link

github-actions bot commented Mar 12, 2025

Size Change: +419 B (+0.02%)

Total Size: 2.07 MB

Filename Size Change
./dist/assets/js/googlesitekit-main-dashboard-********************.js 162 kB -1.25 kB (-0.77%)
./dist/assets/js/googlesitekit-notifications-********************.js 46.2 kB +1.41 kB (+3.16%)
ℹ️ View Unchanged
Filename Size Change
./dist/assets/css/googlesitekit-admin-css-********************.min.css 64 kB 0 B
./dist/assets/css/googlesitekit-adminbar-css-********************.min.css 11.8 kB 0 B
./dist/assets/css/googlesitekit-authorize-application-css-********************.min.css 846 B 0 B
./dist/assets/css/googlesitekit-wp-dashboard-css-********************.min.css 8.48 kB 0 B
./dist/assets/js/33-********************.js 2.76 kB 0 B
./dist/assets/js/34-********************.js 2.25 kB 0 B
./dist/assets/js/35-********************.js 3.64 kB 0 B
./dist/assets/js/36-********************.js 936 B 0 B
./dist/assets/js/37-********************.js 892 B 0 B
./dist/assets/js/38-********************.js 1.61 kB 0 B
./dist/assets/js/39-********************.js 1.57 kB 0 B
./dist/assets/js/40-********************.js 1.61 kB 0 B
./dist/assets/js/41-********************.js 1.59 kB 0 B
./dist/assets/js/42-********************.js 1.83 kB 0 B
./dist/assets/js/43-********************.js 3.12 kB +1 B (+0.03%)
./dist/assets/js/analytics-advanced-tracking-********************.js 901 B 0 B
./dist/assets/js/blocks/reader-revenue-manager/block-editor-plugin/editor-styles.css 124 B 0 B
./dist/assets/js/blocks/reader-revenue-manager/block-editor-plugin/editor-styles.js 492 B 0 B
./dist/assets/js/blocks/reader-revenue-manager/block-editor-plugin/index.js 7.95 kB 0 B
./dist/assets/js/blocks/reader-revenue-manager/common/editor-styles.css 307 B 0 B
./dist/assets/js/blocks/reader-revenue-manager/common/editor-styles.js 492 B 0 B
./dist/assets/js/blocks/reader-revenue-manager/contribute-with-google/index.js 9.1 kB 0 B
./dist/assets/js/blocks/reader-revenue-manager/subscribe-with-google/index.js 9.11 kB 0 B
./dist/assets/js/blocks/sign-in-with-google/editor-styles.css 84 B 0 B
./dist/assets/js/blocks/sign-in-with-google/editor-styles.js 492 B 0 B
./dist/assets/js/blocks/sign-in-with-google/index.js 18.1 kB 0 B
./dist/assets/js/googlesitekit-activation-********************.js 23.8 kB -4 B (-0.02%)
./dist/assets/js/googlesitekit-ad-blocking-recovery-********************.js 54.5 kB +18 B (+0.03%)
./dist/assets/js/googlesitekit-adminbar-********************.js 34.7 kB -2 B (-0.01%)
./dist/assets/js/googlesitekit-api-********************.js 10.1 kB -1 B (-0.01%)
./dist/assets/js/googlesitekit-components-gm2-********************.js 6.46 kB -1 B (-0.02%)
./dist/assets/js/googlesitekit-components-gm3-********************.js 10.1 kB +1 B (+0.01%)
./dist/assets/js/googlesitekit-consent-mode-********************.js 25.6 kB 0 B
./dist/assets/js/googlesitekit-data-********************.js 2.37 kB +1 B (+0.04%)
./dist/assets/js/googlesitekit-datastore-forms-********************.js 8.95 kB -1 B (-0.01%)
./dist/assets/js/googlesitekit-datastore-location-********************.js 2.08 kB -1 B (-0.05%)
./dist/assets/js/googlesitekit-datastore-site-********************.js 20.3 kB +1 B (0%)
./dist/assets/js/googlesitekit-datastore-ui-********************.js 10 kB -5 B (-0.05%)
./dist/assets/js/googlesitekit-datastore-user-********************.js 28.3 kB -20 B (-0.07%)
./dist/assets/js/googlesitekit-entity-dashboard-********************.js 85.9 kB +29 B (+0.03%)
./dist/assets/js/googlesitekit-events-provider-contact-form-7-********************.js 646 B 0 B
./dist/assets/js/googlesitekit-events-provider-easy-digital-downloads-********************.js 624 B 0 B
./dist/assets/js/googlesitekit-events-provider-mailchimp-********************.js 630 B 0 B
./dist/assets/js/googlesitekit-events-provider-ninja-forms-********************.js 712 B 0 B
./dist/assets/js/googlesitekit-events-provider-optin-monster-********************.js 675 B 0 B
./dist/assets/js/googlesitekit-events-provider-popup-maker-********************.js 634 B 0 B
./dist/assets/js/googlesitekit-events-provider-woocommerce-********************.js 657 B 0 B
./dist/assets/js/googlesitekit-events-provider-wpforms-********************.js 633 B 0 B
./dist/assets/js/googlesitekit-i18n-********************.js 3.93 kB 0 B
./dist/assets/js/googlesitekit-metric-selection-********************.js 52.2 kB -9 B (-0.02%)
./dist/assets/js/googlesitekit-modules-********************.js 22.4 kB +8 B (+0.04%)
./dist/assets/js/googlesitekit-modules-ads-********************.js 49.6 kB +48 B (+0.1%)
./dist/assets/js/googlesitekit-modules-adsense-********************.js 116 kB +17 B (+0.01%)
./dist/assets/js/googlesitekit-modules-analytics-4-********************.js 194 kB +35 B (+0.02%)
./dist/assets/js/googlesitekit-modules-pagespeed-insights-********************.js 22.9 kB -2 B (-0.01%)
./dist/assets/js/googlesitekit-modules-reader-revenue-manager-********************.js 46.9 kB +42 B (+0.09%)
./dist/assets/js/googlesitekit-modules-search-console-********************.js 69.4 kB +6 B (+0.01%)
./dist/assets/js/googlesitekit-modules-sign-in-with-google-********************.js 32.8 kB +30 B (+0.09%)
./dist/assets/js/googlesitekit-modules-tagmanager-********************.js 32.2 kB +6 B (+0.02%)
./dist/assets/js/googlesitekit-polyfills-********************.js 378 B +1 B (+0.27%)
./dist/assets/js/googlesitekit-settings-********************.js 129 kB +46 B (+0.04%)
./dist/assets/js/googlesitekit-splash-********************.js 69.1 kB -11 B (-0.02%)
./dist/assets/js/googlesitekit-user-input-********************.js 43.9 kB -6 B (-0.01%)
./dist/assets/js/googlesitekit-vendor-********************.js 324 kB +1 B (0%)
./dist/assets/js/googlesitekit-widgets-********************.js 105 kB +13 B (+0.01%)
./dist/assets/js/googlesitekit-wp-dashboard-********************.js 63.2 kB +14 B (+0.02%)
./dist/assets/js/runtime-********************.js 1.4 kB +4 B (+0.29%)

compressed-size-action

Copy link
Collaborator

@aaemnnosttv aaemnnosttv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @benbowler – this is is on the right track. I don't want to burn time here but I've left a few more points to address. LMK if you have any questions 👍

Comment on lines +67 to +73
const hasMultipleRecoverableModules = useMemo(
() => Object.keys( recoverableModules || {} ).length > 1,
[ recoverableModules ]
);
const hasUserRecoverableModules = useMemo(
() => !! Object.keys( userAccessibleModules || {} ).length,
[ userAccessibleModules ]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • +1 to "user recoverable modules" as this is what I would have used as well. Please also update userAccessibleModules above as well since name is more accurate and applies there as well.
  • I'm not sure useMemo is providing a benefit here when used with [ userAccessibleModules ] as the dependencies since it returns a new array every time (so it's likely never returning the memoized value). I think it should be okay to skip using this hook here anyways since the computation is probably less expensive than the overhead of the hook.

<Description
id={ id }
recoverableModules={ recoverableModules }
userAccessibleModules={ userAccessibleModules }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also use the same naming here as well

Suggested change
userAccessibleModules={ userAccessibleModules }
userRecoverableModules={ userRecoverableModules }

<Actions
id={ id }
recoverableModules={ recoverableModules }
userAccessibleModules={ userAccessibleModules }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too

Comment on lines +587 to +588
recoverableModules === undefined ||
recoverableModulesList.length === 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part appears to be unchanged, did you mean to push something?

Comment on lines +74 to +76
recoverableModules[
Object.keys( recoverableModules || {} )[ 0 ]
]?.name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's only one, we can grab the first without using the key.

Suggested change
recoverableModules[
Object.keys( recoverableModules || {} )[ 0 ]
]?.name
Object.values( recoverableModules )[ 0 ].name

Comment on lines +120 to +135
// Disable the CTA if no modules are selected to be restored.
const disableCTA = useMemo(
() =>
checkboxes !== null &&
! Object.values( checkboxes ).some( ( checked ) => checked ),
[ checkboxes ]
);

// Only allow the alert to be dismissed if all recoverable modules are selected.
const shouldDismissOnCTAClick = useMemo(
() =>
Object.keys( userAccessibleModules || {} ).length ===
Object.values( checkboxes || {} ).filter( ( checked ) => checked )
.length,
[ checkboxes, userAccessibleModules ]
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, useMemo seems unnecessary for booleans that don't appear expensive to compute.

Comment on lines +199 to +203
{ Object.keys( recoverableModules ).map(
( slug ) => (
<li className="mdc-list-item" key={ slug }>
<span className="mdc-list-item__text">
{ recoverableModules[ slug ].name }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're iterating over keys only but need the value too. How about using Object.entries?

Comment on lines +139 to +168
{ hasUserRecoverableModules && (
<Fragment>
{ hasMultipleRecoverableModules && (
<Fragment>
{ checkboxes !== null &&
userAccessibleModules.map( ( slug ) => (
<div key={ slug }>
<Checkbox
checked={ checkboxes[ slug ] }
name="module-recovery-alert-checkbox"
id={ `module-recovery-alert-checkbox-${ slug }` }
onChange={ () =>
updateCheckboxes( slug )
}
value={ slug }
disabled={ recoveringModules }
>
{ recoverableModules[ slug ].name }
</Checkbox>
</div>
) ) }
<p className="googlesitekit-publisher-win__desc">
{ __(
'By recovering the selected modules, you will restore access for other users by sharing access via your Google account. This does not make any changes to external services and can be managed at any time via the dashboard sharing settings.',
'google-site-kit'
) }
</p>
</Fragment>
) }
{ ! hasMultipleRecoverableModules && (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about splitting out the output into separate components for has/not-has recoverable modules?

hasMultipleRecoverableModules,
} ) {
const [ checkboxes, setCheckboxes ] = useState( null );
const [ recoveringModules, setRecoveringModules ] = useState( false );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about inProgress?

'dashboard-sharing'
);
} );

const userAccessibleModules = useSelect( ( select ) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is from before, but I noticed it in some of the new components. There is a mismatch of types which can easily happen looking at the names:

  • recoverableModules ({ [key: string]: Object }) – subset of getModules
  • userAccessibleModules (string[]) – list of module slugs: subset of recoverableModules that the user has access to

Both are *Modules but different types which is confusing. For a list of slugs a name like *ModuleSlugs would be much more clear 👍 Even once we start integrating TypeScript, we should still use the same level of care when naming things.

TL;DR If we have a use for user recoverable module objects, then we can remove the slug mapping at the end and move that outside to a new variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants