Skip to content

Commit 99cf76b

Browse files
Merge pull request #12839 from google/enhancement/12631-pdf-orchestrator-sidesheet-registry
Register and render first data into PDF report.
2 parents a3e8a68 + 80853f7 commit 99cf76b

49 files changed

Lines changed: 1993 additions & 758 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assets/js/components/DashboardMainApp.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ import MetricsSelectionPanel from './KeyMetrics/MetricsSelectionPanel';
8989
import ModuleDashboardEffects from './ModuleDashboardEffects';
9090
import Notifications from './notifications/Notifications';
9191
import OfflineNotification from './notifications/OfflineNotification';
92+
import PDFDownloadButton from './pdf-export/PDFDownloadButton';
9293
import PDFExportRoot from './pdf-export/PDFExportRoot';
93-
import PDFDownloadButton from './pdf-generation/PDFDownloadButton';
94-
import PDFSectionsSelectionPanel from './pdf-generation/PDFSectionsSelectionPanel';
94+
import PDFSectionsSelectionPanel from './pdf-export/PDFSectionsSelectionPanel';
9595
import CurrentSurveyPortal from './surveys/CurrentSurveyPortal';
9696
import SurveyViewTrigger from './surveys/SurveyViewTrigger';
9797
import WelcomeModal from './WelcomeModal';

assets/js/components/PDFExport/components/PDFMetricTile.tsx

Lines changed: 0 additions & 133 deletions
This file was deleted.

assets/js/components/pdf-generation/PDFDownloadButton.test.tsx renamed to assets/js/components/pdf-export/PDFDownloadButton.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/**
2020
* Internal dependencies
2121
*/
22-
import { PDF_DOWNLOAD_PANEL_OPENED_KEY } from '@/js/components/pdf-generation/constants';
22+
import { PDF_DOWNLOAD_PANEL_OPENED_KEY } from '@/js/components/pdf-export/constants';
2323
import { CORE_UI } from '@/js/googlesitekit/datastore/ui/constants';
2424
import { createTestRegistry, fireEvent, render } from '@tests/js/test-utils';
2525
import PDFDownloadButton from './PDFDownloadButton';

assets/js/components/pdf-generation/PDFDownloadButton.tsx renamed to assets/js/components/pdf-export/PDFDownloadButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { __ } from '@wordpress/i18n';
3232
*/
3333
import { Button } from 'googlesitekit-components';
3434
import { Select, useDispatch, useSelect } from 'googlesitekit-data';
35-
import { PDF_DOWNLOAD_PANEL_OPENED_KEY } from '@/js/components/pdf-generation/constants';
35+
import { PDF_DOWNLOAD_PANEL_OPENED_KEY } from '@/js/components/pdf-export/constants';
3636
import { CORE_UI } from '@/js/googlesitekit/datastore/ui/constants';
3737
import DownloadIcon from '@/svg/icons/download.svg';
3838

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* PDFExportOrchestrator tests.
3+
*
4+
* Site Kit by Google, Copyright 2026 Google LLC
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
/**
20+
* External dependencies
21+
*/
22+
import { pdf } from '@react-pdf/renderer';
23+
24+
/**
25+
* Internal dependencies
26+
*/
27+
import { VIEW_CONTEXT_MAIN_DASHBOARD } from '@/js/googlesitekit/constants';
28+
import { CORE_PDF } from '@/js/googlesitekit/datastore/pdf/constants';
29+
import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants';
30+
import { CORE_WIDGETS } from '@/js/googlesitekit/widgets/datastore/constants';
31+
import { CONTEXT_MAIN_DASHBOARD_TRAFFIC } from '@/js/googlesitekit/widgets/default-contexts';
32+
import {
33+
createTestRegistry,
34+
provideSiteInfo,
35+
provideUserInfo,
36+
render,
37+
waitFor,
38+
} from '@tests/js/test-utils';
39+
import PDFExportOrchestrator from './PDFExportOrchestrator';
40+
41+
// Stub the download trigger so the anchor click does not attempt a JSDOM
42+
// navigation; the filename helper stays real.
43+
jest.mock( './pdf-utils', () => ( {
44+
...jest.requireActual( './pdf-utils' ),
45+
triggerDownload: jest.fn(),
46+
} ) );
47+
48+
function NullComponent() {
49+
return null;
50+
}
51+
52+
describe( 'PDFExportOrchestrator', () => {
53+
let registry: ReturnType< typeof createTestRegistry >;
54+
55+
beforeEach( () => {
56+
registry = createTestRegistry();
57+
provideSiteInfo( registry, { siteName: 'Example Site' } );
58+
provideUserInfo( registry );
59+
registry.dispatch( CORE_USER ).setReferenceDate( '2021-01-10' );
60+
registry.dispatch( CORE_USER ).setDateRange( 'last-28-days' );
61+
62+
( pdf as jest.Mock ).mockClear();
63+
64+
global.URL.createObjectURL = jest.fn( () => 'blob:mock-url' );
65+
global.URL.revokeObjectURL = jest.fn();
66+
} );
67+
68+
function registerPDFWidget(
69+
areaSlug: string,
70+
widgetSlug: string,
71+
getData: jest.Mock
72+
) {
73+
const dispatch = registry.dispatch( CORE_WIDGETS );
74+
dispatch.registerWidgetArea( areaSlug, {
75+
title: 'Area',
76+
pdfTitle: 'Traffic',
77+
style: 'boxes',
78+
priority: 1,
79+
} );
80+
dispatch.assignWidgetArea( areaSlug, CONTEXT_MAIN_DASHBOARD_TRAFFIC );
81+
dispatch.registerWidget( widgetSlug, {
82+
Component: NullComponent,
83+
pdf: { Component: NullComponent, getData },
84+
} );
85+
dispatch.assignWidget( widgetSlug, areaSlug );
86+
}
87+
88+
function renderOrchestrator() {
89+
return render( <PDFExportOrchestrator onComplete={ () => {} } />, {
90+
registry,
91+
viewContext: VIEW_CONTEXT_MAIN_DASHBOARD,
92+
} );
93+
}
94+
95+
it( 'loads the selected widget data with PDF-adjusted dates and builds the PDF', async () => {
96+
const getData: jest.Mock = jest.fn( () =>
97+
Promise.resolve( { data: { totalUsers: 100 } } )
98+
);
99+
registerPDFWidget( 'trafficArea', 'trafficWidget', getData );
100+
registry.dispatch( CORE_PDF ).setSelection( {
101+
contextSlugs: [ CONTEXT_MAIN_DASHBOARD_TRAFFIC ],
102+
widgetSlugs: [],
103+
} );
104+
105+
renderOrchestrator();
106+
107+
await waitFor( () => {
108+
expect( registry.select( CORE_PDF ).getStatus() ).toBe( 'success' );
109+
} );
110+
111+
expect( getData ).toHaveBeenCalledTimes( 1 );
112+
113+
const { dates, signal } = getData.mock.calls[ 0 ][ 0 ];
114+
// End date is shifted back one day from the reference date.
115+
expect( dates.endDate ).toBe( '2021-01-09' );
116+
expect( dates.compareStartDate ).toBeDefined();
117+
expect( signal ).toBeInstanceOf( AbortSignal );
118+
119+
expect( pdf ).toHaveBeenCalledTimes( 1 );
120+
} );
121+
122+
it( 'transitions to error and does not build a PDF when the only widget fails', async () => {
123+
const getData = jest.fn( () =>
124+
Promise.reject( new Error( 'report failed' ) )
125+
);
126+
registerPDFWidget( 'trafficArea', 'trafficWidget', getData );
127+
registry.dispatch( CORE_PDF ).setSelection( {
128+
contextSlugs: [ CONTEXT_MAIN_DASHBOARD_TRAFFIC ],
129+
widgetSlugs: [],
130+
} );
131+
132+
renderOrchestrator();
133+
134+
await waitFor( () => {
135+
expect( registry.select( CORE_PDF ).getStatus() ).toBe( 'error' );
136+
} );
137+
138+
expect( pdf ).not.toHaveBeenCalled();
139+
} );
140+
141+
it( 'isolates a failing widget when another widget succeeds', async () => {
142+
const failing = jest.fn( () =>
143+
Promise.reject( new Error( 'report failed' ) )
144+
);
145+
const succeeding = jest.fn( () =>
146+
Promise.resolve( { data: { totalUsers: 100 } } )
147+
);
148+
registerPDFWidget( 'trafficAreaA', 'trafficWidgetA', failing );
149+
registerPDFWidget( 'trafficAreaB', 'trafficWidgetB', succeeding );
150+
registry.dispatch( CORE_PDF ).setSelection( {
151+
contextSlugs: [ CONTEXT_MAIN_DASHBOARD_TRAFFIC ],
152+
widgetSlugs: [],
153+
} );
154+
155+
renderOrchestrator();
156+
157+
await waitFor( () => {
158+
expect( registry.select( CORE_PDF ).getStatus() ).toBe( 'success' );
159+
} );
160+
161+
expect( failing ).toHaveBeenCalledTimes( 1 );
162+
expect( succeeding ).toHaveBeenCalledTimes( 1 );
163+
expect( pdf ).toHaveBeenCalledTimes( 1 );
164+
} );
165+
} );

0 commit comments

Comments
 (0)