From d90df7a0be6831393511cfec1076e8f2f88d4101 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 2 Sep 2020 12:50:59 -0700 Subject: [PATCH] Pre work for Task Manager: fixing types and minor stuff --- x-pack/plugins/reporting/common/types.ts | 2 +- .../register_pdf_png_reporting.tsx | 6 +- .../chromium/driver/chromium_driver.ts | 3 +- x-pack/plugins/reporting/server/core.ts | 7 +- .../common/decrypt_job_headers.test.ts | 29 +-- .../common/decrypt_job_headers.ts | 25 +-- .../common/get_conditional_headers.test.ts | 68 +------ .../common/get_conditional_headers.ts | 13 +- .../common/get_custom_logo.test.ts | 14 +- .../export_types/common/get_custom_logo.ts | 19 +- .../export_types/common/get_full_urls.test.ts | 86 +++----- .../export_types/common/get_full_urls.ts | 25 +-- .../common/omit_blacklisted_headers.test.ts | 17 +- .../common/omit_blacklisted_headers.ts | 8 +- .../server/export_types/csv/create_job.ts | 6 +- .../export_types/csv/execute_job.test.ts | 6 +- .../server/export_types/csv/execute_job.ts | 14 +- .../export_types/csv/generate_csv/index.ts | 11 +- .../server/export_types/csv/index.ts | 12 +- .../server/export_types/csv/types.d.ts | 5 +- .../csv_from_savedobject/create_job.ts | 27 +-- .../csv_from_savedobject/execute_job.ts | 22 +- .../csv_from_savedobject/index.ts | 6 +- .../lib/get_csv_job.test.ts | 22 +- .../csv_from_savedobject/lib/get_csv_job.ts | 4 +- .../csv_from_savedobject/types.d.ts | 19 +- .../export_types/png/create_job/index.ts | 18 +- .../png/execute_job/index.test.ts | 6 +- .../export_types/png/execute_job/index.ts | 26 ++- .../server/export_types/png/index.ts | 12 +- .../export_types/png/lib/generate_png.ts | 3 +- .../server/export_types/png/types.d.ts | 8 +- .../printable_pdf/create_job/index.ts | 12 +- .../printable_pdf/execute_job/index.test.ts | 6 +- .../printable_pdf/execute_job/index.ts | 28 ++- .../export_types/printable_pdf/index.ts | 12 +- .../printable_pdf/lib/generate_pdf.ts | 7 +- .../export_types/printable_pdf/types.d.ts | 10 +- .../reporting/server/lib/create_queue.ts | 5 +- .../reporting/server/lib/create_worker.ts | 14 +- .../reporting/server/lib/enqueue_job.ts | 30 ++- x-pack/plugins/reporting/server/lib/index.ts | 4 +- .../server/lib/layouts/create_layout.ts | 8 +- .../reporting/server/lib/layouts/index.ts | 2 +- .../server/lib/layouts/preserve_layout.ts | 3 +- .../server/lib/layouts/print_layout.ts | 10 +- .../screenshots/get_element_position_data.ts | 4 +- .../lib/screenshots/get_number_of_items.ts | 2 +- .../server/lib/screenshots/get_screenshots.ts | 4 +- .../server/lib/screenshots/get_time_range.ts | 2 +- .../reporting/server/lib/screenshots/index.ts | 57 ++++++ .../server/lib/screenshots/observable.test.ts | 5 +- .../server/lib/screenshots/observable.ts | 4 +- .../server/lib/screenshots/open_url.ts | 2 +- .../server/lib/screenshots/wait_for_render.ts | 2 +- .../screenshots/wait_for_visualizations.ts | 2 +- .../reporting/server/lib/store/index.ts | 2 +- .../reporting/server/lib/store/report.test.ts | 157 ++++++++++----- .../reporting/server/lib/store/report.ts | 138 +++++++++---- .../reporting/server/lib/store/store.test.ts | 190 ++++++++++++------ .../reporting/server/lib/store/store.ts | 172 ++++++++++------ .../plugins/reporting/server/plugin.test.ts | 37 ++-- x-pack/plugins/reporting/server/plugin.ts | 4 +- .../server/routes/generate_from_jobparams.ts | 15 +- .../generate_from_savedobject_immediate.ts | 25 +-- .../server/routes/generation.test.ts | 20 +- .../plugins/reporting/server/routes/index.ts | 7 + .../reporting/server/routes/jobs.test.ts | 5 +- .../plugins/reporting/server/routes/jobs.ts | 7 +- .../server/routes/lib/get_document_payload.ts | 17 +- .../routes/lib/get_job_params_from_request.ts | 6 +- .../reporting/server/routes/lib/jobs_query.ts | 5 +- .../reporting/server/routes/types.d.ts | 4 +- .../create_mock_browserdriverfactory.ts | 3 +- .../create_mock_reportingplugin.ts | 58 +++--- x-pack/plugins/reporting/server/types.ts | 126 ++---------- 76 files changed, 910 insertions(+), 872 deletions(-) diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts index 18b0ac2a7280261..038f869b994caa0 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types.ts @@ -7,7 +7,7 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths export { ReportingConfigType } from '../server/config'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { LayoutInstance } from '../server/lib/layouts'; +export { LayoutParams } from '../server/lib/layouts'; export type JobId = string; export type JobStatus = diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index e10d04ea5fc6bd2..2dab66187bb25de 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; import { ShareContext } from '../../../../../src/plugins/share/public'; import { LicensingPluginSetup } from '../../../licensing/public'; -import { LayoutInstance } from '../../common/types'; +import { LayoutParams } from '../../common/types'; import { JobParamsPNG } from '../../server/export_types/png/types'; import { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content'; @@ -80,7 +80,7 @@ export const reportingPDFPNGProvider = ({ objectType, browserTimezone, relativeUrls: [relativeUrl], // multi URL for PDF - layout: sharingData.layout as LayoutInstance, + layout: sharingData.layout as LayoutParams, title: sharingData.title as string, }; }; @@ -96,7 +96,7 @@ export const reportingPDFPNGProvider = ({ objectType, browserTimezone, relativeUrl, // single URL for PNG - layout: sharingData.layout as LayoutInstance, + layout: sharingData.layout as LayoutParams, title: sharingData.title as string, }; }; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 5f23bbc390bb80c..0a76c7fcfd3b274 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -12,7 +12,8 @@ import { parse as parseUrl } from 'url'; import { getDisallowedOutgoingUrlError } from '../'; import { LevelLogger } from '../../../lib'; import { ViewZoomWidthHeight } from '../../../lib/layouts/layout'; -import { ConditionalHeaders, ElementPosition } from '../../../types'; +import { ElementPosition } from '../../../lib/screenshots'; +import { ConditionalHeaders } from '../../../types'; import { allowRequest, NetworkPolicy } from '../../network_policy'; export interface ChromiumDriverOptions { diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 25594e1c0140b78..ddffa1e7def22cb 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -17,12 +17,11 @@ import { } from 'src/core/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; -import { ScreenshotsObservableFn } from '../server/types'; import { ReportingConfig } from './'; import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; -import { screenshotsObservableFactory } from './lib/screenshots'; import { checkLicense, getExportTypesRegistry } from './lib'; import { ESQueueInstance } from './lib/create_queue'; +import { screenshotsObservableFactory, ScreenshotsObservableFn } from './lib/screenshots'; import { ReportingStore } from './lib/store'; export interface ReportingInternalSetup { @@ -42,11 +41,11 @@ export interface ReportingInternalStart { } export class ReportingCore { + private exportTypesRegistry = getExportTypesRegistry(); private pluginSetupDeps?: ReportingInternalSetup; private pluginStartDeps?: ReportingInternalStart; private readonly pluginSetup$ = new Rx.ReplaySubject(); // observe async background setupDeps and config each are done private readonly pluginStart$ = new Rx.ReplaySubject(); // observe async background startDeps - private exportTypesRegistry = getExportTypesRegistry(); private config?: ReportingConfig; constructor() {} @@ -62,7 +61,7 @@ export class ReportingCore { /* * Register startDeps */ - public pluginStart(startDeps: ReportingInternalStart) { + public async pluginStart(startDeps: ReportingInternalStart) { this.pluginStart$.next(startDeps); // trigger the observer this.pluginStartDeps = startDeps; // cache } diff --git a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts index 908817a2ccf818b..9c6b1440f7c100a 100644 --- a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cryptoFactory, LevelLogger } from '../../lib'; +import { cryptoFactory } from '../../lib'; +import { createMockLevelLogger } from '../../test_helpers/create_mock_levellogger'; import { decryptJobHeaders } from './'; const encryptHeaders = async (encryptionKey: string, headers: Record) => { @@ -13,17 +14,13 @@ const encryptHeaders = async (encryptionKey: string, headers: Record { + const encryptionKey = 'abcsecretsauce'; + const logger = createMockLevelLogger(); + test(`fails if it can't decrypt headers`, async () => { - const getDecryptedHeaders = () => - decryptJobHeaders({ - encryptionKey: 'abcsecretsauce', - job: { - headers: 'Q53+9A+zf+Xe+ceR/uB/aR/Sw/8e+M+qR+WiG+8z+EY+mo+HiU/zQL+Xn', - }, - logger: ({ - error: jest.fn(), - } as unknown) as LevelLogger, - }); + const headers = 'Q53+9A+zf+Xe+ceR/uB/aR/Sw/8e+M+qR+WiG+8z+EY+mo+HiU/zQL+Xn'; + + const getDecryptedHeaders = () => decryptJobHeaders(encryptionKey, headers, logger); await expect(getDecryptedHeaders()).rejects.toMatchInlineSnapshot( `[Error: Failed to decrypt report job data. Please ensure that xpack.reporting.encryptionKey is set and re-generate this report. Error: Invalid IV length]` ); @@ -36,15 +33,7 @@ describe('headers', () => { }; const encryptedHeaders = await encryptHeaders('abcsecretsauce', headers); - const decryptedHeaders = await decryptJobHeaders({ - encryptionKey: 'abcsecretsauce', - job: { - title: 'cool-job-bro', - type: 'csv', - headers: encryptedHeaders, - }, - logger: {} as LevelLogger, - }); + const decryptedHeaders = await decryptJobHeaders(encryptionKey, encryptedHeaders, logger); expect(decryptedHeaders).toEqual(headers); }); }); diff --git a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts index 845b9adb38be98e..a80a1f740d40b3a 100644 --- a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts @@ -7,25 +7,14 @@ import { i18n } from '@kbn/i18n'; import { cryptoFactory, LevelLogger } from '../../lib'; -interface HasEncryptedHeaders { - headers?: string; -} - // TODO merge functionality with CSV execute job -export const decryptJobHeaders = async < - JobParamsType, - ScheduledTaskParamsType extends HasEncryptedHeaders ->({ - encryptionKey, - job, - logger, -}: { - encryptionKey?: string; - job: ScheduledTaskParamsType; - logger: LevelLogger; -}): Promise> => { +export const decryptJobHeaders = async ( + encryptionKey: string | undefined, + headers: string | undefined, + logger: LevelLogger +): Promise> => { try { - if (typeof job.headers !== 'string') { + if (typeof headers !== 'string') { throw new Error( i18n.translate('xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage', { defaultMessage: 'Job headers are missing', @@ -33,7 +22,7 @@ export const decryptJobHeaders = async < ); } const crypto = cryptoFactory(encryptionKey); - const decryptedHeaders = (await crypto.decrypt(job.headers)) as Record; + const decryptedHeaders = (await crypto.decrypt(headers)) as Record; return decryptedHeaders; } catch (err) { logger.error(err); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts index 0372d515c21a865..855c12c12e9cdd7 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts @@ -8,8 +8,6 @@ import sinon from 'sinon'; import { ReportingConfig } from '../../'; import { ReportingCore } from '../../core'; import { createMockReportingCore } from '../../test_helpers'; -import { ScheduledTaskParams } from '../../types'; -import { ScheduledTaskParamsPDF } from '../printable_pdf/types'; import { getConditionalHeaders, getCustomLogo } from './'; let mockConfig: ReportingConfig; @@ -36,11 +34,7 @@ describe('conditions', () => { baz: 'quix', }; - const conditionalHeaders = await getConditionalHeaders({ - job: {} as ScheduledTaskParams, - filteredHeaders: permittedHeaders, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, permittedHeaders); expect(conditionalHeaders.conditions.hostname).toEqual( mockConfig.get('kibanaServer', 'hostname') @@ -55,32 +49,7 @@ describe('conditions', () => { }); }); -test('uses basePath from job when creating saved object service', async () => { - const mockGetSavedObjectsClient = jest.fn(); - mockReportingPlugin.getSavedObjectsClient = mockGetSavedObjectsClient; - - const permittedHeaders = { - foo: 'bar', - baz: 'quix', - }; - const conditionalHeaders = await getConditionalHeaders({ - job: {} as ScheduledTaskParams, - filteredHeaders: permittedHeaders, - config: mockConfig, - }); - const jobBasePath = '/sbp/s/marketing'; - await getCustomLogo({ - reporting: mockReportingPlugin, - job: { basePath: jobBasePath } as ScheduledTaskParamsPDF, - conditionalHeaders, - config: mockConfig, - }); - - const getBasePath = mockGetSavedObjectsClient.mock.calls[0][0].getBasePath; - expect(getBasePath()).toBe(jobBasePath); -}); - -test(`uses basePath from server if job doesn't have a basePath when creating saved object service`, async () => { +test(`uses basePath from server when creating saved object service`, async () => { const mockGetSavedObjectsClient = jest.fn(); mockReportingPlugin.getSavedObjectsClient = mockGetSavedObjectsClient; @@ -93,18 +62,9 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav foo: 'bar', baz: 'quix', }; - const conditionalHeaders = await getConditionalHeaders({ - job: {} as ScheduledTaskParams, - filteredHeaders: permittedHeaders, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, permittedHeaders); - await getCustomLogo({ - reporting: mockReportingPlugin, - job: {} as ScheduledTaskParamsPDF, - conditionalHeaders, - config: mockConfig, - }); + await getCustomLogo(mockReportingPlugin, mockConfig, conditionalHeaders); const getBasePath = mockGetSavedObjectsClient.mock.calls[0][0].getBasePath; expect(getBasePath()).toBe(`/sbp`); @@ -138,11 +98,7 @@ describe('config formatting', () => { const mockConfigGet = sinon.stub().withArgs('server', 'host').returns('COOL-HOSTNAME'); mockConfig = getMockConfig(mockConfigGet); - const conditionalHeaders = await getConditionalHeaders({ - job: {} as ScheduledTaskParams, - filteredHeaders: {}, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, {}); expect(conditionalHeaders.conditions.hostname).toEqual('cool-hostname'); }); @@ -152,19 +108,7 @@ describe('config formatting', () => { .withArgs('kibanaServer', 'hostname') .returns('GREAT-HOSTNAME'); mockConfig = getMockConfig(mockConfigGet); - const conditionalHeaders = await getConditionalHeaders({ - job: { - title: 'cool-job-bro', - type: 'csv', - jobParams: { - savedObjectId: 'abc-123', - isImmediate: false, - savedObjectType: 'search', - }, - }, - filteredHeaders: {}, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, {}); expect(conditionalHeaders.conditions.hostname).toEqual('great-hostname'); }); }); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts index 799d02348683272..0bd8a634ce0e827 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts @@ -7,15 +7,10 @@ import { ReportingConfig } from '../../'; import { ConditionalHeaders } from '../../types'; -export const getConditionalHeaders = ({ - config, - job, - filteredHeaders, -}: { - config: ReportingConfig; - job: ScheduledTaskParamsType; - filteredHeaders: Record; -}) => { +export const getConditionalHeaders = ( + config: ReportingConfig, + filteredHeaders: Record +) => { const { kbnConfig } = config; const [hostname, port, basePath, protocol] = [ config.get('kibanaServer', 'hostname'), diff --git a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts index a3d65a1398a2027..201cda6a125ea28 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts @@ -6,7 +6,6 @@ import { ReportingCore } from '../../core'; import { createMockReportingCore } from '../../test_helpers'; -import { ScheduledTaskParamsPDF } from '../printable_pdf/types'; import { getConditionalHeaders, getCustomLogo } from './'; const mockConfigGet = jest.fn().mockImplementation((key: string) => { @@ -36,18 +35,9 @@ test(`gets logo from uiSettings`, async () => { get: mockGet, }); - const conditionalHeaders = await getConditionalHeaders({ - job: {} as ScheduledTaskParamsPDF, - filteredHeaders: permittedHeaders, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, permittedHeaders); - const { logo } = await getCustomLogo({ - reporting: mockReportingPlugin, - config: mockConfig, - job: {} as ScheduledTaskParamsPDF, - conditionalHeaders, - }); + const { logo } = await getCustomLogo(mockReportingPlugin, mockConfig, conditionalHeaders); expect(mockGet).toBeCalledWith('xpackReporting:customPdfLogo'); expect(logo).toBe('purple pony'); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts index 547cc45258dae2b..35c3cbd63903ed2 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts @@ -7,26 +7,19 @@ import { ReportingConfig, ReportingCore } from '../../'; import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../common/constants'; import { ConditionalHeaders } from '../../types'; -import { ScheduledTaskParamsPDF } from '../printable_pdf/types'; // Logo is PDF only -export const getCustomLogo = async ({ - reporting, - config, - job, - conditionalHeaders, -}: { - reporting: ReportingCore; - config: ReportingConfig; - job: ScheduledTaskParamsPDF; - conditionalHeaders: ConditionalHeaders; -}) => { +export const getCustomLogo = async ( + reporting: ReportingCore, + config: ReportingConfig, + conditionalHeaders: ConditionalHeaders +) => { const serverBasePath: string = config.kbnConfig.get('server', 'basePath'); const fakeRequest: any = { headers: conditionalHeaders.headers, // This is used by the spaces SavedObjectClientWrapper to determine the existing space. // We use the basePath from the saved job, which we'll have post spaces being implemented; // or we use the server base path, which uses the default space - getBasePath: () => job.basePath || serverBasePath, + getBasePath: () => serverBasePath, path: '/', route: { settings: {} }, url: { href: '/' }, diff --git a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts index 73d7c7b03c1283f..6aeea7813679df5 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts @@ -5,15 +5,10 @@ */ import { ReportingConfig } from '../../'; -import { ScheduledTaskParamsPNG } from '../png/types'; -import { ScheduledTaskParamsPDF } from '../printable_pdf/types'; +import { TaskPayloadPNG } from '../png/types'; +import { TaskPayloadPDF } from '../printable_pdf/types'; import { getFullUrls } from './get_full_urls'; -interface FullUrlsOpts { - job: ScheduledTaskParamsPNG & ScheduledTaskParamsPDF; - config: ReportingConfig; -} - let mockConfig: ReportingConfig; const getMockConfig = (mockConfigGet: jest.Mock) => { return { @@ -35,10 +30,10 @@ beforeEach(() => { mockConfig = getMockConfig(mockConfigGet); }); -const getMockJob = (base: object) => base as ScheduledTaskParamsPNG & ScheduledTaskParamsPDF; +const getMockJob = (base: object) => base as TaskPayloadPNG & TaskPayloadPDF; test(`fails if no URL is passed`, async () => { - const fn = () => getFullUrls({ job: getMockJob({}), config: mockConfig } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({})); expect(fn).toThrowErrorMatchingInlineSnapshot( `"No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`"` ); @@ -47,11 +42,7 @@ test(`fails if no URL is passed`, async () => { test(`fails if URLs are file-protocols for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrl, forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrl, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); @@ -61,11 +52,7 @@ test(`fails if URLs are absolute for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrl, forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrl, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); @@ -74,14 +61,7 @@ test(`fails if URLs are absolute for PNGs`, async () => { test(`fails if URLs are file-protocols for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ - relativeUrls: [relativeUrl], - forceNow, - }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrls: [relativeUrl], forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); @@ -91,14 +71,7 @@ test(`fails if URLs are absolute for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ - relativeUrls: [relativeUrl], - forceNow, - }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrls: [relativeUrl], forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); @@ -112,22 +85,14 @@ test(`fails if any URLs are absolute or file's for PDF`, async () => { 'file://etc/passwd/#/something', ]; - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrls, forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrls, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something file://etc/passwd/#/something"` ); }); test(`fails if URL does not route to a visualization`, async () => { - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrl: '/app/phoney' }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/phoney' })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"No valid hash in the URL! A hash is expected for the application to route to the intended visualization."` ); @@ -135,10 +100,10 @@ test(`fails if URL does not route to a visualization`, async () => { test(`adds forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls({ - job: getMockJob({ relativeUrl: '/app/kibana#/something', forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const urls = await getFullUrls( + mockConfig, + getMockJob({ relativeUrl: '/app/kibana#/something', forceNow }) + ); expect(urls[0]).toEqual( 'http://localhost:5601/sbp/app/kibana#/something?forceNow=2000-01-01T00%3A00%3A00.000Z' @@ -148,10 +113,10 @@ test(`adds forceNow to hash's query, if it exists`, async () => { test(`appends forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls({ - job: getMockJob({ relativeUrl: '/app/kibana#/something?_g=something', forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const urls = await getFullUrls( + mockConfig, + getMockJob({ relativeUrl: '/app/kibana#/something?_g=something', forceNow }) + ); expect(urls[0]).toEqual( 'http://localhost:5601/sbp/app/kibana#/something?_g=something&forceNow=2000-01-01T00%3A00%3A00.000Z' @@ -159,18 +124,16 @@ test(`appends forceNow to hash's query, if it exists`, async () => { }); test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { - const urls = await getFullUrls({ - job: getMockJob({ relativeUrl: '/app/kibana#/something' }), - config: mockConfig, - } as FullUrlsOpts); + const urls = await getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something' })); expect(urls[0]).toEqual('http://localhost:5601/sbp/app/kibana#/something'); }); test(`adds forceNow to each of multiple urls`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls({ - job: getMockJob({ + const urls = await getFullUrls( + mockConfig, + getMockJob({ relativeUrls: [ '/app/kibana#/something_aaa', '/app/kibana#/something_bbb', @@ -178,9 +141,8 @@ test(`adds forceNow to each of multiple urls`, async () => { '/app/kibana#/something_ddd', ], forceNow, - }), - config: mockConfig, - } as FullUrlsOpts); + }) + ); expect(urls).toEqual([ 'http://localhost:5601/sbp/app/kibana#/something_aaa?forceNow=2000-01-01T00%3A00%3A00.000Z', diff --git a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts index d3362fd1906807a..aa146e044948f8a 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts @@ -11,29 +11,19 @@ import { UrlWithStringQuery, } from 'url'; import { ReportingConfig } from '../../'; -import { ScheduledTaskParamsPNG } from '../png/types'; -import { ScheduledTaskParamsPDF } from '../printable_pdf/types'; +import { TaskPayloadPNG } from '../png/types'; +import { TaskPayloadPDF } from '../printable_pdf/types'; import { getAbsoluteUrlFactory } from './get_absolute_url'; import { validateUrls } from './validate_urls'; -function isPngJob( - job: ScheduledTaskParamsPNG | ScheduledTaskParamsPDF -): job is ScheduledTaskParamsPNG { - return (job as ScheduledTaskParamsPNG).relativeUrl !== undefined; +function isPngJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPNG { + return (job as TaskPayloadPNG).relativeUrl !== undefined; } -function isPdfJob( - job: ScheduledTaskParamsPNG | ScheduledTaskParamsPDF -): job is ScheduledTaskParamsPDF { - return (job as ScheduledTaskParamsPDF).relativeUrls !== undefined; +function isPdfJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPDF { + return (job as TaskPayloadPDF).relativeUrls !== undefined; } -export function getFullUrls({ - config, - job, -}: { - config: ReportingConfig; - job: ScheduledTaskParamsPDF | ScheduledTaskParamsPNG; -}) { +export function getFullUrls(config: ReportingConfig, job: TaskPayloadPDF | TaskPayloadPNG) { const [basePath, protocol, hostname, port] = [ config.kbnConfig.get('server', 'basePath'), config.get('kibanaServer', 'protocol'), @@ -65,7 +55,6 @@ export function getFullUrls({ const urls = relativeUrls.map((relativeUrl) => { const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); const jobUrl = getAbsoluteUrl({ - basePath: job.basePath, path: parsedRelative.pathname, hash: parsedRelative.hash, search: parsedRelative.search, diff --git a/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.test.ts index abf5784dacff9f8..d12866e43895979 100644 --- a/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.test.ts @@ -24,20 +24,9 @@ test(`omits blacklisted headers`, async () => { trailer: 's are for trucks', }; - const filteredHeaders = await omitBlacklistedHeaders({ - job: { - title: 'cool-job-bro', - type: 'csv', - jobParams: { - savedObjectId: 'abc-123', - isImmediate: false, - savedObjectType: 'search', - }, - }, - decryptedHeaders: { - ...permittedHeaders, - ...blacklistedHeaders, - }, + const filteredHeaders = omitBlacklistedHeaders({ + ...permittedHeaders, + ...blacklistedHeaders, }); expect(filteredHeaders).toEqual(permittedHeaders); diff --git a/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.ts b/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.ts index e56ffc737764c89..15f07e6a7efb310 100644 --- a/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/omit_blacklisted_headers.ts @@ -9,13 +9,7 @@ import { KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN, } from '../../../common/constants'; -export const omitBlacklistedHeaders = ({ - job, - decryptedHeaders, -}: { - job: ScheduledTaskParamsType; - decryptedHeaders: Record; -}) => { +export const omitBlacklistedHeaders = (decryptedHeaders: Record) => { const filteredHeaders: Record = omitBy( decryptedHeaders, (_value, header: string) => diff --git a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts index 252968e386b53f4..be18bd7fff361d3 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts @@ -5,16 +5,16 @@ */ import { cryptoFactory } from '../../lib'; -import { CreateJobFn, ScheduleTaskFnFactory } from '../../types'; +import { CreateJobFn, CreateJobFnFactory } from '../../types'; import { JobParamsDiscoverCsv } from './types'; -export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - return async function scheduleTask(jobParams, context, request) { + return async function createJob(jobParams, context, request) { const serializedEncryptedHeaders = await crypto.encrypt(request.headers); const savedObjectsClient = context.core.savedObjects.client; diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts index 5eeef0f9906dd49..7883e45411a67f0 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts @@ -18,10 +18,11 @@ import { import { CancellationToken } from '../../../common'; import { CSV_BOM_CHARS } from '../../../common/constants'; import { LevelLogger } from '../../lib'; +import { ReportTaskParams } from '../../lib/store'; import { setFieldFormats } from '../../services'; import { createMockReportingCore } from '../../test_helpers'; import { runTaskFnFactory } from './execute_job'; -import { ScheduledTaskParamsCSV } from './types'; +import { TaskPayloadCSV } from './types'; const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms)); @@ -30,7 +31,8 @@ const getRandomScrollId = () => { return puid.generate(); }; -const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsCSV; +const getScheduledTaskParams = (baseObj: any) => + ({ payload: baseObj } as ReportTaskParams); describe('CSV Execute Job', function () { const encryptionKey = 'testEncryptionKey'; diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts index b56a08b86b0cd2d..b6fa0b145008f9d 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts @@ -10,8 +10,8 @@ import Hapi from 'hapi'; import { KibanaRequest } from '../../../../../../src/core/server'; import { CONTENT_TYPE_CSV, CSV_JOB_TYPE } from '../../../common/constants'; import { cryptoFactory, LevelLogger } from '../../lib'; -import { WorkerExecuteFn, RunTaskFnFactory } from '../../types'; -import { ScheduledTaskParamsCSV } from './types'; +import { RunTaskFn, RunTaskFnFactory } from '../../types'; +import { TaskPayloadCSV } from './types'; import { createGenerateCsv } from './generate_csv'; const getRequest = async (headers: string | undefined, crypto: Crypto, logger: LevelLogger) => { @@ -55,8 +55,8 @@ const getRequest = async (headers: string | undefined, crypto: Crypto, logger: L } as Hapi.Request); }; -export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); @@ -67,8 +67,8 @@ export const runTaskFnFactory: RunTaskFnFactory @@ -78,7 +78,7 @@ export const runTaskFnFactory: RunTaskFnFactory { - const settings = await getUiSettings( - job.jobParams?.browserTimezone, - uiSettingsClient, - config, - logger - ); + const settings = await getUiSettings(job.browserTimezone, uiSettingsClient, config, logger); const escapeValue = createEscapeValue(settings.quoteValues, settings.escapeFormulaValues); const bom = config.get('csv', 'useByteOrderMarkEncoding') ? CSV_BOM_CHARS : ''; const builder = new MaxSizeStringBuilder(settings.maxSizeBytes, bom); diff --git a/x-pack/plugins/reporting/server/export_types/csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/index.ts index 4bca42e0661e5cd..bc851da7ba2b54e 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/index.ts @@ -13,22 +13,22 @@ import { LICENSE_TYPE_TRIAL, } from '../../../common/constants'; import { CSV_JOB_TYPE as jobType } from '../../../constants'; -import { CreateJobFn, WorkerExecuteFn, ExportTypeDefinition } from '../../types'; -import { scheduleTaskFnFactory } from './create_job'; +import { CreateJobFn, RunTaskFn, ExportTypeDefinition } from '../../types'; +import { createJobFnFactory } from './create_job'; import { runTaskFnFactory } from './execute_job'; import { metadata } from './metadata'; -import { JobParamsDiscoverCsv, ScheduledTaskParamsCSV } from './types'; +import { JobParamsDiscoverCsv, TaskPayloadCSV } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsDiscoverCsv, CreateJobFn, - ScheduledTaskParamsCSV, - WorkerExecuteFn + TaskPayloadCSV, + RunTaskFn > => ({ ...metadata, jobType, jobContentExtension: 'csv', - scheduleTaskFnFactory, + createJobFnFactory: createJobFnFactory, runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts index e0d09d04a3d3a4e..01649bae2f5f4f1 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CreateJobBaseParams, ScheduledTaskParams } from '../../types'; +import { CreateJobBaseParams, TaskPayload } from '../../types'; export type RawValue = string | object | null | undefined; @@ -37,8 +37,7 @@ export interface JobParamsDiscoverCsv extends CreateJobBaseParams { conflictedTypesFields: string[]; } -export interface ScheduledTaskParamsCSV extends ScheduledTaskParams { - basePath: string; +export interface TaskPayloadCSV extends TaskPayload { searchRequest: any; fields: any; indexPatternSavedObject: any; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts index 9acfc6d8c608d1d..20bdf6fd26e67ea 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts @@ -8,10 +8,10 @@ import { notFound, notImplemented } from 'boom'; import { get } from 'lodash'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { cryptoFactory } from '../../lib'; -import { ScheduleTaskFnFactory, TimeRangeParams } from '../../types'; +import { CreateJobFnFactory, TimeRangeParams } from '../../types'; import { JobParamsPanelCsv, + JobPayloadPanelCsv, SavedObject, SavedObjectReference, SavedObjectServiceError, @@ -22,14 +22,9 @@ import { export type ImmediateCreateJobFn = ( jobParams: JobParamsPanelCsv, - headers: KibanaRequest['headers'], context: RequestHandlerContext, req: KibanaRequest -) => Promise<{ - type: string; - title: string; - jobParams: JobParamsPanelCsv; -}>; +) => Promise; interface VisData { title: string; @@ -37,19 +32,16 @@ interface VisData { panel: SearchPanel; } -export const scheduleTaskFnFactory: ScheduleTaskFnFactory = function createJobFactoryFn( +export const createJobFnFactory: CreateJobFnFactory = function createJobFactoryFn( reporting, parentLogger ) { - const config = reporting.getConfig(); - const crypto = cryptoFactory(config.get('encryptionKey')); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); - return async function scheduleTask(jobParams, headers, context, req) { + return async function createJob(jobParams, context, req) { const { savedObjectType, savedObjectId } = jobParams; - const serializedEncryptedHeaders = await crypto.encrypt(headers); - const { panel, title, visType }: VisData = await Promise.resolve() + const { panel, visType }: VisData = await Promise.resolve() .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId)) .then(async (savedObject: SavedObject) => { const { attributes, references } = savedObject; @@ -109,11 +101,6 @@ export const scheduleTaskFnFactory: ScheduleTaskFnFactory throw new Error(`Unable to create a job from saved object data! Error: ${err}`); }); - return { - headers: serializedEncryptedHeaders, - jobParams: { ...jobParams, panel, visType }, - type: visType, - title, - }; + return { ...jobParams, panel, visType }; }; }; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts index ec7e0a21f0498aa..2c9dd5b4885017b 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts @@ -7,16 +7,10 @@ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { CancellationToken } from '../../../common'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { RunTaskFnFactory, ScheduledTaskParams, TaskRunResult } from '../../types'; +import { RunTaskFnFactory, TaskRunResult } from '../../types'; import { createGenerateCsv } from '../csv/generate_csv'; -import { JobParamsPanelCsv, SearchPanel } from './types'; import { getGenerateCsvParams } from './lib/get_csv_job'; - -/* - * The run function receives the full request which provides the un-encrypted - * headers, so encrypted headers are not part of these kind of job params - */ -type ImmediateJobParams = Omit, 'headers'>; +import { JobPayloadPanelCsv } from './types'; /* * ImmediateExecuteFn receives the job doc payload because the payload was @@ -24,7 +18,7 @@ type ImmediateJobParams = Omit, 'headers' */ export type ImmediateExecuteFn = ( jobId: null, - job: ImmediateJobParams, + job: JobPayloadPanelCsv, context: RequestHandlerContext, req: KibanaRequest ) => Promise; @@ -36,21 +30,17 @@ export const runTaskFnFactory: RunTaskFnFactory = function e const config = reporting.getConfig(); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); - return async function runTask(jobId: string | null, jobPayload, context, req) { - // There will not be a jobID for "immediate" generation. - // jobID is only for "queued" jobs - // Use the jobID as a logging tag or "immediate" - const { jobParams } = jobPayload; + return async function runTask(jobId, jobPayload, context, req) { const jobLogger = logger.clone(['immediate']); const generateCsv = createGenerateCsv(jobLogger); - const { panel, visType } = jobParams as JobParamsPanelCsv & { panel: SearchPanel }; + const { panel, visType } = jobPayload; jobLogger.debug(`Execute job generating [${visType}] csv`); const savedObjectsClient = context.core.savedObjects.client; const uiConfig = await reporting.getUiSettingsServiceFactory(savedObjectsClient); - const job = await getGenerateCsvParams(jobParams, panel, savedObjectsClient, uiConfig); + const job = await getGenerateCsvParams(jobPayload, panel, savedObjectsClient, uiConfig); const elasticsearch = reporting.getElasticsearchService(); const { callAsCurrentUser } = elasticsearch.legacy.client.asScoped(req); diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts index 7467f415299fa1c..97d09249c4fba1a 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts @@ -15,7 +15,7 @@ import { import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../constants'; import { ExportTypeDefinition } from '../../types'; import { metadata } from './metadata'; -import { ImmediateCreateJobFn, scheduleTaskFnFactory } from './create_job'; +import { ImmediateCreateJobFn, createJobFnFactory } from './create_job'; import { ImmediateExecuteFn, runTaskFnFactory } from './execute_job'; import { JobParamsPanelCsv } from './types'; @@ -23,7 +23,7 @@ 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 { scheduleTaskFnFactory } from './create_job'; +export { createJobFnFactory } from './create_job'; export { runTaskFnFactory } from './execute_job'; export const getExportType = (): ExportTypeDefinition< @@ -35,7 +35,7 @@ export const getExportType = (): ExportTypeDefinition< ...metadata, jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobContentExtension: 'csv', - scheduleTaskFnFactory, + createJobFnFactory: createJobFnFactory, runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts index 9646d7eecd5b56a..acf749584c6cde2 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts @@ -13,7 +13,7 @@ describe('Get CSV Job', () => { let mockSavedObjectsClient: any; let mockUiSettingsClient: any; beforeEach(() => { - mockJobParams = { isImmediate: true, savedObjectType: 'search', savedObjectId: '234-ididid' }; + mockJobParams = { savedObjectType: 'search', savedObjectId: '234-ididid' }; mockSearchPanel = { indexPatternSavedObjectId: '123-indexId', attributes: { @@ -45,6 +45,7 @@ describe('Get CSV Job', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "browserTimezone": "PST", "conflictedTypesFields": Array [], "fields": Array [], "indexPatternSavedObject": Object { @@ -57,9 +58,6 @@ describe('Get CSV Job', () => { "timeFieldName": null, "title": null, }, - "jobParams": Object { - "browserTimezone": "PST", - }, "metaFields": Array [], "searchRequest": Object { "body": Object { @@ -99,6 +97,7 @@ describe('Get CSV Job', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "browserTimezone": "PST", "conflictedTypesFields": Array [], "fields": Array [], "indexPatternSavedObject": Object { @@ -111,9 +110,6 @@ describe('Get CSV Job', () => { "timeFieldName": null, "title": null, }, - "jobParams": Object { - "browserTimezone": "PST", - }, "metaFields": Array [], "searchRequest": Object { "body": Object { @@ -156,6 +152,7 @@ describe('Get CSV Job', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "browserTimezone": "Africa/Timbuktu", "conflictedTypesFields": Array [], "fields": Array [], "indexPatternSavedObject": Object { @@ -168,9 +165,6 @@ describe('Get CSV Job', () => { "timeFieldName": null, "title": null, }, - "jobParams": Object { - "browserTimezone": "Africa/Timbuktu", - }, "metaFields": Array [], "searchRequest": Object { "body": Object { @@ -212,6 +206,7 @@ describe('Get CSV Job', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "browserTimezone": "Africa/Timbuktu", "conflictedTypesFields": Array [], "fields": Array [ "@test_time", @@ -226,9 +221,6 @@ describe('Get CSV Job', () => { "timeFieldName": "@test_time", "title": "test search", }, - "jobParams": Object { - "browserTimezone": "Africa/Timbuktu", - }, "metaFields": Array [], "searchRequest": Object { "body": Object { @@ -286,6 +278,7 @@ describe('Get CSV Job', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "browserTimezone": "Africa/Timbuktu", "conflictedTypesFields": Array [], "fields": Array [ "@test_time", @@ -300,9 +293,6 @@ describe('Get CSV Job', () => { "timeFieldName": "@test_time", "title": "test search", }, - "jobParams": Object { - "browserTimezone": "Africa/Timbuktu", - }, "metaFields": Array [], "searchRequest": Object { "body": Object { diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts index 0fc29c5b208d9a0..c6041970ab8b628 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts @@ -12,6 +12,7 @@ import { IIndexPattern, Query, } from '../../../../../../../src/plugins/data/server'; +import { GenerateCsvParams } from '../../csv/generate_csv'; import { DocValueFields, IndexPatternField, @@ -23,7 +24,6 @@ import { } from '../types'; import { getDataSource } from './get_data_source'; import { getFilters } from './get_filters'; -import { GenerateCsvParams } from '../../csv/generate_csv'; export const getEsQueryConfig = async (config: IUiSettingsClient) => { const configs = await Promise.all([ @@ -136,7 +136,7 @@ export const getGenerateCsvParams = async ( }; return { - jobParams: { browserTimezone: timerange.timezone }, + browserTimezone: timerange.timezone, indexPatternSavedObject, searchRequest, fields: includes, diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts index 0d19a24114f06d5..221092ede4a0105 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts @@ -4,25 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobParamPostPayload, ScheduledTaskParams, TimeRangeParams } from '../../types'; +import { TimeRangeParams } from '../../types'; export interface FakeRequest { - headers: Record; -} - -export interface JobParamsPostPayloadPanelCsv extends JobParamPostPayload { - state?: any; + headers: Record; } export interface JobParamsPanelCsv { savedObjectType: string; savedObjectId: string; - isImmediate: boolean; - panel?: SearchPanel; - post?: JobParamsPostPayloadPanelCsv; + post?: { + timerange?: TimeRangeParams; + state?: any; + }; visType?: string; } +export interface JobPayloadPanelCsv extends JobParamsPanelCsv { + panel: SearchPanel; +} + export interface SavedObjectServiceError { statusCode: number; error?: string; diff --git a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts index 2252177e9808505..9fcce2ff1e395e2 100644 --- a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts @@ -5,18 +5,18 @@ */ import { cryptoFactory } from '../../../lib'; -import { CreateJobFn, ScheduleTaskFnFactory } from '../../../types'; +import { CreateJobFn, CreateJobFnFactory } from '../../../types'; import { validateUrls } from '../../common'; import { JobParamsPNG } from '../types'; -export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - return async function scheduleTask( - { objectType, title, relativeUrl, browserTimezone, layout }, + return async function createJob( + { browserTimezone, layout, objectType, relativeUrl, title }, context, req ) { @@ -25,14 +25,12 @@ export const scheduleTaskFnFactory: ScheduleTaskFnFactory ({ generatePngObservableFactory: jest.fn() })); @@ -36,7 +37,8 @@ const encryptHeaders = async (headers: Record) => { return await crypto.encrypt(headers); }; -const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsPNG; +const getScheduledTaskParams = (baseObj: any) => + ({ payload: baseObj } as ReportTaskParams); beforeEach(async () => { const kbnConfig = { diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 35cd4139df413fd..71c0769283fe9a8 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -8,7 +8,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PNG_JOB_TYPE } from '../../../../common/constants'; -import { WorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../..//types'; +import { RunTaskFnFactory, RunTaskFn, TaskRunResult } from '../../../types'; import { decryptJobHeaders, getConditionalHeaders, @@ -16,19 +16,18 @@ import { omitBlacklistedHeaders, } from '../../common'; import { generatePngObservableFactory } from '../lib/generate_png'; -import { ScheduledTaskParamsPNG } from '../types'; +import { TaskPayloadPNG } from '../types'; -type QueuedPngExecutorFactory = RunTaskFnFactory>; - -export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFactoryFn( - reporting, - parentLogger -) { +export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); - return async function runTask(jobId, job, cancellationToken) { + return async function runTask(jobId, task, cancellationToken) { + const job = task.payload; + const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting'); const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); let apmGeneratePng: { end: () => void } | null | undefined; @@ -36,11 +35,11 @@ export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFac const generatePngObservable = await generatePngObservableFactory(reporting); const jobLogger = logger.clone([jobId]); const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), - map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })), - map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, logger)), + map((decryptedHeaders) => omitBlacklistedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), mergeMap((conditionalHeaders) => { - const urls = getFullUrls({ config, job }); + const urls = getFullUrls(config, job); const hashUrl = urls[0]; if (apmGetAssets) apmGetAssets.end(); @@ -60,7 +59,6 @@ export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFac content_type: 'image/png', content: base64, size: (base64 && base64.length) || 0, - warnings, }; }), diff --git a/x-pack/plugins/reporting/server/export_types/png/index.ts b/x-pack/plugins/reporting/server/export_types/png/index.ts index c966dedb6b076d6..9ae518bcf001368 100644 --- a/x-pack/plugins/reporting/server/export_types/png/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/index.ts @@ -12,23 +12,23 @@ import { LICENSE_TYPE_TRIAL, PNG_JOB_TYPE as jobType, } from '../../../common/constants'; -import { CreateJobFn, WorkerExecuteFn, ExportTypeDefinition } from '../../types'; -import { scheduleTaskFnFactory } from './create_job'; +import { CreateJobFn, RunTaskFn, ExportTypeDefinition } from '../../types'; +import { createJobFnFactory } from './create_job'; import { runTaskFnFactory } from './execute_job'; import { metadata } from './metadata'; -import { JobParamsPNG, ScheduledTaskParamsPNG } from './types'; +import { JobParamsPNG, TaskPayloadPNG } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsPNG, CreateJobFn, - ScheduledTaskParamsPNG, - WorkerExecuteFn + TaskPayloadPNG, + RunTaskFn > => ({ ...metadata, jobType, jobContentEncoding: 'base64', jobContentExtension: 'PNG', - scheduleTaskFnFactory, + createJobFnFactory: createJobFnFactory, runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, diff --git a/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts b/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts index 5969b5b8abc0026..c3d5b2cc6005139 100644 --- a/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts +++ b/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts @@ -10,7 +10,8 @@ import { map } from 'rxjs/operators'; import { ReportingCore } from '../../../'; import { LevelLogger } from '../../../lib'; import { LayoutParams, PreserveLayout } from '../../../lib/layouts'; -import { ConditionalHeaders, ScreenshotResults } from '../../../types'; +import { ScreenshotResults } from '../../../lib/screenshots'; +import { ConditionalHeaders } from '../../../types'; export async function generatePngObservableFactory(reporting: ReportingCore) { const getScreenshots = await reporting.getScreenshotsObservable(); diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/server/export_types/png/types.d.ts index 1ddee8419df3092..0de60661137148e 100644 --- a/x-pack/plugins/reporting/server/export_types/png/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/png/types.d.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CreateJobBaseParams, ScheduledTaskParams } from '../../../server/types'; -import { LayoutInstance, LayoutParams } from '../../lib/layouts'; +import { CreateJobBaseParams, TaskPayload } from '../../../server/types'; +import { LayoutParams } from '../../lib/layouts'; // Job params: structure of incoming user request data export interface JobParamsPNG extends CreateJobBaseParams { @@ -14,9 +14,7 @@ export interface JobParamsPNG extends CreateJobBaseParams { } // Job payload: structure of stored job data provided by create_job -export interface ScheduledTaskParamsPNG extends ScheduledTaskParams { - basePath?: string; - browserTimezone: string; +export interface TaskPayloadPNG extends TaskPayload { forceNow?: string; layout: LayoutParams; relativeUrl: string; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts index 5de089a13bfa440..2f0fd9c82c0d54b 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts @@ -5,18 +5,18 @@ */ import { cryptoFactory } from '../../../lib'; -import { CreateJobFn, ScheduleTaskFnFactory } from '../../../types'; +import { CreateJobFn, CreateJobFnFactory } from '../../../types'; import { validateUrls } from '../../common'; import { JobParamsPDF } from '../types'; -export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - return async function scheduleTaskFn( - { title, relativeUrls, browserTimezone, layout, objectType }, + return async function createJob( + { browserTimezone, layout, objectType, relativeUrls, title }, context, req ) { @@ -25,14 +25,12 @@ export const scheduleTaskFnFactory: ScheduleTaskFnFactory) => { return await crypto.encrypt(headers); }; -const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsPDF; +const getScheduledTaskParams = (baseObj: any) => + ({ payload: baseObj } as ReportTaskParams); beforeEach(async () => { const kbnConfig = { diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index 5ace1c987adb5a7..9b0b7ae1bbd665c 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -8,7 +8,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PDF_JOB_TYPE } from '../../../../common/constants'; -import { WorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../types'; +import { RunTaskFn, RunTaskFnFactory, TaskRunResult } from '../../../types'; import { decryptJobHeaders, getConditionalHeaders, @@ -17,20 +17,18 @@ import { omitBlacklistedHeaders, } from '../../common'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; -import { ScheduledTaskParamsPDF } from '../types'; +import { TaskPayloadPDF } from '../types'; -type QueuedPdfExecutorFactory = RunTaskFnFactory>; - -export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn( - reporting, - parentLogger -) { +export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']); - return async function runTask(jobId, job, cancellationToken) { + return async function runTask(jobId, task, cancellationToken) { + const job = task.payload; const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting'); const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; @@ -39,14 +37,12 @@ export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFac const jobLogger = logger.clone([jobId]); const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), - map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })), - map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), - mergeMap((conditionalHeaders) => - getCustomLogo({ reporting, config, job, conditionalHeaders }) - ), + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, logger)), + map((decryptedHeaders) => omitBlacklistedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), + mergeMap((conditionalHeaders) => getCustomLogo(reporting, config, conditionalHeaders)), mergeMap(({ logo, conditionalHeaders }) => { - const urls = getFullUrls({ config, job }); + const urls = getFullUrls(config, job); const { browserTimezone, layout, title } = job; if (apmGetAssets) apmGetAssets.end(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts index 7f21d36c4b72c4b..3f58ee876512e68 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts @@ -12,23 +12,23 @@ import { LICENSE_TYPE_TRIAL, PDF_JOB_TYPE as jobType, } from '../../../common/constants'; -import { CreateJobFn, WorkerExecuteFn, ExportTypeDefinition } from '../../types'; -import { scheduleTaskFnFactory } from './create_job'; +import { CreateJobFn, RunTaskFn, ExportTypeDefinition } from '../../types'; +import { createJobFnFactory } from './create_job'; import { runTaskFnFactory } from './execute_job'; import { metadata } from './metadata'; -import { JobParamsPDF, ScheduledTaskParamsPDF } from './types'; +import { JobParamsPDF, TaskPayloadPDF } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsPDF, CreateJobFn, - ScheduledTaskParamsPDF, - WorkerExecuteFn + TaskPayloadPDF, + RunTaskFn > => ({ ...metadata, jobType, jobContentEncoding: 'base64', jobContentExtension: 'pdf', - scheduleTaskFnFactory, + createJobFnFactory: createJobFnFactory, runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts index 2e0292e1f9ab585..f3f4fa2cac61eca 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts @@ -9,8 +9,9 @@ import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; import { LevelLogger } from '../../../lib'; -import { createLayout, LayoutInstance, LayoutParams } from '../../../lib/layouts'; -import { ConditionalHeaders, ScreenshotResults } from '../../../types'; +import { createLayout, LayoutParams } from '../../../lib/layouts'; +import { ScreenshotResults } from '../../../lib/screenshots'; +import { ConditionalHeaders } from '../../../types'; // @ts-ignore untyped module import { pdf } from './pdf'; import { getTracker } from './tracker'; @@ -42,7 +43,7 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) { const tracker = getTracker(); tracker.startLayout(); - const layout = createLayout(captureConfig, layoutParams) as LayoutInstance; + const layout = createLayout(captureConfig, layoutParams); tracker.endLayout(); tracker.startScreenshots(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts index 7830f87780c2eb0..b3e930cc4b54e75 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts @@ -4,20 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CreateJobBaseParams, ScheduledTaskParams } from '../../../server/types'; -import { LayoutInstance, LayoutParams } from '../../lib/layouts'; +import { CreateJobBaseParams, TaskPayload } from '../../../server/types'; +import { LayoutParams } from '../../lib/layouts'; // Job params: structure of incoming user request data, after being parsed from RISON export interface JobParamsPDF extends CreateJobBaseParams { title: string; relativeUrls: string[]; - layout: LayoutInstance; + layout: LayoutParams; } // Job payload: structure of stored job data provided by create_job -export interface ScheduledTaskParamsPDF extends ScheduledTaskParams { - basePath?: string; - browserTimezone: string; +export interface TaskPayloadPDF extends TaskPayload { forceNow?: string; layout: LayoutParams; relativeUrls: string[]; diff --git a/x-pack/plugins/reporting/server/lib/create_queue.ts b/x-pack/plugins/reporting/server/lib/create_queue.ts index 2da3d8bd47ccb31..d4e2f5655279e9a 100644 --- a/x-pack/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/plugins/reporting/server/lib/create_queue.ts @@ -5,13 +5,14 @@ */ import { ReportingCore } from '../core'; -import { JobSource, TaskRunResult } from '../types'; +import { TaskRunResult } from '../types'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; import { createTaggedLogger } from './esqueue/create_tagged_logger'; import { LevelLogger } from './level_logger'; import { ReportingStore } from './store'; +import { ReportDocument } from './store/store'; interface ESQueueWorker { on: (event: string, handler: any) => void; @@ -32,7 +33,7 @@ export interface ESQueueInstance { // GenericWorkerFn is a generic for ImmediateExecuteFn | ESQueueWorkerExecuteFn, type GenericWorkerFn = ( - jobSource: JobSource, + jobSource: ReportDocument, ...workerRestArgs: any[] ) => void | Promise; diff --git a/x-pack/plugins/reporting/server/lib/create_worker.ts b/x-pack/plugins/reporting/server/lib/create_worker.ts index 5b0f1ddb2f1579e..72969e8e7619116 100644 --- a/x-pack/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.ts @@ -8,10 +8,12 @@ import { CancellationToken } from '../../common'; import { PLUGIN_ID } from '../../common/constants'; import { ReportingCore } from '../../server'; import { LevelLogger } from '../../server/lib'; -import { ExportTypeDefinition, JobSource, WorkerExecuteFn } from '../../server/types'; +import { ExportTypeDefinition, RunTaskFn, TaskPayload } from '../../server/types'; import { ESQueueInstance } from './create_queue'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; +import { ReportTaskParams } from './store'; +import { ReportDocument } from './store/store'; export function createWorkerFactory(reporting: ReportingCore, logger: LevelLogger) { const config = reporting.getConfig(); @@ -22,18 +24,18 @@ export function createWorkerFactory(reporting: ReportingCore, log // Once more document types are added, this will need to be passed in return async function createWorker(queue: ESQueueInstance) { // export type / execute job map - const jobExecutors: Map> = new Map(); + const jobExecutors: Map>> = new Map(); for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< - ExportTypeDefinition> + ExportTypeDefinition >) { const jobExecutor = exportType.runTaskFnFactory(reporting, logger); jobExecutors.set(exportType.jobType, jobExecutor); } - const workerFn = ( - jobSource: JobSource, - jobParams: ScheduledTaskParamsType, + const workerFn = ( + jobSource: ReportDocument, + jobParams: ReportTaskParams, cancellationToken: CancellationToken ) => { const { diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts index 3e3aef92c55a3cc..e03529677ee53af 100644 --- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -18,11 +18,21 @@ export type EnqueueJobFn = ( request: KibanaRequest ) => Promise; +/* + * Queues Reporting Jobs + */ export function enqueueJobFactory( reporting: ReportingCore, parentLogger: LevelLogger ): EnqueueJobFn { const logger = parentLogger.clone(['queue-job']); + const config = reporting.getConfig(); + const jobSettings = { + timeout: config.get('queue', 'timeout'), + browser_type: config.get('capture', 'browser', 'type'), + max_attempts: config.get('capture', 'maxAttempts'), + priority: 10, // unused + }; return async function enqueueJob( exportTypeId: string, @@ -31,7 +41,7 @@ export function enqueueJobFactory( context: RequestHandlerContext, request: KibanaRequest ) { - type ScheduleTaskFnType = CreateJobFn; + type CreateJobFnType = CreateJobFn; const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); @@ -39,16 +49,26 @@ export function enqueueJobFactory( throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const [scheduleTask, { store }] = await Promise.all([ - exportType.scheduleTaskFnFactory(reporting, logger) as ScheduleTaskFnType, + const [createJob, { store }] = await Promise.all([ + exportType.createJobFnFactory(reporting, logger) as CreateJobFnType, reporting.getPluginStartDeps(), ]); // add encrytped headers - const payload = await scheduleTask(jobParams, context, request); + const job = await createJob(jobParams, context, request); + const pendingReport = new Report({ + jobtype: exportType.jobType, + created_by: user ? user.username : false, + payload: { ...job, jobtype: exportType.jobType }, + meta: { + objectType: jobParams.objectType, + layout: jobParams.layout, + }, + ...jobSettings, + }); // store the pending report, puts it in the Reporting Management UI table - const report = await store.addReport(exportType.jobType, user, payload); + const report = await store.addReport(pendingReport); logger.info(`Scheduled ${exportType.name} report: ${report._id}`); diff --git a/x-pack/plugins/reporting/server/lib/index.ts b/x-pack/plugins/reporting/server/lib/index.ts index f3a09cffbb1047f..5fca856b5dea3c8 100644 --- a/x-pack/plugins/reporting/server/lib/index.ts +++ b/x-pack/plugins/reporting/server/lib/index.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkLicense } from './check_license'; export { createQueueFactory } from './create_queue'; -export { cryptoFactory } from './crypto'; export { enqueueJobFactory } from './enqueue_job'; +export { checkLicense } from './check_license'; +export { cryptoFactory } from './crypto'; export { ExportTypesRegistry, getExportTypesRegistry } from './export_types_registry'; export { LevelLogger } from './level_logger'; export { statuses } from './statuses'; diff --git a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts index 921d302387edf65..12936bd11ab2236 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts @@ -5,12 +5,14 @@ */ import { CaptureConfig } from '../../types'; -import { LayoutParams, LayoutTypes } from './'; -import { Layout } from './layout'; +import { LayoutInstance, LayoutParams, LayoutTypes } from './'; import { PreserveLayout } from './preserve_layout'; import { PrintLayout } from './print_layout'; -export function createLayout(captureConfig: CaptureConfig, layoutParams?: LayoutParams): Layout { +export function createLayout( + captureConfig: CaptureConfig, + layoutParams?: LayoutParams +): LayoutInstance { if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) { return new PreserveLayout(layoutParams.dimensions); } diff --git a/x-pack/plugins/reporting/server/lib/layouts/index.ts b/x-pack/plugins/reporting/server/lib/layouts/index.ts index d46f088475222ff..930ce2b6941879c 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/index.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/index.ts @@ -63,4 +63,4 @@ interface LayoutSelectors { positionElements?: (browser: HeadlessChromiumDriver, logger: LevelLogger) => Promise; } -export type LayoutInstance = Layout & LayoutSelectors & Size; +export type LayoutInstance = Layout & LayoutSelectors & Partial; diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts index 9041055ddce2dae..c9a9ee390b9a625 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts @@ -12,12 +12,13 @@ import { LayoutTypes, PageSizeParams, Size, + LayoutInstance, } from './'; // We use a zoom of two to bump up the resolution of the screenshot a bit. const ZOOM: number = 2; -export class PreserveLayout extends Layout { +export class PreserveLayout extends Layout implements LayoutInstance { public readonly selectors: LayoutSelectorDictionary = getDefaultLayoutSelectors(); public readonly groupCount = 1; public readonly height: number; diff --git a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts index b055fae8a780dc8..33f16bc7865d567 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts @@ -9,10 +9,16 @@ import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer'; import { LevelLogger } from '../'; import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig } from '../../types'; -import { getDefaultLayoutSelectors, LayoutSelectorDictionary, LayoutTypes, Size } from './'; +import { + getDefaultLayoutSelectors, + LayoutInstance, + LayoutSelectorDictionary, + LayoutTypes, + Size, +} from './'; import { Layout } from './layout'; -export class PrintLayout extends Layout { +export class PrintLayout extends Layout implements LayoutInstance { public readonly selectors: LayoutSelectorDictionary = { ...getDefaultLayoutSelectors(), screenshot: '[data-shared-item]', diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts index 4fb9fd96ecfe6d7..6c619a726f428cb 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts @@ -5,10 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { AttributesMap, ElementsPositionAndAttribute } from '../../types'; import { LevelLogger, startTrace } from '../'; +import { HeadlessChromiumDriver } from '../../browsers'; import { LayoutInstance } from '../layouts'; +import { AttributesMap, ElementsPositionAndAttribute } from './'; import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; export const getElementPositionAndAttributes = async ( diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts index 49c690e8c024d9a..6a87f91b325bad6 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; +import { LevelLogger, startTrace } from '../'; import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig } from '../../types'; -import { LevelLogger, startTrace } from '../'; import { LayoutInstance } from '../layouts'; import { CONTEXT_GETNUMBEROFITEMS, CONTEXT_READMETADATA } from './constants'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts index bc7b7005674a73d..4ea83d115c04ab0 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { ElementsPositionAndAttribute, Screenshot } from '../../types'; +import { ElementsPositionAndAttribute, Screenshot } from '.'; import { LevelLogger, startTrace } from '../'; +import { HeadlessChromiumDriver } from '../../browsers'; export const getScreenshots = async ( browser: HeadlessChromiumDriver, diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts index afd63644548352f..4b0bae5fe774cc8 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts @@ -5,9 +5,9 @@ */ import { LevelLogger, startTrace } from '../'; -import { LayoutInstance } from '../../../common/types'; import { HeadlessChromiumDriver } from '../../browsers'; import { CONTEXT_GETTIMERANGE } from './constants'; +import { LayoutInstance } from '../layouts'; export const getTimeRange = async ( browser: HeadlessChromiumDriver, diff --git a/x-pack/plugins/reporting/server/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/lib/screenshots/index.ts index 5a04f1a497abf68..c1d33cb51938433 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/index.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/index.ts @@ -4,4 +4,61 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as Rx from 'rxjs'; +import { LevelLogger } from '../'; +import { ConditionalHeaders } from '../../types'; +import { LayoutInstance } from '../layouts'; + export { screenshotsObservableFactory } from './observable'; + +export interface ScreenshotObservableOpts { + logger: LevelLogger; + urls: string[]; + conditionalHeaders: ConditionalHeaders; + layout: LayoutInstance; + browserTimezone: string; +} + +export interface AttributesMap { + [key: string]: any; +} + +export interface ElementPosition { + boundingClientRect: { + // modern browsers support x/y, but older ones don't + top: number; + left: number; + width: number; + height: number; + }; + scroll: { + x: number; + y: number; + }; +} + +export interface ElementsPositionAndAttribute { + position: ElementPosition; + attributes: AttributesMap; +} + +export interface Screenshot { + base64EncodedData: string; + title: string; + description: string; +} + +export interface ScreenshotResults { + timeRange: string | null; + screenshots: Screenshot[]; + error?: Error; + elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing +} + +export type ScreenshotsObservableFn = ({ + logger, + urls, + conditionalHeaders, + layout, + browserTimezone, +}: ScreenshotObservableOpts) => Rx.Observable; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts index b25e8fab3abcf72..3749e4372bdab08 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts @@ -16,11 +16,12 @@ jest.mock('../../browsers/chromium/puppeteer', () => ({ })); import * as Rx from 'rxjs'; +import { LevelLogger } from '../'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { HeadlessChromiumDriver } from '../../browsers'; -import { LevelLogger } from '../'; import { createMockBrowserDriverFactory, createMockLayoutInstance } from '../../test_helpers'; -import { CaptureConfig, ConditionalHeaders, ElementsPositionAndAttribute } from '../../types'; +import { CaptureConfig, ConditionalHeaders } from '../../types'; +import { ElementsPositionAndAttribute } from './'; import * as contexts from './constants'; import { screenshotsObservableFactory } from './observable'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts index c6d3d826c88fb02..342293d113d24d7 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts @@ -17,13 +17,13 @@ import { toArray, } from 'rxjs/operators'; import { HeadlessChromiumDriverFactory } from '../../browsers'; +import { CaptureConfig } from '../../types'; import { - CaptureConfig, ElementsPositionAndAttribute, ScreenshotObservableOpts, ScreenshotResults, ScreenshotsObservableFn, -} from '../../types'; +} from './'; import { checkPageIsOpen } from './check_browser_open'; import { DEFAULT_PAGELOAD_SELECTOR } from './constants'; import { getElementPositionAndAttributes } from './get_element_position_data'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts index c21ef3b91fab3fa..67e87c80b30deea 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; +import { LevelLogger, startTrace } from '../'; import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig, ConditionalHeaders } from '../../types'; -import { LevelLogger, startTrace } from '../'; export const openUrl = async ( captureConfig: CaptureConfig, diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts index f36a7b6f73664ae..33bbaa998f11e8d 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts @@ -5,9 +5,9 @@ */ import { i18n } from '@kbn/i18n'; +import { LevelLogger, startTrace } from '../'; import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig } from '../../types'; -import { LevelLogger, startTrace } from '../'; import { LayoutInstance } from '../layouts'; import { CONTEXT_WAITFORRENDER } from './constants'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts index 779d00442522d09..f74025ed9306276 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import { HeadlessChromiumDriver } from '../../browsers'; import { LevelLogger, startTrace } from '../'; +import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig } from '../../types'; import { LayoutInstance } from '../layouts'; import { CONTEXT_WAITFORELEMENTSTOBEINDOM } from './constants'; diff --git a/x-pack/plugins/reporting/server/lib/store/index.ts b/x-pack/plugins/reporting/server/lib/store/index.ts index a88d36d3fdf9ae3..6deee82d4e28921 100644 --- a/x-pack/plugins/reporting/server/lib/store/index.ts +++ b/x-pack/plugins/reporting/server/lib/store/index.ts @@ -4,5 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Report } from './report'; +export { Report, ReportTaskParams } from './report'; export { ReportingStore } from './store'; diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts index 9ac5d1f87c387fe..ef880f8384e4572 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts @@ -14,7 +14,8 @@ describe('Class Report', () => { created_by: 'created_by_test_string', browser_type: 'browser_type_test_string', max_attempts: 50, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + payload: { title: 'test' } as any, + meta: { metaTest: 'test' } as any, timeout: 30000, priority: 1, }); @@ -25,25 +26,49 @@ describe('Class Report', () => { attempts: 0, browser_type: 'browser_type_test_string', completed_at: undefined, - created_at: undefined, created_by: 'created_by_test_string', jobtype: 'test-report', max_attempts: 50, - meta: undefined, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + meta: { + layout: 'none', + objectType: undefined, + }, + payload: { + browserTimezone: undefined, + layout: undefined, + objectType: undefined, + }, priority: 1, started_at: undefined, status: 'pending', timeout: 30000, }, }); + expect(report.toReportTaskJSON()).toMatchObject({ + attempts: 0, + created_by: 'created_by_test_string', + index: '.reporting-test-index-12345', + jobtype: 'test-report', + meta: { metaTest: 'test' }, + payload: { title: 'test' }, + }); expect(report.toApiJSON()).toMatchObject({ + attempts: 0, browser_type: 'browser_type_test_string', + completed_at: undefined, created_by: 'created_by_test_string', + index: '.reporting-test-index-12345', jobtype: 'test-report', max_attempts: 50, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + meta: { + metaTest: 'test', + }, + payload: { + title: 'test', + }, priority: 1, + started_at: undefined, + status: 'pending', timeout: 30000, }); @@ -57,7 +82,14 @@ describe('Class Report', () => { created_by: 'created_by_test_string', browser_type: 'browser_type_test_string', max_attempts: 50, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + payload: { + title: 'test report', + jobtype: 'testJt', + objectType: 'testOt', + browserTimezone: 'TTT', + headers: 'headerinos', + }, + meta: { objectType: 'stange' }, timeout: 30000, priority: 1, }); @@ -70,51 +102,76 @@ describe('Class Report', () => { }; report.updateWithEsDoc(metadata); - expect(report.toEsDocsJSON()).toMatchInlineSnapshot(` - Object { - "_id": "12342p9o387549o2345", - "_index": ".reporting-test-update", - "_source": Object { - "attempts": 0, - "browser_type": "browser_type_test_string", - "completed_at": undefined, - "created_at": undefined, - "created_by": "created_by_test_string", - "jobtype": "test-report", - "max_attempts": 50, - "meta": undefined, - "payload": Object { - "headers": "payload_test_field", - "objectType": "testOt", - }, - "priority": 1, - "started_at": undefined, - "status": "pending", - "timeout": 30000, + expect(report.toEsDocsJSON()).toMatchObject({ + _id: '12342p9o387549o2345', + _index: '.reporting-test-update', + _source: { + attempts: 0, + browser_type: 'browser_type_test_string', + completed_at: undefined, + // created_at: 'Fri Aug 21 2020 19:45:48 GMT+0000', + created_by: 'created_by_test_string', + jobtype: 'test-report', + max_attempts: 50, + meta: { + layout: 'none', + objectType: 'stange', }, - } - `); - expect(report.toApiJSON()).toMatchInlineSnapshot(` - Object { - "attempts": 0, - "browser_type": "browser_type_test_string", - "completed_at": undefined, - "created_at": undefined, - "created_by": "created_by_test_string", - "id": "12342p9o387549o2345", - "index": ".reporting-test-update", - "jobtype": "test-report", - "max_attempts": 50, - "meta": undefined, - "payload": Object { - "headers": "payload_test_field", - "objectType": "testOt", + payload: { + browserTimezone: 'TTT', + layout: undefined, + objectType: 'testOt', }, - "priority": 1, - "started_at": undefined, - "status": "pending", - "timeout": 30000, - } - `); + priority: 1, + started_at: undefined, + status: 'pending', + timeout: 30000, + }, + }); + expect(report.toReportTaskJSON()).toMatchObject({ + attempts: 0, + // created_at: 'Fri Aug 21 2020 19:45:48 GMT+0000', + created_by: 'created_by_test_string', + id: '12342p9o387549o2345', + index: '.reporting-test-update', + jobtype: 'test-report', + meta: { + objectType: 'stange', + }, + payload: { + browserTimezone: 'TTT', + objectType: 'testOt', + }, + }); + expect(report.toApiJSON()).toMatchObject({ + attempts: 0, + browser_type: 'browser_type_test_string', + completed_at: undefined, + // created_at: 'Fri Aug 21 2020 19:45:48 GMT+0000', + created_by: 'created_by_test_string', + id: '12342p9o387549o2345', + index: '.reporting-test-update', + jobtype: 'test-report', + max_attempts: 50, + meta: { + objectType: 'stange', + }, + payload: { + browserTimezone: 'TTT', + headers: 'headerinos', + objectType: 'testOt', + }, + priority: 1, + started_at: undefined, + status: 'pending', + timeout: 30000, + }); + }); + + it('throws error if converted to task JSON before being synced with ES storage', () => { + const report = new Report({} as any); + expect(() => report.updateWithEsDoc(report)).toThrowErrorMatchingInlineSnapshot( + `"Report object from ES has missing fields!"` + ); }); }); diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts index 5c9b9ced7cce75c..c675a1689d6e39c 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.ts @@ -4,84 +4,110 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; // @ts-ignore no module definition import Puid from 'puid'; import { JobStatuses } from '../../../constants'; -import { LayoutInstance } from '../layouts'; +import { TaskPayload, TaskRunResult } from '../../types'; +import { LayoutParams } from '../layouts'; -/* - * The document created by Reporting to store in the .reporting index - */ -interface ReportingDocument { - _id: string; - _index: string; - _seq_no: unknown; - _primary_term: unknown; +export interface ReportSource { jobtype: string; created_by: string | false; payload: { - headers: string; // encrypted headers + browserTimezone: string; + headers: string; + jobtype: string; + layout?: LayoutParams; + objectType: string; + title: string; + }; + meta: { objectType: string; - layout?: LayoutInstance; + layout?: LayoutParams; }; - meta: unknown; browser_type: string; max_attempts: number; timeout: number; status: string; attempts: number; - output?: unknown; + created_at: string; + output: TaskRunResult | null; started_at?: string; completed_at?: string; - created_at?: string; priority?: number; process_expiration?: string; } +/* + * An abstraction of the document stored in the .reporting index + * Without the abstraction, the body of the document would be under _source + * The abstraction provides a shorter path to access the body's fields + */ +interface AbstractReport extends ReportSource { + _id: string; + _index: string; + _seq_no: unknown; + _primary_term: unknown; +} + +/* + * The document created by Reporting to store as task parameters for Task + * Manager to reference the report in .reporting + */ +export interface ReportTaskParams { + id: string; + index?: string; // For ad-hoc, which as an existing "pending" record + payload: JobPayloadType; + created_at: AbstractReport['created_at']; + created_by: AbstractReport['created_by']; + jobtype: AbstractReport['jobtype']; + attempts: AbstractReport['attempts']; + meta: AbstractReport['meta']; +} + /* * The document created by Reporting to store as task parameters for Task * Manager to reference the report in .reporting */ const puid = new Puid(); -export class Report implements Partial { +export class Report implements Partial { public _index?: string; public _id: string; public _primary_term?: unknown; // set by ES public _seq_no: unknown; // set by ES - public readonly jobtype: string; - public readonly created_at?: string; - public readonly created_by?: string | false; - public readonly payload: { - headers: string; // encrypted headers - objectType: string; - layout?: LayoutInstance; - }; - public readonly meta: unknown; - public readonly max_attempts: number; - public readonly browser_type?: string; - - public readonly status: string; - public readonly attempts: number; - public readonly output?: unknown; - public readonly started_at?: string; - public readonly completed_at?: string; - public readonly process_expiration?: string; - public readonly priority?: number; - public readonly timeout?: number; + public readonly jobtype: AbstractReport['jobtype']; + public readonly created_at: AbstractReport['created_at']; + public readonly created_by: AbstractReport['created_by']; + public readonly payload: AbstractReport['payload']; + public readonly meta: AbstractReport['meta']; + public readonly max_attempts: AbstractReport['max_attempts']; + public readonly browser_type?: AbstractReport['browser_type']; + + public readonly status: AbstractReport['status']; + public readonly attempts: AbstractReport['attempts']; + public readonly output?: AbstractReport['output']; + public readonly started_at?: AbstractReport['started_at']; + public readonly completed_at?: AbstractReport['completed_at']; + public readonly process_expiration?: AbstractReport['process_expiration']; + public readonly priority?: AbstractReport['priority']; + public readonly timeout?: AbstractReport['timeout']; /* * Create an unsaved report + * Index string is required */ - constructor(opts: Partial) { + constructor(opts: Partial) { this._id = opts._id != null ? opts._id : puid.generate(); this._index = opts._index; this._primary_term = opts._primary_term; this._seq_no = opts._seq_no; this.payload = opts.payload!; + this.meta = opts.meta!; this.jobtype = opts.jobtype!; this.max_attempts = opts.max_attempts!; this.attempts = opts.attempts || 0; @@ -89,9 +115,8 @@ export class Report implements Partial { this.process_expiration = opts.process_expiration; this.timeout = opts.timeout; - this.created_at = opts.created_at; - this.created_by = opts.created_by; - this.meta = opts.meta; + this.created_at = opts.created_at || moment.utc().toISOString(); + this.created_by = opts.created_by || false; this.browser_type = opts.browser_type; this.priority = opts.priority; @@ -115,8 +140,14 @@ export class Report implements Partial { /* * Data structure for writing to Elasticsearch index + * + * Unlike in 7.x, this explicitly does NOT provide the encrypted header string in the body + * payload. Task Manager params include them in order to make recurring tasks. */ toEsDocsJSON() { + const payload = this.payload || {}; + const meta = this.meta || {}; + return { _id: this._id, _index: this._index, @@ -124,8 +155,17 @@ export class Report implements Partial { jobtype: this.jobtype, created_at: this.created_at, created_by: this.created_by, - payload: this.payload, - meta: this.meta, + payload: { + title: payload.title, + objectType: payload.objectType, + browserTimezone: payload.browserTimezone, + layout: payload.layout, + }, + meta: { + // aggregatable stats + objectType: meta.objectType, + layout: meta.layout ? meta.layout.id : 'none', + }, timeout: this.timeout, max_attempts: this.max_attempts, priority: this.priority, @@ -138,6 +178,22 @@ export class Report implements Partial { }; } + /* + * Parameters to save in a task instance + */ + toReportTaskJSON(): ReportTaskParams { + return { + id: this._id, + index: this._index, + jobtype: this.jobtype, + created_at: this.created_at, + created_by: this.created_by, + payload: this.payload, + meta: this.meta, + attempts: this.attempts, + }; + } + /* * Data structure for API responses */ diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts index e6c4eb73464609a..83925224db4ff8d 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts @@ -10,7 +10,7 @@ import { ReportingConfig, ReportingCore } from '../..'; import { createMockReportingCore } from '../../test_helpers'; import { createMockLevelLogger } from '../../test_helpers/create_mock_levellogger'; import { Report } from './report'; -import { ReportingStore } from './store'; +import { ReportDocument, ReportingStore } from './store'; const getMockConfig = (mockConfigGet: sinon.SinonStub) => ({ get: mockConfigGet, @@ -27,6 +27,7 @@ describe('ReportingStore', () => { beforeEach(async () => { const mockConfigGet = sinon.stub(); + mockConfigGet.withArgs('encryptionKey').returns('aaaaaaa'); mockConfigGet.withArgs('index').returns('.reporting-test'); mockConfigGet.withArgs('queue', 'indexInterval').returns('week'); mockConfig = getMockConfig(mockConfigGet); @@ -47,15 +48,16 @@ describe('ReportingStore', () => { describe('addReport', () => { it('returns Report object', async () => { const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_1', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'username1' }, reportPayload) - ).resolves.toMatchObject({ + const mockReport = new Report({ + _index: '.reporting-mock', + attempts: 0, + created_by: 'username1', + jobtype: 'unknowntype', + status: 'pending', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, attempts: 0, @@ -65,7 +67,7 @@ describe('ReportingStore', () => { jobtype: 'unknowntype', max_attempts: undefined, payload: {}, - priority: 10, + meta: {}, started_at: undefined, status: 'pending', timeout: undefined, @@ -74,21 +76,22 @@ describe('ReportingStore', () => { it('throws if options has invalid indexInterval', async () => { const mockConfigGet = sinon.stub(); + mockConfigGet.withArgs('encryptionKey').returns('bbbbbbbbbbb'); mockConfigGet.withArgs('index').returns('.reporting-test'); mockConfigGet.withArgs('queue', 'indexInterval').returns('centurially'); mockConfig = getMockConfig(mockConfigGet); mockCore = await createMockReportingCore(mockConfig); const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_2', - objectType: 'testOt', - }; - expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).rejects.toMatchInlineSnapshot(`[Error: Invalid index interval: centurially]`); + const mockReport = new Report({ + _index: '.reporting-errortest', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + `[TypeError: this.client.callAsInternalUser is not a function]` + ); }); it('handles error creating the index', async () => { @@ -97,15 +100,15 @@ describe('ReportingStore', () => { callClusterStub.withArgs('indices.create').rejects(new Error('horrible error')); const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_3', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).rejects.toMatchInlineSnapshot(`[Error: horrible error]`); + const mockReport = new Report({ + _index: '.reporting-errortest', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + `[Error: horrible error]` + ); }); /* Creating the index will fail, if there were multiple jobs staged in @@ -120,15 +123,15 @@ describe('ReportingStore', () => { callClusterStub.withArgs('indices.create').rejects(new Error('devastating error')); const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_4', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).rejects.toMatchInlineSnapshot(`[Error: devastating error]`); + const mockReport = new Report({ + _index: '.reporting-mock', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + `[Error: devastating error]` + ); }); it('skips creating the index if already exists', async () => { @@ -139,15 +142,13 @@ describe('ReportingStore', () => { .rejects(new Error('resource_already_exists_exception')); // will be triggered but ignored const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_5', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).resolves.toMatchObject({ + const mockReport = new Report({ + created_by: 'user1', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, attempts: 0, @@ -157,7 +158,6 @@ describe('ReportingStore', () => { jobtype: 'unknowntype', max_attempts: undefined, payload: {}, - priority: 10, started_at: undefined, status: 'pending', timeout: undefined, @@ -172,13 +172,21 @@ describe('ReportingStore', () => { .rejects(new Error('resource_already_exists_exception')); // will be triggered but ignored const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_test_headers', - objectType: 'testOt', - }; - await expect(store.addReport(reportType, false, reportPayload)).resolves.toMatchObject({ + const mockReport = new Report({ + _index: '.reporting-unsecured', + attempts: 0, + browser_type: undefined, + completed_at: undefined, + created_by: false, + jobtype: 'unknowntype', + payload: {}, + meta: {}, + max_attempts: undefined, + started_at: undefined, + status: 'pending', + timeout: undefined, + } as any); + await expect(store.addReport(mockReport)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, attempts: 0, @@ -187,8 +195,8 @@ describe('ReportingStore', () => { created_by: false, jobtype: 'unknowntype', max_attempts: undefined, + meta: {}, payload: {}, - priority: 10, started_at: undefined, status: 'pending', timeout: undefined, @@ -196,6 +204,60 @@ describe('ReportingStore', () => { }); }); + it('findReport gets a report from ES and returns a Report object', async () => { + // setup + const mockReport: ReportDocument = { + _id: '1234-foo-78', + _index: '.reporting-test-17409', + _primary_term: 'primary_term string', + _seq_no: 'seq_no string', + _source: { + created_at: 'some time', + created_by: 'some security person', + jobtype: 'csv', + status: 'pending', + meta: { testMeta: 'meta' } as any, + payload: { testPayload: 'payload' } as any, + browser_type: 'browser type string', + attempts: 0, + max_attempts: 1, + timeout: 30000, + priority: 12, + output: null, + }, + }; + callClusterStub.withArgs('get').resolves(mockReport); + const store = new ReportingStore(mockCore, mockLogger); + + expect(await store.findReport({} as any)).toMatchInlineSnapshot(` + Report { + "_id": "1234-foo-78", + "_index": ".reporting-test-17409", + "_primary_term": "primary_term string", + "_seq_no": "seq_no string", + "attempts": 0, + "browser_type": "browser type string", + "completed_at": undefined, + "created_at": "some time", + "created_by": "some security person", + "jobtype": "csv", + "max_attempts": 1, + "meta": Object { + "testMeta": "meta", + }, + "output": null, + "payload": Object { + "testPayload": "payload", + }, + "priority": 12, + "process_expiration": undefined, + "started_at": undefined, + "status": "pending", + "timeout": 30000, + } + `); + }); + it('setReportClaimed sets the status of a record to processing', async () => { const store = new ReportingStore(mockCore, mockLogger); const report = new Report({ @@ -206,11 +268,13 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', + jobtype: 'testJ', objectType: 'testOt', + browserTimezone: 'ABC', }, timeout: 30000, - priority: 1, }); await store.setReportClaimed(report, { testDoc: 'test' } as any); @@ -245,11 +309,13 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', + jobtype: 'testJ', objectType: 'testOt', + browserTimezone: 'BCD', }, timeout: 30000, - priority: 1, }); await store.setReportFailed(report, { errors: 'yes' } as any); @@ -284,11 +350,13 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', + jobtype: 'testJ', objectType: 'testOt', + browserTimezone: 'CDE', }, timeout: 30000, - priority: 1, }); await store.setReportCompleted(report, { certainly_completed: 'yes' } as any); @@ -323,11 +391,13 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', + jobtype: 'testJ', objectType: 'testOt', + browserTimezone: 'utc', }, timeout: 30000, - priority: 1, }); await store.setReportCompleted(report, { diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 88f6fa418a1b353..28d8729471bdd2d 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -4,22 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchParams } from 'elasticsearch'; import { ElasticsearchServiceSetup } from 'src/core/server'; import { LevelLogger, statuses } from '../'; import { ReportingCore } from '../../'; -import { - CreateJobBaseParams, - CreateJobBaseParamsEncryptedFields, - ReportingUser, -} from '../../types'; import { indexTimestamp } from './index_timestamp'; import { mapping } from './mapping'; -import { Report } from './report'; -interface JobSettings { - timeout: number; - browser_type: string; - max_attempts: number; - priority: number; +import { Report, ReportSource, ReportTaskParams } from './report'; + +/* + * The fields for a reporting record that come back from search + */ +export interface ReportDocument { + _id: string; + _index: string; + _primary_term: unknown; // catch race conditions over read & update + _seq_no: unknown; // catch race conditions over read & update + _source: ReportSource; +} + +/* + * When searching for zombie reports, we get a subset of fields + */ +export interface ReportRecordTimeout { + _id: string; + _index: string; + _source: { + status: string; + process_expiration?: string; + created_at?: string; + }; } const checkReportIsEditable = (report: Report) => { @@ -37,7 +51,6 @@ const checkReportIsEditable = (report: Report) => { export class ReportingStore { private readonly indexPrefix: string; private readonly indexInterval: string; - private readonly jobSettings: JobSettings; private client: ElasticsearchServiceSetup['legacy']['client']; private logger: LevelLogger; @@ -48,13 +61,6 @@ export class ReportingStore { this.client = elasticsearch.legacy.client; this.indexPrefix = config.get('index'); this.indexInterval = config.get('queue', 'indexInterval'); - this.jobSettings = { - timeout: config.get('queue', 'timeout'), - browser_type: config.get('capture', 'browser', 'type'), - max_attempts: config.get('capture', 'maxAttempts'), - priority: 10, // unused - }; - this.logger = logger; } @@ -103,36 +109,17 @@ export class ReportingStore { * Called from addReport, which handles any errors */ private async indexReport(report: Report) { - const params = report.payload; - - // Queing is handled by TM. These queueing-based fields for reference in Report Info panel - const infoFields = { - timeout: report.timeout, - process_expiration: new Date(0), // use epoch so the job query works - created_at: new Date(), - attempts: 0, - max_attempts: report.max_attempts, - status: statuses.JOB_STATUS_PENDING, - browser_type: report.browser_type, - }; - - const indexParams = { + const doc = { index: report._index, id: report._id, body: { - ...infoFields, - jobtype: report.jobtype, - meta: { - // We are copying these values out of payload because these fields are indexed and can be aggregated on - // for tracking stats, while payload contents are not. - objectType: params.objectType, - layout: params.layout ? params.layout.id : 'none', - }, - payload: report.payload, - created_by: report.created_by, + ...report.toEsDocsJSON()._source, + process_expiration: new Date(0), // use epoch so the job query works + attempts: 0, + status: statuses.JOB_STATUS_PENDING, }, }; - return await this.client.callAsInternalUser('index', indexParams); + return await this.client.callAsInternalUser('index', doc); } /* @@ -142,23 +129,15 @@ export class ReportingStore { return await this.client.callAsInternalUser('indices.refresh', { index }); } - public async addReport( - type: string, - user: ReportingUser, - payload: CreateJobBaseParams & CreateJobBaseParamsEncryptedFields - ): Promise { - const timestamp = indexTimestamp(this.indexInterval); - const index = `${this.indexPrefix}-${timestamp}`; + public async addReport(report: Report): Promise { + let index = report._index; + if (!index) { + const timestamp = indexTimestamp(this.indexInterval); + index = `${this.indexPrefix}-${timestamp}`; + report._index = index; + } await this.createIndex(index); - const report = new Report({ - _index: index, - payload, - jobtype: type, - created_by: user ? user.username : false, - ...this.jobSettings, - }); - try { const doc = await this.indexReport(report); report.updateWithEsDoc(doc); @@ -168,7 +147,42 @@ export class ReportingStore { return report; } catch (err) { - this.logger.error(`Error in addReport!`); + this.logger.error(`Error in adding a report!`); + this.logger.error(err); + throw err; + } + } + + /* + * Search for a report from task data and return back the report + */ + public async findReport(taskJson: ReportTaskParams): Promise { + try { + const document = await this.client.callAsInternalUser('get', { + index: taskJson.index, + id: taskJson.id, + }); + + return new Report({ + _id: document._id, + _index: document._index, + _seq_no: document._seq_no, + _primary_term: document._primary_term, + jobtype: document._source.jobtype, + attempts: document._source.attempts, + browser_type: document._source.browser_type, + created_at: document._source.created_at, + created_by: document._source.created_by, + max_attempts: document._source.max_attempts, + meta: document._source.meta, + payload: document._source.payload, + process_expiration: document._source.process_expiration, + status: document._source.status, + timeout: document._source.timeout, + priority: document._source.priority, + }); + } catch (err) { + this.logger.error('Error in finding a report! ' + JSON.stringify({ report: taskJson })); this.logger.error(err); throw err; } @@ -222,7 +236,7 @@ export class ReportingStore { public async setReportCompleted(report: Report, stats: Partial): Promise { try { - const { output } = stats as { output: any }; + const { output } = stats; const status = output && output.warnings && output.warnings.length > 0 ? statuses.JOB_STATUS_WARNINGS @@ -246,4 +260,40 @@ export class ReportingStore { throw err; } } + + /* + * FIXME how to test this? + */ + public async findTimeoutReports(): Promise { + const searchParams: SearchParams = { + index: '.reporting-*', // FIXME: use config + filterPath: [ + 'hits.hits._id', + 'hits.hits._index', + 'hits.hits._source.created_at', + 'hits.hits._source.process_expiration', + 'hits.hits._source.status', + ].join(','), + body: { + query: { + bool: { + filter: [ + { + bool: { + must: [{ range: { process_expiration: { lte: 'now' } } }], + must_not: [{ term: { status: 'completed' } }, { term: { status: 'failed' } }], + }, + }, + ], + }, + }, + }, + }; + + const result = await this.client.callAsInternalUser( + 'search', + searchParams + ); + return result.hits?.hits; + } } diff --git a/x-pack/plugins/reporting/server/plugin.test.ts b/x-pack/plugins/reporting/server/plugin.test.ts index 420fa8347cdebb9..0bc4ae7ea62bb8d 100644 --- a/x-pack/plugins/reporting/server/plugin.test.ts +++ b/x-pack/plugins/reporting/server/plugin.test.ts @@ -15,8 +15,13 @@ jest.mock('./browsers/install', () => ({ })); import { coreMock } from 'src/core/server/mocks'; +import { DataPluginStart } from 'src/plugins/data/server/plugin'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { SecurityPluginSetup } from '../../security/server'; import { ReportingPlugin } from './plugin'; import { createMockConfigSchema } from './test_helpers'; +import { ReportingSetupDeps, ReportingStartDeps } from './types'; const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); @@ -25,21 +30,22 @@ describe('Reporting Plugin', () => { let initContext: any; let coreSetup: any; let coreStart: any; - let pluginSetup: any; - let pluginStart: any; + let pluginSetup: ReportingSetupDeps; + let pluginStart: ReportingStartDeps; beforeEach(async () => { configSchema = createMockConfigSchema(); initContext = coreMock.createPluginInitializerContext(configSchema); - coreSetup = await coreMock.createSetup(configSchema); - coreStart = await coreMock.createStart(); - pluginSetup = ({ - licensing: {}, - usageCollection: { + coreSetup = coreMock.createSetup(configSchema); + coreStart = coreMock.createStart(); + + pluginSetup = { + licensing: {} as LicensingPluginSetup, + usageCollection: ({ makeUsageCollector: jest.fn(), registerCollector: jest.fn(), - }, - security: { + } as unknown) as UsageCollectionSetup, + security: ({ authc: { getCurrentUser: () => ({ id: '123', @@ -47,13 +53,12 @@ describe('Reporting Plugin', () => { username: 'Tom Riddle', }), }, - }, - } as unknown) as any; - pluginStart = ({ - data: { - fieldFormats: {}, - }, - } as unknown) as any; + } as unknown) as SecurityPluginSetup, + }; + + pluginStart = { + data: { fieldFormats: {} } as DataPluginStart, + } as ReportingStartDeps; }); it('has a sync setup process', () => { diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 20e22c2db00e353..40dbef16a06e78d 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -8,7 +8,7 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core import { ReportingCore } from './'; import { initializeBrowserDriverFactory } from './browsers'; import { buildConfig, ReportingConfigType } from './config'; -import { createQueueFactory, LevelLogger, runValidations, ReportingStore } from './lib'; +import { createQueueFactory, LevelLogger, ReportingStore, runValidations } from './lib'; import { registerRoutes } from './routes'; import { setFieldFormats } from './services'; import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types'; @@ -89,7 +89,7 @@ export class ReportingPlugin const store = new ReportingStore(reportingCore, logger); const esqueue = await createQueueFactory(reportingCore, store, logger); // starts polling for pending jobs - reportingCore.pluginStart({ + await reportingCore.pluginStart({ browserDriverFactory, savedObjects: core.savedObjects, uiSettings: core.uiSettings, diff --git a/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts index f4959b56dfea1e7..1432299221d6ab4 100644 --- a/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -67,16 +67,10 @@ export function registerGenerateFromJobParams( } const { exportType } = req.params; - let jobParams; + let jobParams: CreateJobBaseParams | null; try { jobParams = rison.decode(jobParamsRison) as CreateJobBaseParams | null; - if (!jobParams) { - return res.customError({ - statusCode: 400, - body: 'Missing jobParams!', - }); - } } catch (err) { return res.customError({ statusCode: 400, @@ -84,6 +78,13 @@ export function registerGenerateFromJobParams( }); } + if (!jobParams) { + return res.customError({ + statusCode: 400, + body: 'Missing jobParams!', + }); + } + try { return await handler(user, exportType, jobParams, context, req, res); } catch (err) { diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index a0a8f25de7fc493..b2825f2e17551a5 100644 --- a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -8,11 +8,10 @@ import { schema } from '@kbn/config-schema'; import { KibanaRequest } from 'src/core/server'; import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; -import { scheduleTaskFnFactory } from '../export_types/csv_from_savedobject/create_job'; +import { createJobFnFactory } from '../export_types/csv_from_savedobject/create_job'; import { runTaskFnFactory } from '../export_types/csv_from_savedobject/execute_job'; -import { JobParamsPostPayloadPanelCsv } from '../export_types/csv_from_savedobject/types'; import { LevelLogger as Logger } from '../lib'; -import { TaskRunResult } from '../types'; +import { TaskRunResult, TimeRangeParams } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { getJobParamsFromRequest } from './lib/get_job_params_from_request'; import { HandlerErrorFunction } from './types'; @@ -20,7 +19,7 @@ import { HandlerErrorFunction } from './types'; export type CsvFromSavedObjectRequest = KibanaRequest< { savedObjectType: string; savedObjectId: string }, unknown, - JobParamsPostPayloadPanelCsv + { timerange: TimeRangeParams; state: any } >; /* @@ -43,7 +42,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: - * - re-use the scheduleTask function to build up es query config + * - re-use the createJob function to build up es query config * - re-use the runTask function to run the scan and scroll queries and capture the entire CSV in a result object. */ router.post( @@ -66,27 +65,21 @@ export function registerGenerateCsvFromSavedObjectImmediate( }, userHandler(async (user, context, req: CsvFromSavedObjectRequest, res) => { const logger = parentLogger.clone(['savedobject-csv']); - const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); - const scheduleTaskFn = scheduleTaskFnFactory(reporting, logger); + const jobParams = getJobParamsFromRequest(req); + const createJob = createJobFnFactory(reporting, logger); const runTaskFn = runTaskFnFactory(reporting, logger); try { - // FIXME: no scheduleTaskFn for immediate download - const jobDocPayload = await scheduleTaskFn(jobParams, req.headers, context, req); + const payload = await createJob(jobParams, context, req); const { content_type: jobOutputContentType, content: jobOutputContent, size: jobOutputSize, - }: TaskRunResult = await runTaskFn(null, jobDocPayload, context, req); + }: TaskRunResult = await runTaskFn(null, payload, context, req); logger.info(`Job output size: ${jobOutputSize} bytes`); - /* - * ESQueue worker function defaults `content` to null, even if the - * runTask returned undefined. - * - * This converts null to undefined so the value can be sent to h.response() - */ + // convert null to undefined so the value can be sent to h.response() if (jobOutputContent === null) { logger.warn('CSV Job Execution created empty content result'); } diff --git a/x-pack/plugins/reporting/server/routes/generation.test.ts b/x-pack/plugins/reporting/server/routes/generation.test.ts index cef4da9aabbd474..bc99476ac7a9f50 100644 --- a/x-pack/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/plugins/reporting/server/routes/generation.test.ts @@ -29,6 +29,8 @@ describe('POST /api/reporting/generate', () => { get: jest.fn().mockImplementation((...args) => { const key = args.join('.'); switch (key) { + case 'encryptionKey': + return 'lalalalallalaaalaalalalaa'; case 'queue.indexInterval': return 'year'; case 'queue.timeout': @@ -75,7 +77,7 @@ describe('POST /api/reporting/generate', () => { jobContentEncoding: 'base64', jobContentExtension: 'pdf', validLicenses: ['basic', 'gold'], - scheduleTaskFnFactory: () => () => ({ scheduleParamsTest: { test1: 'yes' } }), + createJobFnFactory: () => () => ({ scheduleParamsTest: { test1: 'yes' } }), runTaskFnFactory: () => () => ({ runParamsTest: { test2: 'yes' } }), }); core.getExportTypesRegistry = () => mockExportTypesRegistry; @@ -164,9 +166,21 @@ describe('POST /api/reporting/generate', () => { .then(({ body }) => { expect(body).toMatchObject({ job: { - id: expect.any(String), + attempts: 0, + created_by: 'Tom Riddle', + id: 'foo', + index: 'foo-index', + jobtype: 'printable_pdf', + payload: { + scheduleParamsTest: { + test1: 'yes', + }, + }, + priority: 10, + status: 'pending', + timeout: 10000, }, - path: expect.any(String), + path: 'undefined/api/reporting/jobs/download/foo', }); }); }); diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index 005d82086665c7a..12be9466f1fcd43 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -13,3 +13,10 @@ export function registerRoutes(reporting: ReportingCore, logger: Logger) { registerJobGenerationRoutes(reporting, logger); registerJobInfoRoutes(reporting); } + +export interface ReportingRequestPre { + management: { + jobTypes: string[]; + }; + user: string; +} diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.ts b/x-pack/plugins/reporting/server/routes/jobs.test.ts index 2957bc76f468268..d201be031125709 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.test.ts @@ -25,7 +25,10 @@ describe('GET /api/reporting/jobs/download', () => { let exportTypesRegistry: ExportTypesRegistry; let core: ReportingCore; - const config = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; + const config = { + get: jest.fn().mockImplementation(() => 'testConfigValue'), + kbnConfig: { get: jest.fn().mockImplementation(() => 'testConfigValue') }, + }; const mockLogger = ({ error: jest.fn(), debug: jest.fn(), diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index db62c0cc403fc05..06175f6b859ad23 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -92,6 +92,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { ); // return the raw output from a job + // TODO: for what? router.get( { path: `${MAIN_ENTRY}/output/{docId}`, @@ -123,12 +124,16 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { _source: { jobtype: jobType, output: jobOutput }, } = result; + if (!jobOutput) { + // return 503 + } + if (!jobTypes.includes(jobType)) { throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); } return res.ok({ - body: jobOutput, + body: jobOutput || '', headers: { 'content-type': 'application/json', }, diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index 84a98d6d1f1d7a6..90b5dbb767b4a3b 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -9,7 +9,8 @@ import contentDisposition from 'content-disposition'; import { get } from 'lodash'; import { CSV_JOB_TYPE } from '../../../common/constants'; import { ExportTypesRegistry, statuses } from '../../lib'; -import { ExportTypeDefinition, JobSource, TaskRunResult } from '../../types'; +import { ExportTypeDefinition, TaskRunResult } from '../../types'; +import { ReportDocument } from '../../lib/store/store'; type ExportTypeType = ExportTypeDefinition; @@ -92,16 +93,18 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist }; } - return function getDocumentPayload(doc: JobSource): Payload { + return function getDocumentPayload(doc: ReportDocument): Payload { const { status, jobtype: jobType, payload: { title } = { title: '' } } = doc._source; const { output } = doc._source; - if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { - return getCompleted(output, jobType, title); - } + if (output) { + if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { + return getCompleted(output, jobType, title); + } - if (status === statuses.JOB_STATUS_FAILED) { - return getFailure(output); + if (status === statuses.JOB_STATUS_FAILED) { + return getFailure(output); + } } // send a 503 indicating that the report isn't completed yet diff --git a/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts b/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts index bfa15a4022a4d6c..e685339c966ed0d 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts @@ -7,17 +7,13 @@ import { JobParamsPanelCsv } from '../../export_types/csv_from_savedobject/types'; import { CsvFromSavedObjectRequest } from '../generate_from_savedobject_immediate'; -export function getJobParamsFromRequest( - request: CsvFromSavedObjectRequest, - opts: { isImmediate: boolean } -): JobParamsPanelCsv { +export function getJobParamsFromRequest(request: CsvFromSavedObjectRequest): JobParamsPanelCsv { const { savedObjectType, savedObjectId } = request.params; const { timerange, state } = request.body; const post = timerange || state ? { timerange, state } : undefined; return { - isImmediate: opts.isImmediate, savedObjectType, savedObjectId, post, diff --git a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts index b01c880abe82051..0e63d244858526e 100644 --- a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts @@ -8,7 +8,8 @@ import { i18n } from '@kbn/i18n'; import { errors as elasticsearchErrors } from 'elasticsearch'; import { get } from 'lodash'; import { ReportingCore } from '../../'; -import { JobSource, ReportingUser } from '../../types'; +import { ReportDocument } from '../../lib/store/store'; +import { ReportingUser } from '../../types'; const esErrors = elasticsearchErrors as Record; const defaultSize = 10; @@ -130,7 +131,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore) { }); }, - get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise | void> { + get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise { if (!id) return Promise.resolve(); const username = getUsername(user); diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index ab1dd841bbbc22f..e3b2a407a7d1d7c 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -5,7 +5,7 @@ */ import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import { CreateJobBaseParams, ReportingUser, ScheduledTaskParams } from '../types'; +import { CreateJobBaseParams, ReportingUser, TaskPayload } from '../types'; export type HandlerFunction = ( user: ReportingUser, @@ -22,7 +22,7 @@ export interface QueuedJobPayload { error?: boolean; source: { job: { - payload: ScheduledTaskParams; + payload: TaskPayload; }; }; } diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts index 08313e6892f3c42..f2785bce10964a8 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts @@ -8,8 +8,9 @@ import { Page } from 'puppeteer'; import * as Rx from 'rxjs'; import { chromium, HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers'; import { LevelLogger } from '../lib'; +import { ElementsPositionAndAttribute } from '../lib/screenshots'; import * as contexts from '../lib/screenshots/constants'; -import { CaptureConfig, ElementsPositionAndAttribute } from '../types'; +import { CaptureConfig } from '../types'; interface CreateMockBrowserDriverFactoryOpts { evaluate: jest.Mock, any[]>; diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index c508ee6974ca004..ff9e404c2e99f67 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -19,7 +19,6 @@ import { } from '../browsers'; import { ReportingInternalSetup, ReportingInternalStart } from '../core'; import { ReportingStore } from '../lib'; -import { ReportingStartDeps } from '../types'; import { createMockLevelLogger } from './create_mock_levellogger'; (initializeBrowserDriverFactory as jest.Mock< @@ -28,31 +27,31 @@ import { createMockLevelLogger } from './create_mock_levellogger'; (chromium as any).createDriverFactory.mockImplementation(() => ({})); +const logger = createMockLevelLogger(); + const createMockPluginSetup = ( - mockReportingCore: ReportingCore, - setupMock?: any + setupMock: Partial ): ReportingInternalSetup => { return { - elasticsearch: setupMock.elasticsearch || { legacy: { client: {} } }, - basePath: setupMock.basePath || '/all-about-that-basepath', - router: setupMock.router, - security: setupMock.security, + elasticsearch: { legacy: { client: {} } } as any, licensing: { license$: Rx.of({ isAvailable: true, isActive: true, type: 'basic' }) } as any, + basePath: () => '/all-about-that-basePath', + router: {} as any, + ...setupMock, }; }; const createMockPluginStart = ( - mockReportingCore: ReportingCore, - startMock?: any + startMock: Partial, + store: ReportingStore ): ReportingInternalStart => { - const logger = createMockLevelLogger(); - const store = new ReportingStore(mockReportingCore, logger); return { - browserDriverFactory: startMock.browserDriverFactory, - esqueue: startMock.esqueue, - savedObjects: startMock.savedObjects || { getScopedClient: jest.fn() }, - uiSettings: startMock.uiSettings || { asScopedToClient: () => ({ get: jest.fn() }) }, + esqueue: startMock.esqueue as any, + savedObjects: { getScopedClient: jest.fn() } as any, + uiSettings: { asScopedToClient: () => ({ get: jest.fn() }) } as any, store, + browserDriverFactory: startMock.browserDriverFactory!, + ...startMock, }; }; @@ -63,34 +62,23 @@ export const createMockConfigSchema = (overrides?: any) => ({ ...overrides, }); -export const createMockStartDeps = (startMock?: any): ReportingStartDeps => ({ - data: startMock.data, -}); - export const createMockReportingCore = async ( config: ReportingConfig, - setupDepsMock: ReportingInternalSetup | undefined = undefined, - startDepsMock: ReportingInternalStart | undefined = undefined + setupDeps: ReportingInternalSetup | undefined = undefined, + startDeps: ReportingInternalStart | undefined = undefined ) => { - const mockReportingCore = { - getConfig: () => config, - getElasticsearchService: () => setupDepsMock?.elasticsearch, - } as ReportingCore; - - if (!setupDepsMock) { - setupDepsMock = createMockPluginSetup(mockReportingCore, {}); - } - if (!startDepsMock) { - startDepsMock = createMockPluginStart(mockReportingCore, {}); - } - config = config || {}; - const core = new ReportingCore(); + // setup + const core = new ReportingCore(); + const setupDepsMock = createMockPluginSetup(setupDeps || ({} as ReportingInternalSetup)); core.pluginSetup(setupDepsMock); core.setConfig(config); - await core.pluginSetsUp(); + // start + const store = new ReportingStore(core, logger); + const startDepsMock = createMockPluginStart(startDeps || ({} as ReportingInternalStart), store); + await core.pluginSetsUp(); core.pluginStart(startDepsMock); await core.pluginStartsUp(); diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index 542c9cdb3f67746..27fea4eedf32633 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as Rx from 'rxjs'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { DataPluginStart } from 'src/plugins/data/server/plugin'; @@ -12,60 +11,22 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { CancellationToken } from '../../../plugins/reporting/common'; import { LicensingPluginSetup } from '../../licensing/server'; import { AuthenticatedUser, SecurityPluginSetup } from '../../security/server'; -import { JobStatus } from '../common/types'; import { ReportingConfigType } from './config'; import { ReportingCore } from './core'; import { LevelLogger } from './lib'; -import { LayoutInstance } from './lib/layouts'; +import { LayoutParams } from './lib/layouts'; +import { ReportTaskParams } from './lib/store'; /* * Routing types */ -export interface ReportingRequestPre { - management: { - jobTypes: string[]; - }; - user: string; -} - -// generate a report with unparsed jobParams -export interface GenerateExportTypePayload { - jobParams: string; -} - -export type ReportingRequestPayload = GenerateExportTypePayload | JobParamPostPayload; - export interface TimeRangeParams { timezone: string; min?: Date | string | number | null; max?: Date | string | number | null; } -// the "raw" data coming from the client, unencrypted -export interface JobParamPostPayload { - timerange?: TimeRangeParams; -} - -// the pre-processed, encrypted data ready for storage -export interface ScheduledTaskParams { - headers: string; // serialized encrypted headers - jobParams: JobParamsType; - title: string; - type: string; -} - -export interface JobSource { - _id: string; - _index: string; - _source: { - jobtype: string; - output: TaskRunResult; - payload: ScheduledTaskParams; - status: JobStatus; - }; -} - export interface TaskRunResult { content_type: string | null; content: string | null; @@ -87,62 +48,6 @@ export interface ConditionalHeaders { conditions: ConditionalHeadersConditions; } -/* - * Screenshots - */ - -export interface ScreenshotObservableOpts { - logger: LevelLogger; - urls: string[]; - conditionalHeaders: ConditionalHeaders; - layout: LayoutInstance; - browserTimezone: string; -} - -export interface AttributesMap { - [key: string]: any; -} - -export interface ElementPosition { - boundingClientRect: { - // modern browsers support x/y, but older ones don't - top: number; - left: number; - width: number; - height: number; - }; - scroll: { - x: number; - y: number; - }; -} - -export interface ElementsPositionAndAttribute { - position: ElementPosition; - attributes: AttributesMap; -} - -export interface Screenshot { - base64EncodedData: string; - title: string; - description: string; -} - -export interface ScreenshotResults { - timeRange: string | null; - screenshots: Screenshot[]; - error?: Error; - elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing -} - -export type ScreenshotsObservableFn = ({ - logger, - urls, - conditionalHeaders, - layout, - browserTimezone, -}: ScreenshotObservableOpts) => Rx.Observable; - /* * Plugin Contract */ @@ -171,29 +76,34 @@ export type ScrollConfig = ReportingConfigType['csv']['scroll']; export interface CreateJobBaseParams { browserTimezone: string; - layout?: LayoutInstance; // for screenshot type reports + layout?: LayoutParams; objectType: string; + title: string; } -export interface CreateJobBaseParamsEncryptedFields extends CreateJobBaseParams { - basePath?: string; // for screenshot type reports - headers: string; // encrypted headers +// the pre-processed, encrypted data ready for storage +export interface TaskPayload { + browserTimezone: string; + headers: string; // serialized encrypted headers + objectType: string; + title: string; } -export type CreateJobFn = ( +// default fn type for ScheduleTaskFnFactory +export type CreateJobFn = ( jobParams: JobParamsType, context: RequestHandlerContext, request: KibanaRequest -) => Promise; +) => Promise; -// rename me -export type WorkerExecuteFn = ( +// default fn type for RunTaskFnFactory +export type RunTaskFn = ( jobId: string, - job: ScheduledTaskParamsType, + job: ReportTaskParams, cancellationToken: CancellationToken ) => Promise; -export type ScheduleTaskFnFactory = ( +export type CreateJobFnFactory = ( reporting: ReportingCore, logger: LevelLogger ) => ScheduleTaskFnType; @@ -214,7 +124,7 @@ export interface ExportTypeDefinition< jobType: string; jobContentEncoding?: string; jobContentExtension: string; - scheduleTaskFnFactory: ScheduleTaskFnFactory; + createJobFnFactory: CreateJobFnFactory; runTaskFnFactory: RunTaskFnFactory; validLicenses: string[]; }