Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Reporting/Dashboard] Use Chromium for print-optimized PDFs #130546

Merged
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
bc8439b
first version of semi-sane results
jloleysens Mar 21, 2022
340daa6
getting a bit more sophisticated
jloleysens Mar 21, 2022
3180772
wip on footer, page numbers not working, but logo working
jloleysens Mar 21, 2022
4d0c0eb
re-work PoC for readability, added a lot of comments
jloleysens Mar 24, 2022
da4789d
change up formatting for readability
jloleysens Mar 24, 2022
7744c26
added comment
jloleysens Mar 24, 2022
3fde026
remove some comments and remove HACK
jloleysens Apr 11, 2022
2a6f0f2
use page.pdf function
jloleysens Apr 12, 2022
036346f
remove controls from shared PoC ui
jloleysens Apr 13, 2022
52d0c1b
preserveDrawingBuffer fix for maps, needs review
jloleysens Apr 13, 2022
2f59cf7
minor clean up
jloleysens Apr 13, 2022
6c08a1c
update sass
jloleysens Apr 13, 2022
285a213
clean up experimental code
jloleysens Apr 13, 2022
fb461fd
moved a few files around to get this ready for review
jloleysens Apr 19, 2022
9f32cba
added appservices as print media code owners
jloleysens Apr 19, 2022
3eb2002
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Apr 19, 2022
21f99d6
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine Apr 19, 2022
da4f4e9
added PDFJS to get num pages
jloleysens Apr 20, 2022
3ca4e21
fix getting page number using pdfjs-dist
jloleysens Apr 20, 2022
eb41323
update inline snapshot
jloleysens Apr 20, 2022
02a03e7
Revert "update inline snapshot"
jloleysens Apr 20, 2022
0631db1
do not create a new page at the very end
jloleysens Apr 20, 2022
c140c25
major overhaul, rather use puppeteers footerTemplate and headerTempla…
jloleysens Apr 20, 2022
39cfe72
add TODO
jloleysens Apr 20, 2022
c2bade1
update test fixture
jloleysens Apr 21, 2022
2258154
update doc comment
jloleysens Apr 21, 2022
90fdf81
remove whitespace
jloleysens Apr 21, 2022
1fa5854
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine Apr 21, 2022
c3b5500
fix missing time range from print PDF header and make size much smaller
jloleysens Apr 21, 2022
4704274
update tests
jloleysens Apr 21, 2022
fda90d2
update test
jloleysens Apr 22, 2022
ea0096b
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine Apr 22, 2022
1de112a
try out slash instead of nbsp
jloleysens Apr 22, 2022
f8a4df8
Revert "try out slash instead of nbsp"
jloleysens Apr 22, 2022
6a411f6
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine Apr 22, 2022
6a5f4fd
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine Apr 25, 2022
13125e0
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine Apr 29, 2022
74629b6
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine May 3, 2022
90619d0
implement ability to inject logo using handlebars templates
jloleysens May 4, 2022
dd4995f
move assets to shared location
jloleysens May 4, 2022
d678010
fix injecting of values via handlebars and minor style tweaks for 3rd…
jloleysens May 4, 2022
3da4202
inject a few more values to the footer
jloleysens May 4, 2022
1873a65
update casing check
jloleysens May 5, 2022
50fcf73
use locales version of headless chromium zip
jloleysens May 5, 2022
efe61c3
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine May 5, 2022
a0cf2ca
fix tests and update sizing of logos
jloleysens May 5, 2022
b7e64e9
use locales version for arm64 too
jloleysens May 5, 2022
0530b6a
fix jest test
jloleysens May 5, 2022
18f3bee
Merge branch 'main' of github.com:elastic/kibana into reporting/use-c…
jloleysens May 9, 2022
4e1c83f
fix types
jloleysens May 9, 2022
a320172
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine May 9, 2022
7d5ac9e
Merge branch 'main' into reporting/use-chromium-to-print-pdf-part-1
kibanamachine May 9, 2022
dbcfc90
made pdf capture check stricter
jloleysens May 9, 2022
688b1b8
fix PDF generation issue due to query bar rendering content that caus…
jloleysens May 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
/x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-services
/x-pack/plugins/runtime_fields @elastic/kibana-app-services
/x-pack/test/search_sessions_integration/ @elastic/kibana-app-services
/src/plugins/dashboard/public/application/embeddable/viewport/print_media @elastic/kibana-app-services

