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.