Skip to content

Commit 97688a3

Browse files
authored
Merge pull request #12619 from google/enhancement/12507-pdf-generation-button-and-sidesheet
Implement PDF Generation side sheet.
2 parents 92887e3 + d07d692 commit 97688a3

26 files changed

Lines changed: 1158 additions & 4 deletions

File tree

assets/js/components/DashboardMainApp.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import CurrentSurveyPortal from './surveys/CurrentSurveyPortal';
5252
import MetricsSelectionPanel from './KeyMetrics/MetricsSelectionPanel';
5353
import UserSettingsSelectionPanel from './email-reporting/UserSettingsSelectionPanel';
5454
import PUESurveyTriggers from './email-reporting/PUESurveyTriggers';
55+
import PDFDownloadButton from './pdf-generation/PDFDownloadButton';
56+
import PDFSectionsSelectionPanel from './pdf-generation/PDFSectionsSelectionPanel';
5557
import WelcomeModal from './WelcomeModal';
5658
import SiteGoalsIntroModalBanner from '@/js/modules/analytics-4/components/site-goals/notifications/IntroModalBanner';
5759
import { useFeature } from '@/js/hooks/useFeature';
@@ -190,7 +192,7 @@ export default function DashboardMainApp() {
190192
} );
191193

192194
const widgetContextOptions = {
193-
modules: viewableModules ? viewableModules : undefined,
195+
modules: viewableModules,
194196
};
195197

