diff --git a/x-pack/legacy/plugins/reporting/common/constants.ts b/x-pack/legacy/plugins/reporting/common/constants.ts index 723320e74bfbd..03b2d51c7b396 100644 --- a/x-pack/legacy/plugins/reporting/common/constants.ts +++ b/x-pack/legacy/plugins/reporting/common/constants.ts @@ -50,3 +50,9 @@ export const PNG_JOB_TYPE = 'PNG'; export const CSV_JOB_TYPE = 'csv'; export const CSV_FROM_SAVEDOBJECT_JOB_TYPE = 'csv_from_savedobject'; export const USES_HEADLESS_JOB_TYPES = [PDF_JOB_TYPE, PNG_JOB_TYPE]; + +export const LICENSE_TYPE_TRIAL = 'trial'; +export const LICENSE_TYPE_BASIC = 'basic'; +export const LICENSE_TYPE_STANDARD = 'standard'; +export const LICENSE_TYPE_GOLD = 'gold'; +export const LICENSE_TYPE_PLATINUM = 'platinum'; diff --git a/x-pack/legacy/plugins/reporting/common/export_types_registry.js b/x-pack/legacy/plugins/reporting/common/export_types_registry.js deleted file mode 100644 index 39abd8911e751..0000000000000 --- a/x-pack/legacy/plugins/reporting/common/export_types_registry.js +++ /dev/null @@ -1,64 +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 { isString } from 'lodash'; - -export class ExportTypesRegistry { - - constructor() { - this._map = new Map(); - } - - register(item) { - if (!isString(item.id)) { - throw new Error(`'item' must have a String 'id' property `); - } - - if (this._map.has(item.id)) { - throw new Error(`'item' with id ${item.id} has already been registered`); - } - - this._map.set(item.id, item); - } - - getAll() { - return this._map.values(); - } - - getSize() { - return this._map.size; - } - - getById(id) { - if (!this._map.has(id)) { - throw new Error(`Unknown id ${id}`); - } - - return this._map.get(id); - } - - get(callback) { - let result; - for (const value of this._map.values()) { - if (!callback(value)) { - continue; - } - - if (result) { - throw new Error('Found multiple items matching predicate.'); - } - - result = value; - } - - if (!result) { - throw new Error('Found no items matching predicate'); - } - - return result; - } - -} 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 2b4411584d752..152ef32e331b9 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 @@ -6,8 +6,12 @@ import * as Rx from 'rxjs'; import { first, mergeMap } from 'rxjs/operators'; -import { ServerFacade, CaptureConfig } from '../../../../types'; -import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver'; +import { + ServerFacade, + CaptureConfig, + HeadlessChromiumDriverFactory, + HeadlessChromiumDriver as HeadlessBrowser, +} from '../../../../types'; import { ElementsPositionAndAttribute, ScreenshotResults, @@ -26,10 +30,12 @@ import { getElementPositionAndAttributes } from './get_element_position_data'; import { getScreenshots } from './get_screenshots'; import { skipTelemetry } from './skip_telemetry'; -export function screenshotsObservableFactory(server: ServerFacade) { +export function screenshotsObservableFactory( + server: ServerFacade, + browserDriverFactory: HeadlessChromiumDriverFactory +) { const config = server.config(); const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); - const { browserDriverFactory } = server.plugins.reporting!; return function screenshotsObservable({ logger, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv/index.ts new file mode 100644 index 0000000000000..4f8aeb2be0c99 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/index.ts @@ -0,0 +1,39 @@ +/* + * 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 { + CSV_JOB_TYPE as jobType, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ESQueueCreateJobFn, ESQueueWorkerExecuteFn } from '../../types'; +import { metadata } from './metadata'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { JobParamsDiscoverCsv, JobDocPayloadDiscoverCsv } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsDiscoverCsv, + ESQueueCreateJobFn, + JobDocPayloadDiscoverCsv, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentExtension: 'csv', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/index.ts deleted file mode 100644 index d752cdcd9779d..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/index.ts +++ /dev/null @@ -1,22 +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 { ExportTypesRegistry } from '../../../types'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; -import { metadata } from '../metadata'; -import { CSV_JOB_TYPE as jobType } from '../../../common/constants'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType, - jobContentExtension: 'csv', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'basic', 'standard', 'gold', 'platinum'], - }); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts index 68ad4a4b49155..4876cea0b1b28 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts @@ -4,9 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + CSV_FROM_SAVEDOBJECT_JOB_TYPE, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ImmediateCreateJobFn, ImmediateExecuteFn } from '../../types'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { metadata } from './metadata'; +import { JobParamsPanelCsv } from './types'; + /* * These functions are exported to share with the API route handler that * generates csv from saved object immediately on request. */ export { executeJobFactory } from './server/execute_job'; export { createJobFactory } from './server/create_job'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPanelCsv, + ImmediateCreateJobFn, + JobParamsPanelCsv, + ImmediateExecuteFn +> => ({ + ...metadata, + jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, + jobContentExtension: 'csv', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/index.ts deleted file mode 100644 index b614fd3c681b3..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/index.ts +++ /dev/null @@ -1,22 +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 { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { ExportTypesRegistry } from '../../../types'; -import { metadata } from '../metadata'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, - jobContentExtension: 'csv', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'basic', 'standard', 'gold', 'platinum'], - }); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/png/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/index.ts new file mode 100644 index 0000000000000..bc00bc428f306 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/png/index.ts @@ -0,0 +1,38 @@ +/* + * 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 { + PNG_JOB_TYPE as jobType, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ESQueueCreateJobFn, ESQueueWorkerExecuteFn } from '../../types'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { metadata } from './metadata'; +import { JobParamsPNG, JobDocPayloadPNG } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPNG, + ESQueueCreateJobFn, + JobDocPayloadPNG, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentEncoding: 'base64', + jobContentExtension: 'PNG', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js index 867d537017f41..267c606449c3a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js @@ -68,7 +68,7 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = generatePngObservableFactory(); generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const browserTimezone = 'UTC'; await executeJob('pngJobId', { relativeUrl: '/app/kibana#/something', browserTimezone, headers: encryptedHeaders }, cancellationToken); @@ -76,7 +76,7 @@ test(`passes browserTimezone to generatePng`, async () => { }); test(`returns content_type of application/png`, async () => { - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = generatePngObservableFactory(); @@ -93,7 +93,7 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = generatePngObservableFactory(); generatePngObservable.mockReturnValue(Rx.of(Buffer.from(testContent))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob('pngJobId', { relativeUrl: '/app/kibana#/something', timeRange: {}, headers: encryptedHeaders }, cancellationToken); 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 6678a83079d31..b289ae45dde67 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 @@ -7,7 +7,12 @@ import * as Rx from 'rxjs'; import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; import { PLUGIN_ID, PNG_JOB_TYPE } from '../../../../common/constants'; -import { ServerFacade, ExecuteJobFactory, ESQueueWorkerExecuteFn } from '../../../../types'; +import { + ServerFacade, + ExecuteJobFactory, + ESQueueWorkerExecuteFn, + HeadlessChromiumDriverFactory, +} from '../../../../types'; import { LevelLogger } from '../../../../server/lib'; import { decryptJobHeaders, @@ -18,10 +23,13 @@ import { import { JobDocPayloadPNG } from '../../types'; import { generatePngObservableFactory } from '../lib/generate_png'; -export const executeJobFactory: ExecuteJobFactory> = function executeJobFactoryFn(server: ServerFacade) { - const generatePngObservable = generatePngObservableFactory(server); +type QueuedPngExecutorFactory = ExecuteJobFactory>; + +export const executeJobFactory: QueuedPngExecutorFactory = function executeJobFactoryFn( + server: ServerFacade, + { browserDriverFactory }: { browserDriverFactory: HeadlessChromiumDriverFactory } +) { + const generatePngObservable = generatePngObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PNG_JOB_TYPE, 'execute']); return function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/index.ts deleted file mode 100644 index a569719f02324..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/index.ts +++ /dev/null @@ -1,23 +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 { ExportTypesRegistry } from '../../../types'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; -import { metadata } from '../metadata'; -import { PNG_JOB_TYPE as jobType } from '../../../common/constants'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType, - jobContentEncoding: 'base64', - jobContentExtension: 'PNG', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'standard', 'gold', 'platinum'], - }); -} 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 90aeea25db858..e2b1474515786 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 @@ -7,13 +7,16 @@ import * as Rx from 'rxjs'; import { map } from 'rxjs/operators'; import { LevelLogger } from '../../../../server/lib'; -import { ServerFacade, ConditionalHeaders } from '../../../../types'; +import { ServerFacade, HeadlessChromiumDriverFactory, ConditionalHeaders } from '../../../../types'; import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; import { PreserveLayout } from '../../../common/layouts/preserve_layout'; import { LayoutParams } from '../../../common/layouts/layout'; -export function generatePngObservableFactory(server: ServerFacade) { - const screenshotsObservable = screenshotsObservableFactory(server); +export function generatePngObservableFactory( + server: ServerFacade, + browserDriverFactory: HeadlessChromiumDriverFactory +) { + const screenshotsObservable = screenshotsObservableFactory(server, browserDriverFactory); return function generatePngObservable( logger: LevelLogger, diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/index.ts new file mode 100644 index 0000000000000..99880c1237a7a --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/index.ts @@ -0,0 +1,38 @@ +/* + * 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 { + PDF_JOB_TYPE as jobType, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ESQueueCreateJobFn, ESQueueWorkerExecuteFn } from '../../types'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { metadata } from './metadata'; +import { JobParamsPDF, JobDocPayloadPDF } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPDF, + ESQueueCreateJobFn, + JobDocPayloadPDF, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js index 8084c077ed23f..6a5c47829fd19 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js @@ -67,7 +67,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = generatePdfObservableFactory(); generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const browserTimezone = 'UTC'; await executeJob('pdfJobId', { objects: [], browserTimezone, headers: encryptedHeaders }, cancellationToken); @@ -84,7 +84,7 @@ test(`passes browserTimezone to generatePdf`, async () => { }); test(`returns content_type of application/pdf`, async () => { - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = generatePdfObservableFactory(); @@ -104,7 +104,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = generatePdfObservableFactory(); generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(testContent))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob('pdfJobId', { objects: [], timeRange: {}, headers: encryptedHeaders }, cancellationToken); 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 543d5b587906d..e2b3183464cf2 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 @@ -6,7 +6,12 @@ import * as Rx from 'rxjs'; import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; -import { ExecuteJobFactory, ESQueueWorkerExecuteFn, ServerFacade } from '../../../../types'; +import { + ServerFacade, + ExecuteJobFactory, + ESQueueWorkerExecuteFn, + HeadlessChromiumDriverFactory, +} from '../../../../types'; import { JobDocPayloadPDF } from '../../types'; import { PLUGIN_ID, PDF_JOB_TYPE } from '../../../../common/constants'; import { LevelLogger } from '../../../../server/lib'; @@ -19,10 +24,13 @@ import { getCustomLogo, } from '../../../common/execute_job/'; -export const executeJobFactory: ExecuteJobFactory> = function executeJobFactoryFn(server: ServerFacade) { - const generatePdfObservable = generatePdfObservableFactory(server); +type QueuedPdfExecutorFactory = ExecuteJobFactory>; + +export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn( + server: ServerFacade, + { browserDriverFactory }: { browserDriverFactory: HeadlessChromiumDriverFactory } +) { + const generatePdfObservable = generatePdfObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'execute']); return function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/index.ts deleted file mode 100644 index df798a7af23ec..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/index.ts +++ /dev/null @@ -1,23 +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 { ExportTypesRegistry } from '../../../types'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; -import { metadata } from '../metadata'; -import { PDF_JOB_TYPE as jobType } from '../../../common/constants'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType, - jobContentEncoding: 'base64', - jobContentExtension: 'pdf', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'standard', 'gold', 'platinum'], - }); -} 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 1e0245ebd513f..898a13a2dfe80 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 @@ -8,7 +8,7 @@ import * as Rx from 'rxjs'; import { toArray, mergeMap } from 'rxjs/operators'; import { groupBy } from 'lodash'; import { LevelLogger } from '../../../../server/lib'; -import { ServerFacade, ConditionalHeaders } from '../../../../types'; +import { ServerFacade, HeadlessChromiumDriverFactory, ConditionalHeaders } from '../../../../types'; // @ts-ignore untyped module import { pdf } from './pdf'; import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; @@ -26,8 +26,11 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { return null; }; -export function generatePdfObservableFactory(server: ServerFacade) { - const screenshotsObservable = screenshotsObservableFactory(server); +export function generatePdfObservableFactory( + server: ServerFacade, + browserDriverFactory: HeadlessChromiumDriverFactory +) { + const screenshotsObservable = screenshotsObservableFactory(server, browserDriverFactory); const captureConcurrency = 1; return function generatePdfObservable( diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index 9add3accd262f..c0c9e458132f0 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -13,8 +13,7 @@ import { registerRoutes } from './server/routes'; import { LevelLogger, checkLicenseFactory, - createQueueFactory, - exportTypesRegistryFactory, + getExportTypesRegistry, runValidations, } from './server/lib'; import { config as reportingConfig } from './config'; @@ -74,20 +73,23 @@ export const reporting = (kibana: any) => { // TODO: Decouple Hapi: Build a server facade object based on the server to // pass through to the libs. Do not pass server directly async init(server: ServerFacade) { + const exportTypesRegistry = getExportTypesRegistry(); + let isCollectorReady = false; // Register a function with server to manage the collection of usage stats const { usageCollection } = server.newPlatform.setup.plugins; - registerReportingUsageCollector(usageCollection, server, () => isCollectorReady); + registerReportingUsageCollector( + usageCollection, + server, + () => isCollectorReady, + exportTypesRegistry + ); const logger = LevelLogger.createForServer(server, [PLUGIN_ID]); - const [exportTypesRegistry, browserFactory] = await Promise.all([ - exportTypesRegistryFactory(server), - createBrowserDriverFactory(server), - ]); - server.expose('exportTypesRegistry', exportTypesRegistry); + const browserDriverFactory = await createBrowserDriverFactory(server); logConfiguration(server, logger); - runValidations(server, logger, browserFactory); + runValidations(server, logger, browserDriverFactory); const { xpack_main: xpackMainPlugin } = server.plugins; mirrorPluginStatus(xpackMainPlugin, this); @@ -101,11 +103,8 @@ export const reporting = (kibana: any) => { // Post initialization of the above code, the collector is now ready to fetch its data isCollectorReady = true; - server.expose('browserDriverFactory', browserFactory); - server.expose('queue', createQueueFactory(server)); - // Reporting routes - registerRoutes(server, logger); + registerRoutes(server, exportTypesRegistry, browserDriverFactory, logger); }, deprecations({ unused }: any) { diff --git a/x-pack/legacy/plugins/reporting/common/__tests__/export_types_registry.js b/x-pack/legacy/plugins/reporting/server/lib/__tests__/export_types_registry.js similarity index 100% rename from x-pack/legacy/plugins/reporting/common/__tests__/export_types_registry.js rename to x-pack/legacy/plugins/reporting/server/lib/__tests__/export_types_registry.js 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 174c6d587e523..5cf760250ec0e 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -5,7 +5,12 @@ */ import { PLUGIN_ID } from '../../common/constants'; -import { ServerFacade, QueueConfig } from '../../types'; +import { + ServerFacade, + ExportTypesRegistry, + HeadlessChromiumDriverFactory, + QueueConfig, +} from '../../types'; // @ts-ignore import { Esqueue } from './esqueue'; import { createWorkerFactory } from './create_worker'; @@ -13,7 +18,15 @@ import { LevelLogger } from './level_logger'; // @ts-ignore import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed -export function createQueueFactory(server: ServerFacade): Esqueue { +interface CreateQueueFactoryOpts { + exportTypesRegistry: ExportTypesRegistry; + browserDriverFactory: HeadlessChromiumDriverFactory; +} + +export function createQueueFactory( + server: ServerFacade, + { exportTypesRegistry, browserDriverFactory }: CreateQueueFactoryOpts +): Esqueue { const queueConfig: QueueConfig = server.config().get('xpack.reporting.queue'); const index = server.config().get('xpack.reporting.index'); @@ -29,7 +42,7 @@ export function createQueueFactory(server: ServerFacade): Esqueue { if (queueConfig.pollEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(server); + const createWorker = createWorkerFactory(server, { exportTypesRegistry, browserDriverFactory }); createWorker(queue); } else { const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'create_queue']); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts index afad8f096a8bb..8f843752491ec 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts @@ -5,7 +5,8 @@ */ import * as sinon from 'sinon'; -import { ServerFacade } from '../../types'; +import { ServerFacade, HeadlessChromiumDriverFactory } from '../../types'; +import { ExportTypesRegistry } from './export_types_registry'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; @@ -22,16 +23,17 @@ configGetStub.withArgs('server.uuid').returns('g9ymiujthvy6v8yrh7567g6fwzgzftzfr const executeJobFactoryStub = sinon.stub(); -const getMockServer = ( - exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] -): ServerFacade => { +const getMockServer = (): ServerFacade => { return ({ log: sinon.stub(), - expose: sinon.stub(), config: () => ({ get: configGetStub }), - plugins: { reporting: { exportTypesRegistry: { getAll: () => exportTypes } } }, } as unknown) as ServerFacade; }; +const getMockExportTypesRegistry = ( + exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] +) => ({ + getAll: () => exportTypes, +}); describe('Create Worker', () => { let queue: Esqueue; @@ -44,7 +46,11 @@ describe('Create Worker', () => { }); test('Creates a single Esqueue worker for Reporting', async () => { - const createWorker = createWorkerFactory(getMockServer()); + const exportTypesRegistry = getMockExportTypesRegistry(); + const createWorker = createWorkerFactory(getMockServer(), { + exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + }); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); createWorker(queue); @@ -68,15 +74,17 @@ Object { }); test('Creates a single Esqueue worker for Reporting, even if there are multiple export types', async () => { - const createWorker = createWorkerFactory( - getMockServer([ - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - ]) - ); + const exportTypesRegistry = getMockExportTypesRegistry([ + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + ]); + const createWorker = createWorkerFactory(getMockServer(), { + exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + }); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); createWorker(queue); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 01f59099a1d99..1326e411b6c5c 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -5,6 +5,7 @@ */ import { PLUGIN_ID } from '../../common/constants'; +import { ExportTypesRegistry, HeadlessChromiumDriverFactory } from '../../types'; import { CancellationToken } from '../../common/cancellation_token'; import { ESQueueInstance, @@ -21,14 +22,21 @@ import { import { events as esqueueEvents } from './esqueue'; import { LevelLogger } from './level_logger'; -export function createWorkerFactory(server: ServerFacade) { +interface CreateWorkerFactoryOpts { + exportTypesRegistry: ExportTypesRegistry; + browserDriverFactory: HeadlessChromiumDriverFactory; +} + +export function createWorkerFactory( + server: ServerFacade, + { exportTypesRegistry, browserDriverFactory }: CreateWorkerFactoryOpts +) { type JobDocPayloadType = JobDocPayload; const config = server.config(); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'queue-worker']); const queueConfig: QueueConfig = config.get('xpack.reporting.queue'); const kibanaName: string = config.get('server.name'); const kibanaId: string = config.get('server.uuid'); - const { exportTypesRegistry } = server.plugins.reporting!; // Once more document types are added, this will need to be passed in return function createWorker(queue: ESQueueInstance) { @@ -41,8 +49,9 @@ export function createWorkerFactory(server: ServerFacade) { for (const exportType of exportTypesRegistry.getAll() as Array< ExportTypeDefinition >) { - const executeJobFactory = exportType.executeJobFactory(server); - jobExecutors.set(exportType.jobType, executeJobFactory); + // TODO: the executeJobFn should be unwrapped in the register method of the export types registry + const jobExecutor = exportType.executeJobFactory(server, { browserDriverFactory }); + jobExecutors.set(exportType.jobType, jobExecutor); } const workerFn = (jobSource: JobSource, ...workerRestArgs: any[]) => { 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 a8cefa3fdc49b..2d044ab31a160 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -8,12 +8,14 @@ import { get } from 'lodash'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; import { + EnqueueJobFn, ESQueueCreateJobFn, ImmediateCreateJobFn, Job, ServerFacade, RequestFacade, Logger, + ExportTypesRegistry, CaptureConfig, QueueConfig, ConditionalHeaders, @@ -26,13 +28,20 @@ interface ConfirmedJob { _primary_term: number; } -export function enqueueJobFactory(server: ServerFacade) { +interface EnqueueJobFactoryOpts { + exportTypesRegistry: ExportTypesRegistry; + esqueue: any; +} + +export function enqueueJobFactory( + server: ServerFacade, + { exportTypesRegistry, esqueue }: EnqueueJobFactoryOpts +): EnqueueJobFn { const config = server.config(); const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); const browserType = captureConfig.browser.type; const maxAttempts = captureConfig.maxAttempts; const queueConfig: QueueConfig = config.get('xpack.reporting.queue'); - const { exportTypesRegistry, queue: jobQueue } = server.plugins.reporting!; return async function enqueueJob( parentLogger: Logger, @@ -46,6 +55,12 @@ export function enqueueJobFactory(server: ServerFacade) { const logger = parentLogger.clone(['queue-job']); const exportType = exportTypesRegistry.getById(exportTypeId); + + if (exportType == null) { + throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); + } + + // TODO: the createJobFn should be unwrapped in the register method of the export types registry const createJob = exportType.createJobFactory(server) as CreateJobFn; const payload = await createJob(jobParams, headers, request); @@ -57,7 +72,7 @@ export function enqueueJobFactory(server: ServerFacade) { }; return new Promise((resolve, reject) => { - const job = jobQueue.addJob(exportType.jobType, payload, options); + const job = esqueue.addJob(exportType.jobType, payload, options); job.on(esqueueEvents.EVENT_JOB_CREATED, (createdJob: ConfirmedJob) => { if (createdJob.id === job.id) { diff --git a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts index af1457ab52fe2..d553cc07ae3ef 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts @@ -4,40 +4,110 @@ * you may not use this file except in compliance with the Elastic License. */ -import { resolve as pathResolve } from 'path'; -import glob from 'glob'; -import { ServerFacade } from '../../types'; -import { PLUGIN_ID } from '../../common/constants'; -import { oncePerServer } from './once_per_server'; -import { LevelLogger } from './level_logger'; -// @ts-ignore untype module TODO -import { ExportTypesRegistry } from '../../common/export_types_registry'; - -function scan(pattern: string) { - return new Promise((resolve, reject) => { - glob(pattern, {}, (err, files) => { - if (err) { - return reject(err); +import memoizeOne from 'memoize-one'; +import { isString } from 'lodash'; +import { getExportType as getTypeCsv } from '../../export_types/csv'; +import { getExportType as getTypeCsvFromSavedObject } from '../../export_types/csv_from_savedobject'; +import { getExportType as getTypePng } from '../../export_types/png'; +import { getExportType as getTypePrintablePdf } from '../../export_types/printable_pdf'; +import { ExportTypeDefinition } from '../../types'; + +type GetCallbackFn = ( + item: ExportTypeDefinition +) => boolean; +// => ExportTypeDefinition + +export class ExportTypesRegistry { + private _map: Map> = new Map(); + + constructor() {} + + register( + item: ExportTypeDefinition + ): void { + if (!isString(item.id)) { + throw new Error(`'item' must have a String 'id' property `); + } + + if (this._map.has(item.id)) { + throw new Error(`'item' with id ${item.id} has already been registered`); + } + + // TODO: Unwrap the execute function from the item's executeJobFactory + // Move that work out of server/lib/create_worker to reduce dependence on ESQueue + this._map.set(item.id, item); + } + + getAll() { + return Array.from(this._map.values()); + } + + getSize() { + return this._map.size; + } + + getById( + id: string + ): ExportTypeDefinition { + if (!this._map.has(id)) { + throw new Error(`Unknown id ${id}`); + } + + return this._map.get(id) as ExportTypeDefinition< + JobParamsType, + CreateJobFnType, + JobPayloadType, + ExecuteJobFnType + >; + } + + get( + findType: GetCallbackFn + ): ExportTypeDefinition { + let result; + for (const value of this._map.values()) { + if (!findType(value)) { + continue; // try next value } + const foundResult: ExportTypeDefinition< + JobParamsType, + CreateJobFnType, + JobPayloadType, + ExecuteJobFnType + > = value; - resolve(files); - }); - }); -} + if (result) { + throw new Error('Found multiple items matching predicate.'); + } + + result = foundResult; + } -const pattern = pathResolve(__dirname, '../../export_types/*/server/index.[jt]s'); -async function exportTypesRegistryFn(server: ServerFacade) { - const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'exportTypes']); - const exportTypesRegistry = new ExportTypesRegistry(); - const files: string[] = (await scan(pattern)) as string[]; + if (!result) { + throw new Error('Found no items matching predicate'); + } + + return result; + } +} - files.forEach(file => { - logger.debug(`Found exportType at ${file}`); +function getExportTypesRegistryFn(): ExportTypesRegistry { + const registry = new ExportTypesRegistry(); - const { register } = require(file); // eslint-disable-line @typescript-eslint/no-var-requires - register(exportTypesRegistry); + /* this replaces the previously async method of registering export types, + * where this would run a directory scan and types would be registered via + * discovery */ + const getTypeFns: Array<() => ExportTypeDefinition> = [ + getTypeCsv, + getTypeCsvFromSavedObject, + getTypePng, + getTypePrintablePdf, + ]; + getTypeFns.forEach(getType => { + registry.register(getType()); }); - return exportTypesRegistry; + return registry; } -export const exportTypesRegistryFactory = oncePerServer(exportTypesRegistryFn); +// FIXME: is this the best way to return a singleton? +export const getExportTypesRegistry = memoizeOne(getExportTypesRegistryFn); diff --git a/x-pack/legacy/plugins/reporting/server/lib/index.ts b/x-pack/legacy/plugins/reporting/server/lib/index.ts index b11f7bd95d9ef..50d1a276b6b5d 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/index.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export { exportTypesRegistryFactory } from './export_types_registry'; +export { getExportTypesRegistry } from './export_types_registry'; // @ts-ignore untyped module export { checkLicenseFactory } from './check_license'; export { LevelLogger } from './level_logger'; -export { createQueueFactory } from './create_queue'; export { cryptoFactory } from './crypto'; export { oncePerServer } from './once_per_server'; export { runValidations } from './validate'; +export { createQueueFactory } from './create_queue'; +export { enqueueJobFactory } from './enqueue_job'; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 8b0dd1a6e7c4f..bc96c27f64c10 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -10,6 +10,7 @@ import { ServerFacade, RequestFacade, ResponseFacade, + HeadlessChromiumDriverFactory, ReportingResponseToolkit, Logger, JobDocOutputExecuted, @@ -45,8 +46,17 @@ export function registerGenerateCsvFromSavedObjectImmediate( handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); + + /* TODO these functions should be made available in the export types registry: + * + * const { createJobFn, executeJobFn } = exportTypesRegistry.getById(CSV_FROM_SAVEDOBJECT_JOB_TYPE) + * + * Calling an execute job factory requires passing a browserDriverFactory option, so we should not call the factory from here + */ const createJobFn = createJobFactory(server); - const executeJobFn = executeJobFactory(server); + const executeJobFn = executeJobFactory(server, { + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + }); const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( jobParams, request.headers, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts new file mode 100644 index 0000000000000..7bed7bc5773e4 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -0,0 +1,83 @@ +/* + * 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 boom from 'boom'; +import { API_BASE_URL } from '../../common/constants'; +import { + ServerFacade, + ExportTypesRegistry, + HeadlessChromiumDriverFactory, + RequestFacade, + ReportingResponseToolkit, + Logger, +} from '../../types'; +import { registerGenerateFromJobParams } from './generate_from_jobparams'; +import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; +import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; +import { registerLegacy } from './legacy'; +import { createQueueFactory, enqueueJobFactory } from '../lib'; + +export function registerJobGenerationRoutes( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry, + browserDriverFactory: HeadlessChromiumDriverFactory, + logger: Logger +) { + const config = server.config(); + const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; + // @ts-ignore TODO + const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); + + const esqueue = createQueueFactory(server, { exportTypesRegistry, browserDriverFactory }); + const enqueueJob = enqueueJobFactory(server, { exportTypesRegistry, esqueue }); + + /* + * Generates enqueued job details to use in responses + */ + async function handler( + exportTypeId: string, + jobParams: object, + request: RequestFacade, + h: ReportingResponseToolkit + ) { + const user = request.pre.user; + const headers = request.headers; + + const job = await enqueueJob(logger, exportTypeId, jobParams, user, headers, request); + + // return the queue's job information + const jobJson = job.toJSON(); + + return h + .response({ + path: `${DOWNLOAD_BASE_URL}/${jobJson.id}`, + job: jobJson, + }) + .type('application/json'); + } + + function handleError(exportTypeId: string, err: Error) { + if (err instanceof esErrors['401']) { + return boom.unauthorized(`Sorry, you aren't authenticated`); + } + if (err instanceof esErrors['403']) { + return boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); + } + if (err instanceof esErrors['404']) { + return boom.boomify(err, { statusCode: 404 }); + } + return err; + } + + registerGenerateFromJobParams(server, handler, handleError); + registerLegacy(server, handler, handleError); + + // Register beta panel-action download-related API's + if (config.get('xpack.reporting.csv.enablePanelActionDownload')) { + registerGenerateCsvFromSavedObject(server, handler, handleError); + registerGenerateCsvFromSavedObjectImmediate(server, logger); + } +} diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index c48a37a36812e..da664dcb91ae4 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -4,69 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; -import { API_BASE_URL } from '../../common/constants'; -import { ServerFacade, RequestFacade, ReportingResponseToolkit, Logger } from '../../types'; -import { enqueueJobFactory } from '../lib/enqueue_job'; -import { registerGenerateFromJobParams } from './generate_from_jobparams'; -import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; -import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; -import { registerJobs } from './jobs'; -import { registerLegacy } from './legacy'; - -export function registerRoutes(server: ServerFacade, logger: Logger) { - const config = server.config(); - const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; - // @ts-ignore TODO - const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); - const enqueueJob = enqueueJobFactory(server); - - /* - * Generates enqueued job details to use in responses - */ - async function handler( - exportTypeId: string, - jobParams: object, - request: RequestFacade, - h: ReportingResponseToolkit - ) { - const user = request.pre.user; - const headers = request.headers; - - const job = await enqueueJob(logger, exportTypeId, jobParams, user, headers, request); - - // return the queue's job information - const jobJson = job.toJSON(); - - return h - .response({ - path: `${DOWNLOAD_BASE_URL}/${jobJson.id}`, - job: jobJson, - }) - .type('application/json'); - } - - function handleError(exportTypeId: string, err: Error) { - if (err instanceof esErrors['401']) { - return boom.unauthorized(`Sorry, you aren't authenticated`); - } - if (err instanceof esErrors['403']) { - return boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); - } - if (err instanceof esErrors['404']) { - return boom.boomify(err, { statusCode: 404 }); - } - return err; - } - - registerGenerateFromJobParams(server, handler, handleError); - registerLegacy(server, handler, handleError); - - // Register beta panel-action download-related API's - if (config.get('xpack.reporting.csv.enablePanelActionDownload')) { - registerGenerateCsvFromSavedObject(server, handler, handleError); - registerGenerateCsvFromSavedObjectImmediate(server, logger); - } - - registerJobs(server); +import { + ServerFacade, + ExportTypesRegistry, + HeadlessChromiumDriverFactory, + Logger, +} from '../../types'; +import { registerJobGenerationRoutes } from './generation'; +import { registerJobInfoRoutes } from './jobs'; + +export function registerRoutes( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry, + browserDriverFactory: HeadlessChromiumDriverFactory, + logger: Logger +) { + registerJobGenerationRoutes(server, exportTypesRegistry, browserDriverFactory, logger); + registerJobInfoRoutes(server, exportTypesRegistry, logger); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js index 2d1f48dd790a0..c4d4f6e42c9cb 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { difference, memoize } from 'lodash'; -import { registerJobs } from './jobs'; -import { ExportTypesRegistry } from '../../common/export_types_registry'; +import { registerJobInfoRoutes } from './jobs'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; jest.mock('./lib/authorized_user_pre_routing', () => { return { authorizedUserPreRoutingFactory: () => () => ({}) @@ -19,13 +19,17 @@ jest.mock('./lib/reporting_feature_pre_routing', () => { }; }); - let mockServer; +let exportTypesRegistry; +const mockLogger = { + error: jest.fn(), + debug: jest.fn(), +}; beforeEach(() => { mockServer = new Hapi.Server({ debug: false, port: 8080, routes: { log: { collect: true } } }); mockServer.config = memoize(() => ({ get: jest.fn() })); - const exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry = new ExportTypesRegistry(); exportTypesRegistry.register({ id: 'unencoded', jobType: 'unencodedJobType', @@ -44,9 +48,6 @@ beforeEach(() => { callWithRequest: jest.fn(), callWithInternalUser: jest.fn(), })) - }, - reporting: { - exportTypesRegistry } }; }); @@ -63,7 +64,7 @@ test(`returns 404 if job not found`, async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits())); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -79,7 +80,7 @@ test(`returns 401 if not valid job type`, async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -91,12 +92,11 @@ test(`returns 401 if not valid job type`, async () => { }); describe(`when job is incomplete`, () => { - const getIncompleteResponse = async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' }))); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -133,7 +133,7 @@ describe(`when job is failed`, () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(hits)); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -178,7 +178,7 @@ describe(`when job is completed`, () => { }); mockServer.plugins.elasticsearch.getCluster('admin').callWithInternalUser.mockReturnValue(Promise.resolve(hits)); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 71d9f0d3ae13b..fd5014911d262 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -8,6 +8,8 @@ import boom from 'boom'; import { API_BASE_URL } from '../../common/constants'; import { ServerFacade, + ExportTypesRegistry, + Logger, RequestFacade, ReportingResponseToolkit, JobDocOutput, @@ -24,7 +26,11 @@ import { const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export function registerJobs(server: ServerFacade) { +export function registerJobInfoRoutes( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry, + logger: Logger +) { const jobsQuery = jobsQueryFactory(server); const getRouteConfig = getRouteConfigFactoryManagementPre(server); const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server); @@ -119,7 +125,7 @@ export function registerJobs(server: ServerFacade) { }); // trigger a download of the output from a job - const jobResponseHandler = jobResponseHandlerFactory(server); + const jobResponseHandler = jobResponseHandlerFactory(server, exportTypesRegistry); server.route({ path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', @@ -136,13 +142,15 @@ export function registerJobs(server: ServerFacade) { const { statusCode } = response; if (statusCode !== 200) { - const logLevel = statusCode === 500 ? 'error' : 'debug'; - server.log( - [logLevel, 'reporting', 'download'], - `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( - response.source - )}]` - ); + if (statusCode === 500) { + logger.error(`Report ${docId} has failed: ${JSON.stringify(response.source)}`); + } else { + logger.debug( + `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( + response.source + )}]` + ); + } } if (!response.isBoom) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts index a69c19c006b61..c3a30f9dda454 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -9,6 +9,7 @@ import * as _ from 'lodash'; import contentDisposition from 'content-disposition'; import { ServerFacade, + ExportTypesRegistry, ExportTypeDefinition, JobDocExecuted, JobDocOutputExecuted, @@ -40,9 +41,10 @@ const getReportingHeaders = (output: JobDocOutputExecuted, exportType: ExportTyp return metaDataHeaders; }; -export function getDocumentPayloadFactory(server: ServerFacade) { - const exportTypesRegistry = server.plugins.reporting!.exportTypesRegistry; - +export function getDocumentPayloadFactory( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry +) { function encodeContent(content: string | null, exportType: ExportTypeType) { switch (exportType.jobContentEncoding) { case 'base64': diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js index 758c50816c381..6bc370506a255 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js @@ -9,9 +9,9 @@ import { jobsQueryFactory } from '../../lib/jobs_query'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; import { getDocumentPayloadFactory } from './get_document_payload'; -export function jobResponseHandlerFactory(server) { +export function jobResponseHandlerFactory(server, exportTypesRegistry) { const jobsQuery = jobsQueryFactory(server); - const getDocumentPayload = getDocumentPayloadFactory(server); + const getDocumentPayload = getDocumentPayloadFactory(server, exportTypesRegistry); return function jobResponseHandler(validJobTypes, user, h, params, opts = {}) { const { docId } = params; diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.js b/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts similarity index 61% rename from x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.js rename to x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts index a1949b21aa086..f8913a0dcea6b 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.js +++ b/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts @@ -4,17 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { exportTypesRegistryFactory } from '../lib/export_types_registry'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; /* * Gets a handle to the Reporting export types registry and returns a few * functions for examining them - * @param {Object} server: Kibana server * @return {Object} export type handler */ -export async function getExportTypesHandler(server) { - const exportTypesRegistry = await exportTypesRegistryFactory(server); - +export function getExportTypesHandler(exportTypesRegistry: ExportTypesRegistry) { return { /* * Based on the X-Pack license and which export types are available, @@ -23,12 +21,17 @@ export async function getExportTypesHandler(server) { * @param {Object} xpackInfo: xpack_main plugin info object * @return {Object} availability of each export type */ - getAvailability(xpackInfo) { - const exportTypesAvailability = {}; + getAvailability(xpackInfo: XPackMainPlugin['info']) { + const exportTypesAvailability: { [exportType: string]: boolean } = {}; const xpackInfoAvailable = xpackInfo && xpackInfo.isAvailable(); - const licenseType = xpackInfo.license.getType(); - for(const exportType of exportTypesRegistry.getAll()) { - exportTypesAvailability[exportType.jobType] = xpackInfoAvailable ? exportType.validLicenses.includes(licenseType) : false; + const licenseType: string | undefined = xpackInfo.license.getType(); + if (!licenseType) { + throw new Error('No license type returned from XPackMainPlugin#info!'); + } + for (const exportType of exportTypesRegistry.getAll()) { + exportTypesAvailability[exportType.jobType] = xpackInfoAvailable + ? exportType.validLicenses.includes(licenseType) + : false; } return exportTypesAvailability; @@ -39,6 +42,6 @@ export async function getExportTypesHandler(server) { */ getNumExportTypes() { return exportTypesRegistry.getSize(); - } + }, }; } diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts index 0c85d39ae55d3..bd2d0cb835a79 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { ServerFacade, ESCallCluster } from '../../types'; +import { ServerFacade, ExportTypesRegistry, ESCallCluster } from '../../types'; import { AggregationBuckets, AggregationResults, @@ -16,7 +16,6 @@ import { RangeStats, } from './types'; import { decorateRangeStats } from './decorate_range_stats'; -// @ts-ignore untyped module import { getExportTypesHandler } from './get_export_type_handler'; const JOB_TYPES_KEY = 'jobTypes'; @@ -101,7 +100,11 @@ async function handleResponse( }; } -export async function getReportingUsage(server: ServerFacade, callCluster: ESCallCluster) { +export async function getReportingUsage( + server: ServerFacade, + callCluster: ESCallCluster, + exportTypesRegistry: ExportTypesRegistry +) { const config = server.config(); const reportingIndex = config.get('xpack.reporting.index'); @@ -138,13 +141,13 @@ export async function getReportingUsage(server: ServerFacade, callCluster: ESCal return callCluster('search', params) .then((response: AggregationResults) => handleResponse(server, response)) - .then(async (usage: RangeStatSets) => { + .then((usage: RangeStatSets) => { // Allow this to explicitly throw an exception if/when this config is deprecated, // because we shouldn't collect browserType in that case! const browserType = config.get('xpack.reporting.capture.browser.type'); const xpackInfo = server.plugins.xpack_main.info; - const exportTypesHandler = await getExportTypesHandler(server); + const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); const availability = exportTypesHandler.getAvailability(xpackInfo) as FeatureAvailabilityMap; const { lastDay, last7Days, ...all } = usage; diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js index dd040a40a126a..d5b7232551d60 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import sinon from 'sinon'; +import { getExportTypesRegistry } from '../lib/export_types_registry'; import { getReportingUsageCollector } from './reporting_usage_collector'; +const exportTypesRegistry = getExportTypesRegistry(); + function getMockUsageCollection() { class MockUsageCollector { constructor(_server, { fetch }) { // eslint-disable-line no-unused-vars @@ -40,7 +43,6 @@ function getServerMock(customization) { }, }, }, - expose: () => {}, log: () => {}, config: () => ({ get: key => { @@ -67,8 +69,13 @@ describe('license checks', () => { .returns('basic'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); const usageCollection = getMockUsageCollection(); - const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithBasicLicenseMock); - usageStats = await getReportingUsage(callClusterMock); + const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithBasicLicenseMock, + () => {}, + exportTypesRegistry + ); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -93,8 +100,13 @@ describe('license checks', () => { .returns('none'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); const usageCollection = getMockUsageCollection(); - const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithNoLicenseMock); - usageStats = await getReportingUsage(callClusterMock); + const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithNoLicenseMock, + () => {}, + exportTypesRegistry + ); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -121,9 +133,11 @@ describe('license checks', () => { const usageCollection = getMockUsageCollection(); const { fetch: getReportingUsage } = getReportingUsageCollector( usageCollection, - serverWithPlatinumLicenseMock + serverWithPlatinumLicenseMock, + () => {}, + exportTypesRegistry ); - usageStats = await getReportingUsage(callClusterMock); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -148,8 +162,13 @@ describe('license checks', () => { .returns('basic'); const callClusterMock = jest.fn(() => Promise.resolve({})); const usageCollection = getMockUsageCollection(); - const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithBasicLicenseMock); - usageStats = await getReportingUsage(callClusterMock); + const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithBasicLicenseMock, + () => {}, + exportTypesRegistry + ); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -170,7 +189,12 @@ describe('data modeling', () => { serverWithPlatinumLicenseMock.plugins.xpack_main.info.license.getType = sinon .stub() .returns('platinum'); - ({ fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithPlatinumLicenseMock)); + ({ fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithPlatinumLicenseMock, + () => {}, + exportTypesRegistry + )); }); test('with normal looking usage data', async () => { @@ -295,6 +319,7 @@ describe('data modeling', () => { }) ) ); + const usageStats = await getReportingUsage(callClusterMock); expect(usageStats).toMatchInlineSnapshot(` Object { diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts index 0a7ef0a194434..40cf315a78cbb 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts @@ -7,7 +7,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; // @ts-ignore untyped module import { KIBANA_STATS_TYPE_MONITORING } from '../../../monitoring/common/constants'; -import { ServerFacade, ESCallCluster } from '../../types'; +import { ServerFacade, ExportTypesRegistry, ESCallCluster } from '../../types'; import { KIBANA_REPORTING_TYPE } from '../../common/constants'; import { getReportingUsage } from './get_reporting_usage'; import { RangeStats } from './types'; @@ -19,12 +19,14 @@ import { RangeStats } from './types'; export function getReportingUsageCollector( usageCollection: UsageCollectionSetup, server: ServerFacade, - isReady: () => boolean + isReady: () => boolean, + exportTypesRegistry: ExportTypesRegistry ) { return usageCollection.makeUsageCollector({ type: KIBANA_REPORTING_TYPE, isReady, - fetch: (callCluster: ESCallCluster) => getReportingUsage(server, callCluster), + fetch: (callCluster: ESCallCluster) => + getReportingUsage(server, callCluster, exportTypesRegistry), /* * Format the response data into a model for internal upload @@ -49,8 +51,14 @@ export function getReportingUsageCollector( export function registerReportingUsageCollector( usageCollection: UsageCollectionSetup, server: ServerFacade, - isReady: () => boolean + isReady: () => boolean, + exportTypesRegistry: ExportTypesRegistry ) { - const collector = getReportingUsageCollector(usageCollection, server, isReady); + const collector = getReportingUsageCollector( + usageCollection, + server, + isReady, + exportTypesRegistry + ); usageCollection.registerCollector(collector); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index f43caf92ffb15..28f64b8eefcbc 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -13,9 +13,12 @@ import { CallCluster, } from '../../../../src/legacy/core_plugins/elasticsearch'; import { CancellationToken } from './common/cancellation_token'; +import { LevelLogger } from './server/lib/level_logger'; import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; import { BrowserType } from './server/browsers/types'; +export type ReportingPlugin = object; // For Plugin contract + export type Job = EventEmitter & { id: string; toJSON: () => { @@ -23,21 +26,6 @@ export type Job = EventEmitter & { }; }; -export interface ReportingPlugin { - queue: { - addJob: (type: string, payload: PayloadType, options: object) => Job; - }; - // TODO: convert exportTypesRegistry to TS - exportTypesRegistry: { - getById: (id: string) => ExportTypeDefinition; - getAll: () => Array>; - get: ( - callback: (item: ExportTypeDefinition) => boolean - ) => ExportTypeDefinition; - }; - browserDriverFactory: HeadlessChromiumDriverFactory; -} - export interface ReportingConfigOptions { browser: BrowserConfig; poll: { @@ -88,7 +76,6 @@ export type ReportingPluginSpecOptions = Legacy.PluginSpecOptions; export type ServerFacade = Legacy.Server & { plugins: { - reporting?: ReportingPlugin; xpack_main?: XPackMainPlugin & { status?: any; }; @@ -107,6 +94,15 @@ interface ReportingRequest { }; } +export type EnqueueJobFn = ( + parentLogger: LevelLogger, + exportTypeId: string, + jobParams: JobParamsType, + user: string, + headers: Record, + request: RequestFacade +) => Promise; + export type RequestFacade = ReportingRequest & Legacy.Request; export type ResponseFacade = ResponseObject & { @@ -246,6 +242,10 @@ export interface JobDocOutputExecuted { size: number; } +export interface ESQueue { + addJob: (type: string, payload: object, options: object) => Job; +} + export interface ESQueueWorker { on: (event: string, handler: any) => void; } @@ -304,7 +304,12 @@ export interface ESQueueInstance { } export type CreateJobFactory = (server: ServerFacade) => CreateJobFnType; -export type ExecuteJobFactory = (server: ServerFacade) => ExecuteJobFnType; +export type ExecuteJobFactory = ( + server: ServerFacade, + opts: { + browserDriverFactory: HeadlessChromiumDriverFactory; + } +) => ExecuteJobFnType; export interface ExportTypeDefinition< JobParamsType, @@ -322,18 +327,16 @@ export interface ExportTypeDefinition< validLicenses: string[]; } -export interface ExportTypesRegistry { - register: ( - exportTypeDefinition: ExportTypeDefinition< - JobParamsType, - CreateJobFnType, - JobPayloadType, - ExecuteJobFnType - > - ) => void; -} - +export { ExportTypesRegistry } from './server/lib/export_types_registry'; +export { HeadlessChromiumDriver } from './server/browsers/chromium/driver'; +export { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; export { CancellationToken } from './common/cancellation_token'; -// Prefer to import this type using: `import { LevelLogger } from 'relative/path/server/lib';` -export { LevelLogger as Logger } from './server/lib/level_logger'; +export { LevelLogger as Logger }; + +export interface AbsoluteURLFactoryOptions { + defaultBasePath: string; + protocol: string; + hostname: string; + port: string | number; +}