Skip to content

Premium Analytics: add conditional widget availability API#50031

Draft
nerrad wants to merge 1 commit into
trunkfrom
codex/premium-analytics-widget-availability
Draft

Premium Analytics: add conditional widget availability API#50031
nerrad wants to merge 1 commit into
trunkfrom
codex/premium-analytics-widget-availability

Conversation

@nerrad

@nerrad nerrad commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #49701.

Note

This is AI generated based of an idea related to how we'd handle both other plugin requirement checks before widgets are available for users. This covers two scenarios: a. Widgets we bundle in Premium Analytics (defaults), and ; b. widgets extension developers can offer.

The latter is something that will likely still need some thought because of the sync for the data, so extensibility will require much more than just exposing the widget likely.

We will need something regardless for ensuring WooCommerce widgets (and other plugins supported for launch like WooCommerce Bookings) don't show widgets that aren't available. There is related existing work in #50002 / WOOA7S-1611 for a server-side widget type availability filter; this draft explores the requirements/version-gating layer that could build on that.

Another thing to keep in mind is that there is a potential opportunity for upsells in the future that could be controlled via this API as well.

Proposed changes

  • Add a server-side Premium Analytics widget availability helper for conditional widget registration.
  • Route bundled widget registration through register_widget_type(), which checks requirements before adding widgets to Widget_Type_Registry.
  • Add jetpack_premium_analytics_widget_requirements so Premium Analytics or extensions can add requirements for bundled or extension-owned widgets.
  • Add jetpack_premium_analytics_register_widget_types so extensions can register widget types with Premium Analytics after bundled widgets are processed.
  • Add the Bookings by device example requirement: jpa/bookings-by-device requires the WooCommerce Bookings plugin file (woocommerce-bookings/woocommerce-bookings.php) to exist and either WC_Bookings or WC_BOOKINGS_VERSION to be present at runtime. No minimum Bookings version is required yet.
  • Add PHP tests for the availability helper, extension action, filter path, and version requirement support.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • Run composer phpunit from projects/packages/premium-analytics.
  • Run vendor/bin/phpcs -p projects/packages/premium-analytics/src/widget-availability.php projects/packages/premium-analytics/src/widget-types.php projects/packages/premium-analytics/tests/php/Widget_Availability_Test.php projects/packages/premium-analytics/tests/php/fixtures/widget-modules-manifest.php from the repo root.
  • Run pnpm --filter @automattic/jetpack-premium-analytics typecheck from the repo root.
  • Run pnpm --filter @automattic/jetpack-premium-analytics build from the repo root.

@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

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:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jun 28, 2026
@jp-launch-control

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/src/widget-types.php 4/26 (15.38%) 15.38% 1 ❤️‍🩹

1 file is newly checked for coverage.

File Coverage
projects/packages/premium-analytics/src/widget-availability.php 10/56 (17.86%) 💔

Full summary · PHP report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@nerrad nerrad force-pushed the codex/premium-analytics-widget-availability branch from a404aaf to 9456084 Compare June 28, 2026 13:31
@nerrad nerrad force-pushed the codex/premium-analytics-widget-availability branch from 9456084 to 86e11a2 Compare June 28, 2026 13:41

@nerrad nerrad left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I left a few comments on the AI generated prototype.

Comment on lines +15 to +22
const WIDGET_REQUIREMENTS_FILTER = 'jetpack_premium_analytics_widget_requirements';

/**
* Action name for registering widget types supplied outside Premium Analytics.
*
* @var string
*/
const REGISTER_WIDGET_TYPES_ACTION = 'jetpack_premium_analytics_register_widget_types';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If these are used, should maybe prefix with __experimental as an indicator for third parties to avoid using for now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also, if we want to make this forward compatible with potential upstream changes, we could have the filter and action names returned by function, that way we can swap out the string to whatever the upstream filter/action hook ends up being and it should "just work".

return array(
'jpa/bookings-by-device' => array(
array(
'plugin_file' => 'woocommerce-bookings/woocommerce-bookings.php',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This assumes a plugin is installed at a specific path - which might not always be the case.

Comment on lines +35 to +36
'active_constant' => 'WC_BOOKINGS_VERSION',
'version_constant' => 'WC_BOOKINGS_VERSION',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What's the difference between these two?


/**
* Returns Premium Analytics' built-in widget availability requirements.
*

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should document widget requirements array.

return array( $requirements );
}

return array_values( array_filter( $requirements, 'is_array' ) );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think the fallback should be to reject the value if it's not a valid requirements array and return just the default requirements? Otherwise it seems like this could still be incorrect.

Comment on lines +100 to +102
if ( isset( $requirements['plugin_file'] ) || isset( $requirements['active_class'] ) || isset( $requirements['active_function'] ) || isset( $requirements['active_constant'] ) || isset( $requirements['version_constant'] ) ) {
return array( $requirements );
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should every property be required?

Comment on lines +122 to +144
if ( ! is_array( $requirement ) ) {
return false;
}

if ( ! empty( $requirement['plugin_file'] ) && ! is_widget_requirement_plugin_file_available( $requirement['plugin_file'] ) ) {
return false;
}

if ( has_widget_requirement_active_signal( $requirement ) && ! is_widget_requirement_active_signal_met( $requirement ) ) {
return false;
}

if ( empty( $requirement['active_class'] ) && empty( $requirement['active_function'] ) && empty( $requirement['active_constant'] ) && ! empty( $requirement['plugin_file'] ) && ! is_widget_requirement_plugin_active( $requirement['plugin_file'] ) ) {
return false;
}

if ( ! empty( $requirement['min_version'] ) ) {
if ( empty( $requirement['version_constant'] ) || ! defined( $requirement['version_constant'] ) ) {
return false;
}

return version_compare( (string) constant( $requirement['version_constant'] ), (string) $requirement['min_version'], '>=' );
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Several of the conditional checks could be eliminated here is we made the shape of the requirements config array non-optional.

* @param array $args Widget type arguments.
* @return Widget_Type|false The registered widget type on success, or false on failure/unavailable requirements.
*/
function register_widget_type( $name, $args = array() ) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Definitely something that should live as a part of the potential upstream API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also, the requirements check should be in Widget_Type_Registry::register() method? Everything that is in widet-availability also seems like something that should be in the Widget_Type_Registry class.

@retrofox

Copy link
Copy Markdown
Contributor

Thanks @nerrad.

As we know, #50002 and this PR overlap, so I've updated it.

It now exposes two neutral filters in core (the PA copy of the registry that could go upstream):

  • registry-time: drops manifest candidates before they're registered (gone
    everywhere).
  • runtime: scopes the registered set on read.

Here, PA uses the registry-time filter as a consumer to keep the dev widget out of production, so it's never registered there at all (as suggested by gate-at-registration @chihsuan).

The runtime filter stays available for soft cases, e.g., a paid widget shown locked.

The one thing I'd push back on is declaring availability directly on the widget.

A widget should carry facts (name, category), not policy (requires, plan, environment): it doesn't know the plans/env/active plugins, a business-rule change would mean rebuilding the artifact, and a registry that understands plugin_file/version_constant` can't go upstream problem-agnostic.

The line I'd hold: conditioning the registration and visibility of widgets is the consumer's job, not the infrastructure's.

Core just exposes the filters; PA decides the policy (dev widgets, paid widgets, whatever).

The two filters split the decision cleanly: registry-time = should it exist here? (hard: dev tools in prod), runtime = how is it shown? (soft: a paid widget rendered locked).

So Bookings becomes a PA/Woo filter callback; core never knows "Bookings".

Your extension-registration action fits this directly. I'd just keep the
requirements engine as a PA-side policy on top of these filters rather than in the registry/schema, which also makes these two filters the upstreamable part.

I hope it does make sense. Thanks again for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Premium Analytics [Status] In Progress [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants