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

Release Optimization Detective v0.3.1 #1296

Merged
merged 2 commits into from
Jun 12, 2024

Conversation

westonruter
Copy link
Member

@westonruter westonruter commented Jun 10, 2024

optimization-detective

svn status:

M       class-od-url-metrics-group-collection.php
M       class-od-url-metrics-group.php
M       detect.js
M       detection.php
M       load.php
M       readme.txt
svn diff
Index: class-od-url-metrics-group-collection.php
===================================================================
--- class-od-url-metrics-group-collection.php	(revision 3100836)
+++ class-od-url-metrics-group-collection.php	(working copy)
@@ -21,7 +21,7 @@
  * @since 0.1.0
  * @access private
  */
-final class OD_URL_Metrics_Group_Collection implements Countable, IteratorAggregate {
+final class OD_URL_Metrics_Group_Collection implements Countable, IteratorAggregate, JSONSerializable {
 
 	/**
 	 * URL metrics groups.
@@ -450,4 +450,50 @@
 	public function count(): int {
 		return count( $this->groups );
 	}
+
+	/**
+	 * Specifies data which should be serialized to JSON.
+	 *
+	 * @since 0.3.1
+	 *
+	 * @return array{
+	 *             breakpoints: positive-int[],
+	 *             freshness_ttl: 0|positive-int,
+	 *             sample_size: positive-int,
+	 *             all_element_max_intersection_ratios: array<string, float>,
+	 *             common_lcp_element: ?ElementData,
+	 *             every_group_complete: bool,
+	 *             every_group_populated: bool,
+	 *             groups: array<int, array{
+	 *                 lcp_element: ?ElementData,
+	 *                 minimum_viewport_width: 0|positive-int,
+	 *                 maximum_viewport_width: positive-int,
+	 *                 complete: bool,
+	 *                 url_metrics: OD_URL_Metric[]
+	 *             }>
+	 *         } Data which can be serialized by json_encode().
+	 */
+	public function jsonSerialize(): array {
+		return array(
+			'breakpoints'                         => $this->breakpoints,
+			'freshness_ttl'                       => $this->freshness_ttl,
+			'sample_size'                         => $this->sample_size,
+			'all_element_max_intersection_ratios' => $this->get_all_element_max_intersection_ratios(),
+			'common_lcp_element'                  => $this->get_common_lcp_element(),
+			'every_group_complete'                => $this->is_every_group_complete(),
+			'every_group_populated'               => $this->is_every_group_populated(),
+			'groups'                              => array_map(
+				static function ( OD_URL_Metrics_Group $group ): array {
+					$group_data = $group->jsonSerialize();
+					// Remove redundant data.
+					unset(
+						$group_data['freshness_ttl'],
+						$group_data['sample_size']
+					);
+					return $group_data;
+				},
+				$this->groups
+			),
+		);
+	}
 }
Index: class-od-url-metrics-group.php
===================================================================
--- class-od-url-metrics-group.php	(revision 3100836)
+++ class-od-url-metrics-group.php	(working copy)
@@ -20,7 +20,7 @@
  * @since 0.1.0
  * @access private
  */
-final class OD_URL_Metrics_Group implements IteratorAggregate, Countable {
+final class OD_URL_Metrics_Group implements IteratorAggregate, Countable, JsonSerializable {
 
 	/**
 	 * URL metrics.
@@ -335,4 +335,31 @@
 	public function count(): int {
 		return count( $this->url_metrics );
 	}
+
+	/**
+	 * Specifies data which should be serialized to JSON.
+	 *
+	 * @since 0.3.1
+	 *
+	 * @return array{
+	 *             freshness_ttl: 0|positive-int,
+	 *             sample_size: positive-int,
+	 *             minimum_viewport_width: 0|positive-int,
+	 *             maximum_viewport_width: positive-int,
+	 *             lcp_element: ?ElementData,
+	 *             complete: bool,
+	 *             url_metrics: OD_URL_Metric[]
+	 *         } Data which can be serialized by json_encode().
+	 */
+	public function jsonSerialize(): array {
+		return array(
+			'freshness_ttl'          => $this->freshness_ttl,
+			'sample_size'            => $this->sample_size,
+			'minimum_viewport_width' => $this->minimum_viewport_width,
+			'maximum_viewport_width' => $this->maximum_viewport_width,
+			'lcp_element'            => $this->get_lcp_element(),
+			'complete'               => $this->is_complete(),
+			'url_metrics'            => $this->url_metrics,
+		);
+	}
 }
Index: detect.js
===================================================================
--- detect.js	(revision 3100836)
+++ detect.js	(working copy)
@@ -1 +1 @@
-const win=window,doc=win.document,consoleLogPrefix="[Optimization Detective]",storageLockTimeSessionKey="odStorageLockTime";function isStorageLocked(e,t){if(0===t)return!1;try{const o=parseInt(sessionStorage.getItem(storageLockTimeSessionKey));return!isNaN(o)&&e<o+1e3*t}catch(e){return!1}}function setStorageLock(e){try{sessionStorage.setItem(storageLockTimeSessionKey,String(e))}catch(e){}}function log(...e){console.log(consoleLogPrefix,...e)}function warn(...e){console.warn(consoleLogPrefix,...e)}function error(...e){console.error(consoleLogPrefix,...e)}function isViewportNeeded(e,t){let o=!1;for(const{minimumViewportWidth:n,complete:i}of t){if(!(e>=n))break;o=!i}return o}function getCurrentTime(){return Date.now()}export default async function detect({serveTime:e,detectionTimeWindow:t,isDebug:o,restApiEndpoint:n,restApiNonce:i,currentUrl:r,urlMetricsSlug:s,urlMetricsNonce:c,urlMetricsGroupStatuses:a,storageLockTTL:d,webVitalsLibrarySrc:l}){const u=getCurrentTime();if(u-e>t)return void(o&&warn("Aborted detection due to being outside detection time window."));if(!isViewportNeeded(win.innerWidth,a))return void(o&&log("No need for URL metrics from the current viewport."));if(await new Promise((e=>{"loading"!==doc.readyState?e():doc.addEventListener("DOMContentLoaded",e,{once:!0})})),await new Promise((e=>{"complete"===doc.readyState?e():win.addEventListener("load",e,{once:!0})})),"function"==typeof requestIdleCallback&&await new Promise((e=>{requestIdleCallback(e)})),isStorageLocked(u,d))return void(o&&warn("Aborted detection due to storage being locked."));if(doc.documentElement.scrollTop>0)return void(o&&warn("Aborted detection since initial scroll position of page is not at the top."));o&&log("Proceeding with detection");const g=doc.body.querySelectorAll("[data-od-xpath]"),w=new Map([...g].map((e=>[e,e.dataset.odXpath]))),f=[];let m;function p(){m instanceof IntersectionObserver&&(m.disconnect(),win.removeEventListener("scroll",p))}w.size>0&&(await new Promise((e=>{m=new IntersectionObserver((t=>{for(const e of t)e.isIntersecting&&f.push(e);e()}),{root:null,threshold:0});for(const e of w.keys())m.observe(e)})),win.addEventListener("scroll",p,{once:!0,passive:!0}));const{onLCP:h}=await import(l),L=[];await new Promise((e=>{h((t=>{L.push(t),e()}),{reportAllChanges:!0})})),p(),o&&log("Detection is stopping.");const b={url:r,slug:s,nonce:c,viewport:{width:win.innerWidth,height:win.innerHeight},elements:[]},S=L.at(-1);for(const e of f){const t=w.get(e.target);if(!t){o&&error("Unable to look up XPath for element");continue}const n={isLCP:e.target===S?.entries[0]?.element,isLCPCandidate:!!L.find((t=>t.entries[0]?.element===e.target)),xpath:t,intersectionRatio:e.intersectionRatio,intersectionRect:e.intersectionRect,boundingClientRect:e.boundingClientRect};b.elements.push(n)}o&&log("URL metrics:",b),await new Promise((e=>{setTimeout(e,0)}));try{const e=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":i},body:JSON.stringify(b)});if(200===e.status&&setStorageLock(getCurrentTime()),o){const t=await e.json();200===e.status?log("Response:",t):error("Failure:",t)}}catch(e){o&&error(e)}w.clear()}
\ No newline at end of file
+const win=window,doc=win.document,consoleLogPrefix="[Optimization Detective]",storageLockTimeSessionKey="odStorageLockTime";function isStorageLocked(e,t){if(0===t)return!1;try{const o=parseInt(sessionStorage.getItem(storageLockTimeSessionKey));return!isNaN(o)&&e<o+1e3*t}catch(e){return!1}}function setStorageLock(e){try{sessionStorage.setItem(storageLockTimeSessionKey,String(e))}catch(e){}}function log(...e){console.log(consoleLogPrefix,...e)}function warn(...e){console.warn(consoleLogPrefix,...e)}function error(...e){console.error(consoleLogPrefix,...e)}function isViewportNeeded(e,t){let o=!1;for(const{minimumViewportWidth:n,complete:i}of t){if(!(e>=n))break;o=!i}return o}function getCurrentTime(){return Date.now()}export default async function detect({serveTime:e,detectionTimeWindow:t,isDebug:o,restApiEndpoint:n,restApiNonce:i,currentUrl:r,urlMetricsSlug:s,urlMetricsNonce:c,urlMetricsGroupStatuses:a,storageLockTTL:d,webVitalsLibrarySrc:l,urlMetricsGroupCollection:u}){const g=getCurrentTime();if(o&&log("Stored URL metrics group collection:",u),g-e>t)return void(o&&warn("Aborted detection due to being outside detection time window."));if(!isViewportNeeded(win.innerWidth,a))return void(o&&log("No need for URL metrics from the current viewport."));if(await new Promise((e=>{"loading"!==doc.readyState?e():doc.addEventListener("DOMContentLoaded",e,{once:!0})})),await new Promise((e=>{"complete"===doc.readyState?e():win.addEventListener("load",e,{once:!0})})),"function"==typeof requestIdleCallback&&await new Promise((e=>{requestIdleCallback(e)})),isStorageLocked(g,d))return void(o&&warn("Aborted detection due to storage being locked."));if(doc.documentElement.scrollTop>0)return void(o&&warn("Aborted detection since initial scroll position of page is not at the top."));o&&log("Proceeding with detection");const w=doc.body.querySelectorAll("[data-od-xpath]"),m=new Map([...w].map((e=>[e,e.dataset.odXpath]))),f=[];let p;function h(){p instanceof IntersectionObserver&&(p.disconnect(),win.removeEventListener("scroll",h))}m.size>0&&(await new Promise((e=>{p=new IntersectionObserver((t=>{for(const e of t)f.push(e);e()}),{root:null,threshold:0});for(const e of m.keys())p.observe(e)})),win.addEventListener("scroll",h,{once:!0,passive:!0}));const{onLCP:L}=await import(l),S=[];await new Promise((e=>{L((t=>{S.push(t),e()}),{reportAllChanges:!0})})),h(),o&&log("Detection is stopping.");const b={url:r,slug:s,nonce:c,viewport:{width:win.innerWidth,height:win.innerHeight},elements:[]},v=S.at(-1);for(const e of f){const t=m.get(e.target);if(!t){o&&error("Unable to look up XPath for element");continue}const n={isLCP:e.target===v?.entries[0]?.element,isLCPCandidate:!!S.find((t=>t.entries[0]?.element===e.target)),xpath:t,intersectionRatio:e.intersectionRatio,intersectionRect:e.intersectionRect,boundingClientRect:e.boundingClientRect};b.elements.push(n)}o&&log("Current URL metrics:",b),await new Promise((e=>{setTimeout(e,0)}));try{const e=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":i},body:JSON.stringify(b)});if(200===e.status&&setStorageLock(getCurrentTime()),o){const t=await e.json();200===e.status?log("Response:",t):error("Failure:",t)}}catch(e){o&&error(e)}m.clear()}
\ No newline at end of file
Index: detection.php
===================================================================
--- detection.php	(revision 3100836)
+++ detection.php	(working copy)
@@ -60,6 +60,9 @@
 		'storageLockTTL'          => OD_Storage_Lock::get_ttl(),
 		'webVitalsLibrarySrc'     => $web_vitals_lib_src,
 	);
+	if ( WP_DEBUG ) {
+		$detect_args['urlMetricsGroupCollection'] = $group_collection;
+	}
 
 	return wp_get_inline_script_tag(
 		sprintf(
Index: load.php
===================================================================
--- load.php	(revision 3100836)
+++ load.php	(working copy)
@@ -5,7 +5,7 @@
  * Description: Provides an API for leveraging real user metrics to detect optimizations to apply on the frontend to improve page performance.
  * Requires at least: 6.4
  * Requires PHP: 7.2
- * Version: 0.3.0
+ * Version: 0.3.1
  * Author: WordPress Performance Team
  * Author URI: https://make.wordpress.org/performance/
  * License: GPLv2 or later
@@ -65,7 +65,7 @@
 	}
 )(
 	'optimization_detective_pending_plugin',
-	'0.3.0',
+	'0.3.1',
 	static function ( string $version ): void {
 
 		// Define the constant.
Index: readme.txt
===================================================================
--- readme.txt	(revision 3100836)
+++ readme.txt	(working copy)
@@ -4,7 +4,7 @@
 Requires at least: 6.4
 Tested up to:      6.5
 Requires PHP:      7.2
-Stable tag:        0.3.0
+Stable tag:        0.3.1
 License:           GPLv2 or later
 License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 Tags:              performance, optimization, rum
@@ -135,6 +135,16 @@
 
 == Changelog ==
 
+= 0.3.1 =
+
+**Enhancements**
+
+* Log URL metrics group collection to console when debugging is enabled (`WP_DEBUG` is true). ([1295](https://github.com/WordPress/performance/pull/1295))
+
+**Bug Fixes**
+
+* Include non-intersecting elements in URL metrics to fix lazy-load optimization. ([1293](https://github.com/WordPress/performance/pull/1293))
+
 = 0.3.0 =
 
 * The image optimization features have been split out into a new dependent plugin called [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/), which also now optimizes image lazy-loading. ([1088](https://github.com/WordPress/performance/issues/1088))

@westonruter westonruter added [Type] Documentation Documentation to be added or enhanced [Plugin] Optimization Detective Issues for the Optimization Detective plugin skip changelog PRs that should not be mentioned in changelogs labels Jun 10, 2024
@westonruter westonruter requested a review from joemcgill June 10, 2024 23:31
@westonruter westonruter requested a review from felixarntz as a code owner June 10, 2024 23:31
@westonruter westonruter force-pushed the publish/optimization-detective-0.3.1 branch from 95217a9 to 3d6950d Compare June 10, 2024 23:32
Copy link

github-actions bot commented Jun 10, 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 <[email protected]>
Co-authored-by: swissspidy <[email protected]>

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

@westonruter
Copy link
Member Author

westonruter commented Jun 10, 2024

Build for testing:
optimization-detective.zip

@github-actions github-actions bot temporarily deployed to wp.org plugin: optimization-detective June 12, 2024 05:25 Destroyed
@westonruter westonruter merged commit d11821d into trunk Jun 12, 2024
19 checks passed
@westonruter westonruter deleted the publish/optimization-detective-0.3.1 branch June 12, 2024 05:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Optimization Detective Issues for the Optimization Detective plugin skip changelog PRs that should not be mentioned in changelogs [Type] Documentation Documentation to be added or enhanced
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants