);
diff --git a/assets/js/modules/pagespeed-insights/components/common/LabReportMetrics.js b/assets/js/modules/pagespeed-insights/components/common/LabReportMetrics.js
index 3d0202b732f..e68e9d5d732 100644
--- a/assets/js/modules/pagespeed-insights/components/common/LabReportMetrics.js
+++ b/assets/js/modules/pagespeed-insights/components/common/LabReportMetrics.js
@@ -33,6 +33,8 @@ import ReportMetric from './ReportMetric';
import MetricsLearnMoreLink from './MetricsLearnMoreLink';
import { getScoreCategory } from '../../util';
import { CATEGORY_AVERAGE } from '../../util/constants';
+import { getReportErrorMessage } from '../../../../util/errors';
+import ReportErrorActions from '../../../../components/ReportErrorActions';
import ErrorText from '../../../../components/ErrorText';
export default function LabReportMetrics( { data, error } ) {
@@ -44,10 +46,17 @@ export default function LabReportMetrics( { data, error } ) {
data?.lighthouseResult?.audits?.[ 'total-blocking-time' ];
if ( error ) {
+ const errorMessage = getReportErrorMessage( error );
+
return (
-
);
diff --git a/assets/js/modules/pagespeed-insights/components/dashboard/DashboardPageSpeedWidget.stories.js b/assets/js/modules/pagespeed-insights/components/dashboard/DashboardPageSpeedWidget.stories.js
new file mode 100644
index 00000000000..e5ec47e458e
--- /dev/null
+++ b/assets/js/modules/pagespeed-insights/components/dashboard/DashboardPageSpeedWidget.stories.js
@@ -0,0 +1,222 @@
+/**
+ * PageSpeed Insights Module Component Stories.
+ *
+ * Site Kit by Google, Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import {
+ freezeFetch,
+ provideModules,
+ provideSiteInfo,
+} from '../../../../../../tests/js/utils';
+import { withWidgetComponentProps } from '../../../../googlesitekit/widgets/util';
+import {
+ MODULES_PAGESPEED_INSIGHTS,
+ STRATEGY_MOBILE,
+ STRATEGY_DESKTOP,
+} from '../../datastore/constants';
+import WithRegistrySetup from '../../../../../../tests/js/WithRegistrySetup';
+import DashboardPageSpeedWidget from './DashboardPageSpeedWidget';
+import * as fixtures from '../../datastore/__fixtures__';
+
+const url = fixtures.pagespeedMobile.loadingExperience.id;
+
+const WidgetWithComponentProps = withWidgetComponentProps( 'widget-slug' )(
+ DashboardPageSpeedWidget
+);
+
+const Template = ( { setupRegistry, ...args } ) => (
+
+
+
+);
+
+export const Ready = Template.bind( {} );
+Ready.storyName = 'Ready';
+Ready.args = {
+ setupRegistry: ( registry ) => {
+ const { dispatch } = registry;
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedMobile,
+ {
+ url,
+ strategy: STRATEGY_MOBILE,
+ }
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_MOBILE,
+ ] );
+
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedDesktop,
+ {
+ url,
+ strategy: STRATEGY_DESKTOP,
+ }
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_DESKTOP,
+ ] );
+ },
+};
+
+export const Loading = Template.bind( {} );
+Loading.storyName = 'Loading';
+Loading.args = {
+ setupRegistry: ( { dispatch } ) => {
+ freezeFetch(
+ /^\/google-site-kit\/v1\/modules\/pagespeed-insights\/data\/pagespeed/
+ );
+
+ // Component will be loading as long as both reports are not present.
+ // Omit receiving mobile here to trigger the request only once.
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedDesktop,
+ {
+ url,
+ strategy: STRATEGY_DESKTOP,
+ }
+ );
+ },
+};
+
+export const NoRecommendations = Template.bind( {} );
+NoRecommendations.storyName = 'No Recommendations';
+NoRecommendations.args = {
+ setupRegistry: ( { dispatch } ) => {
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedMobileNoStackPacks,
+ {
+ url,
+ strategy: STRATEGY_MOBILE,
+ }
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_MOBILE,
+ ] );
+
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedDesktopNoStackPacks,
+ {
+ url,
+ strategy: STRATEGY_DESKTOP,
+ }
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_DESKTOP,
+ ] );
+ },
+};
+
+export const FieldDataUnavailable = Template.bind( {} );
+FieldDataUnavailable.storyName = 'Field Data Unavailable';
+FieldDataUnavailable.args = {
+ setupRegistry: ( { dispatch } ) => {
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedMobileNoFieldData,
+ {
+ url,
+ strategy: STRATEGY_MOBILE,
+ }
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_MOBILE,
+ ] );
+
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
+ fixtures.pagespeedDesktopNoFieldData,
+ {
+ url,
+ strategy: STRATEGY_DESKTOP,
+ }
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_DESKTOP,
+ ] );
+ },
+};
+
+export const Error = Template.bind( {} );
+Error.storyName = 'Errors for Mobile and Desktop';
+Error.args = {
+ setupRegistry: ( { dispatch } ) => {
+ const mobileError = {
+ code: 'fetching_mobile_data_failed',
+ message:
+ 'Fetching PageSpeed Insights report with strategy mobile failed.',
+ };
+ const desktopError = {
+ code: 'fetching_desktop_data_failed',
+ message:
+ 'Fetching PageSpeed Insights report with strategy desktop failed.',
+ };
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveError(
+ mobileError,
+ 'getReport',
+ [ url, STRATEGY_MOBILE ]
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_MOBILE,
+ ] );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveError(
+ desktopError,
+ 'getReport',
+ [ url, STRATEGY_DESKTOP ]
+ );
+ dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution( 'getReport', [
+ url,
+ STRATEGY_DESKTOP,
+ ] );
+ },
+};
+
+export default {
+ title: 'Modules/PageSpeed Insights/Widgets/DashboardPageSpeedWidget',
+ decorators: [
+ ( Story, { args } ) => {
+ const setupRegistry = ( registry ) => {
+ provideSiteInfo( registry, {
+ referenceSiteURL: url,
+ } );
+ provideModules( registry, [
+ {
+ slug: 'pagespeed-insights',
+ active: true,
+ connected: true,
+ },
+ ] );
+
+ // Call story-specific setup.
+ args.setupRegistry( registry );
+ };
+
+ return (
+
+
+
+ );
+ },
+ ],
+};
diff --git a/assets/js/util/errors.js b/assets/js/util/errors.js
index afb05f4860a..9ea35b54e0a 100644
--- a/assets/js/util/errors.js
+++ b/assets/js/util/errors.js
@@ -21,10 +21,17 @@
*/
import isPlainObject from 'lodash/isPlainObject';
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
// Error codes and reasons.
export const ERROR_CODE_MISSING_REQUIRED_SCOPE = 'missing_required_scopes'; // When scopes are missing.
export const ERROR_REASON_INSUFFICIENT_PERMISSIONS = 'insufficientPermissions';
export const ERROR_REASON_FORBIDDEN = 'forbidden';
+export const ERROR_INTERNAL_SERVER_ERROR = 'internal_server_error';
+export const ERROR_INVALID_JSON = 'invalid_json';
/**
* Checks if the provided object is an instance of WP_Error class.
@@ -103,3 +110,27 @@ export function isErrorRetryable( error, selectorData ) {
! isAuthError( error )
);
}
+
+/**
+ * Sets the error message for specific error codes.
+ *
+ * @since n.e.x.t
+ *
+ * @param {Object} error The error object to check.
+ * @return {Object} The updated error object.
+ */
+export function getReportErrorMessage( error ) {
+ if ( error?.code === ERROR_INTERNAL_SERVER_ERROR ) {
+ return __(
+ 'There was a critical error on this website while fetching data.',
+ 'google-site-kit'
+ );
+ } else if ( error?.code === ERROR_INVALID_JSON ) {
+ return __(
+ 'The server provided an invalid response.',
+ 'google-site-kit'
+ );
+ }
+
+ return error?.message;
+}
diff --git a/assets/js/util/errors.test.js b/assets/js/util/errors.test.js
index e4e1f98732a..fdbadae645c 100644
--- a/assets/js/util/errors.test.js
+++ b/assets/js/util/errors.test.js
@@ -26,8 +26,11 @@ import {
ERROR_CODE_MISSING_REQUIRED_SCOPE,
ERROR_REASON_INSUFFICIENT_PERMISSIONS,
ERROR_REASON_FORBIDDEN,
+ ERROR_INTERNAL_SERVER_ERROR,
+ ERROR_INVALID_JSON,
isAuthError,
isErrorRetryable,
+ getReportErrorMessage,
} from './errors';
describe( 'Error Utilities', () => {
@@ -230,4 +233,31 @@ describe( 'Error Utilities', () => {
).toBe( true );
} );
} );
+
+ describe( 'getReportErrorMessage', () => {
+ describe.each( [
+ [
+ 'return the same error message when error code is not internal_server_error or invalid_json',
+ { code: 'some-error', message: 'Not found' },
+ 'Not found',
+ ],
+ [
+ 'return the appropriate error message when error code is internal_server_error',
+ {
+ code: ERROR_INTERNAL_SERVER_ERROR,
+ message: 'Internal server error',
+ },
+ 'There was a critical error on this website while fetching data.',
+ ],
+ [
+ 'return the appropriate error message when error code is invalid_json',
+ { code: ERROR_INVALID_JSON, message: 'Invalid JSON' },
+ 'The server provided an invalid response.',
+ ],
+ ] )( '%s', ( label, error, message ) => {
+ it( `should ${ label }`, () => {
+ expect( getReportErrorMessage( error ) ).toEqual( message );
+ } );
+ } );
+ } );
} );
diff --git a/assets/sass/components/dashboard/_googlesitekit-DashboardPageSpeed.scss b/assets/sass/components/dashboard/_googlesitekit-DashboardPageSpeed.scss
index bab936c9cbf..c1a8e66d4a6 100644
--- a/assets/sass/components/dashboard/_googlesitekit-DashboardPageSpeed.scss
+++ b/assets/sass/components/dashboard/_googlesitekit-DashboardPageSpeed.scss
@@ -198,6 +198,16 @@
}
}
+ .googlesitekit-pagespeed-report__row--error {
+ align-items: flex-start;
+ flex-direction: column;
+ padding: 12px $grid-gap-phone 8px $grid-gap-phone;
+
+ @media (min-width: $bp-tablet) {
+ padding: 18px $grid-gap-desktop 12px $grid-gap-desktop;
+ }
+ }
+
.googlesitekit-pagespeed--recommendations {
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
display: flex;
diff --git a/assets/sass/components/global/_googlesitekit-error-text.scss b/assets/sass/components/global/_googlesitekit-error-text.scss
index 0becb4486c4..3d327a90099 100644
--- a/assets/sass/components/global/_googlesitekit-error-text.scss
+++ b/assets/sass/components/global/_googlesitekit-error-text.scss
@@ -26,7 +26,7 @@
margin-left: 1rem;
}
-.googlesitekit-error-cta-wrapper {
+.googlesitekit-report-error-actions {
align-items: center;
display: flex;
gap: 1rem;
diff --git a/stories/module-pagespeed-insights-components.stories.js b/stories/module-pagespeed-insights-components.stories.js
deleted file mode 100644
index e45f6d22ff2..00000000000
--- a/stories/module-pagespeed-insights-components.stories.js
+++ /dev/null
@@ -1,262 +0,0 @@
-/**
- * PageSpeed Insights Module Component Stories.
- *
- * Site Kit by Google, Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * External dependencies
- */
-import { storiesOf } from '@storybook/react';
-
-/**
- * Internal dependencies
- */
-import DashboardPageSpeedWidget from '../assets/js/modules/pagespeed-insights/components/dashboard/DashboardPageSpeedWidget';
-import * as fixtures from '../assets/js/modules/pagespeed-insights/datastore/__fixtures__';
-import {
- MODULES_PAGESPEED_INSIGHTS,
- STRATEGY_MOBILE,
- STRATEGY_DESKTOP,
-} from '../assets/js/modules/pagespeed-insights/datastore/constants';
-import {
- WithTestRegistry,
- freezeFetch,
- provideSiteInfo,
- provideModules,
-} from '../tests/js/utils';
-import { getWidgetComponentProps } from '../assets/js/googlesitekit/widgets/util';
-
-const widgetComponentProps = getWidgetComponentProps( 'dashboardPageSpeed' );
-
-storiesOf( 'PageSpeed Insights Module/Components', module )
- .add( 'Dashboard widget', () => {
- const url = fixtures.pagespeedMobile.loadingExperience.id;
- const setupRegistry = ( registry ) => {
- const { dispatch } = registry;
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedMobile,
- {
- url,
- strategy: STRATEGY_MOBILE,
- }
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_MOBILE ]
- );
-
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedDesktop,
- {
- url,
- strategy: STRATEGY_DESKTOP,
- }
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_DESKTOP ]
- );
-
- provideSiteInfo( registry, {
- referenceSiteURL: url,
- } );
- provideModules( registry, [
- {
- slug: 'pagespeed-insights',
- active: true,
- connected: true,
- },
- ] );
- };
-
- return (
-
-
-
- );
- } )
- .add( 'Dashboard widget (loading)', () => {
- freezeFetch(
- /^\/google-site-kit\/v1\/modules\/pagespeed-insights\/data\/pagespeed/
- );
- const url = fixtures.pagespeedMobile.loadingExperience.id;
- const setupRegistry = ( registry ) => {
- const { dispatch } = registry;
- // Component will be loading as long as both reports are not present.
- // Omit receiving mobile here to trigger the request only once.
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedDesktop,
- {
- url,
- strategy: STRATEGY_DESKTOP,
- }
- );
- provideSiteInfo( registry, {
- referenceSiteURL: url,
- } );
- provideModules( registry, [
- {
- slug: 'pagespeed-insights',
- active: true,
- connected: true,
- },
- ] );
- };
- return (
-
-
-
- );
- } )
- .add( 'Dashboard widget (No Recommendations)', () => {
- const url = fixtures.pagespeedMobile.loadingExperience.id;
- const setupRegistry = ( registry ) => {
- const { dispatch } = registry;
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedMobileNoStackPacks,
- {
- url,
- strategy: STRATEGY_MOBILE,
- }
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_MOBILE ]
- );
-
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedDesktopNoStackPacks,
- {
- url,
- strategy: STRATEGY_DESKTOP,
- }
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_DESKTOP ]
- );
-
- provideSiteInfo( registry, {
- referenceSiteURL: url,
- } );
- provideModules( registry, [
- {
- slug: 'pagespeed-insights',
- active: true,
- connected: true,
- },
- ] );
- };
- return (
-
-
-
- );
- } )
- .add( 'Dashboard widget (Field Data Unavailable)', () => {
- const url = fixtures.pagespeedMobile.loadingExperience.id;
- const setupRegistry = ( registry ) => {
- const { dispatch } = registry;
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedMobileNoFieldData,
- {
- url,
- strategy: STRATEGY_MOBILE,
- }
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_MOBILE ]
- );
-
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveGetReport(
- fixtures.pagespeedDesktopNoFieldData,
- {
- url,
- strategy: STRATEGY_DESKTOP,
- }
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_DESKTOP ]
- );
-
- provideSiteInfo( registry, {
- referenceSiteURL: url,
- } );
- provideModules( registry, [
- {
- slug: 'pagespeed-insights',
- active: true,
- connected: true,
- },
- ] );
- };
- return (
-
-
-
- );
- } )
- .add( 'Dashboard widget (Errors for Mobile and Desktop)', () => {
- const url = fixtures.pagespeedMobile.loadingExperience.id;
- const setupRegistry = ( registry ) => {
- const { dispatch } = registry;
- const mobileError = {
- code: 'fetching_mobile_data_failed',
- message:
- 'Fetching PageSpeed Insights report with strategy mobile failed.',
- };
- const desktopError = {
- code: 'fetching_desktop_data_failed',
- message:
- 'Fetching PageSpeed Insights report with strategy desktop failed.',
- };
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveError(
- mobileError,
- 'getReport',
- [ url, STRATEGY_MOBILE ]
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_MOBILE ]
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).receiveError(
- desktopError,
- 'getReport',
- [ url, STRATEGY_DESKTOP ]
- );
- dispatch( MODULES_PAGESPEED_INSIGHTS ).finishResolution(
- 'getReport',
- [ url, STRATEGY_DESKTOP ]
- );
- provideSiteInfo( registry, {
- referenceSiteURL: url,
- } );
- provideModules( registry, [
- {
- slug: 'pagespeed-insights',
- active: true,
- connected: true,
- },
- ] );
- };
- return (
-
-
-
- );
- } );