### Observability Plugins

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@
"p-retry": "^4.2.0",
"papaparse": "^5.2.0",
"pbf": "3.2.1",
"pdfjs-dist": "^2.13.216",
"pdfmake": "^0.2.4",
"peggy": "^1.2.0",
"pluralize": "3.1.0",
Expand Down
12 changes: 6 additions & 6 deletions src/dev/precommit_hook/casing_check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ export const TEMPORARILY_IGNORED_PATHS = [
'x-pack/plugins/monitoring/public/icons/health-green.svg',
'x-pack/plugins/monitoring/public/icons/health-red.svg',
'x-pack/plugins/monitoring/public/icons/health-yellow.svg',
'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Medium.ttf',
'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Regular.ttf',
'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Italic.ttf',
'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Medium.ttf',
'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Regular.ttf',
'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/img/logo-grey.png',
'x-pack/plugins/screenshotting/server/assets/fonts/noto/NotoSansCJKtc-Medium.ttf',
'x-pack/plugins/screenshotting/server/assets/fonts/noto/NotoSansCJKtc-Regular.ttf',
'x-pack/plugins/screenshotting/server/assets/fonts/roboto/Roboto-Italic.ttf',
'x-pack/plugins/screenshotting/server/assets/fonts/roboto/Roboto-Medium.ttf',
'x-pack/plugins/screenshotting/server/assets/fonts/roboto/Roboto-Regular.ttf',
'x-pack/plugins/screenshotting/server/assets/img/logo-grey.png',
];
13 changes: 6 additions & 7 deletions src/plugins/dashboard/public/application/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,12 @@ export function DashboardApp({
<>
{isCompleteDashboardAppState(dashboardAppState) && (
<>
{!printMode && (
<DashboardTopNav
redirectTo={redirectTo}
embedSettings={embedSettings}
dashboardAppState={dashboardAppState}
/>
)}
<DashboardTopNav
printMode={printMode}
redirectTo={redirectTo}
embedSettings={embedSettings}
dashboardAppState={dashboardAppState}
/>

{dashboardAppState.savedDashboard.outcome === 'conflict' &&
dashboardAppState.savedDashboard.id &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,68 @@
.printViewport {
&__vis {
height: 600px; // These values might need to be passed in as dimensions for the report. I.e., print should use layout dimensions.
width: 975px;
@import './print_media/styling/index';

// Some vertical space between vis, but center horizontally
margin: 10px auto;
$visualisationsPerPage: 2;
$visPadding: 4mm;

/*
We set the same visual padding on the browser and print versions of the UI so that
we don't hit a race condition where padding is being updated while the print image
is being formed. This can result in parts of the vis being cut out.
*/
@mixin visualizationPadding {
// Open space from page margin
padding-left: $visPadding;
padding-right: $visPadding;

// Last vis on the page
&:nth-child(#{$visualisationsPerPage}n) {
page-break-after: always;
padding-top: $visPadding;
padding-bottom: $visPadding;
}

&:last-child {
page-break-after: avoid;
}
}

@media screen, projection {
.printViewport {
&__vis {
@include visualizationPadding();

& .embPanel__header button {
display: none;
}

margin: $euiSizeL auto;
height: calc(#{$a4PageContentHeight} / #{$visualisationsPerPage});
width: $a4PageContentWidth;
padding: $visPadding;
}
}
}

@media print {
.printViewport {
&__vis {
@include visualizationPadding();

height: calc(#{$a4PageContentHeight} / #{$visualisationsPerPage});
width: $a4PageContentWidth;

& .euiPanel {
box-shadow: none !important;
}

& .embPanel__header button {
display: none;
}

page-break-inside: avoid;

& * {
overflow: hidden !important;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Print media

The code here is designed to be movable outside the domain of Dashboard. Currently,
the components and styles are only used by Dashboard but we may choose to move them to,
for example, a Kibana package in the future.

Any changes to this code must be tested by generating a print-optimized PDF in dashboard.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@import './vars';

/*
This styling contains utility and minimal layout styles to help plugins create
print-ready HTML.

Observations:
1. We currently do not control the user-agent's header and footer content
(including the style of fonts) for client-side printing.

2. Page box model is quite different from what we have in browsers - page
margins define where the "no-mans-land" exists for actual content. Moving
content into this space by, for example setting negative margins resulted
in slightly unpredictable behaviour because the browser wants to either
move this content to another page or it may get split across two
pages.

3. page-break-* is your friend!
*/

// Currently we cannot control or style the content the browser places in
// margins, this might change in the future:
// See https://drafts.csswg.org/css-page-3/#margin-boxes
@page {
size: A4;
orientation: portrait;
margin: 0;
margin-top: $a4PageHeaderHeight;
margin-bottom: $a4PageFooterHeight;
}

@media print {

html {
background-color: #FFF;
}

// It is good practice to show the full URL in the final, printed output
a[href]:after {
content: ' [' attr(href) ']';
}

figure {
page-break-inside: avoid;
}

* {
-webkit-print-color-adjust: exact !important; /* Chrome, Safari, Edge */
color-adjust: exact !important; /*Firefox*/
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

$a4PageHeight: 297mm;
$a4PageWidth: 210mm;
$a4PageMargin: 0;
$a4PagePadding: 0;
$a4PageHeaderHeight: 15mm;
$a4PageFooterHeight: 20mm;

$a4PageContentHeight: $a4PageHeight - $a4PageHeaderHeight - $a4PageFooterHeight;
$a4PageContentWidth: $a4PageWidth;
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,7 @@ export const useDashboardAppState = ({
savedDashboard,
});

// Backwards compatible way of detecting that we are taking a screenshot
const legacyPrintLayoutDetected =
const printLayoutDetected =
screenshotModeService?.isScreenshotMode() &&
screenshotModeService.getScreenshotContext('layout') === 'print';

Expand All @@ -194,8 +193,7 @@ export const useDashboardAppState = ({
...initialDashboardStateFromUrl,
...forwardedAppState,

// if we are in legacy print mode, dashboard needs to be in print viewMode
...(legacyPrintLayoutDetected ? { viewMode: ViewMode.PRINT } : {}),
...(printLayoutDetected ? { viewMode: ViewMode.PRINT } : {}),

// if there is an incoming embeddable, dashboard always needs to be in edit mode to receive it.
...(incomingEmbeddable ? { viewMode: ViewMode.EDIT } : {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface DashboardTopNavProps {
dashboardAppState: CompleteDashboardAppState;
embedSettings?: DashboardEmbedSettings;
redirectTo: DashboardRedirect;
printMode: boolean;
}

const LabsFlyout = withSuspense(LazyLabsFlyout, null);
Expand All @@ -90,6 +91,7 @@ export function DashboardTopNav({
dashboardAppState,
embedSettings,
redirectTo,
printMode,
}: DashboardTopNavProps) {
const {
core,
Expand Down Expand Up @@ -488,7 +490,9 @@ export function DashboardTopNav({

const isFullScreenMode = dashboardState.fullScreenMode;
const showTopNavMenu = shouldShowNavBarComponent(Boolean(embedSettings?.forceShowTopNavMenu));
const showQueryInput = shouldShowNavBarComponent(Boolean(embedSettings?.forceShowQueryInput));
const showQueryInput = shouldShowNavBarComponent(
Boolean(embedSettings?.forceShowQueryInput || printMode)
);
const showDatePicker = shouldShowNavBarComponent(Boolean(embedSettings?.forceShowDatePicker));
const showFilterBar = shouldShowFilterBar(Boolean(embedSettings?.forceHideFilterBar));
const showQueryBar = showQueryInput || showDatePicker;
Expand Down Expand Up @@ -535,6 +539,7 @@ export function DashboardTopNav({
useDefaultBehaviors: true,
savedQuery: state.savedQuery,
savedQueryId: dashboardState.savedQuery,
visible: printMode !== true,
onQuerySubmit: (_payload, isUpdate) => {
if (isUpdate === false) {
dashboardAppState.$triggerDashboardRefresh.next({ force: true });
Expand Down Expand Up @@ -585,10 +590,10 @@ export function DashboardTopNav({
return (
<>
<TopNavMenu {...getNavBarProps()} />
{isLabsEnabled && isLabsShown ? (
{!printMode && isLabsEnabled && isLabsShown ? (
<LabsFlyout solutions={['dashboard']} onClose={() => setIsLabsShown(false)} />
) : null}
{dashboardState.viewMode !== ViewMode.VIEW ? (
{dashboardState.viewMode !== ViewMode.VIEW && !printMode ? (
<>
<EuiHorizontalRule margin="none" />
<SolutionToolbar isDarkModeEnabled={IS_DARK_THEME}>
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/navigation/public/top_nav_menu/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
}
}

.kbnTopNavMenu__wrapper {
&--hidden {
display: none;
}
}

.kbnTopNavMenu__badgeWrapper {
display: flex;
align-items: baseline;
Expand Down
7 changes: 5 additions & 2 deletions src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type TopNavMenuProps = StatefulSearchBarProps &
showFilterBar?: boolean;
unifiedSearch?: UnifiedSearchPublicPluginStart;
className?: string;
visible?: boolean;
/**
* If provided, the menu part of the component will be rendered as a portal inside the given mount point.
*
Expand Down Expand Up @@ -105,9 +106,11 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null {
}

function renderLayout() {
const { setMenuMountPoint } = props;
const { setMenuMountPoint, visible } = props;
const menuClassName = classNames('kbnTopNavMenu', props.className);
const wrapperClassName = 'kbnTopNavMenu__wrapper';
const wrapperClassName = classNames('kbnTopNavMenu__wrapper', {
'kbnTopNavMenu__wrapper--hidden': visible === false,
});
if (setMenuMountPoint) {
return (
<>
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/maps/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"mapsEms",
"savedObjects",
"share",
"presentationUtil"
"presentationUtil",
"screenshotMode"
],
"optionalPlugins": [
"cloud",
Expand Down
12 changes: 11 additions & 1 deletion x-pack/plugins/maps/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type { SecurityPluginStart } from '@kbn/security-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { CloudSetup } from '@kbn/cloud-plugin/public';
import type { LensPublicSetup } from '@kbn/lens-plugin/public';
import { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/public';
import {
createRegionMapFn,
regionMapRenderer,
Expand Down Expand Up @@ -88,6 +89,7 @@ export interface MapsPluginSetupDependencies {
share: SharePluginSetup;
licensing: LicensingPluginSetup;
usageCollection?: UsageCollectionSetup;
screenshotMode: ScreenshotModePluginSetup;
}

export interface MapsPluginStartDependencies {
Expand Down Expand Up @@ -144,7 +146,15 @@ export class MapsPlugin
registerLicensedFeatures(plugins.licensing);

const config = this._initializerContext.config.get<MapsConfigType>();
setMapAppConfig(config);
setMapAppConfig({
...config,

// Override this when we know we are taking a screenshot (i.e. no user interaction)
// to avoid a blank-canvas issue when rendering maps on a PDF
preserveDrawingBuffer: plugins.screenshotMode.isScreenshotMode()
? true
: config.preserveDrawingBuffer,
});

const locator = plugins.share.url.locators.create(
new MapsAppLocatorDefinition({
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/maps/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
{ "path": "../../../src/plugins/kibana_react/tsconfig.json" },
{ "path": "../../../src/plugins/kibana_utils/tsconfig.json" },
{ "path": "../../../src/plugins/shared_ux/tsconfig.json" },
{ "path": "../../../src/plugins/screenshot_mode/tsconfig.json" },
{ "path": "../cloud/tsconfig.json" },
{ "path": "../features/tsconfig.json" },
{ "path": "../lens/tsconfig.json" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { ConfigType } from '../../config';
import { allowRequest } from '../network_policy';
import { stripUnsafeHeaders } from './strip_unsafe_headers';
import { getFooterTemplate, getHeaderTemplate } from './templates';

export type Context = Record<string, unknown>;

Expand Down Expand Up @@ -155,6 +156,18 @@ export class HeadlessChromiumDriver {
return !this.page.isClosed();
}

async printA4Pdf({ title, logo }: { title: string; logo?: string }): Promise<Buffer> {
return this.page.pdf({
format: 'a4',
preferCSSPageSize: true,
scale: 1,
landscape: false,
displayHeaderFooter: true,
headerTemplate: await getHeaderTemplate({ title }),
footerTemplate: await getFooterTemplate({ logo }),
});
}

/*
* Call Page.screenshot and return a base64-encoded string of the image
*/
Expand Down
Loading