[Reporting] Convert Export Type Definitions to Typescript (#51643)

* simplify ts

* fix generate_png + get_full_url

* fix pdf execute job

* fix pdf create job

* fix decrypt job headers

* fix generate pdf / generate png

* remove log

* export consts

* move export type registration to ts

* more export type registration to ts

* ts generics

* remove console.log

* use generics

* fix ts

* fix ts

* fix ts

* fix ts

* Multi-type handling readability fix

* Support createJob's jobParams

* i18n fixes

* track down mysterious field

* revisit ts-ignores

* remove an any type in get_conditional_headers

* ts fixes

* typed export treatment for csv_from_savedobject#executeJob

* refactor helper function plain bonkers signature

* i18n merge fix

* add error handling test

* todo

* fix .headers type def

* Reduce number of loc change

* remove unused params from generic signatures

* Remove as/any

* hoist out GenericWorkerFn for naming

* remove unnecessary fields from JobDocPayloadPanelCsv

* Introduce user defined type guard
This commit is contained in:
Tim Sullivan 2019-12-03 14:19:08 -07:00 committed by GitHub
parent c4143b28f4
commit 45ef370e84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 422 additions and 368 deletions

View file

@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore
import { cryptoFactory } from '../../../server/lib/crypto';
import { createMockServer } from '../../../test_helpers/create_mock_server';
import { decryptJobHeaders } from './index';
import { Logger } from '../../../types';
import { decryptJobHeaders } from './decrypt_job_headers';
let mockServer: any;
beforeEach(() => {
@ -24,17 +24,16 @@ describe('headers', () => {
await expect(
decryptJobHeaders({
job: {
title: 'cool-job-bro',
type: 'csv',
jobParams: {
savedObjectId: 'abc-123',
isImmediate: false,
savedObjectType: 'search',
},
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 Logger,
server: mockServer,
})
).rejects.toBeDefined();
).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]`
);
});
test(`passes back decrypted headers that were passed in`, async () => {
@ -48,13 +47,9 @@ describe('headers', () => {
job: {
title: 'cool-job-bro',
type: 'csv',
jobParams: {
savedObjectId: 'abc-123',
isImmediate: false,
savedObjectType: 'search',
},
headers: encryptedHeaders,
},
logger: {} as Logger,
server: mockServer,
});
expect(decryptedHeaders).toEqual(headers);

View file

@ -3,18 +3,48 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore
import { cryptoFactory } from '../../../server/lib/crypto';
import { CryptoFactory, JobDocPayload, ServerFacade } from '../../../types';
export const decryptJobHeaders = async ({
import { i18n } from '@kbn/i18n';
import { cryptoFactory } from '../../../server/lib/crypto';
import { CryptoFactory, ServerFacade, Logger } from '../../../types';
interface HasEncryptedHeaders {
headers?: string;
}
// TODO merge functionality with CSV execute job
export const decryptJobHeaders = async <
JobParamsType,
JobDocPayloadType extends HasEncryptedHeaders
>({
job,
server,
logger,
}: {
job: JobDocPayload;
job: JobDocPayloadType;
server: ServerFacade;
}) => {
logger: Logger;
}): Promise<{
job: JobDocPayloadType;
server: ServerFacade;
decryptedHeaders: Record<string, string>;
}> => {
const crypto: CryptoFactory = cryptoFactory(server);
const decryptedHeaders: string = await crypto.decrypt(job.headers);
return { job, decryptedHeaders, server };
try {
const decryptedHeaders: Record<string, string> = await crypto.decrypt(job.headers);
return { job, decryptedHeaders, server };
} catch (err) {
logger.error(err);
throw new Error(
i18n.translate(
'xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage',
{
defaultMessage:
'Failed to decrypt report job data. Please ensure that {encryptionKey} is set and re-generate this report. {err}',
values: { encryptionKey: 'xpack.reporting.encryptionKey', err: err.toString() },
}
)
);
}
};

View file

@ -28,7 +28,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -45,7 +45,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -66,7 +66,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -83,7 +83,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -98,7 +98,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -121,7 +121,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -138,7 +138,7 @@ describe('conditions', () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -154,7 +154,7 @@ test('uses basePath from job when creating saved object service', async () => {
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -181,7 +181,7 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav
};
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: permittedHeaders,
server: mockServer,
});
@ -204,7 +204,7 @@ describe('config formatting', () => {
test(`lowercases server.host`, async () => {
mockServer = createMockServer({ settings: { 'server.host': 'COOL-HOSTNAME' } });
const { conditionalHeaders } = await getConditionalHeaders({
job: {} as JobDocPayload,
job: {} as JobDocPayload<any>,
filteredHeaders: {},
server: mockServer,
});

View file

@ -3,14 +3,14 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ConditionalHeaders, JobDocPayload, ServerFacade } from '../../../types';
import { ConditionalHeaders, ServerFacade } from '../../../types';
export const getConditionalHeaders = ({
export const getConditionalHeaders = <JobDocPayloadType>({
job,
filteredHeaders,
server,
}: {
job: JobDocPayload;
job: JobDocPayloadType;
filteredHeaders: Record<string, string>;
server: ServerFacade;
}) => {

View file

@ -5,10 +5,17 @@
*/
import { createMockServer } from '../../../test_helpers/create_mock_server';
import { ServerFacade } from '../../../types';
import { JobDocPayloadPNG } from '../../png/types';
import { JobDocPayloadPDF } from '../../printable_pdf/types';
import { getFullUrls } from './get_full_urls';
interface FullUrlsOpts {
job: JobDocPayloadPNG & JobDocPayloadPDF;
server: ServerFacade;
conditionalHeaders: any;
}
let mockServer: any;
beforeEach(() => {
mockServer = createMockServer('');
@ -17,9 +24,9 @@ beforeEach(() => {
test(`fails if no URL is passed`, async () => {
await expect(
getFullUrls({
job: {} as JobDocPayloadPNG,
job: {},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`]`
);
@ -33,9 +40,9 @@ test(`fails if URLs are file-protocols for PNGs`, async () => {
job: {
relativeUrl,
forceNow,
} as JobDocPayloadPNG,
},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]`
);
@ -50,9 +57,9 @@ test(`fails if URLs are absolute for PNGs`, async () => {
job: {
relativeUrl,
forceNow,
} as JobDocPayloadPNG,
},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]`
);
@ -70,9 +77,9 @@ test(`fails if URLs are file-protocols for PDF`, async () => {
},
],
forceNow,
} as JobDocPayloadPDF,
},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]`
);
@ -91,9 +98,9 @@ test(`fails if URLs are absolute for PDF`, async () => {
},
],
forceNow,
} as JobDocPayloadPDF,
},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]`
);
@ -118,9 +125,9 @@ test(`fails if any URLs are absolute or file's for PDF`, async () => {
job: {
objects,
forceNow,
} as JobDocPayloadPDF,
},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: Found invalid URL(s), all URLs must be relative: ${objects[1].relativeUrl} ${objects[2].relativeUrl}]`
);
@ -131,9 +138,9 @@ test(`fails if URL does not route to a visualization`, async () => {
getFullUrls({
job: {
relativeUrl: '/app/phoney',
} as JobDocPayloadPNG,
},
server: mockServer,
})
} as FullUrlsOpts)
).rejects.toMatchInlineSnapshot(
`[Error: No valid hash in the URL! A hash is expected for the application to route to the intended visualization.]`
);
@ -145,9 +152,9 @@ test(`adds forceNow to hash's query, if it exists`, async () => {
job: {
relativeUrl: '/app/kibana#/something',
forceNow,
} as JobDocPayloadPNG,
},
server: mockServer,
});
} as FullUrlsOpts);
expect(urls[0]).toEqual(
'http://localhost:5601/sbp/app/kibana#/something?forceNow=2000-01-01T00%3A00%3A00.000Z'
@ -161,9 +168,9 @@ test(`appends forceNow to hash's query, if it exists`, async () => {
job: {
relativeUrl: '/app/kibana#/something?_g=something',
forceNow,
} as JobDocPayloadPNG,
},
server: mockServer,
});
} as FullUrlsOpts);
expect(urls[0]).toEqual(
'http://localhost:5601/sbp/app/kibana#/something?_g=something&forceNow=2000-01-01T00%3A00%3A00.000Z'
@ -174,9 +181,9 @@ test(`doesn't append forceNow query to url, if it doesn't exists`, async () => {
const { urls } = await getFullUrls({
job: {
relativeUrl: '/app/kibana#/something',
} as JobDocPayloadPNG,
},
server: mockServer,
});
} as FullUrlsOpts);
expect(urls[0]).toEqual('http://localhost:5601/sbp/app/kibana#/something');
});
@ -192,9 +199,9 @@ test(`adds forceNow to each of multiple urls`, async () => {
{ relativeUrl: '/app/kibana#/something_ddd' },
],
forceNow,
} as JobDocPayloadPDF,
},
server: mockServer,
});
} as FullUrlsOpts);
expect(urls).toEqual([
'http://localhost:5601/sbp/app/kibana#/something_aaa?forceNow=2000-01-01T00%3A00%3A00.000Z',

View file

@ -12,21 +12,26 @@ import {
} from 'url';
import { getAbsoluteUrlFactory } from '../../../common/get_absolute_url';
import { validateUrls } from '../../../common/validate_urls';
import { ServerFacade } from '../../../types';
import { ServerFacade, ConditionalHeaders } from '../../../types';
import { JobDocPayloadPNG } from '../../png/types';
import { JobDocPayloadPDF } from '../../printable_pdf/types';
interface KeyedRelativeUrl {
relativeUrl: string;
function isPngJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPNG {
return (job as JobDocPayloadPNG).relativeUrl !== undefined;
}
function isPdfJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPDF {
return (job as JobDocPayloadPDF).objects !== undefined;
}
export async function getFullUrls({
export async function getFullUrls<JobDocPayloadType>({
job,
server,
...mergeValues // pass-throughs
}: {
job: JobDocPayloadPNG | JobDocPayloadPDF;
job: JobDocPayloadPDF | JobDocPayloadPNG;
server: ServerFacade;
conditionalHeaders: ConditionalHeaders;
logo?: string;
}) {
const config = server.config();
@ -40,12 +45,10 @@ export async function getFullUrls({
// PDF and PNG job params put in the url differently
let relativeUrls: string[] = [];
if (job.relativeUrl) {
// single page (png)
if (isPngJob(job)) {
relativeUrls = [job.relativeUrl];
} else if (job.objects) {
// multi page (pdf)
relativeUrls = job.objects.map((obj: KeyedRelativeUrl) => obj.relativeUrl);
} else if (isPdfJob(job)) {
relativeUrls = job.objects.map(obj => obj.relativeUrl);
} else {
throw new Error(
`No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\``
@ -93,5 +96,5 @@ export async function getFullUrls({
});
});
return { job, urls, server, ...mergeValues };
return { job, server, urls, ...mergeValues };
}

View file

@ -5,14 +5,14 @@
*/
import { omit } from 'lodash';
import { KBN_SCREENSHOT_HEADER_BLACKLIST } from '../../../common/constants';
import { JobDocPayload, ServerFacade } from '../../../types';
import { ServerFacade } from '../../../types';
export const omitBlacklistedHeaders = ({
export const omitBlacklistedHeaders = <JobDocPayloadType>({
job,
decryptedHeaders,
server,
}: {
job: JobDocPayload;
job: JobDocPayloadType;
decryptedHeaders: Record<string, string>;
server: ServerFacade;
}) => {

View file

@ -10,7 +10,7 @@ import { ServerFacade, CaptureConfig } from '../../../../types';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import {
ElementsPositionAndAttribute,
Screenshot,
ScreenshotResults,
ScreenshotObservableOpts,
TimeRange,
} from './types';
@ -26,11 +26,6 @@ import { getElementPositionAndAttributes } from './get_element_position_data';
import { getScreenshots } from './get_screenshots';
import { skipTelemetry } from './skip_telemetry';
interface ScreenshotResults {
timeRange: TimeRange;
screenshots: Screenshot[];
}
export function screenshotsObservableFactory(server: ServerFacade) {
const config = server.config();
const captureConfig: CaptureConfig = config.get('xpack.reporting.capture');
@ -48,7 +43,7 @@ export function screenshotsObservableFactory(server: ServerFacade) {
browserTimezone,
});
// @ts-ignore this needs to be refactored to use less random type declaration and instead rely on structures that work with inference
// @ts-ignore this needs to be refactored to use less random type declaration and instead rely on structures that work with inference TODO
return create$.pipe(
mergeMap(({ driver$, exit$ }) => {
const screenshot$ = driver$.pipe(

View file

@ -30,7 +30,12 @@ export interface ElementsPositionAndAttribute {
}
export interface Screenshot {
base64EncodedData: string;
base64EncodedData: Buffer;
title: string;
description: string;
}
export interface ScreenshotResults {
timeRange: TimeRange;
screenshots: Screenshot[];
}

View file

@ -6,5 +6,5 @@
export const metadata = {
id: 'csv',
name: 'CSV'
name: 'CSV',
};

View file

@ -5,15 +5,23 @@
*/
import { cryptoFactory } from '../../../server/lib/crypto';
import { ConditionalHeaders, ServerFacade, RequestFacade } from '../../../types';
import {
CreateJobFactory,
ConditionalHeaders,
ServerFacade,
RequestFacade,
ESQueueCreateJobFn,
} from '../../../types';
import { JobParamsDiscoverCsv } from '../types';
export const createJobFactory = function createJobFn(server: ServerFacade) {
export const createJobFactory: CreateJobFactory<ESQueueCreateJobFn<
JobParamsDiscoverCsv
>> = function createJobFactoryFn(server: ServerFacade) {
const crypto = cryptoFactory(server);
return async function createJob(
jobParams: JobParamsDiscoverCsv,
headers: ConditionalHeaders,
headers: ConditionalHeaders['headers'],
request: RequestFacade
) {
const serializedEncryptedHeaders = await crypto.encrypt(headers);

View file

@ -4,20 +4,30 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { ExecuteJobFactory, ESQueueWorkerExecuteFn, ServerFacade } from '../../../types';
import { CSV_JOB_TYPE, PLUGIN_ID } from '../../../common/constants';
import { cryptoFactory, LevelLogger } from '../../../server/lib';
import { JobDocPayloadDiscoverCsv } from '../types';
// @ts-ignore untyped module TODO
import { createGenerateCsv } from './lib/generate_csv';
// @ts-ignore untyped module TODO
import { fieldFormatMapFactory } from './lib/field_format_map';
import { i18n } from '@kbn/i18n';
export function executeJobFactory(server) {
export const executeJobFactory: ExecuteJobFactory<ESQueueWorkerExecuteFn<
JobDocPayloadDiscoverCsv
>> = function executeJobFactoryFn(server: ServerFacade) {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
const crypto = cryptoFactory(server);
const config = server.config();
const logger = LevelLogger.createForServer(server, [PLUGIN_ID, CSV_JOB_TYPE, 'execute-job']);
const serverBasePath = config.get('server.basePath');
return async function executeJob(jobId, job, cancellationToken) {
return async function executeJob(
jobId: string,
job: JobDocPayloadDiscoverCsv,
cancellationToken: any
) {
const jobLogger = logger.clone([jobId]);
const {
@ -65,7 +75,7 @@ export function executeJobFactory(server) {
},
};
const callEndpoint = (endpoint, clientParams = {}, options = {}) => {
const callEndpoint = (endpoint: string, clientParams = {}, options = {}) => {
return callWithRequest(fakeRequest, endpoint, clientParams, options);
};
const savedObjects = server.savedObjects;
@ -76,6 +86,7 @@ export function executeJobFactory(server) {
const [formatsMap, uiSettings] = await Promise.all([
(async () => {
// @ts-ignore fieldFormatServiceFactory' does not exist on type 'ServerFacade TODO
const fieldFormats = await server.fieldFormatServiceFactory(uiConfig);
return fieldFormatMapFactory(indexPatternSavedObject, fieldFormats);
})(),
@ -125,4 +136,4 @@ export function executeJobFactory(server) {
csv_contains_formulas: csvContainsFormulas,
};
};
}
};

View file

@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ExportTypesRegistry } from '../../../types';
import { createJobFactory } from './create_job';
import { executeJobFactory } from './execute_job';
import { metadata } from '../metadata';
import { CSV_JOB_TYPE as jobType } from '../../../common/constants';
export function register(registry) {
export function register(registry: ExportTypesRegistry) {
registry.register({
...metadata,
jobType,

View file

@ -84,8 +84,8 @@ describe(`Check CSV Injected values`, () => {
checkIfRowsHaveFormulas(
{
_doc: 'foo-bar',
// @ts-ignore need to assert non-string values still return false
value: nonRow,
// need to assert non-string values still return false
value: (nonRow as unknown) as string,
title: 'nice',
},
['_doc', 'value', 'title']

View file

@ -15,19 +15,14 @@ export interface JobParamPostPayloadDiscoverCsv extends JobParamPostPayload {
export interface JobParamsDiscoverCsv {
indexPatternId?: string;
post?: JobParamPostPayloadDiscoverCsv; // delete this
post?: JobParamPostPayloadDiscoverCsv;
}
export interface JobDocPayloadDiscoverCsv extends JobDocPayload {
export interface JobDocPayloadDiscoverCsv extends JobDocPayload<JobParamsDiscoverCsv> {
basePath: string;
searchRequest: any;
fields: any;
indexPatternSavedObject: any;
metaFields: any;
conflictedTypesFields: any;
}
export type ESQueueCreateJobFnDiscoverCsv = (
jobParams: JobParamsDiscoverCsv,
headers: ConditionalHeaders,
request: RequestFacade
) => Promise<JobParamsDiscoverCsv>;

View file

@ -8,7 +8,12 @@ import { notFound, notImplemented } from 'boom';
import { get } from 'lodash';
import { PLUGIN_ID, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants';
import { cryptoFactory, LevelLogger } from '../../../../server/lib';
import { ImmediateCreateJobFn, ServerFacade, RequestFacade } from '../../../../types';
import {
CreateJobFactory,
ImmediateCreateJobFn,
ServerFacade,
RequestFacade,
} from '../../../../types';
import {
SavedObject,
SavedObjectServiceError,
@ -27,7 +32,9 @@ interface VisData {
panel: SearchPanel;
}
export function createJobFactory(server: ServerFacade): ImmediateCreateJobFn {
export const createJobFactory: CreateJobFactory<ImmediateCreateJobFn<
JobParamsPanelCsv
>> = function createJobFactoryFn(server: ServerFacade) {
const crypto = cryptoFactory(server);
const logger = LevelLogger.createForServer(server, [
PLUGIN_ID,
@ -88,9 +95,8 @@ export function createJobFactory(server: ServerFacade): ImmediateCreateJobFn {
return {
headers: serializedEncryptedHeaders,
jobParams: { ...jobParams, panel, visType },
type: null, // resolved in executeJob
objects: null, // resolved in executeJob
type: null,
title,
};
};
}
};

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { cryptoFactory, LevelLogger } from '../../../server/lib';
import {
ExecuteJobFactory,
ImmediateExecuteFn,
JobDocOutputExecuted,
ServerFacade,
@ -26,7 +27,9 @@ import {
} from '../types';
import { createGenerateCsv } from './lib';
export function executeJobFactory(server: ServerFacade): ImmediateExecuteFn {
export const executeJobFactory: ExecuteJobFactory<ImmediateExecuteFn<
JobParamsPanelCsv
>> = function executeJobFactoryFn(server: ServerFacade) {
const crypto = cryptoFactory(server);
const logger = LevelLogger.createForServer(server, [
PLUGIN_ID,
@ -118,4 +121,4 @@ export function executeJobFactory(server: ServerFacade): ImmediateExecuteFn {
size,
};
};
}
};

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore no module definition
// @ts-ignore no module definition TODO
import { createGenerateCsv } from '../../../csv/server/lib/generate_csv';
import { CancellationToken } from '../../../../common/cancellation_token';
import { ServerFacade, RequestFacade, Logger } from '../../../../types';

View file

@ -25,8 +25,7 @@ export interface JobParamsPanelCsv {
visType?: string;
}
export interface JobDocPayloadPanelCsv extends JobDocPayload {
type: string | null;
export interface JobDocPayloadPanelCsv extends JobDocPayload<JobParamsPanelCsv> {
jobParams: JobParamsPanelCsv;
}

View file

@ -6,5 +6,5 @@
export const metadata = {
id: 'png',
name: 'PNG'
name: 'PNG',
};

View file

@ -4,17 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ServerFacade, RequestFacade, ConditionalHeaders } from '../../../../types';
import {
CreateJobFactory,
ServerFacade,
RequestFacade,
ESQueueCreateJobFn,
ConditionalHeaders,
} from '../../../../types';
import { validateUrls } from '../../../../common/validate_urls';
import { cryptoFactory } from '../../../../server/lib/crypto';
import { JobParamsPNG } from '../../types';
export const createJobFactory = function createJobFn(server: ServerFacade) {
export const createJobFactory: CreateJobFactory<ESQueueCreateJobFn<
JobParamsPNG
>> = function createJobFactoryFn(server: ServerFacade) {
const crypto = cryptoFactory(server);
return async function createJob(
{ objectType, title, relativeUrl, browserTimezone, layout }: JobParamsPNG,
headers: ConditionalHeaders,
headers: ConditionalHeaders['headers'],
request: RequestFacade
) {
const serializedEncryptedHeaders = await crypto.encrypt(headers);

View file

@ -45,6 +45,7 @@ beforeEach(() => {
getScopedSavedObjectsClient: jest.fn(),
},
uiSettingsServiceFactory: jest.fn().mockReturnValue({ get: jest.fn() }),
log: jest.fn(),
};
mockServer.config().get.mockImplementation((key) => {

View file

@ -5,39 +5,33 @@
*/
import * as Rx from 'rxjs';
import { i18n } from '@kbn/i18n';
import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators';
import { PLUGIN_ID, PNG_JOB_TYPE } from '../../../../common/constants';
import { ServerFacade, ExecuteJobFactory, ESQueueWorkerExecuteFn } from '../../../../types';
import { LevelLogger } from '../../../../server/lib';
import { generatePngObservableFactory } from '../lib/generate_png';
import {
decryptJobHeaders,
omitBlacklistedHeaders,
getConditionalHeaders,
getFullUrls,
} from '../../../common/execute_job/';
import { JobDocPayloadPNG } from '../../types';
import { generatePngObservableFactory } from '../lib/generate_png';
export function executeJobFactory(server) {
export const executeJobFactory: ExecuteJobFactory<ESQueueWorkerExecuteFn<
JobDocPayloadPNG
>> = function executeJobFactoryFn(server: ServerFacade) {
const generatePngObservable = generatePngObservableFactory(server);
const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PNG_JOB_TYPE, 'execute']);
return function executeJob(jobId, jobToExecute, cancellationToken) {
return function executeJob(
jobId: string,
jobToExecute: JobDocPayloadPNG,
cancellationToken: any
) {
const jobLogger = logger.clone([jobId]);
const process$ = Rx.of({ job: jobToExecute, server }).pipe(
const process$ = Rx.of({ job: jobToExecute, server, logger }).pipe(
mergeMap(decryptJobHeaders),
catchError(err => {
jobLogger.error(err);
return Rx.throwError(
i18n.translate(
'xpack.reporting.exportTypes.png.compShim.failedToDecryptReportJobDataErrorMessage',
{
defaultMessage:
'Failed to decrypt report job data. Please ensure that {encryptionKey} is set and re-generate this report. {err}',
values: { encryptionKey: 'xpack.reporting.encryptionKey', err: err.toString() },
}
)
);
}),
map(omitBlacklistedHeaders),
map(getConditionalHeaders),
mergeMap(getFullUrls),
@ -51,11 +45,13 @@ export function executeJobFactory(server) {
job.layout
);
}),
map(buffer => ({
content_type: 'image/png',
content: buffer.toString('base64'),
size: buffer.byteLength,
})),
map((buffer: Buffer) => {
return {
content_type: 'image/png',
content: buffer.toString('base64'),
size: buffer.byteLength,
};
}),
catchError(err => {
jobLogger.error(err);
return Rx.throwError(err);
@ -63,7 +59,6 @@ export function executeJobFactory(server) {
);
const stop$ = Rx.fromEventPattern(cancellationToken.on);
return process$.pipe(takeUntil(stop$)).toPromise();
};
}
};

View file

@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ExportTypesRegistry } from '../../../types';
import { createJobFactory } from './create_job';
import { executeJobFactory } from './execute_job';
import { metadata } from '../metadata';
import { PNG_JOB_TYPE as jobType } from '../../../common/constants';
export function register(registry) {
export function register(registry: ExportTypesRegistry) {
registry.register({
...metadata,
jobType,

View file

@ -5,40 +5,15 @@
*/
import * as Rx from 'rxjs';
import { toArray, mergeMap } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { LevelLogger } from '../../../../server/lib';
import { ServerFacade, ConditionalHeaders } from '../../../../types';
import { screenshotsObservableFactory } from '../../../common/lib/screenshots';
import { PreserveLayout } from '../../../common/layouts/preserve_layout';
import { LayoutParams } from '../../../common/layouts/layout';
interface ScreenshotData {
base64EncodedData: string;
}
interface UrlScreenshot {
screenshots: ScreenshotData[];
}
export function generatePngObservableFactory(server: ServerFacade) {
const screenshotsObservable = screenshotsObservableFactory(server);
const captureConcurrency = 1;
// prettier-ignore
const createPngWithScreenshots = async ({ urlScreenshots }: { urlScreenshots: UrlScreenshot[] }): Promise<string> => {
if (urlScreenshots.length !== 1) {
throw new Error(
`Expected there to be 1 URL screenshot, but there are ${urlScreenshots.length}`
);
}
if (urlScreenshots[0].screenshots.length !== 1) {
throw new Error(
`Expected there to be 1 screenshot, but there are ${urlScreenshots[0].screenshots.length}`
);
}
return urlScreenshots[0].screenshots[0].base64EncodedData;
};
return function generatePngObservable(
logger: LevelLogger,
@ -46,24 +21,30 @@ export function generatePngObservableFactory(server: ServerFacade) {
browserTimezone: string,
conditionalHeaders: ConditionalHeaders,
layoutParams: LayoutParams
): Rx.Observable<string> {
): Rx.Observable<Buffer> {
if (!layoutParams || !layoutParams.dimensions) {
throw new Error(`LayoutParams.Dimensions is undefined.`);
}
const layout = new PreserveLayout(layoutParams.dimensions);
const screenshots$ = Rx.of(url).pipe(
mergeMap(
iUrl =>
screenshotsObservable({ logger, url: iUrl, conditionalHeaders, layout, browserTimezone }),
(jUrl: string, screenshot: UrlScreenshot) => screenshot,
captureConcurrency
)
const screenshots$ = screenshotsObservable({
logger,
url,
conditionalHeaders,
layout,
browserTimezone,
}).pipe(
map(urlScreenshots => {
if (urlScreenshots.screenshots.length !== 1) {
throw new Error(
`Expected there to be 1 screenshot, but there are ${urlScreenshots.screenshots.length}`
);
}
return urlScreenshots.screenshots[0].base64EncodedData;
})
);
return screenshots$.pipe(
toArray(),
mergeMap(urlScreenshots => createPngWithScreenshots({ urlScreenshots }))
);
return screenshots$;
};
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LayoutInstance } from '../common/layouts/layout';
import { LayoutInstance, LayoutParams } from '../common/layouts/layout';
import { ConditionalHeaders, JobDocPayload, RequestFacade } from '../../types';
// Job params: structure of incoming user request data
@ -17,17 +17,10 @@ export interface JobParamsPNG {
}
// Job payload: structure of stored job data provided by create_job
export interface JobDocPayloadPNG extends JobDocPayload {
export interface JobDocPayloadPNG extends JobDocPayload<JobParamsPNG> {
basePath?: string;
browserTimezone: string;
forceNow?: string;
layout: LayoutInstance;
layout: LayoutParams;
relativeUrl: string;
objects: undefined;
}
export type ESQueueCreateJobFnPNG = (
jobParams: JobParamsPNG,
headers: ConditionalHeaders,
request: RequestFacade
) => Promise<JobParamsPNG>;

View file

@ -6,5 +6,5 @@
export const metadata = {
id: 'printablePdf',
name: 'PDF'
name: 'PDF',
};

View file

@ -5,20 +5,39 @@
*/
import { PLUGIN_ID, PDF_JOB_TYPE } from '../../../../common/constants';
import {
CreateJobFactory,
ESQueueCreateJobFn,
ServerFacade,
RequestFacade,
ConditionalHeaders,
} from '../../../../types';
import { validateUrls } from '../../../../common/validate_urls';
import { LevelLogger } from '../../../../server/lib';
import { cryptoFactory } from '../../../../server/lib/crypto';
import { JobParamsPDF } from '../../types';
// @ts-ignore untyped module
import { compatibilityShimFactory } from './compatibility_shim';
export function createJobFactory(server) {
interface CreateJobFnOpts {
objectType: any;
title: string;
relativeUrls: string[];
browserTimezone: string;
layout: any;
}
export const createJobFactory: CreateJobFactory<ESQueueCreateJobFn<
JobParamsPDF
>> = function createJobFactoryFn(server: ServerFacade) {
const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'create']);
const compatibilityShim = compatibilityShimFactory(server, logger);
const crypto = cryptoFactory(server);
return compatibilityShim(async function createJobFn(
{ objectType, title, relativeUrls, browserTimezone, layout },
headers,
request
{ objectType, title, relativeUrls, browserTimezone, layout }: CreateJobFnOpts,
headers: ConditionalHeaders['headers'],
request: RequestFacade
) {
const serializedEncryptedHeaders = await crypto.encrypt(headers);
@ -35,4 +54,4 @@ export function createJobFactory(server) {
forceNow: new Date().toISOString(),
};
});
}
};

View file

@ -6,7 +6,8 @@
import * as Rx from 'rxjs';
import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import { ExecuteJobFactory, ESQueueWorkerExecuteFn, ServerFacade } from '../../../../types';
import { JobDocPayloadPDF } from '../../types';
import { PLUGIN_ID, PDF_JOB_TYPE } from '../../../../common/constants';
import { LevelLogger } from '../../../../server/lib';
import { generatePdfObservableFactory } from '../lib/generate_pdf';
@ -18,44 +19,40 @@ import {
getCustomLogo,
} from '../../../common/execute_job/';
export function executeJobFactory(server) {
export const executeJobFactory: ExecuteJobFactory<ESQueueWorkerExecuteFn<
JobDocPayloadPDF
>> = function executeJobFactoryFn(server: ServerFacade) {
const generatePdfObservable = generatePdfObservableFactory(server);
const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'execute']);
return function executeJob(jobId, jobToExecute, cancellationToken) {
return function executeJob(
jobId: string,
jobToExecute: JobDocPayloadPDF,
cancellationToken: any
) {
const jobLogger = logger.clone([jobId]);
const process$ = Rx.of({ job: jobToExecute, server }).pipe(
const process$ = Rx.of({ job: jobToExecute, server, logger }).pipe(
mergeMap(decryptJobHeaders),
catchError(err => {
jobLogger.error(err);
return Rx.throwError(
i18n.translate(
'xpack.reporting.exportTypes.printablePdf.compShim.failedToDecryptReportJobDataErrorMessage',
{
defaultMessage:
'Failed to decrypt report job data. Please ensure that {encryptionKey} is set and re-generate this report. {err}',
values: { encryptionKey: 'xpack.reporting.encryptionKey', err: err.toString() },
}
)
);
}),
map(omitBlacklistedHeaders),
map(getConditionalHeaders),
mergeMap(getCustomLogo),
mergeMap(getFullUrls),
mergeMap(({ job, conditionalHeaders, logo, urls }) => {
return generatePdfObservable(
jobLogger,
job.title,
urls,
job.browserTimezone,
conditionalHeaders,
job.layout,
logo
);
}),
map(buffer => ({
mergeMap(
({ job, conditionalHeaders, logo, urls }): Rx.Observable<Buffer> => {
const { browserTimezone, layout } = jobToExecute;
return generatePdfObservable(
jobLogger,
job.title,
urls,
browserTimezone,
conditionalHeaders,
layout,
logo
);
}
),
map((buffer: Buffer) => ({
content_type: 'application/pdf',
content: buffer.toString('base64'),
size: buffer.byteLength,
@ -70,4 +67,4 @@ export function executeJobFactory(server) {
return process$.pipe(takeUntil(stop$)).toPromise();
};
}
};

View file

@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ExportTypesRegistry } from '../../../types';
import { createJobFactory } from './create_job';
import { executeJobFactory } from './execute_job';
import { metadata } from '../metadata';
import { PDF_JOB_TYPE as jobType } from '../../../common/constants';
export function register(registry) {
export function register(registry: ExportTypesRegistry) {
registry.register({
...metadata,
jobType,

View file

@ -13,21 +13,10 @@ import { ServerFacade, ConditionalHeaders } from '../../../../types';
import { pdf } from './pdf';
import { screenshotsObservableFactory } from '../../../common/lib/screenshots';
import { createLayout } from '../../../common/layouts';
import { TimeRange } from '../../../common/lib/screenshots/types';
import { ScreenshotResults } from '../../../common/lib/screenshots/types';
import { LayoutInstance, LayoutParams } from '../../../common/layouts/layout';
interface ScreenshotData {
base64EncodedData: string;
title: string;
description: string;
}
interface UrlScreenshot {
screenshots: ScreenshotData[];
timeRange: TimeRange;
}
const getTimeRange = (urlScreenshots: UrlScreenshot[]) => {
const getTimeRange = (urlScreenshots: ScreenshotResults[]) => {
const grouped = groupBy(urlScreenshots.map(u => u.timeRange));
const values = Object.values(grouped);
if (values.length === 1) {
@ -48,20 +37,16 @@ export function generatePdfObservableFactory(server: ServerFacade) {
browserTimezone: string,
conditionalHeaders: ConditionalHeaders,
layoutParams: LayoutParams,
logo: string
logo?: string
) {
const layout = createLayout(server, layoutParams) as LayoutInstance;
const screenshots$ = Rx.from(urls).pipe(
mergeMap(
url => screenshotsObservable({ logger, url, conditionalHeaders, layout, browserTimezone }),
captureConcurrency
)
);
return screenshots$.pipe(
),
toArray(),
mergeMap(async (urlScreenshots: UrlScreenshot[]) => {
mergeMap(async (urlScreenshots: ScreenshotResults[]) => {
const pdfOutput = pdf.create(layout, logo);
if (title) {
@ -84,5 +69,7 @@ export function generatePdfObservableFactory(server: ServerFacade) {
return buffer;
})
);
return screenshots$;
};
}

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LayoutInstance } from '../common/layouts/layout';
import { ConditionalHeaders, JobDocPayload, ServerFacade, RequestFacade } from '../../types';
import { LayoutInstance, LayoutParams } from '../common/layouts/layout';
import { JobDocPayload, ServerFacade, RequestFacade } from '../../types';
// Job params: structure of incoming user request data, after being parsed from RISON
export interface JobParamsPDF {
@ -17,21 +17,12 @@ export interface JobParamsPDF {
}
// Job payload: structure of stored job data provided by create_job
export interface JobDocPayloadPDF extends JobDocPayload {
export interface JobDocPayloadPDF extends JobDocPayload<JobParamsPDF> {
basePath?: string;
browserTimezone: string;
forceNow?: string;
layout: any;
layout: LayoutParams;
objects: Array<{
relativeUrl: string;
}>;
relativeUrl: undefined;
}
export type ESQueueCreateJobFnPDF = (
jobParams: JobParamsPDF,
headers: ConditionalHeaders,
request: RequestFacade
) => Promise<JobParamsPDF>;
export type CreateJobFactoryPDF = (server: ServerFacade) => ESQueueCreateJobFnPDF;

View file

@ -11,9 +11,8 @@ import {
QueueConfig,
ExportTypeDefinition,
ESQueueWorkerExecuteFn,
ImmediateExecuteFn,
JobDoc,
JobDocPayload,
ImmediateExecuteFn,
JobSource,
RequestFacade,
ServerFacade,
@ -22,7 +21,8 @@ import {
import { events as esqueueEvents } from './esqueue';
import { LevelLogger } from './level_logger';
export function createWorkerFactory(server: ServerFacade) {
export function createWorkerFactory<JobParamsType>(server: ServerFacade) {
type JobDocPayloadType = JobDocPayload<JobParamsType>;
const config = server.config();
const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'queue-worker']);
const queueConfig: QueueConfig = config.get('xpack.reporting.queue');
@ -31,34 +31,47 @@ export function createWorkerFactory(server: ServerFacade) {
const { exportTypesRegistry } = server.plugins.reporting!;
// Once more document types are added, this will need to be passed in
return function createWorker(queue: ESQueueInstance) {
return function createWorker(queue: ESQueueInstance<JobParamsType, JobDocPayloadType>) {
// export type / execute job map
const jobExecutors: Map<string, ESQueueWorkerExecuteFn | ImmediateExecuteFn> = new Map();
const jobExecutors: Map<
string,
ImmediateExecuteFn<JobParamsType> | ESQueueWorkerExecuteFn<JobDocPayloadType>
> = new Map();
for (const exportType of exportTypesRegistry.getAll() as ExportTypeDefinition[]) {
for (const exportType of exportTypesRegistry.getAll() as Array<
ExportTypeDefinition<JobParamsType, any, any, any>
>) {
const executeJobFactory = exportType.executeJobFactory(server);
jobExecutors.set(exportType.jobType, executeJobFactory);
}
const workerFn = (
job: JobSource,
arg1: JobDocPayload | JobDoc,
arg2: CancellationToken | RequestFacade | undefined
) => {
const workerFn = (jobSource: JobSource<JobParamsType>, ...workerRestArgs: any[]) => {
const {
_id: jobId,
_source: { jobtype: jobType },
} = jobSource;
const jobTypeExecutor = jobExecutors.get(jobType);
// pass the work to the jobExecutor
if (!jobExecutors.get(job._source.jobtype)) {
throw new Error(`Unable to find a job executor for the claimed job: [${job._id}]`);
if (!jobTypeExecutor) {
throw new Error(`Unable to find a job executor for the claimed job: [${jobId}]`);
}
// job executor function signature is different depending on whether it
// is ESQueueWorkerExecuteFn or ImmediateExecuteFn
if (job._id) {
const jobExecutor = jobExecutors.get(job._source.jobtype) as ESQueueWorkerExecuteFn;
return jobExecutor(job._id, arg1 as JobDoc, arg2 as CancellationToken);
if (jobId) {
const jobExecutorWorker = jobTypeExecutor as ESQueueWorkerExecuteFn<JobDocPayloadType>;
return jobExecutorWorker(
jobId,
...(workerRestArgs as [JobDocPayloadType, CancellationToken])
);
} else {
const jobExecutor = jobExecutors.get(job._source.jobtype) as ImmediateExecuteFn;
return jobExecutor(null, arg1 as JobDocPayload, arg2 as RequestFacade);
const jobExecutorImmediate = jobExecutors.get(jobType) as ImmediateExecuteFn<JobParamsType>;
return jobExecutorImmediate(
null,
...(workerRestArgs as [JobDocPayload<JobParamsType>, RequestFacade])
);
}
};
const workerOptions = {
kibanaName,
kibanaId,

View file

@ -8,6 +8,8 @@ import { get } from 'lodash';
// @ts-ignore
import { events as esqueueEvents } from './esqueue';
import {
ESQueueCreateJobFn,
ImmediateCreateJobFn,
Job,
ServerFacade,
RequestFacade,
@ -32,17 +34,19 @@ export function enqueueJobFactory(server: ServerFacade) {
const queueConfig: QueueConfig = config.get('xpack.reporting.queue');
const { exportTypesRegistry, queue: jobQueue } = server.plugins.reporting!;
return async function enqueueJob(
return async function enqueueJob<JobParamsType>(
parentLogger: Logger,
exportTypeId: string,
jobParams: object,
jobParams: JobParamsType,
user: string,
headers: ConditionalHeaders['headers'],
request: RequestFacade
): Promise<Job> {
type CreateJobFn = ESQueueCreateJobFn<JobParamsType> | ImmediateCreateJobFn<JobParamsType>;
const logger = parentLogger.clone(['queue-job']);
const exportType = exportTypesRegistry.getById(exportTypeId);
const createJob = exportType.createJobFactory(server);
const createJob = exportType.createJobFactory(server) as CreateJobFn;
const payload = await createJob(jobParams, headers, request);
const options = {

View file

@ -10,7 +10,7 @@ import { ServerFacade } from '../../types';
import { PLUGIN_ID } from '../../common/constants';
import { oncePerServer } from './once_per_server';
import { LevelLogger } from './level_logger';
// @ts-ignore untype module
// @ts-ignore untype module TODO
import { ExportTypesRegistry } from '../../common/export_types_registry';
function scan(pattern: string) {

View file

@ -4,11 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore untyped module
export { exportTypesRegistryFactory } from './export_types_registry';
// @ts-ignore untyped module
export { checkLicenseFactory } from './check_license';
export { LevelLogger } from './level_logger';
export { createQueueFactory } from './create_queue';
export { cryptoFactory } from './crypto';

View file

@ -38,7 +38,7 @@ export function registerGenerateCsvFromSavedObject(
* 3. Ensure that details for a queued job were returned
*/
let result: QueuedJobPayload;
let result: QueuedJobPayload<any>;
try {
const jobParams = getJobParamsFromRequest(request, { isImmediate: false });
result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, request, h);

View file

@ -12,7 +12,6 @@ import {
ResponseFacade,
ReportingResponseToolkit,
Logger,
JobIDForImmediate,
JobDocOutputExecuted,
} from '../../types';
import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types';
@ -57,11 +56,7 @@ export function registerGenerateCsvFromSavedObjectImmediate(
content_type: jobOutputContentType,
content: jobOutputContent,
size: jobOutputSize,
}: JobDocOutputExecuted = await executeJobFn(
null as JobIDForImmediate,
jobDocPayload,
request
);
}: JobDocOutputExecuted = await executeJobFn(null, jobDocPayload, request);
logger.info(`Job output size: ${jobOutputSize} bytes`);

View file

@ -17,7 +17,7 @@ import { registerLegacy } from './legacy';
export function registerRoutes(server: ServerFacade, logger: Logger) {
const config = server.config();
const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`;
// @ts-ignore
// @ts-ignore TODO
const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin');
const enqueueJob = enqueueJobFactory(server);
@ -30,7 +30,6 @@ export function registerRoutes(server: ServerFacade, logger: Logger) {
request: RequestFacade,
h: ReportingResponseToolkit
) {
// @ts-ignore
const user = request.pre.user;
const headers = request.headers;

View file

@ -6,7 +6,13 @@
import boom from 'boom';
import { API_BASE_URL } from '../../common/constants';
import { JobDoc, ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types';
import {
ServerFacade,
RequestFacade,
ReportingResponseToolkit,
JobDocOutput,
JobSource,
} from '../../types';
// @ts-ignore
import { jobsQueryFactory } from '../lib/jobs_query';
// @ts-ignore
@ -65,8 +71,7 @@ export function registerJobs(server: ServerFacade) {
const { docId } = request.params;
return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then(
(doc: any): JobDoc => {
const job = doc._source;
({ _source: job }: JobSource<any>): JobDocOutput => {
if (!job) {
throw boom.notFound();
}
@ -90,9 +95,9 @@ export function registerJobs(server: ServerFacade) {
handler: (request: RequestFacade) => {
const { docId } = request.params;
return jobsQuery.get(request.pre.user, docId).then(
(doc: any): JobDoc => {
const job: JobDoc = doc._source;
return jobsQuery
.get(request.pre.user, docId)
.then(({ _source: job }: JobSource<any>): JobSource<any>['_source'] => {
if (!job) {
throw boom.notFound();
}
@ -103,14 +108,13 @@ export function registerJobs(server: ServerFacade) {
}
return {
...doc._source,
...job,
payload: {
...payload,
headers: undefined,
},
};
}
);
});
},
});

View file

@ -21,10 +21,12 @@ interface ICustomHeaders {
const DEFAULT_TITLE = 'report';
const getTitle = (exportType: ExportTypeDefinition, title?: string): string =>
type ExportTypeType = ExportTypeDefinition<any, any, any, any>;
const getTitle = (exportType: ExportTypeType, title?: string): string =>
`${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`;
const getReportingHeaders = (output: JobDocOutputExecuted, exportType: ExportTypeDefinition) => {
const getReportingHeaders = (output: JobDocOutputExecuted, exportType: ExportTypeType) => {
const metaDataHeaders: ICustomHeaders = {};
if (exportType.jobType === CSV_JOB_TYPE) {
@ -41,7 +43,7 @@ const getReportingHeaders = (output: JobDocOutputExecuted, exportType: ExportTyp
export function getDocumentPayloadFactory(server: ServerFacade) {
const exportTypesRegistry = server.plugins.reporting!.exportTypesRegistry;
function encodeContent(content: string | null, exportType: ExportTypeDefinition) {
function encodeContent(content: string | null, exportType: ExportTypeType) {
switch (exportType.jobContentEncoding) {
case 'base64':
return content ? Buffer.from(content, 'base64') : content; // Buffer.from rejects null
@ -51,9 +53,7 @@ export function getDocumentPayloadFactory(server: ServerFacade) {
}
function getCompleted(output: JobDocOutputExecuted, jobType: string, title: string) {
const exportType = exportTypesRegistry.get(
(item: ExportTypeDefinition) => item.jobType === jobType
);
const exportType = exportTypesRegistry.get((item: ExportTypeType) => item.jobType === jobType);
const filename = getTitle(exportType, title);
const headers = getReportingHeaders(output, exportType);
@ -88,9 +88,11 @@ export function getDocumentPayloadFactory(server: ServerFacade) {
};
}
return function getDocumentPayload(doc: { _source: JobDocExecuted }) {
return function getDocumentPayload(doc: {
_source: JobDocExecuted<{ output: JobDocOutputExecuted }>;
}) {
const { status, jobtype: jobType, payload: { title } = { title: '' } } = doc._source;
const { output } = doc._source as { output: JobDocOutputExecuted };
const { output } = doc._source;
if (status === 'completed') {
return getCompleted(output, jobType, title);

View file

@ -15,11 +15,11 @@ export type HandlerFunction = (
export type HandlerErrorFunction = (exportType: string, err: Error) => any;
export interface QueuedJobPayload {
export interface QueuedJobPayload<JobParamsType> {
error?: boolean;
source: {
job: {
payload: JobDocPayload;
payload: JobDocPayload<JobParamsType>;
};
};
}

View file

@ -25,13 +25,15 @@ export type Job = EventEmitter & {
export interface ReportingPlugin {
queue: {
addJob: (type: string, payload: object, options: object) => Job;
addJob: <PayloadType>(type: string, payload: PayloadType, options: object) => Job;
};
// TODO: convert exportTypesRegistry to TS
exportTypesRegistry: {
getById: (id: string) => ExportTypeDefinition;
getAll: () => ExportTypeDefinition[];
get: (callback: (item: ExportTypeDefinition) => boolean) => ExportTypeDefinition;
getById: <T, U, V, W>(id: string) => ExportTypeDefinition<T, U, V, W>;
getAll: <T, U, V, W>() => Array<ExportTypeDefinition<T, U, V, W>>;
get: <T, U, V, W>(
callback: (item: ExportTypeDefinition<T, U, V, W>) => boolean
) => ExportTypeDefinition<T, U, V, W>;
};
browserDriverFactory: HeadlessChromiumDriverFactory;
}
@ -182,7 +184,7 @@ export interface ConditionalHeadersConditions {
}
export interface CryptoFactory {
decrypt: (headers?: Record<string, string>) => string;
decrypt: (headers?: string) => any;
}
export interface TimeRangeParams {
@ -196,17 +198,11 @@ export interface JobParamPostPayload {
timerange: TimeRangeParams;
}
export interface JobDocPayload {
headers?: Record<string, string>;
jobParams: any;
export interface JobDocPayload<JobParamsType> {
headers?: string; // serialized encrypted headers
jobParams: JobParamsType;
title: string;
type: string | null;
objects?: null | object[];
}
export interface JobSource {
_id: string;
_source: JobDoc;
}
export interface JobDocOutput {
@ -214,18 +210,21 @@ export interface JobDocOutput {
contentType: string;
}
export interface JobDoc {
export interface JobDocExecuted<JobParamsType> {
jobtype: string;
output: JobDocOutput;
payload: JobDocPayload;
output: JobDocOutputExecuted;
payload: JobDocPayload<JobParamsType>;
status: string; // completed, failed, etc
}
export interface JobDocExecuted {
jobtype: string;
output: JobDocOutputExecuted;
payload: JobDocPayload;
status: string; // completed, failed, etc
export interface JobSource<JobParamsType> {
_id: string;
_source: {
jobtype: string;
output: JobDocOutput;
payload: JobDocPayload<JobParamsType>;
status: string; // completed, failed, etc
};
}
/*
@ -251,41 +250,35 @@ export interface ESQueueWorker {
on: (event: string, handler: any) => void;
}
type JobParamsUrl = object;
interface JobParamsSavedObject {
savedObjectType: string;
savedObjectId: string;
isImmediate: boolean;
}
export type ESQueueCreateJobFn = (
jobParams: JobParamsSavedObject | JobParamsUrl,
export type ESQueueCreateJobFn<JobParamsType> = (
jobParams: JobParamsType,
headers: Record<string, string>,
request: RequestFacade
) => Promise<JobParamsSavedObject | JobParamsUrl>;
) => Promise<JobParamsType>;
export type ImmediateCreateJobFn = (
jobParams: any,
export type ImmediateCreateJobFn<JobParamsType> = (
jobParams: JobParamsType,
headers: Record<string, string>,
req: RequestFacade
) => Promise<{
type: string | null;
title: string;
jobParams: any;
jobParams: JobParamsType;
}>;
export type ESQueueWorkerExecuteFn = (
export type ESQueueWorkerExecuteFn<JobDocPayloadType> = (
jobId: string,
job: JobDoc,
job: JobDocPayloadType,
cancellationToken?: CancellationToken
) => void;
export type JobIDForImmediate = null;
export type ImmediateExecuteFn = (
jobId: JobIDForImmediate,
job: JobDocPayload,
/*
* ImmediateExecuteFn receives the job doc payload because the payload was
* generated in the CreateFn
*/
export type ImmediateExecuteFn<JobParamsType> = (
jobId: null,
job: JobDocPayload<JobParamsType>,
request: RequestFacade
) => Promise<JobDocOutputExecuted>;
@ -296,30 +289,48 @@ export interface ESQueueWorkerOptions {
intervalErrorMultiplier: number;
}
export interface ESQueueInstance {
// GenericWorkerFn is a generic for ImmediateExecuteFn<JobParamsType> | ESQueueWorkerExecuteFn<JobDocPayloadType>,
type GenericWorkerFn<JobParamsType> = (
jobSource: JobSource<JobParamsType>,
...workerRestArgs: any[]
) => void | Promise<JobDocOutputExecuted>;
export interface ESQueueInstance<JobParamsType, JobDocPayloadType> {
registerWorker: (
jobtype: string,
workerFn: any,
pluginId: string,
workerFn: GenericWorkerFn<JobParamsType>,
workerOptions: ESQueueWorkerOptions
) => ESQueueWorker;
}
export type CreateJobFactory = (server: ServerFacade) => ESQueueCreateJobFn | ImmediateCreateJobFn;
export type ExecuteJobFactory = (server: ServerFacade) => ESQueueWorkerExecuteFn | ImmediateExecuteFn; // prettier-ignore
export type CreateJobFactory<CreateJobFnType> = (server: ServerFacade) => CreateJobFnType;
export type ExecuteJobFactory<ExecuteJobFnType> = (server: ServerFacade) => ExecuteJobFnType;
export interface ExportTypeDefinition {
export interface ExportTypeDefinition<
JobParamsType,
CreateJobFnType,
JobPayloadType,
ExecuteJobFnType
> {
id: string;
name: string;
jobType: string;
jobContentEncoding?: string;
jobContentExtension: string;
createJobFactory: CreateJobFactory;
executeJobFactory: ExecuteJobFactory;
createJobFactory: CreateJobFactory<CreateJobFnType>;
executeJobFactory: ExecuteJobFactory<ExecuteJobFnType>;
validLicenses: string[];
}
export interface ExportTypesRegistry {
register: (exportTypeDefinition: ExportTypeDefinition) => void;
register: <JobParamsType, CreateJobFnType, JobPayloadType, ExecuteJobFnType>(
exportTypeDefinition: ExportTypeDefinition<
JobParamsType,
CreateJobFnType,
JobPayloadType,
ExecuteJobFnType
>
) => void;
}
export { CancellationToken } from './common/cancellation_token';

View file

@ -9610,8 +9610,6 @@
"xpack.reporting.exportTypes.csv.executeJob.failedToDecryptReportJobDataErrorMessage": "レポートジョブデータの解読に失敗しました{encryptionKey} が設定されていることを確認してこのレポートを再生成してください。{err}",
"xpack.reporting.exportTypes.csv.hitIterator.expectedHitsErrorMessage": "次の Elasticsearch からの応答で期待される {hits}: {response}",
"xpack.reporting.exportTypes.csv.hitIterator.expectedScrollIdErrorMessage": "次の Elasticsearch からの応答で期待される {scrollId}: {response}",
"xpack.reporting.exportTypes.png.compShim.failedToDecryptReportJobDataErrorMessage": "レポートジョブデータの解読に失敗しました{encryptionKey} が設定されていることを確認してこのレポートを再生成してください。{err}",
"xpack.reporting.exportTypes.printablePdf.compShim.failedToDecryptReportJobDataErrorMessage": "レポートジョブデータの解読に失敗しました{encryptionKey} が設定されていることを確認してこのレポートを再生成してください。{err}",
"xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage": "ドキュメントストリームが生成されていません。",
"xpack.reporting.exportTypes.printablePdf.logoDescription": "Elastic 提供",
"xpack.reporting.exportTypes.printablePdf.pagingDescription": "{pageCount} ページ中 {currentPage} ページ目",

View file

@ -9699,8 +9699,6 @@
"xpack.reporting.exportTypes.csv.executeJob.failedToDecryptReportJobDataErrorMessage": "无法解密报告作业数据。请确保已设置 {encryptionKey},然后重新生成此报告。{err}",
"xpack.reporting.exportTypes.csv.hitIterator.expectedHitsErrorMessage": "在以下 Elasticsearch 响应中预期 {hits}{response}",
"xpack.reporting.exportTypes.csv.hitIterator.expectedScrollIdErrorMessage": "在以下 Elasticsearch 响应中预期 {scrollId}{response}",
"xpack.reporting.exportTypes.png.compShim.failedToDecryptReportJobDataErrorMessage": "无法解密报告作业数据。请确保已设置 {encryptionKey},然后重新生成此报告。{err}",
"xpack.reporting.exportTypes.printablePdf.compShim.failedToDecryptReportJobDataErrorMessage": "无法解密报告作业数据。请确保已设置 {encryptionKey},然后重新生成此报告。{err}",
"xpack.reporting.exportTypes.printablePdf.documentStreamIsNotgeneratedErrorMessage": "尚未生成文档流",
"xpack.reporting.exportTypes.printablePdf.logoDescription": "由 Elastic 提供支持",
"xpack.reporting.exportTypes.printablePdf.pagingDescription": "第 {currentPage} 页,共 {pageCount} 页",