diff --git a/projects/plugins/boost/app/assets/src/js/features/critical-css/folding-element/folding-element.tsx b/projects/plugins/boost/app/assets/src/js/features/critical-css/folding-element/folding-element.tsx index 308f048b56f81..293e0c005044c 100644 --- a/projects/plugins/boost/app/assets/src/js/features/critical-css/folding-element/folding-element.tsx +++ b/projects/plugins/boost/app/assets/src/js/features/critical-css/folding-element/folding-element.tsx @@ -1,11 +1,11 @@ -import useMeasure from 'react-use-measure'; +import ChevronDown from '$svg/chevron-down'; +import ChevronUp from '$svg/chevron-up'; +import { Button } from '@automattic/jetpack-components'; import { animated, useSpring } from '@react-spring/web'; import clsx from 'clsx'; import { useState } from 'react'; -import ChevronDown from '$svg/chevron-down'; -import ChevronUp from '$svg/chevron-up'; +import useMeasure from 'react-use-measure'; import styles from './folding-element.module.scss'; -import { Button } from '@automattic/jetpack-components'; type PropTypes = { labelExpandedText: string; diff --git a/projects/plugins/boost/app/assets/src/js/features/lcp/lcp.tsx b/projects/plugins/boost/app/assets/src/js/features/lcp/lcp.tsx index b2d96f9fbeac7..a0b4a54b83782 100644 --- a/projects/plugins/boost/app/assets/src/js/features/lcp/lcp.tsx +++ b/projects/plugins/boost/app/assets/src/js/features/lcp/lcp.tsx @@ -1,62 +1,13 @@ -import { __ } from '@wordpress/i18n'; -import styles from './lcp.module.scss'; -import { Button } from '@automattic/jetpack-components'; -import RefreshIcon from '$svg/refresh'; import Module from '$features/module/module'; -import { useLcpState, useOptimizeLcpAction } from './lib/stores/lcp-state'; -import TimeAgo from '$features/critical-css/time-ago/time-ago'; -import { recordBoostEvent } from '$lib/utils/analytics'; import Pill from '$features/ui/pill/pill'; - -const Status = () => { - const [ query ] = useLcpState(); - const lcpState = query?.data; - - if ( lcpState?.status === 'error' ) { - return ( -
- { __( - "An error occurred while optimizing your Cornerstone Page's LCP. Please try again.", - 'jetpack-boost' - ) } -
- ); - } - - if ( lcpState?.status === 'not_analyzed' ) { - // This should never happen, but just in case. - return ( -
- { __( - "Click the optimize button to start optimizing your Cornerstone Page's LCP.", - 'jetpack-boost' - ) } -
- ); - } - - if ( lcpState?.status === 'pending' ) { - return ( -
- { __( - "Jetpack Boost is optimizing your Cornerstone Page's LCP for you.", - 'jetpack-boost' - ) } -
- ); - } - - if ( lcpState?.status !== 'analyzed' || ! lcpState?.updated ) { - return null; - } - - return ( -
- { __( 'Last optimized', 'jetpack-boost' ) }{ ' ' } - . -
- ); -}; +import { recordBoostEvent } from '$lib/utils/analytics'; +import RefreshIcon from '$svg/refresh'; +import { Button } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import styles from './status/status.module.scss'; +import { useLcpState, useOptimizeLcpAction } from './lib/stores/lcp-state'; +import Status from './status/status'; +import { ErrorDetails } from './status/error-details'; const Lcp = () => { const [ query ] = useLcpState(); @@ -109,6 +60,7 @@ const Lcp = () => { { __( 'Optimize', 'jetpack-boost' ) } + { lcpState?.status === 'analyzed' && } ); }; diff --git a/projects/plugins/boost/app/assets/src/js/features/lcp/lib/stores/lcp-state-types.ts b/projects/plugins/boost/app/assets/src/js/features/lcp/lib/stores/lcp-state-types.ts index 6d21c9ce7e48d..71d2c547ca8c4 100644 --- a/projects/plugins/boost/app/assets/src/js/features/lcp/lib/stores/lcp-state-types.ts +++ b/projects/plugins/boost/app/assets/src/js/features/lcp/lib/stores/lcp-state-types.ts @@ -1,7 +1,6 @@ -import { JSONSchema } from '$lib/utils/json-types'; import z from 'zod'; -// @TODO: We don't send this back from the API, but it's here for if we do. +// TODO: Reflect this in Boost Cloud after Beta release, each one should be an Error type. export const LcpErrorType = z.enum( [ 'UrlError', 'HttpError', @@ -11,10 +10,7 @@ export const LcpErrorType = z.enum( [ ] ); export const LcpErrorDetailsSchema = z.object( { - url: z.coerce.string(), message: z.coerce.string(), - meta: z.record( JSONSchema ).catch( {} ), - type: LcpErrorType, } ); export const PageSchema = z.object( { @@ -25,7 +21,6 @@ export const PageSchema = z.object( { // Status status: z.enum( [ 'success', 'pending', 'error' ] ).catch( 'pending' ), // Error details - // @TODO: We don't send this back from the API, but it's here for if we do. errors: z.array( LcpErrorDetailsSchema ).optional(), } ); diff --git a/projects/plugins/boost/app/assets/src/js/features/lcp/status/error-details.module.scss b/projects/plugins/boost/app/assets/src/js/features/lcp/status/error-details.module.scss new file mode 100644 index 0000000000000..30d42e39112f2 --- /dev/null +++ b/projects/plugins/boost/app/assets/src/js/features/lcp/status/error-details.module.scss @@ -0,0 +1,22 @@ +@import '$css/main/mixins'; + +.summary { + margin-top: 16px; + margin-bottom: 16px; + + @include breakpoint( xs ) { + margin-bottom: 1em; + } + + &__list { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__row { + list-style: disc; + margin-bottom: 0; + margin-left: 20px; + } +} diff --git a/projects/plugins/boost/app/assets/src/js/features/lcp/status/error-details.tsx b/projects/plugins/boost/app/assets/src/js/features/lcp/status/error-details.tsx new file mode 100644 index 0000000000000..a751535f212aa --- /dev/null +++ b/projects/plugins/boost/app/assets/src/js/features/lcp/status/error-details.tsx @@ -0,0 +1,65 @@ +import FoldingElement from '$features/critical-css/folding-element/folding-element'; +import { recordBoostEvent } from '$lib/utils/analytics'; +import { Notice } from '@automattic/jetpack-components'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useLcpState } from '../lib/stores/lcp-state'; +import styles from './error-details.module.scss'; + +export const ErrorDetails = () => { + const [ query ] = useLcpState(); + const lcpState = query?.data; + + if ( lcpState?.status !== 'analyzed' ) { + return null; + } + + const pages = lcpState?.pages; + if ( ! pages || pages.length === 0 ) { + return null; + } + + const errors = pages.filter( page => ( page?.errors?.length || 0 ) > 0 ); + if ( errors.length === 0 ) { + return null; + } + + const errorMessages = errors.flatMap( p => ( p.errors || [] ).map( e => e.message ) ); + + return ( + +
+ { sprintf( + // translators: %d is a number of pages which failed to be optimized + _n( + '%d page could not be optimized.', + '%d pages could not be optimized.', + errorMessages.length, + 'jetpack-boost' + ), + errorMessages.length + ) } +
+ { + if ( isExpanded ) { + recordBoostEvent( 'lcp_error_details_expanded', {} ); + } + } } + > +
    + { errorMessages.map( ( error, index ) => ( +
  • + { error } +
  • + ) ) } +
+
+
+ ); +}; diff --git a/projects/plugins/boost/app/assets/src/js/features/lcp/lcp.module.scss b/projects/plugins/boost/app/assets/src/js/features/lcp/status/status.module.scss similarity index 50% rename from projects/plugins/boost/app/assets/src/js/features/lcp/lcp.module.scss rename to projects/plugins/boost/app/assets/src/js/features/lcp/status/status.module.scss index c14164007bc01..c923bdb017500 100644 --- a/projects/plugins/boost/app/assets/src/js/features/lcp/lcp.module.scss +++ b/projects/plugins/boost/app/assets/src/js/features/lcp/status/status.module.scss @@ -1,11 +1,11 @@ -@import "$css/main/mixins"; -@import "$css/main/variables"; +@import '$css/main/mixins'; +@import '$css/main/variables'; // TODO: This is a copy of the styles from the critical-css/status component. // We should move this to a shared location. .status { - margin-bottom: 32px; + margin-bottom: 16px; font-size: 14px; line-height: 22px; display: flex; @@ -16,7 +16,7 @@ margin-bottom: 0; } - @include breakpoint(xs) { + @include breakpoint( xs ) { display: block; } @@ -25,47 +25,28 @@ flex-grow: 1; position: relative; - .successes, .generating { + .successes, + .generating { color: $gray_40; - @include breakpoint(md) { + @include breakpoint( md ) { margin-right: 115px; } } - - .failures { - margin-top: 1em; - color: $gray_100; - - @include breakpoint(xs) { - margin-bottom: 1em; - } - - svg { - position: absolute; - width: 1.4rem; - height: 1.4rem; - left: -60px; - } - } } button { - &:global(.components-button.has-icon) { + &:global( .components-button.has-icon ) { min-width: auto; } - - svg { - fill: $jetpack-green; - } } - .optimize-button:global(.components-button) { + .optimize-button:global( .components-button ) { &:disabled { background: transparent; - opacity: .5; + opacity: 0.5; } } } diff --git a/projects/plugins/boost/app/assets/src/js/features/lcp/status/status.tsx b/projects/plugins/boost/app/assets/src/js/features/lcp/status/status.tsx new file mode 100644 index 0000000000000..d0a2d989dd05a --- /dev/null +++ b/projects/plugins/boost/app/assets/src/js/features/lcp/status/status.tsx @@ -0,0 +1,58 @@ +import TimeAgo from '$features/critical-css/time-ago/time-ago'; +import { __ } from '@wordpress/i18n'; +import { useLcpState } from '../lib/stores/lcp-state'; +import styles from './status.module.scss'; + +const Status: React.FC = () => { + const [ query ] = useLcpState(); + const lcpState = query?.data; + + if ( lcpState?.status === 'error' ) { + return ( +
+ { __( + "An error occurred while optimizing your Cornerstone Page's LCP. Please try again.", + 'jetpack-boost' + ) } +
+ ); + } + + if ( lcpState?.status === 'not_analyzed' ) { + // This should never happen, but just in case. + return ( +
+ { __( + "Click the optimize button to start optimizing your Cornerstone Page's LCP.", + 'jetpack-boost' + ) } +
+ ); + } + + if ( lcpState?.status === 'pending' ) { + return ( +
+ { __( + "Jetpack Boost is optimizing your Cornerstone Page's LCP for you.", + 'jetpack-boost' + ) } +
+ ); + } + + if ( lcpState?.status !== 'analyzed' || ! lcpState?.updated ) { + return null; + } + + return ( + <> +
+ { __( 'Last optimized', 'jetpack-boost' ) }{ ' ' } + . +
+ + ); +}; + +export default Status; diff --git a/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-optimize-bg-image.php b/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-optimize-bg-image.php index cb7f5087aaafa..a78857cda369a 100644 --- a/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-optimize-bg-image.php +++ b/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-optimize-bg-image.php @@ -41,6 +41,11 @@ public function preload_background_images() { $selectors = array(); foreach ( $this->lcp_data as $lcp_data ) { + $lcp_optimizer = new LCP_Optimization_Util( $lcp_data ); + if ( ! $lcp_optimizer->can_optimize() ) { + continue; + } + if ( in_array( $lcp_data['selector'], $selectors, true ) ) { // If we already printed the styling for this element, skip it. continue; @@ -74,14 +79,18 @@ public function add_bg_style_override() { $selectors = array(); foreach ( $this->lcp_data as $lcp_data ) { + $lcp_optimizer = new LCP_Optimization_Util( $lcp_data ); + if ( ! $lcp_optimizer->can_optimize() ) { + continue; + } + if ( in_array( $lcp_data['selector'], $selectors, true ) ) { // If we already printed the styling for this element, skip it. continue; } $selectors[] = $lcp_data['selector']; - $lcp_optimizer = new LCP_Optimization_Util( $lcp_data ); - $image_url = $lcp_optimizer->get_lcp_image_url(); + $image_url = $lcp_optimizer->get_lcp_image_url(); if ( empty( $image_url ) ) { continue; } diff --git a/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-state.php b/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-state.php index 669c48241902a..f353e53fe2987 100644 --- a/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-state.php +++ b/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp-state.php @@ -112,6 +112,24 @@ public function set_page_success( $page_key ) { $page_key, array( 'status' => self::PAGE_STATES['success'], + 'errors' => null, + ) + ); + } + + /** + * Signifies that the page was not optimized for reason(s) in $errors. + * + * @param string $page_key The page key. + * @param array $errors The errors to set for the page. + * @return bool|\WP_Error True on success, WP_Error on failure. + */ + public function set_page_errors( $page_key, $errors ) { + return $this->update_page_state( + $page_key, + array( + 'status' => self::PAGE_STATES['error'], + 'errors' => $errors, ) ); } diff --git a/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp.php b/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp.php index a623743a7fa03..5033b68861af4 100644 --- a/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp.php +++ b/projects/plugins/boost/app/modules/optimizations/lcp/class-lcp.php @@ -158,6 +158,13 @@ public function register_data_sync( $instance ) { 'key' => Schema::as_string(), 'url' => Schema::as_string(), 'status' => Schema::as_string(), + 'errors' => Schema::as_array( + Schema::as_assoc_array( + array( + 'message' => Schema::as_string(), + ) + ) + )->nullable(), ) ) ), diff --git a/projects/plugins/boost/app/rest-api/endpoints/class-update-lcp.php b/projects/plugins/boost/app/rest-api/endpoints/class-update-lcp.php index 0636a9a8c8b2e..471796de1df42 100644 --- a/projects/plugins/boost/app/rest-api/endpoints/class-update-lcp.php +++ b/projects/plugins/boost/app/rest-api/endpoints/class-update-lcp.php @@ -63,12 +63,23 @@ public function response( $request ) { return $api_successful; } - // @TODO: handle bad payload coming from the Cloud. - // Update each page. foreach ( $pages as $entry ) { - // Mark the page as successfully analyzed as we don't know what to do if mobile fails but desktop succeeds. - $state->set_page_success( $entry['key'] ); + if ( $entry['success'] ) { + $state->set_page_success( $entry['key'] ); + } else { + $errors = array(); + foreach ( $entry['reports'] as $report ) { + if ( false === $report['success'] && ! empty( $report['message'] ) ) { + $errors[] = array( + // @TODO: Add a type and meta here (and the Cloud) after Beta release to further explain the error. + 'message' => $report['message'] ?? __( 'An unknown error occurred', 'jetpack-boost' ), + ); + } + } + + $state->set_page_errors( $entry['key'], $errors ); + } // Store the LCP data for this page. $storage->store_lcp( $entry['key'], $entry['reports'] ); diff --git a/projects/plugins/boost/changelog/add-boost-lcp-error-messaging b/projects/plugins/boost/changelog/add-boost-lcp-error-messaging new file mode 100644 index 0000000000000..85e2e20674008 --- /dev/null +++ b/projects/plugins/boost/changelog/add-boost-lcp-error-messaging @@ -0,0 +1,5 @@ +Significance: patch +Type: added +Comment: LCP: Changes to an unreleased feature. + +