[Reporting] Clean up any
usage, reorganize server route files (#110740)
* refactor/reflatten server routes * fix import * fix any usage in server/lib * clean up unused parameter * remove any in server/browsers * refactor handle request function into a class * more cleanup
This commit is contained in:
parent
b0a0dc224b
commit
e6faa58873
|
@ -78,7 +78,8 @@ export class HeadlessChromiumDriverFactory {
|
|||
{ viewport, browserTimezone }: { viewport: ViewportConfig; browserTimezone?: string },
|
||||
pLogger: LevelLogger
|
||||
): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable<never> }> {
|
||||
return Rx.Observable.create(async (observer: InnerSubscriber<any, any>) => {
|
||||
// FIXME: 'create' is deprecated
|
||||
return Rx.Observable.create(async (observer: InnerSubscriber<unknown, unknown>) => {
|
||||
const logger = pLogger.clone(['browser-driver']);
|
||||
logger.info(`Creating browser page driver`);
|
||||
|
||||
|
|
|
@ -49,23 +49,57 @@ test('throws validation error if provided with data over max size', () => {
|
|||
});
|
||||
|
||||
test('throws validation error if provided with non-image data', () => {
|
||||
const invalidErrorMatcher = /try a different image/;
|
||||
|
||||
expect(() => PdfLogoSchema.validate('')).toThrowError(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(true)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(false)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate({})).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate([])).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(0)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(0x00f)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate('')).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: Sorry, that file will not work. Please try a different image file.
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
expect(() => PdfLogoSchema.validate(true)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: expected value of type [string] but got [boolean]
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
expect(() => PdfLogoSchema.validate(false)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: expected value of type [string] but got [boolean]
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
expect(() => PdfLogoSchema.validate({})).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: expected value of type [string] but got [Object]
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
expect(() => PdfLogoSchema.validate([])).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: expected value of type [string] but got [Array]
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
expect(() => PdfLogoSchema.validate(0)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: expected value of type [string] but got [number]
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
expect(() => PdfLogoSchema.validate(0x00f)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: expected value of type [string] but got [number]
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
|
||||
const csvString =
|
||||
`data:text/csv;base64,Il9pZCIsIl9pbmRleCIsIl9zY29yZSIsIl90eXBlIiwiZm9vLmJhciIsImZvby5iYXIua2V5d29yZCIKZjY1QU9IZ0J5bFZmWW04W` +
|
||||
`TRvb1EsYmVlLDEsIi0iLGJheixiYXoKbks1QU9IZ0J5bFZmWW04WTdZcUcsYmVlLDEsIi0iLGJvbyxib28K`;
|
||||
expect(() => PdfLogoSchema.validate(csvString)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(csvString)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: Sorry, that file will not work. Please try a different image file.
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
|
||||
const scriptString =
|
||||
`data:application/octet-stream;base64,QEVDSE8gT0ZGCldFRUtPRllSLkNPTSB8IEZJTkQgIlRoaXMgaXMiID4gVEVNUC5CQV` +
|
||||
`QKRUNITz5USElTLkJBVCBTRVQgV0VFSz0lJTMKQ0FMTCBURU1QLkJBVApERUwgIFRFTVAuQkFUCkRFTCAgVEhJUy5CQVQKRUNITyBXZWVrICVXRUVLJQo=`;
|
||||
expect(() => PdfLogoSchema.validate(scriptString)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(scriptString)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"types that failed validation:
|
||||
- [0]: Sorry, that file will not work. Please try a different image file.
|
||||
- [1]: expected value to equal [null]"
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ const maxLogoSizeInBase64 = kbToBase64Length(200);
|
|||
const dataurlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)(;[a-z-]+=[a-z0-9-]+)?(;([a-z0-9]+))?,/;
|
||||
const imageTypes = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif'];
|
||||
|
||||
const isImageData = (str: any): boolean => {
|
||||
const isImageData = (str: string) => {
|
||||
const matches = str.match(dataurlRegex);
|
||||
|
||||
if (!matches) {
|
||||
|
@ -33,7 +33,7 @@ const isImageData = (str: any): boolean => {
|
|||
return true;
|
||||
};
|
||||
|
||||
const validatePdfLogoBase64String = (str: any) => {
|
||||
const validatePdfLogoBase64String = (str: string) => {
|
||||
if (typeof str !== 'string' || !isImageData(str)) {
|
||||
return i18n.translate('xpack.reporting.uiSettings.validate.customLogo.badFile', {
|
||||
defaultMessage: `Sorry, that file will not work. Please try a different image file.`,
|
||||
|
@ -46,7 +46,9 @@ const validatePdfLogoBase64String = (str: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const PdfLogoSchema = schema.nullable(schema.any({ validate: validatePdfLogoBase64String }));
|
||||
export const PdfLogoSchema = schema.nullable(
|
||||
schema.string({ validate: validatePdfLogoBase64String })
|
||||
);
|
||||
|
||||
export function registerUiSettings(core: CoreSetup<object, unknown>) {
|
||||
core.uiSettings.register({
|
||||
|
|
|
@ -101,7 +101,10 @@ export class PdfMaker {
|
|||
this._addContents(contents);
|
||||
}
|
||||
|
||||
addImage(image: Buffer, opts = { title: '', description: '' }) {
|
||||
addImage(
|
||||
image: Buffer,
|
||||
opts: { title?: string; description?: string } = { title: '', description: '' }
|
||||
) {
|
||||
const size = this._layout.getPdfImageSize();
|
||||
const img = {
|
||||
image: `data:image/png;base64,${image.toString('base64')}`,
|
||||
|
|
|
@ -73,8 +73,8 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) {
|
|||
tracker.startAddImage();
|
||||
tracker.endAddImage();
|
||||
pdfOutput.addImage(screenshot.data, {
|
||||
title: screenshot.title,
|
||||
description: screenshot.description,
|
||||
title: screenshot.title ?? undefined,
|
||||
description: screenshot.description ?? undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -84,8 +84,8 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) {
|
|||
tracker.startAddImage();
|
||||
tracker.endAddImage();
|
||||
pdfOutput.addImage(screenshot.data, {
|
||||
title: screenshot.title,
|
||||
description: screenshot.description,
|
||||
title: screenshot.title ?? undefined,
|
||||
description: screenshot.description ?? undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ export const getElementPositionAndAttributes = async (
|
|||
},
|
||||
attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => {
|
||||
const attribute = attributes[key];
|
||||
(result as any)[key] = element.getAttribute(attribute);
|
||||
result[key] = element.getAttribute(attribute);
|
||||
return result;
|
||||
}, {} as AttributesMap),
|
||||
});
|
||||
|
|
|
@ -8,13 +8,10 @@
|
|||
import { HeadlessChromiumDriver } from '../../browsers';
|
||||
import {
|
||||
createMockBrowserDriverFactory,
|
||||
createMockConfig,
|
||||
createMockConfigSchema,
|
||||
createMockLayoutInstance,
|
||||
createMockLevelLogger,
|
||||
createMockReportingCore,
|
||||
} from '../../test_helpers';
|
||||
import { LayoutInstance } from '../layouts';
|
||||
import { getScreenshots } from './get_screenshots';
|
||||
|
||||
describe('getScreenshots', () => {
|
||||
|
@ -35,17 +32,12 @@ describe('getScreenshots', () => {
|
|||
},
|
||||
];
|
||||
|
||||
let layout: LayoutInstance;
|
||||
let logger: ReturnType<typeof createMockLevelLogger>;
|
||||
let browser: jest.Mocked<HeadlessChromiumDriver>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const schema = createMockConfigSchema();
|
||||
const config = createMockConfig(schema);
|
||||
const captureConfig = config.get('capture');
|
||||
const core = await createMockReportingCore(schema);
|
||||
const core = await createMockReportingCore(createMockConfigSchema());
|
||||
|
||||
layout = createMockLayoutInstance(captureConfig);
|
||||
logger = createMockLevelLogger();
|
||||
|
||||
await createMockBrowserDriverFactory(core, logger, {
|
||||
|
@ -71,7 +63,7 @@ describe('getScreenshots', () => {
|
|||
});
|
||||
|
||||
it('should return screenshots', async () => {
|
||||
await expect(getScreenshots(browser, layout, elementsPositionAndAttributes, logger)).resolves
|
||||
await expect(getScreenshots(browser, elementsPositionAndAttributes, logger)).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
|
@ -117,7 +109,7 @@ describe('getScreenshots', () => {
|
|||
});
|
||||
|
||||
it('should forward elements positions', async () => {
|
||||
await getScreenshots(browser, layout, elementsPositionAndAttributes, logger);
|
||||
await getScreenshots(browser, elementsPositionAndAttributes, logger);
|
||||
|
||||
expect(browser.screenshot).toHaveBeenCalledTimes(2);
|
||||
expect(browser.screenshot).toHaveBeenNthCalledWith(
|
||||
|
@ -134,7 +126,7 @@ describe('getScreenshots', () => {
|
|||
browser.screenshot.mockResolvedValue(Buffer.from(''));
|
||||
|
||||
await expect(
|
||||
getScreenshots(browser, layout, elementsPositionAndAttributes, logger)
|
||||
getScreenshots(browser, elementsPositionAndAttributes, logger)
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { LevelLogger, startTrace } from '../';
|
||||
import { HeadlessChromiumDriver } from '../../browsers';
|
||||
import { LayoutInstance } from '../layouts';
|
||||
import { ElementsPositionAndAttribute, Screenshot } from './';
|
||||
|
||||
export const getScreenshots = async (
|
||||
browser: HeadlessChromiumDriver,
|
||||
layout: LayoutInstance,
|
||||
elementsPositionAndAttributes: ElementsPositionAndAttribute[],
|
||||
logger: LevelLogger
|
||||
): Promise<Screenshot[]> => {
|
||||
|
|
|
@ -21,7 +21,7 @@ export interface ScreenshotObservableOpts {
|
|||
}
|
||||
|
||||
export interface AttributesMap {
|
||||
[key: string]: any;
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
export interface ElementPosition {
|
||||
|
@ -45,8 +45,8 @@ export interface ElementsPositionAndAttribute {
|
|||
|
||||
export interface Screenshot {
|
||||
data: Buffer;
|
||||
title: string;
|
||||
description: string;
|
||||
title: string | null;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface ScreenshotResults {
|
||||
|
|
|
@ -123,7 +123,7 @@ export function getScreenshots$(
|
|||
const elements = data.elementsPositionAndAttributes
|
||||
? data.elementsPositionAndAttributes
|
||||
: getDefaultElementPosition(layout.getViewport(1));
|
||||
const screenshots = await getScreenshots(driver, layout, elements, logger);
|
||||
const screenshots = await getScreenshots(driver, elements, logger);
|
||||
const { timeRange, error: setupError } = data;
|
||||
return {
|
||||
timeRange,
|
||||
|
|
|
@ -47,8 +47,8 @@ interface TaskExecutor extends Pick<ExportTypeDefinition, 'jobContentEncoding'>
|
|||
jobExecutor: RunTaskFn<BasePayload>;
|
||||
}
|
||||
|
||||
function isOutput(output: any): output is CompletedReportOutput {
|
||||
return output?.size != null;
|
||||
function isOutput(output: CompletedReportOutput | Error): output is CompletedReportOutput {
|
||||
return (output as CompletedReportOutput).size != null;
|
||||
}
|
||||
|
||||
function reportFromTask(task: ReportTaskParams) {
|
||||
|
|
|
@ -36,7 +36,7 @@ describe('POST /diagnose/screenshot', () => {
|
|||
toPromise: () => (resp instanceof Error ? Promise.reject(resp) : Promise.resolve(resp)),
|
||||
}),
|
||||
}));
|
||||
(generatePngObservableFactory as any).mockResolvedValue(generateMock);
|
||||
(generatePngObservableFactory as jest.Mock).mockResolvedValue(generateMock);
|
||||
};
|
||||
|
||||
const config = createMockConfigSchema({ queue: { timeout: 120000 } });
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Writable } from 'stream';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
import { ReportingCore } from '../';
|
||||
import { runTaskFnFactory } from '../export_types/csv_searchsource_immediate/execute_job';
|
||||
import { JobParamsDownloadCSV } from '../export_types/csv_searchsource_immediate/types';
|
||||
import { LevelLogger as Logger } from '../lib';
|
||||
import { TaskRunResult } from '../lib/tasks';
|
||||
import { authorizedUserPreRouting } from './lib/authorized_user_pre_routing';
|
||||
import { HandlerErrorFunction } from './types';
|
||||
import { Writable } from 'stream';
|
||||
import { ReportingCore } from '../../';
|
||||
import { runTaskFnFactory } from '../../export_types/csv_searchsource_immediate/execute_job';
|
||||
import { JobParamsDownloadCSV } from '../../export_types/csv_searchsource_immediate/types';
|
||||
import { LevelLogger as Logger } from '../../lib';
|
||||
import { TaskRunResult } from '../../lib/tasks';
|
||||
import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing';
|
||||
import { RequestHandler } from '../lib/request_handler';
|
||||
|
||||
const API_BASE_URL_V1 = '/api/reporting/v1';
|
||||
const API_BASE_GENERATE_V1 = `${API_BASE_URL_V1}/generate`;
|
||||
|
@ -32,7 +32,6 @@ export type CsvFromSavedObjectRequest = KibanaRequest<unknown, unknown, JobParam
|
|||
*/
|
||||
export function registerGenerateCsvFromSavedObjectImmediate(
|
||||
reporting: ReportingCore,
|
||||
handleError: HandlerErrorFunction,
|
||||
parentLogger: Logger
|
||||
) {
|
||||
const setupDeps = reporting.getPluginSetupDeps();
|
||||
|
@ -64,9 +63,10 @@ export function registerGenerateCsvFromSavedObjectImmediate(
|
|||
},
|
||||
authorizedUserPreRouting(
|
||||
reporting,
|
||||
async (_user, context, req: CsvFromSavedObjectRequest, res) => {
|
||||
async (user, context, req: CsvFromSavedObjectRequest, res) => {
|
||||
const logger = parentLogger.clone(['csv_searchsource_immediate']);
|
||||
const runTaskFn = runTaskFnFactory(reporting, logger);
|
||||
const requestHandler = new RequestHandler(reporting, user, context, req, res, logger);
|
||||
|
||||
try {
|
||||
let buffer = Buffer.from('');
|
||||
|
@ -107,7 +107,7 @@ export function registerGenerateCsvFromSavedObjectImmediate(
|
|||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return handleError(res, err);
|
||||
return requestHandler.handleError(err);
|
||||
}
|
||||
}
|
||||
)
|
|
@ -7,19 +7,16 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import rison from 'rison-node';
|
||||
import { ReportingCore } from '../';
|
||||
import { API_BASE_URL } from '../../common/constants';
|
||||
import { BaseParams } from '../types';
|
||||
import { authorizedUserPreRouting } from './lib/authorized_user_pre_routing';
|
||||
import { HandlerErrorFunction, HandlerFunction } from './types';
|
||||
import { ReportingCore } from '../..';
|
||||
import { API_BASE_URL } from '../../../common/constants';
|
||||
import { LevelLogger } from '../../lib';
|
||||
import { BaseParams } from '../../types';
|
||||
import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing';
|
||||
import { RequestHandler } from '../lib/request_handler';
|
||||
|
||||
const BASE_GENERATE = `${API_BASE_URL}/generate`;
|
||||
|
||||
export function registerGenerateFromJobParams(
|
||||
reporting: ReportingCore,
|
||||
handler: HandlerFunction,
|
||||
handleError: HandlerErrorFunction
|
||||
) {
|
||||
export function registerJobGenerationRoutes(reporting: ReportingCore, logger: LevelLogger) {
|
||||
const setupDeps = reporting.getPluginSetupDeps();
|
||||
const { router } = setupDeps;
|
||||
|
||||
|
@ -62,7 +59,6 @@ export function registerGenerateFromJobParams(
|
|||
});
|
||||
}
|
||||
|
||||
const { exportType } = req.params;
|
||||
let jobParams;
|
||||
|
||||
try {
|
||||
|
@ -80,10 +76,12 @@ export function registerGenerateFromJobParams(
|
|||
});
|
||||
}
|
||||
|
||||
const requestHandler = new RequestHandler(reporting, user, context, req, res, logger);
|
||||
|
||||
try {
|
||||
return await handler(user, exportType, jobParams, context, req, res);
|
||||
return await requestHandler.handleGenerateRequest(req.params.exportType, jobParams);
|
||||
} catch (err) {
|
||||
return handleError(res, err);
|
||||
return requestHandler.handleError(err);
|
||||
}
|
||||
})
|
||||
);
|
|
@ -12,15 +12,15 @@ import { of } from 'rxjs';
|
|||
import { ElasticsearchClient } from 'kibana/server';
|
||||
import { setupServer } from 'src/core/server/test_utils';
|
||||
import supertest from 'supertest';
|
||||
import { ReportingCore } from '..';
|
||||
import { ExportTypesRegistry } from '../lib/export_types_registry';
|
||||
import { createMockLevelLogger, createMockReportingCore } from '../test_helpers';
|
||||
import { ReportingCore } from '../..';
|
||||
import { ExportTypesRegistry } from '../../lib/export_types_registry';
|
||||
import { createMockLevelLogger, createMockReportingCore } from '../../test_helpers';
|
||||
import {
|
||||
createMockConfigSchema,
|
||||
createMockPluginSetup,
|
||||
} from '../test_helpers/create_mock_reportingplugin';
|
||||
import { registerJobGenerationRoutes } from './generation';
|
||||
import type { ReportingRequestHandlerContext } from '../types';
|
||||
} from '../../test_helpers/create_mock_reportingplugin';
|
||||
import type { ReportingRequestHandlerContext } from '../../types';
|
||||
import { registerJobGenerationRoutes } from './generate_from_jobparams';
|
||||
|
||||
type SetupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
|
||||
|
10
x-pack/plugins/reporting/server/routes/generate/index.ts
Normal file
10
x-pack/plugins/reporting/server/routes/generate/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { registerGenerateCsvFromSavedObjectImmediate } from './csv_searchsource_immediate'; // FIXME: should not need to register each immediate export type separately
|
||||
export { registerJobGenerationRoutes } from './generate_from_jobparams';
|
||||
export { registerLegacy } from './legacy';
|
|
@ -6,21 +6,16 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import querystring from 'querystring';
|
||||
import { authorizedUserPreRouting } from './lib/authorized_user_pre_routing';
|
||||
import { API_BASE_URL } from '../../common/constants';
|
||||
import { HandlerErrorFunction, HandlerFunction } from './types';
|
||||
import { ReportingCore } from '../core';
|
||||
import { LevelLogger } from '../lib';
|
||||
import querystring, { ParsedUrlQueryInput } from 'querystring';
|
||||
import { API_BASE_URL } from '../../../common/constants';
|
||||
import { ReportingCore } from '../../core';
|
||||
import { LevelLogger } from '../../lib';
|
||||
import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing';
|
||||
import { RequestHandler } from '../lib/request_handler';
|
||||
|
||||
const BASE_GENERATE = `${API_BASE_URL}/generate`;
|
||||
|
||||
export function registerLegacy(
|
||||
reporting: ReportingCore,
|
||||
handler: HandlerFunction,
|
||||
handleError: HandlerErrorFunction,
|
||||
logger: LevelLogger
|
||||
) {
|
||||
export function registerLegacy(reporting: ReportingCore, logger: LevelLogger) {
|
||||
const { router } = reporting.getPluginSetupDeps();
|
||||
|
||||
function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: string }) {
|
||||
|
@ -32,12 +27,15 @@ export function registerLegacy(
|
|||
validate: {
|
||||
params: schema.object({
|
||||
savedObjectId: schema.string({ minLength: 3 }),
|
||||
title: schema.string(),
|
||||
browserTimezone: schema.string(),
|
||||
}),
|
||||
query: schema.any(),
|
||||
query: schema.maybe(schema.string()),
|
||||
},
|
||||
},
|
||||
|
||||
authorizedUserPreRouting(reporting, async (user, context, req, res) => {
|
||||
const requestHandler = new RequestHandler(reporting, user, context, req, res, logger);
|
||||
const message = `The following URL is deprecated and will stop working in the next major version: ${req.url.pathname}${req.url.search}`;
|
||||
logger.warn(message, ['deprecation']);
|
||||
|
||||
|
@ -46,26 +44,19 @@ export function registerLegacy(
|
|||
title,
|
||||
savedObjectId,
|
||||
browserTimezone,
|
||||
}: { title: string; savedObjectId: string; browserTimezone: string } = req.params as any;
|
||||
const queryString = querystring.stringify(req.query as any);
|
||||
}: { title: string; savedObjectId: string; browserTimezone: string } = req.params;
|
||||
const queryString = querystring.stringify(req.query as ParsedUrlQueryInput | undefined);
|
||||
|
||||
return await handler(
|
||||
user,
|
||||
exportTypeId,
|
||||
{
|
||||
title,
|
||||
objectType,
|
||||
savedObjectId,
|
||||
browserTimezone,
|
||||
queryString,
|
||||
version: reporting.getKibanaVersion(),
|
||||
},
|
||||
context,
|
||||
req,
|
||||
res
|
||||
);
|
||||
return await requestHandler.handleGenerateRequest(exportTypeId, {
|
||||
title,
|
||||
objectType,
|
||||
savedObjectId,
|
||||
browserTimezone,
|
||||
queryString,
|
||||
version: reporting.getKibanaVersion(),
|
||||
});
|
||||
} catch (err) {
|
||||
throw handleError(res, err);
|
||||
throw requestHandler.handleError(err);
|
||||
}
|
||||
})
|
||||
);
|
|
@ -1,92 +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 Boom from '@hapi/boom';
|
||||
import { kibanaResponseFactory } from 'src/core/server';
|
||||
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 { HandlerFunction } from './types';
|
||||
|
||||
const getDownloadBaseUrl = (reporting: ReportingCore) => {
|
||||
const config = reporting.getConfig();
|
||||
return config.kbnConfig.get('server', 'basePath') + `${API_BASE_URL}/jobs/download`;
|
||||
};
|
||||
|
||||
export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Logger) {
|
||||
/*
|
||||
* Generates enqueued job details to use in responses
|
||||
*/
|
||||
const handler: HandlerFunction = async (user, exportTypeId, jobParams, context, req, res) => {
|
||||
// ensure the async dependencies are loaded
|
||||
if (!context.reporting) {
|
||||
return res.custom({ statusCode: 503, body: 'Not Available' });
|
||||
}
|
||||
|
||||
const licenseInfo = await reporting.getLicenseInfo();
|
||||
const licenseResults = licenseInfo[exportTypeId];
|
||||
|
||||
if (!licenseResults) {
|
||||
return res.badRequest({ body: `Invalid export-type of ${exportTypeId}` });
|
||||
}
|
||||
|
||||
if (!licenseResults.enableLinks) {
|
||||
return res.forbidden({ body: licenseResults.message });
|
||||
}
|
||||
|
||||
try {
|
||||
const report = await enqueueJob(
|
||||
reporting,
|
||||
req,
|
||||
context,
|
||||
user,
|
||||
exportTypeId,
|
||||
jobParams,
|
||||
logger
|
||||
);
|
||||
|
||||
// return task manager's task information and the download URL
|
||||
const downloadBaseUrl = getDownloadBaseUrl(reporting);
|
||||
|
||||
return res.ok({
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
path: `${downloadBaseUrl}/${report._id}`,
|
||||
job: report.toApiJSON(),
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Error should already have been logged by the time we get here
|
||||
*/
|
||||
function handleError(res: typeof kibanaResponseFactory, err: Error | Boom.Boom) {
|
||||
if (err instanceof Boom.Boom) {
|
||||
return res.customError({
|
||||
statusCode: err.output.statusCode,
|
||||
body: err.output.payload.message,
|
||||
});
|
||||
}
|
||||
|
||||
// unknown error, can't convert to 4xx
|
||||
throw err;
|
||||
}
|
||||
|
||||
registerGenerateFromJobParams(reporting, handler, handleError);
|
||||
registerGenerateCsvFromSavedObjectImmediate(reporting, handleError, logger);
|
||||
registerLegacy(reporting, handler, handleError, logger);
|
||||
}
|
|
@ -5,23 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LevelLogger as Logger } from '../lib';
|
||||
import { ReportingCore } from '..';
|
||||
import { LevelLogger } from '../lib';
|
||||
import { registerDeprecationsRoutes } from './deprecations';
|
||||
import { registerDiagnosticRoutes } from './diagnostic';
|
||||
import { registerJobGenerationRoutes } from './generation';
|
||||
import { registerJobInfoRoutes } from './jobs';
|
||||
import { ReportingCore } from '../core';
|
||||
import {
|
||||
registerGenerateCsvFromSavedObjectImmediate,
|
||||
registerJobGenerationRoutes,
|
||||
registerLegacy,
|
||||
} from './generate';
|
||||
import { registerJobInfoRoutes } from './management';
|
||||
|
||||
export function registerRoutes(reporting: ReportingCore, logger: Logger) {
|
||||
export function registerRoutes(reporting: ReportingCore, logger: LevelLogger) {
|
||||
registerDeprecationsRoutes(reporting, logger);
|
||||
registerDiagnosticRoutes(reporting, logger);
|
||||
registerGenerateCsvFromSavedObjectImmediate(reporting, logger);
|
||||
registerJobGenerationRoutes(reporting, logger);
|
||||
registerLegacy(reporting, logger);
|
||||
registerJobInfoRoutes(reporting);
|
||||
}
|
||||
|
||||
export interface ReportingRequestPre {
|
||||
management: {
|
||||
jobTypes: string[];
|
||||
};
|
||||
user: string;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ interface Payload {
|
|||
statusCode: number;
|
||||
content: string | Stream | ErrorFromPayload;
|
||||
contentType: string | null;
|
||||
headers: Record<string, any>;
|
||||
headers: Record<string, string | number>;
|
||||
}
|
||||
|
||||
type TaskRunResult = Required<ReportApiJSON>['output'];
|
||||
|
|
|
@ -60,6 +60,7 @@ export async function downloadJobResponseHandler(
|
|||
} catch (err) {
|
||||
const { logger } = reporting.getPluginSetupDeps();
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@ import { ResponseError } from '@elastic/elasticsearch/lib/errors';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { UnwrapPromise } from '@kbn/utility-types';
|
||||
import { ElasticsearchClient } from 'src/core/server';
|
||||
import { PromiseType } from 'utility-types';
|
||||
import { ReportingCore } from '../../';
|
||||
import { statuses } from '../../lib/statuses';
|
||||
import { ReportApiJSON, ReportSource } from '../../../common/types';
|
||||
import { statuses } from '../../lib/statuses';
|
||||
import { Report } from '../../lib/store';
|
||||
import { ReportingUser } from '../../types';
|
||||
|
||||
|
@ -58,9 +59,9 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory
|
|||
return `${config.get('index')}-*`;
|
||||
}
|
||||
|
||||
async function execQuery<T extends (client: ElasticsearchClient) => any>(
|
||||
callback: T
|
||||
): Promise<UnwrapPromise<ReturnType<T>> | undefined> {
|
||||
async function execQuery<
|
||||
T extends (client: ElasticsearchClient) => Promise<PromiseType<ReturnType<T>> | undefined>
|
||||
>(callback: T): Promise<UnwrapPromise<ReturnType<T>> | undefined> {
|
||||
try {
|
||||
const { asInternalUser: client } = await reportingCore.getEsClient();
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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, KibanaResponseFactory } from 'kibana/server';
|
||||
import { coreMock, httpServerMock } from 'src/core/server/mocks';
|
||||
import { ReportingCore } from '../..';
|
||||
import {
|
||||
createMockConfigSchema,
|
||||
createMockLevelLogger,
|
||||
createMockReportingCore,
|
||||
} from '../../test_helpers';
|
||||
import { BaseParams, 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' }),
|
||||
}),
|
||||
}));
|
||||
|
||||
const getMockContext = () =>
|
||||
(({
|
||||
core: coreMock.createRequestHandlerContext(),
|
||||
} as unknown) as ReportingRequestHandlerContext);
|
||||
|
||||
const getMockRequest = () =>
|
||||
({
|
||||
url: { port: '5601', search: '', pathname: '/foo' },
|
||||
route: { path: '/foo', options: {} },
|
||||
} as KibanaRequest);
|
||||
|
||||
const getMockResponseFactory = () =>
|
||||
(({
|
||||
...httpServerMock.createResponseFactory(),
|
||||
forbidden: (obj: unknown) => obj,
|
||||
unauthorized: (obj: unknown) => obj,
|
||||
} as unknown) as KibanaResponseFactory);
|
||||
|
||||
const mockLogger = createMockLevelLogger();
|
||||
|
||||
describe('Handle request to generate', () => {
|
||||
let reportingCore: ReportingCore;
|
||||
let mockContext: ReturnType<typeof getMockContext>;
|
||||
let mockRequest: ReturnType<typeof getMockRequest>;
|
||||
let mockResponseFactory: ReturnType<typeof getMockResponseFactory>;
|
||||
let requestHandler: RequestHandler;
|
||||
|
||||
const mockJobParams = {} as BaseParams;
|
||||
|
||||
beforeEach(async () => {
|
||||
reportingCore = await createMockReportingCore(createMockConfigSchema({}));
|
||||
mockRequest = getMockRequest();
|
||||
|
||||
mockResponseFactory = getMockResponseFactory();
|
||||
(mockResponseFactory.ok as jest.Mock) = jest.fn((args: unknown) => args);
|
||||
(mockResponseFactory.forbidden as jest.Mock) = jest.fn((args: unknown) => args);
|
||||
(mockResponseFactory.badRequest as jest.Mock) = jest.fn((args: unknown) => args);
|
||||
|
||||
mockContext = getMockContext();
|
||||
mockContext.reporting = {} as ReportingSetup;
|
||||
requestHandler = new RequestHandler(
|
||||
reportingCore,
|
||||
{ username: 'testymcgee' },
|
||||
mockContext,
|
||||
mockRequest,
|
||||
mockResponseFactory,
|
||||
mockLogger
|
||||
);
|
||||
});
|
||||
|
||||
test('disallows invalid export type', async () => {
|
||||
expect(await requestHandler.handleGenerateRequest('neanderthals', mockJobParams))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": "Invalid export-type of neanderthals",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('disallows unsupporting license', async () => {
|
||||
(reportingCore.getLicenseInfo as jest.Mock) = jest.fn(() => ({
|
||||
csv: { enableLinks: false, message: `seeing this means the license isn't supported` },
|
||||
}));
|
||||
|
||||
expect(await requestHandler.handleGenerateRequest('csv', mockJobParams)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": "seeing this means the license isn't supported",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('generates the download path', async () => {
|
||||
expect(await requestHandler.handleGenerateRequest('csv', mockJobParams)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
"job": "{\\"id\\":\\"id-of-this-test-report\\"}",
|
||||
"path": "undefined/api/reporting/jobs/download/id-of-this-test-report",
|
||||
},
|
||||
"headers": Object {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 Boom from '@hapi/boom';
|
||||
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 { BaseParams, ReportingRequestHandlerContext, ReportingUser } from '../../types';
|
||||
|
||||
export const handleUnavailable = (res: KibanaResponseFactory) => {
|
||||
return res.custom({ statusCode: 503, body: 'Not Available' });
|
||||
};
|
||||
|
||||
const getDownloadBaseUrl = (reporting: ReportingCore) => {
|
||||
const config = reporting.getConfig();
|
||||
return config.kbnConfig.get('server', 'basePath') + `${API_BASE_URL}/jobs/download`;
|
||||
};
|
||||
|
||||
export class RequestHandler {
|
||||
constructor(
|
||||
private reporting: ReportingCore,
|
||||
private user: ReportingUser,
|
||||
private context: ReportingRequestHandlerContext,
|
||||
private req: KibanaRequest,
|
||||
private res: KibanaResponseFactory,
|
||||
private logger: LevelLogger
|
||||
) {}
|
||||
|
||||
public async handleGenerateRequest(
|
||||
exportTypeId: string,
|
||||
jobParams: BaseParams | JobParamsPDFLegacy
|
||||
) {
|
||||
// ensure the async dependencies are loaded
|
||||
if (!this.context.reporting) {
|
||||
return handleUnavailable(this.res);
|
||||
}
|
||||
|
||||
const licenseInfo = await this.reporting.getLicenseInfo();
|
||||
const licenseResults = licenseInfo[exportTypeId];
|
||||
|
||||
if (!licenseResults) {
|
||||
return this.res.badRequest({ body: `Invalid export-type of ${exportTypeId}` });
|
||||
}
|
||||
|
||||
if (!licenseResults.enableLinks) {
|
||||
return this.res.forbidden({ body: licenseResults.message });
|
||||
}
|
||||
|
||||
try {
|
||||
const report = await enqueueJob(
|
||||
this.reporting,
|
||||
this.req,
|
||||
this.context,
|
||||
this.user,
|
||||
exportTypeId,
|
||||
jobParams,
|
||||
this.logger
|
||||
);
|
||||
|
||||
// return task manager's task information and the download URL
|
||||
const downloadBaseUrl = getDownloadBaseUrl(this.reporting);
|
||||
|
||||
return this.res.ok({
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: {
|
||||
path: `${downloadBaseUrl}/${report._id}`,
|
||||
job: report.toApiJSON(),
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This method does not log the error, as it assumes the error has already
|
||||
* been caught and logged for stack trace context, and then rethrown
|
||||
*/
|
||||
public handleError(err: Error | Boom.Boom) {
|
||||
if (err instanceof Boom.Boom) {
|
||||
return this.res.customError({
|
||||
statusCode: err.output.statusCode,
|
||||
body: err.output.payload.message,
|
||||
});
|
||||
}
|
||||
|
||||
// unknown error, can't convert to 4xx
|
||||
throw err;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { registerJobInfoRoutes } from './jobs';
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
jest.mock('../lib/content_stream', () => ({
|
||||
jest.mock('../../lib/content_stream', () => ({
|
||||
getContentStream: jest.fn(),
|
||||
}));
|
||||
|
||||
|
@ -16,15 +16,15 @@ import { of } from 'rxjs';
|
|||
import { ElasticsearchClient } from 'kibana/server';
|
||||
import { setupServer } from 'src/core/server/test_utils';
|
||||
import supertest from 'supertest';
|
||||
import { ReportingCore } from '..';
|
||||
import { ReportingInternalSetup } from '../core';
|
||||
import { ContentStream, ExportTypesRegistry, getContentStream } from '../lib';
|
||||
import { ReportingCore } from '../..';
|
||||
import { ReportingInternalSetup } from '../../core';
|
||||
import { ContentStream, ExportTypesRegistry, getContentStream } from '../../lib';
|
||||
import {
|
||||
createMockConfigSchema,
|
||||
createMockPluginSetup,
|
||||
createMockReportingCore,
|
||||
} from '../test_helpers';
|
||||
import { ExportTypeDefinition, ReportingRequestHandlerContext } from '../types';
|
||||
} from '../../test_helpers';
|
||||
import { ExportTypeDefinition, ReportingRequestHandlerContext } from '../../types';
|
||||
import { registerJobInfoRoutes } from './jobs';
|
||||
|
||||
type SetupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
|
|
@ -5,21 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import Boom from '@hapi/boom';
|
||||
import { ROUTE_TAG_CAN_REDIRECT } from '../../../security/server';
|
||||
import { ReportingCore } from '../';
|
||||
import { API_BASE_URL } from '../../common/constants';
|
||||
import { authorizedUserPreRouting } from './lib/authorized_user_pre_routing';
|
||||
import { jobsQueryFactory } from './lib/jobs_query';
|
||||
import { deleteJobResponseHandler, downloadJobResponseHandler } from './lib/job_response_handler';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { ReportingCore } from '../../';
|
||||
import { ROUTE_TAG_CAN_REDIRECT } from '../../../../security/server';
|
||||
import { API_BASE_URL } from '../../../common/constants';
|
||||
import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing';
|
||||
import { jobsQueryFactory } from '../lib/jobs_query';
|
||||
import { deleteJobResponseHandler, downloadJobResponseHandler } from '../lib/job_response_handler';
|
||||
import { handleUnavailable } from '../lib/request_handler';
|
||||
|
||||
const MAIN_ENTRY = `${API_BASE_URL}/jobs`;
|
||||
|
||||
const handleUnavailable = (res: any) => {
|
||||
return res.custom({ statusCode: 503, body: 'Not Available' });
|
||||
};
|
||||
|
||||
export function registerJobInfoRoutes(reporting: ReportingCore) {
|
||||
const setupDeps = reporting.getPluginSetupDeps();
|
||||
const { router } = setupDeps;
|
|
@ -1,35 +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, KibanaResponseFactory } from 'src/core/server';
|
||||
import type {
|
||||
BaseParams,
|
||||
BaseParamsLegacyPDF,
|
||||
BasePayload,
|
||||
ReportingRequestHandlerContext,
|
||||
ReportingUser,
|
||||
} from '../types';
|
||||
|
||||
export type HandlerFunction = (
|
||||
user: ReportingUser,
|
||||
exportType: string,
|
||||
jobParams: BaseParams | BaseParamsLegacyPDF,
|
||||
context: ReportingRequestHandlerContext,
|
||||
req: KibanaRequest,
|
||||
res: KibanaResponseFactory
|
||||
) => any;
|
||||
|
||||
export type HandlerErrorFunction = (res: KibanaResponseFactory, err: Error) => any;
|
||||
|
||||
export interface QueuedJobPayload {
|
||||
error?: boolean;
|
||||
source: {
|
||||
job: {
|
||||
payload: BasePayload;
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue