diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.js b/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.ts similarity index 99% rename from x-pack/plugins/reporting/export_types/printable_pdf/common/constants.js rename to x-pack/plugins/reporting/export_types/printable_pdf/common/constants.ts index 8044c7c40ba2f6..ddc678592760ab 100644 --- a/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.js +++ b/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.ts @@ -7,4 +7,4 @@ export const LayoutTypes = { PRESERVE_LAYOUT: 'preserve_layout', PRINT: 'print', -}; \ No newline at end of file +}; diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js index b68a7c24e16271..198a4d7dde0f19 100644 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js @@ -11,7 +11,7 @@ import { pdf } from './pdf'; import { groupBy } from 'lodash'; import { oncePerServer } from '../../../../server/lib/once_per_server'; import { screenshotsObservableFactory } from './screenshots'; -import { getLayoutFactory } from './layouts'; +import { createLayout } from './layouts'; const getTimeRange = (urlScreenshots) => { const grouped = groupBy(urlScreenshots.map(u => u.timeRange)); @@ -31,7 +31,6 @@ const formatDate = (date, timezone) => { function generatePdfObservableFn(server) { const screenshotsObservable = screenshotsObservableFactory(server); const captureConcurrency = 1; - const getLayout = getLayoutFactory(server); const urlScreenshotsObservable = (urls, headers, layout) => { return Rx.from(urls).pipe( @@ -68,7 +67,9 @@ function generatePdfObservableFn(server) { return function generatePdfObservable(title, urls, browserTimezone, headers, layoutParams, logo) { - const layout = getLayout(layoutParams); + + const layout = createLayout(server, layoutParams); + const screenshots$ = urlScreenshotsObservable(urls, headers, layout); return screenshots$.pipe( diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts new file mode 100644 index 00000000000000..1863c2050fb446 --- /dev/null +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KbnServer, Size } from '../../../../../types'; +import { LayoutTypes } from '../../../common/constants'; +import { Layout } from './layout'; +import { PreserveLayout } from './preserve_layout'; +import { PrintLayout } from './print_layout'; + +interface LayoutParams { + id: string; + dimensions: Size; +} + +export function createLayout(server: KbnServer, layoutParams: LayoutParams): Layout { + if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) { + return new PreserveLayout(layoutParams.id, layoutParams.dimensions); + } + + // this is the default because some jobs won't have anything specified + return new PrintLayout(server, layoutParams.id); +} diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js deleted file mode 100644 index 14c593071b617f..00000000000000 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LayoutTypes } from '../../../common/constants'; -import { preserveLayoutFactory } from './preserve_layout'; -import { printLayoutFactory } from './print'; - -export function getLayoutFactory(server) { - return function getLayout(layoutParams) { - if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) { - return preserveLayoutFactory(server, layoutParams); - } - - // this is the default because some jobs won't have anything specified - return printLayoutFactory(server, layoutParams); - }; -} diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts new file mode 100644 index 00000000000000..fd35485779ba02 --- /dev/null +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createLayout } from './create_layout'; +export { PrintLayout } from './print_layout'; +export { PreserveLayout } from './preserve_layout'; diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts new file mode 100644 index 00000000000000..eb7caf10307f32 --- /dev/null +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Size } from '../../../../../types'; +import { ViewZoomWidthHeight } from './types'; + +export interface PageSizeParams { + pageMarginTop: number; + pageMarginBottom: number; + pageMarginWidth: number; + tableBorderWidth: number; + headingHeight: number; + subheadingHeight: number; +} + +export interface PdfImageSize { + width: number; + height?: number; +} + +export abstract class Layout { + public id: string = ''; + + constructor(id: string) { + this.id = id; + } + + public abstract getPdfImageSize(): PdfImageSize; + + public abstract getPdfPageOrientation(): string | undefined; + + public abstract getPdfPageSize(pageSizeParams: PageSizeParams): string | Size; + + public abstract getViewport(itemsCount: number): ViewZoomWidthHeight; + + public abstract getBrowserZoom(): number; + + public abstract getBrowserViewport(): Size; + + public abstract getCssOverridesPath(): string; +} diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js deleted file mode 100644 index cee717553e1018..00000000000000 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import path from 'path'; - -// you'll notice that we aren't passing the zoom at this time, while it'd be possible to use -// window.pixelDensity to figure out what the current user is seeing, if they're going to send the -// PDF to someone else, I can see there being benefit to using a higher pixel density, so we're -// going to leave this hard-coded for the time being -export function preserveLayoutFactory(server, { dimensions: { height, width }, zoom = 2 }) { - const scaledHeight = height * zoom; - const scaledWidth = width * zoom; - - return { - getCssOverridesPath() { - return path.join(__dirname, 'preserve_layout.css'); - }, - - getBrowserViewport() { - return { - height: scaledHeight, - width: scaledWidth, - }; - }, - - getBrowserZoom() { - return zoom; - }, - - getViewport() { - return { - height: scaledHeight, - width: scaledWidth, - zoom - }; - }, - - getPdfImageSize() { - return { - height: height, - width: width, - }; - }, - - getPdfPageOrientation() { - return undefined; - }, - - getPdfPageSize({ pageMarginTop, pageMarginBottom, pageMarginWidth, tableBorderWidth, headingHeight, subheadingHeight }) { - return { - height: height + pageMarginTop + pageMarginBottom + (tableBorderWidth * 2) + headingHeight + subheadingHeight, - width: width + (pageMarginWidth * 2) + (tableBorderWidth * 2), - }; - }, - - groupCount: 1, - - selectors: { - screenshot: '[data-shared-items-container]', - renderComplete: '[data-shared-item]', - itemsCountAttribute: 'data-shared-items-count', - timefilterFromAttribute: 'data-shared-timefilter-from', - timefilterToAttribute: 'data-shared-timefilter-to', - } - }; -} \ No newline at end of file diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts new file mode 100644 index 00000000000000..ea58a09c5550e5 --- /dev/null +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import path from 'path'; +import { Size } from '../../../../../types'; +import { Layout, PageSizeParams } from './layout'; + +const ZOOM: number = 2; + +export class PreserveLayout extends Layout { + public readonly selectors = { + screenshot: '[data-shared-items-container]', + renderComplete: '[data-shared-item]', + itemsCountAttribute: 'data-shared-items-count', + timefilterFromAttribute: 'data-shared-timefilter-from', + timefilterToAttribute: 'data-shared-timefilter-to', + }; + + public readonly groupCount = 1; + private readonly height: number; + private readonly width: number; + private readonly scaledHeight: number; + private readonly scaledWidth: number; + + constructor(id: string, size: Size) { + super(id); + this.height = size.height; + this.width = size.width; + this.scaledHeight = size.height * ZOOM; + this.scaledWidth = size.width * ZOOM; + } + + public getCssOverridesPath() { + return path.join(__dirname, 'preserve_layout.css'); + } + + public getBrowserViewport() { + return { + height: this.scaledHeight, + width: this.scaledWidth, + }; + } + + public getBrowserZoom() { + return ZOOM; + } + + public getViewport() { + return { + height: this.scaledHeight, + width: this.scaledWidth, + zoom: ZOOM, + }; + } + + public getPdfImageSize() { + return { + height: this.height, + width: this.width, + }; + } + + public getPdfPageOrientation() { + return undefined; + } + + public getPdfPageSize(pageSizeParams: PageSizeParams) { + return { + height: + this.height + + pageSizeParams.pageMarginTop + + pageSizeParams.pageMarginBottom + + pageSizeParams.tableBorderWidth * 2 + + pageSizeParams.headingHeight + + pageSizeParams.subheadingHeight, + width: this.width + pageSizeParams.pageMarginWidth * 2 + pageSizeParams.tableBorderWidth * 2, + }; + } +} diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js deleted file mode 100644 index 1986a2e5a88898..00000000000000 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import path from 'path'; - -export function printLayoutFactory(server) { - const config = server.config(); - const captureConfig = config.get('xpack.reporting.capture'); - - const selectors = { - screenshot: '[data-shared-item]', - renderComplete: '[data-shared-item]', - itemsCountAttribute: 'data-shared-items-count', - timefilterFromAttribute: 'data-shared-timefilter-from', - timefilterToAttribute: 'data-shared-timefilter-to', - }; - - return { - - getCssOverridesPath() { - return path.join(__dirname, 'print.css'); - }, - - getBrowserViewport() { - return captureConfig.viewport; - }, - - getBrowserZoom() { - return captureConfig.zoom; - }, - - getViewport(itemsCount) { - return { - zoom: captureConfig.zoom, - width: captureConfig.viewport.width, - height: captureConfig.viewport.height * itemsCount, - }; - }, - - async positionElements(browser) { - const elementSize = { - width: captureConfig.viewport.width / captureConfig.zoom, - height: captureConfig.viewport.height / captureConfig.zoom - }; - - await browser.evaluate({ - fn: function (selector, height, width) { - const visualizations = document.querySelectorAll(selector); - const visualizationsLength = visualizations.length; - - for (let i = 0; i < visualizationsLength; i++) { - const visualization = visualizations[i]; - const style = visualization.style; - style.position = 'fixed'; - style.top = `${height * i}px`; - style.left = 0; - style.width = `${width}px`; - style.height = `${height}px`; - style.zIndex = 1; - style.backgroundColor = 'inherit'; - } - }, - args: [selectors.screenshot, elementSize.height, elementSize.width], - }); - }, - - getPdfImageSize() { - return { - width: 500, - }; - }, - - getPdfPageOrientation() { - return 'portrait'; - }, - - getPdfPageSize() { - return 'A4'; - }, - - groupCount: 2, - - selectors - - }; -} \ No newline at end of file diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts new file mode 100644 index 00000000000000..44f65d35e6d9cc --- /dev/null +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import path from 'path'; +import { KbnServer, Size } from '../../../../../types'; +import { Layout } from './layout'; +import { CaptureConfig } from './types'; + +type EvalArgs = any[]; + +interface EvaluateOptions { + // 'fn' is a function in string form to avoid tslint from auto formatting it into a version not + // underfood by transform_fn safeWrap. + fn: ((...evalArgs: EvalArgs) => any); + args: EvalArgs; // Arguments to be passed into the function defined by fn. +} + +interface BrowserClient { + evaluate: (evaluateOptions: EvaluateOptions) => void; +} + +export class PrintLayout extends Layout { + public selectors = { + screenshot: '[data-shared-item]', + renderComplete: '[data-shared-item]', + itemsCountAttribute: 'data-shared-items-count', + timefilterFromAttribute: 'data-shared-timefilter-from', + timefilterToAttribute: 'data-shared-timefilter-to', + }; + + public readonly groupCount = 2; + + private captureConfig: CaptureConfig; + + constructor(server: KbnServer, id: string) { + super(id); + this.captureConfig = server.config().get('xpack.reporting.capture'); + } + + public getCssOverridesPath() { + return path.join(__dirname, 'print.css'); + } + + public getBrowserViewport() { + return this.captureConfig.viewport; + } + + public getBrowserZoom() { + return this.captureConfig.zoom; + } + + public getViewport(itemsCount: number) { + return { + zoom: this.captureConfig.zoom, + width: this.captureConfig.viewport.width, + height: this.captureConfig.viewport.height * itemsCount, + }; + } + + public async positionElements(browser: BrowserClient): Promise { + const elementSize: Size = { + width: this.captureConfig.viewport.width / this.captureConfig.zoom, + height: this.captureConfig.viewport.height / this.captureConfig.zoom, + }; + const evalOptions: EvaluateOptions = { + fn: (selector: string, height: number, width: number) => { + const visualizations = document.querySelectorAll(selector) as NodeListOf; + const visualizationsLength = visualizations.length; + + for (let i = 0; i < visualizationsLength; i++) { + const visualization = visualizations[i]; + const style = visualization.style; + style.position = 'fixed'; + style.top = `${height * i}px`; + style.left = '0'; + style.width = `${width}px`; + style.height = `${height}px`; + style.zIndex = '1'; + style.backgroundColor = 'inherit'; + } + }, + args: [this.selectors.screenshot, elementSize.height, elementSize.width], + }; + + await browser.evaluate(evalOptions); + } + + public getPdfImageSize() { + return { + width: 500, + }; + } + + public getPdfPageOrientation() { + return 'portrait'; + } + + public getPdfPageSize() { + return 'A4'; + } +} diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts new file mode 100644 index 00000000000000..0ade1093312ea2 --- /dev/null +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Size } from '../../../../../types'; + +export interface CaptureConfig { + zoom: number; + viewport: Size; +} + +export interface ViewZoomWidthHeight { + zoom: number; + width: number; + height: number; +} diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts new file mode 100644 index 00000000000000..3e5f243fc82525 --- /dev/null +++ b/x-pack/plugins/reporting/types.d.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export interface KbnServer { + config: () => ConfigObject; +} + +export interface ConfigObject { + get: (path: string) => any; +} + +export interface Size { + width: number; + height: number; +} + +export interface Logger { + debug: (message: string) => void; + error: (message: string) => void; + warning: (message: string) => void; +}