[Reporting] Remove boilerplate needed to get thegetScreenshots function (#64269)

* expose reportingCore in ReportingSetup for apps

* improve tests

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Tim Sullivan 2020-04-27 14:25:26 -07:00 committed by GitHub
parent 4c460b8c42
commit 54dc148226
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 148 additions and 93 deletions

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export { screenshotsObservableFactory } from './observable'; export { screenshotsObservableFactory, ScreenshotsObservableFn } from './observable';

View file

@ -21,10 +21,18 @@ import { waitForVisualizations } from './wait_for_visualizations';
const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200; const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200;
const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800; const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800;
export type ScreenshotsObservableFn = ({
logger,
urls,
conditionalHeaders,
layout,
browserTimezone,
}: ScreenshotObservableOpts) => Rx.Observable<ScreenshotResults[]>;
export function screenshotsObservableFactory( export function screenshotsObservableFactory(
captureConfig: CaptureConfig, captureConfig: CaptureConfig,
browserDriverFactory: HeadlessChromiumDriverFactory browserDriverFactory: HeadlessChromiumDriverFactory
) { ): ScreenshotsObservableFn {
return function screenshotsObservable({ return function screenshotsObservable({
logger, logger,
urls, urls,

View file

@ -5,14 +5,14 @@
*/ */
import * as Rx from 'rxjs'; 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 { CancellationToken } from '../../../../common/cancellation_token';
import { ReportingCore } from '../../../../server';
import { LevelLogger } from '../../../../server/lib'; 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 { JobDocPayloadPNG } from '../../types';
import { generatePngObservableFactory } from '../lib/generate_png';
import { executeJobFactory } from './index';
jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() }));
@ -31,8 +31,6 @@ const mockLoggerFactory = {
}; };
const getMockLogger = () => new LevelLogger(mockLoggerFactory); const getMockLogger = () => new LevelLogger(mockLoggerFactory);
const captureConfig = {} as CaptureConfig;
const mockEncryptionKey = 'abcabcsecuresecret'; const mockEncryptionKey = 'abcabcsecuresecret';
const encryptHeaders = async (headers: Record<string, string>) => { const encryptHeaders = async (headers: Record<string, string>) => {
const crypto = cryptoFactory(mockEncryptionKey); const crypto = cryptoFactory(mockEncryptionKey);
@ -46,10 +44,13 @@ beforeEach(async () => {
'server.basePath': '/sbp', 'server.basePath': '/sbp',
}; };
const reportingConfig = { const reportingConfig = {
index: '.reporting-2018.10.10',
encryptionKey: mockEncryptionKey, encryptionKey: mockEncryptionKey,
'kibanaServer.hostname': 'localhost', 'kibanaServer.hostname': 'localhost',
'kibanaServer.port': 5601, 'kibanaServer.port': 5601,
'kibanaServer.protocol': 'http', 'kibanaServer.protocol': 'http',
'queue.indexInterval': 'daily',
'queue.timeout': Infinity,
}; };
const mockReportingConfig = { const mockReportingConfig = {
get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')], get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')],
@ -74,13 +75,8 @@ afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset());
test(`passes browserTimezone to generatePng`, async () => { test(`passes browserTimezone to generatePng`, async () => {
const encryptedHeaders = await encryptHeaders({}); const encryptedHeaders = await encryptHeaders({});
const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock;
generatePngObservable.mockReturnValue(Rx.of(Buffer.from('')));
const generatePngObservable = generatePngObservableFactory(
captureConfig,
mockBrowserDriverFactory
);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC'; const browserTimezone = 'UTC';
@ -94,26 +90,43 @@ test(`passes browserTimezone to generatePng`, async () => {
cancellationToken cancellationToken
); );
expect(generatePngObservable).toBeCalledWith( expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(`
expect.any(LevelLogger), Array [
'http://localhost:5601/sbp/app/kibana#/something', Array [
browserTimezone, LevelLogger {
expect.anything(), "_logger": Object {
undefined "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 () => { test(`returns content_type of application/png`, async () => {
const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({}); const encryptedHeaders = await encryptHeaders({});
const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock;
generatePngObservable.mockReturnValue(Rx.of(Buffer.from('')));
const generatePngObservable = generatePngObservableFactory(
captureConfig,
mockBrowserDriverFactory
);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const { content_type: contentType } = await executeJob( const { content_type: contentType } = await executeJob(
'pngJobId', 'pngJobId',
@ -126,13 +139,8 @@ test(`returns content_type of application/png`, async () => {
test(`returns content of generatePng getBuffer base64 encoded`, async () => { test(`returns content of generatePng getBuffer base64 encoded`, async () => {
const testContent = 'test content'; const testContent = 'test content';
const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock;
generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const generatePngObservable = generatePngObservableFactory(
captureConfig,
mockBrowserDriverFactory
);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const executeJob = await executeJobFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({}); const encryptedHeaders = await encryptHeaders({});

View file

@ -25,13 +25,11 @@ export const executeJobFactory: QueuedPngExecutorFactory = async function execut
parentLogger: Logger parentLogger: Logger
) { ) {
const config = reporting.getConfig(); const config = reporting.getConfig();
const captureConfig = config.get('capture');
const encryptionKey = config.get('encryptionKey'); const encryptionKey = config.get('encryptionKey');
const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']);
return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) {
const browserDriverFactory = await reporting.getBrowserDriverFactory(); const generatePngObservable = await generatePngObservableFactory(reporting);
const generatePngObservable = generatePngObservableFactory(captureConfig, browserDriverFactory);
const jobLogger = logger.clone([jobId]); const jobLogger = logger.clone([jobId]);
const process$: Rx.Observable<JobDocOutput> = Rx.of(1).pipe( const process$: Rx.Observable<JobDocOutput> = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })),

View file

@ -6,19 +6,15 @@
import * as Rx from 'rxjs'; import * as Rx from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ReportingCore } from '../../../../server';
import { LevelLogger } from '../../../../server/lib'; import { LevelLogger } from '../../../../server/lib';
import { CaptureConfig } from '../../../../server/types'; import { ConditionalHeaders } from '../../../../types';
import { ConditionalHeaders, HeadlessChromiumDriverFactory } from '../../../../types';
import { LayoutParams } from '../../../common/layouts/layout'; import { LayoutParams } from '../../../common/layouts/layout';
import { PreserveLayout } from '../../../common/layouts/preserve_layout'; import { PreserveLayout } from '../../../common/layouts/preserve_layout';
import { screenshotsObservableFactory } from '../../../common/lib/screenshots';
import { ScreenshotResults } from '../../../common/lib/screenshots/types'; import { ScreenshotResults } from '../../../common/lib/screenshots/types';
export function generatePngObservableFactory( export async function generatePngObservableFactory(reporting: ReportingCore) {
captureConfig: CaptureConfig, const getScreenshots = await reporting.getScreenshotsObservable();
browserDriverFactory: HeadlessChromiumDriverFactory
) {
const screenshotsObservable = screenshotsObservableFactory(captureConfig, browserDriverFactory);
return function generatePngObservable( return function generatePngObservable(
logger: LevelLogger, logger: LevelLogger,
@ -32,7 +28,7 @@ export function generatePngObservableFactory(
} }
const layout = new PreserveLayout(layoutParams.dimensions); const layout = new PreserveLayout(layoutParams.dimensions);
const screenshots$ = screenshotsObservable({ const screenshots$ = getScreenshots({
logger, logger,
urls: [url], urls: [url],
conditionalHeaders, conditionalHeaders,

View file

@ -5,11 +5,11 @@
*/ */
import * as Rx from 'rxjs'; import * as Rx from 'rxjs';
import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers'; import { createMockReportingCore } from '../../../../test_helpers';
import { cryptoFactory } from '../../../../server/lib/crypto'; import { cryptoFactory } from '../../../../server/lib/crypto';
import { LevelLogger } from '../../../../server/lib'; import { LevelLogger } from '../../../../server/lib';
import { CancellationToken } from '../../../../types'; import { CancellationToken } from '../../../../types';
import { ReportingCore, CaptureConfig } from '../../../../server/types'; import { ReportingCore } from '../../../../server';
import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { generatePdfObservableFactory } from '../lib/generate_pdf';
import { JobDocPayloadPDF } from '../../types'; import { JobDocPayloadPDF } from '../../types';
import { executeJobFactory } from './index'; import { executeJobFactory } from './index';
@ -22,8 +22,6 @@ const cancellationToken = ({
on: jest.fn(), on: jest.fn(),
} as unknown) as CancellationToken; } as unknown) as CancellationToken;
const captureConfig = {} as CaptureConfig;
const mockLoggerFactory = { const mockLoggerFactory = {
get: jest.fn().mockImplementation(() => ({ get: jest.fn().mockImplementation(() => ({
error: jest.fn(), error: jest.fn(),
@ -72,16 +70,64 @@ beforeEach(async () => {
afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset()); 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 () => { test(`returns content_type of application/pdf`, async () => {
const logger = getMockLogger(); const logger = getMockLogger();
const executeJob = await executeJobFactory(mockReporting, logger); const executeJob = await executeJobFactory(mockReporting, logger);
const mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger);
const encryptedHeaders = await encryptHeaders({}); const encryptedHeaders = await encryptHeaders({});
const generatePdfObservable = generatePdfObservableFactory( const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
captureConfig,
mockBrowserDriverFactory
);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const { content_type: contentType } = await executeJob( 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 () => { test(`returns content of generatePdf getBuffer base64 encoded`, async () => {
const testContent = 'test content'; const testContent = 'test content';
const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
const generatePdfObservable = generatePdfObservableFactory(
captureConfig,
mockBrowserDriverFactory
);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const executeJob = await executeJobFactory(mockReporting, getMockLogger());

View file

@ -26,14 +26,12 @@ export const executeJobFactory: QueuedPdfExecutorFactory = async function execut
parentLogger: Logger parentLogger: Logger
) { ) {
const config = reporting.getConfig(); const config = reporting.getConfig();
const captureConfig = config.get('capture');
const encryptionKey = config.get('encryptionKey'); const encryptionKey = config.get('encryptionKey');
const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']); const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']);
return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) {
const browserDriverFactory = await reporting.getBrowserDriverFactory(); const generatePdfObservable = await generatePdfObservableFactory(reporting);
const generatePdfObservable = generatePdfObservableFactory(captureConfig, browserDriverFactory);
const jobLogger = logger.clone([jobId]); const jobLogger = logger.clone([jobId]);
const process$: Rx.Observable<JobDocOutput> = Rx.of(1).pipe( const process$: Rx.Observable<JobDocOutput> = Rx.of(1).pipe(

View file

@ -7,12 +7,11 @@
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import * as Rx from 'rxjs'; import * as Rx from 'rxjs';
import { mergeMap } from 'rxjs/operators'; import { mergeMap } from 'rxjs/operators';
import { ReportingCore } from '../../../../server';
import { LevelLogger } from '../../../../server/lib'; import { LevelLogger } from '../../../../server/lib';
import { CaptureConfig } from '../../../../server/types'; import { ConditionalHeaders } from '../../../../types';
import { ConditionalHeaders, HeadlessChromiumDriverFactory } from '../../../../types';
import { createLayout } from '../../../common/layouts'; import { createLayout } from '../../../common/layouts';
import { LayoutInstance, LayoutParams } from '../../../common/layouts/layout'; import { LayoutInstance, LayoutParams } from '../../../common/layouts/layout';
import { screenshotsObservableFactory } from '../../../common/lib/screenshots';
import { ScreenshotResults } from '../../../common/lib/screenshots/types'; import { ScreenshotResults } from '../../../common/lib/screenshots/types';
// @ts-ignore untyped module // @ts-ignore untyped module
import { pdf } from './pdf'; import { pdf } from './pdf';
@ -27,11 +26,10 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => {
return null; return null;
}; };
export function generatePdfObservableFactory( export async function generatePdfObservableFactory(reporting: ReportingCore) {
captureConfig: CaptureConfig, const config = reporting.getConfig();
browserDriverFactory: HeadlessChromiumDriverFactory const captureConfig = config.get('capture');
) { const getScreenshots = await reporting.getScreenshotsObservable();
const screenshotsObservable = screenshotsObservableFactory(captureConfig, browserDriverFactory);
return function generatePdfObservable( return function generatePdfObservable(
logger: LevelLogger, logger: LevelLogger,
@ -43,7 +41,7 @@ export function generatePdfObservableFactory(
logo?: string logo?: string
): Rx.Observable<{ buffer: Buffer; warnings: string[] }> { ): Rx.Observable<{ buffer: Buffer; warnings: string[] }> {
const layout = createLayout(captureConfig, layoutParams) as LayoutInstance; const layout = createLayout(captureConfig, layoutParams) as LayoutInstance;
const screenshots$ = screenshotsObservable({ const screenshots$ = getScreenshots({
logger, logger,
urls, urls,
conditionalHeaders, conditionalHeaders,

View file

@ -24,6 +24,10 @@ import { ReportingConfig, ReportingConfigType } from './config';
import { checkLicenseFactory, getExportTypesRegistry, LevelLogger } from './lib'; import { checkLicenseFactory, getExportTypesRegistry, LevelLogger } from './lib';
import { registerRoutes } from './routes'; import { registerRoutes } from './routes';
import { ReportingSetupDeps } from './types'; import { ReportingSetupDeps } from './types';
import {
screenshotsObservableFactory,
ScreenshotsObservableFn,
} from '../export_types/common/lib/screenshots';
interface ReportingInternalSetup { interface ReportingInternalSetup {
browserDriverFactory: HeadlessChromiumDriverFactory; browserDriverFactory: HeadlessChromiumDriverFactory;
@ -95,13 +99,13 @@ export class ReportingCore {
return (await this.getPluginStartDeps()).enqueueJob; return (await this.getPluginStartDeps()).enqueueJob;
} }
public async getBrowserDriverFactory(): Promise<HeadlessChromiumDriverFactory> {
return (await this.getPluginSetupDeps()).browserDriverFactory;
}
public getConfig(): ReportingConfig { public getConfig(): ReportingConfig {
return this.config; return this.config;
} }
public async getScreenshotsObservable(): Promise<ScreenshotsObservableFn> {
const { browserDriverFactory } = await this.getPluginSetupDeps();
return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory);
}
/* /*
* Outside dependencies * Outside dependencies

View file

@ -14,3 +14,5 @@ export const plugin = (context: PluginInitializerContext, config: ReportingConfi
export { ReportingPlugin } from './plugin'; export { ReportingPlugin } from './plugin';
export { ReportingConfig, ReportingCore }; export { ReportingConfig, ReportingCore };
export { PreserveLayout, PrintLayout } from '../export_types/common/layouts';

View file

@ -16,21 +16,23 @@ export async function createQueueFactory<JobParamsType, JobPayloadType>(
logger: Logger logger: Logger
): Promise<ESQueueInstance> { ): Promise<ESQueueInstance> {
const config = reporting.getConfig(); const config = reporting.getConfig();
const queueConfig = config.get('queue'); const queueIndexInterval = config.get('queue', 'indexInterval');
const index = config.get('index'); const queueTimeout = config.get('queue', 'timeout');
const elasticsearch = await reporting.getElasticsearchService(); const queueIndex = config.get('index');
const isPollingEnabled = config.get('queue', 'pollEnabled');
const elasticsearch = await reporting.getElasticsearchService();
const queueOptions = { const queueOptions = {
interval: queueConfig.indexInterval, interval: queueIndexInterval,
timeout: queueConfig.timeout, timeout: queueTimeout,
dateSeparator: '.', dateSeparator: '.',
client: elasticsearch.dataClient, client: elasticsearch.dataClient,
logger: createTaggedLogger(logger, ['esqueue', 'queue-worker']), 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 // create workers to poll the index for idle jobs waiting to be claimed and executed
const createWorker = createWorkerFactory(reporting, logger); const createWorker = createWorkerFactory(reporting, logger);
await createWorker(queue); await createWorker(queue);

View file

@ -26,12 +26,12 @@ interface ConfirmedJob {
} }
export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger): EnqueueJobFn { export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger): EnqueueJobFn {
const logger = parentLogger.clone(['queue-job']);
const config = reporting.getConfig(); const config = reporting.getConfig();
const captureConfig = config.get('capture'); const queueTimeout = config.get('queue', 'timeout');
const queueConfig = config.get('queue'); const browserType = config.get('capture', 'browser', 'type');
const browserType = captureConfig.browser.type; const maxAttempts = config.get('capture', 'maxAttempts');
const maxAttempts = captureConfig.maxAttempts;
const logger = parentLogger.clone(['queue-job']);
return async function enqueueJob<JobParamsType>( return async function enqueueJob<JobParamsType>(
exportTypeId: string, exportTypeId: string,
@ -53,7 +53,7 @@ export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger
const payload = await createJob(jobParams, headers, request); const payload = await createJob(jobParams, headers, request);
const options = { const options = {
timeout: queueConfig.timeout, timeout: queueTimeout,
created_by: get(user, 'username', false), created_by: get(user, 'username', false),
browser_type: browserType, browser_type: browserType,
max_attempts: maxAttempts, max_attempts: maxAttempts,