Decouple module-specific effects from core components
Feature Description
As a general principal, module-specific code can depend on core infrastructure, but not the other way around. Core infra being core data stores as one of the most explicit examples, but also things like very top-level components like DashboardMainApp.
The SK dashboards have a well-defined API for adding elements to the dashboard but the concept of widgets is directly associated with layout. Sometimes we want to invoke various module-specific side-effects which in the past have either been shoe-horned into an existing component, or added into a higher level component where they don't really belong.
There have been a few instances recently where we've wanted to use such functionality where it hasn't been available such as synchronizing module settings and creating custom dimensions to name a few.
One solution to this is to use the Widgets API – even today this can be used to accomplish the end goal.
Example
widgets.registerWidget(
'analyticsDashboardEffects',
{
Component( { WidgetNull } ) {
useEffect( () => {
console.log( 'analyticsDashboardEffects:effect-run' );
return () => console.log( 'analyticsDashboardEffects:effect-cleanup' );
}, [] );
return <WidgetNull />;
},
width: widgets.WIDGET_WIDTHS.FULL, // Required but irrelevant.
priority: 0,
modules: [ 'analytics-4' ],
},
[
AREA_MAIN_DASHBOARD_TRAFFIC_PRIMARY,
]
);
This registers a widget on the main dashboard for the sole purpose of running effects. This will work, and by using WidgetNull unconditionally, the layout will never be affected by the "empty" widget. This could be combined with the whenActive() HOC for guarding it to only run when a module is active or connected. It isn't entirely ideal as-is here as there are things like width that aren't relevant, nor should we need to render WidgetNull to avoid throwing a wrench in the layout when it isn't concerned with layout at all.
One idea is to register a new widget area that would always be hidden which could be used for this kind of thing, and maybe we build a bit of a layer on top to avoid needing to define properties that aren't relevant for this kind of widget/area. Most of the parts of the Widget API are still relevant though like declaring modules for use with Dashboard Sharing, so it doesn't seem that a new API of sorts would be warranted.
Do not alter or remove anything below. The following sections will be managed by moderators only.
Acceptance criteria
- All use of module-specific actions, selectors, and components should be removed from
Dashboard*AppandDashboardEntryPointcomponents and moved to module-registered locations- These should only run when their respective modules are connected in most cases (if not, please note these exceptions) and guarded accordingly to prevent invoking
Note: the example in the description is not a requirement but could be used as an part of an initial implementation. It could be worth exploring what a proper API for this would look like, although there would likely be substantial overlap with features already provided by the widgets API.
Implementation Brief
- [ ] In
assets/js/googlesitekit/modules/index.js- Update the documentation header for the
registerModuleto list two new optional components:MainRootComponentandEntityRootComponent
- Update the documentation header for the
- [ ] Update
assets/js/googlesitekit/modules/datastore/modules.js- Include two more props in the
registerModuleaction -MainRootComponentandEntityRootComponentand add them to thesettingsvariable
- Include two more props in the
- [ ] Add
assets/js/components/ModuleRootComponents.js- Include one prop -
dashboardType, it should be string enum, holding eithermainorentityvalue - Use
getModulesselector fromCORE_MODULESstore to fetch all modules - Iterate over returned
modulesand filter for the ones that have propertyconnectedand depending on thedashboardTypeprop, if it ismainthen filter in for the modules that haveMainRootComponentproperty, otherwise filter forEntityRootComponentand save the results in a variable, sayfilteredModulesfor example - If
filteredModulesis empty return early - Otherwise, iterate over the
filteredModulesand render theirMainRootComponentorEntityRootComponentdepending on the value ofdashboardTypeprop
- Include one prop -
- [ ] Update
assets/js/components/DashboardEntryPoint.js- Render
ModuleRootComponentstogether with eitherModuleSetuporDashboardMainAppdepending on the current value ofsetupModuleSlug
- Render
- [ ] Update
assets/js/components/DashboardEntityApp.js- Render
ModuleRootComponentscomponent afterScrollEffectcomponent. Pass thedashboardTypeprop with value ofentity.
- Render
- [ ] Extract:
- Custom dimensions logic from
DashboardMainAppandsyncGoogleTagSettingscall with it'suseMounthook fromDashboardEntryPointinto a separate componentassets/js/modules/analytics-4/components/MainRootComponent.js, which only contain hooks, and return null in the renderer.
- Custom dimensions logic from
- [ ] Pass
MainRootComponentcomponent inassets/js/modules/analytics-4/index.jsto theregisterModulefunction asMainRootComponentproperty.
Test Coverage
- Add tests to the
assets/js/googlesitekit/modules/datastore/modules.test.jsto verify new components. You can useaccepts settings components for the moduleanddefaults settings components tonullif not providedas starting point
QA Brief
- Do overall smoke test, also around creating custom dimensions
- Verify that everything works as expected
Changelog entry
One more report of this error in the support forums. Awaiting user feedback.
One more report of this to add here.
User has shared their SH Info and confirmed that they don't encounter the same from an Incognito window. I will continue to troubleshoot this with the user and add any more updates here if I get them.
Thanks, @zutigrm, but please do not use the PR as IB approach because it is strongly discouraged. If you can't write an IB without coding it beforehand, then just leave it for someone else. By spending time on your PoC, you spend time on implementation in the current sprint for a thing that is supposed to be implemented in a following sprint, thus our planning for that sprint will less accurate. Plus it is not always the case that PoC will be needed, thus time spent on it will be wasted.
Add
assets/js/googlesitekit/widgets/datastore/effects.js
- Add
registerModuleEffectaction, which should be a minimal adaptation fromregisterWidgetaction, only handlingslugandeffect- callback hook that will be passed
This is a nice start, but I don't think it should be done this way. The problem here is that you try to squeeze module related functionality into the widgets domain that should know nothing about that. We need to separate concerns.
The more appropriate solution would be updating the registerModule function to accept two new properties, let's say MainRootComponent and EntityRootComponent (similarly to SettingsEditComponent, SettingsViewComponent, etc). These two components will be responsible for all side effects that a module needs to do and will be rendered on MainDashboard and EntityDashboard respectively.
Using components approach will let us perform side effects that rely on certain criteria to happen and can be deferred in time.
syncGoogleTagSettingsfromDashboardEntryPointinto a separate hook
Looks like there is no syncGoogleTagSettings in the DashboardEntryPoint component anymore.
@eugene-manuilov Thanks for the feedback/clarification and new route suggestion. I updated IB to reflect that approach.
Thanks, @zutigrm. Mostly looks good.
Add
assets/js/googlesitekit/modules/ModuleEffects.js
Let's put it into the assets/js/components folder and call it ModuleRootComponents.
Update
assets/js/components/DashboardMainApp.js
We need to update the DashboardEntryPoint component to render ModuleRootComponents. It should render the root components element + either ModuleSetup or DashboardMainApp depending on the setupModuleSlug value.
Extract:
- Custom dimensions logic from
DashboardMainAppinto a separate componentassets/js/modules/analytics-4/dashboard/SideEffects.js, which only contain hooks, and return null in the renderer
- Let's put it into the
assets/js/modules/analytics-4/componentsfolder and call itMainRootComponent. - We also need to move
syncGoogleTagSettingsfromDashboardEntryPointinto this new component: https://github.com/google/site-kit-wp/blob/f885564ff92344bdb6fd0f4fd2a9c64492e3335b/assets/js/components/DashboardEntryPoint.js#L36-L42
@eugene-manuilov Thanks, IB updated
Thanks, @zutigrm. IB ✔️
This is good to go now but we should only merge it after the split for the release. Assigning to @tofumatt to do that afterwards 👍
Just a note to mention there are a couple more reports of this users impacted by this over the past few days. Details below:
- https://wordpress.org/support/topic/sitekit-encountered-an-error-not-working/
- https://wordpress.org/support/topic/site-kit-encountered-an-error-131/
QA Update ✅
- Tested on main environment.
- Verified plugin setup.
- Verified main and entity dashboard.
- Verified data according to date range.
- Verified zero and gathering data state.
- Verified all modules setup.
- Verified all functionalities working same as latest environment and as expected.
- Verified tag manager setup.
- Verified custom dimension creation.
- Verified Google tag settings syncing.
https://github.com/user-attachments/assets/41a3f4a5-16ee-4ad0-bdcf-6d9de342170b
https://github.com/user-attachments/assets/c718d277-6c5f-4711-8197-5c6fda31d13c
https://github.com/user-attachments/assets/4fcd7e3b-be1c-4044-a859-bec23cac2402
https://github.com/user-attachments/assets/cff0b23a-7b70-4487-998a-89fb34c1516d
https://github.com/user-attachments/assets/2231d7db-5335-4df2-a415-06ed6d566514
https://github.com/user-attachments/assets/fbafbdf0-4e40-42c9-b49b-87d58a5b5148
https://github.com/user-attachments/assets/fbd2d859-ec19-4857-a78d-b30a3072fc8e
https://github.com/user-attachments/assets/b2a98ae5-5d38-4936-b4e6-a6a5f0e7c95f
Custom dimension
https://github.com/user-attachments/assets/d6d7114a-c1a9-49ca-a5db-7bf01364f63b
Google tag sync
https://github.com/user-attachments/assets/a64077d9-ee55-4018-95af-f5c5b8dd34a5