diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts index 0fa65c191a3db..8833453efecec 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types.ts @@ -62,8 +62,6 @@ export interface BaseParams { layout?: LayoutParams; objectType: string; title: string; - savedObjectId?: string; // legacy (7.x) only - queryString?: string; // legacy (7.x) only browserTimezone: string; // to format dates in the user's time zone version: string; // to handle any state migrations } 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 2acdfeb071cf1..a69de1034f5a4 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 @@ -68,11 +68,7 @@ test(`fails if URLs are absolute for PDF`, async () => { getFullUrls( mockConfig, getMockJob({ - objects: [ - { - relativeUrl, - }, - ], + objects: [{ relativeUrl }], forceNow, }) ); @@ -107,7 +103,7 @@ 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( + const urls = getFullUrls( mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something', forceNow }) ); @@ -120,7 +116,7 @@ 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( + const urls = getFullUrls( mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something?_g=something', forceNow }) ); @@ -131,7 +127,7 @@ 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(mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something' })); + const urls = getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something' })); expect(urls[0]).toEqual('http://localhost:5601/sbp/app/kibana#/something'); }); 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 61a0bdfa9e659..5ae4092a466fa 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 @@ -21,7 +21,7 @@ function isPngJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPNG { return (job as TaskPayloadPNG).relativeUrl !== undefined; } function isPdfJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPDF { - return (job as TaskPayloadPDF).objects !== undefined; // 7.x only + return (job as TaskPayloadPDF).objects !== undefined; } export function getFullUrls(config: ReportingConfig, job: TaskPayloadPDF | TaskPayloadPNG) { @@ -49,7 +49,7 @@ export function getFullUrls(config: ReportingConfig, job: TaskPayloadPDF | TaskP validateUrls(relativeUrls); const urls = relativeUrls.map((relativeUrl) => { - const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); + const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); // FIXME: '(urlStr: string): UrlWithStringQuery' is deprecated const jobUrl = getAbsoluteUrl({ path: parsedRelative.pathname === null ? undefined : parsedRelative.pathname, hash: parsedRelative.hash === null ? undefined : parsedRelative.hash, diff --git a/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts b/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts index 22e0fc7de8455..e6d392c0bb55c 100644 --- a/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts @@ -22,6 +22,15 @@ const isBogusUrl = (url: string) => { }; export const validateUrls = (urls: string[]): void => { + if (!Array.isArray(urls)) { + throw new Error('Invalid relativeUrls. String[] is expected.'); + } + urls.forEach((url) => { + if (typeof url !== 'string') { + throw new Error('Invalid Relative URL in relativeUrls. String is expected.'); + } + }); + const badUrls = filter(urls, (url) => isBogusUrl(url)); if (badUrls.length) { diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.js b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.js deleted file mode 100644 index 94251201cc301..0000000000000 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.js +++ /dev/null @@ -1,127 +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 { url } from '../../../../../../../src/plugins/kibana_utils/server'; - -/* - * TODO: Kibana 8.0: - * Remove support for parsing Saved Object details from objectType / savedObjectId - * Including support for determining the Report title from objectType / savedObjectId - * - * - `objectType` is optional, but helps differentiate the type of report in the job listing - * - `title` must be explicitly passed - * - `relativeUrls` array OR `relativeUrl` string must be passed - */ - -const getSavedObjectTitle = async (objectType, savedObjectId, savedObjectsClient) => { - const savedObject = await savedObjectsClient.get(objectType, savedObjectId); - return savedObject.attributes.title; -}; - -const getSavedObjectRelativeUrl = (objectType, savedObjectId, queryString) => { - const appPrefixes = { - dashboard: '/dashboard/', - visualization: '/visualize/edit/', - search: '/discover/', - }; - - const appPrefix = appPrefixes[objectType]; - if (!appPrefix) throw new Error('Unexpected app type: ' + objectType); - - const hash = appPrefix + url.encodeUriQuery(savedObjectId, true); - - return `/app/kibana#${hash}?${queryString || ''}`; -}; - -export function compatibilityShimFactory(logger) { - return function (createJobFn) { - return async function ( - { - savedObjectId, // deprecating - queryString, // deprecating - browserTimezone, - objectType, - title, - relativeUrls, - layout, - }, - context, - req - ) { - // input validation and deprecation logging - if (savedObjectId) { - if (typeof savedObjectId !== 'string') { - throw new Error('Invalid savedObjectId (deprecated). String is expected.'); - } - if (relativeUrls) { - throw new Error(`savedObjectId should not be provided if relativeUrls are provided`); - } - } else { - if (!relativeUrls) { - throw new Error(`Either relativeUrls or savedObjectId must be provided`); - } - if (!Array.isArray(relativeUrls)) { - throw new Error('Invalid relativeUrls. String[] is expected.'); - } - relativeUrls.forEach((url) => { - if (typeof url !== 'string') { - throw new Error('Invalid Relative URL in relativeUrls. String is expected.'); - } - }); - } - - let kibanaRelativeUrls; - if (relativeUrls) { - kibanaRelativeUrls = relativeUrls; - } else { - kibanaRelativeUrls = [getSavedObjectRelativeUrl(objectType, savedObjectId, queryString)]; - logger.warn( - `The relativeUrls have been derived from saved object parameters. ` + - `This functionality will be removed with the next major version.` - ); - } - - let reportTitle; - try { - if (title) { - reportTitle = title; - } else { - if (objectType && savedObjectId) { - reportTitle = await getSavedObjectTitle( - objectType, - savedObjectId, - context.core.savedObjects.client - ); - logger.warn( - `The title has been derived from saved object parameters. This ` + - `functionality will be removed with the next major version.` - ); - } else { - logger.warn( - `A title parameter should be provided with the job generation ` + - `request. Please use Kibana to regenerate your POST URL to have a ` + - `title included in the PDF.` - ); - } - } - } catch (err) { - logger.error(err); // 404 for the savedObjectId, etc - throw err; - } - - const transformedJobParams = { - objectType, - title: reportTitle, - relativeUrls: kibanaRelativeUrls, - browserTimezone, - layout, - }; - - return await createJobFn(transformedJobParams, context, req); - }; - }; -} diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.js b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts similarity index 61% rename from x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.js rename to x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts index 20fcb44415fbb..4e366f506af2d 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.js +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts @@ -5,35 +5,39 @@ * 2.0. */ -import { compatibilityShimFactory } from './compatibility_shim'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { createMockLevelLogger } from '../../../test_helpers'; +import { compatibilityShim } from './compatibility_shim'; -const createMockLogger = () => ({ - warn: jest.fn(), - error: jest.fn(), +const mockRequestHandlerContext = { + core: coreMock.createRequestHandlerContext(), + reporting: { usesUiCapabilities: () => true }, +}; +const mockLogger = createMockLevelLogger(); + +const mockKibanaRequest = httpServerMock.createKibanaRequest(); +const createMockSavedObject = (body: any) => ({ + id: 'mockSavedObjectId123', + type: 'mockSavedObjectType', + references: [], + ...body, +}); +const createMockJobParams = (body: any) => ({ + ...body, }); -const createMockContext = () => { - return { - core: { - savedObjects: { - client: { - get: jest.fn(), - }, - }, - }, - }; -}; +beforeEach(() => { + jest.clearAllMocks(); +}); test(`passes title through if provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); const title = 'test title'; const createJobMock = jest.fn(); - await compatibilityShim(createJobMock)( - { title, relativeUrls: ['/something'] }, - createMockContext(), - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title, relativeUrls: ['/something'] }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(0); @@ -44,22 +48,18 @@ test(`passes title through if provided`, async () => { }); test(`gets the title from the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const context = createMockContext(); const title = 'savedTitle'; - context.core.savedObjects.client.get.mockReturnValue({ - attributes: { - title, - }, - }); + mockRequestHandlerContext.core.savedObjects.client.get.mockResolvedValue( + createMockSavedObject({ + attributes: { title }, + }) + ); - await compatibilityShim(createJobMock)( - { objectType: 'search', savedObjectId: 'abc' }, - context, - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(2); @@ -73,20 +73,19 @@ test(`gets the title from the savedObject`, async () => { }); test(`passes the objectType and savedObjectId to the savedObjectsClient`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const context = createMockContext(); - context.core.savedObjects.client.get.mockReturnValue({ - attributes: { - title: '', - }, - }); + const context = mockRequestHandlerContext; + context.core.savedObjects.client.get.mockResolvedValue( + createMockSavedObject({ attributes: { title: '' } }) + ); const objectType = 'search'; const savedObjectId = 'abc'; - await compatibilityShim(createJobMock)({ objectType, savedObjectId }, context, null); + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ objectType, savedObjectId }), + context, + mockKibanaRequest + ); expect(mockLogger.warn.mock.calls.length).toBe(2); expect(mockLogger.warn.mock.calls[0][0]).toEqual( @@ -104,16 +103,12 @@ test(`passes the objectType and savedObjectId to the savedObjectsClient`, async }); test(`logs no warnings when title and relativeUrls is passed`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const context = createMockContext(); - await compatibilityShim(createJobMock)( - { title: 'Phenomenal Dashboard', relativeUrls: ['/abc', '/def'] }, - context, - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'Phenomenal Dashboard', relativeUrls: ['/abc', '/def'] }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(0); @@ -121,12 +116,12 @@ test(`logs no warnings when title and relativeUrls is passed`, async () => { }); test(`logs warning if title can not be provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const context = createMockContext(); - await compatibilityShim(createJobMock)({ relativeUrls: ['/abc'] }, context, null); + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ relativeUrls: ['/abc'] }), + mockRequestHandlerContext, + mockKibanaRequest + ); expect(mockLogger.warn.mock.calls.length).toBe(1); expect(mockLogger.warn.mock.calls[0][0]).toEqual( @@ -136,21 +131,17 @@ test(`logs warning if title can not be provided`, async () => { }); test(`logs deprecations when generating the title/relativeUrl using the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const context = createMockContext(); - context.core.savedObjects.client.get.mockReturnValue({ - attributes: { - title: '', - }, - }); + mockRequestHandlerContext.core.savedObjects.client.get.mockResolvedValue( + createMockSavedObject({ + attributes: { title: '' }, + }) + ); - await compatibilityShim(createJobMock)( - { objectType: 'search', savedObjectId: 'abc' }, - context, - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(2); @@ -163,17 +154,13 @@ test(`logs deprecations when generating the title/relativeUrl using the savedObj }); test(`passes objectType through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const context = createMockContext(); const objectType = 'foo'; - await compatibilityShim(createJobMock)( - { title: 'test', relativeUrls: ['/something'], objectType }, - context, - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', relativeUrls: ['/something'], objectType }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(0); @@ -184,13 +171,14 @@ test(`passes objectType through`, async () => { }); test(`passes the relativeUrls through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); const relativeUrls = ['/app/kibana#something', '/app/kibana#something-else']; - await compatibilityShim(createJobMock)({ title: 'test', relativeUrls }, null, null); + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', relativeUrls }), + mockRequestHandlerContext, + mockKibanaRequest + ); expect(mockLogger.warn.mock.calls.length).toBe(0); expect(mockLogger.error.mock.calls.length).toBe(0); @@ -199,16 +187,14 @@ test(`passes the relativeUrls through`, async () => { expect(createJobMock.mock.calls[0][0].relativeUrls).toBe(relativeUrls); }); -const testSavedObjectRelativeUrl = (objectType, expectedUrl) => { +const testSavedObjectRelativeUrl = (objectType: string, expectedUrl: string) => { test(`generates the saved object relativeUrl for ${objectType}`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); const createJobMock = jest.fn(); - await compatibilityShim(createJobMock)( - { title: 'test', objectType, savedObjectId: 'abc' }, - null, - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', objectType, savedObjectId: 'abc' }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(1); @@ -227,14 +213,17 @@ testSavedObjectRelativeUrl('visualization', '/app/kibana#/visualize/edit/abc?'); testSavedObjectRelativeUrl('dashboard', '/app/kibana#/dashboard/abc?'); test(`appends the queryString to the relativeUrl when generating from the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); const createJobMock = jest.fn(); - await compatibilityShim(createJobMock)( - { title: 'test', objectType: 'search', savedObjectId: 'abc', queryString: 'foo=bar' }, - null, - null + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ + title: 'test', + objectType: 'search', + savedObjectId: 'abc', + queryString: 'foo=bar', + }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(1); @@ -250,43 +239,35 @@ test(`appends the queryString to the relativeUrl when generating from the savedO }); test(`throw an Error if the objectType, savedObjectId and relativeUrls are provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); const createJobMock = jest.fn(); - const promise = compatibilityShim(createJobMock)( - { + const promise = compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', objectType: 'something', relativeUrls: ['/something'], savedObjectId: 'abc', - }, - null, - null + }), + mockRequestHandlerContext, + mockKibanaRequest ); await expect(promise).rejects.toBeDefined(); }); test(`passes headers and request through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(mockLogger); - const createJobMock = jest.fn(); - const req = {}; - const context = createMockContext(); - - await compatibilityShim(createJobMock)( - { title: 'test', relativeUrls: ['/something'] }, - context, - req + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', relativeUrls: ['/something'] }), + mockRequestHandlerContext, + mockKibanaRequest ); expect(mockLogger.warn.mock.calls.length).toBe(0); expect(mockLogger.error.mock.calls.length).toBe(0); expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][1]).toBe(context); - expect(createJobMock.mock.calls[0][2]).toBe(req); + expect(createJobMock.mock.calls[0][1]).toBe(mockRequestHandlerContext); + expect(createJobMock.mock.calls[0][2]).toBe(mockKibanaRequest); }); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts new file mode 100644 index 0000000000000..f806b8a7e5bca --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts @@ -0,0 +1,130 @@ +/* + * 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 type { KibanaRequest } from 'kibana/server'; +import { url as urlUtils } from '../../../../../../../src/plugins/kibana_utils/server'; +import type { LevelLogger } from '../../../lib'; +import type { CreateJobFn, ReportingRequestHandlerContext } from '../../../types'; +import type { JobParamsPDF, JobParamsPDFLegacy, TaskPayloadPDF } from '../types'; + +function isLegacyJob( + jobParams: JobParamsPDF | JobParamsPDFLegacy +): jobParams is JobParamsPDFLegacy { + return (jobParams as JobParamsPDFLegacy).savedObjectId != null; +} + +const getSavedObjectTitle = async ( + objectType: string, + savedObjectId: string, + savedObjectsClient: any +) => { + const savedObject = await savedObjectsClient.get(objectType, savedObjectId); + return savedObject.attributes.title; +}; + +const getSavedObjectRelativeUrl = ( + objectType: string, + savedObjectId: string, + queryString: string +) => { + const appPrefixes: Record = { + dashboard: '/dashboard/', + visualization: '/visualize/edit/', + search: '/discover/', + }; + + const appPrefix = appPrefixes[objectType]; + if (!appPrefix) throw new Error('Unexpected app type: ' + objectType); + + const hash = appPrefix + urlUtils.encodeUriQuery(savedObjectId, true); + + return `/app/kibana#${hash}?${queryString || ''}`; +}; + +/* + * The compatibility shim is responsible for migrating an older shape of the + * PDF Job Params into a newer shape, by deriving a report title and relative + * URL from a savedObjectId and queryString. + */ +export function compatibilityShim( + createJobFn: CreateJobFn, + logger: LevelLogger +) { + return async function ( + jobParams: JobParamsPDF | JobParamsPDFLegacy, + context: ReportingRequestHandlerContext, + req: KibanaRequest + ) { + let kibanaRelativeUrls = (jobParams as JobParamsPDF).relativeUrls; + let reportTitle = jobParams.title; + let isDeprecated = false; + + if ( + (jobParams as JobParamsPDFLegacy).savedObjectId && + (jobParams as JobParamsPDF).relativeUrls + ) { + throw new Error(`savedObjectId should not be provided if relativeUrls are provided`); + } + + if (isLegacyJob(jobParams)) { + const { savedObjectId, objectType, queryString } = jobParams; + + // input validation and deprecation logging + if (typeof savedObjectId !== 'string') { + throw new Error('Invalid savedObjectId (deprecated). String is expected.'); + } + if (typeof objectType !== 'string') { + throw new Error('Invalid objectType (deprecated). String is expected.'); + } + + // legacy parameters need to be converted into a relative URL + kibanaRelativeUrls = [getSavedObjectRelativeUrl(objectType, savedObjectId, queryString)]; + logger.warn( + `The relativeUrls have been derived from saved object parameters. ` + + `This functionality will be removed with the next major version.` + ); + + // legacy parameters might need to get the title from the saved object + if (!reportTitle) { + try { + reportTitle = await getSavedObjectTitle( + objectType, + savedObjectId, + context.core.savedObjects.client + ); + logger.warn( + `The title has been derived from saved object parameters. This ` + + `functionality will be removed with the next major version.` + ); + } catch (err) { + logger.error(err); // 404 for the savedObjectId, etc + throw err; + } + } + + isDeprecated = true; + } + + if (typeof reportTitle !== 'string') { + logger.warn( + `A title parameter should be provided with the job generation ` + + `request. Please use Kibana to regenerate your POST URL to have a ` + + `title included in the PDF.` + ); + reportTitle = ''; + } + + const transformedJobParams: JobParamsPDF = { + ...jobParams, + title: reportTitle, + relativeUrls: kibanaRelativeUrls, + isDeprecated, // tack on this flag so it will be saved the TaskPayload + }; + + return await createJobFn(transformedJobParams, context, req); + }; +} 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 29b9c6b91f02a..011d0f237d3e6 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,38 +5,40 @@ * 2.0. */ -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { cryptoFactory } from '../../../lib'; -import { CreateJobFn, CreateJobFnFactory } from '../../../types'; +import { CreateJobFn, CreateJobFnFactory, ReportingRequestHandlerContext } from '../../../types'; import { validateUrls } from '../../common'; -import { JobParamsPDF, TaskPayloadPDF } from '../types'; -// @ts-ignore no module def (deprecated module) -import { compatibilityShimFactory } from './compatibility_shim'; +import { JobParamsPDF, JobParamsPDFLegacy, TaskPayloadPDF } from '../types'; +import { compatibilityShim } from './compatibility_shim'; +/* + * Incoming job params can be `JobParamsPDF` or `JobParamsPDFLegacy` depending + * on the version that the POST URL was copied from. + */ export const createJobFnFactory: CreateJobFnFactory< - CreateJobFn + CreateJobFn > = function createJobFactoryFn(reporting, logger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const compatibilityShim = compatibilityShimFactory(logger); - // compatibilityShim 7.x and below only return compatibilityShim(async function createJobFn( - { relativeUrls, ...jobParams }: JobParamsPDF, - context: RequestHandlerContext, + { relativeUrls, ...jobParams }: JobParamsPDF, // relativeUrls does not belong in the payload + _context: ReportingRequestHandlerContext, req: KibanaRequest ) { - const serializedEncryptedHeaders = await crypto.encrypt(req.headers); - validateUrls(relativeUrls); + const serializedEncryptedHeaders = await crypto.encrypt(req.headers); + + // return the payload return { + ...jobParams, isDeprecated: true, headers: serializedEncryptedHeaders, spaceId: reporting.getSpaceId(req, logger), forceNow: new Date().toISOString(), - objects: relativeUrls.map((u) => ({ relativeUrl: u })), // 7.x only: `objects` in the payload - ...jobParams, + objects: relativeUrls.map((u) => ({ relativeUrl: u })), }; - }); + }, logger); }; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts index 8679b1c0905f4..98c00287aa196 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts @@ -7,8 +7,8 @@ jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() })); -import { Writable } from 'stream'; import * as Rx from 'rxjs'; +import { Writable } from 'stream'; import { ReportingCore } from '../../../'; import { CancellationToken } from '../../../../common'; import { cryptoFactory, LevelLogger } from '../../../lib'; @@ -95,7 +95,7 @@ test(`returns content_type of application/pdf`, async () => { const { content_type: contentType } = await runTask( 'pdfJobId', - getBasePayload({ objects: [], timeRange: {}, headers: encryptedHeaders }), // 7.x and below only + getBasePayload({ objects: [], headers: encryptedHeaders }), cancellationToken, stream ); @@ -111,7 +111,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const encryptedHeaders = await encryptHeaders({}); await runTask( 'pdfJobId', - getBasePayload({ objects: [], timeRange: {}, headers: encryptedHeaders }), // 7.x and below only + getBasePayload({ objects: [], headers: encryptedHeaders }), cancellationToken, stream ); 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 1508f23ada6a1..8e4c45ad79506 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 @@ -10,9 +10,8 @@ import { BaseParams, BasePayload } from '../../types'; interface BaseParamsPDF { layout: LayoutParams; - forceNow?: string; - // TODO: Add comment explaining this field relativeUrls: string[]; + isDeprecated?: boolean; } // Job params: structure of incoming user request data, after being parsed from RISON @@ -22,7 +21,11 @@ export type JobParamsPDF = BaseParamsPDF & BaseParams; export interface TaskPayloadPDF extends BasePayload { layout: LayoutParams; forceNow?: string; - objects: Array<{ - relativeUrl: string; - }>; + objects: Array<{ relativeUrl: string }>; +} + +type Legacy = Omit; +export interface JobParamsPDFLegacy extends Legacy { + savedObjectId: string; + queryString: string; } diff --git a/x-pack/plugins/reporting/server/routes/generation.ts b/x-pack/plugins/reporting/server/routes/generation.ts index 00f84c6efc1eb..adbfbda727af2 100644 --- a/x-pack/plugins/reporting/server/routes/generation.ts +++ b/x-pack/plugins/reporting/server/routes/generation.ts @@ -11,9 +11,9 @@ import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; import { LevelLogger as Logger } from '../lib'; import { enqueueJob } from '../lib/enqueue_job'; +import { registerGenerateCsvFromSavedObjectImmediate } from './csv_searchsource_immediate'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerLegacy } from './legacy'; -import { registerGenerateCsvFromSavedObjectImmediate } from './csv_searchsource_immediate'; import { HandlerFunction } from './types'; const getDownloadBaseUrl = (reporting: ReportingCore) => { @@ -87,6 +87,6 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo } registerGenerateFromJobParams(reporting, handler, handleError); - registerLegacy(reporting, handler, handleError, logger); // 7.x only registerGenerateCsvFromSavedObjectImmediate(reporting, handleError, logger); + registerLegacy(reporting, handler, handleError, logger); } diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index 2e8e94eaf265a..336605e6ff9b9 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -8,15 +8,16 @@ import { KibanaRequest, KibanaResponseFactory } from 'src/core/server'; import type { BaseParams, + BaseParamsLegacyPDF, BasePayload, - ReportingUser, ReportingRequestHandlerContext, + ReportingUser, } from '../types'; export type HandlerFunction = ( user: ReportingUser, exportType: string, - jobParams: BaseParams, + jobParams: BaseParams | BaseParamsLegacyPDF, context: ReportingRequestHandlerContext, req: KibanaRequest, res: KibanaResponseFactory