From 733b26d83c01ef2b59410749e6b97a6f7faab63a Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Jan 2019 19:33:49 -0700 Subject: [PATCH 01/12] remove unused file --- .../common/lib/__tests__/get_absolute_time.js | 129 ------------------ .../common/lib/get_absolute_time.js | 25 ---- 2 files changed, 154 deletions(-) delete mode 100644 x-pack/plugins/reporting/export_types/common/lib/__tests__/get_absolute_time.js delete mode 100644 x-pack/plugins/reporting/export_types/common/lib/get_absolute_time.js diff --git a/x-pack/plugins/reporting/export_types/common/lib/__tests__/get_absolute_time.js b/x-pack/plugins/reporting/export_types/common/lib/__tests__/get_absolute_time.js deleted file mode 100644 index 915b94dd1219b..0000000000000 --- a/x-pack/plugins/reporting/export_types/common/lib/__tests__/get_absolute_time.js +++ /dev/null @@ -1,129 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from 'expect.js'; -import moment from 'moment'; -import sinon from 'sinon'; -import { getAbsoluteTime } from '../get_absolute_time'; - -describe('get_absolute_time', function () { - let anchor; - let unix; - let clock; - - beforeEach(() => { - anchor = '2016-07-04T14:16:32.123Z'; - unix = moment(anchor).valueOf(); - clock = sinon.useFakeTimers(unix); - }); - - afterEach(() => { - clock.restore(); - }); - - describe('invalid input', function () { - let timeObj; - - beforeEach(() => { - timeObj = { - mode: 'absolute', - from: '2016-07-04T14:01:32.123Z', - to: '2016-07-04T14:16:32.123Z', - }; - }); - - it('should return time if missing mode', function () { - delete timeObj.mode; - expect(getAbsoluteTime(timeObj)).to.equal(timeObj); - }); - - it('should return time if missing from', function () { - delete timeObj.from; - expect(getAbsoluteTime(timeObj)).to.equal(timeObj); - }); - - it('should return time if missing to', function () { - delete timeObj.to; - expect(getAbsoluteTime(timeObj)).to.equal(timeObj); - }); - }); - - describe('absolute time', function () { - let timeObj; - - beforeEach(() => { - timeObj = { - mode: 'absolute', - from: '2016-07-04T14:01:32.123Z', - to: '2016-07-04T14:16:32.123Z', - testParam: 'some value', - }; - }); - - it('should return time if already absolute', function () { - expect(getAbsoluteTime(timeObj)).to.equal(timeObj); - }); - }); - - describe('relative time', function () { - let timeObj; - - beforeEach(() => { - timeObj = { - mode: 'relative', - from: 'now-15m', - to: 'now', - testParam: 'some value', - }; - }); - - it('should return the absolute time', function () { - const output = getAbsoluteTime(timeObj); - expect(output.mode).to.equal('absolute'); - }); - - it('should map from and to values to times', function () { - const output = getAbsoluteTime(timeObj); - const check = { - from: '2016-07-04T14:01:32.123Z', - to: '2016-07-04T14:16:32.123Z', - }; - - expect(moment(output.from).toISOString()).to.equal(check.from); - expect(moment(output.to).toISOString()).to.equal(check.to); - }); - }); - - describe('quick time', function () { - let timeObj; - - beforeEach(() => { - timeObj = { - mode: 'quick', - from: 'now-1w/w', - to: 'now-1w/w', - testParam: 'some value', - }; - }); - - it('should return the absolute time', function () { - const output = getAbsoluteTime(timeObj); - expect(output.mode).to.equal('absolute'); - }); - - it('should map previous week values to times', function () { - const output = getAbsoluteTime(timeObj); - const check = { - from: /2016\-06\-2(5|6)T..\:00\:00\.000Z/, - to: /2016\-07\-0(2|3)T..\:59\:59\.999Z/, - }; - - expect(moment(output.from).toISOString()).to.match(check.from); - expect(moment(output.to).toISOString()).to.match(check.to); - }); - }); - -}); \ No newline at end of file diff --git a/x-pack/plugins/reporting/export_types/common/lib/get_absolute_time.js b/x-pack/plugins/reporting/export_types/common/lib/get_absolute_time.js deleted file mode 100644 index d211c7afadc6e..0000000000000 --- a/x-pack/plugins/reporting/export_types/common/lib/get_absolute_time.js +++ /dev/null @@ -1,25 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; -import datemath from '@elastic/datemath'; - -export function getAbsoluteTime(time) { - const mode = get(time, 'mode'); - const timeFrom = get(time, 'from'); - const timeTo = get(time, 'to'); - - if (!mode || !timeFrom || !timeTo) return time; - if (mode === 'absolute') { - return time; - } - - const output = { mode: 'absolute' }; - output.from = datemath.parse(timeFrom); - output.to = datemath.parse(timeTo, { roundUp: true }); - - return output; -} From a6c8662dd77824deb8c480b745938a75e10cf27f Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 4 Feb 2019 12:37:57 -0700 Subject: [PATCH 02/12] refactor routes files and helpers --- .../csv/server/lib/generate_csv.js | 13 +- x-pack/plugins/reporting/index.js | 6 +- .../reporting/server/routes/generate.js | 46 ++++++ .../plugins/reporting/server/routes/index.js | 53 +++++++ .../plugins/reporting/server/routes/jobs.js | 148 ++++++++---------- .../reporting/server/routes/jobs.test.js | 12 +- .../plugins/reporting/server/routes/legacy.js | 64 ++++++++ .../authorized_user_pre_routing.test.js} | 0 .../lib/authorized_user_pre_routing.js | 5 +- .../{ => routes}/lib/get_document_payload.js | 2 +- .../{ => routes}/lib/job_response_handler.js | 6 +- .../lib/reporting_feature_pre_routing.js | 2 +- .../routes/lib/route_config_factories.js | 58 +++++++ .../plugins/reporting/server/routes/main.js | 133 ---------------- 14 files changed, 311 insertions(+), 237 deletions(-) create mode 100644 x-pack/plugins/reporting/server/routes/generate.js create mode 100644 x-pack/plugins/reporting/server/routes/index.js create mode 100644 x-pack/plugins/reporting/server/routes/legacy.js rename x-pack/plugins/reporting/server/{lib/__tests__/authorized_user_pre_routing.js => routes/lib/__tests__/authorized_user_pre_routing.test.js} (100%) rename x-pack/plugins/reporting/server/{ => routes}/lib/authorized_user_pre_routing.js (92%) rename x-pack/plugins/reporting/server/{ => routes}/lib/get_document_payload.js (96%) rename x-pack/plugins/reporting/server/{ => routes}/lib/job_response_handler.js (89%) rename x-pack/plugins/reporting/server/{ => routes}/lib/reporting_feature_pre_routing.js (94%) create mode 100644 x-pack/plugins/reporting/server/routes/lib/route_config_factories.js delete mode 100644 x-pack/plugins/reporting/server/routes/main.js diff --git a/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js b/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js index 2d0133238bf82..0311db2716c39 100644 --- a/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js +++ b/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js @@ -14,22 +14,19 @@ export function createGenerateCsv(logger) { const hitIterator = createHitIterator(logger); return async function generateCsv({ + callEndpoint, searchRequest, + settings, fields, - formatsMap, metaFields, conflictedTypesFields, - callEndpoint, cancellationToken, - settings + formatsMap, }) { const escapeValue = createEscapeValue(settings.quoteValues); - const flattenHit = createFlattenHit(fields, metaFields, conflictedTypesFields); - const formatCsvValues = createFormatCsvValues(escapeValue, settings.separator, fields, formatsMap); - + const header = `${fields.map(escapeValue).join(settings.separator)}\n`; const builder = new MaxSizeStringBuilder(settings.maxSizeBytes); - const header = `${fields.map(escapeValue).join(settings.separator)}\n`; if (!builder.tryAppend(header)) { return { content: '', @@ -40,6 +37,8 @@ export function createGenerateCsv(logger) { const iterator = hitIterator(settings.scroll, callEndpoint, searchRequest, cancellationToken); let maxSizeReached = false; + const flattenHit = createFlattenHit(fields, metaFields, conflictedTypesFields); + const formatCsvValues = createFormatCsvValues(escapeValue, settings.separator, fields, formatsMap); try { while (true) { const { done, value: hit } = await iterator.next(); diff --git a/x-pack/plugins/reporting/index.js b/x-pack/plugins/reporting/index.js index e38dc453b5c86..51c94b6e757a1 100644 --- a/x-pack/plugins/reporting/index.js +++ b/x-pack/plugins/reporting/index.js @@ -7,8 +7,7 @@ import { resolve } from 'path'; import { UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; -import { main as mainRoutes } from './server/routes/main'; -import { jobs as jobRoutes } from './server/routes/jobs'; +import { registerRoutes } from './server/routes'; import { createQueueFactory } from './server/lib/create_queue'; import { config as appConfig } from './server/config/config'; @@ -174,8 +173,7 @@ export const reporting = (kibana) => { server.expose('queue', createQueueFactory(server)); // Reporting routes - mainRoutes(server); - jobRoutes(server); + registerRoutes(server); }, deprecations: function ({ unused }) { diff --git a/x-pack/plugins/reporting/server/routes/generate.js b/x-pack/plugins/reporting/server/routes/generate.js new file mode 100644 index 0000000000000..380ab045dd812 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/generate.js @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import boom from 'boom'; +import rison from 'rison-node'; +import { API_BASE_URL } from '../../common/constants'; +import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; + +const BASE_GENERATE = `${API_BASE_URL}/generate`; + +export function registerGenerate(server, handler, handleError) { + const getRouteConfig = getRouteConfigFactoryReportingPre(server); + + // generate report + server.route({ + path: `${BASE_GENERATE}/{exportType}`, + method: 'POST', + config: getRouteConfig(request => request.params.exportType), + handler: async (request, h) => { + const { exportType } = request.params; + let response; + try { + const jobParams = rison.decode(request.query.jobParams); + response = await handler(exportType, jobParams, request, h); + } catch (err) { + throw handleError(exportType, err); + } + return response; + }, + }); + + // show error about GET method to user + server.route({ + path: `${BASE_GENERATE}/{p*}`, + method: 'GET', + config: getRouteConfig(), + handler: () => { + const err = boom.methodNotAllowed('GET is not allowed'); + err.output.headers.allow = 'POST'; + return err; + }, + }); +} diff --git a/x-pack/plugins/reporting/server/routes/index.js b/x-pack/plugins/reporting/server/routes/index.js new file mode 100644 index 0000000000000..f5da38842128b --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/index.js @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import boom from 'boom'; +import { API_BASE_URL } from '../../common/constants'; +import { enqueueJobFactory } from '../lib/enqueue_job'; +import { registerLegacy } from './legacy'; +import { registerGenerate } from './generate'; +import { registerJobs } from './jobs'; + +export function registerRoutes(server) { + const config = server.config(); + const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; + const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); + const enqueueJob = enqueueJobFactory(server); + + async function handler(exportTypeId, jobParams, request, h) { + const user = request.pre.user; + const headers = request.headers; + + const job = await enqueueJob(exportTypeId, jobParams, user, headers, request); + + // return the queue's job information + const jobJson = job.toJSON(); + + return h + .response({ + path: `${DOWNLOAD_BASE_URL}/${jobJson.id}`, + job: jobJson, + }) + .type('application/json'); + } + + function handleError(exportType, err) { + if (err instanceof esErrors['401']) { + return boom.unauthorized(`Sorry, you aren't authenticated`); + } + if (err instanceof esErrors['403']) { + return boom.forbidden(`Sorry, you are not authorized to create ${exportType} reports`); + } + if (err instanceof esErrors['404']) { + return boom.boomify(err, { statusCode: 404 }); + } + return err; + } + + registerGenerate(server, handler, handleError); + registerJobs(server, handler, handleError); + registerLegacy(server, handler, handleError); +} diff --git a/x-pack/plugins/reporting/server/routes/jobs.js b/x-pack/plugins/reporting/server/routes/jobs.js index afbdb9cc7e213..293c980baa10d 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.js +++ b/x-pack/plugins/reporting/server/routes/jobs.js @@ -7,130 +7,127 @@ import boom from 'boom'; import { API_BASE_URL } from '../../common/constants'; import { jobsQueryFactory } from '../lib/jobs_query'; -import { reportingFeaturePreRoutingFactory } from'../lib/reporting_feature_pre_routing'; -import { authorizedUserPreRoutingFactory } from '../lib/authorized_user_pre_routing'; -import { jobResponseHandlerFactory } from '../lib/job_response_handler'; +import { jobResponseHandlerFactory } from './lib/job_response_handler'; +import { + getRouteConfigFactoryManagementPre, + getRouteConfigFactoryDownloadPre, +} from './lib/route_config_factories'; -const mainEntry = `${API_BASE_URL}/jobs`; -const API_TAG = 'api'; +const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export function jobs(server) { +export function registerJobs(server) { const jobsQuery = jobsQueryFactory(server); - const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); - const jobResponseHandler = jobResponseHandlerFactory(server); - - const managementPreRouting = reportingFeaturePreRouting(() => 'management'); - - function getRouteConfig() { - return { - pre: [ - { method: authorizedUserPreRouting, assign: 'user' }, - { method: managementPreRouting, assign: 'management' }, - ], - }; - } + const getRouteConfig = getRouteConfigFactoryManagementPre(server); + const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server); // list jobs in the queue, paginated server.route({ - path: `${mainEntry}/list`, + path: `${MAIN_ENTRY}/list`, method: 'GET', - handler: (request) => { + config: getRouteConfig(), + handler: request => { const page = parseInt(request.query.page) || 0; const size = Math.min(100, parseInt(request.query.size) || 10); const jobIds = request.query.ids ? request.query.ids.split(',') : null; - const results = jobsQuery.list(request.pre.management.jobTypes, request.pre.user, page, size, jobIds); + const results = jobsQuery.list( + request.pre.management.jobTypes, + request.pre.user, + page, + size, + jobIds + ); return results; }, - config: getRouteConfig(), }); // return the count of all jobs in the queue server.route({ - path: `${mainEntry}/count`, + path: `${MAIN_ENTRY}/count`, method: 'GET', - handler: (request) => { + config: getRouteConfig(), + handler: request => { const results = jobsQuery.count(request.pre.management.jobTypes, request.pre.user); return results; }, - config: getRouteConfig(), }); // return the raw output from a job server.route({ - path: `${mainEntry}/output/{docId}`, + path: `${MAIN_ENTRY}/output/{docId}`, method: 'GET', - handler: (request) => { + config: getRouteConfig(), + handler: request => { const { docId } = request.params; - return jobsQuery.get(request.pre.user, docId, { includeContent: true }) - .then((doc) => { - if (!doc) { - return boom.notFound(); - } + return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then(doc => { + if (!doc) { + return boom.notFound(); + } - const { jobtype: jobType } = doc._source; - if (!request.pre.management.jobTypes.includes(jobType)) { - return boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); - } + const { jobtype: jobType } = doc._source; + if (!request.pre.management.jobTypes.includes(jobType)) { + return boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); + } - return doc._source.output; - }); + return doc._source.output; + }); }, - config: getRouteConfig(), }); // return some info about the job server.route({ - path: `${mainEntry}/info/{docId}`, + path: `${MAIN_ENTRY}/info/{docId}`, method: 'GET', - handler: (request) => { + config: getRouteConfig(), + handler: request => { const { docId } = request.params; - return jobsQuery.get(request.pre.user, docId) - .then((doc) => { - if (!doc) { - return boom.notFound(); - } - - const { jobtype: jobType } = doc._source; - if (!request.pre.management.jobTypes.includes(jobType)) { - return boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); - } - - const { payload } = doc._source; - payload.headers = 'not shown'; - - return { - ...doc._source, - payload - }; - }); + return jobsQuery.get(request.pre.user, docId).then(doc => { + if (!doc) { + return boom.notFound(); + } + + const { jobtype: jobType } = doc._source; + if (!request.pre.management.jobTypes.includes(jobType)) { + return boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + } + + const { payload } = doc._source; + payload.headers = 'not shown'; + + return { + ...doc._source, + payload, + }; + }); }, - config: getRouteConfig(), }); // trigger a download of the output from a job - // NOTE: We're disabling range request for downloading the PDF. There's a bug in Firefox's PDF.js viewer - // (https://github.com/mozilla/pdf.js/issues/8958) where they're using a range request to retrieve the - // TOC at the end of the PDF, but it's sending multiple cookies and causing our auth to fail with a 401. - // Additionally, the range-request doesn't alleviate any performance issues on the server as the entire - // download is loaded into memory. + const jobResponseHandler = jobResponseHandlerFactory(server); server.route({ - path: `${mainEntry}/download/{docId}`, + path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', + config: getRouteConfigDownload(), handler: async (request, h) => { const { docId } = request.params; - let response = await jobResponseHandler(request.pre.management.jobTypes, request.pre.user, h, { docId }); + let response = await jobResponseHandler( + request.pre.management.jobTypes, + request.pre.user, + h, + { docId } + ); const { statusCode } = response; if (statusCode !== 200) { const logLevel = statusCode === 500 ? 'error' : 'debug'; server.log( - [logLevel, "reporting", "download"], - `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify(response.source)}]` + [logLevel, 'reporting', 'download'], + `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( + response.source + )}]` ); } @@ -140,12 +137,5 @@ export function jobs(server) { return response; }, - config: { - ...getRouteConfig(), - tags: [API_TAG], - response: { - ranges: false - } - }, }); } diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.js b/x-pack/plugins/reporting/server/routes/jobs.test.js index 006c5853c2770..3d866d1e7d458 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.test.js +++ b/x-pack/plugins/reporting/server/routes/jobs.test.js @@ -6,7 +6,7 @@ import Hapi from 'hapi'; import { difference, memoize } from 'lodash'; -import { jobs } from './jobs'; +import { registerJobs } from './jobs'; import { ExportTypesRegistry } from '../../common/export_types_registry'; jest.mock('../lib/authorized_user_pre_routing', () => { return { @@ -63,7 +63,7 @@ test(`returns 404 if job not found`, async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits())); - jobs(mockServer); + registerJobs(mockServer); const request = { method: 'GET', @@ -79,7 +79,7 @@ test(`returns 401 if not valid job type`, async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))); - jobs(mockServer); + registerJobs(mockServer); const request = { method: 'GET', @@ -96,7 +96,7 @@ describe(`when job is incomplete`, () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' }))); - jobs(mockServer); + registerJobs(mockServer); const request = { method: 'GET', @@ -133,7 +133,7 @@ describe(`when job is failed`, () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(hits)); - jobs(mockServer); + registerJobs(mockServer); const request = { method: 'GET', @@ -178,7 +178,7 @@ describe(`when job is completed`, () => { }); mockServer.plugins.elasticsearch.getCluster('admin').callWithInternalUser.mockReturnValue(Promise.resolve(hits)); - jobs(mockServer); + registerJobs(mockServer); const request = { method: 'GET', diff --git a/x-pack/plugins/reporting/server/routes/legacy.js b/x-pack/plugins/reporting/server/routes/legacy.js new file mode 100644 index 0000000000000..5be40db6980ff --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/legacy.js @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import querystring from 'querystring'; +import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; +import { API_BASE_URL } from '../../common/constants'; + +const getStaticFeatureConfig = (getRouteConfig, featureId) => getRouteConfig(() => featureId); +const BASE_GENERATE = `${API_BASE_URL}/generate`; + +export function registerLegacy(server, handler, handleError) { + const getRouteConfig = getRouteConfigFactoryReportingPre(server); + + function createLegacyPdfRoute(server, handler, { path, objectType }) { + const exportTypeId = 'printablePdf'; + server.route({ + path: path, + method: 'POST', + config: getStaticFeatureConfig(getRouteConfig, exportTypeId), + handler: async (request, h) => { + const message = `The following URL is deprecated and will stop working in the next major version: ${ + request.url.path + }`; + server.log(['warning', 'reporting', 'deprecation'], message); + + try { + const savedObjectId = request.params.savedId; + const queryString = querystring.stringify(request.query); + + return await handler( + exportTypeId, + { + objectType, + savedObjectId, + queryString, + }, + request, + h + ); + } catch (err) { + throw handleError(exportTypeId, err); + } + }, + }); + } + + createLegacyPdfRoute(server, handler, { + path: `${BASE_GENERATE}/visualization/{savedId}`, + objectType: 'visualization', + }); + + createLegacyPdfRoute(server, handler, { + path: `${BASE_GENERATE}/search/{savedId}`, + objectType: 'search', + }); + + createLegacyPdfRoute(server, handler, { + path: `${BASE_GENERATE}/dashboard/{savedId}`, + objectType: 'dashboard', + }); +} diff --git a/x-pack/plugins/reporting/server/lib/__tests__/authorized_user_pre_routing.js b/x-pack/plugins/reporting/server/routes/lib/__tests__/authorized_user_pre_routing.test.js similarity index 100% rename from x-pack/plugins/reporting/server/lib/__tests__/authorized_user_pre_routing.js rename to x-pack/plugins/reporting/server/routes/lib/__tests__/authorized_user_pre_routing.test.js diff --git a/x-pack/plugins/reporting/server/lib/authorized_user_pre_routing.js b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js similarity index 92% rename from x-pack/plugins/reporting/server/lib/authorized_user_pre_routing.js rename to x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js index bace2e39105aa..10ff9f477f424 100644 --- a/x-pack/plugins/reporting/server/lib/authorized_user_pre_routing.js +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js @@ -5,8 +5,8 @@ */ import boom from 'boom'; -import { getUserFactory } from './get_user'; -import { oncePerServer } from './once_per_server'; +import { getUserFactory } from '../../lib/get_user'; +import { oncePerServer } from '../../lib/once_per_server'; const superuserRole = 'superuser'; @@ -43,4 +43,3 @@ function authorizedUserPreRoutingFn(server) { } export const authorizedUserPreRoutingFactory = oncePerServer(authorizedUserPreRoutingFn); - diff --git a/x-pack/plugins/reporting/server/lib/get_document_payload.js b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.js similarity index 96% rename from x-pack/plugins/reporting/server/lib/get_document_payload.js rename to x-pack/plugins/reporting/server/routes/lib/get_document_payload.js index 8ed0439034c60..87f8254a343b9 100644 --- a/x-pack/plugins/reporting/server/lib/get_document_payload.js +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { oncePerServer } from './once_per_server'; +import { oncePerServer } from '../../lib/once_per_server'; function getDocumentPayloadFn(server) { const exportTypesRegistry = server.plugins.reporting.exportTypesRegistry; diff --git a/x-pack/plugins/reporting/server/lib/job_response_handler.js b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.js similarity index 89% rename from x-pack/plugins/reporting/server/lib/job_response_handler.js rename to x-pack/plugins/reporting/server/routes/lib/job_response_handler.js index 05f9313583a35..75bffcafc5c33 100644 --- a/x-pack/plugins/reporting/server/lib/job_response_handler.js +++ b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.js @@ -5,10 +5,10 @@ */ import boom from 'boom'; -import { oncePerServer } from './once_per_server'; -import { jobsQueryFactory } from './jobs_query'; +import { oncePerServer } from '../../lib/once_per_server'; +import { jobsQueryFactory } from '../../lib/jobs_query'; +import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; import { getDocumentPayloadFactory } from './get_document_payload'; -import { WHITELISTED_JOB_CONTENT_TYPES } from '../../common/constants'; function jobResponseHandlerFn(server) { const jobsQuery = jobsQueryFactory(server); diff --git a/x-pack/plugins/reporting/server/lib/reporting_feature_pre_routing.js b/x-pack/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js similarity index 94% rename from x-pack/plugins/reporting/server/lib/reporting_feature_pre_routing.js rename to x-pack/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js index 5a2d2a6213ef9..ad91e5a654a4e 100644 --- a/x-pack/plugins/reporting/server/lib/reporting_feature_pre_routing.js +++ b/x-pack/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { oncePerServer } from './once_per_server'; +import { oncePerServer } from '../../lib/once_per_server'; function reportingFeaturePreRoutingFn(server) { const xpackMainPlugin = server.plugins.xpack_main; diff --git a/x-pack/plugins/reporting/server/routes/lib/route_config_factories.js b/x-pack/plugins/reporting/server/routes/lib/route_config_factories.js new file mode 100644 index 0000000000000..53569457ff034 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/route_config_factories.js @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; +import { reportingFeaturePreRoutingFactory } from './reporting_feature_pre_routing'; + +const API_TAG = 'api'; + +export function getRouteConfigFactoryReportingPre(server) { + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); + const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); + + return getFeatureId => { + const preRouting = [{ method: authorizedUserPreRouting, assign: 'user' }]; + if (getFeatureId) { + preRouting.push(reportingFeaturePreRouting(getFeatureId)); + } + + return { + tags: [API_TAG], + pre: preRouting, + }; + }; +} + +export function getRouteConfigFactoryManagementPre(server) { + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); + const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); + const managementPreRouting = reportingFeaturePreRouting(() => 'management'); + + return () => { + return { + pre: [ + { method: authorizedUserPreRouting, assign: 'user' }, + { method: managementPreRouting, assign: 'management' }, + ], + }; + }; +} + +// NOTE: We're disabling range request for downloading the PDF. There's a bug in Firefox's PDF.js viewer +// (https://github.com/mozilla/pdf.js/issues/8958) where they're using a range request to retrieve the +// TOC at the end of the PDF, but it's sending multiple cookies and causing our auth to fail with a 401. +// Additionally, the range-request doesn't alleviate any performance issues on the server as the entire +// download is loaded into memory. +export function getRouteConfigFactoryDownloadPre(server) { + const getManagementRouteConfig = getRouteConfigFactoryManagementPre(server); + return () => ({ + ...getManagementRouteConfig(), + tags: [API_TAG], + response: { + ranges: false, + }, + }); +} diff --git a/x-pack/plugins/reporting/server/routes/main.js b/x-pack/plugins/reporting/server/routes/main.js deleted file mode 100644 index f68ada5ac89df..0000000000000 --- a/x-pack/plugins/reporting/server/routes/main.js +++ /dev/null @@ -1,133 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import boom from 'boom'; -import { API_BASE_URL } from '../../common/constants'; -import { enqueueJobFactory } from '../lib/enqueue_job'; -import { reportingFeaturePreRoutingFactory } from '../lib/reporting_feature_pre_routing'; -import { authorizedUserPreRoutingFactory } from '../lib/authorized_user_pre_routing'; -import rison from 'rison-node'; -import querystring from 'querystring'; - -const mainEntry = `${API_BASE_URL}/generate`; -const API_TAG = 'api'; - -export function main(server) { - const config = server.config(); - const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; - const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); - - const enqueueJob = enqueueJobFactory(server); - const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); - - function getRouteConfig(getFeatureId) { - const preRouting = [ { method: authorizedUserPreRouting, assign: 'user' } ]; - if (getFeatureId) { - preRouting.push(reportingFeaturePreRouting(getFeatureId)); - } - - return { - tags: [API_TAG], - pre: preRouting, - }; - } - - function getStaticFeatureConfig(featureId) { - return getRouteConfig(() => featureId); - } - - // show error about method to user - server.route({ - path: `${mainEntry}/{p*}`, - method: 'GET', - handler: () => { - const err = boom.methodNotAllowed('GET is not allowed'); - err.output.headers.allow = 'POST'; - return err; - }, - config: getRouteConfig(), - }); - - function createLegacyPdfRoute({ path, objectType }) { - const exportTypeId = 'printablePdf'; - server.route({ - path: path, - method: 'POST', - handler: async (request, h) => { - const message = `The following URL is deprecated and will stop working in the next major version: ${request.url.path}`; - server.log(['warning', 'reporting', 'deprecation'], message); - - try { - const savedObjectId = request.params.savedId; - const queryString = querystring.stringify(request.query); - - return await handler(exportTypeId, { - objectType, - savedObjectId, - queryString - }, request, h); - } catch (err) { - throw handleError(exportTypeId, err); - } - }, - config: getStaticFeatureConfig(exportTypeId), - }); - } - - createLegacyPdfRoute({ - path: `${mainEntry}/visualization/{savedId}`, - objectType: 'visualization', - }); - - createLegacyPdfRoute({ - path: `${mainEntry}/search/{savedId}`, - objectType: 'search', - }); - - createLegacyPdfRoute({ - path: `${mainEntry}/dashboard/{savedId}`, - objectType: 'dashboard' - }); - - server.route({ - path: `${mainEntry}/{exportType}`, - method: 'POST', - handler: async (request, h) => { - const exportType = request.params.exportType; - try { - const jobParams = rison.decode(request.query.jobParams); - return await handler(exportType, jobParams, request, h); - } catch (err) { - throw handleError(exportType, err); - } - }, - config: getRouteConfig(request => request.params.exportType), - }); - - async function handler(exportTypeId, jobParams, request, h) { - const user = request.pre.user; - const headers = request.headers; - - const job = await enqueueJob(exportTypeId, jobParams, user, headers, request); - - // return the queue's job information - const jobJson = job.toJSON(); - - return h.response({ - path: `${DOWNLOAD_BASE_URL}/${jobJson.id}`, - job: jobJson, - }) - .type('application/json'); - } - - function handleError(exportType, err) { - if (err instanceof esErrors['401']) return boom.unauthorized(`Sorry, you aren't authenticated`); - if (err instanceof esErrors['403']) return boom.forbidden(`Sorry, you are not authorized to create ${exportType} reports`); - if (err instanceof esErrors['404']) return boom.boomify(err, { statusCode: 404 }); - return err; - } -} From a2a4a24be6c9499fad1986db7c429c2c7b13efef Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 4 Feb 2019 12:38:37 -0700 Subject: [PATCH 03/12] default empty array for conflictedTypesFields --- .../reporting/export_types/csv/server/lib/flatten_hit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js b/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js index 38bcd98aa1de4..98531b4436d05 100644 --- a/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js +++ b/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js @@ -6,7 +6,7 @@ import _ from 'lodash'; -export function createFlattenHit(fields, metaFields, conflictedTypesFields) { +export function createFlattenHit(fields, metaFields, conflictedTypesFields = []) { const flattenSource = (flat, obj, keyPrefix) => { keyPrefix = keyPrefix ? keyPrefix + '.' : ''; _.forOwn(obj, (val, key) => { From 27dbc8cdaf17fdb9553fcd0354acf93d0a29ba1a Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 4 Feb 2019 12:38:48 -0700 Subject: [PATCH 04/12] minor prettier --- .../export_types/csv/server/lib/field_format_map.js | 6 +++--- .../reporting/server/usage/get_reporting_type_counts.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js b/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js index 20e1b5ed15973..87420be59301d 100644 --- a/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js +++ b/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js @@ -14,13 +14,12 @@ import _ from 'lodash'; * @return {Map} key: field name, value: FieldFormat instance */ export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { - const formatsMap = new Map(); //Add FieldFormat instances for fields with custom formatters if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) { const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap); - Object.keys(fieldFormatMap).forEach((fieldName) => { + Object.keys(fieldFormatMap).forEach(fieldName => { const formatConfig = fieldFormatMap[fieldName]; if (!_.isEmpty(formatConfig)) { @@ -31,7 +30,8 @@ export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { //Add default FieldFormat instances for all other fields const indexFields = JSON.parse(_.get(indexPatternSavedObject, 'attributes.fields', '[]')); - indexFields.forEach((field) => { + // for example, type date needs formatting as utc + indexFields.forEach(field => { if (!formatsMap.has(field.name)) { formatsMap.set(field.name, fieldFormats.getDefaultInstance(field.type)); } diff --git a/x-pack/plugins/reporting/server/usage/get_reporting_type_counts.js b/x-pack/plugins/reporting/server/usage/get_reporting_type_counts.js index a831bb4c4fc64..1f6524bf85d31 100644 --- a/x-pack/plugins/reporting/server/usage/get_reporting_type_counts.js +++ b/x-pack/plugins/reporting/server/usage/get_reporting_type_counts.js @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import _ from 'lodash'; /* From 267cd9179d3460201210a3a32118f388f854e331 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 14:25:33 -0700 Subject: [PATCH 05/12] remove some unrelated diff --- .../export_types/csv/server/lib/field_format_map.js | 6 +++--- .../reporting/export_types/csv/server/lib/flatten_hit.js | 2 +- .../export_types/csv/server/lib/generate_csv.js | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js b/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js index 87420be59301d..20e1b5ed15973 100644 --- a/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js +++ b/x-pack/plugins/reporting/export_types/csv/server/lib/field_format_map.js @@ -14,12 +14,13 @@ import _ from 'lodash'; * @return {Map} key: field name, value: FieldFormat instance */ export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { + const formatsMap = new Map(); //Add FieldFormat instances for fields with custom formatters if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) { const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap); - Object.keys(fieldFormatMap).forEach(fieldName => { + Object.keys(fieldFormatMap).forEach((fieldName) => { const formatConfig = fieldFormatMap[fieldName]; if (!_.isEmpty(formatConfig)) { @@ -30,8 +31,7 @@ export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { //Add default FieldFormat instances for all other fields const indexFields = JSON.parse(_.get(indexPatternSavedObject, 'attributes.fields', '[]')); - // for example, type date needs formatting as utc - indexFields.forEach(field => { + indexFields.forEach((field) => { if (!formatsMap.has(field.name)) { formatsMap.set(field.name, fieldFormats.getDefaultInstance(field.type)); } diff --git a/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js b/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js index 98531b4436d05..38bcd98aa1de4 100644 --- a/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js +++ b/x-pack/plugins/reporting/export_types/csv/server/lib/flatten_hit.js @@ -6,7 +6,7 @@ import _ from 'lodash'; -export function createFlattenHit(fields, metaFields, conflictedTypesFields = []) { +export function createFlattenHit(fields, metaFields, conflictedTypesFields) { const flattenSource = (flat, obj, keyPrefix) => { keyPrefix = keyPrefix ? keyPrefix + '.' : ''; _.forOwn(obj, (val, key) => { diff --git a/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js b/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js index 5cd1ca893aa74..b214c1083058f 100644 --- a/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js +++ b/x-pack/plugins/reporting/export_types/csv/server/lib/generate_csv.js @@ -14,19 +14,18 @@ export function createGenerateCsv(logger) { const hitIterator = createHitIterator(logger); return async function generateCsv({ - callEndpoint, searchRequest, - settings, fields, + formatsMap, metaFields, conflictedTypesFields, + callEndpoint, cancellationToken, - formatsMap, + settings }) { const escapeValue = createEscapeValue(settings.quoteValues); - const header = `${fields.map(escapeValue).join(settings.separator)}\n`; const builder = new MaxSizeStringBuilder(settings.maxSizeBytes); - + const header = `${fields.map(escapeValue).join(settings.separator)}\n`; if (!builder.tryAppend(header)) { return { content: '', From 6bfce10c327a4cfb91c83fc1ff02ef24c1367459 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 15:41:52 -0700 Subject: [PATCH 06/12] some typescript conversions --- .../routes/{generate.js => generate.ts} | 12 +++++-- ...factories.js => route_config_factories.ts} | 31 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) rename x-pack/plugins/reporting/server/routes/{generate.js => generate.ts} (78%) rename x-pack/plugins/reporting/server/routes/lib/{route_config_factories.js => route_config_factories.ts} (66%) diff --git a/x-pack/plugins/reporting/server/routes/generate.js b/x-pack/plugins/reporting/server/routes/generate.ts similarity index 78% rename from x-pack/plugins/reporting/server/routes/generate.js rename to x-pack/plugins/reporting/server/routes/generate.ts index 380ab045dd812..8db64f354169f 100644 --- a/x-pack/plugins/reporting/server/routes/generate.js +++ b/x-pack/plugins/reporting/server/routes/generate.ts @@ -5,13 +5,21 @@ */ import boom from 'boom'; +import { Request } from 'hapi'; import rison from 'rison-node'; import { API_BASE_URL } from '../../common/constants'; import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; const BASE_GENERATE = `${API_BASE_URL}/generate`; -export function registerGenerate(server, handler, handleError) { +type HandlerFunction = (exportType: any, jobParams: any, request: Request, h: any) => any; +type HandlerErrorFunction = (exportType: any, err: Error) => any; + +export function registerGenerate( + server: any, + handler: HandlerFunction, + handleError: HandlerErrorFunction +) { const getRouteConfig = getRouteConfigFactoryReportingPre(server); // generate report @@ -19,7 +27,7 @@ export function registerGenerate(server, handler, handleError) { path: `${BASE_GENERATE}/{exportType}`, method: 'POST', config: getRouteConfig(request => request.params.exportType), - handler: async (request, h) => { + handler: async (request: any, h: HandlerFunction) => { const { exportType } = request.params; let response; try { diff --git a/x-pack/plugins/reporting/server/routes/lib/route_config_factories.js b/x-pack/plugins/reporting/server/routes/lib/route_config_factories.ts similarity index 66% rename from x-pack/plugins/reporting/server/routes/lib/route_config_factories.js rename to x-pack/plugins/reporting/server/routes/lib/route_config_factories.ts index 53569457ff034..2396694d5c56f 100644 --- a/x-pack/plugins/reporting/server/routes/lib/route_config_factories.js +++ b/x-pack/plugins/reporting/server/routes/lib/route_config_factories.ts @@ -4,16 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Request } from 'hapi'; +import { KbnServer } from '../../../types'; +// @ts-ignore import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; +// @ts-ignore import { reportingFeaturePreRoutingFactory } from './reporting_feature_pre_routing'; const API_TAG = 'api'; -export function getRouteConfigFactoryReportingPre(server) { - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); - const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); +interface RouteConfigFactory { + tags?: string[]; + pre: any[]; + response?: { + ranges: boolean; + }; +} + +type GetFeatureFunction = (request: Request) => any; +type PreRoutingFunction = (getFeatureId?: GetFeatureFunction) => any; + +export function getRouteConfigFactoryReportingPre(server: KbnServer) { + const authorizedUserPreRouting: PreRoutingFunction = authorizedUserPreRoutingFactory(server); + const reportingFeaturePreRouting: PreRoutingFunction = reportingFeaturePreRoutingFactory(server); - return getFeatureId => { + return (getFeatureId?: GetFeatureFunction): RouteConfigFactory => { const preRouting = [{ method: authorizedUserPreRouting, assign: 'user' }]; if (getFeatureId) { preRouting.push(reportingFeaturePreRouting(getFeatureId)); @@ -26,12 +41,12 @@ export function getRouteConfigFactoryReportingPre(server) { }; } -export function getRouteConfigFactoryManagementPre(server) { +export function getRouteConfigFactoryManagementPre(server: KbnServer) { const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); const managementPreRouting = reportingFeaturePreRouting(() => 'management'); - return () => { + return (): RouteConfigFactory => { return { pre: [ { method: authorizedUserPreRouting, assign: 'user' }, @@ -46,9 +61,9 @@ export function getRouteConfigFactoryManagementPre(server) { // TOC at the end of the PDF, but it's sending multiple cookies and causing our auth to fail with a 401. // Additionally, the range-request doesn't alleviate any performance issues on the server as the entire // download is loaded into memory. -export function getRouteConfigFactoryDownloadPre(server) { +export function getRouteConfigFactoryDownloadPre(server: KbnServer) { const getManagementRouteConfig = getRouteConfigFactoryManagementPre(server); - return () => ({ + return (): RouteConfigFactory => ({ ...getManagementRouteConfig(), tags: [API_TAG], response: { From 027cdd8349e9a5efaf43597fe423ff269fda7875 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 22:22:49 -0700 Subject: [PATCH 07/12] more typescripts --- .../reporting/server/routes/generate.ts | 12 ++-- .../server/routes/{index.js => index.ts} | 14 ++-- .../server/routes/{jobs.js => jobs.ts} | 65 ++++++++++++------- .../server/routes/{legacy.js => legacy.ts} | 26 +++++--- .../reporting/server/routes/types.d.ts | 16 +++++ x-pack/plugins/reporting/types.d.ts | 2 + 6 files changed, 93 insertions(+), 42 deletions(-) rename x-pack/plugins/reporting/server/routes/{index.js => index.ts} (82%) rename x-pack/plugins/reporting/server/routes/{jobs.js => jobs.ts} (71%) rename x-pack/plugins/reporting/server/routes/{legacy.js => legacy.ts} (72%) create mode 100644 x-pack/plugins/reporting/server/routes/types.d.ts diff --git a/x-pack/plugins/reporting/server/routes/generate.ts b/x-pack/plugins/reporting/server/routes/generate.ts index 8db64f354169f..c1a3e39d21e9c 100644 --- a/x-pack/plugins/reporting/server/routes/generate.ts +++ b/x-pack/plugins/reporting/server/routes/generate.ts @@ -5,18 +5,17 @@ */ import boom from 'boom'; -import { Request } from 'hapi'; +import { Request, ResponseToolkit } from 'hapi'; import rison from 'rison-node'; import { API_BASE_URL } from '../../common/constants'; +import { KbnServer } from '../../types'; import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; +import { HandlerErrorFunction, HandlerFunction } from './types'; const BASE_GENERATE = `${API_BASE_URL}/generate`; -type HandlerFunction = (exportType: any, jobParams: any, request: Request, h: any) => any; -type HandlerErrorFunction = (exportType: any, err: Error) => any; - export function registerGenerate( - server: any, + server: KbnServer, handler: HandlerFunction, handleError: HandlerErrorFunction ) { @@ -27,10 +26,11 @@ export function registerGenerate( path: `${BASE_GENERATE}/{exportType}`, method: 'POST', config: getRouteConfig(request => request.params.exportType), - handler: async (request: any, h: HandlerFunction) => { + handler: async (request: Request, h: ResponseToolkit) => { const { exportType } = request.params; let response; try { + // @ts-ignore const jobParams = rison.decode(request.query.jobParams); response = await handler(exportType, jobParams, request, h); } catch (err) { diff --git a/x-pack/plugins/reporting/server/routes/index.js b/x-pack/plugins/reporting/server/routes/index.ts similarity index 82% rename from x-pack/plugins/reporting/server/routes/index.js rename to x-pack/plugins/reporting/server/routes/index.ts index f5da38842128b..f51634056b1b3 100644 --- a/x-pack/plugins/reporting/server/routes/index.js +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -5,19 +5,23 @@ */ import boom from 'boom'; +import { Request, ResponseToolkit } from 'hapi'; import { API_BASE_URL } from '../../common/constants'; +import { KbnServer } from '../../types'; +// @ts-ignore import { enqueueJobFactory } from '../lib/enqueue_job'; -import { registerLegacy } from './legacy'; import { registerGenerate } from './generate'; import { registerJobs } from './jobs'; +import { registerLegacy } from './legacy'; -export function registerRoutes(server) { +export function registerRoutes(server: KbnServer) { const config = server.config(); const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); const enqueueJob = enqueueJobFactory(server); - async function handler(exportTypeId, jobParams, request, h) { + async function handler(exportTypeId: any, jobParams: any, request: Request, h: ResponseToolkit) { + // @ts-ignore const user = request.pre.user; const headers = request.headers; @@ -34,7 +38,7 @@ export function registerRoutes(server) { .type('application/json'); } - function handleError(exportType, err) { + function handleError(exportType: any, err: Error) { if (err instanceof esErrors['401']) { return boom.unauthorized(`Sorry, you aren't authenticated`); } @@ -48,6 +52,6 @@ export function registerRoutes(server) { } registerGenerate(server, handler, handleError); - registerJobs(server, handler, handleError); registerLegacy(server, handler, handleError); + registerJobs(server); } diff --git a/x-pack/plugins/reporting/server/routes/jobs.js b/x-pack/plugins/reporting/server/routes/jobs.ts similarity index 71% rename from x-pack/plugins/reporting/server/routes/jobs.js rename to x-pack/plugins/reporting/server/routes/jobs.ts index 293c980baa10d..5ae3606d2c07a 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.js +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -5,17 +5,31 @@ */ import boom from 'boom'; +import { Request, ResponseToolkit } from 'hapi'; import { API_BASE_URL } from '../../common/constants'; +import { KbnServer } from '../../types'; +// @ts-ignore import { jobsQueryFactory } from '../lib/jobs_query'; +// @ts-ignore import { jobResponseHandlerFactory } from './lib/job_response_handler'; import { - getRouteConfigFactoryManagementPre, getRouteConfigFactoryDownloadPre, + getRouteConfigFactoryManagementPre, } from './lib/route_config_factories'; const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export function registerJobs(server) { +interface JobDoc { + _source: { + output: any; + jobtype: any; + payload: { + headers: string; + }; + }; +} + +export function registerJobs(server: KbnServer) { const jobsQuery = jobsQueryFactory(server); const getRouteConfig = getRouteConfigFactoryManagementPre(server); const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server); @@ -25,9 +39,12 @@ export function registerJobs(server) { path: `${MAIN_ENTRY}/list`, method: 'GET', config: getRouteConfig(), - handler: request => { - const page = parseInt(request.query.page) || 0; - const size = Math.min(100, parseInt(request.query.size) || 10); + handler: (request: Request) => { + // @ts-ignore + const page = parseInt(request.query.page, 10) || 0; + // @ts-ignore + const size = Math.min(100, parseInt(request.query.size, 10) || 10); + // @ts-ignore const jobIds = request.query.ids ? request.query.ids.split(',') : null; const results = jobsQuery.list( @@ -46,7 +63,7 @@ export function registerJobs(server) { path: `${MAIN_ENTRY}/count`, method: 'GET', config: getRouteConfig(), - handler: request => { + handler: (request: Request) => { const results = jobsQuery.count(request.pre.management.jobTypes, request.pre.user); return results; }, @@ -57,21 +74,25 @@ export function registerJobs(server) { path: `${MAIN_ENTRY}/output/{docId}`, method: 'GET', config: getRouteConfig(), - handler: request => { + handler: (request: Request) => { const { docId } = request.params; - return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then(doc => { - if (!doc) { - return boom.notFound(); - } - - const { jobtype: jobType } = doc._source; - if (!request.pre.management.jobTypes.includes(jobType)) { - return boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); - } - - return doc._source.output; - }); + return jobsQuery + .get(request.pre.user, docId, { includeContent: true }) + .then((doc: JobDoc) => { + if (!doc) { + return boom.notFound(); + } + + const { jobtype: jobType } = doc._source; + if (!request.pre.management.jobTypes.includes(jobType)) { + return boom.unauthorized( + `Sorry, you are not authorized to download ${jobType} reports` + ); + } + + return doc._source.output; + }); }, }); @@ -80,10 +101,10 @@ export function registerJobs(server) { path: `${MAIN_ENTRY}/info/{docId}`, method: 'GET', config: getRouteConfig(), - handler: request => { + handler: (request: Request) => { const { docId } = request.params; - return jobsQuery.get(request.pre.user, docId).then(doc => { + return jobsQuery.get(request.pre.user, docId).then((doc: JobDoc) => { if (!doc) { return boom.notFound(); } @@ -110,7 +131,7 @@ export function registerJobs(server) { path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', config: getRouteConfigDownload(), - handler: async (request, h) => { + handler: async (request: Request, h: ResponseToolkit) => { const { docId } = request.params; let response = await jobResponseHandler( diff --git a/x-pack/plugins/reporting/server/routes/legacy.js b/x-pack/plugins/reporting/server/routes/legacy.ts similarity index 72% rename from x-pack/plugins/reporting/server/routes/legacy.js rename to x-pack/plugins/reporting/server/routes/legacy.ts index 5be40db6980ff..d95791fea8b98 100644 --- a/x-pack/plugins/reporting/server/routes/legacy.js +++ b/x-pack/plugins/reporting/server/routes/legacy.ts @@ -4,23 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Request, ResponseToolkit } from 'hapi'; import querystring from 'querystring'; -import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; import { API_BASE_URL } from '../../common/constants'; +import { KbnServer } from '../../types'; +import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; +import { HandlerErrorFunction, HandlerFunction } from './types'; -const getStaticFeatureConfig = (getRouteConfig, featureId) => getRouteConfig(() => featureId); +const getStaticFeatureConfig = (getRouteConfig: any, featureId: any) => + getRouteConfig(() => featureId); const BASE_GENERATE = `${API_BASE_URL}/generate`; -export function registerLegacy(server, handler, handleError) { +export function registerLegacy( + server: KbnServer, + handler: HandlerFunction, + handleError: HandlerErrorFunction +) { const getRouteConfig = getRouteConfigFactoryReportingPre(server); - function createLegacyPdfRoute(server, handler, { path, objectType }) { + function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: any }) { const exportTypeId = 'printablePdf'; server.route({ - path: path, + path, method: 'POST', config: getStaticFeatureConfig(getRouteConfig, exportTypeId), - handler: async (request, h) => { + handler: async (request: Request, h: ResponseToolkit) => { const message = `The following URL is deprecated and will stop working in the next major version: ${ request.url.path }`; @@ -47,17 +55,17 @@ export function registerLegacy(server, handler, handleError) { }); } - createLegacyPdfRoute(server, handler, { + createLegacyPdfRoute({ path: `${BASE_GENERATE}/visualization/{savedId}`, objectType: 'visualization', }); - createLegacyPdfRoute(server, handler, { + createLegacyPdfRoute({ path: `${BASE_GENERATE}/search/{savedId}`, objectType: 'search', }); - createLegacyPdfRoute(server, handler, { + createLegacyPdfRoute({ path: `${BASE_GENERATE}/dashboard/{savedId}`, objectType: 'dashboard', }); diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts new file mode 100644 index 0000000000000..90040870778bd --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Request, ResponseToolkit } from 'hapi'; + +export type HandlerFunction = ( + exportType: any, + jobParams: any, + request: Request, + h: ResponseToolkit +) => any; + +export type HandlerErrorFunction = (exportType: any, err: Error) => any; diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts index 80e534685bb70..f12b6d4b88ae2 100644 --- a/x-pack/plugins/reporting/types.d.ts +++ b/x-pack/plugins/reporting/types.d.ts @@ -15,6 +15,8 @@ export interface KbnServer { info: { protocol: string }; config: () => ConfigObject; plugins: Record; + route: any; + log: any; savedObjects: { getScopedSavedObjectsClient: ( fakeRequest: { headers: object; getBasePath: () => string } From 47cdbe9b9d56c7f5af937818d605d04d5a8cfc03 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 22:35:45 -0700 Subject: [PATCH 08/12] more typscript --- x-pack/plugins/reporting/server/routes/jobs.ts | 17 ++--------------- x-pack/plugins/reporting/types.d.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index 5ae3606d2c07a..f2930a00a7142 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -7,7 +7,7 @@ import boom from 'boom'; import { Request, ResponseToolkit } from 'hapi'; import { API_BASE_URL } from '../../common/constants'; -import { KbnServer } from '../../types'; +import { JobDoc, KbnServer } from '../../types'; // @ts-ignore import { jobsQueryFactory } from '../lib/jobs_query'; // @ts-ignore @@ -19,16 +19,6 @@ import { const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -interface JobDoc { - _source: { - output: any; - jobtype: any; - payload: { - headers: string; - }; - }; -} - export function registerJobs(server: KbnServer) { const jobsQuery = jobsQueryFactory(server); const getRouteConfig = getRouteConfigFactoryManagementPre(server); @@ -114,12 +104,9 @@ export function registerJobs(server: KbnServer) { return boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); } - const { payload } = doc._source; - payload.headers = 'not shown'; - return { ...doc._source, - payload, + headers: undefined, }; }); }, diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts index f12b6d4b88ae2..5ec5f9f623c05 100644 --- a/x-pack/plugins/reporting/types.d.ts +++ b/x-pack/plugins/reporting/types.d.ts @@ -90,6 +90,7 @@ export interface ConditionalHeadersConditions { export interface CryptoFactory { decrypt: (headers?: Record) => string; } + export interface ReportingJob { headers?: Record; basePath?: string; @@ -99,3 +100,11 @@ export interface ReportingJob { timeRange?: any; objects?: [any]; } + +export interface JobDoc { + _source: { + output: any; + jobtype: any; + payload: ReportingJob; + }; +} From bbadf6041b7044bb830e6843ca122ec5c09a9fdf Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 22:47:30 -0700 Subject: [PATCH 09/12] jobtype is a string --- x-pack/plugins/reporting/types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts index 5ec5f9f623c05..1e3406c8f9410 100644 --- a/x-pack/plugins/reporting/types.d.ts +++ b/x-pack/plugins/reporting/types.d.ts @@ -104,7 +104,7 @@ export interface ReportingJob { export interface JobDoc { _source: { output: any; - jobtype: any; + jobtype: string; payload: ReportingJob; }; } From 8c4edb2d18aa9266e10146a0a44e8ae51ecd31da Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 22:49:44 -0700 Subject: [PATCH 10/12] revert some logic change --- x-pack/plugins/reporting/server/routes/jobs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index f2930a00a7142..55bccd739acce 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -106,7 +106,7 @@ export function registerJobs(server: KbnServer) { return { ...doc._source, - headers: undefined, + headers: 'not shown', }; }); }, From 9d9af297b64383cb79e8f16a22399118a53ee90c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 23:47:26 -0700 Subject: [PATCH 11/12] set payload.headers to undefined + not mutate --- .../plugins/reporting/server/routes/jobs.ts | 53 ++++++++++--------- x-pack/plugins/reporting/types.d.ts | 8 ++- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index 55bccd739acce..2b1dc30bb1992 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -67,22 +67,21 @@ export function registerJobs(server: KbnServer) { handler: (request: Request) => { const { docId } = request.params; - return jobsQuery - .get(request.pre.user, docId, { includeContent: true }) - .then((doc: JobDoc) => { - if (!doc) { - return boom.notFound(); + return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then( + (doc: any): JobDoc => { + const job = doc._source; + if (!job) { + throw boom.notFound(); } - const { jobtype: jobType } = doc._source; + const { jobtype: jobType } = job; if (!request.pre.management.jobTypes.includes(jobType)) { - return boom.unauthorized( - `Sorry, you are not authorized to download ${jobType} reports` - ); + throw boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); } - return doc._source.output; - }); + return job.output; + } + ); }, }); @@ -94,21 +93,27 @@ export function registerJobs(server: KbnServer) { handler: (request: Request) => { const { docId } = request.params; - return jobsQuery.get(request.pre.user, docId).then((doc: JobDoc) => { - if (!doc) { - return boom.notFound(); - } + return jobsQuery.get(request.pre.user, docId).then( + (doc: any): JobDoc => { + const job: JobDoc = doc._source; + if (!job) { + throw boom.notFound(); + } - const { jobtype: jobType } = doc._source; - if (!request.pre.management.jobTypes.includes(jobType)) { - return boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); - } + const { jobtype: jobType, payload } = job; + if (!request.pre.management.jobTypes.includes(jobType)) { + throw boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + } - return { - ...doc._source, - headers: 'not shown', - }; - }); + return { + ...doc._source, + payload: { + ...payload, + headers: undefined, + }, + }; + } + ); }, }); diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts index 1e3406c8f9410..cb3b6d61ce535 100644 --- a/x-pack/plugins/reporting/types.d.ts +++ b/x-pack/plugins/reporting/types.d.ts @@ -102,9 +102,7 @@ export interface ReportingJob { } export interface JobDoc { - _source: { - output: any; - jobtype: string; - payload: ReportingJob; - }; + output: any; + jobtype: string; + payload: any; } From d7f22772ac5b523b3cc28e165359215dba60c79c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Feb 2019 23:51:42 -0700 Subject: [PATCH 12/12] fix jest import --- x-pack/plugins/reporting/server/routes/jobs.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.js b/x-pack/plugins/reporting/server/routes/jobs.test.js index 3d866d1e7d458..8a65a108af8b3 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.test.js +++ b/x-pack/plugins/reporting/server/routes/jobs.test.js @@ -8,12 +8,12 @@ import Hapi from 'hapi'; import { difference, memoize } from 'lodash'; import { registerJobs } from './jobs'; import { ExportTypesRegistry } from '../../common/export_types_registry'; -jest.mock('../lib/authorized_user_pre_routing', () => { +jest.mock('./lib/authorized_user_pre_routing', () => { return { authorizedUserPreRoutingFactory: () => () => ({}) }; }); -jest.mock('../lib/reporting_feature_pre_routing', () => { +jest.mock('./lib/reporting_feature_pre_routing', () => { return { reportingFeaturePreRoutingFactory: () => () => () => ({ jobTypes: ['unencodedJobType', 'base64EncodedJobType'] }) };