196198
const isKeyMetricsActive = useSelect( ( select ) =>
@@ -260,6 +262,7 @@ export default function DashboardMainApp() {
260262

261263
const emailReportingEnabled = useFeature( 'proactiveUserEngagement' );
262264
const setupFlowRefreshEnabled = useFeature( 'setupFlowRefresh' );
265+
const pdfGenerationEnabled = useFeature( 'pdfGeneration' );
263266
const showWelcomeModal = useSelect( ( select ) => {
264267
if ( ! setupFlowRefreshEnabled ) {
265268
return false;
@@ -297,10 +300,14 @@ export default function DashboardMainApp() {
297300

298301
useMonitorInternetConnection();
299302

300-
const isWelcomeTourActive = useSelect( ( select ) => {
303+
const showSetupOverlays = useSelect( ( select ) => {
304+
if ( hideSetupCTAs ) {
305+
return false;
306+
}
307+
301308
const currentTour = select( CORE_USER ).getCurrentTour();
302309

303-
return [
310+
return ! [
304311
WELCOME_TOUR.WITHOUT_ANALYTICS,
305312
WELCOME_TOUR.WITH_ANALYTICS,
306313
].includes( currentTour?.slug );
@@ -318,6 +325,7 @@ export default function DashboardMainApp() {
318325
<Header showNavigation>
319326
<EntitySearchInput />
320327
<DateRangeSelector />
328+
{ pdfGenerationEnabled && <PDFDownloadButton /> }
321329
{ ! viewOnlyDashboard && <DashboardSharingSettingsButton /> }
322330
<HelpMenu showFeatureTour />
323331
</Header>
@@ -337,7 +345,7 @@ export default function DashboardMainApp() {
337345
/>
338346
) }
339347

340-
{ ! isWelcomeTourActive && ! hideSetupCTAs && (
348+
{ showSetupOverlays && (
341349
<Notifications
342350
areaSlug={ NOTIFICATION_AREAS.OVERLAYS }
343351
groupID={ NOTIFICATION_GROUPS.SETUP_CTAS }
@@ -407,6 +415,8 @@ export default function DashboardMainApp() {
407415

408416
{ showKeyMetricsSelectionPanel && <MetricsSelectionPanel /> }
409417

418+
{ pdfGenerationEnabled && <PDFSectionsSelectionPanel /> }
419+
410420
{ emailReportingEnabled && (
411421
<Fragment>
412422
<UserSettingsSelectionPanel />
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* PDFDownloadButton 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+
* Internal dependencies
21+
*/
22+
import {
23+
createTestRegistry,
24+
fireEvent,
25+
render,
26+
} from '../../../../tests/js/test-utils';
27+
import { CORE_UI } from '@/js/googlesitekit/datastore/ui/constants';
28+
import { PDF_DOWNLOAD_PANEL_OPENED_KEY } from '@/js/components/pdf-generation/constants';
29+
import PDFDownloadButton from './PDFDownloadButton';
30+
31+
describe( 'PDFDownloadButton', () => {
32+
let registry: ReturnType< typeof createTestRegistry >;
33+
34+
beforeEach( () => {
35+
registry = createTestRegistry();
36+
} );
37+
38+
it( 'renders the button with the accessible label', () => {
39+
const { getByLabelText } = render( <PDFDownloadButton />, {
40+
registry,
41+
} );
42+
43+
expect( getByLabelText( 'Download PDF report' ) ).toBeInTheDocument();
44+
} );
45+
46+
it( 'toggles PDF_DOWNLOAD_PANEL_OPENED_KEY on click', () => {
47+
const { getByLabelText } = render( <PDFDownloadButton />, {
48+
registry,
49+
} );
50+
51+
expect(
52+
registry.select( CORE_UI ).getValue( PDF_DOWNLOAD_PANEL_OPENED_KEY )
53+
).toBeUndefined();
54+
55+
fireEvent.click( getByLabelText( 'Download PDF report' ) );
56+
57+
expect(
58+
registry.select( CORE_UI ).getValue( PDF_DOWNLOAD_PANEL_OPENED_KEY )
59+
).toBe( true );
60+
61+
fireEvent.click( getByLabelText( 'Download PDF report' ) );
62+
63+
expect(
64+
registry.select( CORE_UI ).getValue( PDF_DOWNLOAD_PANEL_OPENED_KEY )
65+
).toBe( false );
66+
} );
67+
} );
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* PDFDownloadButton component.
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 { FC } from 'react';
23+
24+
/**
25+
* WordPress dependencies
26+
*/
27+
import { __ } from '@wordpress/i18n';
28+
import { useCallback } from '@wordpress/element';
29+
30+
/**
31+
* Internal dependencies
32+
*/
33+
import { useSelect, useDispatch, type Select } from 'googlesitekit-data';
34+
import { Button } from 'googlesitekit-components';
35+
import { CORE_UI } from '@/js/googlesitekit/datastore/ui/constants';
36+
import { PDF_DOWNLOAD_PANEL_OPENED_KEY } from '@/js/components/pdf-generation/constants';
37+
import DownloadIcon from '@/svg/icons/download.svg';
38+
39+
const PDFDownloadButton: FC = () => {
40+
const isOpen = useSelect(
41+
( select: Select ) =>
42+
select( CORE_UI ).getValue( PDF_DOWNLOAD_PANEL_OPENED_KEY ),
43+
[]
44+
);
45+
46+
const { setValue } = useDispatch( CORE_UI );
47+
48+
const togglePanel = useCallback( () => {
49+
setValue( PDF_DOWNLOAD_PANEL_OPENED_KEY, ! isOpen );
50+
}, [ isOpen, setValue ] );
51+
52+
return (
53+
<Button
54+
aria-label={ __( 'Download PDF report', 'google-site-kit' ) }
55+
// @ts-expect-error - The `Button` component is not typed yet.
56+
className="googlesitekit-pdf-download__button googlesitekit-header__dropdown googlesitekit-border-radius-round googlesitekit-button-icon"
57+
onClick={ togglePanel }
58+
icon={ <DownloadIcon width={ 20 } height={ 20 } /> }
59+
tooltipEnterDelayInMS={ 500 }
60+
tertiary
61+
/>
62+
);
63+
};
64+
65+
export default PDFDownloadButton;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* PDF Sections Selection Panel Footer.
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 { FC } from 'react';
23+
24+
/**
25+
* WordPress dependencies
26+
*/
27+
import { __ } from '@wordpress/i18n';
28+
import { useCallback } from '@wordpress/element';
29+
30+
/**
31+
* Internal dependencies
32+
*/
33+
import { useSelect, useDispatch, type Select } from 'googlesitekit-data';
34+
import { Button } from 'googlesitekit-components';
35+
import { CORE_UI } from '@/js/googlesitekit/datastore/ui/constants';
36+
import { PDF_GENERATING_KEY } from '@/js/components/pdf-generation/constants';
37+
38+
interface FooterProps {
39+
closePanel: () => void;
40+
hasSelection: boolean;
41+
}
42+
43+
const Footer: FC< FooterProps > = ( { closePanel, hasSelection } ) => {
44+
const isGenerating = useSelect(
45+
( select: Select ) => select( CORE_UI ).getValue( PDF_GENERATING_KEY ),
46+
[]
47+
);
48+
49+
const { setValue } = useDispatch( CORE_UI );
50+
51+
const onDownloadClick = useCallback( () => {
52+
// Temporary stub: toggle the "generating" flag so the notice renders.
53+
// To be replaced by the real orchestrator handoff in #12537.
54+
setValue( PDF_GENERATING_KEY, true );
55+
}, [ setValue ] );
56+
57+
return (
58+
<footer className="googlesitekit-selection-panel-footer googlesitekit-pdf-download-panel__footer">
59+
<div className="googlesitekit-selection-panel-footer__content">
60+
<div className="googlesitekit-selection-panel-footer__actions">
61+
{ /* @ts-expect-error - The `Button` component is not typed yet. */ }
62+
<Button onClick={ closePanel } tertiary>
63+
{ __( 'Cancel', 'google-site-kit' ) }
64+
</Button>
65+
{ /* @ts-expect-error - The `Button` component is not typed yet. */ }
66+
<Button
67+
onClick={ onDownloadClick }
68+
disabled={ ! hasSelection || !! isGenerating }
69+
>
70+
{ __( 'Download report', 'google-site-kit' ) }
71+
</Button>
72+
</div>
73+
</div>
74+
</footer>
75+
);
76+
};
77+
78+
export default Footer;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* PDF Sections Selection Panel Header.
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 { FC } from 'react';
23+
24+
/**
25+
* WordPress dependencies
26+
*/
27+
import { __ } from '@wordpress/i18n';
28+
29+
/**
30+
* Internal dependencies
31+
*/
32+
import { SelectionPanelHeader } from '@/js/components/SelectionPanel';
33+
import P from '@/js/components/Typography/P';
34+
35+
interface HeaderProps {
36+
closePanel: () => void;
37+
}
38+
39+
const Header: FC< HeaderProps > = ( { closePanel } ) => {
40+
return (
41+
<SelectionPanelHeader
42+
title={ __( 'Download your Site Kit report', 'google-site-kit' ) }
43+
onCloseClick={ closePanel }
44+
>
45+
{ /* @ts-expect-error - The `P` component's `size` prop has a default for non-mobile devices and is not currently inferred as optional. */ }
46+
<P>
47+
{ __(
48+
'Generate a PDF featuring the current metrics from your dashboard. The report reflects the same date range selected in your dashboard, excluding data from the current day to ensure accuracy.',
49+
'google-site-kit'
50+
) }
51+
</P>
52+
{ /* @ts-expect-error - The `P` component's `size` prop has a default for non-mobile devices and is not currently inferred as optional. */ }
53+
<P>
54+
{ __(
55+
'Select the topics you would like to include in your report:',
56+
'google-site-kit'
57+
) }
58+
</P>
59+
</SelectionPanelHeader>
60+
);
61+
};
62+
63+
export default Header;

0 commit comments

Comments
 (0)