diff --git a/packages/kbn-reporting/export_types/pdf/generate_pdf.ts b/packages/kbn-reporting/export_types/pdf/generate_pdf.ts deleted file mode 100644 index 5703e16e48abc..0000000000000 --- a/packages/kbn-reporting/export_types/pdf/generate_pdf.ts +++ /dev/null @@ -1,58 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Observable } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; - -import type { PdfScreenshotOptions, PdfScreenshotResult } from '@kbn/screenshotting-plugin/server'; -import { getTracker } from './pdf_tracker'; - -interface PdfResult { - buffer: Uint8Array | null; - metrics?: PdfScreenshotResult['metrics']; - warnings: string[]; -} - -type GetScreenshotsFn = (options: PdfScreenshotOptions) => Observable; - -export function generatePdfObservable( - getScreenshots: GetScreenshotsFn, - options: PdfScreenshotOptions -): Observable { - const tracker = getTracker(); - tracker.startScreenshots(); - - return getScreenshots(options).pipe( - tap(({ metrics }) => { - if (metrics.cpu) { - tracker.setCpuUsage(metrics.cpu); - } - if (metrics.memory) { - tracker.setMemoryUsage(metrics.memory); - } - }), - mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { - tracker.endScreenshots(); - const warnings: string[] = []; - if (errors) { - warnings.push(...errors.map((error) => error.message)); - } - if (renderErrors) { - warnings.push(...renderErrors); - } - - tracker.end(); - - return { - buffer, - metrics, - warnings, - }; - }) - ); -} diff --git a/packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts b/packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts deleted file mode 100644 index 54489c6258bd8..0000000000000 --- a/packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts +++ /dev/null @@ -1,75 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Observable } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; - -import type { LocatorParams, ReportingServerInfo } from '@kbn/reporting-common/types'; -import type { TaskPayloadPDFV2 } from '@kbn/reporting-export-types-pdf-common'; -import type { PdfScreenshotOptions, PdfScreenshotResult } from '@kbn/screenshotting-plugin/server'; -import type { UrlOrUrlWithContext } from '@kbn/screenshotting-plugin/server/screenshots'; -import { type ReportingConfigType, getFullRedirectAppUrl } from '@kbn/reporting-server'; - -import { getTracker } from './pdf_tracker'; - -interface PdfResult { - buffer: Uint8Array | null; - metrics?: PdfScreenshotResult['metrics']; - warnings: string[]; -} - -type GetScreenshotsFn = (options: PdfScreenshotOptions) => Observable; - -export function generatePdfObservableV2( - config: ReportingConfigType, - serverInfo: ReportingServerInfo, - getScreenshots: GetScreenshotsFn, - job: TaskPayloadPDFV2, - locatorParams: LocatorParams[], - options: Omit -): Observable { - const tracker = getTracker(); - tracker.startScreenshots(); - - /** - * For each locator we get the relative URL to the redirect app - */ - const urls = locatorParams.map((locator) => [ - getFullRedirectAppUrl(config, serverInfo, job.spaceId, job.forceNow), - locator, - ]) as unknown as UrlOrUrlWithContext[]; - - const screenshots$ = getScreenshots({ ...options, urls }).pipe( - tap(({ metrics }) => { - if (metrics.cpu) { - tracker.setCpuUsage(metrics.cpu); - } - if (metrics.memory) { - tracker.setMemoryUsage(metrics.memory); - } - }), - mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { - tracker.endScreenshots(); - const warnings: string[] = []; - if (errors) { - warnings.push(...errors.map((error) => error.message)); - } - if (renderErrors) { - warnings.push(...renderErrors); - } - - return { - buffer, - metrics, - warnings, - }; - }) - ); - - return screenshots$; -} diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts index 9ca0fa34effcd..10c21ede18227 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; +import * as Rx from 'rxjs'; import { Writable } from 'stream'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; @@ -14,12 +14,8 @@ import { CancellationToken } from '@kbn/reporting-common'; import { TaskPayloadPDF } from '@kbn/reporting-export-types-pdf-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; import { cryptoFactory } from '@kbn/reporting-server'; -import { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; - +import { createMockScreenshottingStart } from '@kbn/screenshotting-plugin/server/mock'; import { PdfV1ExportType } from '.'; -import { generatePdfObservable } from './generate_pdf'; - -jest.mock('./generate_pdf'); let content: string; let mockPdfExportType: PdfV1ExportType; @@ -34,6 +30,9 @@ const encryptHeaders = async (headers: Record) => { return await crypto.encrypt(headers); }; +const screenshottingMock = createMockScreenshottingStart(); +const getScreenshotsSpy = jest.spyOn(screenshottingMock, 'getScreenshots'); +const testContent = 'raw string from get_screenhots'; const getBasePayload = (baseObj: any) => baseObj as TaskPayloadPDF; beforeEach(async () => { @@ -54,15 +53,20 @@ beforeEach(async () => { esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: mockCoreStart.savedObjects, uiSettings: mockCoreStart.uiSettings, - screenshotting: {} as unknown as ScreenshottingStart, + screenshotting: screenshottingMock, + }); + getScreenshotsSpy.mockImplementation(() => { + return Rx.of({ + metrics: { cpu: 0, pages: 1 }, + data: Buffer.from(testContent), + errors: [], + renderErrors: [], + }); }); }); -afterEach(() => (generatePdfObservable as jest.Mock).mockReset()); - test(`passes browserTimezone to generatePdf`, async () => { const encryptedHeaders = await encryptHeaders({}); - (generatePdfObservable as jest.Mock).mockReturnValue(of({ buffer: Buffer.from('') })); const browserTimezone = 'UTC'; await mockPdfExportType.runTask( @@ -76,17 +80,20 @@ test(`passes browserTimezone to generatePdf`, async () => { stream ); - expect(generatePdfObservable).toHaveBeenCalledWith( - expect.anything(), - expect.objectContaining({ browserTimezone: 'UTC' }) - ); + expect(getScreenshotsSpy).toHaveBeenCalledWith({ + browserTimezone: 'UTC', + format: 'pdf', + headers: {}, + layout: undefined, + logo: false, + title: undefined, + urls: ['http://localhost:80/mock-server-basepath/app/kibana#/something'], + }); }); test(`returns content_type of application/pdf`, async () => { const encryptedHeaders = await encryptHeaders({}); - (generatePdfObservable as jest.Mock).mockReturnValue(of({ buffer: Buffer.from('') })); - const { content_type: contentType } = await mockPdfExportType.runTask( 'pdfJobId', getBasePayload({ objects: [], headers: encryptedHeaders }), @@ -97,9 +104,6 @@ test(`returns content_type of application/pdf`, async () => { }); test(`returns content of generatePdf getBuffer base64 encoded`, async () => { - const testContent = 'test content'; - (generatePdfObservable as jest.Mock).mockReturnValue(of({ buffer: Buffer.from(testContent) })); - const encryptedHeaders = await encryptHeaders({}); await mockPdfExportType.runTask( 'pdfJobId', diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf.ts index 6c8ce2a5b1d0d..6aaf0e5c491e2 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf.ts @@ -29,10 +29,10 @@ import { } from '@kbn/reporting-export-types-pdf-common'; import { ExportType, decryptJobHeaders } from '@kbn/reporting-server'; -import { generatePdfObservable } from './generate_pdf'; -import { validateUrls } from './validate_urls'; import { getCustomLogo } from './get_custom_logo'; import { getFullUrls } from './get_full_urls'; +import { getTracker } from './pdf_tracker'; +import { validateUrls } from './validate_urls'; /** * @deprecated @@ -76,15 +76,15 @@ export class PdfV1ExportType extends ExportType { - const jobLogger = this.logger.get(`execute-job:${jobId}`); + const logger = this.logger.get(`execute-job:${jobId}`); const apmTrans = apm.startTransaction('execute-job-pdf', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans.startSpan('get-assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; const process$: Observable = of(1).pipe( - mergeMap(() => decryptJobHeaders(this.config.encryptionKey, job.headers, jobLogger)), + mergeMap(() => decryptJobHeaders(this.config.encryptionKey, job.headers, logger)), mergeMap(async (headers) => { - const fakeRequest = this.getFakeRequest(headers, job.spaceId, jobLogger); + const fakeRequest = this.getFakeRequest(headers, job.spaceId, logger); const uiSettingsClient = await this.getUiSettingsClient(fakeRequest); return getCustomLogo(uiSettingsClient, headers); }), @@ -96,18 +96,11 @@ export class PdfV1ExportType extends ExportType - this.startDeps.screenshotting!.getScreenshots({ - format: 'pdf', - title, - logo, - urls, - browserTimezone, - headers, - layout, - }), - { + const tracker = getTracker(); + tracker.startScreenshots(); + + return this.startDeps + .screenshotting!.getScreenshots({ format: 'pdf', title, logo, @@ -115,8 +108,35 @@ export class PdfV1ExportType extends ExportType { + if (metrics.cpu) { + tracker.setCpuUsage(metrics.cpu); + } + if (metrics.memory) { + tracker.setMemoryUsage(metrics.memory); + } + }), + mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { + tracker.endScreenshots(); + const warnings: string[] = []; + if (errors) { + warnings.push(...errors.map((error) => error.message)); + } + if (renderErrors) { + warnings.push(...renderErrors); + } + + tracker.end(); + + return { + buffer, + metrics, + warnings, + }; + }) + ); }), tap(({ buffer }) => { apmGeneratePdf?.end(); @@ -130,7 +150,7 @@ export class PdfV1ExportType extends ExportType { - jobLogger.error(err); + logger.error(err); return throwError(err); }) ); diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts index 4e4f0c1491130..28d28ade31e97 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; -import type { Writable } from 'stream'; +import * as Rx from 'rxjs'; +import { Writable } from 'stream'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { CancellationToken } from '@kbn/reporting-common'; @@ -15,12 +15,8 @@ import type { LocatorParams } from '@kbn/reporting-common/types'; import type { TaskPayloadPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; import { cryptoFactory } from '@kbn/reporting-server'; -import type { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; - +import { createMockScreenshottingStart } from '@kbn/screenshotting-plugin/server/mock'; import { PdfExportType } from '.'; -import { generatePdfObservableV2 } from './generate_pdf_v2'; - -jest.mock('./generate_pdf_v2'); let content: string; let mockPdfExportType: PdfExportType; @@ -34,7 +30,11 @@ const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); return await crypto.encrypt(headers); }; +let encryptedHeaders: string; +const screenshottingMock = createMockScreenshottingStart(); +const getScreenshotsSpy = jest.spyOn(screenshottingMock, 'getScreenshots'); +const testContent = 'raw string from get_screenhots'; const getBasePayload = (baseObj: any) => ({ params: { forceNow: 'test' }, @@ -50,8 +50,10 @@ beforeEach(async () => { const mockCoreSetup = coreMock.createSetup(); const mockCoreStart = coreMock.createStart(); - mockPdfExportType = new PdfExportType(mockCoreSetup, configType, mockLogger, context); + encryptedHeaders = await encryptHeaders({}); + + mockPdfExportType = new PdfExportType(mockCoreSetup, configType, mockLogger, context); mockPdfExportType.setup({ basePath: { set: jest.fn() }, }); @@ -59,21 +61,26 @@ beforeEach(async () => { esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: mockCoreStart.savedObjects, uiSettings: mockCoreStart.uiSettings, - screenshotting: {} as unknown as ScreenshottingStart, + screenshotting: screenshottingMock, }); -}); -afterEach(() => (generatePdfObservableV2 as jest.Mock).mockReset()); + getScreenshotsSpy.mockImplementation(() => { + return Rx.of({ + metrics: { cpu: 0, pages: 1 }, + data: Buffer.from(testContent), + errors: [], + renderErrors: [], + }); + }); +}); test(`passes browserTimezone to generatePdf`, async () => { - const encryptedHeaders = await encryptHeaders({}); - (generatePdfObservableV2 as jest.Mock).mockReturnValue(of(Buffer.from(''))); - const browserTimezone = 'UTC'; await mockPdfExportType.runTask( 'pdfJobId', getBasePayload({ forceNow: 'test', + layout: { dimensions: {} }, title: 'PDF Params Timezone Test', locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], browserTimezone, @@ -83,24 +90,30 @@ test(`passes browserTimezone to generatePdf`, async () => { stream ); - expect(generatePdfObservableV2).toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - expect.objectContaining({ browserTimezone: 'UTC' }) - ); + expect(getScreenshotsSpy).toHaveBeenCalledWith({ + browserTimezone: 'UTC', + format: 'pdf', + headers: {}, + layout: { dimensions: {} }, + logo: false, + title: 'PDF Params Timezone Test', + urls: [ + [ + 'http://localhost:80/mock-server-basepath/app/reportingRedirect?forceNow=test', + { __REPORTING_REDIRECT_LOCATOR_STORE_KEY__: { id: 'test', version: 'test' } }, + ], + ], + }); }); test(`returns content_type of application/pdf`, async () => { - const encryptedHeaders = await encryptHeaders({}); - - (generatePdfObservableV2 as jest.Mock).mockReturnValue(of({ buffer: Buffer.from('') })); - const { content_type: contentType } = await mockPdfExportType.runTask( 'pdfJobId', - getBasePayload({ locatorParams: [], headers: encryptedHeaders }), + getBasePayload({ + layout: { dimensions: {} }, + locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], + headers: encryptedHeaders, + }), cancellationToken, stream ); @@ -108,13 +121,13 @@ test(`returns content_type of application/pdf`, async () => { }); test(`returns content of generatePdf getBuffer base64 encoded`, async () => { - const testContent = 'test content'; - (generatePdfObservableV2 as jest.Mock).mockReturnValue(of({ buffer: Buffer.from(testContent) })); - - const encryptedHeaders = await encryptHeaders({}); await mockPdfExportType.runTask( 'pdfJobId', - getBasePayload({ locatorParams: [], headers: encryptedHeaders }), + getBasePayload({ + layout: { dimensions: {} }, + locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], + headers: encryptedHeaders, + }), cancellationToken, stream ); diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts index cc975c536803d..24e445e503d51 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts @@ -22,17 +22,18 @@ import { REPORTING_REDIRECT_LOCATOR_STORE_KEY, REPORTING_TRANSACTION_TYPE, } from '@kbn/reporting-common'; -import type { TaskRunResult, UrlOrUrlLocatorTuple } from '@kbn/reporting-common/types'; +import type { TaskRunResult } from '@kbn/reporting-common/types'; +import type { TaskPayloadPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { JobParamsPDFV2, PDF_JOB_TYPE_V2, PDF_REPORT_TYPE_V2, - TaskPayloadPDFV2, } from '@kbn/reporting-export-types-pdf-common'; -import { decryptJobHeaders, getFullRedirectAppUrl, ExportType } from '@kbn/reporting-server'; +import { ExportType, decryptJobHeaders, getFullRedirectAppUrl } from '@kbn/reporting-server'; +import type { UrlOrUrlWithContext } from '@kbn/screenshotting-plugin/server/screenshots'; -import { generatePdfObservableV2 } from './generate_pdf_v2'; import { getCustomLogo } from './get_custom_logo'; +import { getTracker } from './pdf_tracker'; export class PdfExportType extends ExportType { id = PDF_REPORT_TYPE_V2; @@ -80,66 +81,82 @@ export class PdfExportType extends ExportType cancellationToken: CancellationToken, stream: Writable ) => { - const jobLogger = this.logger.get(`execute-job:${jobId}`); + const logger = this.logger.get(`execute-job:${jobId}`); const apmTrans = apm.startTransaction('execute-job-pdf-v2', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans.startSpan('get-assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; const { encryptionKey } = this.config; const process$: Rx.Observable = of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, jobLogger)), + mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, logger)), mergeMap(async (headers: Headers) => { - const fakeRequest = this.getFakeRequest(headers, payload.spaceId, jobLogger); + const fakeRequest = this.getFakeRequest(headers, payload.spaceId, logger); const uiSettingsClient = await this.getUiSettingsClient(fakeRequest); return await getCustomLogo(uiSettingsClient, headers); }), mergeMap(({ logo, headers }) => { const { browserTimezone, layout, title, locatorParams } = payload; - let urls: UrlOrUrlLocatorTuple[]; - if (locatorParams) { - urls = locatorParams.map((locator) => [ - getFullRedirectAppUrl( - this.config, - this.getServerInfo(), - payload.spaceId, - payload.forceNow - ), - locator, - ]); - } apmGetAssets?.end(); apmGeneratePdf = apmTrans.startSpan('generate-pdf-pipeline', 'execute'); - return generatePdfObservableV2( - this.config, - this.getServerInfo(), - () => - this.startDeps.screenshotting!.getScreenshots({ - format: 'pdf', - title, - logo, - browserTimezone, - headers, - layout, - urls: urls.map((url) => - typeof url === 'string' - ? url - : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }] - ), - }), - payload, - locatorParams, - { + const tracker = getTracker(); + tracker.startScreenshots(); + + /** + * For each locator we get the relative URL to the redirect app + */ + const urls = locatorParams.map((locator) => [ + getFullRedirectAppUrl( + this.config, + this.getServerInfo(), + payload.spaceId, + payload.forceNow + ), + locator, + ]) as unknown as UrlOrUrlWithContext[]; + + return this.startDeps + .screenshotting!.getScreenshots({ format: 'pdf', title, logo, browserTimezone, headers, layout, - } - ); + urls: urls.map((url) => + typeof url === 'string' + ? url + : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }] + ), + }) + .pipe( + tap(({ metrics }) => { + if (metrics.cpu) { + tracker.setCpuUsage(metrics.cpu); + } + if (metrics.memory) { + tracker.setMemoryUsage(metrics.memory); + } + }), + mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { + tracker.endScreenshots(); + const warnings: string[] = []; + if (errors) { + warnings.push(...errors.map((error) => error.message)); + } + if (renderErrors) { + warnings.push(...renderErrors); + } + + return { + buffer, + metrics, + warnings, + }; + }) + ); }), tap(({ buffer }) => { apmGeneratePdf?.end(); @@ -154,7 +171,7 @@ export class PdfExportType extends ExportType warnings, })), catchError((err) => { - jobLogger.error(err); + logger.error(err); return Rx.throwError(() => err); }) ); diff --git a/packages/kbn-reporting/export_types/png/generate_png.ts b/packages/kbn-reporting/export_types/png/generate_png.ts deleted file mode 100644 index d0ce9b9791690..0000000000000 --- a/packages/kbn-reporting/export_types/png/generate_png.ts +++ /dev/null @@ -1,73 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import apm from 'elastic-apm-node'; -import { Observable } from 'rxjs'; -import { finalize, map, tap } from 'rxjs/operators'; - -import type { Logger } from '@kbn/logging'; -import { REPORTING_TRANSACTION_TYPE } from '@kbn/reporting-common/constants'; -import type { PngScreenshotOptions, PngScreenshotResult } from '@kbn/screenshotting-plugin/server'; - -interface PngResult { - buffer: Buffer; - metrics?: PngScreenshotResult['metrics']; - warnings: string[]; -} - -type GetScreenshotsFn = (options: PngScreenshotOptions) => Observable; - -export function generatePngObservable( - getScreenshots: GetScreenshotsFn, - logger: Logger, - options: Omit -): Observable { - const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE); - if (!options.layout?.dimensions) { - throw new Error(`LayoutParams.Dimensions is undefined.`); - } - - const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); - let apmBuffer: typeof apm.currentSpan; - - return getScreenshots({ - ...options, - format: 'png', - layout: { id: 'preserve_layout', ...options.layout }, - }).pipe( - tap(({ metrics }) => { - if (metrics) { - apmTrans.setLabel('cpu', metrics.cpu, false); - apmTrans.setLabel('memory', metrics.memory, false); - } - apmScreenshots?.end(); - apmBuffer = apmTrans.startSpan('get-buffer', 'output') ?? null; - }), - map(({ metrics, results }) => ({ - metrics, - buffer: results[0].screenshots[0].data, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), - })), - tap(({ buffer }) => { - logger.debug(`PNG buffer byte length: ${buffer.byteLength}`); - apmTrans.setLabel('byte-length', buffer.byteLength, false); - }), - finalize(() => { - apmBuffer?.end(); - apmTrans.end(); - }) - ); -} diff --git a/packages/kbn-reporting/export_types/png/png_v2.test.ts b/packages/kbn-reporting/export_types/png/png_v2.test.ts index a6cc8b1891eef..bd59306af8168 100644 --- a/packages/kbn-reporting/export_types/png/png_v2.test.ts +++ b/packages/kbn-reporting/export_types/png/png_v2.test.ts @@ -15,12 +15,9 @@ import type { LocatorParams } from '@kbn/reporting-common/types'; import type { TaskPayloadPNGV2 } from '@kbn/reporting-export-types-png-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; import { cryptoFactory } from '@kbn/reporting-server'; -import type { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; - +import { createMockScreenshottingStart } from '@kbn/screenshotting-plugin/server/mock'; +import type { CaptureResult } from '@kbn/screenshotting-plugin/server/screenshots'; import { PngExportType } from '.'; -import { generatePngObservable } from './generate_png'; - -jest.mock('./generate_png'); let content: string; let mockPngExportType: PngExportType; @@ -34,49 +31,51 @@ const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); return await crypto.encrypt(headers); }; +let encryptedHeaders: string; +const screenshottingMock = createMockScreenshottingStart(); +const getScreenshotsSpy = jest.spyOn(screenshottingMock, 'getScreenshots'); +const testContent = 'raw string from get_screenhots'; const getBasePayload = (baseObj: unknown) => baseObj as TaskPayloadPNGV2; beforeEach(async () => { content = ''; stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; - const configType = createMockConfigSchema({ - encryptionKey: mockEncryptionKey, - queue: { - indexInterval: 'daily', - timeout: Infinity, - }, - }); - + const configType = createMockConfigSchema({ encryptionKey: mockEncryptionKey }); const context = coreMock.createPluginInitializerContext(configType); const mockCoreSetup = coreMock.createSetup(); const mockCoreStart = coreMock.createStart(); + encryptedHeaders = await encryptHeaders({}); + mockPngExportType = new PngExportType(mockCoreSetup, configType, mockLogger, context); mockPngExportType.setup({ basePath: { set: jest.fn() }, }); mockPngExportType.start({ + esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: mockCoreStart.savedObjects, uiSettings: mockCoreStart.uiSettings, - screenshotting: {} as unknown as ScreenshottingStart, - esClient: elasticsearchServiceMock.createClusterClient(), + screenshotting: screenshottingMock, }); -}); -afterEach(() => (generatePngObservable as jest.Mock).mockReset()); + getScreenshotsSpy.mockImplementation(() => { + return Rx.of({ + metrics: { cpu: 0 }, + results: [{ screenshots: [{ data: Buffer.from(testContent) }] }] as CaptureResult['results'], + }); + }); +}); test(`passes browserTimezone to generatePng`, async () => { - const encryptedHeaders = await encryptHeaders({}); - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); - const browserTimezone = 'UTC'; await mockPngExportType.runTask( 'pngJobId', getBasePayload({ forceNow: 'test', + layout: { dimensions: {} }, locatorParams: [], browserTimezone, headers: encryptedHeaders, @@ -85,25 +84,24 @@ test(`passes browserTimezone to generatePng`, async () => { stream ); - expect(generatePngObservable).toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - expect.objectContaining({ - browserTimezone: 'UTC', - headers: {}, - layout: { id: 'preserve_layout' }, - }) - ); + expect(getScreenshotsSpy).toHaveBeenCalledWith({ + format: 'png', + headers: {}, + layout: { dimensions: {}, id: 'preserve_layout' }, + urls: [ + [ + 'http://localhost:80/mock-server-basepath/app/reportingRedirect?forceNow=test', + { __REPORTING_REDIRECT_LOCATOR_STORE_KEY__: undefined }, + ], + ], + }); }); test(`returns content_type of application/png`, async () => { - const encryptedHeaders = await encryptHeaders({}); - - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('foo') })); - const { content_type: contentType } = await mockPngExportType.runTask( 'pngJobId', getBasePayload({ + layout: { dimensions: {} }, locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], headers: encryptedHeaders, }), @@ -114,13 +112,10 @@ test(`returns content_type of application/png`, async () => { }); test(`returns content of generatePng getBuffer base64 encoded`, async () => { - const testContent = 'raw string from get_screenhots'; - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); - - const encryptedHeaders = await encryptHeaders({}); await mockPngExportType.runTask( 'pngJobId', getBasePayload({ + layout: { dimensions: {} }, locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], headers: encryptedHeaders, }), diff --git a/packages/kbn-reporting/export_types/png/png_v2.ts b/packages/kbn-reporting/export_types/png/png_v2.ts index 15f0c26d45932..2b824bde18a0b 100644 --- a/packages/kbn-reporting/export_types/png/png_v2.ts +++ b/packages/kbn-reporting/export_types/png/png_v2.ts @@ -38,9 +38,7 @@ import { PNG_REPORT_TYPE_V2, TaskPayloadPNGV2, } from '@kbn/reporting-export-types-png-common'; -import { decryptJobHeaders, getFullRedirectAppUrl, ExportType } from '@kbn/reporting-server'; - -import { generatePngObservable } from './generate_png'; +import { decryptJobHeaders, ExportType, getFullRedirectAppUrl } from '@kbn/reporting-server'; export class PngExportType extends ExportType { id = PNG_REPORT_TYPE_V2; @@ -88,14 +86,14 @@ export class PngExportType extends ExportType cancellationToken: CancellationToken, stream: Writable ) => { - const jobLogger = this.logger.get(`execute-job:${jobId}`); + const logger = this.logger.get(`execute-job:${jobId}`); const apmTrans = apm.startTransaction('execute-job-pdf-v2', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans.startSpan('get-assets', 'setup'); let apmGeneratePng: { end: () => void } | null | undefined; const { encryptionKey } = this.config; const process$: Observable = of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, jobLogger)), + mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, logger)), mergeMap((headers) => { const url = getFullRedirectAppUrl( this.config, @@ -108,22 +106,57 @@ export class PngExportType extends ExportType apmGetAssets?.end(); apmGeneratePng = apmTrans.startSpan('generate-png-pipeline', 'execute'); + const options = { + headers, + browserTimezone: payload.browserTimezone, + layout: { ...payload.layout, id: 'preserve_layout' as const }, + }; - return generatePngObservable( - () => - this.startDeps.screenshotting!.getScreenshots({ - format: 'png', - headers, - layout: { ...payload.layout, id: 'preserve_layout' }, - urls: [[url, { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: locatorParams }]], - }), - jobLogger, - { + if (!options.layout?.dimensions) { + throw new Error(`LayoutParams.Dimensions is undefined.`); + } + + const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); + let apmBuffer: typeof apm.currentSpan; + + return this.startDeps + .screenshotting!.getScreenshots({ + format: 'png', headers, - browserTimezone: payload.browserTimezone, layout: { ...payload.layout, id: 'preserve_layout' }, - } - ); + urls: [[url, { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: locatorParams }]], + }) + .pipe( + tap(({ metrics }) => { + if (metrics) { + apmTrans.setLabel('cpu', metrics.cpu, false); + apmTrans.setLabel('memory', metrics.memory, false); + } + apmScreenshots?.end(); + apmBuffer = apmTrans.startSpan('get-buffer', 'output') ?? null; + }), + map(({ metrics, results }) => ({ + metrics, + buffer: results[0].screenshots[0].data, + warnings: results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, [] as string[]), + })), + tap(({ buffer }) => { + logger.debug(`PNG buffer byte length: ${buffer.byteLength}`); + apmTrans.setLabel('byte-length', buffer.byteLength, false); + }), + finalize(() => { + apmBuffer?.end(); + apmTrans.end(); + }) + ); }), tap(({ buffer }) => stream.write(buffer)), map(({ metrics, warnings }) => ({ @@ -131,7 +164,7 @@ export class PngExportType extends ExportType metrics: { png: metrics }, warnings, })), - tap({ error: (error) => jobLogger.error(error) }), + tap({ error: (error) => logger.error(error) }), finalize(() => apmGeneratePng?.end()) ); diff --git a/packages/kbn-reporting/export_types/png/tsconfig.json b/packages/kbn-reporting/export_types/png/tsconfig.json index bac09188079b5..b8a141a320dd8 100644 --- a/packages/kbn-reporting/export_types/png/tsconfig.json +++ b/packages/kbn-reporting/export_types/png/tsconfig.json @@ -23,6 +23,5 @@ "@kbn/reporting-export-types-png-common", "@kbn/core", "@kbn/reporting-mocks-server", - "@kbn/logging", ] }