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

Fix storage of URL Metric when plain non-pretty permalinks are enabled #1574

Merged
merged 10 commits into from
Oct 3, 2024

Conversation

westonruter
Copy link
Member

@westonruter westonruter commented Oct 2, 2024

Fixes #1567

The REST API merges params from GET, POST, and JSON into one params array. This causes problems when pretty permalinks, as the fetched URL is:

http://localhost:8888/index.php?rest_route=/optimization-detective/v1/url-metrics:store

Where the rest_route is erroneously picked up as one of the parameters.

To fix this, this PR separates meta parameters from the core URL metric parameters. The meta parameters (nonce and slug) are added as GET parameters whereas the parameters that comprise the URL metric data are all exclusively in the content body as JSON. This feels cleaner from a REST API perspective since the entity then becomes truly URL metric data, rather than URL metric with additional meta parameters added to it.

This also tidies up some symbol names, with "URL Metrics" (plural) being changed to "URL Metric" (singular) where appropriate.

Aside: Perhaps OD_URL_Metrics_Group_Collection should be renamed to OD_URL_Metric_Group_Collection as well. The "metrics" plural here is redundant with the group in the name.

@westonruter westonruter added [Type] Bug An existing feature is broken [Plugin] Optimization Detective Issues for the Optimization Detective plugin labels Oct 2, 2024
Copy link

github-actions bot commented Oct 2, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

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

@westonruter This makes sense to me and looks good, however I'm curious: Does this actually fix a bug, or is the change only about improving the code?

You were previously removing slug and nonce from the REST get_params() anyway, which seems it would be the same as now using get_json_params(). You mention the rest_route query parameter, but I thought this would not get included anyway since it's not a supported parameter of the endpoint?

Aside: Perhaps OD_URL_Metrics_Group_Collection should be renamed to OD_URL_Metric_Group_Collection as well. The "metrics" plural here is redundant with the group in the name.

+1 for that. Since it seems nobody other than us is adopting the plugin yet, this is probably fine to just rename. Once we document this further and encourage external usage, we'll need to be more careful about this kind of change.

@westonruter
Copy link
Member Author

westonruter commented Oct 2, 2024

Does this actually fix a bug, or is the change only about improving the code?

Yes, it fixes the bug, as well as improving the code. In reality, the most minimal patch to only fix the code would be to unset rest_route from the $params as well. The bug is that WordPress erroneously includes rest_route in the return value of get_params() even though it is not registered as one of the endpoint args.

So for non-pretty permalinks, using get_json_params() would be functionally the same as get_params() with the addition of the existing unset() of $data['nonce'] and $data['slug'].

Getting the URL Metric data from the JSON exclusively is safer as it's less likely that arbitrary query parameters will be added to the REST API URL to cause a data validation problem in the future. For example, if I had modified the plugin to add some cachebuster query parameter:

--- a/plugins/optimization-detective/detect.js
+++ b/plugins/optimization-detective/detect.js
@@ -392,7 +392,9 @@ export default async function detect( {
 	} );
 
 	try {
-		const response = await fetch( restApiEndpoint, {
+		const restUrl = new URL( restApiEndpoint );
+		restUrl.searchParams.append( 'cachebust', '123' );
+		const response = await fetch( restUrl, {
 			method: 'POST',
 			headers: {
 				'Content-Type': 'application/json',

(Such a parameter could be added by a proxy layer even.)

Then I get the same error even when pretty permalinks are enabled:

Failed to validate URL metric: cachebust is not a valid property of Object.

So again, it seems like a core bug that get_params() returns all of the GET and POST params, even those which are not registered.

@westonruter
Copy link
Member Author

+1 for that. Since it seems nobody other than us is adopting the plugin yet, this is probably fine to just rename. Once we document this further and encourage external usage, we'll need to be more careful about this kind of change.

@felixarntz Done in 942b397, although it touched quite a bit of code.

$this->processor = $processor;
$this->url_metrics_group_collection = $url_metrics_group_collection;
$this->link_collection = $link_collection;
public function __get( string $name ): OD_URL_Metric_Group_Collection {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is needed to account for someone updating Optimization Detective but not right away updating Image Prioritizer or Embed Optimizer. They are using the url_metrics_group_collection symbol, so without this back-compat code, a fatal error would occur. This is similar to following code in Image Prioritizer:

} elseif (
is_string( $processor->get_attribute( 'fetchpriority' ) )
&&
// Temporary condition in case someone updates Image Prioritizer without also updating Optimization Detective.
method_exists( $context->url_metrics_group_collection, 'is_any_group_populated' )
&&
$context->url_metrics_group_collection->is_any_group_populated()
) {
/*
* At this point, the element is not the shared LCP across all viewport groups. Nevertheless, server-side
* heuristics have added fetchpriority=high to the element, but this is not warranted either due to a lack
* of data or because the LCP element is not common across all viewport groups. Since we have collected at
* least some URL metrics (per is_any_group_populated), further below a fetchpriority=high preload link will
* be added for the viewport(s) for which this is actually the LCP element. Some viewport groups may never
* get populated due to a lack of traffic (e.g. from tablets or phablets), so it is important to remove
* fetchpriority=high in such case to prevent server-side heuristics from prioritizing loading the image
* which isn't actually the LCP element for actual visitors.
*/
$processor->remove_attribute( 'fetchpriority' );
}

The is_any_group_populated() method was added to Optimization Detective in a recent release, and to avoid a fatal error if someone updates Optimization Detective without updating Image Prioritizer as well, this prevents the code from causing a fatal error.

All of this code could be removed later once there is no longer this risk.

Copy link
Member

Choose a reason for hiding this comment

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

Let's add a TODO to remove this later.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added in 5960ff3

Comment on lines 191 to 197
array(
'post_id' => $post_id,
'request' => $request,
'url_metric' => $url_metric,
'url_metrics_group' => $group,
'url_metrics_group_collection' => $group_collection,
'post_id' => $post_id,
'request' => $request,
'url_metric' => $url_metric,
'url_metric_group' => $url_metric_group,
'url_metric_group_collection' => $url_metric_group_collection,
)
Copy link
Member Author

Choose a reason for hiding this comment

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

Instead of using an array here, it would be better to implement something like OD_Tag_Visitor_Context which has properties instead of array keys. This would allow for autocompletion as well as it would support possible future renaming of the members.

Copy link
Member Author

Choose a reason for hiding this comment

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

Like this: 1cf24f1

… OD_URL_Metric_Stored_Context instead of array
* @param OD_URL_Metric_Group $url_metric_group URL metric group.
* @param OD_URL_Metric $url_metric URL metric.
*/
public function __construct( WP_REST_Request $request, int $post_id, OD_URL_Metric_Group_Collection $url_metric_group_collection, OD_URL_Metric_Group $url_metric_group, OD_URL_Metric $url_metric ) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This class has a terrible amount of redundancy. However, in newer versions of PHP this can be made much simpler, thanks to Constructor Promotion.

So this could be replaced with:

final class OD_URL_Metric_Stored_Context {
	public function __construct(
		public WP_REST_Request $request,
		public int $post_id,
		public OD_URL_Metric_Group_Collection $url_metric_group_collection,
		public OD_URL_Metric_Group $url_metric_group,
		public OD_URL_Metric $url_metric
	) {}
}

This works in PHP 8.0+: https://3v4l.org/OqvTC

So we can't take advantage of it. But it will greatly simplify this class and OD_Tag_Visitor_Context.

Copy link
Member

Choose a reason for hiding this comment

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

How do you mean redundancy? Just because the properties are declared in several places?

Copy link
Member Author

@westonruter westonruter Oct 3, 2024

Choose a reason for hiding this comment

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

Yes. There are 5 instances of the url_metric_group_collection string in this class file when there could just be 1.

Copy link
Member

Choose a reason for hiding this comment

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

But that's just about how PHP (and many other languages work) right? I wouldn't necessarily call that redundancy, it's just more code to write. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. The verbosity can be reduced in the future.

@felixarntz
Copy link
Member

Does this actually fix a bug, or is the change only about improving the code?

Yes, it fixes the bug, as well as improving the code. In reality, the most minimal patch to only fix the code would be to unset rest_route from the $params as well. The bug is that WordPress erroneously includes rest_route in the return value of get_params() even though it is not registered as one of the endpoint args.

[...]

So again, it seems like a core bug that get_params() returns all of the GET and POST params, even those which are not registered.

Is there a Trac ticket for this? If not, could you please open one? :)

Comment on lines 19 to 20
*
* @property-read OD_URL_Metric_Group_Collection $url_metrics_group_collection
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's worth adding this, given it's only for backward compatibility.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's needed for static analysis. In 5960ff3 I've added a note that it is deprecated.

$this->processor = $processor;
$this->url_metrics_group_collection = $url_metrics_group_collection;
$this->link_collection = $link_collection;
public function __get( string $name ): OD_URL_Metric_Group_Collection {
Copy link
Member

Choose a reason for hiding this comment

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

Let's add a TODO to remove this later.

@@ -205,7 +205,7 @@ The [plugin source code](https://github.com/WordPress/performance/tree/trunk/plu

**Enhancements**

* Log URL metrics group collection to console when debugging is enabled (`WP_DEBUG` is true). ([1295](https://github.com/WordPress/performance/pull/1295))
* Log URL metric group collection to console when debugging is enabled (`WP_DEBUG` is true). ([1295](https://github.com/WordPress/performance/pull/1295))
Copy link
Member

Choose a reason for hiding this comment

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

Nit-pick, but I don't think we should update older changelogs as they are "history artifacts" (e.g. we probably wouldn't update this in an old GitHub release either). Let's revert this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted in ef9f3ae

* @param OD_URL_Metric_Group $url_metric_group URL metric group.
* @param OD_URL_Metric $url_metric URL metric.
*/
public function __construct( WP_REST_Request $request, int $post_id, OD_URL_Metric_Group_Collection $url_metric_group_collection, OD_URL_Metric_Group $url_metric_group, OD_URL_Metric $url_metric ) {
Copy link
Member

Choose a reason for hiding this comment

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

How do you mean redundancy? Just because the properties are declared in several places?

plugins/optimization-detective/storage/rest-api.php Outdated Show resolved Hide resolved
@westonruter
Copy link
Member Author

Is there a Trac ticket for this? If not, could you please open one? :)

I've just opened Core-62163

@westonruter westonruter requested a review from felixarntz October 3, 2024 16:20
plugins/optimization-detective/storage/rest-api.php Outdated Show resolved Hide resolved
'url_metrics_group' => $group,
'url_metrics_group_collection' => $group_collection,
'od_url_metric_stored',
new OD_URL_Metric_Stored_Context(
Copy link
Member

Choose a reason for hiding this comment

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

Since the only reason for this class to exist is the filter, I think we should clarify that in the name, which could be confusing if you look at the class alone.

Either we go with OD_URL_Metric_Context, which sounds like a good name, but could have the risk of sounding more generic than it actually is. Or we go with OD_URL_Metric_Stored_Filter_Context, which keeps the scope narrow as it is now, but clarifies that it's for a filter. I think the latter was unclear to me when I first saw the class, and I was wondering what "stored context" is.

Copy link
Member Author

Choose a reason for hiding this comment

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

How about eaed94f?

Copy link
Member

Choose a reason for hiding this comment

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

WFM 👍

westonruter and others added 2 commits October 3, 2024 09:56
Co-authored-by: Felix Arntz <felixarntz@google.com>
@westonruter westonruter merged commit 1e82f7b into trunk Oct 3, 2024
18 of 20 checks passed
@westonruter westonruter deleted the fix/od-plain-permalinks-compat branch October 3, 2024 18:07
@westonruter westonruter added [Plugin] Embed Optimizer Issues for the Embed Optimizer plugin (formerly Auto Sizes) [Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) labels Oct 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Embed Optimizer Issues for the Embed Optimizer plugin (formerly Auto Sizes) [Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) [Plugin] Optimization Detective Issues for the Optimization Detective plugin [Type] Bug An existing feature is broken
Projects
None yet
Development

Successfully merging this pull request may close these issues.

REST API calls to store URL metrics fail when pretty permalinks aren't enabled
2 participants