mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Reporting] Remove encryption logic from export types (#111472)
* [Reporting] change CreateJobFn type to not handle KibanaRequest * fix enqueueJob * fix tests * fix imports * convert enqueue_job into a method of request_handler * Update types.ts * Update report.ts * fix import * update comment and structure Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
543a7cbd7d
commit
af06aec5b0
15 changed files with 225 additions and 332 deletions
|
@ -76,7 +76,7 @@ export interface BasePayload extends BaseParams {
|
|||
|
||||
export interface ReportSource {
|
||||
/*
|
||||
* Required fields: populated in enqueue_job when the request comes in to
|
||||
* Required fields: populated in RequestHandler.enqueueJob when the request comes in to
|
||||
* generate the report
|
||||
*/
|
||||
jobtype: string; // refers to `ExportTypeDefinition.jobType`
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { cryptoFactory } from '../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../types';
|
||||
import {
|
||||
IndexPatternSavedObjectDeprecatedCSV,
|
||||
|
@ -15,15 +14,11 @@ import {
|
|||
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsDeprecatedCSV, TaskPayloadDeprecatedCSV>
|
||||
> = function createJobFactoryFn(reporting, logger) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob(jobParams, context, request) {
|
||||
> = function createJobFactoryFn(_reporting, logger) {
|
||||
return async function createJob(jobParams, context) {
|
||||
logger.warn(
|
||||
`The "/generate/csv" endpoint is deprecated and will be removed in Kibana 8.0. Please recreate the POST URL used to automate this CSV export.`
|
||||
);
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(request.headers);
|
||||
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const indexPatternSavedObject = ((await savedObjectsClient.get(
|
||||
|
@ -33,8 +28,6 @@ export const createJobFnFactory: CreateJobFnFactory<
|
|||
|
||||
return {
|
||||
isDeprecated: true,
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(request, logger),
|
||||
indexPatternSavedObject,
|
||||
...jobParams,
|
||||
};
|
||||
|
|
|
@ -5,26 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CSV_JOB_TYPE } from '../../../common/constants';
|
||||
import { cryptoFactory } from '../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../types';
|
||||
import { JobParamsCSV, TaskPayloadCSV } from './types';
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsCSV, TaskPayloadCSV>
|
||||
> = function createJobFactoryFn(reporting, parentLogger) {
|
||||
const logger = parentLogger.clone([CSV_JOB_TYPE, 'create-job']);
|
||||
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob(jobParams, context, request) {
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(request.headers);
|
||||
|
||||
return {
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(request, logger),
|
||||
...jobParams,
|
||||
};
|
||||
> = function createJobFactoryFn() {
|
||||
return async function createJob(jobParams) {
|
||||
return jobParams;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,26 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { cryptoFactory } from '../../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../../types';
|
||||
import { validateUrls } from '../../common';
|
||||
import { JobParamsPNG, TaskPayloadPNG } from '../types';
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsPNG, TaskPayloadPNG>
|
||||
> = function createJobFactoryFn(reporting, logger) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob(jobParams, _context, req) {
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(req.headers);
|
||||
|
||||
> = function createJobFactoryFn() {
|
||||
return async function createJob(jobParams) {
|
||||
validateUrls([jobParams.relativeUrl]);
|
||||
|
||||
return {
|
||||
...jobParams,
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(req, logger),
|
||||
forceNow: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,23 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { cryptoFactory } from '../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../types';
|
||||
import { JobParamsPNGV2, TaskPayloadPNGV2 } from './types';
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsPNGV2, TaskPayloadPNGV2>
|
||||
> = function createJobFactoryFn(reporting, logger) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob({ locatorParams, ...jobParams }, context, req) {
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(req.headers);
|
||||
|
||||
> = function createJobFactoryFn() {
|
||||
return async function createJob({ locatorParams, ...jobParams }) {
|
||||
return {
|
||||
...jobParams,
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(req, logger),
|
||||
locatorParams: [locatorParams],
|
||||
forceNow: new Date().toISOString(),
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { coreMock, httpServerMock } from 'src/core/server/mocks';
|
||||
import { coreMock } from 'src/core/server/mocks';
|
||||
import { createMockLevelLogger } from '../../../test_helpers';
|
||||
import { compatibilityShim } from './compatibility_shim';
|
||||
|
||||
|
@ -15,7 +15,6 @@ const mockRequestHandlerContext = {
|
|||
};
|
||||
const mockLogger = createMockLevelLogger();
|
||||
|
||||
const mockKibanaRequest = httpServerMock.createKibanaRequest();
|
||||
const createMockSavedObject = (body: any) => ({
|
||||
id: 'mockSavedObjectId123',
|
||||
type: 'mockSavedObjectType',
|
||||
|
@ -36,8 +35,7 @@ test(`passes title through if provided`, async () => {
|
|||
const createJobMock = jest.fn();
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ title, relativeUrls: ['/something'] }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(0);
|
||||
|
@ -58,8 +56,7 @@ test(`gets the title from the savedObject`, async () => {
|
|||
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(2);
|
||||
|
@ -83,8 +80,7 @@ test(`passes the objectType and savedObjectId to the savedObjectsClient`, async
|
|||
const savedObjectId = 'abc';
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ objectType, savedObjectId }),
|
||||
context,
|
||||
mockKibanaRequest
|
||||
context
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(2);
|
||||
|
@ -107,8 +103,7 @@ test(`logs no warnings when title and relativeUrls is passed`, async () => {
|
|||
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ title: 'Phenomenal Dashboard', relativeUrls: ['/abc', '/def'] }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(0);
|
||||
|
@ -119,8 +114,7 @@ test(`logs warning if title can not be provided`, async () => {
|
|||
const createJobMock = jest.fn();
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ relativeUrls: ['/abc'] }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(1);
|
||||
|
@ -140,8 +134,7 @@ test(`logs deprecations when generating the title/relativeUrl using the savedObj
|
|||
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(2);
|
||||
|
@ -159,8 +152,7 @@ test(`passes objectType through`, async () => {
|
|||
const objectType = 'foo';
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ title: 'test', relativeUrls: ['/something'], objectType }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(0);
|
||||
|
@ -176,8 +168,7 @@ test(`passes the relativeUrls through`, async () => {
|
|||
const relativeUrls = ['/app/kibana#something', '/app/kibana#something-else'];
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ title: 'test', relativeUrls }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(0);
|
||||
|
@ -193,8 +184,7 @@ const testSavedObjectRelativeUrl = (objectType: string, expectedUrl: string) =>
|
|||
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ title: 'test', objectType, savedObjectId: 'abc' }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(1);
|
||||
|
@ -222,8 +212,7 @@ test(`appends the queryString to the relativeUrl when generating from the savedO
|
|||
savedObjectId: 'abc',
|
||||
queryString: 'foo=bar',
|
||||
}),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(1);
|
||||
|
@ -248,8 +237,7 @@ test(`throw an Error if the objectType, savedObjectId and relativeUrls are provi
|
|||
relativeUrls: ['/something'],
|
||||
savedObjectId: 'abc',
|
||||
}),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
await expect(promise).rejects.toBeDefined();
|
||||
|
@ -260,8 +248,7 @@ test(`passes headers and request through`, async () => {
|
|||
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ title: 'test', relativeUrls: ['/something'] }),
|
||||
mockRequestHandlerContext,
|
||||
mockKibanaRequest
|
||||
mockRequestHandlerContext
|
||||
);
|
||||
|
||||
expect(mockLogger.warn.mock.calls.length).toBe(0);
|
||||
|
@ -269,5 +256,4 @@ test(`passes headers and request through`, async () => {
|
|||
|
||||
expect(createJobMock.mock.calls.length).toBe(1);
|
||||
expect(createJobMock.mock.calls[0][1]).toBe(mockRequestHandlerContext);
|
||||
expect(createJobMock.mock.calls[0][2]).toBe(mockKibanaRequest);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
|
||||
import type { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { url as urlUtils } from '../../../../../../../src/plugins/kibana_utils/server';
|
||||
import type { LevelLogger } from '../../../lib';
|
||||
import type { CreateJobFn, ReportingRequestHandlerContext } from '../../../types';
|
||||
|
@ -56,8 +56,7 @@ export function compatibilityShim(
|
|||
) {
|
||||
return async function (
|
||||
jobParams: JobParamsPDF | JobParamsPDFLegacy,
|
||||
context: ReportingRequestHandlerContext,
|
||||
req: KibanaRequest
|
||||
context: ReportingRequestHandlerContext
|
||||
) {
|
||||
let kibanaRelativeUrls = (jobParams as JobParamsPDF).relativeUrls;
|
||||
let reportTitle = jobParams.title;
|
||||
|
@ -125,6 +124,6 @@ export function compatibilityShim(
|
|||
isDeprecated, // tack on this flag so it will be saved the TaskPayload
|
||||
};
|
||||
|
||||
return await createJobFn(transformedJobParams, context, req);
|
||||
return await createJobFn(transformedJobParams, context);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
import { cryptoFactory } from '../../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory, ReportingRequestHandlerContext } from '../../../types';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../../types';
|
||||
import { validateUrls } from '../../common';
|
||||
import { JobParamsPDF, JobParamsPDFLegacy, TaskPayloadPDF } from '../types';
|
||||
import { compatibilityShim } from './compatibility_shim';
|
||||
|
@ -18,24 +16,15 @@ import { compatibilityShim } from './compatibility_shim';
|
|||
*/
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsPDF | JobParamsPDFLegacy, TaskPayloadPDF>
|
||||
> = function createJobFactoryFn(reporting, logger) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
> = function createJobFactoryFn(_reporting, logger) {
|
||||
return compatibilityShim(async function createJobFn(
|
||||
{ relativeUrls, ...jobParams }: JobParamsPDF, // relativeUrls does not belong in the payload
|
||||
_context: ReportingRequestHandlerContext,
|
||||
req: KibanaRequest
|
||||
{ relativeUrls, ...jobParams }: JobParamsPDF // relativeUrls does not belong in the payload of PDFV1
|
||||
) {
|
||||
validateUrls(relativeUrls);
|
||||
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(req.headers);
|
||||
|
||||
// return the payload
|
||||
return {
|
||||
...jobParams,
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(req, logger),
|
||||
forceNow: new Date().toISOString(),
|
||||
objects: relativeUrls.map((u) => ({ relativeUrl: u })),
|
||||
};
|
||||
|
|
|
@ -5,23 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { cryptoFactory } from '../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../types';
|
||||
import { JobParamsPDFV2, TaskPayloadPDFV2 } from './types';
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsPDFV2, TaskPayloadPDFV2>
|
||||
> = function createJobFactoryFn(reporting, logger) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob(jobParams, context, req) {
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(req.headers);
|
||||
|
||||
> = function createJobFactoryFn() {
|
||||
return async function createJob(jobParams) {
|
||||
return {
|
||||
...jobParams,
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(req, logger),
|
||||
forceNow: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
import { ReportingCore } from '../';
|
||||
import { TaskManagerStartContract } from '../../../task_manager/server';
|
||||
import { ReportingInternalStart } from '../core';
|
||||
import {
|
||||
createMockConfigSchema,
|
||||
createMockLevelLogger,
|
||||
createMockReportingCore,
|
||||
} from '../test_helpers';
|
||||
import { ReportingRequestHandlerContext } from '../types';
|
||||
import { ExportTypesRegistry, ReportingStore } from './';
|
||||
import { enqueueJob } from './enqueue_job';
|
||||
import { Report } from './store';
|
||||
|
||||
describe('Enqueue Job', () => {
|
||||
const logger = createMockLevelLogger();
|
||||
let mockReporting: ReportingCore;
|
||||
let mockExportTypesRegistry: ExportTypesRegistry;
|
||||
|
||||
const mockBaseParams = {
|
||||
browserTimezone: 'UTC',
|
||||
headers: 'cool_encrypted_headers',
|
||||
objectType: 'cool_object_type',
|
||||
title: 'cool_title',
|
||||
version: 'unknown' as any,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockBaseParams.version = '7.15.0-test';
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
mockExportTypesRegistry = new ExportTypesRegistry();
|
||||
mockExportTypesRegistry.register({
|
||||
id: 'printablePdf',
|
||||
name: 'Printable PDFble',
|
||||
jobType: 'printable_pdf',
|
||||
jobContentEncoding: 'base64',
|
||||
jobContentExtension: 'pdf',
|
||||
validLicenses: ['turquoise'],
|
||||
createJobFnFactory: () => async () => mockBaseParams,
|
||||
runTaskFnFactory: jest.fn(),
|
||||
});
|
||||
mockReporting = await createMockReportingCore(createMockConfigSchema());
|
||||
mockReporting.getExportTypesRegistry = () => mockExportTypesRegistry;
|
||||
mockReporting.getStore = () =>
|
||||
Promise.resolve(({
|
||||
addReport: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(report) => new Report({ ...report, _index: '.reporting-foo-index-234' })
|
||||
),
|
||||
} as unknown) as ReportingStore);
|
||||
|
||||
const scheduleMock = jest.fn().mockImplementation(() => ({
|
||||
id: '123-great-id',
|
||||
}));
|
||||
|
||||
await mockReporting.pluginStart(({
|
||||
taskManager: ({
|
||||
ensureScheduled: jest.fn(),
|
||||
schedule: scheduleMock,
|
||||
} as unknown) as TaskManagerStartContract,
|
||||
} as unknown) as ReportingInternalStart);
|
||||
});
|
||||
|
||||
it('returns a Report object', async () => {
|
||||
const report = await enqueueJob(
|
||||
mockReporting,
|
||||
({} as unknown) as KibanaRequest,
|
||||
({} as unknown) as ReportingRequestHandlerContext,
|
||||
false,
|
||||
'printablePdf',
|
||||
mockBaseParams,
|
||||
logger
|
||||
);
|
||||
|
||||
const { _id, created_at: _created_at, ...snapObj } = report;
|
||||
expect(snapObj).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_index": ".reporting-foo-index-234",
|
||||
"_primary_term": undefined,
|
||||
"_seq_no": undefined,
|
||||
"attempts": 0,
|
||||
"browser_type": undefined,
|
||||
"completed_at": undefined,
|
||||
"created_by": false,
|
||||
"jobtype": "printable_pdf",
|
||||
"kibana_id": undefined,
|
||||
"kibana_name": undefined,
|
||||
"max_attempts": undefined,
|
||||
"meta": Object {
|
||||
"isDeprecated": undefined,
|
||||
"layout": undefined,
|
||||
"objectType": "cool_object_type",
|
||||
},
|
||||
"migration_version": "7.14.0",
|
||||
"output": null,
|
||||
"payload": Object {
|
||||
"browserTimezone": "UTC",
|
||||
"headers": "cool_encrypted_headers",
|
||||
"objectType": "cool_object_type",
|
||||
"title": "cool_title",
|
||||
"version": "7.15.0-test",
|
||||
},
|
||||
"process_expiration": undefined,
|
||||
"started_at": undefined,
|
||||
"status": "pending",
|
||||
"timeout": undefined,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('provides a default kibana version field for older POST URLs', async () => {
|
||||
mockBaseParams.version = undefined;
|
||||
const report = await enqueueJob(
|
||||
mockReporting,
|
||||
({} as unknown) as KibanaRequest,
|
||||
({} as unknown) as ReportingRequestHandlerContext,
|
||||
false,
|
||||
'printablePdf',
|
||||
mockBaseParams,
|
||||
logger
|
||||
);
|
||||
|
||||
const { _id, created_at: _created_at, ...snapObj } = report;
|
||||
expect(snapObj.payload.version).toBe('7.14.0');
|
||||
});
|
||||
});
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
import { ReportingCore } from '../';
|
||||
import type { ReportingRequestHandlerContext } from '../types';
|
||||
import { BaseParams, ReportingUser } from '../types';
|
||||
import { checkParamsVersion, LevelLogger } from './';
|
||||
import { Report } from './store';
|
||||
|
||||
export async function enqueueJob(
|
||||
reporting: ReportingCore,
|
||||
request: KibanaRequest,
|
||||
context: ReportingRequestHandlerContext,
|
||||
user: ReportingUser,
|
||||
exportTypeId: string,
|
||||
jobParams: BaseParams,
|
||||
parentLogger: LevelLogger
|
||||
): Promise<Report> {
|
||||
const logger = parentLogger.clone(['createJob']);
|
||||
const exportType = reporting.getExportTypesRegistry().getById(exportTypeId);
|
||||
|
||||
if (exportType == null) {
|
||||
throw new Error(`Export type ${exportTypeId} does not exist in the registry!`);
|
||||
}
|
||||
|
||||
if (!exportType.createJobFnFactory) {
|
||||
throw new Error(`Export type ${exportTypeId} is not an async job type!`);
|
||||
}
|
||||
|
||||
const [createJob, store] = await Promise.all([
|
||||
exportType.createJobFnFactory(reporting, logger.clone([exportType.id])),
|
||||
reporting.getStore(),
|
||||
]);
|
||||
|
||||
jobParams.version = checkParamsVersion(jobParams, logger);
|
||||
const job = await createJob!(jobParams, context, request);
|
||||
|
||||
// 1. Add the report to ReportingStore to show as pending
|
||||
const report = await store.addReport(
|
||||
new Report({
|
||||
jobtype: exportType.jobType,
|
||||
created_by: user ? user.username : false,
|
||||
payload: job,
|
||||
meta: {
|
||||
// telemetry fields
|
||||
objectType: jobParams.objectType,
|
||||
layout: jobParams.layout?.id,
|
||||
isDeprecated: job.isDeprecated,
|
||||
},
|
||||
})
|
||||
);
|
||||
logger.debug(`Successfully stored pending job: ${report._index}/${report._id}`);
|
||||
|
||||
// 2. Schedule the report with Task Manager
|
||||
const task = await reporting.scheduleTask(report.toReportTaskJSON());
|
||||
logger.info(
|
||||
`Scheduled ${exportType.name} reporting task. Task ID: task:${task.id}. Report ID: ${report._id}`
|
||||
);
|
||||
|
||||
return report;
|
||||
}
|
|
@ -15,7 +15,7 @@ import {
|
|||
ReportDocumentHead,
|
||||
ReportSource,
|
||||
} from '../../../common/types';
|
||||
import { ReportTaskParams } from '../tasks';
|
||||
import type { ReportTaskParams } from '../tasks';
|
||||
|
||||
export { ReportDocument };
|
||||
export { ReportApiJSON, ReportSource };
|
||||
|
@ -67,7 +67,7 @@ export class Report implements Partial<ReportSource & ReportDocumentHead> {
|
|||
|
||||
this.migration_version = MIGRATION_VERSION;
|
||||
|
||||
// see enqueue_job for all the fields that are expected to exist when adding a report
|
||||
// see RequestHandler.enqueueJob for all the fields that are expected to exist when adding a report
|
||||
if (opts.jobtype == null) {
|
||||
throw new Error(`jobtype is expected!`);
|
||||
}
|
||||
|
|
|
@ -8,18 +8,20 @@
|
|||
import { KibanaRequest, KibanaResponseFactory } from 'kibana/server';
|
||||
import { coreMock, httpServerMock } from 'src/core/server/mocks';
|
||||
import { ReportingCore } from '../..';
|
||||
import { JobParamsPDF, TaskPayloadPDF } from '../../export_types/printable_pdf/types';
|
||||
import { Report, ReportingStore } from '../../lib/store';
|
||||
import { ReportApiJSON } from '../../lib/store/report';
|
||||
import {
|
||||
createMockConfigSchema,
|
||||
createMockLevelLogger,
|
||||
createMockReportingCore,
|
||||
} from '../../test_helpers';
|
||||
import { BaseParams, ReportingRequestHandlerContext, ReportingSetup } from '../../types';
|
||||
import { ReportingRequestHandlerContext, ReportingSetup } from '../../types';
|
||||
import { RequestHandler } from './request_handler';
|
||||
|
||||
jest.mock('../../lib/enqueue_job', () => ({
|
||||
enqueueJob: () => ({
|
||||
_id: 'id-of-this-test-report',
|
||||
toApiJSON: () => JSON.stringify({ id: 'id-of-this-test-report' }),
|
||||
jest.mock('../../lib/crypto', () => ({
|
||||
cryptoFactory: () => ({
|
||||
encrypt: () => `hello mock cypher text`,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
@ -50,10 +52,25 @@ describe('Handle request to generate', () => {
|
|||
let mockResponseFactory: ReturnType<typeof getMockResponseFactory>;
|
||||
let requestHandler: RequestHandler;
|
||||
|
||||
const mockJobParams = {} as BaseParams;
|
||||
const mockJobParams: JobParamsPDF = {
|
||||
browserTimezone: 'UTC',
|
||||
objectType: 'cool_object_type',
|
||||
title: 'cool_title',
|
||||
version: 'unknown',
|
||||
layout: { id: 'preserve_layout' },
|
||||
relativeUrls: [],
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
reportingCore = await createMockReportingCore(createMockConfigSchema({}));
|
||||
reportingCore.getStore = () =>
|
||||
Promise.resolve(({
|
||||
addReport: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(report) => new Report({ ...report, _index: '.reporting-foo-index-234' })
|
||||
),
|
||||
} as unknown) as ReportingStore);
|
||||
mockRequest = getMockRequest();
|
||||
|
||||
mockResponseFactory = getMockResponseFactory();
|
||||
|
@ -73,6 +90,64 @@ describe('Handle request to generate', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('Enqueue Job', () => {
|
||||
test('creates a report object to queue', async () => {
|
||||
const report = await requestHandler.enqueueJob('printablePdf', mockJobParams);
|
||||
|
||||
const { _id, created_at: _created_at, payload, ...snapObj } = report;
|
||||
expect(snapObj).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_index": ".reporting-foo-index-234",
|
||||
"_primary_term": undefined,
|
||||
"_seq_no": undefined,
|
||||
"attempts": 0,
|
||||
"browser_type": undefined,
|
||||
"completed_at": undefined,
|
||||
"created_by": "testymcgee",
|
||||
"jobtype": "printable_pdf",
|
||||
"kibana_id": undefined,
|
||||
"kibana_name": undefined,
|
||||
"max_attempts": undefined,
|
||||
"meta": Object {
|
||||
"isDeprecated": false,
|
||||
"layout": "preserve_layout",
|
||||
"objectType": "cool_object_type",
|
||||
},
|
||||
"migration_version": "7.14.0",
|
||||
"output": null,
|
||||
"process_expiration": undefined,
|
||||
"started_at": undefined,
|
||||
"status": "pending",
|
||||
"timeout": undefined,
|
||||
}
|
||||
`);
|
||||
const { forceNow, ...snapPayload } = payload as TaskPayloadPDF;
|
||||
expect(snapPayload).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"browserTimezone": "UTC",
|
||||
"headers": "hello mock cypher text",
|
||||
"isDeprecated": false,
|
||||
"layout": Object {
|
||||
"id": "preserve_layout",
|
||||
},
|
||||
"objectType": "cool_object_type",
|
||||
"objects": Array [],
|
||||
"spaceId": undefined,
|
||||
"title": "cool_title",
|
||||
"version": "unknown",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('provides a default kibana version field for older POST URLs', async () => {
|
||||
((mockJobParams as unknown) as { version?: string }).version = undefined;
|
||||
const report = await requestHandler.enqueueJob('printablePdf', mockJobParams);
|
||||
|
||||
const { _id, created_at: _created_at, ...snapObj } = report;
|
||||
expect(snapObj.payload.version).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
|
||||
test('disallows invalid export type', async () => {
|
||||
expect(await requestHandler.handleGenerateRequest('neanderthals', mockJobParams))
|
||||
.toMatchInlineSnapshot(`
|
||||
|
@ -95,15 +170,45 @@ describe('Handle request to generate', () => {
|
|||
});
|
||||
|
||||
test('generates the download path', async () => {
|
||||
expect(await requestHandler.handleGenerateRequest('csv', mockJobParams)).toMatchInlineSnapshot(`
|
||||
const response = ((await requestHandler.handleGenerateRequest(
|
||||
'csv',
|
||||
mockJobParams
|
||||
)) as unknown) as { body: { job: ReportApiJSON } };
|
||||
const { id, created_at: _created_at, ...snapObj } = response.body.job;
|
||||
expect(snapObj).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
"job": "{\\"id\\":\\"id-of-this-test-report\\"}",
|
||||
"path": "undefined/api/reporting/jobs/download/id-of-this-test-report",
|
||||
"attempts": 0,
|
||||
"browser_type": undefined,
|
||||
"completed_at": undefined,
|
||||
"created_by": "testymcgee",
|
||||
"index": ".reporting-foo-index-234",
|
||||
"jobtype": "csv",
|
||||
"kibana_id": undefined,
|
||||
"kibana_name": undefined,
|
||||
"max_attempts": undefined,
|
||||
"meta": Object {
|
||||
"isDeprecated": true,
|
||||
"layout": "preserve_layout",
|
||||
"objectType": "cool_object_type",
|
||||
},
|
||||
"headers": Object {
|
||||
"content-type": "application/json",
|
||||
"migration_version": "7.14.0",
|
||||
"output": Object {},
|
||||
"payload": Object {
|
||||
"browserTimezone": "UTC",
|
||||
"indexPatternSavedObject": undefined,
|
||||
"isDeprecated": true,
|
||||
"layout": Object {
|
||||
"id": "preserve_layout",
|
||||
},
|
||||
"objectType": "cool_object_type",
|
||||
"relativeUrls": Array [],
|
||||
"spaceId": undefined,
|
||||
"title": "cool_title",
|
||||
"version": "7.14.0",
|
||||
},
|
||||
"started_at": undefined,
|
||||
"status": "pending",
|
||||
"timeout": undefined,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -10,8 +10,8 @@ import { KibanaRequest, KibanaResponseFactory } from 'kibana/server';
|
|||
import { ReportingCore } from '../..';
|
||||
import { API_BASE_URL } from '../../../common/constants';
|
||||
import { JobParamsPDFLegacy } from '../../export_types/printable_pdf/types';
|
||||
import { LevelLogger } from '../../lib';
|
||||
import { enqueueJob } from '../../lib/enqueue_job';
|
||||
import { checkParamsVersion, cryptoFactory, LevelLogger } from '../../lib';
|
||||
import { Report } from '../../lib/store';
|
||||
import { BaseParams, ReportingRequestHandlerContext, ReportingUser } from '../../types';
|
||||
|
||||
export const handleUnavailable = (res: KibanaResponseFactory) => {
|
||||
|
@ -33,6 +33,75 @@ export class RequestHandler {
|
|||
private logger: LevelLogger
|
||||
) {}
|
||||
|
||||
private async encryptHeaders() {
|
||||
const encryptionKey = this.reporting.getConfig().get('encryptionKey');
|
||||
const crypto = cryptoFactory(encryptionKey);
|
||||
return await crypto.encrypt(this.req.headers);
|
||||
}
|
||||
|
||||
public async enqueueJob(exportTypeId: string, jobParams: BaseParams) {
|
||||
const { reporting, logger, context, req: request, user } = this;
|
||||
|
||||
const exportType = reporting.getExportTypesRegistry().getById(exportTypeId);
|
||||
|
||||
if (exportType == null) {
|
||||
throw new Error(`Export type ${exportTypeId} does not exist in the registry!`);
|
||||
}
|
||||
|
||||
if (!exportType.createJobFnFactory) {
|
||||
throw new Error(`Export type ${exportTypeId} is not an async job type!`);
|
||||
}
|
||||
|
||||
const [createJob, store] = await Promise.all([
|
||||
exportType.createJobFnFactory(reporting, logger.clone([exportType.id])),
|
||||
reporting.getStore(),
|
||||
]);
|
||||
|
||||
if (!createJob) {
|
||||
throw new Error(`Export type ${exportTypeId} is not an async job type!`);
|
||||
}
|
||||
|
||||
// 1. ensure the incoming params have a version field
|
||||
jobParams.version = checkParamsVersion(jobParams, logger);
|
||||
|
||||
// 2. encrypt request headers for the running report job to authenticate itself with Kibana
|
||||
// 3. call the export type's createJobFn to create the job payload
|
||||
const [headers, job] = await Promise.all([
|
||||
this.encryptHeaders(),
|
||||
createJob(jobParams, context),
|
||||
]);
|
||||
|
||||
const payload = {
|
||||
...job,
|
||||
headers,
|
||||
spaceId: reporting.getSpaceId(request, logger),
|
||||
};
|
||||
|
||||
// 4. Add the report to ReportingStore to show as pending
|
||||
const report = await store.addReport(
|
||||
new Report({
|
||||
jobtype: exportType.jobType,
|
||||
created_by: user ? user.username : false,
|
||||
payload,
|
||||
meta: {
|
||||
// telemetry fields
|
||||
objectType: jobParams.objectType,
|
||||
layout: jobParams.layout?.id,
|
||||
isDeprecated: job.isDeprecated,
|
||||
},
|
||||
})
|
||||
);
|
||||
logger.debug(`Successfully stored pending job: ${report._index}/${report._id}`);
|
||||
|
||||
// 5. Schedule the report with Task Manager
|
||||
const task = await reporting.scheduleTask(report.toReportTaskJSON());
|
||||
logger.info(
|
||||
`Scheduled ${exportType.name} reporting task. Task ID: task:${task.id}. Report ID: ${report._id}`
|
||||
);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
public async handleGenerateRequest(
|
||||
exportTypeId: string,
|
||||
jobParams: BaseParams | JobParamsPDFLegacy
|
||||
|
@ -54,15 +123,7 @@ export class RequestHandler {
|
|||
}
|
||||
|
||||
try {
|
||||
const report = await enqueueJob(
|
||||
this.reporting,
|
||||
this.req,
|
||||
this.context,
|
||||
this.user,
|
||||
exportTypeId,
|
||||
jobParams,
|
||||
this.logger
|
||||
);
|
||||
const report = await this.enqueueJob(exportTypeId, jobParams);
|
||||
|
||||
// return task manager's task information and the download URL
|
||||
const downloadBaseUrl = getDownloadBaseUrl(this.reporting);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { IRouter, KibanaRequest, RequestHandlerContext } from 'src/core/server';
|
||||
import type { IRouter, RequestHandlerContext } from 'src/core/server';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { DataPluginStart } from 'src/plugins/data/server/plugin';
|
||||
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
|
||||
|
@ -62,9 +62,8 @@ export { BaseParams, BasePayload };
|
|||
// default fn type for CreateJobFnFactory
|
||||
export type CreateJobFn<JobParamsType = BaseParams, JobPayloadType = BasePayload> = (
|
||||
jobParams: JobParamsType,
|
||||
context: ReportingRequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
) => Promise<JobPayloadType>;
|
||||
context: ReportingRequestHandlerContext
|
||||
) => Promise<Omit<JobPayloadType, 'headers' | 'spaceId'>>;
|
||||
|
||||
// default fn type for RunTaskFnFactory
|
||||
export type RunTaskFn<TaskPayloadType = BasePayload> = (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue