diff --git a/projects/packages/premium-analytics/changelog/change-uni-846-report-error-notice b/projects/packages/premium-analytics/changelog/change-uni-846-report-error-notice
new file mode 100644
index 000000000000..470193703d3c
--- /dev/null
+++ b/projects/packages/premium-analytics/changelog/change-uni-846-report-error-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Show report load errors in a notice across the report, without Retry when access is denied. Show access denied on author, post, and video details as an error.
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/__tests__/describe-detail-page-error.test.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/__tests__/describe-detail-page-error.test.ts
deleted file mode 100644
index eb41b90890c1..000000000000
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/__tests__/describe-detail-page-error.test.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * External dependencies
- */
-import { StatsResponseShapeError } from '@jetpack-premium-analytics/data';
-/**
- * Internal dependencies
- */
-import { describeDetailPageError } from '../describe-detail-page-error';
-
-const RETRY_DESCRIPTION = "We couldn't load this video. Please try again in a moment.";
-
-describe( 'describeDetailPageError', () => {
- it( 'marks an unusable response as an error', () => {
- expect(
- describeDetailPageError( new StatsResponseShapeError( 'bad shape' ), {
- retryDescription: RETRY_DESCRIPTION,
- onRetry: jest.fn(),
- } )
- ).toEqual( {
- intent: 'error',
- description: 'This data is unavailable right now.',
- } );
- } );
-
- it( 'marks a 403 as info, without actions', () => {
- expect(
- describeDetailPageError(
- { error: 'unauthorized', status: 403 },
- { retryDescription: RETRY_DESCRIPTION, onRetry: jest.fn() }
- )
- ).toEqual( {
- intent: 'info',
- description: "You don't have access to this data.",
- } );
- } );
-
- it.each( [
- [ 'a no_connection 403', { error: 'no_connection', status: 403 } ],
- [ 'a generic failure', { status: 500 } ],
- ] )( 'marks %s as a retryable error', ( _, error ) => {
- const onRetry = jest.fn();
-
- expect(
- describeDetailPageError( error, { retryDescription: RETRY_DESCRIPTION, onRetry } )
- ).toEqual( {
- intent: 'error',
- description: RETRY_DESCRIPTION,
- actions: [ { label: 'Retry', onClick: onRetry } ],
- } );
- } );
-} );
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/describe-detail-page-error.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/describe-detail-page-error.ts
deleted file mode 100644
index 277f7499d696..000000000000
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/describe-detail-page-error.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * External dependencies
- */
-import { isAccessDenied } from '@jetpack-premium-analytics/data';
-/**
- * Internal dependencies
- */
-import { describeError, type DescribeErrorOptions } from '../../helpers/describe-error';
-import type { DetailPageNoticeProps } from './detail-page-notice';
-
-/**
- * `describeError()` plus the intent a `DetailPageNotice` needs.
- *
- * @param error - The failed query error.
- * @param options - Error-state copy and retry options, as `describeError()` takes them.
- * @return The detail page notice props.
- */
-export function describeDetailPageError(
- error: unknown,
- options: DescribeErrorOptions
-): DetailPageNoticeProps {
- return {
- ...describeError( error, options ),
- intent: isAccessDenied( error ) ? 'info' : 'error',
- };
-}
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/index.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/index.ts
index 895ef331145b..d9b25b3c8905 100644
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/index.ts
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/index.ts
@@ -7,8 +7,6 @@ export {
type DetailPageSectionProps,
} from './detail-page-layout';
export { DetailPageEmptyState, type DetailPageEmptyStateProps } from './detail-page-empty-state';
-export { DetailPageNotice, type DetailPageNoticeProps } from './detail-page-notice';
-export { describeDetailPageError } from './describe-detail-page-error';
export { DetailPageShell, type DetailPageShellProps } from './detail-page-shell';
export {
DetailPageActions,
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/index.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/index.ts
index 87251aff3c39..c3f4cad56e81 100644
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/index.ts
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/index.ts
@@ -138,7 +138,6 @@ export {
export {
ReportChartSection,
ReportDrilldownTable,
- ReportErrorState,
ReportLocationsMap,
ReportPageLayout,
ReportPageSection,
@@ -156,7 +155,6 @@ export {
type ReportChartMetric,
type ReportChartSectionProps,
type ReportDrilldownTableProps,
- type ReportErrorStateProps,
type ReportLocationsMapProps,
type ReportPageLayoutProps,
type ReportPageSectionProps,
@@ -174,20 +172,18 @@ export {
DETAIL_HEADER_GLYPH_SIZE,
DetailPageEmptyState,
DetailPageLayout,
- DetailPageNotice,
DetailPageSection,
DetailPageShell,
- describeDetailPageError,
useDetailPageCustomize,
type DetailPageActionsProps,
type DetailPageBreadcrumbsProps,
type DetailPageCustomize,
type DetailPageHeaderSlots,
type DetailPageLayoutProps,
- type DetailPageNoticeProps,
type DetailPageSectionProps,
type DetailPageShellProps,
} from './detail-page';
+export { PageNotice, type PageNoticeProps } from './page-notice';
export {
FeedbackModal,
PageOptionsMenu,
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/__tests__/page-notice.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/__tests__/page-notice.test.tsx
new file mode 100644
index 000000000000..417591bddec4
--- /dev/null
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/__tests__/page-notice.test.tsx
@@ -0,0 +1,20 @@
+/**
+ * External dependencies
+ */
+import { render } from '@testing-library/react';
+/**
+ * Internal dependencies
+ */
+import { getNoticeAnnouncement } from '../../../../../../tests/js/notice-test-utils';
+import { PageNotice } from '../page-notice';
+
+describe( 'PageNotice', () => {
+ it.each( [
+ [ 'assertively by default', undefined, 'assertive' ],
+ [ 'politely for info', 'info', 'polite' ],
+ ] as const )( 'announces %s', ( _, intent, politeness ) => {
+ render( );
+
+ expect( getNoticeAnnouncement( 'Something happened.', politeness ) ).toBeInTheDocument();
+ } );
+} );
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/index.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/index.ts
new file mode 100644
index 000000000000..9319e99352e4
--- /dev/null
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/index.ts
@@ -0,0 +1 @@
+export { PageNotice, type PageNoticeProps } from './page-notice';
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/detail-page-notice.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/page-notice.tsx
similarity index 60%
rename from projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/detail-page-notice.tsx
rename to projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/page-notice.tsx
index edee1fed2e9c..831858eb7379 100644
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/detail-page/detail-page-notice.tsx
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/page-notice/page-notice.tsx
@@ -8,20 +8,21 @@ import { Notice } from '@jetpack-premium-analytics/externals';
import type { WidgetStateError } from '../widget-state';
import type { ReactElement } from 'react';
-export interface DetailPageNoticeProps extends Pick< WidgetStateError, 'description' | 'actions' > {
- /** `error` when something failed; `info` when the request answered and the answer is a fact, such as no access. */
- intent: 'error' | 'info';
+export interface PageNoticeProps extends Pick< WidgetStateError, 'description' | 'actions' > {
+ /** `info` for a fact rather than a fault, such as a missing item; failures, access denied included, are an `error`. */
+ intent?: 'error' | 'info';
/** A way out of the page, such as back to its report; `render` is a childless router link, as its children would replace `label`. */
link?: { label: string; render: ReactElement };
}
/**
- * Stand in for a detail page's widgets when the page's subject failed to load or does not exist.
+ * Stand in for a report's sections or a detail page's widgets when their data failed to load or does not exist.
+ * Replace the sections rather than sit beside them: report tables pick their empty state from row count, not fetch status.
*
- * @param {DetailPageNoticeProps} props - The component props.
- * @return The detail page notice.
+ * @param {PageNoticeProps} props - The component props.
+ * @return The page notice.
*/
-export function DetailPageNotice( { intent, description, actions, link }: DetailPageNoticeProps ) {
+export function PageNotice( { intent = 'error', description, actions, link }: PageNoticeProps ) {
return (
// The default announcement (children) would trail the action labels.
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-error-state.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-error-state.test.tsx
deleted file mode 100644
index 0e8f2751347a..000000000000
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-error-state.test.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * External dependencies
- */
-import { fireEvent, render, screen } from '@testing-library/react';
-/**
- * Internal dependencies
- */
-import { ReportErrorState } from '../report-error-state';
-
-describe( 'ReportErrorState', () => {
- it( 'renders the given title', () => {
- render( );
-
- expect( screen.getByText( 'Unable to load posts' ) ).toBeInTheDocument();
- } );
-
- it( 'renders a custom description', () => {
- render(
-
- );
-
- expect( screen.getByText( 'The posts report is unavailable.' ) ).toBeInTheDocument();
- } );
-
- it( 'renders the default description', () => {
- render( );
-
- expect(
- screen.getByText( "We couldn't load this data. Please try again in a moment." )
- ).toBeInTheDocument();
- } );
-
- it( 'calls onRetry when Retry is clicked', () => {
- const onRetry = jest.fn();
- render( );
-
- // eslint-disable-next-line testing-library/prefer-user-event -- @testing-library/user-event is not a direct dependency of this package.
- fireEvent.click( screen.getByRole( 'button', { name: 'Retry' } ) );
-
- expect( onRetry ).toHaveBeenCalledTimes( 1 );
- } );
-} );
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/index.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/index.ts
index d9aaada3a1f1..eb5968b38825 100644
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/index.ts
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/index.ts
@@ -5,7 +5,6 @@ export {
type ReportPageSectionProps,
} from './report-page-layout';
export { ReportChartSection, type ReportChartSectionProps } from './report-chart-section';
-export { ReportErrorState, type ReportErrorStateProps } from './report-error-state';
export { ReportLocationsMap, type ReportLocationsMapProps } from './report-locations-map';
export { ExporterCsvAction } from './exporter-csv-action';
export { ReportCsvAction, type ReportCsvActionProps } from './report-csv-action';
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-error-state.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-error-state.tsx
deleted file mode 100644
index f16268ea1407..000000000000
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-error-state.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * External dependencies
- */
-import { Button, EmptyState } from '@jetpack-premium-analytics/externals';
-import { __ } from '@wordpress/i18n';
-/**
- * Internal dependencies
- */
-import { ReportPageSection } from './report-page-layout';
-
-export interface ReportErrorStateProps {
- title: string;
- description?: string;
- onRetry: () => void;
-}
-
-/**
- * Replace report sections with a shared error and retry state.
- *
- * The error state replaces the report sections rather than sitting beside
- * them. The report tables pick their empty state from row count, not fetch
- * status, so a failed first load would read as an empty report, and a failed
- * refetch over cached rows would leave stale data with no way to retry.
- *
- * @param {ReportErrorStateProps} props - The component props.
- * @return The report error state.
- */
-export function ReportErrorState( {
- title,
- description = __(
- "We couldn't load this data. Please try again in a moment.",
- 'jetpack-premium-analytics-pkg'
- ),
- onRetry,
-}: ReportErrorStateProps ) {
- return (
-
-
- { title }
- { description }
-
-
-
-
-
- );
-}
diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/index.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/index.ts
index 7308db133d04..e10c53bfdf9a 100644
--- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/index.ts
+++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/index.ts
@@ -122,23 +122,22 @@ export {
DETAIL_HEADER_GLYPH_SIZE,
DetailPageEmptyState,
DetailPageLayout,
- DetailPageNotice,
DetailPageSection,
DetailPageActions,
DetailPageBreadcrumbs,
DetailPageShell,
- describeDetailPageError,
useDetailPageCustomize,
type DetailPageActionsProps,
type DetailPageBreadcrumbsProps,
type DetailPageCustomize,
type DetailPageHeaderSlots,
type DetailPageLayoutProps,
- type DetailPageNoticeProps,
type DetailPageSectionProps,
type DetailPageShellProps,
FeedbackModal,
type FeedbackSource,
+ PageNotice,
+ type PageNoticeProps,
PageOptionsMenu,
type PageOptionsMenuProps,
ResetLayoutAction,
@@ -150,7 +149,6 @@ export {
type LocationsGeoRow,
ReportChartSection,
ReportDrilldownTable,
- ReportErrorState,
ReportLocationsMap,
ReportPageLayout,
ReportPageSection,
@@ -166,7 +164,6 @@ export {
type ReportChartMetric,
type ReportChartSectionProps,
type ReportDrilldownTableProps,
- type ReportErrorStateProps,
type ReportLocationsMapProps,
type ReportPageLayoutProps,
type ReportPageSectionProps,
diff --git a/projects/packages/premium-analytics/routes/author-detail/stage.test.tsx b/projects/packages/premium-analytics/routes/author-detail/stage.test.tsx
index 45f205da0b1e..ee26f5787287 100644
--- a/projects/packages/premium-analytics/routes/author-detail/stage.test.tsx
+++ b/projects/packages/premium-analytics/routes/author-detail/stage.test.tsx
@@ -332,7 +332,7 @@ describe( 'author detail stage', () => {
expect( getNoticeText( "You don't have access to this data." ) ).toBeInTheDocument();
expect(
- getNoticeAnnouncement( "You don't have access to this data.", 'polite' )
+ getNoticeAnnouncement( "You don't have access to this data.", 'assertive' )
).toBeInTheDocument();
expect( screen.queryByRole( 'button', { name: 'Retry' } ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/author-detail/stage.tsx b/projects/packages/premium-analytics/routes/author-detail/stage.tsx
index dad0424e095b..4703041ee8dc 100644
--- a/projects/packages/premium-analytics/routes/author-detail/stage.tsx
+++ b/projects/packages/premium-analytics/routes/author-detail/stage.tsx
@@ -17,14 +17,14 @@ import {
DetailPageActions,
DetailPageBreadcrumbs,
DetailPageLayout,
- DetailPageNotice,
+ PageNotice,
DetailPageSection,
DetailPageShell,
- describeDetailPageError,
+ describeError,
useDetailPageCustomize,
useStoredDetailLayout,
useTrackedDateRangeApply,
- type DetailPageNoticeProps,
+ type PageNoticeProps,
} from '@jetpack-premium-analytics/widgets-toolkit';
import { useCallback, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
@@ -143,7 +143,7 @@ function AuthorDetail(): JSX.Element {
[ search ]
);
- let notice: DetailPageNoticeProps | null = null;
+ let notice: PageNoticeProps | null = null;
if ( summary.isError ) {
// Same split as the widgets, plus a 404: sites that hide the users
@@ -157,7 +157,7 @@ function AuthorDetail(): JSX.Element {
'jetpack-premium-analytics-pkg'
),
}
- : describeDetailPageError( summary.error, {
+ : describeError( summary.error, {
retryDescription: __(
"We couldn't load this author. Please try again in a moment.",
'jetpack-premium-analytics-pkg'
@@ -230,7 +230,7 @@ function AuthorDetail(): JSX.Element {
) : null }
{ notice ? (
-
+
) : null }
diff --git a/projects/packages/premium-analytics/routes/post-detail/stage.test.tsx b/projects/packages/premium-analytics/routes/post-detail/stage.test.tsx
index 69d8f818a1ed..ba94d9eef071 100644
--- a/projects/packages/premium-analytics/routes/post-detail/stage.test.tsx
+++ b/projects/packages/premium-analytics/routes/post-detail/stage.test.tsx
@@ -740,7 +740,7 @@ describe( 'post detail stage on the provisional all-time window', () => {
expect( refetch ).toHaveBeenCalledTimes( 1 );
} );
- it( 'offers no Retry when the publish day is denied, as an info notice', () => {
+ it( 'offers no Retry when the publish day is denied', () => {
mockSummary( {
isError: true,
error: { code: 'rest_forbidden', status: 403 },
@@ -751,7 +751,7 @@ describe( 'post detail stage on the provisional all-time window', () => {
expect( getNoticeText( "You don't have access to this data." ) ).toBeInTheDocument();
expect(
- getNoticeAnnouncement( "You don't have access to this data.", 'polite' )
+ getNoticeAnnouncement( "You don't have access to this data.", 'assertive' )
).toBeInTheDocument();
expect( screen.queryByRole( 'button', { name: 'Retry' } ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/post-detail/stage.tsx b/projects/packages/premium-analytics/routes/post-detail/stage.tsx
index 0dc6b4072376..232163bd8d72 100644
--- a/projects/packages/premium-analytics/routes/post-detail/stage.tsx
+++ b/projects/packages/premium-analytics/routes/post-detail/stage.tsx
@@ -21,10 +21,10 @@ import {
DetailPageBreadcrumbs,
DetailPageEmptyState,
DetailPageLayout,
- DetailPageNotice,
+ PageNotice,
DetailPageSection,
DetailPageShell,
- describeDetailPageError,
+ describeError,
useDetailPageCustomize,
useStoredDetailLayout,
useTrackedDateRangeApply,
@@ -182,8 +182,8 @@ function PostDetail(): JSX.Element {
const anchorErrorNotice =
! isEmailTab && isAnchoringAllTime && summary.isError ? (
- {
render( );
- expect( screen.getByText( 'Unable to load annual insights' ) ).toBeInTheDocument();
+ expect(
+ getNoticeText( "We couldn't load annual insights. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( screen.getByRole( 'button', { name: 'Retry' } ) ).toBeInTheDocument();
expect( screen.queryByText( '2026' ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/annual-insights/page.tsx b/projects/packages/premium-analytics/routes/reports/annual-insights/page.tsx
index 8e5ac08ce04f..2556c09b38b3 100644
--- a/projects/packages/premium-analytics/routes/reports/annual-insights/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/annual-insights/page.tsx
@@ -4,7 +4,8 @@
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ExporterCsvAction,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportRecordsTable,
@@ -78,9 +79,14 @@ function AnnualInsightsReport(): JSX.Element {
>
{ records.isError ? (
-
) : (
diff --git a/projects/packages/premium-analytics/routes/reports/authors/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/authors/config/use-report-records.ts
index 65ca39b4419e..3074e28c098d 100644
--- a/projects/packages/premium-analytics/routes/reports/authors/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/authors/config/use-report-records.ts
@@ -31,6 +31,7 @@ export function useAuthorsReportRecords( reportParams: ReportParams ) {
return {
isError: authors.isError,
+ error: authors.error,
refetch: authors.refetch,
rows,
hasComparison: authors.hasComparison,
diff --git a/projects/packages/premium-analytics/routes/reports/authors/page.test.tsx b/projects/packages/premium-analytics/routes/reports/authors/page.test.tsx
index 707ee6d85f7e..b62069ef0a6f 100644
--- a/projects/packages/premium-analytics/routes/reports/authors/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/authors/page.test.tsx
@@ -9,6 +9,7 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { useAuthorsReportRecords } from './config';
import AuthorsReportPage from './page';
import type { AuthorRow } from '@jetpack-premium-analytics/widgets-toolkit';
@@ -114,7 +115,9 @@ describe( 'AuthorsReportPage', () => {
render( );
- expect( screen.getByText( 'Unable to load authors' ) ).toBeInTheDocument();
+ expect(
+ getNoticeText( "We couldn't load authors. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( screen.getByRole( 'button', { name: 'Retry' } ) ).toBeInTheDocument();
expect( screen.queryByText( 'Ada Lovelace' ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/authors/page.tsx b/projects/packages/premium-analytics/routes/reports/authors/page.tsx
index 161fe171555a..685930c8f3b1 100644
--- a/projects/packages/premium-analytics/routes/reports/authors/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/authors/page.tsx
@@ -6,7 +6,8 @@ import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ReportDrilldownTable,
ExporterCsvAction,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
authorsCsvExporter,
@@ -78,9 +79,14 @@ function AuthorsReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/clicks/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/clicks/config/use-report-records.ts
index d036cc967a0d..09cbf0b5a7cf 100644
--- a/projects/packages/premium-analytics/routes/reports/clicks/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/clicks/config/use-report-records.ts
@@ -28,6 +28,7 @@ export function useClicksReportRecords( reportParams: ReportParams ) {
return {
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
rows,
hasComparison: report.hasComparison,
diff --git a/projects/packages/premium-analytics/routes/reports/clicks/page.test.tsx b/projects/packages/premium-analytics/routes/reports/clicks/page.test.tsx
index 5315518a8252..feccaf1c452f 100644
--- a/projects/packages/premium-analytics/routes/reports/clicks/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/clicks/page.test.tsx
@@ -31,7 +31,7 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( {
jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
ExporterCsvAction: () => null,
ReportDrilldownTable: jest.fn( () => null ),
- ReportErrorState: () => null,
+ PageNotice: () => null,
ReportPageLayout: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageShell: ( { children }: { children: ReactNode } ) => <>{ children }>,
useReportRetry: ( refetch: () => unknown ) => refetch,
diff --git a/projects/packages/premium-analytics/routes/reports/clicks/page.tsx b/projects/packages/premium-analytics/routes/reports/clicks/page.tsx
index ceca82d1805f..6f380af097d5 100644
--- a/projects/packages/premium-analytics/routes/reports/clicks/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/clicks/page.tsx
@@ -5,7 +5,8 @@ import { useReportDateFilters } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ReportDrilldownTable,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ExporterCsvAction,
@@ -83,9 +84,14 @@ function ClicksReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/comment-followers/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/comment-followers/config/use-report-records.ts
index 21e05b107684..557674393f73 100644
--- a/projects/packages/premium-analytics/routes/reports/comment-followers/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/comment-followers/config/use-report-records.ts
@@ -37,6 +37,7 @@ export function useCommentFollowersReportRecords() {
isLoading: report.isLoading,
isFetching: report.isFetching,
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/comment-followers/page.test.tsx b/projects/packages/premium-analytics/routes/reports/comment-followers/page.test.tsx
index 22b1a88e946f..5234799dce5f 100644
--- a/projects/packages/premium-analytics/routes/reports/comment-followers/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/comment-followers/page.test.tsx
@@ -5,6 +5,7 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { useCommentFollowersReportRecords } from './config';
import CommentFollowersReportPage from './page';
import type { ReactNode } from 'react';
@@ -65,7 +66,9 @@ describe( 'CommentFollowersReportPage', () => {
render( );
- expect( screen.getByText( 'Unable to load subscribers' ) ).toBeInTheDocument();
+ expect(
+ getNoticeText( "We couldn't load subscribers. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( screen.getByRole( 'button', { name: 'Retry' } ) ).toBeInTheDocument();
// The stale row must not stay on screen behind the error.
expect( screen.queryByText( 'Hello world' ) ).not.toBeInTheDocument();
@@ -82,7 +85,7 @@ describe( 'CommentFollowersReportPage', () => {
render( );
expect( screen.getByText( 'All Posts' ) ).toBeInTheDocument();
- expect( screen.queryByText( 'Unable to load subscribers' ) ).not.toBeInTheDocument();
+ expect( screen.queryByRole( 'button', { name: 'Retry' } ) ).not.toBeInTheDocument();
} );
it( 'hides the All Posts summary when there are no subscribers', () => {
diff --git a/projects/packages/premium-analytics/routes/reports/comment-followers/page.tsx b/projects/packages/premium-analytics/routes/reports/comment-followers/page.tsx
index bd24884a5844..126f0f4823dc 100644
--- a/projects/packages/premium-analytics/routes/reports/comment-followers/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/comment-followers/page.tsx
@@ -6,7 +6,8 @@ import { Text } from '@jetpack-premium-analytics/externals';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
MetricValue,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageSection,
ReportPageShell,
@@ -102,9 +103,14 @@ function CommentFollowersReport(): JSX.Element {
>
{ records.isError ? (
-
) : (
<>
diff --git a/projects/packages/premium-analytics/routes/reports/comments/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/comments/config/use-report-records.ts
index 897d9e2cb554..31cc8a34cf2a 100644
--- a/projects/packages/premium-analytics/routes/reports/comments/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/comments/config/use-report-records.ts
@@ -34,6 +34,7 @@ export function useCommentsReportRecords( activeTab: CommentsReportTabId ) {
isLoading: report.isLoading,
isFetching: report.isFetching,
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/comments/page.test.tsx b/projects/packages/premium-analytics/routes/reports/comments/page.test.tsx
index 2ab033e1d921..e858b2126b12 100644
--- a/projects/packages/premium-analytics/routes/reports/comments/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/comments/page.test.tsx
@@ -5,6 +5,7 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { useCommentsReportRecords } from './config';
import CommentsReportPage from './page';
@@ -73,7 +74,9 @@ describe( 'CommentsReportPage', () => {
render( );
- expect( screen.getByText( 'Unable to load comments' ) ).toBeInTheDocument();
+ expect(
+ getNoticeText( "We couldn't load comments. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( screen.getByRole( 'button', { name: 'Retry' } ) ).toBeInTheDocument();
expect( screen.queryByText( 'Hello world' ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/comments/page.tsx b/projects/packages/premium-analytics/routes/reports/comments/page.tsx
index 9e506dfe80b9..4521f020bbf0 100644
--- a/projects/packages/premium-analytics/routes/reports/comments/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/comments/page.tsx
@@ -5,7 +5,8 @@ import { useSectionTab } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ExporterCsvAction,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportPageTabs,
@@ -87,9 +88,14 @@ function CommentsReport(): JSX.Element {
tabs={ }
>
{ records.isError ? (
-
) : (
diff --git a/projects/packages/premium-analytics/routes/reports/downloads/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/downloads/config/use-report-records.ts
index a8466f9040bf..8e3d446e2a46 100644
--- a/projects/packages/premium-analytics/routes/reports/downloads/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/downloads/config/use-report-records.ts
@@ -20,6 +20,7 @@ export function useDownloadsReportRecords( reportParams: ReportParams ) {
return {
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
rows: report.comparisonRows?.rows ?? [],
hasComparison: report.hasComparison,
diff --git a/projects/packages/premium-analytics/routes/reports/downloads/page.test.tsx b/projects/packages/premium-analytics/routes/reports/downloads/page.test.tsx
index 5c2868e44772..6bfdd3b01456 100644
--- a/projects/packages/premium-analytics/routes/reports/downloads/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/downloads/page.test.tsx
@@ -29,7 +29,7 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( {
jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
ExporterCsvAction: () => null,
- ReportErrorState: () => null,
+ PageNotice: () => null,
ReportPageLayout: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageShell: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportRecordsTable: jest.fn( () => null ),
@@ -69,6 +69,7 @@ describe( 'DownloadsReportPage', () => {
it( 'reports the loading state on first load, when there is nothing to show yet', () => {
useRecordsMock.mockReturnValue( {
isError: false,
+ error: null,
refetch: jest.fn(),
rows: [],
hasComparison: false,
@@ -91,6 +92,7 @@ describe( 'DownloadsReportPage', () => {
// page position survive a date or comparison change instead of resetting.
useRecordsMock.mockReturnValue( {
isError: false,
+ error: null,
refetch: jest.fn(),
rows: [ row ],
hasComparison: true,
@@ -112,6 +114,7 @@ describe( 'DownloadsReportPage', () => {
it( 'shows current rows once both requests are settled', () => {
useRecordsMock.mockReturnValue( {
isError: false,
+ error: null,
refetch: jest.fn(),
rows: [ row ],
hasComparison: true,
diff --git a/projects/packages/premium-analytics/routes/reports/downloads/page.tsx b/projects/packages/premium-analytics/routes/reports/downloads/page.tsx
index bfd244c307e9..9ebdace23a07 100644
--- a/projects/packages/premium-analytics/routes/reports/downloads/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/downloads/page.tsx
@@ -5,7 +5,8 @@ import { type StatsFileDownloadsComparisonItem } from '@jetpack-premium-analytic
import { useReportDateFilters } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportRecordsTable,
@@ -67,9 +68,14 @@ function DownloadsReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/earnings/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/earnings/config/use-report-records.ts
index 66976c724991..c73633ad714c 100644
--- a/projects/packages/premium-analytics/routes/reports/earnings/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/earnings/config/use-report-records.ts
@@ -54,6 +54,7 @@ export function useEarningsReportRecords( tab: EarningsReportTabId ) {
isLoading: report.isLoading,
isFetching: report.isFetching,
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/earnings/page.test.tsx b/projects/packages/premium-analytics/routes/reports/earnings/page.test.tsx
index 4a8ba99811a5..a96542c01941 100644
--- a/projects/packages/premium-analytics/routes/reports/earnings/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/earnings/page.test.tsx
@@ -7,6 +7,7 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { useEarningsReportRecords } from './config';
import EarningsReportPage from './page';
import type { EarningsReportTabId } from './config';
@@ -152,7 +153,9 @@ describe( 'EarningsReportPage', () => {
render( );
- expect( screen.getByText( 'Unable to load earnings' ) ).toBeInTheDocument();
+ expect(
+ getNoticeText( "We couldn't load earnings. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( screen.getByRole( 'button', { name: 'Retry' } ) ).toBeInTheDocument();
expect( screen.queryByText( 'September 2026' ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/earnings/page.tsx b/projects/packages/premium-analytics/routes/reports/earnings/page.tsx
index 24264d1b63f8..3351355ddb71 100644
--- a/projects/packages/premium-analytics/routes/reports/earnings/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/earnings/page.tsx
@@ -5,7 +5,8 @@ import { useSectionTab } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ReportCsvAction,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportPageTabs,
@@ -148,9 +149,14 @@ function EarningsReport(): JSX.Element {
}
>
{ records.isError ? (
-
) : (
diff --git a/projects/packages/premium-analytics/routes/reports/emails/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/emails/config/use-report-records.ts
index fbbed29507aa..b65d7a1de94a 100644
--- a/projects/packages/premium-analytics/routes/reports/emails/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/emails/config/use-report-records.ts
@@ -22,6 +22,7 @@ export function useEmailsReportRecords() {
isLoading: report.isLoading,
isFetching: report.isFetching,
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/emails/page.test.tsx b/projects/packages/premium-analytics/routes/reports/emails/page.test.tsx
index c1c0f8fd8535..db82ef6487b1 100644
--- a/projects/packages/premium-analytics/routes/reports/emails/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/emails/page.test.tsx
@@ -6,6 +6,7 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { useEmailsReportRecords } from './config';
import EmailsReportPage from './page';
import type { StatsEmailSummaryItem } from '@jetpack-premium-analytics/data';
@@ -113,7 +114,9 @@ describe( 'EmailsReportPage', () => {
render( );
- expect( screen.getByText( 'Unable to load emails' ) ).toBeInTheDocument();
+ expect(
+ getNoticeText( "We couldn't load emails. Please try again in a moment." )
+ ).toBeInTheDocument();
await userEvent.setup().click( screen.getByRole( 'button', { name: 'Retry' } ) );
expect( refetch ).toHaveBeenCalled();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/emails/page.tsx b/projects/packages/premium-analytics/routes/reports/emails/page.tsx
index a4c50a4fcd54..78ab8fe59e8c 100644
--- a/projects/packages/premium-analytics/routes/reports/emails/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/emails/page.tsx
@@ -4,7 +4,8 @@
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ExporterCsvAction,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportRecordsTable,
@@ -80,9 +81,14 @@ function EmailsReport(): JSX.Element {
>
{ records.isError ? (
-
) : (
diff --git a/projects/packages/premium-analytics/routes/reports/locations/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/locations/config/use-report-records.ts
index f4a70d2ef7ec..dc682167e693 100644
--- a/projects/packages/premium-analytics/routes/reports/locations/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/locations/config/use-report-records.ts
@@ -77,6 +77,7 @@ export function useLocationsReportRecords(
options: countryOptions,
},
isError: activeReport.primary.isError,
+ error: activeReport.primary.error,
refetch: activeReport.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/locations/page.test.tsx b/projects/packages/premium-analytics/routes/reports/locations/page.test.tsx
index cea02ca563d2..f023342c6656 100644
--- a/projects/packages/premium-analytics/routes/reports/locations/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/locations/page.test.tsx
@@ -5,7 +5,6 @@ import { usePrefetchViewerCountry } from '@jetpack-premium-analytics/data';
import { useSectionTab } from '@jetpack-premium-analytics/routing';
import {
ExporterCsvAction,
- ReportErrorState,
ReportLocationsMap,
ReportPageTabs,
ReportRecordsTable,
@@ -17,6 +16,7 @@ import { useState } from 'react';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { getLocationFields, useLocationsReportRecords } from './config';
import LocationsReportPage from './page';
import type { LocationRow, ReportLocationsTabId } from './config';
@@ -61,12 +61,8 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
supportsLocationsCountryFilter: jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' )
.supportsLocationsCountryFilter,
flagUrl: ( countryCode: string ) => `https://example.com/${ countryCode }.svg`,
- ReportErrorState: jest.fn( ( { title, onRetry }: { title: string; onRetry: () => void } ) => (
-
- { title }
-
-
- ) ),
+ PageNotice: jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ).PageNotice,
+ describeError: jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ).describeError,
ReportPageLayout: ( { tabs, children }: { tabs: ReactNode; children: ReactNode } ) => (
<>
{ tabs }
@@ -103,7 +99,6 @@ jest.mock( '@wordpress/route', () => ( {
const useRecordsMock = jest.mocked( useLocationsReportRecords );
const useSectionTabMock = jest.mocked( useSectionTab );
-const reportErrorStateMock = jest.mocked( ReportErrorState );
const reportPageTabsMock = jest.mocked( ReportPageTabs );
const reportRecordsTableMock = jest.mocked( ReportRecordsTable );
const reportLocationsMapMock = jest.mocked( ReportLocationsMap );
@@ -282,13 +277,21 @@ describe( 'LocationsReportPage', () => {
render( );
- expect( screen.getByTestId( 'report-error-state' ) ).toHaveTextContent(
- 'Unable to load locations'
- );
- expect( reportErrorStateMock ).toHaveBeenCalled();
+ expect(
+ getNoticeText( "We couldn't load locations. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( reportRecordsTableMock ).not.toHaveBeenCalled();
} );
+ it( 'drops Retry when the request is denied', () => {
+ mockRecords( { isError: true, error: { error: 'unauthorized', status: 403 } } );
+
+ render( );
+
+ expect( getNoticeText( "You don't have access to this data." ) ).toBeInTheDocument();
+ expect( screen.queryByRole( 'button', { name: 'Retry' } ) ).not.toBeInTheDocument();
+ } );
+
it( 'refetches the active tab when Retry is clicked', async () => {
const records = mockRecords( { isError: true } );
diff --git a/projects/packages/premium-analytics/routes/reports/locations/page.tsx b/projects/packages/premium-analytics/routes/reports/locations/page.tsx
index 8502dbad999a..5453b49ca036 100644
--- a/projects/packages/premium-analytics/routes/reports/locations/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/locations/page.tsx
@@ -7,7 +7,8 @@ import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ExporterCsvAction,
LOCATIONS_GEO_MODES,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportLocationsMap,
ReportPageLayout,
ReportPageShell,
@@ -164,9 +165,14 @@ export default function LocationsReportPage(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/posts/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/posts/config/use-report-records.ts
index 1e4197084666..8156c194550b 100644
--- a/projects/packages/premium-analytics/routes/reports/posts/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/posts/config/use-report-records.ts
@@ -43,6 +43,7 @@ export function usePostsReportRecords( activeTab: ReportPostsTabId, reportParams
return {
isError: activeReport.isError,
+ error: activeReport.error,
refetch: activeReport.refetch,
posts: {
rows: postRows,
diff --git a/projects/packages/premium-analytics/routes/reports/posts/page.test.tsx b/projects/packages/premium-analytics/routes/reports/posts/page.test.tsx
index 97d2437922dd..9d2d9b839953 100644
--- a/projects/packages/premium-analytics/routes/reports/posts/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/posts/page.test.tsx
@@ -6,7 +6,6 @@ import { useSectionTab } from '@jetpack-premium-analytics/routing';
import {
ExporterCsvAction,
ReportDrilldownTable,
- ReportErrorState,
ReportRecordsTable,
archivesCsvExporter,
postsPagesCsvExporter,
@@ -16,6 +15,7 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { usePostsReportRecords } from './config';
import PostsReportPage from './page';
import type { ReactNode } from 'react';
@@ -52,12 +52,8 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
};
} )(),
formatLegendLabels: () => [],
- ReportErrorState: jest.fn( ( { title, onRetry }: { title: string; onRetry: () => void } ) => (
-
- { title }
-
-
- ) ),
+ PageNotice: jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ).PageNotice,
+ describeError: jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ).describeError,
ReportPageLayout: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageShell: ( { actions, children }: { actions?: ReactNode; children: ReactNode } ) => (
<>
@@ -93,7 +89,6 @@ const usePostThumbnailsMock = jest.mocked( usePostThumbnails );
const useSectionTabMock = jest.mocked( useSectionTab );
const exporterCsvActionMock = jest.mocked( ExporterCsvAction );
const reportDrilldownTableMock = jest.mocked( ReportDrilldownTable );
-const reportErrorStateMock = jest.mocked( ReportErrorState );
const reportRecordsTableMock = jest.mocked( ReportRecordsTable );
/**
@@ -116,6 +111,7 @@ function buildRecords( {
} = {} ) {
return {
isError,
+ error: null,
refetch: jest.fn(),
posts: {
rows: [
@@ -269,10 +265,9 @@ describe( 'PostsReportPage', () => {
render( );
- expect( screen.getByTestId( 'report-error-state' ) ).toHaveTextContent(
- 'Unable to load posts'
- );
- expect( reportErrorStateMock ).toHaveBeenCalled();
+ expect(
+ getNoticeText( "We couldn't load posts. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( reportRecordsTableMock ).not.toHaveBeenCalled();
expect( reportDrilldownTableMock ).not.toHaveBeenCalled();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/posts/page.tsx b/projects/packages/premium-analytics/routes/reports/posts/page.tsx
index 6da20b0e9297..f0a0650e9add 100644
--- a/projects/packages/premium-analytics/routes/reports/posts/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/posts/page.tsx
@@ -8,7 +8,8 @@ import {
import { useReportDateFilters, useSectionTab } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportPageTabs,
@@ -188,9 +189,14 @@ function PostsReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/referrers/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/referrers/config/use-report-records.ts
index 4412541484d5..6f80dff1288d 100644
--- a/projects/packages/premium-analytics/routes/reports/referrers/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/referrers/config/use-report-records.ts
@@ -25,6 +25,7 @@ export function useReferrersReportRecords( reportParams: ReportParams ) {
return {
isError: report.isError,
+ error: report.error,
refetch: report.refetch,
rows,
isLoading: report.isLoading,
diff --git a/projects/packages/premium-analytics/routes/reports/referrers/page.tsx b/projects/packages/premium-analytics/routes/reports/referrers/page.tsx
index 5168948a7018..81ffdc51cb39 100644
--- a/projects/packages/premium-analytics/routes/reports/referrers/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/referrers/page.tsx
@@ -5,7 +5,8 @@ import { useReportDateFilters } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ReportDrilldownTable,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ExporterCsvAction,
@@ -78,9 +79,14 @@ function ReferrersReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx b/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx
index 2b828e7b5761..ff7f257d44f8 100644
--- a/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx
@@ -69,7 +69,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => {
getWordAdsHistoryFields: () => [],
ReportCsvAction: jest.fn( () => null ),
ReportDrilldownTable: () => null,
- ReportErrorState: () => null,
+ PageNotice: () => null,
ReportPageLayout: Container,
ReportPageSection: Container,
ReportPageShell: Container,
@@ -146,6 +146,7 @@ const reportStatus = {
isLoading: false,
isFetching: false,
isError: false,
+ error: null,
refetch: jest.fn(),
};
diff --git a/projects/packages/premium-analytics/routes/reports/search-terms/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/search-terms/config/use-report-records.ts
index 0c423f7ca4a2..32cf2e8f6e20 100644
--- a/projects/packages/premium-analytics/routes/reports/search-terms/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/search-terms/config/use-report-records.ts
@@ -47,6 +47,7 @@ export function useSearchTermsReportRecords( reportParams: ReportParams ) {
// A comparison-only failure still renders the table with primary rows
// and no deltas, via the comparisonSettled guard above.
isError: report.primary.isError,
+ error: report.primary.error,
refetch: report.refetch,
table: {
...table,
diff --git a/projects/packages/premium-analytics/routes/reports/search-terms/page.test.tsx b/projects/packages/premium-analytics/routes/reports/search-terms/page.test.tsx
index a077a74780e5..3540f1d960bf 100644
--- a/projects/packages/premium-analytics/routes/reports/search-terms/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/search-terms/page.test.tsx
@@ -31,7 +31,7 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( {
jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
ExporterCsvAction: () => null,
- ReportErrorState: () => null,
+ PageNotice: () => null,
ReportPageLayout: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageShell: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportRecordsTable: jest.fn( () => null ),
diff --git a/projects/packages/premium-analytics/routes/reports/search-terms/page.tsx b/projects/packages/premium-analytics/routes/reports/search-terms/page.tsx
index 6ef3bf9bfac7..72822eb26c63 100644
--- a/projects/packages/premium-analytics/routes/reports/search-terms/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/search-terms/page.tsx
@@ -4,7 +4,8 @@
import { useReportDateFilters } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportRecordsTable,
@@ -67,9 +68,14 @@ export default function SearchTermsReportPage(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts
index 7f62eb8e3f9c..52613e126bd1 100644
--- a/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts
@@ -32,6 +32,7 @@ export function useTagsReportRecords() {
isLoading: tags.isLoading,
isFetching: tags.isFetching,
isError: tags.isError,
+ error: tags.error,
refetch: tags.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/tags/page.tsx b/projects/packages/premium-analytics/routes/reports/tags/page.tsx
index 768570276364..263314d46114 100644
--- a/projects/packages/premium-analytics/routes/reports/tags/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/tags/page.tsx
@@ -4,7 +4,8 @@
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
ExporterCsvAction,
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportRecordsTable,
@@ -68,9 +69,14 @@ function TagsReport(): JSX.Element {
>
{ records.isError ? (
-
) : (
diff --git a/projects/packages/premium-analytics/routes/reports/utm/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/utm/config/use-report-records.ts
index 56d2e010cb58..3037595d2924 100644
--- a/projects/packages/premium-analytics/routes/reports/utm/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/utm/config/use-report-records.ts
@@ -58,6 +58,7 @@ export function useUtmReportRecords( activeTab: UtmReportTabId, reportParams: Re
isLoading: activeReport.isLoading,
isFetching: activeReport.isFetching,
isError: activeReport.isError,
+ error: activeReport.error,
refetch: activeReport.refetch,
};
}
diff --git a/projects/packages/premium-analytics/routes/reports/utm/page.test.tsx b/projects/packages/premium-analytics/routes/reports/utm/page.test.tsx
index 4c0a3829cfb6..32fca239da78 100644
--- a/projects/packages/premium-analytics/routes/reports/utm/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/utm/page.test.tsx
@@ -35,7 +35,7 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( {
jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
ExporterCsvAction: () => null,
ReportDrilldownTable: jest.fn( () => null ),
- ReportErrorState: () => null,
+ PageNotice: () => null,
ReportPageLayout: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageShell: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageTabs: () => null,
diff --git a/projects/packages/premium-analytics/routes/reports/utm/page.tsx b/projects/packages/premium-analytics/routes/reports/utm/page.tsx
index 97ba1dd59d74..217422a0524a 100644
--- a/projects/packages/premium-analytics/routes/reports/utm/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/utm/page.tsx
@@ -4,7 +4,8 @@
import { useReportDateFilters, useSectionTab } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportDrilldownTable,
ReportPageLayout,
ReportPageShell,
@@ -85,9 +86,14 @@ function UtmReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/reports/videos/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/videos/config/use-report-records.ts
index b97655d5f65a..893f271f94ff 100644
--- a/projects/packages/premium-analytics/routes/reports/videos/config/use-report-records.ts
+++ b/projects/packages/premium-analytics/routes/reports/videos/config/use-report-records.ts
@@ -26,6 +26,7 @@ export function useVideosReportRecords( reportParams: ReportParams ) {
return {
isError: summary.primary.isError,
+ error: summary.primary.error,
refetch: summary.refetch,
rows: summary.comparisonRows?.rows ?? EMPTY_VIDEO_ROWS,
hasComparison: summary.hasComparison,
diff --git a/projects/packages/premium-analytics/routes/reports/videos/page.test.tsx b/projects/packages/premium-analytics/routes/reports/videos/page.test.tsx
index 4b3720fe33c6..bd03e0276d83 100644
--- a/projects/packages/premium-analytics/routes/reports/videos/page.test.tsx
+++ b/projects/packages/premium-analytics/routes/reports/videos/page.test.tsx
@@ -3,7 +3,7 @@
*/
import {
ExporterCsvAction,
- ReportErrorState,
+ PageNotice,
ReportRecordsTable,
} from '@jetpack-premium-analytics/widgets-toolkit';
import { render, screen } from '@testing-library/react';
@@ -11,6 +11,7 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
+import { getNoticeText } from '../../../tests/js/notice-test-utils';
import { getVideosFields, useVideosReportRecords } from './config';
import VideosReportPage from './page';
import type { StatsVideoPlaysComparisonItem } from '@jetpack-premium-analytics/data';
@@ -34,12 +35,10 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( {
} ) );
jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( {
- ReportErrorState: jest.fn( ( { title, onRetry }: { title: string; onRetry: () => void } ) => (
-
- { title }
-
-
- ) ),
+ PageNotice: jest.fn(
+ jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ).PageNotice
+ ),
+ describeError: jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ).describeError,
ReportPageLayout: ( { children }: { children: ReactNode } ) => <>{ children }>,
ReportPageShell: ( { actions, children }: { actions?: ReactNode; children: ReactNode } ) => (
<>
@@ -69,7 +68,7 @@ jest.mock( '@wordpress/route', () => ( {
const useRecordsMock = jest.mocked( useVideosReportRecords );
const exporterCsvActionMock = jest.mocked( ExporterCsvAction );
const getVideosFieldsMock = jest.mocked( getVideosFields );
-const reportErrorStateMock = jest.mocked( ReportErrorState );
+const pageNoticeMock = jest.mocked( PageNotice );
const reportRecordsTableMock = jest.mocked( ReportRecordsTable );
const videoRow = {
@@ -112,7 +111,7 @@ describe( 'VideosReportPage', () => {
render( );
expect( reportRecordsTableMock ).toHaveBeenCalled();
- expect( reportErrorStateMock ).not.toHaveBeenCalled();
+ expect( pageNoticeMock ).not.toHaveBeenCalled();
} );
it( 'draws each video poster beside its title', () => {
@@ -244,9 +243,9 @@ describe( 'VideosReportPage', () => {
render( );
- expect( screen.getByTestId( 'report-error-state' ) ).toHaveTextContent(
- 'Unable to load videos'
- );
+ expect(
+ getNoticeText( "We couldn't load videos. Please try again in a moment." )
+ ).toBeInTheDocument();
expect( reportRecordsTableMock ).not.toHaveBeenCalled();
} );
diff --git a/projects/packages/premium-analytics/routes/reports/videos/page.tsx b/projects/packages/premium-analytics/routes/reports/videos/page.tsx
index ef6eed05577b..6932e56a9339 100644
--- a/projects/packages/premium-analytics/routes/reports/videos/page.tsx
+++ b/projects/packages/premium-analytics/routes/reports/videos/page.tsx
@@ -5,7 +5,8 @@ import { type StatsVideoPlaysComparisonItem } from '@jetpack-premium-analytics/d
import { useReportDateFilters } from '@jetpack-premium-analytics/routing';
import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui';
import {
- ReportErrorState,
+ PageNotice,
+ describeError,
ReportPageLayout,
ReportPageShell,
ReportRecordsTable,
@@ -81,9 +82,14 @@ function VideosReport(): JSX.Element {
if ( records.isError ) {
tableReplacement = (
-
);
}
diff --git a/projects/packages/premium-analytics/routes/video-detail/stage.test.tsx b/projects/packages/premium-analytics/routes/video-detail/stage.test.tsx
index 9640a26eaaab..d77d509942bb 100644
--- a/projects/packages/premium-analytics/routes/video-detail/stage.test.tsx
+++ b/projects/packages/premium-analytics/routes/video-detail/stage.test.tsx
@@ -271,7 +271,7 @@ describe( 'video detail stage', () => {
expect( getNoticeText( "You don't have access to this data." ) ).toBeInTheDocument();
expect(
- getNoticeAnnouncement( "You don't have access to this data.", 'polite' )
+ getNoticeAnnouncement( "You don't have access to this data.", 'assertive' )
).toBeInTheDocument();
expect( screen.queryByRole( 'button', { name: 'Retry' } ) ).not.toBeInTheDocument();
} );
diff --git a/projects/packages/premium-analytics/routes/video-detail/stage.tsx b/projects/packages/premium-analytics/routes/video-detail/stage.tsx
index ef3286d99387..044a1f55afe2 100644
--- a/projects/packages/premium-analytics/routes/video-detail/stage.tsx
+++ b/projects/packages/premium-analytics/routes/video-detail/stage.tsx
@@ -15,14 +15,14 @@ import {
DetailPageActions,
DetailPageBreadcrumbs,
DetailPageLayout,
- DetailPageNotice,
+ PageNotice,
DetailPageSection,
DetailPageShell,
- describeDetailPageError,
+ describeError,
useDetailPageCustomize,
useStoredDetailLayout,
useTrackedDateRangeApply,
- type DetailPageNoticeProps,
+ type PageNoticeProps,
} from '@jetpack-premium-analytics/widgets-toolkit';
import { useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
@@ -138,10 +138,10 @@ function VideoDetail(): JSX.Element {
// The reason a video is missing goes below the header, where the widgets
// would have been.
- let notice: DetailPageNoticeProps | null = null;
+ let notice: PageNoticeProps | null = null;
if ( summary.isError ) {
- notice = describeDetailPageError( summary.error, {
+ notice = describeError( summary.error, {
retryDescription: __(
"We couldn't load this video. Please try again in a moment.",
'jetpack-premium-analytics-pkg'
@@ -217,7 +217,7 @@ function VideoDetail(): JSX.Element {
) : null }
{ notice ? (
-
+
) : null }
diff --git a/projects/packages/premium-analytics/tests/groups/widgets-toolkit-no-mocks-part2.test.tsx b/projects/packages/premium-analytics/tests/groups/widgets-toolkit-no-mocks-part2.test.tsx
index c348c92b9470..f114932df502 100644
--- a/projects/packages/premium-analytics/tests/groups/widgets-toolkit-no-mocks-part2.test.tsx
+++ b/projects/packages/premium-analytics/tests/groups/widgets-toolkit-no-mocks-part2.test.tsx
@@ -1,8 +1,8 @@
// See README.md before adding a suite to this group.
import '../../packages/widgets-toolkit/src/components/metric-tile/__tests__/metric-tile-grid.test';
+import '../../packages/widgets-toolkit/src/components/page-notice/__tests__/page-notice.test';
import '../../packages/widgets-toolkit/src/components/post-highlight-card/__tests__/post-highlight-card-skeleton.test';
-import '../../packages/widgets-toolkit/src/components/report-page/__tests__/report-error-state.test';
import '../../packages/widgets-toolkit/src/components/report-page/__tests__/report-records-table.test';
import '../../packages/widgets-toolkit/src/components/report-page/utils/build-report-metric-series.test';
import '../../packages/widgets-toolkit/src/components/subscriber-list/__tests__/subscriber-list-fit-rows.test';
diff --git a/projects/plugins/jetpack/changelog/change-uni-846-report-error-notice b/projects/plugins/jetpack/changelog/change-uni-846-report-error-notice
new file mode 100644
index 000000000000..c22f0741286a
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/change-uni-846-report-error-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: bugfix
+
+Premium Analytics: Show report load errors in a notice across the report, without Retry when access is denied. Show access denied on author, post, and video details as an error.
diff --git a/projects/plugins/premium-analytics/changelog/change-uni-846-report-error-notice b/projects/plugins/premium-analytics/changelog/change-uni-846-report-error-notice
new file mode 100644
index 000000000000..470193703d3c
--- /dev/null
+++ b/projects/plugins/premium-analytics/changelog/change-uni-846-report-error-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Show report load errors in a notice across the report, without Retry when access is denied. Show access denied on author, post, and video details as an error.
diff --git a/projects/plugins/wpcomsh/changelog/change-uni-846-report-error-notice b/projects/plugins/wpcomsh/changelog/change-uni-846-report-error-notice
new file mode 100644
index 000000000000..6213092cb95a
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/change-uni-846-report-error-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Premium Analytics: Show report load errors in a notice across the report, without Retry when access is denied. Show access denied on author, post, and video details as an error.