Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/rules/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,6 @@ without them still falls back to it — the section date state this widget no lo
follows. So the outer component defaults the attribute
(`attributes.reportParams ?? DEFAULT_REPORT_PARAMS`) and wraps `WidgetRoot` in the scope
its body supports (`<ReportScopeProvider offersComparison={ false }>` for a report with
no comparison). `widgets/wordads-chart-tabs/` is the reference.
no comparison). `projects/packages/ads/widgets/wordads-chart-tabs/` is the reference.

<!-- TODO: link to the canonical widget API declaration (contract types). -->
20 changes: 20 additions & 0 deletions projects/packages/premium-analytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ capability-gated admin page serves the dashboard.
- [Dashboard widget types](docs/dashboard-widgets.md): how a widget type is registered, filtered,
served to the client and imported, and how another plugin registers one.

## Extending the dashboard from another plugin

The package owns the dashboard, not the features. A section and its widgets belong to the code that knows the feature is there, and `jetpack-mu-wpcom` registers on WordPress.com Simple and Atomic where that fact is a plan feature.

A plugin extends the dashboard from two actions. Each fires once, when its registry hydrates on the first read after `init`.

### Sections

Hook `jetpack_premium_analytics_register_dashboard_sections` and call `register_dashboard_section()` with the section's label, availability rule and default layout. See [Dashboard sections](docs/dashboard-sections.md).

### Widget types

Hook `jetpack_premium_analytics_register_widget_types`, compare `WIDGET_API_VERSION`, require the `build/build.php` wp-build generated for your `widgets/` folder, and call `register_widget_types_from_manifest()` with its manifest, your text domain and the URL of your `i18n-manifest.json`. See [Dashboard widget types](docs/dashboard-widgets.md).

Your widgets import the dashboard through `@automattic/jetpack-premium-analytics-sdk` (`projects/js-packages/premium-analytics-sdk`). Your build keeps that import external, and the dashboard page's import map resolves it to the module this package registers.

### The reference consumer

`projects/packages/ads`: the Ads section and its three widgets, called by the WordAds module of the Jetpack plugin and by `jetpack-mu-wpcom`. Report pages and detail routes are the next contract; today they are the package's own.

## Requirements

- **PHP** >= 7.4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Docs only: the SDK a plugin's widgets import and the Ads package as the reference consumer; nothing a site owner can observe.


