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.
+
+