From 54dc14822672d6bb974a222d7a87004440a02ddf Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 27 Apr 2020 14:25:26 -0700 Subject: [PATCH] [Reporting] Remove boilerplate needed to get the`getScreenshots` function (#64269) * expose reportingCore in ReportingSetup for apps * improve tests Co-authored-by: Elastic Machine --- .../common/lib/screenshots/index.ts | 2 +- .../common/lib/screenshots/observable.ts | 10 ++- .../png/server/execute_job/index.test.ts | 78 ++++++++++--------- .../png/server/execute_job/index.ts | 4 +- .../png/server/lib/generate_png.ts | 14 ++-- .../server/execute_job/index.test.ts | 71 +++++++++++++---- .../printable_pdf/server/execute_job/index.ts | 4 +- .../printable_pdf/server/lib/generate_pdf.ts | 16 ++-- .../legacy/plugins/reporting/server/core.ts | 12 ++- .../legacy/plugins/reporting/server/index.ts | 2 + .../reporting/server/lib/create_queue.ts | 16 ++-- .../reporting/server/lib/enqueue_job.ts | 12 +-- 12 files changed, 148 insertions(+), 93 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index 5a04f1a497ab..0d75b067fbe6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { screenshotsObservableFactory } from './observable'; +export { screenshotsObservableFactory, ScreenshotsObservableFn } from './observable'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts index 519a3289395b..c6861ae1d17a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/observable.ts @@ -21,10 +21,18 @@ import { waitForVisualizations } from './wait_for_visualizations'; const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200; const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800; +export type ScreenshotsObservableFn = ({ + logger, + urls, + conditionalHeaders, + layout, + browserTimezone, +}: ScreenshotObservableOpts) => Rx.Observable; + export function screenshotsObservableFactory( captureConfig: CaptureConfig, browserDriverFactory: HeadlessChromiumDriverFactory -) { +): ScreenshotsObservableFn { return function screenshotsObservable({ logger, urls, diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts index c9cba64a732b..9d3deda5d98b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts @@ -5,14 +5,14 @@ */ import * as Rx from 'rxjs'; -import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers'; -import { cryptoFactory } from '../../../../server/lib/crypto'; -import { executeJobFactory } from './index'; -import { generatePngObservableFactory } from '../lib/generate_png'; import { CancellationToken } from '../../../../common/cancellation_token'; +import { ReportingCore } from '../../../../server'; import { LevelLogger } from '../../../../server/lib'; -import { ReportingCore, CaptureConfig } from '../../../../server/types'; +import { cryptoFactory } from '../../../../server/lib/crypto'; +import { createMockReportingCore } from '../../../../test_helpers'; import { JobDocPayloadPNG } from '../../types'; +import { generatePngObservableFactory } from '../lib/generate_png'; +import { executeJobFactory } from './index'; jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); @@ -31,8 +31,6 @@ const mockLoggerFactory = { }; const getMockLogger = () => new LevelLogger(mockLoggerFactory); -const captureConfig = {} as CaptureConfig; - const mockEncryptionKey = 'abcabcsecuresecret'; const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); @@ -46,10 +44,13 @@ beforeEach(async () => { 'server.basePath': '/sbp', }; const reportingConfig = { + index: '.reporting-2018.10.10', encryptionKey: mockEncryptionKey, 'kibanaServer.hostname': 'localhost', 'kibanaServer.port': 5601, 'kibanaServer.protocol': 'http', + 'queue.indexInterval': 'daily', + 'queue.timeout': Infinity, }; const mockReportingConfig = { get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')], @@ -74,13 +75,8 @@ afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset()); test(`passes browserTimezone to generatePng`, async () => { const encryptedHeaders = await encryptHeaders({}); - const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); - - const generatePngObservable = generatePngObservableFactory( - captureConfig, - mockBrowserDriverFactory - ); - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); + const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; + generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; @@ -94,26 +90,43 @@ test(`passes browserTimezone to generatePng`, async () => { cancellationToken ); - expect(generatePngObservable).toBeCalledWith( - expect.any(LevelLogger), - 'http://localhost:5601/sbp/app/kibana#/something', - browserTimezone, - expect.anything(), - undefined - ); + expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + LevelLogger { + "_logger": Object { + "get": [MockFunction], + }, + "_tags": Array [ + "PNG", + "execute", + "pngJobId", + ], + "warning": [Function], + }, + "http://localhost:5601/sbp/app/kibana#/something", + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, + }, + undefined, + ], + ] + `); }); test(`returns content_type of application/png`, async () => { const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); - const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); - - const generatePngObservable = generatePngObservableFactory( - captureConfig, - mockBrowserDriverFactory - ); - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); + const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; + generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); const { content_type: contentType } = await executeJob( 'pngJobId', @@ -126,13 +139,8 @@ test(`returns content_type of application/png`, async () => { test(`returns content of generatePng getBuffer base64 encoded`, async () => { const testContent = 'test content'; - const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); - - const generatePngObservable = generatePngObservableFactory( - captureConfig, - mockBrowserDriverFactory - ); - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); + const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; + generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index 113da92d1862..0ffd42d0b52f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -25,13 +25,11 @@ export const executeJobFactory: QueuedPngExecutorFactory = async function execut parentLogger: Logger ) { const config = reporting.getConfig(); - const captureConfig = config.get('capture'); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { - const browserDriverFactory = await reporting.getBrowserDriverFactory(); - const generatePngObservable = generatePngObservableFactory(captureConfig, browserDriverFactory); + const generatePngObservable = await generatePngObservableFactory(reporting); const jobLogger = logger.clone([jobId]); const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index a15541d99f6f..c03ea170f76e 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -6,19 +6,15 @@ import * as Rx from 'rxjs'; import { map } from 'rxjs/operators'; +import { ReportingCore } from '../../../../server'; import { LevelLogger } from '../../../../server/lib'; -import { CaptureConfig } from '../../../../server/types'; -import { ConditionalHeaders, HeadlessChromiumDriverFactory } from '../../../../types'; +import { ConditionalHeaders } from '../../../../types'; import { LayoutParams } from '../../../common/layouts/layout'; import { PreserveLayout } from '../../../common/layouts/preserve_layout'; -import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; import { ScreenshotResults } from '../../../common/lib/screenshots/types'; -export function generatePngObservableFactory( - captureConfig: CaptureConfig, - browserDriverFactory: HeadlessChromiumDriverFactory -) { - const screenshotsObservable = screenshotsObservableFactory(captureConfig, browserDriverFactory); +export async function generatePngObservableFactory(reporting: ReportingCore) { + const getScreenshots = await reporting.getScreenshotsObservable(); return function generatePngObservable( logger: LevelLogger, @@ -32,7 +28,7 @@ export function generatePngObservableFactory( } const layout = new PreserveLayout(layoutParams.dimensions); - const screenshots$ = screenshotsObservable({ + const screenshots$ = getScreenshots({ logger, urls: [url], conditionalHeaders, diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts index c3c0d38584bc..ae18c0f4f9f4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts @@ -5,11 +5,11 @@ */ import * as Rx from 'rxjs'; -import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers'; +import { createMockReportingCore } from '../../../../test_helpers'; import { cryptoFactory } from '../../../../server/lib/crypto'; import { LevelLogger } from '../../../../server/lib'; import { CancellationToken } from '../../../../types'; -import { ReportingCore, CaptureConfig } from '../../../../server/types'; +import { ReportingCore } from '../../../../server'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { JobDocPayloadPDF } from '../../types'; import { executeJobFactory } from './index'; @@ -22,8 +22,6 @@ const cancellationToken = ({ on: jest.fn(), } as unknown) as CancellationToken; -const captureConfig = {} as CaptureConfig; - const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ error: jest.fn(), @@ -72,16 +70,64 @@ beforeEach(async () => { afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset()); +test(`passes browserTimezone to generatePdf`, async () => { + const encryptedHeaders = await encryptHeaders({}); + const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; + generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); + + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const browserTimezone = 'UTC'; + await executeJob( + 'pdfJobId', + getJobDocPayload({ + relativeUrl: '/app/kibana#/something', + browserTimezone, + headers: encryptedHeaders, + }), + cancellationToken + ); + + expect(generatePdfObservable.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + LevelLogger { + "_logger": Object { + "get": [MockFunction], + }, + "_tags": Array [ + "printable_pdf", + "execute", + "pdfJobId", + ], + "warning": [Function], + }, + undefined, + Array [ + "http://localhost:5601/sbp/app/kibana#/something", + ], + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, + }, + undefined, + false, + ], + ] + `); +}); + test(`returns content_type of application/pdf`, async () => { const logger = getMockLogger(); const executeJob = await executeJobFactory(mockReporting, logger); - const mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger); const encryptedHeaders = await encryptHeaders({}); - const generatePdfObservable = generatePdfObservableFactory( - captureConfig, - mockBrowserDriverFactory - ); + const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); const { content_type: contentType } = await executeJob( @@ -94,12 +140,7 @@ test(`returns content_type of application/pdf`, async () => { test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const testContent = 'test content'; - const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); - - const generatePdfObservable = generatePdfObservableFactory( - captureConfig, - mockBrowserDriverFactory - ); + const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const executeJob = await executeJobFactory(mockReporting, getMockLogger()); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index dbdccb6160a6..3d69042b6c7a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -26,14 +26,12 @@ export const executeJobFactory: QueuedPdfExecutorFactory = async function execut parentLogger: Logger ) { const config = reporting.getConfig(); - const captureConfig = config.get('capture'); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']); return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { - const browserDriverFactory = await reporting.getBrowserDriverFactory(); - const generatePdfObservable = generatePdfObservableFactory(captureConfig, browserDriverFactory); + const generatePdfObservable = await generatePdfObservableFactory(reporting); const jobLogger = logger.clone([jobId]); const process$: Rx.Observable = Rx.of(1).pipe( diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index a62b7ec7013a..c882ef682f95 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -7,12 +7,11 @@ import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; +import { ReportingCore } from '../../../../server'; import { LevelLogger } from '../../../../server/lib'; -import { CaptureConfig } from '../../../../server/types'; -import { ConditionalHeaders, HeadlessChromiumDriverFactory } from '../../../../types'; +import { ConditionalHeaders } from '../../../../types'; import { createLayout } from '../../../common/layouts'; import { LayoutInstance, LayoutParams } from '../../../common/layouts/layout'; -import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; import { ScreenshotResults } from '../../../common/lib/screenshots/types'; // @ts-ignore untyped module import { pdf } from './pdf'; @@ -27,11 +26,10 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { return null; }; -export function generatePdfObservableFactory( - captureConfig: CaptureConfig, - browserDriverFactory: HeadlessChromiumDriverFactory -) { - const screenshotsObservable = screenshotsObservableFactory(captureConfig, browserDriverFactory); +export async function generatePdfObservableFactory(reporting: ReportingCore) { + const config = reporting.getConfig(); + const captureConfig = config.get('capture'); + const getScreenshots = await reporting.getScreenshotsObservable(); return function generatePdfObservable( logger: LevelLogger, @@ -43,7 +41,7 @@ export function generatePdfObservableFactory( logo?: string ): Rx.Observable<{ buffer: Buffer; warnings: string[] }> { const layout = createLayout(captureConfig, layoutParams) as LayoutInstance; - const screenshots$ = screenshotsObservable({ + const screenshots$ = getScreenshots({ logger, urls, conditionalHeaders, diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index 9be61d091b00..0b243f13adb8 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -24,6 +24,10 @@ import { ReportingConfig, ReportingConfigType } from './config'; import { checkLicenseFactory, getExportTypesRegistry, LevelLogger } from './lib'; import { registerRoutes } from './routes'; import { ReportingSetupDeps } from './types'; +import { + screenshotsObservableFactory, + ScreenshotsObservableFn, +} from '../export_types/common/lib/screenshots'; interface ReportingInternalSetup { browserDriverFactory: HeadlessChromiumDriverFactory; @@ -95,13 +99,13 @@ export class ReportingCore { return (await this.getPluginStartDeps()).enqueueJob; } - public async getBrowserDriverFactory(): Promise { - return (await this.getPluginSetupDeps()).browserDriverFactory; - } - public getConfig(): ReportingConfig { return this.config; } + public async getScreenshotsObservable(): Promise { + const { browserDriverFactory } = await this.getPluginSetupDeps(); + return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); + } /* * Outside dependencies diff --git a/x-pack/legacy/plugins/reporting/server/index.ts b/x-pack/legacy/plugins/reporting/server/index.ts index c564963e363c..4288a37fe6ad 100644 --- a/x-pack/legacy/plugins/reporting/server/index.ts +++ b/x-pack/legacy/plugins/reporting/server/index.ts @@ -14,3 +14,5 @@ export const plugin = (context: PluginInitializerContext, config: ReportingConfi export { ReportingPlugin } from './plugin'; export { ReportingConfig, ReportingCore }; + +export { PreserveLayout, PrintLayout } from '../export_types/common/layouts'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 8230ee889ae0..560cc943ed45 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -16,21 +16,23 @@ export async function createQueueFactory( logger: Logger ): Promise { const config = reporting.getConfig(); - const queueConfig = config.get('queue'); - const index = config.get('index'); - const elasticsearch = await reporting.getElasticsearchService(); + const queueIndexInterval = config.get('queue', 'indexInterval'); + const queueTimeout = config.get('queue', 'timeout'); + const queueIndex = config.get('index'); + const isPollingEnabled = config.get('queue', 'pollEnabled'); + const elasticsearch = await reporting.getElasticsearchService(); const queueOptions = { - interval: queueConfig.indexInterval, - timeout: queueConfig.timeout, + interval: queueIndexInterval, + timeout: queueTimeout, dateSeparator: '.', client: elasticsearch.dataClient, logger: createTaggedLogger(logger, ['esqueue', 'queue-worker']), }; - const queue: ESQueueInstance = new Esqueue(index, queueOptions); + const queue: ESQueueInstance = new Esqueue(queueIndex, queueOptions); - if (queueConfig.pollEnabled) { + if (isPollingEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed const createWorker = createWorkerFactory(reporting, logger); await createWorker(queue); diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 5a062a693b46..3e87337dc435 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -26,12 +26,12 @@ interface ConfirmedJob { } export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger): EnqueueJobFn { - const logger = parentLogger.clone(['queue-job']); const config = reporting.getConfig(); - const captureConfig = config.get('capture'); - const queueConfig = config.get('queue'); - const browserType = captureConfig.browser.type; - const maxAttempts = captureConfig.maxAttempts; + const queueTimeout = config.get('queue', 'timeout'); + const browserType = config.get('capture', 'browser', 'type'); + const maxAttempts = config.get('capture', 'maxAttempts'); + + const logger = parentLogger.clone(['queue-job']); return async function enqueueJob( exportTypeId: string, @@ -53,7 +53,7 @@ export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger const payload = await createJob(jobParams, headers, request); const options = { - timeout: queueConfig.timeout, + timeout: queueTimeout, created_by: get(user, 'username', false), browser_type: browserType, max_attempts: maxAttempts,