69 changes: 44 additions & 25 deletions projects/packages/premium-analytics/docs/dashboard-sections.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ This page covers sections only. Widget types have their own page, [Dashboard wid

## Files

| File | Role |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/class-dashboard-section.php` | The section model: properties, `is_available()`, `get_default_layout()`, `to_array()`. |
| `src/class-dashboard-section-registry.php` | The registry: `register()` with its validations, the reads, and the lazy hydration that fires the registration action. |
| `src/dashboard-sections.php` | The section API: the `register_dashboard_section()` family, the preview scope, the script data, the REST routes and their schema. |
| `src/dashboard-layout.php` | Layout primitives: `DASHBOARD_NAME`, the default-layout filter name, `get_dashboard_default_widget_instance()`, and the package's availability policy on default layouts. |
| `src/default-dashboard-sections.php` | The package's own sections: Traffic, Insights, Subscribers, Store, their gates and their default layouts, registered through the action like any plugin's. |
| `src/class-analytics.php` | Loads the three files above on wp-admin requests (`load_dashboard_components()`). |
| `src/class-dashboard-support-routes.php` | Loads them on REST requests (`boot_routes()`), on connected sites and, called directly by WordPress.com, on Simple. |
| `packages/data/src/entities/dashboard-entities.ts` | The `dashboardSection` core-data entity the client reads. |
| `routes/dashboard/` | `useDashboardSections()`, `useActiveSection()`, `useDashboardSectionLayout()`, and the stage that renders the navigation. |
| `routes/site-readiness.ts` | `isDashboardSectionInPreviewScope()`, the report routes' gate, fed by the inline script data. |
| File | Role |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/class-dashboard-section.php` | The section model: properties, `is_available()`, `get_default_layout()`, `to_array()`. |
| `src/class-dashboard-section-registry.php` | The registry: `register()` with its validations, the reads, and the lazy hydration that fires the registration action. |
| `src/dashboard-sections.php` | The section API: the `register_dashboard_section()` family, the preview scope, the script data, the REST routes and their schema. |
| `src/dashboard-layout.php` | Layout primitives: `DASHBOARD_NAME`, the default-layout filter name, `get_dashboard_default_widget_instance()`, and the package's availability policy on default layouts. |
| `src/default-dashboard-sections.php` | The package's own sections: Traffic, Insights, Subscribers, Store, their gates and their default layouts, registered through the action like any plugin's. |
| `src/class-analytics.php` | Loads the three files above on wp-admin requests (`load_dashboard_components()`). |
| `src/class-dashboard-support-routes.php` | Loads them on REST requests (`boot_routes()`), on connected sites and, called directly by WordPress.com, on Simple. |
| `packages/data/src/entities/dashboard-entities.ts` | The `dashboardSection` core-data entity the client reads. |
| `routes/dashboard/` | `useDashboardSections()`, `useActiveSection()`, `useDashboardSectionLayout()`, and the stage that renders the navigation. |
| `routes/site-readiness.ts` | `isDashboardSectionInPreviewScope()`, the report routes' gate, fed by the inline script data. |

## When the section files load

Expand Down Expand Up @@ -185,25 +185,44 @@ A section that fails either rule is absent from the sections route, from the scr

![The WordAds module registers the Ads section on self-hosted sites and skips the WordPress.com platform, jetpack-mu-wpcom registers it on Atomic and Simple by plan feature, both at priority 20 skipping an existing ads slug, and the standalone plugin has no registrant.](diagrams/sections-ads-owners.svg)

WordAds is a module of the Jetpack plugin, so on a self-hosted site the module registers the section: `modules/wordads/php/class-wordads-premium-analytics.php` hooks the action at priority 20 from `class-wordads.php`, which loads only while the module is active on a connected site.
### Who owns the section

On the WordPress.com platform the module registrant skips, `Host::is_wpcom_platform()`, and `jetpack-mu-wpcom` registers the section instead when the plan includes WordAds (`src/features/premium-analytics/wordads-section.php`): Simple runs no Jetpack plugin, and on Atomic the module is routinely off while the plan carries the feature. One owner per environment, decided in code rather than by hook order.
The section, its layout and its widgets live in the `jetpack-ads` package (`projects/packages/ads`, `Analytics_Dashboard`). The package decides nothing about who gets Ads; whoever calls it hooks the action at priority 20.

Both registrants skip when a section with slug `ads` already exists. That matters during a deploy skew only: an older package that still registers the section itself keeps it. They read the slug through `get_registered_by_slug()` when the package offers it and through `get_all_registered()` otherwise, since the package and the registrants ship on different cadences.
### On self-hosted sites

Both register the same layout, `Analytics_Dashboard::get_default_layout()`, of the `wordads/*` widget types the same class registers. The standalone `premium-analytics` plugin has no registrant, and no Ads section.
WordAds is a module of the Jetpack plugin, so the module calls `Analytics_Dashboard::init()` from `modules/wordads/php/class-wordads-premium-analytics.php`. `class-wordads.php` loads it only while the module is active on a connected site.

### On WordPress.com

The module registrant skips the platform, `Host::is_wpcom_platform()`. `jetpack-mu-wpcom` calls the package's registrants instead when the plan includes WordAds, from `src/features/premium-analytics/wordads-section.php`.

Simple runs no Jetpack plugin, and on Atomic the module is routinely off while the plan carries the feature. One owner per environment, decided in code rather than by hook order.

The Jetpack plugin bundles the package, and `jetpack-mu-wpcom` lists it as a test-only dependency. WordPress.com loads the Jetpack copy, so Simple and Atomic serve the widget bundles from it, the way they serve this package.

### Deploy skew

Both registrants skip when a section with slug `ads` already exists. That matters during a deploy skew only: an older package that still registers the section itself keeps it.

They read the slug through `get_registered_by_slug()` when the package offers it and through `get_all_registered()` otherwise, since the package and the registrants ship on different cadences.

### The layout

Both register the same layout, `Analytics_Dashboard::get_default_layout()`, of the `wordads/*` widget types the same class registers (see [Dashboard widget types](dashboard-widgets.md#a-real-consumer-the-ads-widgets)). The standalone `premium-analytics` plugin has no registrant, and no Ads section.

## Where the tests are

| Behaviour | Test |
| ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Registry rules, hydration and the action, the built-in sections, preview scope, the sections route and its schema | `tests/php/Dashboard_Section_Test.php` |
| Default-layout primitives, the filter and the package's policy, the bundled layouts | `tests/php/Dashboard_Layout_Test.php` |
| The WordAds module's registrant | `projects/plugins/jetpack/tests/php/modules/wordads/WordAds_Premium_Analytics_Test.php` |
| The WordPress.com registrant | `projects/packages/jetpack-mu-wpcom/tests/php/features/premium-analytics/Wordads_Section_Test.php` |
| The REST entry point WordPress.com calls | `tests/php/Dashboard_Support_Routes_Test.php` |
| Navigation, active section, stored layouts, section heading | `routes/dashboard/**/*.test.ts(x)` |
| Reports behind a hidden section | `routes/reports/registry.test.ts`, `tests/js/site-readiness.test.ts` |
| Behaviour | Test |
| ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Registry rules, hydration and the action, the built-in sections, preview scope, the sections route and its schema | `tests/php/Dashboard_Section_Test.php` |
| Default-layout primitives, the filter and the package's policy, the bundled layouts | `tests/php/Dashboard_Layout_Test.php` |
| The WordAds module's registrant | `projects/plugins/jetpack/tests/php/modules/wordads/WordAds_Premium_Analytics_Test.php` |
| The Ads package's registrants, with and without the widget contract loaded | `projects/packages/ads/tests/php/Analytics_Dashboard_Test.php`, `projects/packages/ads/tests/php/Analytics_Dashboard_Without_Widget_Types_Test.php` |
| The WordPress.com registrant | `projects/packages/jetpack-mu-wpcom/tests/php/features/premium-analytics/Wordads_Section_Test.php` |
| The REST entry point WordPress.com calls | `tests/php/Dashboard_Support_Routes_Test.php` |
| Navigation, active section, stored layouts, section heading | `routes/dashboard/**/*.test.ts(x)` |
| Reports behind a hidden section | `routes/reports/registry.test.ts`, `tests/js/site-readiness.test.ts` |

## Not covered here

Expand Down
Loading
Loading