mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Reporting] Clean Up TypeScript Definitions (#76566)
* [Reporting] Simplify Export Type Definitions, use defaults for generics, refactor * ReportApiJSON interface for common * rename JobSummary to JobStatusBucket for clarity * revert unneeded create mock changes * clean up the diff * revert changes to worker.js * rewrite comment * rename type to jobtype in JobStatusBucket * allow type inference * JobSummarySet * remove odd comment * Reflect that browser timezone may be undefined in the BaseParams * comment about optional browserTimezone * revert unecessary es archive change Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
42026cbbf5
commit
a537f9af50
87 changed files with 699 additions and 894 deletions
|
@ -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 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;
|
||||
}
|
||||
|
||||
export interface JobStatusBuckets {
|
||||
completed: JobSummary[];
|
||||
failed: JobSummary[];
|
||||
}
|
||||
|
||||
type DownloadLink = string;
|
||||
export type DownloadReportFn = (jobId: JobId) => DownloadLink;
|
||||
|
||||
type ManagementLink = string;
|
||||
export type ManagementLinkFn = () => ManagementLink;
|
||||
|
||||
export interface PollerOptions {
|
||||
functionToPoll: () => Promise<any>;
|
||||
pollFrequencyInMillis: number;
|
||||
|
|
|
@ -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<Props, State> {
|
|||
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<Props, State> {
|
|||
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 });
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = (
|
|||
<FormattedMessage
|
||||
id="xpack.reporting.publicNotifier.error.couldNotCreateReportTitle"
|
||||
defaultMessage="Could not create report for {reportObjectType} '{reportObjectTitle}'."
|
||||
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
|
||||
values={{ reportObjectType: job.jobtype, reportObjectTitle: job.title }}
|
||||
/>
|
||||
),
|
||||
text: toMountPoint(
|
||||
|
|
|
@ -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 = (
|
|||
<FormattedMessage
|
||||
id="xpack.reporting.publicNotifier.successfullyCreatedReportNotificationTitle"
|
||||
defaultMessage="Created report for {reportObjectType} '{reportObjectTitle}'"
|
||||
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
|
||||
values={{ reportObjectType: job.jobtype, reportObjectTitle: job.title }}
|
||||
/>
|
||||
),
|
||||
color: 'success',
|
||||
|
|
|
@ -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 = (
|
|||
<FormattedMessage
|
||||
id="xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportTitle"
|
||||
defaultMessage="Report may contain formulas {reportObjectType} '{reportObjectTitle}'"
|
||||
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
|
||||
values={{ reportObjectType: job.jobtype, reportObjectTitle: job.title }}
|
||||
/>
|
||||
),
|
||||
text: toMountPoint(
|
||||
|
|
|
@ -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 = (
|
|||
<FormattedMessage
|
||||
id="xpack.reporting.publicNotifier.maxSizeReached.partialReportTitle"
|
||||
defaultMessage="Created partial report for {reportObjectType} '{reportObjectTitle}'"
|
||||
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
|
||||
values={{ reportObjectType: job.jobtype, reportObjectTitle: job.title }}
|
||||
/>
|
||||
),
|
||||
text: toMountPoint(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<any>;
|
||||
isDirty: boolean;
|
||||
onClose: () => void;
|
||||
|
|
|
@ -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<Props, State> {
|
|||
);
|
||||
};
|
||||
|
||||
private handlePrintLayoutChange = (evt: any) => {
|
||||
private handlePrintLayoutChange = (evt: EuiSwitchEvent) => {
|
||||
this.setState({ usePrintLayout: evt.target.checked });
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -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<JobInfo> {
|
||||
public getInfo(jobId: string): Promise<ReportApiJSON> {
|
||||
return this.http.get(`${API_LIST_URL}/info/${jobId}`, {
|
||||
asSystemRequest: true,
|
||||
});
|
||||
}
|
||||
|
||||
public findForJobIds = (jobIds: JobId[]): Promise<SourceJob[]> => {
|
||||
public findForJobIds = (jobIds: JobId[]): Promise<ReportDocument[]> => {
|
||||
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}`);
|
||||
|
||||
/*
|
||||
|
|
|
@ -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<any> => {
|
||||
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,
|
||||
],
|
||||
|
|
|
@ -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<JobStatusBuckets> {
|
||||
}: JobSummarySet): Rx.Observable<JobSummarySet> {
|
||||
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<JobStatusBuckets> {
|
||||
public findChangedStatusJobs(storedJobs: JobId[]): Rx.Observable<JobSummarySet> {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<JobStatusBuckets> {
|
||||
function handleError(notifications: NotificationsSetup, err: Error): Rx.Observable<JobSummarySet> {
|
||||
notifications.toasts.addDanger(
|
||||
getGeneralErrorToast(
|
||||
i18n.translate('xpack.reporting.publicNotifier.pollingErrorMessage', {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<never> }> {
|
||||
return Rx.Observable.create(async (observer: InnerSubscriber<any, any>) => {
|
||||
|
|
|
@ -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<string, string>) => {
|
||||
const crypto = cryptoFactory(encryptionKey);
|
||||
return await crypto.encrypt(headers);
|
||||
|
@ -15,15 +18,11 @@ const encryptHeaders = async (encryptionKey: string, headers: Record<string, str
|
|||
describe('headers', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<Record<string, string>> => {
|
||||
export const decryptJobHeaders = async (
|
||||
encryptionKey: string | undefined,
|
||||
headers: string,
|
||||
logger: LevelLogger
|
||||
): Promise<Record<string, string>> => {
|
||||
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<string, string>;
|
||||
const decryptedHeaders = (await crypto.decrypt(headers)) as Record<string, string>;
|
||||
return decryptedHeaders;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
|
|
@ -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<any>,
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,17 +5,12 @@
|
|||
*/
|
||||
|
||||
import { ReportingConfig } from '../../';
|
||||
import { ConditionalHeaders } from '../../types';
|
||||
import { ConditionalHeaders } from './';
|
||||
|
||||
export const getConditionalHeaders = <TaskPayloadType>({
|
||||
config,
|
||||
job,
|
||||
filteredHeaders,
|
||||
}: {
|
||||
config: ReportingConfig;
|
||||
job: TaskPayloadType;
|
||||
filteredHeaders: Record<string, string>;
|
||||
}) => {
|
||||
export const getConditionalHeaders = (
|
||||
config: ReportingConfig,
|
||||
filteredHeaders: Record<string, string>
|
||||
) => {
|
||||
const { kbnConfig } = config;
|
||||
const [hostname, port, basePath, protocol] = [
|
||||
config.get('kibanaServer', 'hostname'),
|
||||
|
|
|
@ -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: string\` or \`job.relativeUrls: string[]\`"`
|
||||
);
|
||||
|
@ -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"`
|
||||
);
|
||||
|
@ -67,13 +54,13 @@ 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({
|
||||
getFullUrls(
|
||||
mockConfig,
|
||||
getMockJob({
|
||||
relativeUrls: [relativeUrl],
|
||||
forceNow,
|
||||
}),
|
||||
config: mockConfig,
|
||||
} as FullUrlsOpts);
|
||||
})
|
||||
);
|
||||
expect(fn).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"`
|
||||
);
|
||||
|
@ -84,13 +71,13 @@ 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({
|
||||
relativeUrls: [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"`
|
||||
);
|
||||
|
@ -104,22 +91,14 @@ test(`fails if any URLs are absolute or file's for PDF`, async () => {
|
|||
'file://etc/passwd/#/something',
|
||||
];
|
||||
|
||||
const fn = () =>
|
||||
getFullUrls({
|
||||
job: getMockJob({ relativeUrls, forceNow }),
|
||||
config: mockConfig,
|
||||
} as FullUrlsOpts);
|
||||
const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrls, 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."`
|
||||
);
|
||||
|
@ -127,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'
|
||||
|
@ -140,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'
|
||||
|
@ -151,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: getMockJob({
|
||||
const urls = await getFullUrls(
|
||||
mockConfig,
|
||||
getMockJob({
|
||||
relativeUrls: [
|
||||
'/app/kibana#/something_aaa',
|
||||
'/app/kibana#/something_bbb',
|
||||
|
@ -170,9 +147,8 @@ test(`adds forceNow to each of multiple urls`, async () => {
|
|||
'/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',
|
||||
|
|
|
@ -23,13 +23,7 @@ function isPdfJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPDF {
|
|||
return (job as TaskPayloadPDF).relativeUrls !== undefined;
|
||||
}
|
||||
|
||||
export function getFullUrls<TaskPayloadType>({
|
||||
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'),
|
||||
|
|
|
@ -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<string, string>;
|
||||
conditions: ConditionalHeadersConditions;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -9,13 +9,7 @@ import {
|
|||
KBN_SCREENSHOT_HEADER_BLOCK_LIST_STARTS_WITH_PATTERN,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const omitBlockedHeaders = <TaskPayloadType>({
|
||||
job,
|
||||
decryptedHeaders,
|
||||
}: {
|
||||
job: TaskPayloadType;
|
||||
decryptedHeaders: Record<string, string>;
|
||||
}) => {
|
||||
export const omitBlockedHeaders = (decryptedHeaders: Record<string, string>) => {
|
||||
const filteredHeaders: Record<string, string> = omitBy(
|
||||
decryptedHeaders,
|
||||
(_value, header: string) =>
|
||||
|
|
|
@ -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<CreateJobFn<
|
||||
JobParamsDiscoverCsv
|
||||
JobParamsCSV,
|
||||
TaskPayloadCSV
|
||||
>> = function createJobFactoryFn(reporting) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
@ -18,10 +19,10 @@ export const createJobFnFactory: CreateJobFnFactory<CreateJobFn<
|
|||
const serializedEncryptedHeaders = await crypto.encrypt(request.headers);
|
||||
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const indexPatternSavedObject = await savedObjectsClient.get(
|
||||
const indexPatternSavedObject = ((await savedObjectsClient.get(
|
||||
'index-pattern',
|
||||
jobParams.indexPatternId
|
||||
);
|
||||
)) as unknown) as IndexPatternSavedObject; // FIXME
|
||||
|
||||
return {
|
||||
headers: serializedEncryptedHeaders,
|
||||
|
|
|
@ -22,7 +22,7 @@ export const runTaskFnFactory: RunTaskFnFactory<RunTaskFn<
|
|||
const generateCsv = createGenerateCsv(jobLogger);
|
||||
|
||||
const encryptionKey = config.get('encryptionKey');
|
||||
const headers = await decryptJobHeaders({ encryptionKey, job, logger });
|
||||
const headers = await decryptJobHeaders(encryptionKey, job.headers, logger);
|
||||
const fakeRequest = reporting.getFakeRequest({ headers }, job.spaceId);
|
||||
const uiSettingsClient = await reporting.getUiSettingsClient(fakeRequest);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ interface SearchRequest {
|
|||
}
|
||||
|
||||
export interface GenerateCsvParams {
|
||||
browserTimezone: string;
|
||||
browserTimezone?: string;
|
||||
searchRequest: SearchRequest;
|
||||
indexPatternSavedObject: IndexPatternSavedObject;
|
||||
fields: string[];
|
||||
|
|
|
@ -17,12 +17,10 @@ import { CreateJobFn, ExportTypeDefinition, RunTaskFn } from '../../types';
|
|||
import { createJobFnFactory } from './create_job';
|
||||
import { runTaskFnFactory } from './execute_job';
|
||||
import { metadata } from './metadata';
|
||||
import { JobParamsDiscoverCsv, TaskPayloadCSV } from './types';
|
||||
import { JobParamsCSV, TaskPayloadCSV } from './types';
|
||||
|
||||
export const getExportType = (): ExportTypeDefinition<
|
||||
JobParamsDiscoverCsv,
|
||||
CreateJobFn<JobParamsDiscoverCsv>,
|
||||
TaskPayloadCSV,
|
||||
CreateJobFn<JobParamsCSV>,
|
||||
RunTaskFn<TaskPayloadCSV>
|
||||
> => ({
|
||||
...metadata,
|
||||
|
|
|
@ -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<JobParamsDiscoverCsv> {
|
||||
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;
|
||||
|
|
|
@ -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<JobPayloadPanelCsv>;
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<ImmediateCreateJobFn> = 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<ImmediateCreateJobFn> = 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<ImmediateCreateJobFn> = 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<ImmediateCreateJobFn> = 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 };
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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<BasePayload<JobParamsPanelCsv>, 'headers'>;
|
||||
import { JobPayloadPanelCsv } from './types';
|
||||
|
||||
/*
|
||||
* ImmediateExecuteFn receives the job doc payload because the payload was
|
||||
|
@ -24,7 +19,7 @@ type ImmediateJobParams = Omit<BasePayload<JobParamsPanelCsv>, 'headers'>;
|
|||
*/
|
||||
export type ImmediateExecuteFn = (
|
||||
jobId: null,
|
||||
job: ImmediateJobParams,
|
||||
job: JobPayloadPanelCsv,
|
||||
context: RequestHandlerContext,
|
||||
req: KibanaRequest
|
||||
) => Promise<TaskRunResult>;
|
||||
|
@ -36,20 +31,16 @@ export const runTaskFnFactory: RunTaskFnFactory<ImmediateExecuteFn> = 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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<GenerateCsvParams> => {
|
||||
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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<string, unknown>;
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<CreateJobFn<
|
||||
JobParamsPNG
|
||||
JobParamsPNG,
|
||||
TaskPayloadPNG
|
||||
>> = function createJobFactoryFn(reporting) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
|
|
@ -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<RunTaskFn<TaskPayloadPNG>>;
|
||||
|
||||
export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFactoryFn(
|
||||
reporting,
|
||||
parentLogger
|
||||
) {
|
||||
export const runTaskFnFactory: RunTaskFnFactory<RunTaskFn<
|
||||
TaskPayloadPNG
|
||||
>> = 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<TaskRunResult> = 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,
|
||||
};
|
||||
}),
|
||||
|
|
|
@ -19,9 +19,7 @@ import { metadata } from './metadata';
|
|||
import { JobParamsPNG, TaskPayloadPNG } from './types';
|
||||
|
||||
export const getExportType = (): ExportTypeDefinition<
|
||||
JobParamsPNG,
|
||||
CreateJobFn<JobParamsPNG>,
|
||||
TaskPayloadPNG,
|
||||
RunTaskFn<TaskPayloadPNG>
|
||||
> => ({
|
||||
...metadata,
|
||||
|
|
|
@ -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[] }> {
|
||||
|
|
|
@ -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';
|
||||
|
||||
interface BaseParamsPNG {
|
||||
layout: LayoutParams;
|
||||
forceNow?: string;
|
||||
relativeUrl: string;
|
||||
}
|
||||
|
||||
// Job params: structure of incoming user request data
|
||||
export interface JobParamsPNG extends BaseParams {
|
||||
title: string;
|
||||
relativeUrl: string;
|
||||
}
|
||||
export type JobParamsPNG = BaseParamsPNG & BaseParams;
|
||||
|
||||
// Job payload: structure of stored job data provided by create_job
|
||||
export interface TaskPayloadPNG extends BasePayload<JobParamsPNG> {
|
||||
browserTimezone: string;
|
||||
forceNow?: string;
|
||||
layout: LayoutParams;
|
||||
relativeUrl: string;
|
||||
}
|
||||
export type TaskPayloadPNG = BaseParamsPNG & BasePayload;
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
import { cryptoFactory } from '../../../lib';
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../../types';
|
||||
import { validateUrls } from '../../common';
|
||||
import { JobParamsPDF } from '../types';
|
||||
import { JobParamsPDF, TaskPayloadPDF } from '../types';
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<CreateJobFn<
|
||||
JobParamsPDF
|
||||
JobParamsPDF,
|
||||
TaskPayloadPDF
|
||||
>> = function createJobFactoryFn(reporting) {
|
||||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
|
|
@ -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<RunTaskFn<TaskPayloadPDF>>;
|
||||
|
||||
export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn(
|
||||
reporting,
|
||||
parentLogger
|
||||
) {
|
||||
export const runTaskFnFactory: RunTaskFnFactory<RunTaskFn<
|
||||
TaskPayloadPDF
|
||||
>> = 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<TaskRunResult> = 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();
|
||||
|
|
|
@ -19,9 +19,7 @@ import { metadata } from './metadata';
|
|||
import { JobParamsPDF, TaskPayloadPDF } from './types';
|
||||
|
||||
export const getExportType = (): ExportTypeDefinition<
|
||||
JobParamsPDF,
|
||||
CreateJobFn<JobParamsPDF>,
|
||||
TaskPayloadPDF,
|
||||
RunTaskFn<TaskPayloadPDF>
|
||||
> => ({
|
||||
...metadata,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,20 +4,17 @@
|
|||
* 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';
|
||||
|
||||
interface BaseParamsPDF {
|
||||
layout: LayoutParams;
|
||||
forceNow?: string;
|
||||
relativeUrls: string[];
|
||||
}
|
||||
|
||||
// Job params: structure of incoming user request data, after being parsed from RISON
|
||||
export interface JobParamsPDF extends BaseParams {
|
||||
title: string;
|
||||
relativeUrls: string[];
|
||||
layout: LayoutInstance;
|
||||
}
|
||||
export type JobParamsPDF = BaseParamsPDF & BaseParams;
|
||||
|
||||
// Job payload: structure of stored job data provided by create_job
|
||||
export interface TaskPayloadPDF extends BasePayload<JobParamsPDF> {
|
||||
browserTimezone: string;
|
||||
forceNow?: string;
|
||||
layout: LayoutParams;
|
||||
relativeUrls: string[];
|
||||
}
|
||||
export type TaskPayloadPDF = BaseParamsPDF & BasePayload;
|
||||
|
|
|
@ -24,9 +24,7 @@ const messages = {
|
|||
},
|
||||
};
|
||||
|
||||
const makeManagementFeature = (
|
||||
exportTypes: Array<ExportTypeDefinition<unknown, unknown, unknown, unknown>>
|
||||
) => {
|
||||
const makeManagementFeature = (exportTypes: ExportTypeDefinition[]) => {
|
||||
return {
|
||||
id: 'management',
|
||||
checkLicense: (license?: ILicense) => {
|
||||
|
@ -59,9 +57,7 @@ const makeManagementFeature = (
|
|||
};
|
||||
};
|
||||
|
||||
const makeExportTypeFeature = (
|
||||
exportType: ExportTypeDefinition<unknown, unknown, unknown, unknown>
|
||||
) => {
|
||||
const makeExportTypeFeature = (exportType: ExportTypeDefinition) => {
|
||||
return {
|
||||
id: exportType.id,
|
||||
checkLicense: (license?: ILicense) => {
|
||||
|
|
|
@ -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<JobParamsType> | ESQueueWorkerExecuteFn<ScheduledTaskParamsType>,
|
||||
type GenericWorkerFn<JobParamsType> = (
|
||||
jobSource: JobSource<JobParamsType>,
|
||||
jobSource: ReportDocument,
|
||||
...workerRestArgs: any[]
|
||||
) => void | Promise<TaskRunResult>;
|
||||
|
||||
|
|
|
@ -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<JobParamsType>(reporting: ReportingCore, logger: LevelLogger) {
|
||||
const config = reporting.getConfig();
|
||||
|
@ -23,18 +25,16 @@ export function createWorkerFactory<JobParamsType>(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<string, RunTaskFn<unknown>> = new Map();
|
||||
const jobExecutors: Map<string, RunTaskFn> = new Map();
|
||||
|
||||
for (const exportType of reporting.getExportTypesRegistry().getAll() as Array<
|
||||
ExportTypeDefinition<JobParamsType, unknown, unknown, RunTaskFn<unknown>>
|
||||
>) {
|
||||
for (const exportType of reporting.getExportTypesRegistry().getAll()) {
|
||||
const jobExecutor = exportType.runTaskFnFactory(reporting, logger);
|
||||
jobExecutors.set(exportType.jobType, jobExecutor);
|
||||
}
|
||||
|
||||
const workerFn = <TaskPayloadType>(
|
||||
jobSource: JobSource<TaskPayloadType>,
|
||||
jobParams: TaskPayloadType,
|
||||
const workerFn = (
|
||||
jobSource: ReportDocument,
|
||||
payload: ReportTaskParams['payload'],
|
||||
cancellationToken: CancellationToken
|
||||
) => {
|
||||
const {
|
||||
|
@ -52,7 +52,7 @@ export function createWorkerFactory<JobParamsType>(reporting: ReportingCore, log
|
|||
}
|
||||
|
||||
// pass the work to the jobExecutor
|
||||
return jobTypeExecutor(jobId, jobParams, cancellationToken);
|
||||
return jobTypeExecutor(jobId, payload, cancellationToken);
|
||||
};
|
||||
|
||||
const workerOptions = {
|
||||
|
|
|
@ -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<BaseParams>;
|
||||
|
||||
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}`);
|
||||
|
||||
|
|
|
@ -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<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType> = (
|
||||
item: ExportTypeDefinition<JobParamsType, CreateJobFnType, JobPayloadType, CreateJobFnType>
|
||||
) => boolean;
|
||||
// => ExportTypeDefinition<T, U, V, W>
|
||||
type GetCallbackFn = (item: ExportTypeDefinition) => boolean;
|
||||
|
||||
export class ExportTypesRegistry {
|
||||
private _map: Map<string, ExportTypeDefinition<any, any, any, any>> = new Map();
|
||||
private _map: Map<string, ExportTypeDefinition> = new Map();
|
||||
|
||||
constructor() {}
|
||||
|
||||
register<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType>(
|
||||
item: ExportTypeDefinition<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType>
|
||||
): 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<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType>(
|
||||
id: string
|
||||
): ExportTypeDefinition<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType> {
|
||||
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<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType>(
|
||||
findType: GetCallbackFn<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType>
|
||||
): ExportTypeDefinition<JobParamsType, CreateJobFnType, JobPayloadType, RunTaskFnType> {
|
||||
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<any, any, any, any>> = [
|
||||
type CreateFnType = CreateJobFn<any, any>; // 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<CreateFnType, RunFnType>> = [
|
||||
getTypeCsv,
|
||||
getTypeCsvFromSavedObject,
|
||||
getTypePng,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<void>;
|
||||
}
|
||||
|
||||
export type LayoutInstance = Layout & LayoutSelectors & Size;
|
||||
export type LayoutInstance = Layout & LayoutSelectors & Partial<Size>;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]',
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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!"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<ReportingDocument> {
|
||||
export class Report implements Partial<ReportSource> {
|
||||
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 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 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 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<ReportingDocument>) {
|
||||
constructor(opts: Partial<ReportSource> & Partial<ReportDocumentHead>) {
|
||||
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<ReportingDocument> {
|
|||
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<ReportingDocument> {
|
|||
/*
|
||||
* 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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.objectType,
|
||||
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<Report> {
|
||||
const timestamp = indexTimestamp(this.indexInterval);
|
||||
const index = `${this.indexPrefix}-${timestamp}`;
|
||||
public async addReport(report: Report): Promise<Report> {
|
||||
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<Report>): Promise<Report> {
|
||||
try {
|
||||
const { output } = stats as { output: any };
|
||||
const { output } = stats;
|
||||
const status =
|
||||
output && output.warnings && output.warnings.length > 0
|
||||
? statuses.JOB_STATUS_WARNINGS
|
||||
|
|
32
x-pack/plugins/reporting/server/lib/tasks/index.ts
Normal file
32
x-pack/plugins/reporting/server/lib/tasks/index.ts
Normal file
|
@ -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<JobPayloadType = BasePayload> {
|
||||
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[];
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,3 +15,10 @@ export function registerRoutes(reporting: ReportingCore, logger: Logger) {
|
|||
registerJobInfoRoutes(reporting);
|
||||
registerDiagnosticRoutes(reporting, logger);
|
||||
}
|
||||
|
||||
export interface ReportingRequestPre {
|
||||
management: {
|
||||
jobTypes: string[];
|
||||
};
|
||||
user: string;
|
||||
}
|
||||
|
|
|
@ -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<unknown, unknown, unknown, unknown>);
|
||||
} as ExportTypeDefinition);
|
||||
exportTypesRegistry.register({
|
||||
id: 'base64Encoded',
|
||||
jobType: 'base64EncodedJobType',
|
||||
jobContentEncoding: 'base64',
|
||||
jobContentExtension: 'pdf',
|
||||
validLicenses: ['basic', 'gold'],
|
||||
} as ExportTypeDefinition<unknown, unknown, unknown, unknown>);
|
||||
} as ExportTypeDefinition);
|
||||
core.getExportTypesRegistry = () => exportTypesRegistry;
|
||||
});
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) {
|
|||
}
|
||||
|
||||
return res.ok({
|
||||
body: jobOutput,
|
||||
body: jobOutput || {},
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
|
|
|
@ -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<unknown, unknown, unknown, unknown>;
|
||||
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<string, boolean> = {};
|
||||
|
||||
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<unknown>): 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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<string, any>;
|
||||
const defaultSize = 10;
|
||||
|
@ -130,7 +131,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore) {
|
|||
});
|
||||
},
|
||||
|
||||
get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise<JobSource<unknown> | void> {
|
||||
get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise<ReportDocument | void> {
|
||||
if (!id) return Promise.resolve();
|
||||
const username = getUsername(user);
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ export type HandlerFunction = (
|
|||
|
||||
export type HandlerErrorFunction = (res: KibanaResponseFactory, err: Error) => any;
|
||||
|
||||
export interface QueuedJobPayload<JobParamsType> {
|
||||
export interface QueuedJobPayload {
|
||||
error?: boolean;
|
||||
source: {
|
||||
job: {
|
||||
payload: BasePayload<JobParamsType>;
|
||||
payload: BasePayload;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<JobParamsType> {
|
||||
headers: string; // serialized encrypted headers
|
||||
jobParams: JobParamsType;
|
||||
title: string;
|
||||
type: string;
|
||||
spaceId?: string;
|
||||
}
|
||||
|
||||
export interface JobSource<JobParamsType> {
|
||||
_id: string;
|
||||
_index: string;
|
||||
_source: {
|
||||
jobtype: string;
|
||||
output: TaskRunResult;
|
||||
payload: BasePayload<JobParamsType>;
|
||||
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<string, string>;
|
||||
conditions: ConditionalHeadersConditions;
|
||||
}
|
||||
import { LayoutParams } from './lib/layouts';
|
||||
import { ReportTaskParams, TaskRunResult } from './lib/tasks';
|
||||
|
||||
/*
|
||||
* Plugin Contract
|
||||
|
@ -118,24 +48,29 @@ 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;
|
||||
}
|
||||
|
||||
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<JobParamsType extends BaseParams> = (
|
||||
// default fn type for CreateJobFnFactory
|
||||
export type CreateJobFn<JobParamsType = BaseParams, JobPayloadType = BasePayload> = (
|
||||
jobParams: JobParamsType,
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
) => Promise<JobParamsType & BaseParamsEncryptedFields>;
|
||||
request: KibanaRequest<any, any, any, any>
|
||||
) => Promise<JobPayloadType>;
|
||||
|
||||
export type RunTaskFn<TaskPayloadType> = (
|
||||
// default fn type for RunTaskFnFactory
|
||||
export type RunTaskFn<TaskPayloadType = BasePayload> = (
|
||||
jobId: string,
|
||||
job: TaskPayloadType,
|
||||
payload: ReportTaskParams<TaskPayloadType>['payload'],
|
||||
cancellationToken: CancellationToken
|
||||
) => Promise<TaskRunResult>;
|
||||
|
||||
|
@ -149,12 +84,7 @@ export type RunTaskFnFactory<RunTaskFnType> = (
|
|||
logger: LevelLogger
|
||||
) => RunTaskFnType;
|
||||
|
||||
export interface ExportTypeDefinition<
|
||||
JobParamsType,
|
||||
CreateJobFnType,
|
||||
JobPayloadType,
|
||||
RunTaskFnType
|
||||
> {
|
||||
export interface ExportTypeDefinition<CreateJobFnType = CreateJobFn, RunTaskFnType = RunTaskFn> {
|
||||
id: string;
|
||||
name: string;
|
||||
jobType: string;
|
||||
|
@ -164,9 +94,3 @@ export interface ExportTypeDefinition<
|
|||
runTaskFnFactory: RunTaskFnFactory<RunTaskFnType>;
|
||||
validLicenses: string[];
|
||||
}
|
||||
|
||||
export interface DiagnosticResponse {
|
||||
help: string[];
|
||||
success: boolean;
|
||||
logs: string;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue