Repository navigation
Premium Analytics: fire an action when the widget type registry hydrates - #52568
Conversation
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage SummaryCoverage changed in 3 files.
|
register_widget_type() for plugins; the manifest registers from it
hydration diagram, AGENTS.md and README pointers
register_widget_types_from_manifest(); the package's registrant is its first caller
widgets/ form example, versioning section, diagram
0187e38 to
52a9a99
Compare
Nikschavan
left a comment
There was a problem hiding this comment.
Thank you, the changes look good. I left one question inline about the widget-availability.php header and a few small notes on the tests.
| 1. **Registry-time**, `jetpack_premium_analytics_registrable_widget_types`, over the manifest candidates in `register_widget_types()`. A dropped candidate never registers: gone from the REST list, the import map and every registry reader. For hard availability. | ||
| 2. **Runtime**, `jetpack_premium_analytics_widget_types`, over the registered map on every read of `get_available_widget_types()`. The type stays registered. For request-dependent or soft state, e.g. a type shown locked. | ||
|
|
||
| The package's own policy hooks the first, in `src/widget-availability.php`: developer-only widgets off production, the store and bookings categories without WooCommerce or Bookings, the store report categories without the capability. A plugin's manifest goes through the same filter when it registers through `register_widget_types_from_manifest()`; a type registered one by one with `register_widget_type()` does not. Either way, a plugin decides in its callback whether to register at all, as the section owners do. |
There was a problem hiding this comment.
This line says a type registered with register_widget_type() skips the registry-time filter, but the header of widget-availability.php still says the hide applies "so every consumer of the registry sees the same set". Should that header now say it covers manifest candidates only?
There was a problem hiding this comment.
Yes, the header was stale: the policy runs over manifest candidates, and a type registered one by one with register_widget_type() skips it. Reworded in #52634, the next step of the stack, to keep this one as approved.
| use PHPUnit\Framework\Attributes\CoversFunction; | ||
| use WorDBless\BaseTestCase; | ||
|
|
||
| require_once __DIR__ . '/../../src/widget-modules.php'; |
There was a problem hiding this comment.
Run on its own, this file errors in test_widget_type_registered_on_the_action_reaches_both_readers with Call to undefined function ...is_woocommerce_dashboard_section_available(). The REST read loads widget-availability.php, which calls that function from default-dashboard-sections.php, and nothing here requires that file. The suite passes only because another test loads it first. A require_once of default-dashboard-sections.php, as Widget_Availability_Test.php does, would remove the order dependence.
There was a problem hiding this comment.
Confirmed: run by path, the file errors on that test. #52634 adds the require_once of default-dashboard-sections.php, as Widget_Availability_Test.php does.
| /** | ||
| * The contract version a consumer compares against is a semantic version. | ||
| */ | ||
| public function test_widget_api_version_is_a_semantic_version() { |
There was a problem hiding this comment.
This test checks the shape of a constant that nothing in the package reads, so it can only fail when someone edits the literal itself. I think it could go without losing coverage of any behaviour.
There was a problem hiding this comment.
Agreed, it only guards the literal. Dropped in #52634.
| register_widget_types(); | ||
|
|
||
| $this->assertSame( array(), $this->doing_it_wrong ); | ||
| $this->assertCount( 1, array_keys( get_registered_widget_types(), Widget_Type_Registry::get_instance()->get_registered( self::MANIFEST_WIDGET ), true ) ); |
There was a problem hiding this comment.
The registry is keyed by name, so this count is always 1 once the widget is registered and cannot catch a second registration. The doing_it_wrong assertion above it is what fails when the duplicate skip is removed, so this line could go, or become an assertArrayHasKey if the registered check is the intent.
There was a problem hiding this comment.
Right, the registry is keyed by name, so the count cannot move. #52634 replaces it with assertArrayHasKey( self::MANIFEST_WIDGET, get_registered_widget_types() ): the intent was that the type is still registered after the second call.
require default sections in the registry test, drop the version-shape test, fix the availability header
require default sections in the registry test, drop the version-shape test, fix the availability header
require default sections in the registry test, drop the version-shape test, fix the availability header
…52634) * add the catalog location to widget module records textdomain and i18n_manifest per type; the client loads a plugin's widget catalogs from them * apply the review follow-ups from #52568 require default sections in the registry test, drop the version-shape test, fix the availability header * mock the widget module resolver in the stage test
…52634) * add the catalog location to widget module records textdomain and i18n_manifest per type; the client loads a plugin's widget catalogs from them * apply the review follow-ups from #52568 require default sections in the registry test, drop the version-shape test, fix the availability header * mock the widget module resolver in the stage test
Fixes WOOA7S-2184
Proposed changes
Step 1 of 4 of the widget-type extensibility stack (umbrella: #52637).
What?
A plugin can register widget types on the dashboard from a callback on
jetpack_premium_analytics_register_widget_types, the way it registers sections since #52454.Widget_Type_Registryhydrates on its first read afterinitand fires the action once, with the registry.register_widget_types_from_manifest( $widgets, $args )registers the manifest a plugin's wp-build generates.register_widget_type( $name, $args )registers a single type.bootstrap_widget_types()and itsinithook go away.WIDGET_API_VERSIONnames the contract a widget is built against. A consumer checks it before registering.Why?
Widget types had a registry, two filters, a REST route and an import map, but no public registration and no moment a plugin could hook: hydration ran from the package's own manifest, on
initor on the first read. The first real consumer, the Ads widgets, is #52635.How?
The registry latches before the action fires, so a registrant that reads the registry does not re-enter it. A read before
initis a_doing_it_wrong()that answers what was registered directly and does not latch, so later registrants still run.is_registered()never hydrates, so registering before the action works.docs/dashboard-widgets.mdwalks through the path, with a diagram.Related product discussion/links
Does this pull request change what data or activity we track or use?
No.
Testing instructions
cd projects/packages/premium-analytics && composer phpunitRegister a type from a plugin
Add this mu-plugin. It reuses the package's own Clicks modules under another name, so it needs no build:
I usually create a custom plugin in
tools/docker/mu-plugins/00-retrofox.phpGET /wp-json/wpcom/v2/widget-moduleslistsdemo/clicksnext tojpa/clicks, with the<script>stripped fromhelp.content.You may need to enable the composition mode:
define( 'WP_ENVIRONMENT_TYPE', 'production' )and'category' => 'developer'in the array, the type is gone from the list: the registry-time filter drops it, as it would a package widget.The single-type form is
register_widget_type( 'demo/clicks', array( … ) )with the same arguments, minus the sanitizing.A read before
init.From a plugin's main file, after a
require_onceof the package'ssrc/widget-types.php, call\Automattic\Jetpack\PremiumAnalytics\get_registered_widget_types(). PHP logs a_doing_it_wrong(), and the dashboard still offers every widget afterward.