diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts index 18b0ac2a72802..24c126bfe0571 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types.ts @@ -7,7 +7,12 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths export { ReportingConfigType } from '../server/config'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { LayoutInstance } from '../server/lib/layouts'; +import { LayoutParams } from '../server/lib/layouts'; +export { LayoutParams }; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export { ReportDocument, ReportSource } from '../server/lib/store/report'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export { BaseParams } from '../server/types'; export type JobId = string; export type JobStatus = @@ -17,45 +22,43 @@ export type JobStatus = | 'processing' | 'failed'; -export interface SourceJob { - _id: JobId; - _source: { - status: JobStatus; - output: { - max_size_reached: boolean; - csv_contains_formulas: boolean; - }; - payload: { - type: string; - title: string; - }; - }; -} - export interface JobContent { content: string; } -export interface JobSummary { - id: JobId; - status: JobStatus; - title: string; - type: string; - maxSizeReached: boolean; - csvContainsFormulas: boolean; -} - -export interface JobStatusBuckets { - completed: JobSummary[]; - failed: JobSummary[]; +export interface ReportApiJSON { + id: string; + index: string; + kibana_name: string; + kibana_id: string; + browser_type: string | undefined; + created_at: string; + priority?: number; + jobtype: string; + created_by: string | false; + timeout?: number; + output?: { + content_type: string; + size: number; + warnings?: string[]; + }; + process_expiration?: string; + completed_at: string | undefined; + payload: { + layout?: LayoutParams; + title: string; + browserTimezone?: string; + }; + meta: { + layout?: string; + objectType: string; + }; + max_attempts: number; + started_at: string | undefined; + attempts: number; + status: string; } -type DownloadLink = string; -export type DownloadReportFn = (jobId: JobId) => DownloadLink; - -type ManagementLink = string; -export type ManagementLinkFn = () => ManagementLink; - export interface PollerOptions { functionToPoll: () => Promise; pollFrequencyInMillis: number; diff --git a/x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx b/x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx index 941baa5af6776..068cb7d44b0a1 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx +++ b/x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx @@ -15,10 +15,11 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import React, { Component, Fragment } from 'react'; import { get } from 'lodash'; +import React, { Component, Fragment } from 'react'; +import { ReportApiJSON } from '../../../common/types'; import { USES_HEADLESS_JOB_TYPES } from '../../../constants'; -import { JobInfo, ReportingAPIClient } from '../../lib/reporting_api_client'; +import { ReportingAPIClient } from '../../lib/reporting_api_client'; interface Props { jobId: string; @@ -29,14 +30,14 @@ interface State { isLoading: boolean; isFlyoutVisible: boolean; calloutTitle: string; - info: JobInfo | null; + info: ReportApiJSON | null; error: Error | null; } const NA = 'n/a'; const UNKNOWN = 'unknown'; -const getDimensions = (info: JobInfo): string => { +const getDimensions = (info: ReportApiJSON): string => { const defaultDimensions = { width: null, height: null }; const { width, height } = get(info, 'payload.layout.dimensions', defaultDimensions); if (width && height) { @@ -121,10 +122,6 @@ export class ReportInfoButton extends Component { title: 'Title', description: get(info, 'payload.title') || NA, }, - { - title: 'Type', - description: get(info, 'payload.type') || NA, - }, { title: 'Layout', description: get(info, 'meta.layout') || NA, @@ -263,7 +260,7 @@ export class ReportInfoButton extends Component { private loadInfo = async () => { this.setState({ isLoading: true }); try { - const info: JobInfo = await this.props.apiClient.getInfo(this.props.jobId); + const info: ReportApiJSON = await this.props.apiClient.getInfo(this.props.jobId); if (this.mounted) { this.setState({ isLoading: false, info }); } diff --git a/x-pack/plugins/reporting/public/components/job_download_button.tsx b/x-pack/plugins/reporting/public/components/job_download_button.tsx index 7dff2cafa047b..8cf3ce8644add 100644 --- a/x-pack/plugins/reporting/public/components/job_download_button.tsx +++ b/x-pack/plugins/reporting/public/components/job_download_button.tsx @@ -7,7 +7,8 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { JobId, JobSummary } from '../../common/types'; +import { JobSummary } from '../'; +import { JobId } from '../../common/types'; interface Props { getUrl: (jobId: JobId) => string; diff --git a/x-pack/plugins/reporting/public/components/job_failure.tsx b/x-pack/plugins/reporting/public/components/job_failure.tsx index 0da67ea367437..8d8f32f692343 100644 --- a/x-pack/plugins/reporting/public/components/job_failure.tsx +++ b/x-pack/plugins/reporting/public/components/job_failure.tsx @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { ToastInput } from 'src/core/public'; +import { JobSummary, ManagementLinkFn } from '../'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { JobSummary, ManagementLinkFn } from '../../common/types'; export const getFailureToast = ( errorText: string, @@ -22,7 +22,7 @@ export const getFailureToast = ( ), text: toMountPoint( diff --git a/x-pack/plugins/reporting/public/components/job_success.tsx b/x-pack/plugins/reporting/public/components/job_success.tsx index 7f33321ee3645..05cf2c4c5784a 100644 --- a/x-pack/plugins/reporting/public/components/job_success.tsx +++ b/x-pack/plugins/reporting/public/components/job_success.tsx @@ -7,8 +7,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { ToastInput } from 'src/core/public'; +import { JobSummary } from '../'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { JobId, JobSummary } from '../../common/types'; +import { JobId } from '../../common/types'; import { DownloadButton } from './job_download_button'; import { ReportLink } from './report_link'; @@ -21,7 +22,7 @@ export const getSuccessToast = ( ), color: 'success', diff --git a/x-pack/plugins/reporting/public/components/job_warning_formulas.tsx b/x-pack/plugins/reporting/public/components/job_warning_formulas.tsx index e2afae1feaa01..8cccc94e98dcd 100644 --- a/x-pack/plugins/reporting/public/components/job_warning_formulas.tsx +++ b/x-pack/plugins/reporting/public/components/job_warning_formulas.tsx @@ -7,8 +7,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { ToastInput } from 'src/core/public'; +import { JobSummary } from '../'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { JobId, JobSummary } from '../../common/types'; +import { JobId } from '../../common/types'; import { DownloadButton } from './job_download_button'; import { ReportLink } from './report_link'; @@ -21,7 +22,7 @@ export const getWarningFormulasToast = ( ), text: toMountPoint( diff --git a/x-pack/plugins/reporting/public/components/job_warning_max_size.tsx b/x-pack/plugins/reporting/public/components/job_warning_max_size.tsx index 6c0d6118dfff2..c350eef0e5a54 100644 --- a/x-pack/plugins/reporting/public/components/job_warning_max_size.tsx +++ b/x-pack/plugins/reporting/public/components/job_warning_max_size.tsx @@ -7,8 +7,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { ToastInput } from 'src/core/public'; +import { JobSummary } from '../'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { JobId, JobSummary } from '../../common/types'; +import { JobId } from '../../common/types'; import { DownloadButton } from './job_download_button'; import { ReportLink } from './report_link'; @@ -21,7 +22,7 @@ export const getWarningMaxSizeToast = ( ), text: toMountPoint( diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx index 1f2eddf44a54a..cea402d6a98f2 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.tsx @@ -41,17 +41,17 @@ export interface Job { type: string; object_type: string; object_title: string; - created_by?: string; + created_by?: string | false; created_at: string; started_at?: string; completed_at?: string; status: string; statusLabel: string; - max_size_reached: boolean; + max_size_reached?: boolean; attempts: number; max_attempts: number; csv_contains_formulas: boolean; - warnings: string[]; + warnings?: string[]; } export interface Props { @@ -316,7 +316,7 @@ class ReportListingUi extends Component { return { id: job._id, type: source.jobtype, - object_type: source.payload.type, + object_type: source.payload.objectType, object_title: source.payload.title, created_by: source.created_by, created_at: source.created_at, diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx index eddf151167be8..22b97f45db186 100644 --- a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx +++ b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx @@ -7,10 +7,11 @@ import { EuiButton, EuiCopy, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, ReactElement } from 'react'; -import url from 'url'; import { ToastsSetup } from 'src/core/public'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import url from 'url'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; +import { BaseParams } from '../../common/types'; +import { ReportingAPIClient } from '../lib/reporting_api_client'; interface Props { apiClient: ReportingAPIClient; @@ -19,7 +20,7 @@ interface Props { layoutId: string | undefined; objectId?: string; objectType: string; - getJobParams: () => any; + getJobParams: () => BaseParams; options?: ReactElement; isDirty: boolean; onClose: () => void; diff --git a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx b/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx index 9fb74a70ff1ac..4a62ab2b76508 100644 --- a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx +++ b/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer, EuiSwitch } from '@elastic/eui'; +import { EuiSpacer, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { ToastsSetup } from 'src/core/public'; -import { ReportingPanelContent } from './reporting_panel_content'; +import { BaseParams } from '../../common/types'; import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ReportingPanelContent } from './reporting_panel_content'; interface Props { apiClient: ReportingAPIClient; @@ -17,7 +18,7 @@ interface Props { reportType: string; objectId?: string; objectType: string; - getJobParams: () => any; + getJobParams: () => BaseParams; isDirty: boolean; onClose: () => void; } @@ -83,7 +84,7 @@ export class ScreenCapturePanelContent extends Component { ); }; - private handlePrintLayoutChange = (evt: any) => { + private handlePrintLayoutChange = (evt: EuiSwitchEvent) => { this.setState({ usePrintLayout: evt.target.checked }); }; diff --git a/x-pack/plugins/reporting/public/index.ts b/x-pack/plugins/reporting/public/index.ts index 185367a85bdc0..251fd14ee4d57 100644 --- a/x-pack/plugins/reporting/public/index.ts +++ b/x-pack/plugins/reporting/public/index.ts @@ -7,6 +7,7 @@ import { PluginInitializerContext } from 'src/core/public'; import { ReportingPublicPlugin } from './plugin'; import * as jobCompletionNotifications from './lib/job_completion_notifications'; +import { JobId, JobStatus } from '../common/types'; export function plugin(initializerContext: PluginInitializerContext) { return new ReportingPublicPlugin(initializerContext); @@ -14,3 +15,23 @@ export function plugin(initializerContext: PluginInitializerContext) { export { ReportingPublicPlugin as Plugin }; export { jobCompletionNotifications }; + +export interface JobSummary { + id: JobId; + status: JobStatus; + title: string; + jobtype: string; + maxSizeReached?: boolean; + csvContainsFormulas?: boolean; +} + +export interface JobSummarySet { + completed: JobSummary[]; + failed: JobSummary[]; +} + +type DownloadLink = string; +export type DownloadReportFn = (jobId: JobId) => DownloadLink; + +type ManagementLink = string; +export type ManagementLinkFn = () => ManagementLink; diff --git a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap index 6b95a00ea0009..f1d9d747a7236 100644 --- a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap +++ b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap @@ -6,20 +6,20 @@ Object { Object { "csvContainsFormulas": false, "id": "job-source-mock1", + "jobtype": undefined, "maxSizeReached": false, "status": "completed", "title": "specimen", - "type": "spectacular", }, ], "failed": Array [ Object { "csvContainsFormulas": false, "id": "job-source-mock2", + "jobtype": undefined, "maxSizeReached": false, "status": "failed", "title": "specimen", - "type": "spectacular", }, ], } @@ -49,9 +49,9 @@ Array [ Object { "csvContainsFormulas": true, "id": "yas3", + "jobtype": "yas", "status": "completed", "title": "Yas", - "type": "yas", } } /> @@ -149,10 +149,10 @@ Array [ job={ Object { "id": "yas2", + "jobtype": "yas", "maxSizeReached": true, "status": "completed", "title": "Yas", - "type": "yas", } } /> @@ -191,9 +191,9 @@ Array [ job={ Object { "id": "yas1", + "jobtype": "yas", "status": "completed", "title": "Yas", - "type": "yas", } } /> diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client.ts index 2f813bd811c6c..2853caaaaa1b5 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client.ts @@ -7,10 +7,11 @@ import { stringify } from 'query-string'; import rison from 'rison-node'; import { HttpSetup } from 'src/core/public'; -import { JobId, SourceJob } from '../../common/types'; +import { DownloadReportFn, ManagementLinkFn } from '../'; +import { JobId, ReportApiJSON, ReportDocument, ReportSource } from '../../common/types'; import { - API_BASE_URL, API_BASE_GENERATE, + API_BASE_URL, API_LIST_URL, REPORTING_MANAGEMENT_HOME, } from '../../constants'; @@ -18,7 +19,7 @@ import { add } from './job_completion_notifications'; export interface JobQueueEntry { _id: string; - _source: any; + _source: ReportSource; } export interface JobContent { @@ -26,40 +27,6 @@ export interface JobContent { content_type: boolean; } -export interface JobInfo { - kibana_name: string; - kibana_id: string; - browser_type: string; - created_at: string; - priority: number; - jobtype: string; - created_by: string; - timeout: number; - output: { - content_type: string; - size: number; - warnings: string[]; - }; - process_expiration: string; - completed_at: string; - payload: { - layout: { id: string; dimensions: { width: number; height: number } }; - objects: Array<{ relativeUrl: string }>; - type: string; - title: string; - forceNow: string; - browserTimezone: string; - }; - meta: { - layout: string; - objectType: string; - }; - max_attempts: number; - started_at: string; - attempts: number; - status: string; -} - interface JobParams { [paramName: string]: any; } @@ -121,13 +88,13 @@ export class ReportingAPIClient { }); } - public getInfo(jobId: string): Promise { + public getInfo(jobId: string): Promise { return this.http.get(`${API_LIST_URL}/info/${jobId}`, { asSystemRequest: true, }); } - public findForJobIds = (jobIds: JobId[]): Promise => { + public findForJobIds = (jobIds: JobId[]): Promise => { return this.http.fetch(`${API_LIST_URL}/list`, { query: { page: 0, ids: jobIds.join(',') }, method: 'GET', @@ -159,9 +126,10 @@ export class ReportingAPIClient { return resp; }; - public getManagementLink = () => this.http.basePath.prepend(REPORTING_MANAGEMENT_HOME); + public getManagementLink: ManagementLinkFn = () => + this.http.basePath.prepend(REPORTING_MANAGEMENT_HOME); - public getDownloadLink = (jobId: JobId) => + public getDownloadLink: DownloadReportFn = (jobId: JobId) => this.http.basePath.prepend(`${API_LIST_URL}/download/${jobId}`); /* diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 998f0711b1355..f91517e4397f9 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -6,7 +6,8 @@ import sinon, { stub } from 'sinon'; import { NotificationsStart } from 'src/core/public'; -import { JobSummary, SourceJob } from '../../common/types'; +import { JobSummary } from '../'; +import { ReportDocument } from '../../common/types'; import { ReportingAPIClient } from './reporting_api_client'; import { ReportingNotifierStreamHandler } from './stream_handler'; @@ -23,7 +24,7 @@ const mockJobsFound = [ _source: { status: 'completed', output: { max_size_reached: false, csv_contains_formulas: false }, - payload: { type: 'spectacular', title: 'specimen' }, + payload: { title: 'specimen' }, }, }, { @@ -31,7 +32,7 @@ const mockJobsFound = [ _source: { status: 'failed', output: { max_size_reached: false, csv_contains_formulas: false }, - payload: { type: 'spectacular', title: 'specimen' }, + payload: { title: 'specimen' }, }, }, { @@ -39,14 +40,14 @@ const mockJobsFound = [ _source: { status: 'pending', output: { max_size_reached: false, csv_contains_formulas: false }, - payload: { type: 'spectacular', title: 'specimen' }, + payload: { title: 'specimen' }, }, }, ]; const jobQueueClientMock: ReportingAPIClient = { findForJobIds: async (jobIds: string[]) => { - return mockJobsFound as SourceJob[]; + return mockJobsFound as ReportDocument[]; }, getContent: (): Promise => { return Promise.resolve({ content: 'this is the completed report data' }); @@ -109,7 +110,7 @@ describe('stream handler', () => { { id: 'yas1', title: 'Yas', - type: 'yas', + jobtype: 'yas', status: 'completed', } as JobSummary, ], @@ -130,7 +131,7 @@ describe('stream handler', () => { { id: 'yas2', title: 'Yas', - type: 'yas', + jobtype: 'yas', status: 'completed', maxSizeReached: true, } as JobSummary, @@ -152,7 +153,7 @@ describe('stream handler', () => { { id: 'yas3', title: 'Yas', - type: 'yas', + jobtype: 'yas', status: 'completed', csvContainsFormulas: true, } as JobSummary, @@ -175,7 +176,7 @@ describe('stream handler', () => { { id: 'yas7', title: 'Yas 7', - type: 'yas', + jobtype: 'yas', status: 'failed', } as JobSummary, ], @@ -195,20 +196,20 @@ describe('stream handler', () => { { id: 'yas8', title: 'Yas 8', - type: 'yas', + jobtype: 'yas', status: 'completed', } as JobSummary, { id: 'yas9', title: 'Yas 9', - type: 'yas', + jobtype: 'yas', status: 'completed', csvContainsFormulas: true, } as JobSummary, { id: 'yas10', title: 'Yas 10', - type: 'yas', + jobtype: 'yas', status: 'completed', maxSizeReached: true, } as JobSummary, @@ -217,7 +218,7 @@ describe('stream handler', () => { { id: 'yas13', title: 'Yas 13', - type: 'yas', + jobtype: 'yas', status: 'failed', } as JobSummary, ], diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 80ba02e17d56d..d97c0a7a2d11e 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -8,7 +8,8 @@ import { i18n } from '@kbn/i18n'; import * as Rx from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { NotificationsSetup } from 'src/core/public'; -import { JobId, JobStatusBuckets, JobSummary, SourceJob } from '../../common/types'; +import { JobSummarySet, JobSummary } from '../'; +import { JobId, ReportDocument } from '../../common/types'; import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JOB_STATUS_COMPLETED, @@ -28,14 +29,14 @@ function updateStored(jobIds: JobId[]): void { sessionStorage.setItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JSON.stringify(jobIds)); } -function summarizeJob(src: SourceJob): JobSummary { +function getReportStatus(src: ReportDocument): JobSummary { return { id: src._id, status: src._source.status, title: src._source.payload.title, - type: src._source.payload.type, - maxSizeReached: src._source.output.max_size_reached, - csvContainsFormulas: src._source.output.csv_contains_formulas, + jobtype: src._source.jobtype, + maxSizeReached: src._source.output?.max_size_reached, + csvContainsFormulas: src._source.output?.csv_contains_formulas, }; } @@ -48,7 +49,7 @@ export class ReportingNotifierStreamHandler { public showNotifications({ completed: completedJobs, failed: failedJobs, - }: JobStatusBuckets): Rx.Observable { + }: JobSummarySet): Rx.Observable { const showNotificationsAsync = async () => { // notifications with download link for (const job of completedJobs) { @@ -92,9 +93,9 @@ export class ReportingNotifierStreamHandler { * An observable that finds jobs that are known to be "processing" (stored in * session storage) but have non-processing job status on the server */ - public findChangedStatusJobs(storedJobs: JobId[]): Rx.Observable { + public findChangedStatusJobs(storedJobs: JobId[]): Rx.Observable { return Rx.from(this.apiClient.findForJobIds(storedJobs)).pipe( - map((jobs: SourceJob[]) => { + map((jobs: ReportDocument[]) => { const completedJobs: JobSummary[] = []; const failedJobs: JobSummary[] = []; const pending: JobId[] = []; @@ -107,9 +108,9 @@ export class ReportingNotifierStreamHandler { } = job; if (storedJobs.includes(jobId)) { if (jobStatus === JOB_STATUS_COMPLETED || jobStatus === JOB_STATUS_WARNINGS) { - completedJobs.push(summarizeJob(job)); + completedJobs.push(getReportStatus(job)); } else if (jobStatus === JOB_STATUS_FAILED) { - failedJobs.push(summarizeJob(job)); + failedJobs.push(getReportStatus(job)); } else { pending.push(jobId); } diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index a134377e194b8..cc5964f737988 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -27,8 +27,9 @@ import { ManagementSetup } from '../../../../src/plugins/management/public'; import { SharePluginSetup } from '../../../../src/plugins/share/public'; import { LicensingPluginSetup } from '../../licensing/public'; import { durationToNumber } from '../common/schema_utils'; -import { JobId, JobStatusBuckets, ReportingConfigType } from '../common/types'; +import { JobId, ReportingConfigType } from '../common/types'; import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY } from '../constants'; +import { JobSummarySet } from './'; import { getGeneralErrorToast } from './components'; import { ReportListing } from './components/report_listing'; import { ReportingAPIClient } from './lib/reporting_api_client'; @@ -46,10 +47,7 @@ function getStored(): JobId[] { return sessionValue ? JSON.parse(sessionValue) : []; } -function handleError( - notifications: NotificationsSetup, - err: Error -): Rx.Observable { +function handleError(notifications: NotificationsSetup, err: Error): Rx.Observable { notifications.toasts.addDanger( getGeneralErrorToast( i18n.translate('xpack.reporting.publicNotifier.pollingErrorMessage', { diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx index 4ad35fd768825..451d907199c4c 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; import { ShareContext } from '../../../../../src/plugins/share/public'; import { LicensingPluginSetup } from '../../../licensing/public'; -import { JobParamsDiscoverCsv, SearchRequest } from '../../server/export_types/csv/types'; +import { JobParamsCSV, SearchRequest } from '../../server/export_types/csv/types'; import { ReportingPanelContent } from '../components/reporting_panel_content'; import { checkLicense } from '../lib/license_check'; import { ReportingAPIClient } from '../lib/reporting_api_client'; @@ -59,7 +59,7 @@ export const csvReportingProvider = ({ return []; } - const jobParams: JobParamsDiscoverCsv = { + const jobParams: JobParamsCSV = { browserTimezone, objectType, title: sharingData.title as string, diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index e10d04ea5fc6b..2dab66187bb25 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; import { ShareContext } from '../../../../../src/plugins/share/public'; import { LicensingPluginSetup } from '../../../licensing/public'; -import { LayoutInstance } from '../../common/types'; +import { LayoutParams } from '../../common/types'; import { JobParamsPNG } from '../../server/export_types/png/types'; import { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content'; @@ -80,7 +80,7 @@ export const reportingPDFPNGProvider = ({ objectType, browserTimezone, relativeUrls: [relativeUrl], // multi URL for PDF - layout: sharingData.layout as LayoutInstance, + layout: sharingData.layout as LayoutParams, title: sharingData.title as string, }; }; @@ -96,7 +96,7 @@ export const reportingPDFPNGProvider = ({ objectType, browserTimezone, relativeUrl, // single URL for PNG - layout: sharingData.layout as LayoutInstance, + layout: sharingData.layout as LayoutParams, title: sharingData.title as string, }; }; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 0a76c7fcfd3b2..04ab572a53dbc 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -10,10 +10,10 @@ import open from 'opn'; import { ElementHandle, EvaluateFn, Page, Response, SerializableOrJSHandle } from 'puppeteer'; import { parse as parseUrl } from 'url'; import { getDisallowedOutgoingUrlError } from '../'; +import { ConditionalHeaders, ConditionalHeadersConditions } from '../../../export_types/common'; import { LevelLogger } from '../../../lib'; import { ViewZoomWidthHeight } from '../../../lib/layouts/layout'; import { ElementPosition } from '../../../lib/screenshots'; -import { ConditionalHeaders } from '../../../types'; import { allowRequest, NetworkPolicy } from '../../network_policy'; export interface ChromiumDriverOptions { @@ -34,8 +34,6 @@ interface EvaluateMetaOpts { context: string; } -type ConditionalHeadersConditions = ConditionalHeaders['conditions']; - interface InterceptedRequest { requestId: string; request: { diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 6897f07c45e2b..efef323612322 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -64,7 +64,7 @@ export class HeadlessChromiumDriverFactory { * Return an observable to objects which will drive screenshot capture for a page */ createPage( - { viewport, browserTimezone }: { viewport: ViewportConfig; browserTimezone: string }, + { viewport, browserTimezone }: { viewport: ViewportConfig; browserTimezone?: string }, pLogger: LevelLogger ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { return Rx.Observable.create(async (observer: InnerSubscriber) => { diff --git a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts index 908817a2ccf81..db1e622df4e21 100644 --- a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cryptoFactory, LevelLogger } from '../../lib'; +import { cryptoFactory } from '../../lib'; +import { createMockLevelLogger } from '../../test_helpers'; import { decryptJobHeaders } from './'; +const logger = createMockLevelLogger(); + const encryptHeaders = async (encryptionKey: string, headers: Record) => { const crypto = cryptoFactory(encryptionKey); return await crypto.encrypt(headers); @@ -15,15 +18,11 @@ const encryptHeaders = async (encryptionKey: string, headers: Record { test(`fails if it can't decrypt headers`, async () => { const getDecryptedHeaders = () => - decryptJobHeaders({ - encryptionKey: 'abcsecretsauce', - job: { - headers: 'Q53+9A+zf+Xe+ceR/uB/aR/Sw/8e+M+qR+WiG+8z+EY+mo+HiU/zQL+Xn', - }, - logger: ({ - error: jest.fn(), - } as unknown) as LevelLogger, - }); + decryptJobHeaders( + 'abcsecretsauce', + 'Q53+9A+zf+Xe+ceR/uB/aR/Sw/8e+M+qR+WiG+8z+EY+mo+HiU/zQL+Xn', + logger + ); await expect(getDecryptedHeaders()).rejects.toMatchInlineSnapshot( `[Error: Failed to decrypt report job data. Please ensure that xpack.reporting.encryptionKey is set and re-generate this report. Error: Invalid IV length]` ); @@ -36,15 +35,7 @@ describe('headers', () => { }; const encryptedHeaders = await encryptHeaders('abcsecretsauce', headers); - const decryptedHeaders = await decryptJobHeaders({ - encryptionKey: 'abcsecretsauce', - job: { - title: 'cool-job-bro', - type: 'csv', - headers: encryptedHeaders, - }, - logger: {} as LevelLogger, - }); + const decryptedHeaders = await decryptJobHeaders('abcsecretsauce', encryptedHeaders, logger); expect(decryptedHeaders).toEqual(headers); }); }); diff --git a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts index 4f0088467dd68..131a7936e3463 100644 --- a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts @@ -7,24 +7,13 @@ import { i18n } from '@kbn/i18n'; import { cryptoFactory, LevelLogger } from '../../lib'; -interface HasEncryptedHeaders { - headers?: string; -} - -export const decryptJobHeaders = async < - JobParamsType, - TaskPayloadType extends HasEncryptedHeaders ->({ - encryptionKey, - job, - logger, -}: { - encryptionKey?: string; - job: TaskPayloadType; - logger: LevelLogger; -}): Promise> => { +export const decryptJobHeaders = async ( + encryptionKey: string | undefined, + headers: string, + logger: LevelLogger +): Promise> => { try { - if (typeof job.headers !== 'string') { + if (typeof headers !== 'string') { throw new Error( i18n.translate('xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage', { defaultMessage: 'Job headers are missing', @@ -32,7 +21,7 @@ export const decryptJobHeaders = async < ); } const crypto = cryptoFactory(encryptionKey); - const decryptedHeaders = (await crypto.decrypt(job.headers)) as Record; + const decryptedHeaders = (await crypto.decrypt(headers)) as Record; return decryptedHeaders; } catch (err) { logger.error(err); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts index 794ea9febb5c0..b1d6f6fdf79c1 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.test.ts @@ -6,7 +6,6 @@ import { ReportingConfig } from '../../'; import { createMockConfig, createMockConfigSchema } from '../../test_helpers'; -import { BasePayload } from '../../types'; import { getConditionalHeaders } from './'; let mockConfig: ReportingConfig; @@ -24,11 +23,7 @@ describe('conditions', () => { baz: 'quix', }; - const conditionalHeaders = getConditionalHeaders({ - job: {} as BasePayload, - filteredHeaders: permittedHeaders, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, permittedHeaders); expect(conditionalHeaders.conditions.hostname).toEqual( mockConfig.get('kibanaServer', 'hostname') @@ -49,19 +44,7 @@ describe('config formatting', () => { const mockSchema = createMockConfigSchema(reportingConfig); mockConfig = createMockConfig(mockSchema); - const conditionalHeaders = getConditionalHeaders({ - job: { - title: 'cool-job-bro', - type: 'csv', - jobParams: { - savedObjectId: 'abc-123', - isImmediate: false, - savedObjectType: 'search', - }, - }, - filteredHeaders: {}, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, {}); expect(conditionalHeaders.conditions.hostname).toEqual('great-hostname'); }); }); diff --git a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts index ce83323914eb8..d167ac21635b1 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_conditional_headers.ts @@ -5,17 +5,12 @@ */ import { ReportingConfig } from '../../'; -import { ConditionalHeaders } from '../../types'; +import { ConditionalHeaders } from './'; -export const getConditionalHeaders = ({ - config, - job, - filteredHeaders, -}: { - config: ReportingConfig; - job: TaskPayloadType; - filteredHeaders: Record; -}) => { +export const getConditionalHeaders = ( + config: ReportingConfig, + filteredHeaders: Record +) => { const { kbnConfig } = config; const [hostname, port, basePath, protocol] = [ config.get('kibanaServer', 'hostname'), diff --git a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts index 934d0a3312b83..1b550bddccb22 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts @@ -10,11 +10,6 @@ import { TaskPayloadPNG } from '../png/types'; import { TaskPayloadPDF } from '../printable_pdf/types'; import { getFullUrls } from './get_full_urls'; -interface FullUrlsOpts { - job: TaskPayloadPNG & TaskPayloadPDF; - config: ReportingConfig; -} - let mockConfig: ReportingConfig; beforeEach(() => { @@ -30,7 +25,7 @@ beforeEach(() => { const getMockJob = (base: object) => base as TaskPayloadPNG & TaskPayloadPDF; test(`fails if no URL is passed`, async () => { - const fn = () => getFullUrls({ job: getMockJob({}), config: mockConfig } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({})); expect(fn).toThrowErrorMatchingInlineSnapshot( `"No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`"` ); @@ -39,11 +34,7 @@ test(`fails if no URL is passed`, async () => { test(`fails if URLs are file-protocols for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrl, forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrl, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); @@ -53,11 +44,7 @@ test(`fails if URLs are absolute for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrl, forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrl, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); @@ -66,11 +53,7 @@ test(`fails if URLs are absolute for PNGs`, async () => { test(`fails if URLs are file-protocols for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - const fn = () => - getFullUrls({ - job: getMockJob({ objects: [{ relativeUrl }], forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ objects: [{ relativeUrl }], forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); @@ -81,17 +64,17 @@ test(`fails if URLs are absolute for PDF`, async () => { const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; const fn = () => - getFullUrls({ - job: getMockJob({ + getFullUrls( + mockConfig, + getMockJob({ objects: [ { relativeUrl, }, ], forceNow, - }), - config: mockConfig, - } as FullUrlsOpts); + }) + ); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); @@ -108,22 +91,14 @@ test(`fails if any URLs are absolute or file's for PDF`, async () => { { relativeUrl: 'file://etc/passwd/#/something' }, ]; - const fn = () => - getFullUrls({ - job: getMockJob({ objects, forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ objects, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something file://etc/passwd/#/something"` ); }); test(`fails if URL does not route to a visualization`, async () => { - const fn = () => - getFullUrls({ - job: getMockJob({ relativeUrl: '/app/phoney' }), - config: mockConfig, - } as FullUrlsOpts); + const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/phoney' })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"No valid hash in the URL! A hash is expected for the application to route to the intended visualization."` ); @@ -131,10 +106,10 @@ test(`fails if URL does not route to a visualization`, async () => { test(`adds forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls({ - job: getMockJob({ relativeUrl: '/app/kibana#/something', forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const urls = await getFullUrls( + mockConfig, + getMockJob({ relativeUrl: '/app/kibana#/something', forceNow }) + ); expect(urls[0]).toEqual( 'http://localhost:5601/sbp/app/kibana#/something?forceNow=2000-01-01T00%3A00%3A00.000Z' @@ -144,10 +119,10 @@ test(`adds forceNow to hash's query, if it exists`, async () => { test(`appends forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls({ - job: getMockJob({ relativeUrl: '/app/kibana#/something?_g=something', forceNow }), - config: mockConfig, - } as FullUrlsOpts); + const urls = await getFullUrls( + mockConfig, + getMockJob({ relativeUrl: '/app/kibana#/something?_g=something', forceNow }) + ); expect(urls[0]).toEqual( 'http://localhost:5601/sbp/app/kibana#/something?_g=something&forceNow=2000-01-01T00%3A00%3A00.000Z' @@ -155,18 +130,16 @@ test(`appends forceNow to hash's query, if it exists`, async () => { }); test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { - const urls = await getFullUrls({ - job: getMockJob({ relativeUrl: '/app/kibana#/something' }), - config: mockConfig, - } as FullUrlsOpts); + const urls = await getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something' })); expect(urls[0]).toEqual('http://localhost:5601/sbp/app/kibana#/something'); }); test(`adds forceNow to each of multiple urls`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls({ - job: { + const urls = getFullUrls( + mockConfig, + getMockJob({ objects: [ { relativeUrl: '/app/kibana#/something_aaa' }, { relativeUrl: '/app/kibana#/something_bbb' }, @@ -174,9 +147,8 @@ test(`adds forceNow to each of multiple urls`, async () => { { relativeUrl: '/app/kibana#/something_ddd' }, ], forceNow, - }, - config: mockConfig, - } as FullUrlsOpts); + }) + ); expect(urls).toEqual([ 'http://localhost:5601/sbp/app/kibana#/something_aaa?forceNow=2000-01-01T00%3A00%3A00.000Z', diff --git a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts index d451c60b5a600..f0be0abc1085e 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts @@ -23,13 +23,7 @@ function isPdfJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPDF { return (job as TaskPayloadPDF).objects !== undefined; // 7.x only } -export function getFullUrls({ - config, - job, -}: { - config: ReportingConfig; - job: TaskPayloadPDF | TaskPayloadPNG; -}) { +export function getFullUrls(config: ReportingConfig, job: TaskPayloadPDF | TaskPayloadPNG) { const [basePath, protocol, hostname, port] = [ config.kbnConfig.get('server', 'basePath'), config.get('kibanaServer', 'protocol'), diff --git a/x-pack/plugins/reporting/server/export_types/common/index.ts b/x-pack/plugins/reporting/server/export_types/common/index.ts index 80eaa52d0951b..5fa313c8a2fb7 100644 --- a/x-pack/plugins/reporting/server/export_types/common/index.ts +++ b/x-pack/plugins/reporting/server/export_types/common/index.ts @@ -9,3 +9,21 @@ export { getConditionalHeaders } from './get_conditional_headers'; export { getFullUrls } from './get_full_urls'; export { omitBlockedHeaders } from './omit_blocked_headers'; export { validateUrls } from './validate_urls'; + +export interface TimeRangeParams { + timezone: string; + min?: Date | string | number | null; + max?: Date | string | number | null; +} + +export interface ConditionalHeadersConditions { + protocol: string; + hostname: string; + port: number; + basePath: string; +} + +export interface ConditionalHeaders { + headers: Record; + conditions: ConditionalHeadersConditions; +} diff --git a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.ts index f40651603db8f..1833c2a7c62d7 100644 --- a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.test.ts @@ -24,20 +24,9 @@ test(`omits blocked headers`, async () => { trailer: 's are for trucks', }; - const filteredHeaders = await omitBlockedHeaders({ - job: { - title: 'cool-job-bro', - type: 'csv', - jobParams: { - savedObjectId: 'abc-123', - isImmediate: false, - savedObjectType: 'search', - }, - }, - decryptedHeaders: { - ...permittedHeaders, - ...blockedHeaders, - }, + const filteredHeaders = omitBlockedHeaders({ + ...permittedHeaders, + ...blockedHeaders, }); expect(filteredHeaders).toEqual(permittedHeaders); diff --git a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts b/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts index 946f033b4b481..09512ae703076 100644 --- a/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/omit_blocked_headers.ts @@ -9,13 +9,7 @@ import { KBN_SCREENSHOT_HEADER_BLOCK_LIST_STARTS_WITH_PATTERN, } from '../../../common/constants'; -export const omitBlockedHeaders = ({ - job, - decryptedHeaders, -}: { - job: TaskPayloadType; - decryptedHeaders: Record; -}) => { +export const omitBlockedHeaders = (decryptedHeaders: Record) => { const filteredHeaders: Record = omitBy( decryptedHeaders, (_value, header: string) => diff --git a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts index d768dc6f8e084..cb60b218818f0 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts @@ -6,10 +6,11 @@ import { cryptoFactory } from '../../lib'; import { CreateJobFn, CreateJobFnFactory } from '../../types'; -import { JobParamsDiscoverCsv } from './types'; +import { IndexPatternSavedObject, JobParamsCSV, TaskPayloadCSV } from './types'; export const createJobFnFactory: CreateJobFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); @@ -18,10 +19,10 @@ export const createJobFnFactory: CreateJobFnFactory, - TaskPayloadCSV, + CreateJobFn, RunTaskFn > => ({ ...metadata, diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts index 214157db51cb7..78615a0e7b72c 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts @@ -8,16 +8,6 @@ import { BaseParams, BasePayload } from '../../types'; export type RawValue = string | object | null | undefined; -interface DocValueField { - field: string; - format: string; -} - -interface SortOptions { - order: string; - unmapped_type: string; -} - export interface IndexPatternSavedObject { title: string; timeFieldName: string; @@ -28,25 +18,23 @@ export interface IndexPatternSavedObject { }; } -export interface JobParamsDiscoverCsv extends BaseParams { - browserTimezone: string; - indexPatternId: string; - title: string; +interface BaseParamsCSV { searchRequest: SearchRequest; fields: string[]; metaFields: string[]; conflictedTypesFields: string[]; } -export interface TaskPayloadCSV extends BasePayload { - browserTimezone: string; - basePath: string; - searchRequest: any; - fields: any; - indexPatternSavedObject: any; - metaFields: any; - conflictedTypesFields: any; -} +export type JobParamsCSV = BaseParamsCSV & + BaseParams & { + indexPatternId: string; + }; + +// CSV create job method converts indexPatternID to indexPatternSavedObject +export type TaskPayloadCSV = BaseParamsCSV & + BasePayload & { + indexPatternSavedObject: IndexPatternSavedObject; + }; export interface SearchRequest { index: string; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts index 1746792981a21..c780247dd61b3 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts @@ -6,57 +6,40 @@ import { notFound, notImplemented } from 'boom'; import { get } from 'lodash'; -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { RequestHandlerContext } from 'src/core/server'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { cryptoFactory } from '../../lib'; -import { CreateJobFnFactory, TimeRangeParams } from '../../types'; +import { CsvFromSavedObjectRequest } from '../../routes/generate_from_savedobject_immediate'; +import { CreateJobFnFactory } from '../../types'; import { JobParamsPanelCsv, + JobPayloadPanelCsv, SavedObject, SavedObjectReference, SavedObjectServiceError, - SavedSearchObjectAttributesJSON, - SearchPanel, VisObjectAttributesJSON, } from './types'; export type ImmediateCreateJobFn = ( jobParams: JobParamsPanelCsv, - headers: KibanaRequest['headers'], context: RequestHandlerContext, - req: KibanaRequest -) => Promise<{ - type: string; - title: string; - jobParams: JobParamsPanelCsv; -}>; - -interface VisData { - title: string; - visType: string; - panel: SearchPanel; -} + req: CsvFromSavedObjectRequest +) => Promise; export const createJobFnFactory: CreateJobFnFactory = function createJobFactoryFn( reporting, parentLogger ) { - const config = reporting.getConfig(); - const crypto = cryptoFactory(config.get('encryptionKey')); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); - return async function createJob(jobParams, headers, context, req) { + return async function createJob(jobParams, context, req) { const { savedObjectType, savedObjectId } = jobParams; - const serializedEncryptedHeaders = await crypto.encrypt(headers); - const { panel, title, visType }: VisData = await Promise.resolve() + const panel = await Promise.resolve() .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId)) .then(async (savedObject: SavedObject) => { const { attributes, references } = savedObject; - const { - kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON, - } = attributes as SavedSearchObjectAttributesJSON; - const { timerange } = req.body as { timerange: TimeRangeParams }; + const { kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON } = attributes; + const { timerange } = req.body; if (!kibanaSavedObjectMetaJSON) { throw new Error('Could not parse saved object data!'); @@ -85,7 +68,7 @@ export const createJobFnFactory: CreateJobFnFactory = func throw new Error('Could not find index pattern for the saved search!'); } - const sPanel = { + return { attributes: { ...attributes, kibanaSavedObjectMeta: { searchSource }, @@ -93,8 +76,6 @@ export const createJobFnFactory: CreateJobFnFactory = func indexPatternSavedObjectId: indexPatternMeta.id, timerange, }; - - return { panel: sPanel, title: attributes.title, visType: 'search' }; }) .catch((err: Error) => { const boomErr = (err as unknown) as { isBoom: boolean }; @@ -109,11 +90,6 @@ export const createJobFnFactory: CreateJobFnFactory = func throw new Error(`Unable to create a job from saved object data! Error: ${err}`); }); - return { - headers: serializedEncryptedHeaders, - jobParams: { ...jobParams, panel, visType }, - type: visType, - title, - }; + return { ...jobParams, panel }; }; }; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts index 0ca80581fcc83..19348c0a678d7 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts @@ -7,16 +7,11 @@ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { CancellationToken } from '../../../common'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { BasePayload, RunTaskFnFactory, TaskRunResult } from '../../types'; +import { TaskRunResult } from '../../lib/tasks'; +import { RunTaskFnFactory } from '../../types'; import { createGenerateCsv } from '../csv/generate_csv'; import { getGenerateCsvParams } from './lib/get_csv_job'; -import { JobParamsPanelCsv, SearchPanel } from './types'; - -/* - * The run function receives the full request which provides the un-encrypted - * headers, so encrypted headers are not part of these kind of job params - */ -type ImmediateJobParams = Omit, 'headers'>; +import { JobPayloadPanelCsv } from './types'; /* * ImmediateExecuteFn receives the job doc payload because the payload was @@ -24,7 +19,7 @@ type ImmediateJobParams = Omit, 'headers'>; */ export type ImmediateExecuteFn = ( jobId: null, - job: ImmediateJobParams, + job: JobPayloadPanelCsv, context: RequestHandlerContext, req: KibanaRequest ) => Promise; @@ -36,20 +31,16 @@ export const runTaskFnFactory: RunTaskFnFactory = function e const config = reporting.getConfig(); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); - return async function runTask(jobId: string | null, jobPayload, context, req) { - // There will not be a jobID for "immediate" generation. - // jobID is only for "queued" jobs - // Use the jobID as a logging tag or "immediate" - const { jobParams } = jobPayload; + return async function runTask(jobId, jobPayload, context, req) { const jobLogger = logger.clone(['immediate']); const generateCsv = createGenerateCsv(jobLogger); - const { panel, visType } = jobParams as JobParamsPanelCsv & { panel: SearchPanel }; + const { panel, visType } = jobPayload; jobLogger.debug(`Execute job generating [${visType}] csv`); const savedObjectsClient = context.core.savedObjects.client; const uiSettingsClient = await reporting.getUiSettingsServiceFactory(savedObjectsClient); - const job = await getGenerateCsvParams(jobParams, panel, savedObjectsClient, uiSettingsClient); + const job = await getGenerateCsvParams(jobPayload, panel, savedObjectsClient, uiSettingsClient); const elasticsearch = reporting.getElasticsearchService(); const { callAsCurrentUser } = elasticsearch.legacy.client.asScoped(req); diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts index 4b4cfb3f062bf..abe9fbf3e3950 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts @@ -17,7 +17,6 @@ import { ExportTypeDefinition } from '../../types'; import { createJobFnFactory, ImmediateCreateJobFn } from './create_job'; import { ImmediateExecuteFn, runTaskFnFactory } from './execute_job'; import { metadata } from './metadata'; -import { JobParamsPanelCsv } from './types'; /* * These functions are exported to share with the API route handler that @@ -27,9 +26,7 @@ export { createJobFnFactory } from './create_job'; export { runTaskFnFactory } from './execute_job'; export const getExportType = (): ExportTypeDefinition< - JobParamsPanelCsv, ImmediateCreateJobFn, - JobParamsPanelCsv, ImmediateExecuteFn > => ({ ...metadata, diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts index b387245406fbb..acf749584c6cd 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.test.ts @@ -13,7 +13,7 @@ describe('Get CSV Job', () => { let mockSavedObjectsClient: any; let mockUiSettingsClient: any; beforeEach(() => { - mockJobParams = { isImmediate: true, savedObjectType: 'search', savedObjectId: '234-ididid' }; + mockJobParams = { savedObjectType: 'search', savedObjectId: '234-ididid' }; mockSearchPanel = { indexPatternSavedObjectId: '123-indexId', attributes: { diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts index 26a4b17aaf71f..1fe64a25ebcaa 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_csv_job.ts @@ -12,7 +12,7 @@ import { IIndexPattern, Query, } from '../../../../../../../src/plugins/data/server'; -import { TimeRangeParams } from '../../../types'; +import { TimeRangeParams } from '../../common'; import { GenerateCsvParams } from '../../csv/generate_csv'; import { DocValueFields, @@ -50,11 +50,11 @@ export const getGenerateCsvParams = async ( savedObjectsClient: SavedObjectsClientContract, uiConfig: IUiSettingsClient ): Promise => { - let timerange: TimeRangeParams; + let timerange: TimeRangeParams | null; if (jobParams.post?.timerange) { timerange = jobParams.post?.timerange; } else { - timerange = panel.timerange; + timerange = panel.timerange || null; } const { indexPatternSavedObjectId } = panel; const savedSearchObjectAttr = panel.attributes as SavedSearchObjectAttributes; @@ -137,7 +137,7 @@ export const getGenerateCsvParams = async ( }; return { - browserTimezone: timerange.timezone, + browserTimezone: timerange?.timezone, indexPatternSavedObject, searchRequest, fields: includes, diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.test.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.test.ts index 429b2c518cf14..75e979aa2ec01 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimeRangeParams } from '../../../types'; +import { TimeRangeParams } from '../../common'; import { QueryFilter, SavedSearchObjectAttributes, SearchSourceFilter } from '../types'; import { getFilters } from './get_filters'; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.ts index a1b04cca0419d..8827a30d370d4 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/lib/get_filters.ts @@ -6,7 +6,7 @@ import { badRequest } from 'boom'; import moment from 'moment-timezone'; -import { TimeRangeParams } from '../../../types'; +import { TimeRangeParams } from '../../common'; import { Filter, QueryFilter, SavedSearchObjectAttributes, SearchSourceFilter } from '../types'; export function getFilters( diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts index 9c45d23b13a37..cca79747110d5 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts @@ -4,20 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobParamPostPayload, TimeRangeParams } from '../../types'; +import { TimeRangeParams } from '../common'; export interface FakeRequest { - headers: Record; + headers: Record; } -export interface JobParamsPostPayloadPanelCsv extends JobParamPostPayload { +export interface JobParamsPanelCsvPost { + timerange?: TimeRangeParams; state?: any; } export interface SearchPanel { indexPatternSavedObjectId: string; attributes: SavedSearchObjectAttributes; - timerange: TimeRangeParams; + timerange?: TimeRangeParams; } export interface JobPayloadPanelCsv extends JobParamsPanelCsv { @@ -27,8 +28,7 @@ export interface JobPayloadPanelCsv extends JobParamsPanelCsv { export interface JobParamsPanelCsv { savedObjectType: string; savedObjectId: string; - isImmediate: boolean; - post?: JobParamsPostPayloadPanelCsv; + post?: JobParamsPanelCsvPost; visType?: string; } diff --git a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts index 3727b2ec7b432..eaaa11d461156 100644 --- a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts @@ -7,10 +7,11 @@ import { cryptoFactory } from '../../../lib'; import { CreateJobFn, CreateJobFnFactory } from '../../../types'; import { validateUrls } from '../../common'; -import { JobParamsPNG } from '../types'; +import { JobParamsPNG, TaskPayloadPNG } from '../types'; export const createJobFnFactory: CreateJobFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 67fc51bbfc352..e6b36643900dd 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -8,7 +8,8 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PNG_JOB_TYPE } from '../../../../common/constants'; -import { RunTaskFn, RunTaskFnFactory, TaskRunResult } from '../../..//types'; +import { TaskRunResult } from '../../../lib/tasks'; +import { RunTaskFn, RunTaskFnFactory } from '../../../types'; import { decryptJobHeaders, getConditionalHeaders, @@ -18,12 +19,9 @@ import { import { generatePngObservableFactory } from '../lib/generate_png'; import { TaskPayloadPNG } from '../types'; -type QueuedPngExecutorFactory = RunTaskFnFactory>; - -export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFactoryFn( - reporting, - parentLogger -) { +export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); @@ -36,11 +34,11 @@ export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFac const generatePngObservable = await generatePngObservableFactory(reporting); const jobLogger = logger.clone([jobId]); const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), - map((decryptedHeaders) => omitBlockedHeaders({ job, decryptedHeaders })), - map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, logger)), + map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), mergeMap((conditionalHeaders) => { - const urls = getFullUrls({ config, job }); + const urls = getFullUrls(config, job); const hashUrl = urls[0]; if (apmGetAssets) apmGetAssets.end(); @@ -60,7 +58,6 @@ export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFac content_type: 'image/png', content: base64, size: (base64 && base64.length) || 0, - warnings, }; }), diff --git a/x-pack/plugins/reporting/server/export_types/png/index.ts b/x-pack/plugins/reporting/server/export_types/png/index.ts index 1cc6836572b7b..50e09a9984b2c 100644 --- a/x-pack/plugins/reporting/server/export_types/png/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/index.ts @@ -19,9 +19,7 @@ import { metadata } from './metadata'; import { JobParamsPNG, TaskPayloadPNG } from './types'; export const getExportType = (): ExportTypeDefinition< - JobParamsPNG, CreateJobFn, - TaskPayloadPNG, RunTaskFn > => ({ ...metadata, diff --git a/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts b/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts index 096d0bd428214..786936d43424c 100644 --- a/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts +++ b/x-pack/plugins/reporting/server/export_types/png/lib/generate_png.ts @@ -11,7 +11,7 @@ import { ReportingCore } from '../../../'; import { LevelLogger } from '../../../lib'; import { LayoutParams, PreserveLayout } from '../../../lib/layouts'; import { ScreenshotResults } from '../../../lib/screenshots'; -import { ConditionalHeaders } from '../../../types'; +import { ConditionalHeaders } from '../../common'; export async function generatePngObservableFactory(reporting: ReportingCore) { const getScreenshots = await reporting.getScreenshotsObservable(); @@ -19,7 +19,7 @@ export async function generatePngObservableFactory(reporting: ReportingCore) { return function generatePngObservable( logger: LevelLogger, url: string, - browserTimezone: string, + browserTimezone: string | undefined, conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams ): Rx.Observable<{ base64: string | null; warnings: string[] }> { diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/server/export_types/png/types.d.ts index 373b709592ed2..1f99082c757c6 100644 --- a/x-pack/plugins/reporting/server/export_types/png/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/png/types.d.ts @@ -4,19 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BaseParams, BasePayload } from '../../../server/types'; import { LayoutParams } from '../../lib/layouts'; +import { BaseParams, BasePayload } from '../../types'; -// Job params: structure of incoming user request data -export interface JobParamsPNG extends BaseParams { - title: string; +interface BaseParamsPNG { + layout: LayoutParams; + forceNow?: string; relativeUrl: string; } +// Job params: structure of incoming user request data +export type JobParamsPNG = BaseParamsPNG & BaseParams; + // Job payload: structure of stored job data provided by create_job -export interface TaskPayloadPNG extends BasePayload { - browserTimezone: string; - forceNow?: string; - layout: LayoutParams; - relativeUrl: string; -} +export type TaskPayloadPNG = BaseParamsPNG & BasePayload; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts index c75d721a72d15..44bf269212567 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts @@ -8,12 +8,13 @@ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { cryptoFactory } from '../../../lib'; import { CreateJobFn, CreateJobFnFactory } from '../../../types'; import { validateUrls } from '../../common'; -import { JobParamsPDF } from '../types'; +import { JobParamsPDF, TaskPayloadPDF } from '../types'; // @ts-ignore no module def (deprecated module) import { compatibilityShimFactory } from './compatibility_shim'; export const createJobFnFactory: CreateJobFnFactory> = function createJobFactoryFn(reporting, logger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); @@ -37,7 +38,7 @@ export const createJobFnFactory: CreateJobFnFactory ({ relativeUrl: u })), // 7.x only: `objects` in the payload title, - type: objectType, // 7.x only: this changes the shape of the job params object + objectType, }; }); }; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index f3dc5bd656f73..ea0d60a9fad12 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -8,7 +8,8 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PDF_JOB_TYPE } from '../../../../common/constants'; -import { RunTaskFn, RunTaskFnFactory, TaskRunResult } from '../../../types'; +import { TaskRunResult } from '../../../lib/tasks'; +import { RunTaskFn, RunTaskFnFactory } from '../../../types'; import { decryptJobHeaders, getConditionalHeaders, @@ -19,12 +20,9 @@ import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { getCustomLogo } from '../lib/get_custom_logo'; import { TaskPayloadPDF } from '../types'; -type QueuedPdfExecutorFactory = RunTaskFnFactory>; - -export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn( - reporting, - parentLogger -) { +export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); @@ -39,12 +37,12 @@ export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFac const jobLogger = logger.clone([jobId]); const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), - map((decryptedHeaders) => omitBlockedHeaders({ job, decryptedHeaders })), - map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, logger)), + map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), mergeMap((conditionalHeaders) => getCustomLogo(reporting, conditionalHeaders, job.spaceId)), mergeMap(({ logo, conditionalHeaders }) => { - const urls = getFullUrls({ config, job }); + const urls = getFullUrls(config, job); const { browserTimezone, layout, title } = job; if (apmGetAssets) apmGetAssets.end(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts index cf3ec9cdc8c2d..26704693ee489 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts @@ -19,9 +19,7 @@ import { metadata } from './metadata'; import { JobParamsPDF, TaskPayloadPDF } from './types'; export const getExportType = (): ExportTypeDefinition< - JobParamsPDF, CreateJobFn, - TaskPayloadPDF, RunTaskFn > => ({ ...metadata, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts index 17624c1bedb57..2cf5b69835d1f 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts @@ -9,9 +9,9 @@ import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; import { LevelLogger } from '../../../lib'; -import { createLayout, LayoutInstance, LayoutParams } from '../../../lib/layouts'; +import { createLayout, LayoutParams } from '../../../lib/layouts'; import { ScreenshotResults } from '../../../lib/screenshots'; -import { ConditionalHeaders } from '../../../types'; +import { ConditionalHeaders } from '../../common'; // @ts-ignore untyped module import { pdf } from './pdf'; import { getTracker } from './tracker'; @@ -35,7 +35,7 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) { logger: LevelLogger, title: string, urls: string[], - browserTimezone: string, + browserTimezone: string | undefined, conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams, logo?: string @@ -43,7 +43,7 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) { const tracker = getTracker(); tracker.startLayout(); - const layout = createLayout(captureConfig, layoutParams) as LayoutInstance; + const layout = createLayout(captureConfig, layoutParams); tracker.endLayout(); tracker.startScreenshots(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.test.ts index 8fa8fa5cbe3cb..426770d719069 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.test.ts @@ -11,7 +11,6 @@ import { createMockReportingCore, } from '../../../test_helpers'; import { getConditionalHeaders } from '../../common'; -import { TaskPayloadPDF } from '../types'; import { getCustomLogo } from './get_custom_logo'; let mockConfig: ReportingConfig; @@ -39,11 +38,7 @@ test(`gets logo from uiSettings`, async () => { get: mockGet, }); - const conditionalHeaders = getConditionalHeaders({ - job: {} as TaskPayloadPDF, - filteredHeaders: permittedHeaders, - config: mockConfig, - }); + const conditionalHeaders = getConditionalHeaders(mockConfig, permittedHeaders); const { logo } = await getCustomLogo(mockReportingPlugin, conditionalHeaders); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.ts index 35ab7001ecbe4..7bd1637db1379 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/get_custom_logo.ts @@ -6,7 +6,7 @@ import { ReportingCore } from '../../../'; import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../../common/constants'; -import { ConditionalHeaders } from '../../../types'; +import { ConditionalHeaders } from '../../common'; export const getCustomLogo = async ( reporting: ReportingCore, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts index 172296f62e40b..e47e81d9f717d 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts @@ -4,21 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BaseParams, BasePayload } from '../../../server/types'; -import { LayoutInstance, LayoutParams } from '../../lib/layouts'; +import { LayoutParams } from '../../lib/layouts'; +import { BaseParams, BasePayload } from '../../types'; -// Job params: structure of incoming user request data, after being parsed from RISON -export interface JobParamsPDF extends BaseParams { - title: string; +interface BaseParamsPDF { + layout: LayoutParams; + forceNow?: string; relativeUrls: string[]; - layout: LayoutInstance; } +// Job params: structure of incoming user request data, after being parsed from RISON +export type JobParamsPDF = BaseParamsPDF & BaseParams; + // Job payload: structure of stored job data provided by create_job -export interface TaskPayloadPDF extends BasePayload { - browserTimezone: string; - forceNow?: string; +export interface TaskPayloadPDF extends BasePayload { layout: LayoutParams; + forceNow?: string; objects: Array<{ relativeUrl: string; }>; diff --git a/x-pack/plugins/reporting/server/lib/check_license.ts b/x-pack/plugins/reporting/server/lib/check_license.ts index a764aa1f1eec6..1f8f66fe9b5ee 100644 --- a/x-pack/plugins/reporting/server/lib/check_license.ts +++ b/x-pack/plugins/reporting/server/lib/check_license.ts @@ -24,9 +24,7 @@ const messages = { }, }; -const makeManagementFeature = ( - exportTypes: Array> -) => { +const makeManagementFeature = (exportTypes: ExportTypeDefinition[]) => { return { id: 'management', checkLicense: (license?: ILicense) => { @@ -59,9 +57,7 @@ const makeManagementFeature = ( }; }; -const makeExportTypeFeature = ( - exportType: ExportTypeDefinition -) => { +const makeExportTypeFeature = (exportType: ExportTypeDefinition) => { return { id: exportType.id, checkLicense: (license?: ILicense) => { diff --git a/x-pack/plugins/reporting/server/lib/create_queue.ts b/x-pack/plugins/reporting/server/lib/create_queue.ts index 2da3d8bd47ccb..ded21d105f2f4 100644 --- a/x-pack/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/plugins/reporting/server/lib/create_queue.ts @@ -5,13 +5,13 @@ */ import { ReportingCore } from '../core'; -import { JobSource, TaskRunResult } from '../types'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; import { createTaggedLogger } from './esqueue/create_tagged_logger'; import { LevelLogger } from './level_logger'; -import { ReportingStore } from './store'; +import { ReportDocument, ReportingStore } from './store'; +import { TaskRunResult } from './tasks'; interface ESQueueWorker { on: (event: string, handler: any) => void; @@ -32,7 +32,7 @@ export interface ESQueueInstance { // GenericWorkerFn is a generic for ImmediateExecuteFn | ESQueueWorkerExecuteFn, type GenericWorkerFn = ( - jobSource: JobSource, + jobSource: ReportDocument, ...workerRestArgs: any[] ) => void | Promise; diff --git a/x-pack/plugins/reporting/server/lib/create_worker.ts b/x-pack/plugins/reporting/server/lib/create_worker.ts index c1c88dd8a54ba..7f03cefdb620e 100644 --- a/x-pack/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.ts @@ -9,10 +9,12 @@ import { PLUGIN_ID } from '../../common/constants'; import { durationToNumber } from '../../common/schema_utils'; import { ReportingCore } from '../../server'; import { LevelLogger } from '../../server/lib'; -import { ExportTypeDefinition, JobSource, RunTaskFn } from '../../server/types'; +import { RunTaskFn } from '../../server/types'; import { ESQueueInstance } from './create_queue'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; +import { ReportDocument } from './store'; +import { ReportTaskParams } from './tasks'; export function createWorkerFactory(reporting: ReportingCore, logger: LevelLogger) { const config = reporting.getConfig(); @@ -23,18 +25,16 @@ export function createWorkerFactory(reporting: ReportingCore, log // Once more document types are added, this will need to be passed in return async function createWorker(queue: ESQueueInstance) { // export type / execute job map - const jobExecutors: Map> = new Map(); + const jobExecutors: Map = new Map(); - for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< - ExportTypeDefinition> - >) { + for (const exportType of reporting.getExportTypesRegistry().getAll()) { const jobExecutor = exportType.runTaskFnFactory(reporting, logger); jobExecutors.set(exportType.jobType, jobExecutor); } - const workerFn = ( - jobSource: JobSource, - jobParams: TaskPayloadType, + const workerFn = ( + jobSource: ReportDocument, + payload: ReportTaskParams['payload'], cancellationToken: CancellationToken ) => { const { @@ -52,7 +52,7 @@ export function createWorkerFactory(reporting: ReportingCore, log } // pass the work to the jobExecutor - return jobTypeExecutor(jobId, jobParams, cancellationToken); + return jobTypeExecutor(jobId, payload, cancellationToken); }; const workerOptions = { diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts index 5acc6e38dddf9..305247e6f8637 100644 --- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -6,7 +6,8 @@ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { ReportingCore } from '../'; -import { BaseParams, CreateJobFn, ReportingUser } from '../types'; +import { durationToNumber } from '../../common/schema_utils'; +import { BaseParams, ReportingUser } from '../types'; import { LevelLogger } from './'; import { Report } from './store'; @@ -23,6 +24,13 @@ export function enqueueJobFactory( parentLogger: LevelLogger ): EnqueueJobFn { const logger = parentLogger.clone(['queue-job']); + const config = reporting.getConfig(); + const jobSettings = { + timeout: durationToNumber(config.get('queue', 'timeout')), + browser_type: config.get('capture', 'browser', 'type'), + max_attempts: config.get('capture', 'maxAttempts'), + priority: 10, // unused + }; return async function enqueueJob( exportTypeId: string, @@ -31,8 +39,6 @@ export function enqueueJobFactory( context: RequestHandlerContext, request: KibanaRequest ) { - type CreateJobFnType = CreateJobFn; - const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); if (exportType == null) { @@ -40,15 +46,24 @@ export function enqueueJobFactory( } const [createJob, { store }] = await Promise.all([ - exportType.createJobFnFactory(reporting, logger) as CreateJobFnType, + exportType.createJobFnFactory(reporting, logger), reporting.getPluginStartDeps(), ]); - // add encrytped headers - const payload = await createJob(jobParams, context, request); + const job = await createJob(jobParams, context, request); + const pendingReport = new Report({ + jobtype: exportType.jobType, + created_by: user ? user.username : false, + payload: job, + meta: { + objectType: jobParams.objectType, + layout: jobParams.layout?.id, + }, + ...jobSettings, + }); // store the pending report, puts it in the Reporting Management UI table - const report = await store.addReport(exportType.jobType, user, payload); + const report = await store.addReport(pendingReport); logger.info(`Scheduled ${exportType.name} report: ${report._id}`); diff --git a/x-pack/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/plugins/reporting/server/lib/export_types_registry.ts index 1159221a9224e..e93cdba48a26a 100644 --- a/x-pack/plugins/reporting/server/lib/export_types_registry.ts +++ b/x-pack/plugins/reporting/server/lib/export_types_registry.ts @@ -9,21 +9,16 @@ import { getExportType as getTypeCsv } from '../export_types/csv'; import { getExportType as getTypeCsvFromSavedObject } from '../export_types/csv_from_savedobject'; import { getExportType as getTypePng } from '../export_types/png'; import { getExportType as getTypePrintablePdf } from '../export_types/printable_pdf'; -import { ExportTypeDefinition } from '../types'; +import { CreateJobFn, ExportTypeDefinition } from '../types'; -type GetCallbackFn = ( - item: ExportTypeDefinition -) => boolean; -// => ExportTypeDefinition +type GetCallbackFn = (item: ExportTypeDefinition) => boolean; export class ExportTypesRegistry { - private _map: Map> = new Map(); + private _map: Map = new Map(); constructor() {} - register( - item: ExportTypeDefinition - ): void { + register(item: ExportTypeDefinition): void { if (!isString(item.id)) { throw new Error(`'item' must have a String 'id' property `); } @@ -43,35 +38,21 @@ export class ExportTypesRegistry { return this._map.size; } - getById( - id: string - ): ExportTypeDefinition { + getById(id: string): ExportTypeDefinition { if (!this._map.has(id)) { throw new Error(`Unknown id ${id}`); } - return this._map.get(id) as ExportTypeDefinition< - JobParamsType, - CreateJobFnType, - JobPayloadType, - RunTaskFnType - >; + return this._map.get(id) as ExportTypeDefinition; } - get( - findType: GetCallbackFn - ): ExportTypeDefinition { + get(findType: GetCallbackFn): ExportTypeDefinition { let result; for (const value of this._map.values()) { if (!findType(value)) { continue; // try next value } - const foundResult: ExportTypeDefinition< - JobParamsType, - CreateJobFnType, - JobPayloadType, - RunTaskFnType - > = value; + const foundResult: ExportTypeDefinition = value; if (result) { throw new Error('Found multiple items matching predicate.'); @@ -88,13 +69,19 @@ export class ExportTypesRegistry { } } +// TODO: Define a 2nd ExportTypeRegistry instance for "immediate execute" report job types only. +// It should not require a `CreateJobFn` for its ExportTypeDefinitions, which only makes sense for async. +// Once that is done, the `any` types below can be removed. + +/* + * @return ExportTypeRegistry: the ExportTypeRegistry instance that should be + * used to register async export type definitions + */ export function getExportTypesRegistry(): ExportTypesRegistry { const registry = new ExportTypesRegistry(); - - /* this replaces the previously async method of registering export types, - * where this would run a directory scan and types would be registered via - * discovery */ - const getTypeFns: Array<() => ExportTypeDefinition> = [ + type CreateFnType = CreateJobFn; // can not specify params types because different type of params are not assignable to each other + type RunFnType = any; // can not specify because ImmediateExecuteFn is not assignable to RunTaskFn + const getTypeFns: Array<() => ExportTypeDefinition> = [ getTypeCsv, getTypeCsvFromSavedObject, getTypePng, diff --git a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts index 921d302387edf..585175aac82c5 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts @@ -6,12 +6,11 @@ import { CaptureConfig } from '../../types'; import { LayoutParams, LayoutTypes } from './'; -import { Layout } from './layout'; import { PreserveLayout } from './preserve_layout'; import { PrintLayout } from './print_layout'; -export function createLayout(captureConfig: CaptureConfig, layoutParams?: LayoutParams): Layout { - if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) { +export function createLayout(captureConfig: CaptureConfig, layoutParams?: LayoutParams) { + if (layoutParams && layoutParams.dimensions && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) { return new PreserveLayout(layoutParams.dimensions); } diff --git a/x-pack/plugins/reporting/server/lib/layouts/index.ts b/x-pack/plugins/reporting/server/lib/layouts/index.ts index 507b7614072ea..c091339a60582 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/index.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/index.ts @@ -53,7 +53,7 @@ export interface Size { export interface LayoutParams { id: string; - dimensions: Size; + dimensions?: Size; selectors?: LayoutSelectorDictionary; } @@ -64,4 +64,4 @@ interface LayoutSelectors { positionElements?: (browser: HeadlessChromiumDriver, logger: LevelLogger) => Promise; } -export type LayoutInstance = Layout & LayoutSelectors & Size; +export type LayoutInstance = Layout & LayoutSelectors & Partial; diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts index e8d182dac0b1d..cecd761fbcf32 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts @@ -12,12 +12,13 @@ import { LayoutTypes, PageSizeParams, Size, + LayoutInstance, } from './'; // We use a zoom of two to bump up the resolution of the screenshot a bit. const ZOOM: number = 2; -export class PreserveLayout extends Layout { +export class PreserveLayout extends Layout implements LayoutInstance { public readonly selectors: LayoutSelectorDictionary = getDefaultLayoutSelectors(); public readonly groupCount = 1; public readonly height: number; diff --git a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts index b055fae8a780d..33f16bc7865d5 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts @@ -9,10 +9,16 @@ import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer'; import { LevelLogger } from '../'; import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig } from '../../types'; -import { getDefaultLayoutSelectors, LayoutSelectorDictionary, LayoutTypes, Size } from './'; +import { + getDefaultLayoutSelectors, + LayoutInstance, + LayoutSelectorDictionary, + LayoutTypes, + Size, +} from './'; import { Layout } from './layout'; -export class PrintLayout extends Layout { +export class PrintLayout extends Layout implements LayoutInstance { public readonly selectors: LayoutSelectorDictionary = { ...getDefaultLayoutSelectors(), screenshot: '[data-shared-item]', diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts index afd6364454835..5f7919df4e9fd 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts @@ -5,7 +5,7 @@ */ import { LevelLogger, startTrace } from '../'; -import { LayoutInstance } from '../../../common/types'; +import { LayoutInstance } from '../layouts'; import { HeadlessChromiumDriver } from '../../browsers'; import { CONTEXT_GETTIMERANGE } from './constants'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/lib/screenshots/index.ts index c1d33cb519384..1b9722fb49458 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/index.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/index.ts @@ -6,7 +6,7 @@ import * as Rx from 'rxjs'; import { LevelLogger } from '../'; -import { ConditionalHeaders } from '../../types'; +import { ConditionalHeaders } from '../../export_types/common'; import { LayoutInstance } from '../layouts'; export { screenshotsObservableFactory } from './observable'; @@ -16,7 +16,7 @@ export interface ScreenshotObservableOpts { urls: string[]; conditionalHeaders: ConditionalHeaders; layout: LayoutInstance; - browserTimezone: string; + browserTimezone?: string; } export interface AttributesMap { diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts index 5b671e9f5b47e..798f926cd0a31 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts @@ -18,6 +18,7 @@ jest.mock('../../browsers/chromium/puppeteer', () => ({ import moment from 'moment'; import * as Rx from 'rxjs'; import { HeadlessChromiumDriver } from '../../browsers'; +import { ConditionalHeaders } from '../../export_types/common'; import { createMockBrowserDriverFactory, createMockConfig, @@ -25,7 +26,6 @@ import { createMockLayoutInstance, createMockLevelLogger, } from '../../test_helpers'; -import { ConditionalHeaders } from '../../types'; import { ElementsPositionAndAttribute } from './'; import * as contexts from './constants'; import { screenshotsObservableFactory } from './observable'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts index e28f50851f4d9..e8b7f91764efd 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts @@ -5,10 +5,11 @@ */ import { i18n } from '@kbn/i18n'; -import { durationToNumber } from '../../../common/schema_utils'; import { LevelLogger, startTrace } from '../'; +import { durationToNumber } from '../../../common/schema_utils'; import { HeadlessChromiumDriver } from '../../browsers'; -import { CaptureConfig, ConditionalHeaders } from '../../types'; +import { ConditionalHeaders } from '../../export_types/common'; +import { CaptureConfig } from '../../types'; export const openUrl = async ( captureConfig: CaptureConfig, diff --git a/x-pack/plugins/reporting/server/lib/store/index.ts b/x-pack/plugins/reporting/server/lib/store/index.ts index a88d36d3fdf9a..a48f266120323 100644 --- a/x-pack/plugins/reporting/server/lib/store/index.ts +++ b/x-pack/plugins/reporting/server/lib/store/index.ts @@ -4,5 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Report } from './report'; +export { Report, ReportDocument } from './report'; export { ReportingStore } from './store'; diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts index 9ac5d1f87c387..1e4a833c7cabe 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts @@ -14,7 +14,8 @@ describe('Class Report', () => { created_by: 'created_by_test_string', browser_type: 'browser_type_test_string', max_attempts: 50, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + payload: { headers: 'payload_test_field', objectType: 'testOt', title: 'cool report' }, + meta: { objectType: 'test' }, timeout: 30000, priority: 1, }); @@ -25,11 +26,10 @@ describe('Class Report', () => { attempts: 0, browser_type: 'browser_type_test_string', completed_at: undefined, - created_at: undefined, created_by: 'created_by_test_string', jobtype: 'test-report', max_attempts: 50, - meta: undefined, + meta: { objectType: 'test' }, payload: { headers: 'payload_test_field', objectType: 'testOt' }, priority: 1, started_at: undefined, @@ -38,12 +38,16 @@ describe('Class Report', () => { }, }); expect(report.toApiJSON()).toMatchObject({ + attempts: 0, browser_type: 'browser_type_test_string', created_by: 'created_by_test_string', + index: '.reporting-test-index-12345', jobtype: 'test-report', max_attempts: 50, payload: { headers: 'payload_test_field', objectType: 'testOt' }, + meta: { objectType: 'test' }, priority: 1, + status: 'pending', timeout: 30000, }); @@ -57,7 +61,8 @@ describe('Class Report', () => { created_by: 'created_by_test_string', browser_type: 'browser_type_test_string', max_attempts: 50, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + payload: { headers: 'payload_test_field', objectType: 'testOt', title: 'hot report' }, + meta: { objectType: 'stange' }, timeout: 30000, priority: 1, }); @@ -70,51 +75,46 @@ describe('Class Report', () => { }; report.updateWithEsDoc(metadata); - expect(report.toEsDocsJSON()).toMatchInlineSnapshot(` - Object { - "_id": "12342p9o387549o2345", - "_index": ".reporting-test-update", - "_source": Object { - "attempts": 0, - "browser_type": "browser_type_test_string", - "completed_at": undefined, - "created_at": undefined, - "created_by": "created_by_test_string", - "jobtype": "test-report", - "max_attempts": 50, - "meta": undefined, - "payload": Object { - "headers": "payload_test_field", - "objectType": "testOt", - }, - "priority": 1, - "started_at": undefined, - "status": "pending", - "timeout": 30000, - }, - } - `); - expect(report.toApiJSON()).toMatchInlineSnapshot(` - Object { - "attempts": 0, - "browser_type": "browser_type_test_string", - "completed_at": undefined, - "created_at": undefined, - "created_by": "created_by_test_string", - "id": "12342p9o387549o2345", - "index": ".reporting-test-update", - "jobtype": "test-report", - "max_attempts": 50, - "meta": undefined, - "payload": Object { - "headers": "payload_test_field", - "objectType": "testOt", - }, - "priority": 1, - "started_at": undefined, - "status": "pending", - "timeout": 30000, - } - `); + expect(report.toEsDocsJSON()).toMatchObject({ + _id: '12342p9o387549o2345', + _index: '.reporting-test-update', + _source: { + attempts: 0, + browser_type: 'browser_type_test_string', + completed_at: undefined, + created_by: 'created_by_test_string', + jobtype: 'test-report', + max_attempts: 50, + meta: { objectType: 'stange' }, + payload: { objectType: 'testOt' }, + priority: 1, + started_at: undefined, + status: 'pending', + timeout: 30000, + }, + }); + expect(report.toApiJSON()).toMatchObject({ + attempts: 0, + browser_type: 'browser_type_test_string', + completed_at: undefined, + created_by: 'created_by_test_string', + id: '12342p9o387549o2345', + index: '.reporting-test-update', + jobtype: 'test-report', + max_attempts: 50, + meta: { objectType: 'stange' }, + payload: { headers: 'payload_test_field', objectType: 'testOt' }, + priority: 1, + started_at: undefined, + status: 'pending', + timeout: 30000, + }); + }); + + it('throws error if converted to task JSON before being synced with ES storage', () => { + const report = new Report({} as any); + expect(() => report.updateWithEsDoc(report)).toThrowErrorMatchingInlineSnapshot( + `"Report object from ES has missing fields!"` + ); }); }); diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts index 5c9b9ced7cce7..d82b90f4025ed 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.ts @@ -4,84 +4,96 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; // @ts-ignore no module definition import Puid from 'puid'; +import { JobStatus, ReportApiJSON } from '../../../common/types'; import { JobStatuses } from '../../../constants'; -import { LayoutInstance } from '../layouts'; +import { LayoutParams } from '../layouts'; +import { TaskRunResult } from '../tasks'; -/* - * The document created by Reporting to store in the .reporting index - */ -interface ReportingDocument { +interface ReportDocumentHead { _id: string; _index: string; _seq_no: unknown; _primary_term: unknown; +} + +/* + * The document created by Reporting to store in the .reporting index + */ +export interface ReportDocument extends ReportDocumentHead { + _source: ReportSource; +} + +export interface ReportSource { jobtype: string; + kibana_name: string; + kibana_id: string; created_by: string | false; payload: { headers: string; // encrypted headers + browserTimezone?: string; // may use timezone from advanced settings objectType: string; - layout?: LayoutInstance; + title: string; + layout?: LayoutParams; }; - meta: unknown; + meta: { objectType: string; layout?: string }; browser_type: string; max_attempts: number; timeout: number; - status: string; + status: JobStatus; attempts: number; - output?: unknown; + output: TaskRunResult | null; started_at?: string; completed_at?: string; - created_at?: string; + created_at: string; priority?: number; process_expiration?: string; } -/* - * The document created by Reporting to store as task parameters for Task - * Manager to reference the report in .reporting - */ const puid = new Puid(); -export class Report implements Partial { +export class Report implements Partial { public _index?: string; public _id: string; public _primary_term?: unknown; // set by ES public _seq_no: unknown; // set by ES - public readonly jobtype: string; - public readonly created_at?: string; - public readonly created_by?: string | false; - public readonly payload: { - headers: string; // encrypted headers - objectType: string; - layout?: LayoutInstance; - }; - public readonly meta: unknown; - public readonly max_attempts: number; - public readonly browser_type?: string; - - public readonly status: string; - public readonly attempts: number; - public readonly output?: unknown; - public readonly started_at?: string; - public readonly completed_at?: string; - public readonly process_expiration?: string; - public readonly priority?: number; - public readonly timeout?: number; + public readonly kibana_name: ReportSource['kibana_name']; + public readonly kibana_id: ReportSource['kibana_id']; + public readonly jobtype: ReportSource['jobtype']; + public readonly created_at: ReportSource['created_at']; + public readonly created_by: ReportSource['created_by']; + public readonly payload: ReportSource['payload']; + + public readonly meta: ReportSource['meta']; + public readonly max_attempts: ReportSource['max_attempts']; + public readonly browser_type?: ReportSource['browser_type']; + + public readonly status: ReportSource['status']; + public readonly attempts: ReportSource['attempts']; + public readonly output?: ReportSource['output']; + public readonly started_at?: ReportSource['started_at']; + public readonly completed_at?: ReportSource['completed_at']; + public readonly process_expiration?: ReportSource['process_expiration']; + public readonly priority?: ReportSource['priority']; + public readonly timeout?: ReportSource['timeout']; /* * Create an unsaved report + * Index string is required */ - constructor(opts: Partial) { + constructor(opts: Partial & Partial) { this._id = opts._id != null ? opts._id : puid.generate(); this._index = opts._index; this._primary_term = opts._primary_term; this._seq_no = opts._seq_no; this.payload = opts.payload!; + this.kibana_name = opts.kibana_name!; + this.kibana_id = opts.kibana_id!; this.jobtype = opts.jobtype!; this.max_attempts = opts.max_attempts!; this.attempts = opts.attempts || 0; @@ -89,9 +101,9 @@ export class Report implements Partial { this.process_expiration = opts.process_expiration; this.timeout = opts.timeout; - this.created_at = opts.created_at; - this.created_by = opts.created_by; - this.meta = opts.meta; + this.created_at = opts.created_at || moment.utc().toISOString(); + this.created_by = opts.created_by || false; + this.meta = opts.meta || { objectType: 'unknown' }; this.browser_type = opts.browser_type; this.priority = opts.priority; @@ -141,10 +153,12 @@ export class Report implements Partial { /* * Data structure for API responses */ - toApiJSON() { + toApiJSON(): ReportApiJSON { return { id: this._id, - index: this._index, + index: this._index!, + kibana_name: this.kibana_name, + kibana_id: this.kibana_id, jobtype: this.jobtype, created_at: this.created_at, created_by: this.created_by, diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts index 8dc4edd200052..931eae8b246c4 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts @@ -48,28 +48,25 @@ describe('ReportingStore', () => { describe('addReport', () => { it('returns Report object', async () => { const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_1', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'username1' }, reportPayload) - ).resolves.toMatchObject({ + const mockReport = new Report({ + _index: '.reporting-mock', + attempts: 0, + created_by: 'username1', + jobtype: 'unknowntype', + status: 'pending', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, attempts: 0, - browser_type: undefined, completed_at: undefined, created_by: 'username1', jobtype: 'unknowntype', - max_attempts: undefined, payload: {}, - priority: 10, - started_at: undefined, + meta: {}, status: 'pending', - timeout: 120000, }); }); @@ -83,15 +80,15 @@ describe('ReportingStore', () => { mockCore = await createMockReportingCore(mockConfig); const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_2', - objectType: 'testOt', - }; - expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).rejects.toMatchInlineSnapshot(`[Error: Invalid index interval: centurially]`); + const mockReport = new Report({ + _index: '.reporting-errortest', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + `[TypeError: this.client.callAsInternalUser is not a function]` + ); }); it('handles error creating the index', async () => { @@ -100,15 +97,15 @@ describe('ReportingStore', () => { callClusterStub.withArgs('indices.create').rejects(new Error('horrible error')); const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_3', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).rejects.toMatchInlineSnapshot(`[Error: horrible error]`); + const mockReport = new Report({ + _index: '.reporting-errortest', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + `[Error: horrible error]` + ); }); /* Creating the index will fail, if there were multiple jobs staged in @@ -123,15 +120,15 @@ describe('ReportingStore', () => { callClusterStub.withArgs('indices.create').rejects(new Error('devastating error')); const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_4', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).rejects.toMatchInlineSnapshot(`[Error: devastating error]`); + const mockReport = new Report({ + _index: '.reporting-mock', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + `[Error: devastating error]` + ); }); it('skips creating the index if already exists', async () => { @@ -142,28 +139,20 @@ describe('ReportingStore', () => { .rejects(new Error('resource_already_exists_exception')); // will be triggered but ignored const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_headers_5', - objectType: 'testOt', - }; - await expect( - store.addReport(reportType, { username: 'user1' }, reportPayload) - ).resolves.toMatchObject({ + const mockReport = new Report({ + created_by: 'user1', + jobtype: 'unknowntype', + payload: {}, + meta: {}, + } as any); + await expect(store.addReport(mockReport)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, attempts: 0, - browser_type: undefined, - completed_at: undefined, created_by: 'user1', jobtype: 'unknowntype', - max_attempts: undefined, payload: {}, - priority: 10, - started_at: undefined, status: 'pending', - timeout: 120000, }); }); @@ -175,26 +164,24 @@ describe('ReportingStore', () => { .rejects(new Error('resource_already_exists_exception')); // will be triggered but ignored const store = new ReportingStore(mockCore, mockLogger); - const reportType = 'unknowntype'; - const reportPayload = { - browserTimezone: 'UTC', - headers: 'rp_test_headers', - objectType: 'testOt', - }; - await expect(store.addReport(reportType, false, reportPayload)).resolves.toMatchObject({ + const mockReport = new Report({ + _index: '.reporting-unsecured', + attempts: 0, + created_by: false, + jobtype: 'unknowntype', + payload: {}, + meta: {}, + status: 'pending', + } as any); + await expect(store.addReport(mockReport)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, attempts: 0, - browser_type: undefined, - completed_at: undefined, created_by: false, jobtype: 'unknowntype', - max_attempts: undefined, + meta: {}, payload: {}, - priority: 10, - started_at: undefined, status: 'pending', - timeout: 120000, }); }); }); @@ -209,8 +196,10 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', objectType: 'testOt', + browserTimezone: 'ABC', }, timeout: 30000, priority: 1, @@ -248,8 +237,10 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', objectType: 'testOt', + browserTimezone: 'BCD', }, timeout: 30000, priority: 1, @@ -287,8 +278,10 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', objectType: 'testOt', + browserTimezone: 'CDE', }, timeout: 30000, priority: 1, @@ -326,8 +319,10 @@ describe('ReportingStore', () => { browser_type: 'browser_type_test_string', max_attempts: 50, payload: { + title: 'test report', headers: 'rp_test_headers', objectType: 'testOt', + browserTimezone: 'utc', }, timeout: 30000, priority: 1, diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 2acc03d4f41bd..c20a9e991b4bc 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -5,21 +5,12 @@ */ import { ElasticsearchServiceSetup } from 'src/core/server'; -import { durationToNumber } from '../../../common/schema_utils'; import { LevelLogger, statuses } from '../'; import { ReportingCore } from '../../'; -import { BaseParams, BaseParamsEncryptedFields, ReportingUser } from '../../types'; import { indexTimestamp } from './index_timestamp'; import { mapping } from './mapping'; import { Report } from './report'; -interface JobSettings { - timeout: number; - browser_type: string; - max_attempts: number; - priority: number; -} - const checkReportIsEditable = (report: Report) => { if (!report._id || !report._index) { throw new Error(`Report object is not synced with ES!`); @@ -35,7 +26,6 @@ const checkReportIsEditable = (report: Report) => { export class ReportingStore { private readonly indexPrefix: string; private readonly indexInterval: string; - private readonly jobSettings: JobSettings; private client: ElasticsearchServiceSetup['legacy']['client']; private logger: LevelLogger; @@ -46,13 +36,6 @@ export class ReportingStore { this.client = elasticsearch.legacy.client; this.indexPrefix = config.get('index'); this.indexInterval = config.get('queue', 'indexInterval'); - this.jobSettings = { - timeout: durationToNumber(config.get('queue', 'timeout')), - browser_type: config.get('capture', 'browser', 'type'), - max_attempts: config.get('capture', 'maxAttempts'), - priority: 10, // unused - }; - this.logger = logger; } @@ -101,36 +84,17 @@ export class ReportingStore { * Called from addReport, which handles any errors */ private async indexReport(report: Report) { - const params = report.payload; - - // Queing is handled by TM. These queueing-based fields for reference in Report Info panel - const infoFields = { - timeout: report.timeout, - process_expiration: new Date(0), // use epoch so the job query works - created_at: new Date(), - attempts: 0, - max_attempts: report.max_attempts, - status: statuses.JOB_STATUS_PENDING, - browser_type: report.browser_type, - }; - - const indexParams = { + const doc = { index: report._index, id: report._id, body: { - ...infoFields, - jobtype: report.jobtype, - meta: { - // We are copying these values out of payload because these fields are indexed and can be aggregated on - // for tracking stats, while payload contents are not. - objectType: (params as any).type, // params.type for legacy (7.x) - layout: params.layout ? params.layout.id : 'none', - }, - payload: report.payload, - created_by: report.created_by, + ...report.toEsDocsJSON()._source, + process_expiration: new Date(0), // use epoch so the job query works + attempts: 0, + status: statuses.JOB_STATUS_PENDING, }, }; - return await this.client.callAsInternalUser('index', indexParams); + return await this.client.callAsInternalUser('index', doc); } /* @@ -140,23 +104,15 @@ export class ReportingStore { return await this.client.callAsInternalUser('indices.refresh', { index }); } - public async addReport( - type: string, - user: ReportingUser, - payload: BaseParams & BaseParamsEncryptedFields - ): Promise { - const timestamp = indexTimestamp(this.indexInterval); - const index = `${this.indexPrefix}-${timestamp}`; + public async addReport(report: Report): Promise { + let index = report._index; + if (!index) { + const timestamp = indexTimestamp(this.indexInterval); + index = `${this.indexPrefix}-${timestamp}`; + report._index = index; + } await this.createIndex(index); - const report = new Report({ - _index: index, - payload, - jobtype: type, - created_by: user ? user.username : false, - ...this.jobSettings, - }); - try { const doc = await this.indexReport(report); report.updateWithEsDoc(doc); @@ -166,7 +122,7 @@ export class ReportingStore { return report; } catch (err) { - this.logger.error(`Error in addReport!`); + this.logger.error(`Error in adding a report!`); this.logger.error(err); throw err; } @@ -220,7 +176,7 @@ export class ReportingStore { public async setReportCompleted(report: Report, stats: Partial): Promise { try { - const { output } = stats as { output: any }; + const { output } = stats; const status = output && output.warnings && output.warnings.length > 0 ? statuses.JOB_STATUS_WARNINGS diff --git a/x-pack/plugins/reporting/server/lib/tasks/index.ts b/x-pack/plugins/reporting/server/lib/tasks/index.ts new file mode 100644 index 0000000000000..0dd9945985bfb --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/tasks/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { BasePayload } from '../../types'; +import { ReportSource } from '../store/report'; + +/* + * The document created by Reporting to store as task parameters for Task + * Manager to reference the report in .reporting + */ +export interface ReportTaskParams { + id: string; + index?: string; // For ad-hoc, which as an existing "pending" record + payload: JobPayloadType; + created_at: ReportSource['created_at']; + created_by: ReportSource['created_by']; + jobtype: ReportSource['jobtype']; + attempts: ReportSource['attempts']; + meta: ReportSource['meta']; +} + +export interface TaskRunResult { + content_type: string | null; + content: string | null; + csv_contains_formulas?: boolean; + size: number; + max_size_reached?: boolean; + warnings?: string[]; +} diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts index 33620bc9a0038..dc4b30ffcfa7c 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts @@ -9,8 +9,8 @@ import { ReportingCore } from '../..'; import { API_DIAGNOSE_URL } from '../../../common/constants'; import { browserStartLogs } from '../../browsers/chromium/driver_factory/start_logs'; import { LevelLogger as Logger } from '../../lib'; -import { DiagnosticResponse } from '../../types'; import { authorizedUserPreRoutingFactory } from '../lib/authorized_user_pre_routing'; +import { DiagnosticResponse } from './'; const logsToHelpMap = { 'error while loading shared libraries': i18n.translate( diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/config.ts b/x-pack/plugins/reporting/server/routes/diagnostic/config.ts index 95c3a05bbf680..70428779366b3 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/config.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/config.ts @@ -10,8 +10,8 @@ import { defaults, get } from 'lodash'; import { ReportingCore } from '../..'; import { API_DIAGNOSE_URL } from '../../../common/constants'; import { LevelLogger as Logger } from '../../lib'; -import { DiagnosticResponse } from '../../types'; import { authorizedUserPreRoutingFactory } from '../lib/authorized_user_pre_routing'; +import { DiagnosticResponse } from './'; const KIBANA_MAX_SIZE_BYTES_PATH = 'csv.maxSizeBytes'; const ES_MAX_SIZE_BYTES_PATH = 'http.max_content_length'; diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/index.ts b/x-pack/plugins/reporting/server/routes/diagnostic/index.ts index 895dee32614f1..84df91ea31b62 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/index.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/index.ts @@ -15,3 +15,9 @@ export const registerDiagnosticRoutes = (reporting: ReportingCore, logger: Logge registerDiagnoseConfig(reporting, logger); registerDiagnoseScreenshot(reporting, logger); }; + +export interface DiagnosticResponse { + help: string[]; + success: boolean; + logs: string; +} diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts index 0acf384869ded..6ea6e22c5d7f9 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts @@ -11,8 +11,8 @@ import { omitBlockedHeaders } from '../../export_types/common'; import { getAbsoluteUrlFactory } from '../../export_types/common/get_absolute_url'; import { generatePngObservableFactory } from '../../export_types/png/lib/generate_png'; import { LevelLogger as Logger } from '../../lib'; -import { DiagnosticResponse } from '../../types'; import { authorizedUserPreRoutingFactory } from '../lib/authorized_user_pre_routing'; +import { DiagnosticResponse } from './'; export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Logger) => { const setupDeps = reporting.getPluginSetupDeps(); @@ -54,10 +54,7 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log }; const headers = { - headers: omitBlockedHeaders({ - job: null, - decryptedHeaders, - }), + headers: omitBlockedHeaders(decryptedHeaders), conditions: { hostname, port: +port, diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 517f1dadc0ac1..400fbb16f54dc 100644 --- a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -10,17 +10,20 @@ import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { createJobFnFactory } from '../export_types/csv_from_savedobject/create_job'; import { runTaskFnFactory } from '../export_types/csv_from_savedobject/execute_job'; -import { JobParamsPostPayloadPanelCsv } from '../export_types/csv_from_savedobject/types'; +import { + JobParamsPanelCsv, + JobParamsPanelCsvPost, +} from '../export_types/csv_from_savedobject/types'; import { LevelLogger as Logger } from '../lib'; -import { TaskRunResult } from '../types'; +import { TaskRunResult } from '../lib/tasks'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { getJobParamsFromRequest } from './lib/get_job_params_from_request'; import { HandlerErrorFunction } from './types'; export type CsvFromSavedObjectRequest = KibanaRequest< - { savedObjectType: string; savedObjectId: string }, + JobParamsPanelCsv, unknown, - JobParamsPostPayloadPanelCsv + JobParamsPanelCsvPost >; /* @@ -66,27 +69,22 @@ export function registerGenerateCsvFromSavedObjectImmediate( }, userHandler(async (user, context, req: CsvFromSavedObjectRequest, res) => { const logger = parentLogger.clone(['savedobject-csv']); - const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); + const jobParams = getJobParamsFromRequest(req); const createJob = createJobFnFactory(reporting, logger); const runTaskFn = runTaskFnFactory(reporting, logger); try { // FIXME: no create job for immediate download - const jobDocPayload = await createJob(jobParams, req.headers, context, req); + const payload = await createJob(jobParams, context, req); const { content_type: jobOutputContentType, content: jobOutputContent, size: jobOutputSize, - }: TaskRunResult = await runTaskFn(null, jobDocPayload, context, req); + }: TaskRunResult = await runTaskFn(null, payload, context, req); logger.info(`Job output size: ${jobOutputSize} bytes`); - /* - * ESQueue worker function defaults `content` to null, even if the - * runTask returned undefined. - * - * This converts null to undefined so the value can be sent to h.response() - */ + // convert null to undefined so the value can be sent to h.response() if (jobOutputContent === null) { logger.warn('CSV Job Execution created empty content result'); } diff --git a/x-pack/plugins/reporting/server/routes/generation.test.ts b/x-pack/plugins/reporting/server/routes/generation.test.ts index dd905223a81d5..867af75c8de27 100644 --- a/x-pack/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/plugins/reporting/server/routes/generation.test.ts @@ -74,8 +74,8 @@ describe('POST /api/reporting/generate', () => { jobContentEncoding: 'base64', jobContentExtension: 'pdf', validLicenses: ['basic', 'gold'], - createJobFnFactory: () => () => ({ jobParamsTest: { test1: 'yes' } }), - runTaskFnFactory: () => () => ({ runParamsTest: { test2: 'yes' } }), + createJobFnFactory: () => async () => ({ createJobTest: { test1: 'yes' } } as any), + runTaskFnFactory: () => async () => ({ runParamsTest: { test2: 'yes' } } as any), }); core.getExportTypesRegistry = () => mockExportTypesRegistry; }); @@ -163,9 +163,21 @@ describe('POST /api/reporting/generate', () => { .then(({ body }) => { expect(body).toMatchObject({ job: { - id: expect.any(String), + attempts: 0, + created_by: 'Tom Riddle', + id: 'foo', + index: 'foo-index', + jobtype: 'printable_pdf', + payload: { + createJobTest: { + test1: 'yes', + }, + }, + priority: 10, + status: 'pending', + timeout: 10000, }, - path: expect.any(String), + path: 'undefined/api/reporting/jobs/download/foo', }); }); }); diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index 11ad4cc9d4eb8..22edd4002dbcf 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -15,3 +15,10 @@ export function registerRoutes(reporting: ReportingCore, logger: Logger) { registerJobInfoRoutes(reporting); registerDiagnosticRoutes(reporting, logger); } + +export interface ReportingRequestPre { + management: { + jobTypes: string[]; + }; + user: string; +} diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.ts b/x-pack/plugins/reporting/server/routes/jobs.test.ts index 187c69f4a72ef..fc1cfd00493c3 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.test.ts @@ -25,7 +25,6 @@ describe('GET /api/reporting/jobs/download', () => { let core: ReportingCore; const config = createMockConfig(createMockConfigSchema()); - const getHits = (...sources: any) => { return { hits: { @@ -69,14 +68,14 @@ describe('GET /api/reporting/jobs/download', () => { jobType: 'unencodedJobType', jobContentExtension: 'csv', validLicenses: ['basic', 'gold'], - } as ExportTypeDefinition); + } as ExportTypeDefinition); exportTypesRegistry.register({ id: 'base64Encoded', jobType: 'base64EncodedJobType', jobContentEncoding: 'base64', jobContentExtension: 'pdf', validLicenses: ['basic', 'gold'], - } as ExportTypeDefinition); + } as ExportTypeDefinition); core.getExportTypesRegistry = () => exportTypesRegistry; }); diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index db62c0cc403fc..43e73c137fb13 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -128,7 +128,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { } return res.ok({ - body: jobOutput, + body: jobOutput || {}, headers: { 'content-type': 'application/json', }, diff --git a/x-pack/plugins/reporting/server/routes/legacy.ts b/x-pack/plugins/reporting/server/routes/legacy.ts index baa4e3648c85b..4c08619f8b888 100644 --- a/x-pack/plugins/reporting/server/routes/legacy.ts +++ b/x-pack/plugins/reporting/server/routes/legacy.ts @@ -43,15 +43,17 @@ export function registerLegacy( try { const { + title, savedObjectId, browserTimezone, - }: { savedObjectId: string; browserTimezone: string } = req.params as any; + }: { title: string; savedObjectId: string; browserTimezone: string } = req.params as any; const queryString = querystring.stringify(req.query as any); return await handler( user, exportTypeId, { + title, objectType, savedObjectId, browserTimezone, diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index 84a98d6d1f1d7..b154978d041f4 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -9,9 +9,9 @@ import contentDisposition from 'content-disposition'; import { get } from 'lodash'; import { CSV_JOB_TYPE } from '../../../common/constants'; import { ExportTypesRegistry, statuses } from '../../lib'; -import { ExportTypeDefinition, JobSource, TaskRunResult } from '../../types'; - -type ExportTypeType = ExportTypeDefinition; +import { ReportDocument } from '../../lib/store'; +import { TaskRunResult } from '../../lib/tasks'; +import { ExportTypeDefinition } from '../../types'; interface ErrorFromPayload { message: string; @@ -27,10 +27,10 @@ interface Payload { const DEFAULT_TITLE = 'report'; -const getTitle = (exportType: ExportTypeType, title?: string): string => +const getTitle = (exportType: ExportTypeDefinition, title?: string): string => `${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`; -const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeType) => { +const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeDefinition) => { const metaDataHeaders: Record = {}; if (exportType.jobType === CSV_JOB_TYPE) { @@ -45,7 +45,10 @@ const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeType) }; export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegistry) { - function encodeContent(content: string | null, exportType: ExportTypeType): Buffer | string { + function encodeContent( + content: string | null, + exportType: ExportTypeDefinition + ): Buffer | string { switch (exportType.jobContentEncoding) { case 'base64': return content ? Buffer.from(content, 'base64') : ''; // convert null to empty string @@ -55,7 +58,9 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist } function getCompleted(output: TaskRunResult, jobType: string, title: string): Payload { - const exportType = exportTypesRegistry.get((item: ExportTypeType) => item.jobType === jobType); + const exportType = exportTypesRegistry.get( + (item: ExportTypeDefinition) => item.jobType === jobType + ); const filename = getTitle(exportType, title); const headers = getReportingHeaders(output, exportType); @@ -92,16 +97,18 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist }; } - return function getDocumentPayload(doc: JobSource): Payload { + return function getDocumentPayload(doc: ReportDocument): Payload { const { status, jobtype: jobType, payload: { title } = { title: '' } } = doc._source; const { output } = doc._source; - if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { - return getCompleted(output, jobType, title); - } + if (output) { + if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { + return getCompleted(output, jobType, title); + } - if (status === statuses.JOB_STATUS_FAILED) { - return getFailure(output); + if (status === statuses.JOB_STATUS_FAILED) { + return getFailure(output); + } } // send a 503 indicating that the report isn't completed yet diff --git a/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts b/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts index bfa15a4022a4d..e685339c966ed 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts @@ -7,17 +7,13 @@ import { JobParamsPanelCsv } from '../../export_types/csv_from_savedobject/types'; import { CsvFromSavedObjectRequest } from '../generate_from_savedobject_immediate'; -export function getJobParamsFromRequest( - request: CsvFromSavedObjectRequest, - opts: { isImmediate: boolean } -): JobParamsPanelCsv { +export function getJobParamsFromRequest(request: CsvFromSavedObjectRequest): JobParamsPanelCsv { const { savedObjectType, savedObjectId } = request.params; const { timerange, state } = request.body; const post = timerange || state ? { timerange, state } : undefined; return { - isImmediate: opts.isImmediate, savedObjectType, savedObjectId, post, diff --git a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts index b01c880abe820..d1270215b4821 100644 --- a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts @@ -8,7 +8,8 @@ import { i18n } from '@kbn/i18n'; import { errors as elasticsearchErrors } from 'elasticsearch'; import { get } from 'lodash'; import { ReportingCore } from '../../'; -import { JobSource, ReportingUser } from '../../types'; +import { ReportDocument } from '../../lib/store'; +import { ReportingUser } from '../../types'; const esErrors = elasticsearchErrors as Record; const defaultSize = 10; @@ -130,7 +131,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore) { }); }, - get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise | void> { + get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise { if (!id) return Promise.resolve(); const username = getUsername(user); diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index 5c34d466197fe..b3f9225c3dce5 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -18,11 +18,11 @@ export type HandlerFunction = ( export type HandlerErrorFunction = (res: KibanaResponseFactory, err: Error) => any; -export interface QueuedJobPayload { +export interface QueuedJobPayload { error?: boolean; source: { job: { - payload: BasePayload; + payload: BasePayload; }; }; } diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index 8ba37015b1cc0..a325c4b40cd8d 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -13,81 +13,11 @@ import { CancellationToken } from '../../../plugins/reporting/common'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { AuthenticatedUser, SecurityPluginSetup } from '../../security/server'; -import { JobStatus } from '../common/types'; import { ReportingConfigType } from './config'; import { ReportingCore } from './core'; import { LevelLogger } from './lib'; -import { LayoutInstance } from './lib/layouts'; - -/* - * Routing types - */ - -export interface ReportingRequestPre { - management: { - jobTypes: string[]; - }; - user: string; -} - -// generate a report with unparsed jobParams -export interface GenerateExportTypePayload { - jobParams: string; -} - -export type ReportingRequestPayload = GenerateExportTypePayload | JobParamPostPayload; - -export interface TimeRangeParams { - timezone: string; - min?: Date | string | number | null; - max?: Date | string | number | null; -} - -// the "raw" data coming from the client, unencrypted -export interface JobParamPostPayload { - timerange?: TimeRangeParams; -} - -// the pre-processed, encrypted data ready for storage -export interface BasePayload { - headers: string; // serialized encrypted headers - jobParams: JobParamsType; - title: string; - type: string; - spaceId?: string; -} - -export interface JobSource { - _id: string; - _index: string; - _source: { - jobtype: string; - output: TaskRunResult; - payload: BasePayload; - status: JobStatus; - }; -} - -export interface TaskRunResult { - content_type: string | null; - content: string | null; - csv_contains_formulas?: boolean; - size: number; - max_size_reached?: boolean; - warnings?: string[]; -} - -interface ConditionalHeadersConditions { - protocol: string; - hostname: string; - port: number; - basePath: string; -} - -export interface ConditionalHeaders { - headers: Record; - conditions: ConditionalHeadersConditions; -} +import { LayoutParams } from './lib/layouts'; +import { ReportTaskParams, TaskRunResult } from './lib/tasks'; /* * Plugin Contract @@ -118,26 +48,31 @@ export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; export interface BaseParams { - browserTimezone: string; - layout?: LayoutInstance; // for screenshot type reports + browserTimezone?: string; // browserTimezone is optional: it is not in old POST URLs that were generated prior to being added to this interface + layout?: LayoutParams; objectType: string; + title: string; savedObjectId?: string; // legacy (7.x) only queryString?: string; // legacy (7.x) only } -export interface BaseParamsEncryptedFields extends BaseParams { - headers: string; // encrypted headers +// base params decorated with encrypted headers that come into runJob functions +export interface BasePayload extends BaseParams { + headers: string; + spaceId?: string; } -export type CreateJobFn = ( +// default fn type for CreateJobFnFactory +export type CreateJobFn = ( jobParams: JobParamsType, context: RequestHandlerContext, - request: KibanaRequest -) => Promise; + request: KibanaRequest +) => Promise; -export type RunTaskFn = ( +// default fn type for RunTaskFnFactory +export type RunTaskFn = ( jobId: string, - job: TaskPayloadType, + payload: ReportTaskParams['payload'], cancellationToken: CancellationToken ) => Promise; @@ -151,12 +86,7 @@ export type RunTaskFnFactory = ( logger: LevelLogger ) => RunTaskFnType; -export interface ExportTypeDefinition< - JobParamsType, - CreateJobFnType, - JobPayloadType, - RunTaskFnType -> { +export interface ExportTypeDefinition { id: string; name: string; jobType: string; @@ -166,9 +96,3 @@ export interface ExportTypeDefinition< runTaskFnFactory: RunTaskFnFactory; validLicenses: string[]; } - -export interface DiagnosticResponse { - help: string[]; - success: boolean; - logs: string; -}