mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Reporting] add version to all export types job params (#106137)
* add version to csv params * fix ts * fix api tests * use kibana version from packageInfo * use kibana version from packageInfo * clean up ide warnings * utility to log and set a default params version * fix baseparams ts * update snapshot * check version in enqueue job * add temporary ts-ignore for canvas * clarify comment * fix hardcoded version in png_pdf_panel * clarify the UNVERSIONED_VERSION variable with a comment * fix canvas jest test * fix ts in example app * fix types * send version param to canvas util for job params * update jest snapshot * Update utils.test.ts * fix snapshot * remove browserTimezone and version from integration boilerplate * wip ensure version is always populated in job params inside of the service * wip2 * wip3 * wip4 * wip5 * wip6 * update note * update example plugin * wip7 * improve tests * fix dynamic job params * better testing * improve enqueue_job test * more tests * fix types * fix types * fix example ts * simplify props * fix test * --wip-- [skip ci] * consolidate baseparams back into one interface * fix rison encoding of apiClient param * clean up * reorganize imports * back out functional change * fix 400 error in download csv Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
81fd64c838
commit
5e8b24230a
40 changed files with 493 additions and 353 deletions
|
@ -8,13 +8,13 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
|
||||
import { SetupDeps, StartDeps } from './types';
|
||||
import { ReportingExampleApp } from './components/app';
|
||||
import { SetupDeps, StartDeps } from './types';
|
||||
|
||||
export const renderApp = (
|
||||
coreStart: CoreStart,
|
||||
deps: Omit<StartDeps & SetupDeps, 'developerExamples'>,
|
||||
{ appBasePath, element }: AppMountParameters
|
||||
{ appBasePath, element }: AppMountParameters // FIXME: appBasePath is deprecated
|
||||
) => {
|
||||
ReactDOM.render(<ReportingExampleApp basename={appBasePath} {...coreStart} {...deps} />, element);
|
||||
|
||||
|
|
|
@ -28,16 +28,10 @@ import { BrowserRouter as Router } from 'react-router-dom';
|
|||
import * as Rx from 'rxjs';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/public';
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
|
||||
import { constants, ReportingStart } from '../../../../../x-pack/plugins/reporting/public';
|
||||
import { JobParamsPDF } from '../../../../plugins/reporting/server/export_types/printable_pdf/types';
|
||||
|
||||
interface ReportingExampleAppDeps {
|
||||
interface ReportingExampleAppProps {
|
||||
basename: string;
|
||||
notifications: CoreStart['notifications'];
|
||||
http: CoreStart['http'];
|
||||
navigation: NavigationPublicPluginStart;
|
||||
reporting: ReportingStart;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
}
|
||||
|
@ -46,11 +40,9 @@ const sourceLogos = ['Beats', 'Cloud', 'Logging', 'Kibana'];
|
|||
|
||||
export const ReportingExampleApp = ({
|
||||
basename,
|
||||
notifications,
|
||||
http,
|
||||
reporting,
|
||||
screenshotMode,
|
||||
}: ReportingExampleAppDeps) => {
|
||||
}: ReportingExampleAppProps) => {
|
||||
const { getDefaultLayoutSelectors } = reporting;
|
||||
|
||||
// Context Menu
|
||||
|
@ -74,7 +66,7 @@ export const ReportingExampleApp = ({
|
|||
});
|
||||
});
|
||||
|
||||
const getPDFJobParamsDefault = (): JobParamsPDF => {
|
||||
const getPDFJobParamsDefault = () => {
|
||||
return {
|
||||
layout: {
|
||||
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
|
||||
|
|
|
@ -22,7 +22,6 @@ test('getPdfJobParams returns the correct job params for canvas layout', () => {
|
|||
const jobParams = getPdfJobParams(workpadSharingData, basePath);
|
||||
expect(jobParams).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"browserTimezone": "America/New_York",
|
||||
"layout": Object {
|
||||
"dimensions": Object {
|
||||
"height": 0,
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
*/
|
||||
|
||||
import { IBasePath } from 'kibana/public';
|
||||
import moment from 'moment-timezone';
|
||||
import rison from 'rison-node';
|
||||
import { BaseParams } from '../../../../../reporting/common/types';
|
||||
import { CanvasWorkpad } from '../../../../types';
|
||||
|
||||
export interface CanvasWorkpadSharingData {
|
||||
|
@ -16,13 +14,10 @@ export interface CanvasWorkpadSharingData {
|
|||
pageCount: number;
|
||||
}
|
||||
|
||||
// TODO: get the correct type from Reporting plugin
|
||||
type JobParamsPDF = BaseParams & { relativeUrls: string[] };
|
||||
|
||||
export function getPdfJobParams(
|
||||
{ workpad: { id, name: title, width, height }, pageCount }: CanvasWorkpadSharingData,
|
||||
basePath: IBasePath
|
||||
): JobParamsPDF {
|
||||
) {
|
||||
const urlPrefix = basePath.get().replace(basePath.serverBasePath, ''); // for Spaces prefix, which is included in basePath.get()
|
||||
const canvasEntry = `${urlPrefix}/app/canvas#`;
|
||||
|
||||
|
@ -43,7 +38,6 @@ export function getPdfJobParams(
|
|||
}
|
||||
|
||||
return {
|
||||
browserTimezone: moment.tz.guess(),
|
||||
layout: {
|
||||
dimensions: { width, height },
|
||||
id: 'canvas',
|
||||
|
|
|
@ -111,6 +111,11 @@ export enum JOB_STATUSES {
|
|||
export const REPORT_TABLE_ID = 'reportJobListing';
|
||||
export const REPORT_TABLE_ROW_ID = 'reportJobRow';
|
||||
|
||||
// Job params require a `version` field as of 7.15.0. For older jobs set with
|
||||
// automation that have no version value in the job params, we assume the
|
||||
// intended version is 7.14.0
|
||||
export const UNVERSIONED_VERSION = '7.14.0';
|
||||
|
||||
// hacky endpoint: download CSV without queueing a report
|
||||
// FIXME: find a way to make these endpoints "generic" instead of hardcoded, as are the queued report export types
|
||||
export const API_GENERATE_IMMEDIATE = `${API_BASE_URL_V1}/generate/immediate/csv_searchsource`;
|
||||
|
|
|
@ -97,10 +97,11 @@ export interface ReportDocument extends ReportDocumentHead {
|
|||
}
|
||||
|
||||
export interface BaseParams {
|
||||
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;
|
||||
browserTimezone: string; // to format dates in the user's time zone
|
||||
version: string; // to handle any state migrations
|
||||
}
|
||||
|
||||
export type JobId = string;
|
||||
|
|
|
@ -6,34 +6,45 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { stringify } from 'query-string';
|
||||
import rison from 'rison-node';
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
import rison, { RisonObject } from 'rison-node';
|
||||
import { HttpSetup, IUiSettingsClient } from 'src/core/public';
|
||||
import {
|
||||
API_BASE_GENERATE,
|
||||
API_BASE_URL,
|
||||
API_GENERATE_IMMEDIATE,
|
||||
API_LIST_URL,
|
||||
API_MIGRATE_ILM_POLICY_URL,
|
||||
REPORTING_MANAGEMENT_HOME,
|
||||
} from '../../../common/constants';
|
||||
import { DownloadReportFn, JobId, ManagementLinkFn, ReportApiJSON } from '../../../common/types';
|
||||
import {
|
||||
BaseParams,
|
||||
DownloadReportFn,
|
||||
JobId,
|
||||
ManagementLinkFn,
|
||||
ReportApiJSON,
|
||||
} from '../../../common/types';
|
||||
import { add } from '../../notifier/job_completion_notifications';
|
||||
import { Job } from '../job';
|
||||
|
||||
/*
|
||||
* For convenience, apps do not have to provide the browserTimezone and Kibana version.
|
||||
* Those fields are added in this client as part of the service.
|
||||
* TODO: export a type like this to other plugins: https://github.com/elastic/kibana/issues/107085
|
||||
*/
|
||||
type AppParams = Omit<BaseParams, 'browserTimezone' | 'version'>;
|
||||
|
||||
export interface DiagnoseResponse {
|
||||
help: string[];
|
||||
success: boolean;
|
||||
logs: string;
|
||||
}
|
||||
|
||||
interface JobParams {
|
||||
[paramName: string]: any;
|
||||
}
|
||||
|
||||
interface IReportingAPI {
|
||||
// Helpers
|
||||
getReportURL(jobId: string): string;
|
||||
getReportingJobPath(exportType: string, jobParams: JobParams): string; // Return a URL to queue a job, with the job params encoded in the query string of the URL. Used for copying POST URL
|
||||
getReportingJobPath<T>(exportType: string, jobParams: BaseParams & T): string; // Return a URL to queue a job, with the job params encoded in the query string of the URL. Used for copying POST URL
|
||||
createReportingJob(exportType: string, jobParams: any): Promise<Job>; // Sends a request to queue a job, with the job params in the POST body
|
||||
getServerBasePath(): string; // Provides the raw server basePath to allow it to be stripped out from relativeUrls in job params
|
||||
|
||||
|
@ -57,11 +68,11 @@ interface IReportingAPI {
|
|||
}
|
||||
|
||||
export class ReportingAPIClient implements IReportingAPI {
|
||||
private http: HttpSetup;
|
||||
|
||||
constructor(http: HttpSetup) {
|
||||
this.http = http;
|
||||
}
|
||||
constructor(
|
||||
private http: HttpSetup,
|
||||
private uiSettings: IUiSettingsClient,
|
||||
private kibanaVersion: string
|
||||
) {}
|
||||
|
||||
public getReportURL(jobId: string) {
|
||||
const apiBaseUrl = this.http.basePath.prepend(API_LIST_URL);
|
||||
|
@ -132,13 +143,15 @@ export class ReportingAPIClient implements IReportingAPI {
|
|||
return reports.map((report) => new Job(report));
|
||||
}
|
||||
|
||||
public getReportingJobPath(exportType: string, jobParams: JobParams) {
|
||||
const params = stringify({ jobParams: rison.encode(jobParams) });
|
||||
public getReportingJobPath(exportType: string, jobParams: BaseParams) {
|
||||
const risonObject: RisonObject = jobParams as Record<string, any>;
|
||||
const params = stringify({ jobParams: rison.encode(risonObject) });
|
||||
return `${this.http.basePath.prepend(API_BASE_GENERATE)}/${exportType}?${params}`;
|
||||
}
|
||||
|
||||
public async createReportingJob(exportType: string, jobParams: any) {
|
||||
const jobParamsRison = rison.encode(jobParams);
|
||||
public async createReportingJob(exportType: string, jobParams: BaseParams) {
|
||||
const risonObject: RisonObject = jobParams as Record<string, any>;
|
||||
const jobParamsRison = rison.encode(risonObject);
|
||||
const resp: { job: ReportApiJSON } = await this.http.post(
|
||||
`${API_BASE_GENERATE}/${exportType}`,
|
||||
{
|
||||
|
@ -154,6 +167,27 @@ export class ReportingAPIClient implements IReportingAPI {
|
|||
return new Job(resp.job);
|
||||
}
|
||||
|
||||
public async createImmediateReport(baseParams: BaseParams) {
|
||||
const { objectType: _objectType, ...params } = baseParams; // objectType is not needed for immediate download api
|
||||
return this.http.post(`${API_GENERATE_IMMEDIATE}`, { body: JSON.stringify(params) });
|
||||
}
|
||||
|
||||
public getDecoratedJobParams<T extends AppParams>(baseParams: T): BaseParams {
|
||||
// If the TZ is set to the default "Browser", it will not be useful for
|
||||
// server-side export. We need to derive the timezone and pass it as a param
|
||||
// to the export API.
|
||||
const browserTimezone: string =
|
||||
this.uiSettings.get('dateFormat:tz') === 'Browser'
|
||||
? moment.tz.guess()
|
||||
: this.uiSettings.get('dateFormat:tz');
|
||||
|
||||
return {
|
||||
browserTimezone,
|
||||
version: this.kibanaVersion,
|
||||
...baseParams,
|
||||
};
|
||||
}
|
||||
|
||||
public getManagementLink: ManagementLinkFn = () =>
|
||||
this.http.basePath.prepend(REPORTING_MANAGEMENT_HOME);
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ const mockJobsFound: Job[] = [
|
|||
{ id: 'job-source-mock3', status: 'pending', output: { csv_contains_formulas: false, max_size_reached: false }, payload: { title: 'specimen' } },
|
||||
].map((j) => new Job(j as ReportApiJSON)); // prettier-ignore
|
||||
|
||||
const jobQueueClientMock = new ReportingAPIClient(coreMock.createSetup().http);
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const jobQueueClientMock = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0');
|
||||
jobQueueClientMock.findForJobIds = async () => mockJobsFound;
|
||||
jobQueueClientMock.getInfo = () =>
|
||||
Promise.resolve(({ content: 'this is the completed report data' } as unknown) as Job);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { Job } from '../lib/job';
|
||||
import { ReportInfoButton } from './report_info_button';
|
||||
|
||||
|
@ -14,8 +15,9 @@ jest.mock('../lib/reporting_api_client');
|
|||
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
|
||||
const httpSetup = {} as any;
|
||||
const apiClient = new ReportingAPIClient(httpSetup);
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const apiClient = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0');
|
||||
|
||||
const job = new Job({
|
||||
id: 'abc-123',
|
||||
index: '.reporting-2020.04.12',
|
||||
|
@ -29,6 +31,7 @@ const job = new Job({
|
|||
meta: { layout: 'preserve_layout', objectType: 'canvas workpad' },
|
||||
payload: {
|
||||
browserTimezone: 'America/Phoenix',
|
||||
version: '7.15.0-test',
|
||||
layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' },
|
||||
objectType: 'canvas workpad',
|
||||
title: 'My Canvas Workpad',
|
||||
|
|
|
@ -32,15 +32,15 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
|
|||
});
|
||||
|
||||
const mockJobs: ReportApiJSON[] = [
|
||||
{ id: 'k90e51pk1ieucbae0c3t8wo2', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 0, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '1970-01-01T00:00:00.000Z', status: 'pending', timeout: 300000 }, // prettier-ignore
|
||||
{ id: 'k90e51pk1ieucbae0c3t8wo1', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T21:06:14.526Z', started_at: '2020-04-14T21:01:14.526Z', status: 'processing', timeout: 300000 },
|
||||
{ id: 'k90cmthd1gv8cbae0c2le8bo', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T20:19:14.748Z', created_at: '2020-04-14T20:19:02.977Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T20:24:04.073Z', started_at: '2020-04-14T20:19:04.073Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k906958e1d4wcbae0c9hip1a', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:21:08.223Z', created_at: '2020-04-14T17:20:27.326Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 49468, warnings: [ 'An error occurred when trying to read the page for visualization panel info. You may need to increase \'xpack.reporting.capture.timeouts.waitForElements\'. TimeoutError: waiting for selector "[data-shared-item],[data-shared-items-count]" failed: timeout 30000ms exceeded', ] }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:25:29.444Z', started_at: '2020-04-14T17:20:29.444Z', status: 'completed_with_warnings', timeout: 300000 },
|
||||
{ id: 'k9067y2a1d4wcbae0cad38n0', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:53.244Z', created_at: '2020-04-14T17:19:31.379Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:24:39.883Z', started_at: '2020-04-14T17:19:39.883Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k9067s1m1d4wcbae0cdnvcms', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:36.822Z', created_at: '2020-04-14T17:19:23.578Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:24:25.247Z', started_at: '2020-04-14T17:19:25.247Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k9065q3s1d4wcbae0c00fxlh', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:18:03.910Z', created_at: '2020-04-14T17:17:47.752Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:22:50.379Z', started_at: '2020-04-14T17:17:50.379Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k905zdw11d34cbae0c3y6tzh', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:13:03.719Z', created_at: '2020-04-14T17:12:51.985Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:17:52.431Z', started_at: '2020-04-14T17:12:52.431Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k8t4ylcb07mi9d006214ifyg', index: '.reporting-2020.04.05', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-09T19:10:10.049Z', created_at: '2020-04-09T19:09:52.139Z', created_by: 'elastic', jobtype: 'PNG', kibana_id: 'f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'png', objectType: 'visualization' }, output: { content_type: 'image/png', size: 123456789 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 1575, width: 1423 }, id: 'png' }, objectType: 'visualization', title: 'count' }, process_expiration: '2020-04-09T19:14:54.570Z', started_at: '2020-04-09T19:09:54.570Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k90e51pk1ieucbae0c3t8wo2', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 0, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '1970-01-01T00:00:00.000Z', status: 'pending', timeout: 300000}, // prettier-ignore
|
||||
{ id: 'k90e51pk1ieucbae0c3t8wo1', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T21:06:14.526Z', started_at: '2020-04-14T21:01:14.526Z', status: 'processing', timeout: 300000 },
|
||||
{ id: 'k90cmthd1gv8cbae0c2le8bo', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T20:19:14.748Z', created_at: '2020-04-14T20:19:02.977Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T20:24:04.073Z', started_at: '2020-04-14T20:19:04.073Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k906958e1d4wcbae0c9hip1a', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:21:08.223Z', created_at: '2020-04-14T17:20:27.326Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 49468, warnings: [ 'An error occurred when trying to read the page for visualization panel info. You may need to increase \'xpack.reporting.capture.timeouts.waitForElements\'. TimeoutError: waiting for selector "[data-shared-item],[data-shared-items-count]" failed: timeout 30000ms exceeded' ] }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T17:25:29.444Z', started_at: '2020-04-14T17:20:29.444Z', status: 'completed_with_warnings', timeout: 300000 },
|
||||
{ id: 'k9067y2a1d4wcbae0cad38n0', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:53.244Z', created_at: '2020-04-14T17:19:31.379Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T17:24:39.883Z', started_at: '2020-04-14T17:19:39.883Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k9067s1m1d4wcbae0cdnvcms', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:36.822Z', created_at: '2020-04-14T17:19:23.578Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T17:24:25.247Z', started_at: '2020-04-14T17:19:25.247Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k9065q3s1d4wcbae0c00fxlh', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:18:03.910Z', created_at: '2020-04-14T17:17:47.752Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T17:22:50.379Z', started_at: '2020-04-14T17:17:50.379Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k905zdw11d34cbae0c3y6tzh', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:13:03.719Z', created_at: '2020-04-14T17:12:51.985Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad', version: '7.14.0' }, process_expiration: '2020-04-14T17:17:52.431Z', started_at: '2020-04-14T17:12:52.431Z', status: 'completed', timeout: 300000 },
|
||||
{ id: 'k8t4ylcb07mi9d006214ifyg', index: '.reporting-2020.04.05', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-09T19:10:10.049Z', created_at: '2020-04-09T19:09:52.139Z', created_by: 'elastic', jobtype: 'PNG', kibana_id: 'f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'png', objectType: 'visualization' }, output: { content_type: 'image/png', size: 123456789 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 1575, width: 1423 }, id: 'png' }, objectType: 'visualization', title: 'count', version: '7.14.0' }, process_expiration: '2020-04-09T19:14:54.570Z', started_at: '2020-04-09T19:09:54.570Z', status: 'completed', timeout: 300000 },
|
||||
]; // prettier-ignore
|
||||
|
||||
const reportingAPIClient = {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { ReportingAPIClient } from './lib/reporting_api_client';
|
||||
import { ReportingSetup } from '.';
|
||||
import { getDefaultLayoutSelectors } from '../common';
|
||||
import { getSharedComponents } from './shared';
|
||||
|
@ -14,10 +15,11 @@ type Setup = jest.Mocked<ReportingSetup>;
|
|||
|
||||
const createSetupContract = (): Setup => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const apiClient = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0');
|
||||
return {
|
||||
getDefaultLayoutSelectors: jest.fn().mockImplementation(getDefaultLayoutSelectors),
|
||||
usesUiCapabilities: jest.fn().mockImplementation(() => true),
|
||||
components: getSharedComponents(coreSetup),
|
||||
components: getSharedComponents(coreSetup, apiClient),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -8,13 +8,17 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { LicensingPluginSetup } from '../../../licensing/public';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ReportingCsvPanelAction } from './get_csv_panel_action';
|
||||
|
||||
type LicenseResults = 'valid' | 'invalid' | 'unavailable' | 'expired';
|
||||
|
||||
const core = coreMock.createSetup();
|
||||
let apiClient: ReportingAPIClient;
|
||||
|
||||
describe('GetCsvReportPanelAction', () => {
|
||||
let core: any;
|
||||
let context: any;
|
||||
let mockLicense$: any;
|
||||
let mockSearchSource: any;
|
||||
|
@ -32,6 +36,9 @@ describe('GetCsvReportPanelAction', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
apiClient = new ReportingAPIClient(core.http, core.uiSettings, '7.15.0');
|
||||
jest.spyOn(apiClient, 'createImmediateReport');
|
||||
|
||||
mockLicense$ = (state: LicenseResults = 'valid') => {
|
||||
return (Rx.of({
|
||||
check: jest.fn().mockImplementation(() => ({ state })),
|
||||
|
@ -47,21 +54,6 @@ describe('GetCsvReportPanelAction', () => {
|
|||
null,
|
||||
];
|
||||
|
||||
core = {
|
||||
http: {
|
||||
post: jest.fn().mockImplementation(() => Promise.resolve(true)),
|
||||
},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addSuccess: jest.fn(),
|
||||
addDanger: jest.fn(),
|
||||
},
|
||||
},
|
||||
uiSettings: {
|
||||
get: () => 'Browser',
|
||||
},
|
||||
} as any;
|
||||
|
||||
mockSearchSource = {
|
||||
createCopy: () => mockSearchSource,
|
||||
removeField: jest.fn(),
|
||||
|
@ -92,6 +84,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
it('translates empty embeddable context into job params', async () => {
|
||||
const panel = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -101,12 +94,14 @@ describe('GetCsvReportPanelAction', () => {
|
|||
|
||||
await panel.execute(context);
|
||||
|
||||
expect(core.http.post).toHaveBeenCalledWith(
|
||||
'/api/reporting/v1/generate/immediate/csv_searchsource',
|
||||
{
|
||||
body: '{"searchSource":{},"columns":[],"browserTimezone":"America/New_York"}',
|
||||
}
|
||||
);
|
||||
expect(apiClient.createImmediateReport).toHaveBeenCalledWith({
|
||||
browserTimezone: undefined,
|
||||
columns: [],
|
||||
objectType: 'downloadCsv',
|
||||
searchSource: {},
|
||||
title: undefined,
|
||||
version: '7.15.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('translates embeddable context into job params', async () => {
|
||||
|
@ -126,6 +121,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
|
||||
const panel = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -135,18 +131,20 @@ describe('GetCsvReportPanelAction', () => {
|
|||
|
||||
await panel.execute(context);
|
||||
|
||||
expect(core.http.post).toHaveBeenCalledWith(
|
||||
'/api/reporting/v1/generate/immediate/csv_searchsource',
|
||||
{
|
||||
body:
|
||||
'{"searchSource":{"testData":"testDataValue"},"columns":["column_a","column_b"],"browserTimezone":"America/New_York"}',
|
||||
}
|
||||
);
|
||||
expect(apiClient.createImmediateReport).toHaveBeenCalledWith({
|
||||
browserTimezone: undefined,
|
||||
columns: ['column_a', 'column_b'],
|
||||
objectType: 'downloadCsv',
|
||||
searchSource: { testData: 'testDataValue' },
|
||||
title: undefined,
|
||||
version: '7.15.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows downloading for valid licenses', async () => {
|
||||
const panel = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -162,6 +160,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
it('shows a good old toastie when it successfully starts', async () => {
|
||||
const panel = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -176,14 +175,10 @@ describe('GetCsvReportPanelAction', () => {
|
|||
});
|
||||
|
||||
it('shows a bad old toastie when it successfully fails', async () => {
|
||||
const coreFails = {
|
||||
...core,
|
||||
http: {
|
||||
post: jest.fn().mockImplementation(() => Promise.reject('No more ram!')),
|
||||
},
|
||||
};
|
||||
apiClient.createImmediateReport = jest.fn().mockRejectedValue('No more ram!');
|
||||
const panel = new ReportingCsvPanelAction({
|
||||
core: coreFails,
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -200,6 +195,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
const licenseMock$ = mockLicense$('invalid');
|
||||
const plugin = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: licenseMock$,
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -215,6 +211,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
it('sets a display and icon type', () => {
|
||||
const panel = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -230,6 +227,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
it(`doesn't allow downloads when UI capability is not enabled`, async () => {
|
||||
const plugin = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -248,6 +246,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
mockStartServices$ = new Rx.Subject();
|
||||
const plugin = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: true,
|
||||
|
@ -261,6 +260,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
it(`allows download when license is valid and deprecated roles config is enabled`, async () => {
|
||||
const plugin = new ReportingCsvPanelAction({
|
||||
core,
|
||||
apiClient,
|
||||
license$: mockLicense$(),
|
||||
startServices$: mockStartServices$,
|
||||
usesUiCapabilities: false,
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment-timezone';
|
||||
import * as Rx from 'rxjs';
|
||||
import type { CoreSetup } from 'src/core/public';
|
||||
import type { CoreSetup, IUiSettingsClient, NotificationsSetup } from 'src/core/public';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import type { ISearchEmbeddable, SavedSearch } from '../../../../../src/plugins/discover/public';
|
||||
import {
|
||||
|
@ -20,9 +19,9 @@ import { ViewMode } from '../../../../../src/plugins/embeddable/public';
|
|||
import type { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { IncompatibleActionError } from '../../../../../src/plugins/ui_actions/public';
|
||||
import type { LicensingPluginSetup } from '../../../licensing/public';
|
||||
import { API_GENERATE_IMMEDIATE, CSV_REPORTING_ACTION } from '../../common/constants';
|
||||
import type { JobParamsDownloadCSV } from '../../server/export_types/csv_searchsource_immediate/types';
|
||||
import { CSV_REPORTING_ACTION } from '../../common/constants';
|
||||
import { checkLicense } from '../lib/license_check';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
|
||||
function isSavedSearchEmbeddable(
|
||||
embeddable: IEmbeddable | ISearchEmbeddable
|
||||
|
@ -35,6 +34,7 @@ interface ActionContext {
|
|||
}
|
||||
|
||||
interface Params {
|
||||
apiClient: ReportingAPIClient;
|
||||
core: CoreSetup;
|
||||
startServices$: Rx.Observable<[CoreStart, object, unknown]>;
|
||||
license$: LicensingPluginSetup['license$'];
|
||||
|
@ -47,11 +47,16 @@ export class ReportingCsvPanelAction implements ActionDefinition<ActionContext>
|
|||
public readonly id = CSV_REPORTING_ACTION;
|
||||
private licenseHasDownloadCsv: boolean = false;
|
||||
private capabilityHasDownloadCsv: boolean = false;
|
||||
private core: CoreSetup;
|
||||
private uiSettings: IUiSettingsClient;
|
||||
private notifications: NotificationsSetup;
|
||||
private apiClient: ReportingAPIClient;
|
||||
|
||||
constructor({ core, startServices$, license$, usesUiCapabilities }: Params) {
|
||||
constructor({ core, startServices$, license$, usesUiCapabilities, apiClient }: Params) {
|
||||
this.isDownloading = false;
|
||||
this.core = core;
|
||||
|
||||
this.uiSettings = core.uiSettings;
|
||||
this.notifications = core.notifications;
|
||||
this.apiClient = apiClient;
|
||||
|
||||
license$.subscribe((license) => {
|
||||
const results = license.check('reporting', 'basic');
|
||||
|
@ -83,7 +88,7 @@ export class ReportingCsvPanelAction implements ActionDefinition<ActionContext>
|
|||
return await getSharingData(
|
||||
savedSearch.searchSource,
|
||||
savedSearch, // TODO: get unsaved state (using embeddale.searchScope): https://github.com/elastic/kibana/issues/43977
|
||||
this.core.uiSettings
|
||||
this.uiSettings
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -111,24 +116,16 @@ export class ReportingCsvPanelAction implements ActionDefinition<ActionContext>
|
|||
const savedSearch = embeddable.getSavedSearch();
|
||||
const { columns, searchSource } = await this.getSearchSource(savedSearch, embeddable);
|
||||
|
||||
// If the TZ is set to the default "Browser", it will not be useful for
|
||||
// server-side export. We need to derive the timezone and pass it as a param
|
||||
// to the export API.
|
||||
// TODO: create a helper utility in Reporting. This is repeated in a few places.
|
||||
const kibanaTimezone = this.core.uiSettings.get('dateFormat:tz');
|
||||
const browserTimezone = kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone;
|
||||
const immediateJobParams: JobParamsDownloadCSV = {
|
||||
const immediateJobParams = this.apiClient.getDecoratedJobParams({
|
||||
searchSource,
|
||||
columns,
|
||||
browserTimezone,
|
||||
title: savedSearch.title,
|
||||
};
|
||||
|
||||
const body = JSON.stringify(immediateJobParams);
|
||||
objectType: 'downloadCsv', // FIXME: added for typescript, but immediate download job does not need objectType
|
||||
});
|
||||
|
||||
this.isDownloading = true;
|
||||
|
||||
this.core.notifications.toasts.addSuccess({
|
||||
this.notifications.toasts.addSuccess({
|
||||
title: i18n.translate('xpack.reporting.dashboard.csvDownloadStartedTitle', {
|
||||
defaultMessage: `CSV Download Started`,
|
||||
}),
|
||||
|
@ -138,9 +135,9 @@ export class ReportingCsvPanelAction implements ActionDefinition<ActionContext>
|
|||
'data-test-subj': 'csvDownloadStarted',
|
||||
});
|
||||
|
||||
await this.core.http
|
||||
.post(`${API_GENERATE_IMMEDIATE}`, { body })
|
||||
.then((rawResponse: string) => {
|
||||
await this.apiClient
|
||||
.createImmediateReport(immediateJobParams)
|
||||
.then((rawResponse) => {
|
||||
this.isDownloading = false;
|
||||
|
||||
const download = `${savedSearch.title}.csv`;
|
||||
|
@ -166,7 +163,7 @@ export class ReportingCsvPanelAction implements ActionDefinition<ActionContext>
|
|||
|
||||
private onGenerationFail(error: Error) {
|
||||
this.isDownloading = false;
|
||||
this.core.notifications.toasts.addDanger({
|
||||
this.notifications.toasts.addDanger({
|
||||
title: i18n.translate('xpack.reporting.dashboard.failedCsvDownloadTitle', {
|
||||
defaultMessage: `CSV download failed`,
|
||||
}),
|
||||
|
|
|
@ -11,6 +11,8 @@ import { catchError, filter, map, mergeMap, takeUntil } from 'rxjs/operators';
|
|||
import {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
HttpSetup,
|
||||
IUiSettingsClient,
|
||||
NotificationsSetup,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
|
@ -32,15 +34,14 @@ import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_ha
|
|||
import { getGeneralErrorToast } from './notifier';
|
||||
import { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action';
|
||||
import { getSharedComponents } from './shared';
|
||||
import { ReportingCsvShareProvider } from './share_context_menu/register_csv_reporting';
|
||||
import { reportingScreenshotShareProvider } from './share_context_menu/register_pdf_png_reporting';
|
||||
|
||||
import type {
|
||||
SharePluginSetup,
|
||||
SharePluginStart,
|
||||
UiActionsSetup,
|
||||
UiActionsStart,
|
||||
} from './shared_imports';
|
||||
import { ReportingCsvShareProvider } from './share_context_menu/register_csv_reporting';
|
||||
import { reportingScreenshotShareProvider } from './share_context_menu/register_pdf_png_reporting';
|
||||
|
||||
export interface ClientConfigType {
|
||||
poll: { jobsRefresh: { interval: number; intervalErrorMultiplier: number } };
|
||||
|
@ -89,6 +90,8 @@ export class ReportingPublicPlugin
|
|||
ReportingPublicPluginSetupDendencies,
|
||||
ReportingPublicPluginStartDendencies
|
||||
> {
|
||||
private kibanaVersion: string;
|
||||
private apiClient?: ReportingAPIClient;
|
||||
private readonly stop$ = new Rx.ReplaySubject(1);
|
||||
private readonly title = i18n.translate('xpack.reporting.management.reportingTitle', {
|
||||
defaultMessage: 'Reporting',
|
||||
|
@ -101,6 +104,17 @@ export class ReportingPublicPlugin
|
|||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.config = initializerContext.config.get<ClientConfigType>();
|
||||
this.kibanaVersion = initializerContext.env.packageInfo.version;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use a single instance of ReportingAPIClient for all the reporting code
|
||||
*/
|
||||
private getApiClient(http: HttpSetup, uiSettings: IUiSettingsClient) {
|
||||
if (!this.apiClient) {
|
||||
this.apiClient = new ReportingAPIClient(http, uiSettings, this.kibanaVersion);
|
||||
}
|
||||
return this.apiClient;
|
||||
}
|
||||
|
||||
private getContract(core?: CoreSetup) {
|
||||
|
@ -108,7 +122,7 @@ export class ReportingPublicPlugin
|
|||
this.contract = {
|
||||
getDefaultLayoutSelectors,
|
||||
usesUiCapabilities: () => this.config.roles?.enabled === false,
|
||||
components: getSharedComponents(core),
|
||||
components: getSharedComponents(core, this.getApiClient(core.http, core.uiSettings)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -120,11 +134,11 @@ export class ReportingPublicPlugin
|
|||
}
|
||||
|
||||
public setup(core: CoreSetup, setupDeps: ReportingPublicPluginSetupDendencies) {
|
||||
const { http, getStartServices, uiSettings } = core;
|
||||
const { getStartServices, uiSettings } = core;
|
||||
const {
|
||||
home,
|
||||
management,
|
||||
licensing: { license$ },
|
||||
licensing: { license$ }, // FIXME: 'license$' is deprecated
|
||||
share,
|
||||
uiActions,
|
||||
} = setupDeps;
|
||||
|
@ -132,7 +146,7 @@ export class ReportingPublicPlugin
|
|||
const startServices$ = Rx.from(getStartServices());
|
||||
const usesUiCapabilities = !this.config.roles.enabled;
|
||||
|
||||
const apiClient = new ReportingAPIClient(http);
|
||||
const apiClient = this.getApiClient(core.http, core.uiSettings);
|
||||
|
||||
home.featureCatalogue.register({
|
||||
id: 'reporting',
|
||||
|
@ -181,7 +195,7 @@ export class ReportingPublicPlugin
|
|||
|
||||
uiActions.addTriggerAction(
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
new ReportingCsvPanelAction({ core, startServices$, license$, usesUiCapabilities })
|
||||
new ReportingCsvPanelAction({ core, apiClient, startServices$, license$, usesUiCapabilities })
|
||||
);
|
||||
|
||||
const reportingStart = this.getContract(core);
|
||||
|
@ -213,8 +227,8 @@ export class ReportingPublicPlugin
|
|||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
const { http, notifications } = core;
|
||||
const apiClient = new ReportingAPIClient(http);
|
||||
const { notifications } = core;
|
||||
const apiClient = this.getApiClient(core.http, core.uiSettings);
|
||||
const streamHandler = new StreamHandler(notifications, apiClient);
|
||||
const interval = durationToNumber(this.config.poll.jobsRefresh.interval);
|
||||
Rx.timer(0, interval)
|
||||
|
|
|
@ -349,7 +349,7 @@ exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout
|
|||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="eui-displayBlock"
|
||||
textToCopy="http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%29"
|
||||
textToCopy="http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%2Cversion%3A%277.15.0%27%29"
|
||||
>
|
||||
<EuiToolTip
|
||||
anchorClassName="eui-displayBlock"
|
||||
|
@ -787,7 +787,7 @@ exports[`ScreenCapturePanelContent properly renders a view with "print" layout o
|
|||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="eui-displayBlock"
|
||||
textToCopy="http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%29"
|
||||
textToCopy="http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%2Cversion%3A%277.15.0%27%29"
|
||||
>
|
||||
<EuiToolTip
|
||||
anchorClassName="eui-displayBlock"
|
||||
|
@ -1097,7 +1097,7 @@ exports[`ScreenCapturePanelContent renders the default view properly 1`] = `
|
|||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="eui-displayBlock"
|
||||
textToCopy="http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%29"
|
||||
textToCopy="http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%2Cversion%3A%277.15.0%27%29"
|
||||
>
|
||||
<EuiToolTip
|
||||
anchorClassName="eui-displayBlock"
|
||||
|
|
33
x-pack/plugins/reporting/public/share_context_menu/index.ts
Normal file
33
x-pack/plugins/reporting/public/share_context_menu/index.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as Rx from 'rxjs';
|
||||
import type { IUiSettingsClient, ToastsSetup } from 'src/core/public';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import type { LicensingPluginSetup } from '../../../licensing/public';
|
||||
import type { LayoutParams } from '../../common/types';
|
||||
import type { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
|
||||
export interface ExportPanelShareOpts {
|
||||
apiClient: ReportingAPIClient;
|
||||
toasts: ToastsSetup;
|
||||
uiSettings: IUiSettingsClient;
|
||||
license$: LicensingPluginSetup['license$']; // FIXME: 'license$' is deprecated
|
||||
startServices$: Rx.Observable<[CoreStart, object, unknown]>;
|
||||
usesUiCapabilities: boolean;
|
||||
}
|
||||
|
||||
export interface ReportingSharingData {
|
||||
title: string;
|
||||
layout: LayoutParams;
|
||||
}
|
||||
|
||||
export interface JobParamsProviderOptions {
|
||||
sharingData: ReportingSharingData;
|
||||
shareableUrl: string;
|
||||
objectType: string;
|
||||
}
|
|
@ -6,35 +6,22 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment-timezone';
|
||||
import React from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
import type { IUiSettingsClient, ToastsSetup } from 'src/core/public';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import type { SearchSourceFields } from 'src/plugins/data/common';
|
||||
import { ExportPanelShareOpts } from '.';
|
||||
import type { ShareContext } from '../../../../../src/plugins/share/public';
|
||||
import type { LicensingPluginSetup } from '../../../licensing/public';
|
||||
import { CSV_JOB_TYPE } from '../../common/constants';
|
||||
import type { JobParamsCSV } from '../../server/export_types/csv_searchsource/types';
|
||||
import { checkLicense } from '../lib/license_check';
|
||||
import type { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ReportingPanelContent } from './reporting_panel_content_lazy';
|
||||
|
||||
export const ReportingCsvShareProvider = ({
|
||||
apiClient,
|
||||
toasts,
|
||||
uiSettings,
|
||||
license$,
|
||||
startServices$,
|
||||
uiSettings,
|
||||
usesUiCapabilities,
|
||||
}: {
|
||||
apiClient: ReportingAPIClient;
|
||||
toasts: ToastsSetup;
|
||||
license$: LicensingPluginSetup['license$'];
|
||||
startServices$: Rx.Observable<[CoreStart, object, unknown]>;
|
||||
uiSettings: IUiSettingsClient;
|
||||
usesUiCapabilities: boolean;
|
||||
}) => {
|
||||
}: ExportPanelShareOpts) => {
|
||||
let licenseToolTipContent = '';
|
||||
let licenseHasCsvReporting = false;
|
||||
let licenseDisabled = true;
|
||||
|
@ -56,22 +43,12 @@ export const ReportingCsvShareProvider = ({
|
|||
capabilityHasCsvReporting = true; // deprecated
|
||||
}
|
||||
|
||||
// If the TZ is set to the default "Browser", it will not be useful for
|
||||
// server-side export. We need to derive the timezone and pass it as a param
|
||||
// to the export API.
|
||||
// TODO: create a helper utility in Reporting. This is repeated in a few places.
|
||||
const browserTimezone =
|
||||
uiSettings.get('dateFormat:tz') === 'Browser'
|
||||
? moment.tz.guess()
|
||||
: uiSettings.get('dateFormat:tz');
|
||||
|
||||
const getShareMenuItems = ({ objectType, objectId, sharingData, onClose }: ShareContext) => {
|
||||
if ('search' !== objectType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const jobParams: JobParamsCSV = {
|
||||
browserTimezone,
|
||||
const jobParams = {
|
||||
title: sharingData.title as string,
|
||||
objectType,
|
||||
searchSource: sharingData.searchSource as SearchSourceFields,
|
||||
|
@ -104,6 +81,7 @@ export const ReportingCsvShareProvider = ({
|
|||
requiresSavedState={false}
|
||||
apiClient={apiClient}
|
||||
toasts={toasts}
|
||||
uiSettings={uiSettings}
|
||||
reportType={CSV_JOB_TYPE}
|
||||
layoutId={undefined}
|
||||
objectId={objectId}
|
||||
|
|
|
@ -6,83 +6,53 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment-timezone';
|
||||
import React from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
import type { IUiSettingsClient, ToastsSetup } from 'src/core/public';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { ShareContext } from 'src/plugins/share/public';
|
||||
import type { LicensingPluginSetup } from '../../../licensing/public';
|
||||
import type { LayoutParams } from '../../common/types';
|
||||
import type { JobParamsPNG } from '../../server/export_types/png/types';
|
||||
import type { JobParamsPDF } from '../../server/export_types/printable_pdf/types';
|
||||
import { ExportPanelShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.';
|
||||
import { checkLicense } from '../lib/license_check';
|
||||
import type { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy';
|
||||
|
||||
interface JobParamsProviderOptions {
|
||||
shareableUrl: string;
|
||||
apiClient: ReportingAPIClient;
|
||||
objectType: string;
|
||||
browserTimezone: string;
|
||||
sharingData: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const jobParamsProvider = ({
|
||||
objectType,
|
||||
browserTimezone,
|
||||
sharingData,
|
||||
}: JobParamsProviderOptions) => {
|
||||
return {
|
||||
const getJobParams = (
|
||||
apiClient: ReportingAPIClient,
|
||||
opts: JobParamsProviderOptions,
|
||||
type: 'pdf' | 'png'
|
||||
) => () => {
|
||||
const {
|
||||
objectType,
|
||||
browserTimezone,
|
||||
layout: sharingData.layout as LayoutParams,
|
||||
title: sharingData.title as string,
|
||||
};
|
||||
};
|
||||
sharingData: { title, layout },
|
||||
} = opts;
|
||||
|
||||
const baseParams = {
|
||||
objectType,
|
||||
layout,
|
||||
title,
|
||||
};
|
||||
|
||||
const getPdfJobParams = (opts: JobParamsProviderOptions) => (): JobParamsPDF => {
|
||||
// Relative URL must have URL prefix (Spaces ID prefix), but not server basePath
|
||||
// Replace hashes with original RISON values.
|
||||
const relativeUrl = opts.shareableUrl.replace(
|
||||
window.location.origin + opts.apiClient.getServerBasePath(),
|
||||
window.location.origin + apiClient.getServerBasePath(),
|
||||
''
|
||||
);
|
||||
|
||||
return {
|
||||
...jobParamsProvider(opts),
|
||||
relativeUrls: [relativeUrl], // multi URL for PDF
|
||||
};
|
||||
};
|
||||
if (type === 'pdf') {
|
||||
// multi URL for PDF
|
||||
return { ...baseParams, relativeUrls: [relativeUrl] };
|
||||
}
|
||||
|
||||
const getPngJobParams = (opts: JobParamsProviderOptions) => (): JobParamsPNG => {
|
||||
// Replace hashes with original RISON values.
|
||||
const relativeUrl = opts.shareableUrl.replace(
|
||||
window.location.origin + opts.apiClient.getServerBasePath(),
|
||||
''
|
||||
);
|
||||
|
||||
return {
|
||||
...jobParamsProvider(opts),
|
||||
relativeUrl, // single URL for PNG
|
||||
};
|
||||
// single URL for PNG
|
||||
return { ...baseParams, relativeUrl };
|
||||
};
|
||||
|
||||
export const reportingScreenshotShareProvider = ({
|
||||
apiClient,
|
||||
toasts,
|
||||
uiSettings,
|
||||
license$,
|
||||
startServices$,
|
||||
uiSettings,
|
||||
usesUiCapabilities,
|
||||
}: {
|
||||
apiClient: ReportingAPIClient;
|
||||
toasts: ToastsSetup;
|
||||
license$: LicensingPluginSetup['license$'];
|
||||
startServices$: Rx.Observable<[CoreStart, object, unknown]>;
|
||||
uiSettings: IUiSettingsClient;
|
||||
usesUiCapabilities: boolean;
|
||||
}) => {
|
||||
}: ExportPanelShareOpts) => {
|
||||
let licenseToolTipContent = '';
|
||||
let licenseDisabled = true;
|
||||
let licenseHasScreenshotReporting = false;
|
||||
|
@ -110,22 +80,13 @@ export const reportingScreenshotShareProvider = ({
|
|||
capabilityHasVisualizeScreenshotReporting = true;
|
||||
}
|
||||
|
||||
// If the TZ is set to the default "Browser", it will not be useful for
|
||||
// server-side export. We need to derive the timezone and pass it as a param
|
||||
// to the export API.
|
||||
// TODO: create a helper utility in Reporting. This is repeated in a few places.
|
||||
const browserTimezone =
|
||||
uiSettings.get('dateFormat:tz') === 'Browser'
|
||||
? moment.tz.guess()
|
||||
: uiSettings.get('dateFormat:tz');
|
||||
|
||||
const getShareMenuItems = ({
|
||||
objectType,
|
||||
objectId,
|
||||
sharingData,
|
||||
isDirty,
|
||||
onClose,
|
||||
shareableUrl,
|
||||
...shareOpts
|
||||
}: ShareContext) => {
|
||||
if (!licenseHasScreenshotReporting) {
|
||||
return [];
|
||||
|
@ -143,6 +104,7 @@ export const reportingScreenshotShareProvider = ({
|
|||
return [];
|
||||
}
|
||||
|
||||
const { sharingData } = (shareOpts as unknown) as { sharingData: ReportingSharingData };
|
||||
const shareActions = [];
|
||||
|
||||
const pngPanelTitle = i18n.translate('xpack.reporting.shareContextMenu.pngReportsButtonLabel', {
|
||||
|
@ -165,16 +127,11 @@ export const reportingScreenshotShareProvider = ({
|
|||
<ScreenCapturePanelContent
|
||||
apiClient={apiClient}
|
||||
toasts={toasts}
|
||||
uiSettings={uiSettings}
|
||||
reportType="png"
|
||||
objectId={objectId}
|
||||
requiresSavedState={true}
|
||||
getJobParams={getPngJobParams({
|
||||
shareableUrl,
|
||||
apiClient,
|
||||
objectType,
|
||||
browserTimezone,
|
||||
sharingData,
|
||||
})}
|
||||
getJobParams={getJobParams(apiClient, { shareableUrl, objectType, sharingData }, 'png')}
|
||||
isDirty={isDirty}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
@ -202,17 +159,12 @@ export const reportingScreenshotShareProvider = ({
|
|||
<ScreenCapturePanelContent
|
||||
apiClient={apiClient}
|
||||
toasts={toasts}
|
||||
uiSettings={uiSettings}
|
||||
reportType="printablePdf"
|
||||
objectId={objectId}
|
||||
requiresSavedState={true}
|
||||
layoutOption={objectType === 'dashboard' ? 'print' : undefined}
|
||||
getJobParams={getPdfJobParams({
|
||||
shareableUrl,
|
||||
apiClient,
|
||||
objectType,
|
||||
browserTimezone,
|
||||
sharingData,
|
||||
})}
|
||||
getJobParams={getJobParams(apiClient, { shareableUrl, objectType, sharingData }, 'pdf')}
|
||||
isDirty={isDirty}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
|
|
@ -7,26 +7,56 @@
|
|||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { notificationServiceMock } from 'src/core/public/mocks';
|
||||
|
||||
import { ReportingPanelContent, Props } from './reporting_panel_content';
|
||||
import {
|
||||
httpServiceMock,
|
||||
notificationServiceMock,
|
||||
uiSettingsServiceMock,
|
||||
} from 'src/core/public/mocks';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ReportingPanelContent, ReportingPanelProps as Props } from './reporting_panel_content';
|
||||
|
||||
describe('ReportingPanelContent', () => {
|
||||
const mountComponent = (props: Partial<Props>) =>
|
||||
const props: Partial<Props> = {
|
||||
layoutId: 'super_cool_layout_id_X',
|
||||
};
|
||||
const jobParams = {
|
||||
appState: 'very_cool_app_state_X',
|
||||
objectType: 'noice_object',
|
||||
title: 'ultimate_title',
|
||||
};
|
||||
const toasts = notificationServiceMock.createSetupContract().toasts;
|
||||
const http = httpServiceMock.createSetupContract();
|
||||
const uiSettings = uiSettingsServiceMock.createSetupContract();
|
||||
let apiClient: ReportingAPIClient;
|
||||
|
||||
beforeEach(() => {
|
||||
props.layoutId = 'super_cool_layout_id_X';
|
||||
uiSettings.get.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'dateFormat:tz':
|
||||
return 'Mars';
|
||||
}
|
||||
});
|
||||
apiClient = new ReportingAPIClient(http, uiSettings, '7.15.0-test');
|
||||
});
|
||||
|
||||
const mountComponent = (newProps: Partial<Props>) =>
|
||||
mountWithIntl(
|
||||
<ReportingPanelContent
|
||||
requiresSavedState
|
||||
// We have unsaved changes
|
||||
isDirty={true}
|
||||
isDirty={true} // We have unsaved changes
|
||||
reportType="test"
|
||||
layoutId="test"
|
||||
getJobParams={jest.fn().mockReturnValue({})}
|
||||
objectId={'my-object-id'}
|
||||
apiClient={{ getReportingJobPath: () => 'test' } as any}
|
||||
toasts={notificationServiceMock.createSetupContract().toasts}
|
||||
objectId="my-object-id"
|
||||
layoutId={props.layoutId}
|
||||
getJobParams={() => jobParams}
|
||||
apiClient={apiClient}
|
||||
toasts={toasts}
|
||||
uiSettings={uiSettings}
|
||||
{...props}
|
||||
{...newProps}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('saved state', () => {
|
||||
it('prevents generating reports when saving is required and we have unsaved changes', () => {
|
||||
const wrapper = mountComponent({
|
||||
|
@ -51,5 +81,20 @@ describe('ReportingPanelContent', () => {
|
|||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('changing the layout triggers refreshing the state with the latest job params', () => {
|
||||
const wrapper = mountComponent({ requiresSavedState: false });
|
||||
wrapper.update();
|
||||
expect(wrapper.find('EuiCopy').prop('textToCopy')).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/reporting/generate/test?jobParams=%28appState%3Avery_cool_app_state_X%2CbrowserTimezone%3AMars%2CobjectType%3Anoice_object%2Ctitle%3Aultimate_title%2Cversion%3A%277.15.0-test%27%29"`
|
||||
);
|
||||
|
||||
jobParams.appState = 'very_NOT_cool_app_state_Y';
|
||||
wrapper.setProps({ layoutId: 'super_cool_layout_id_Y' }); // update the component internal state
|
||||
wrapper.update();
|
||||
expect(wrapper.find('EuiCopy').prop('textToCopy')).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/reporting/generate/test?jobParams=%28appState%3Avery_NOT_cool_app_state_Y%2CbrowserTimezone%3AMars%2CobjectType%3Anoice_object%2Ctitle%3Aultimate_title%2Cversion%3A%277.15.0-test%27%29"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,29 +18,30 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import { ToastsSetup } from 'src/core/public';
|
||||
import { ToastsSetup, IUiSettingsClient } from 'src/core/public';
|
||||
import url from 'url';
|
||||
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { CSV_REPORT_TYPE, PDF_REPORT_TYPE, PNG_REPORT_TYPE } from '../../common/constants';
|
||||
import { BaseParams } from '../../common/types';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
|
||||
export interface Props {
|
||||
export interface ReportingPanelProps {
|
||||
apiClient: ReportingAPIClient;
|
||||
toasts: ToastsSetup;
|
||||
uiSettings: IUiSettingsClient;
|
||||
reportType: string;
|
||||
|
||||
/** Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. **/
|
||||
requiresSavedState: boolean;
|
||||
layoutId: string | undefined;
|
||||
requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator.
|
||||
layoutId?: string;
|
||||
objectId?: string;
|
||||
getJobParams: () => BaseParams;
|
||||
getJobParams: () => Omit<BaseParams, 'browserTimezone' | 'version'>;
|
||||
options?: ReactElement<any> | null;
|
||||
isDirty?: boolean;
|
||||
onClose?: () => void;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
export type Props = ReportingPanelProps & { intl: InjectedIntl };
|
||||
|
||||
interface State {
|
||||
isStale: boolean;
|
||||
absoluteUrl: string;
|
||||
|
@ -68,12 +69,12 @@ class ReportingPanelContentUi extends Component<Props, State> {
|
|||
private getAbsoluteReportGenerationUrl = (props: Props) => {
|
||||
const relativePath = this.props.apiClient.getReportingJobPath(
|
||||
props.reportType,
|
||||
props.getJobParams()
|
||||
this.props.apiClient.getDecoratedJobParams(this.props.getJobParams())
|
||||
);
|
||||
return url.resolve(window.location.href, relativePath);
|
||||
return url.resolve(window.location.href, relativePath); // FIXME: '(from: string, to: string): string' is deprecated
|
||||
};
|
||||
|
||||
public componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
public componentDidUpdate(_prevProps: Props, prevState: State) {
|
||||
if (this.props.layoutId && this.props.layoutId !== prevState.layoutId) {
|
||||
this.setState({
|
||||
...prevState,
|
||||
|
@ -231,9 +232,12 @@ class ReportingPanelContentUi extends Component<Props, State> {
|
|||
|
||||
private createReportingJob = () => {
|
||||
const { intl } = this.props;
|
||||
const decoratedJobParams = this.props.apiClient.getDecoratedJobParams(
|
||||
this.props.getJobParams()
|
||||
);
|
||||
|
||||
return this.props.apiClient
|
||||
.createReportingJob(this.props.reportType, this.props.getJobParams())
|
||||
.createReportingJob(this.props.reportType, decoratedJobParams)
|
||||
.then(() => {
|
||||
this.props.toasts.addSuccess({
|
||||
title: intl.formatMessage(
|
||||
|
|
|
@ -8,25 +8,33 @@
|
|||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n/react';
|
||||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { BaseParams } from '../../common/types';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ScreenCapturePanelContent } from './screen_capture_panel_content';
|
||||
|
||||
const getJobParamsDefault: () => BaseParams = () => ({
|
||||
const { http, uiSettings, ...coreSetup } = coreMock.createSetup();
|
||||
uiSettings.get.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'dateFormat:tz':
|
||||
return 'Mars';
|
||||
}
|
||||
});
|
||||
const apiClient = new ReportingAPIClient(http, uiSettings, '7.15.0');
|
||||
|
||||
const getJobParamsDefault = () => ({
|
||||
objectType: 'test-object-type',
|
||||
title: 'Test Report Title',
|
||||
browserTimezone: 'America/New_York',
|
||||
});
|
||||
|
||||
test('ScreenCapturePanelContent renders the default view properly', () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const component = mount(
|
||||
<IntlProvider locale="en">
|
||||
<ScreenCapturePanelContent
|
||||
reportType="Analytical App"
|
||||
requiresSavedState={false}
|
||||
apiClient={new ReportingAPIClient(coreSetup.http)}
|
||||
apiClient={apiClient}
|
||||
uiSettings={uiSettings}
|
||||
toasts={coreSetup.notifications.toasts}
|
||||
getJobParams={getJobParamsDefault}
|
||||
/>
|
||||
|
@ -38,14 +46,14 @@ test('ScreenCapturePanelContent renders the default view properly', () => {
|
|||
});
|
||||
|
||||
test('ScreenCapturePanelContent properly renders a view with "canvas" layout option', () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const component = mount(
|
||||
<IntlProvider locale="en">
|
||||
<ScreenCapturePanelContent
|
||||
layoutOption="canvas"
|
||||
reportType="Analytical App"
|
||||
requiresSavedState={false}
|
||||
apiClient={new ReportingAPIClient(coreSetup.http)}
|
||||
apiClient={apiClient}
|
||||
uiSettings={uiSettings}
|
||||
toasts={coreSetup.notifications.toasts}
|
||||
getJobParams={getJobParamsDefault}
|
||||
/>
|
||||
|
@ -56,14 +64,14 @@ test('ScreenCapturePanelContent properly renders a view with "canvas" layout opt
|
|||
});
|
||||
|
||||
test('ScreenCapturePanelContent properly renders a view with "print" layout option', () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const component = mount(
|
||||
<IntlProvider locale="en">
|
||||
<ScreenCapturePanelContent
|
||||
layoutOption="print"
|
||||
reportType="Analytical App"
|
||||
requiresSavedState={false}
|
||||
apiClient={new ReportingAPIClient(coreSetup.http)}
|
||||
apiClient={apiClient}
|
||||
uiSettings={uiSettings}
|
||||
toasts={coreSetup.notifications.toasts}
|
||||
getJobParams={getJobParamsDefault}
|
||||
/>
|
||||
|
@ -72,3 +80,22 @@ test('ScreenCapturePanelContent properly renders a view with "print" layout opti
|
|||
expect(component.find('EuiForm')).toMatchSnapshot();
|
||||
expect(component.text()).toMatch('Optimize for printing');
|
||||
});
|
||||
|
||||
test('ScreenCapturePanelContent decorated job params are visible in the POST URL', () => {
|
||||
const component = mount(
|
||||
<IntlProvider locale="en">
|
||||
<ScreenCapturePanelContent
|
||||
reportType="Analytical App"
|
||||
requiresSavedState={false}
|
||||
apiClient={apiClient}
|
||||
uiSettings={uiSettings}
|
||||
toasts={coreSetup.notifications.toasts}
|
||||
getJobParams={getJobParamsDefault}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
expect(component.find('EuiCopy').prop('textToCopy')).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%2Cversion%3A%277.15.0%27%29"`
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,24 +7,13 @@
|
|||
|
||||
import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment from 'moment';
|
||||
import React, { Component } from 'react';
|
||||
import { ToastsSetup } from 'src/core/public';
|
||||
import { getDefaultLayoutSelectors } from '../../common';
|
||||
import { BaseParams, LayoutParams } from '../../common/types';
|
||||
import { ReportingAPIClient } from '../lib/reporting_api_client';
|
||||
import { ReportingPanelContent } from './reporting_panel_content';
|
||||
import { LayoutParams } from '../../common/types';
|
||||
import { ReportingPanelContent, ReportingPanelProps } from './reporting_panel_content';
|
||||
|
||||
export interface Props {
|
||||
apiClient: ReportingAPIClient;
|
||||
toasts: ToastsSetup;
|
||||
reportType: string;
|
||||
export interface Props extends ReportingPanelProps {
|
||||
layoutOption?: 'canvas' | 'print';
|
||||
objectId?: string;
|
||||
getJobParams: () => BaseParams;
|
||||
requiresSavedState: boolean;
|
||||
isDirty?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -45,16 +34,10 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
|
|||
public render() {
|
||||
return (
|
||||
<ReportingPanelContent
|
||||
requiresSavedState={this.props.requiresSavedState}
|
||||
apiClient={this.props.apiClient}
|
||||
toasts={this.props.toasts}
|
||||
reportType={this.props.reportType}
|
||||
{...this.props}
|
||||
layoutId={this.getLayout().id}
|
||||
objectId={this.props.objectId}
|
||||
getJobParams={this.getJobParams}
|
||||
options={this.renderOptions()}
|
||||
isDirty={this.props.isDirty}
|
||||
onClose={this.props.onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -147,17 +130,10 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
|
|||
return { id: 'preserve_layout', dimensions, selectors };
|
||||
};
|
||||
|
||||
private getJobParams = (): Required<BaseParams> => {
|
||||
const outerParams = this.props.getJobParams();
|
||||
let browserTimezone = outerParams.browserTimezone;
|
||||
if (!browserTimezone) {
|
||||
browserTimezone = moment.tz.guess();
|
||||
}
|
||||
|
||||
private getJobParams = () => {
|
||||
return {
|
||||
...this.props.getJobParams(),
|
||||
layout: this.getLayout(),
|
||||
browserTimezone,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ type PropsPDF = Pick<PanelPropsScreenCapture, 'getJobParams' | 'layoutOption'> &
|
|||
* This is not planned to expand, as work is to be done on moving the export-type implementations out of Reporting
|
||||
* Related Discuss issue: https://github.com/elastic/kibana/issues/101422
|
||||
*/
|
||||
export function getSharedComponents(core: CoreSetup) {
|
||||
export function getSharedComponents(core: CoreSetup, apiClient: ReportingAPIClient) {
|
||||
return {
|
||||
ReportingPanelPDF(props: PropsPDF) {
|
||||
return (
|
||||
|
@ -31,8 +31,9 @@ export function getSharedComponents(core: CoreSetup) {
|
|||
layoutOption={props.layoutOption}
|
||||
requiresSavedState={false}
|
||||
reportType={PDF_REPORT_TYPE}
|
||||
apiClient={new ReportingAPIClient(core.http)}
|
||||
apiClient={apiClient}
|
||||
toasts={core.notifications.toasts}
|
||||
uiSettings={core.uiSettings}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -58,6 +58,7 @@ export interface ReportingInternalStart {
|
|||
}
|
||||
|
||||
export class ReportingCore {
|
||||
private kibanaVersion: string;
|
||||
private pluginSetupDeps?: ReportingInternalSetup;
|
||||
private pluginStartDeps?: ReportingInternalStart;
|
||||
private readonly pluginSetup$ = new Rx.ReplaySubject<boolean>(); // observe async background setupDeps and config each are done
|
||||
|
@ -72,6 +73,7 @@ export class ReportingCore {
|
|||
public getContract: () => ReportingSetup;
|
||||
|
||||
constructor(private logger: LevelLogger, context: PluginInitializerContext<ReportingConfigType>) {
|
||||
this.kibanaVersion = context.env.packageInfo.version;
|
||||
const syncConfig = context.config.get<ReportingConfigType>();
|
||||
this.deprecatedAllowedRoles = syncConfig.roles.enabled ? syncConfig.roles.allow : false;
|
||||
this.executeTask = new ExecuteReportTask(this, syncConfig, this.logger);
|
||||
|
@ -84,6 +86,10 @@ export class ReportingCore {
|
|||
this.executing = new Set();
|
||||
}
|
||||
|
||||
public getKibanaVersion() {
|
||||
return this.kibanaVersion;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register setupDeps
|
||||
*/
|
||||
|
|
|
@ -59,6 +59,7 @@ test('gets the csv content from job parameters', async () => {
|
|||
searchSource: {},
|
||||
objectType: 'search',
|
||||
title: 'Test Search',
|
||||
version: '7.13.0',
|
||||
},
|
||||
new CancellationToken()
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { IScopedSearchClient } from 'src/plugins/data/server';
|
|||
import { Datatable } from 'src/plugins/expressions/server';
|
||||
import { ReportingConfig } from '../../..';
|
||||
import {
|
||||
cellHasFormulas,
|
||||
ES_SEARCH_STRATEGY,
|
||||
FieldFormat,
|
||||
FieldFormatConfig,
|
||||
|
@ -22,7 +23,6 @@ import {
|
|||
SearchFieldValue,
|
||||
SearchSourceFields,
|
||||
tabifyDocs,
|
||||
cellHasFormulas,
|
||||
} from '../../../../../../../src/plugins/data/common';
|
||||
import { KbnServerError } from '../../../../../../../src/plugins/kibana_utils/server';
|
||||
import { CancellationToken } from '../../../../common';
|
||||
|
@ -68,7 +68,7 @@ export class CsvGenerator {
|
|||
private csvRowCount = 0;
|
||||
|
||||
constructor(
|
||||
private job: JobParamsCSV,
|
||||
private job: Omit<JobParamsCSV, 'version'>,
|
||||
private config: ReportingConfig,
|
||||
private clients: Clients,
|
||||
private dependencies: Dependencies,
|
||||
|
@ -219,7 +219,6 @@ export class CsvGenerator {
|
|||
*/
|
||||
private generateHeader(
|
||||
columns: string[],
|
||||
table: Datatable,
|
||||
builder: MaxSizeStringBuilder,
|
||||
settings: CsvExportSettings
|
||||
) {
|
||||
|
@ -357,7 +356,7 @@ export class CsvGenerator {
|
|||
|
||||
if (first) {
|
||||
first = false;
|
||||
this.generateHeader(columns, table, builder, settings);
|
||||
this.generateHeader(columns, builder, settings);
|
||||
}
|
||||
|
||||
if (table.rows.length < 1) {
|
||||
|
|
|
@ -11,7 +11,6 @@ import type { BaseParams, BasePayload } from '../../types';
|
|||
export type RawValue = string | object | null | undefined;
|
||||
|
||||
interface BaseParamsCSV {
|
||||
browserTimezone: string;
|
||||
searchSource: SearchSourceFields;
|
||||
columns?: string[];
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export const runTaskFnFactory: RunTaskFnFactory<ImmediateExecuteFn> = function e
|
|||
const config = reporting.getConfig();
|
||||
const logger = parentLogger.clone([CSV_SEARCHSOURCE_IMMEDIATE_TYPE, 'execute-job']);
|
||||
|
||||
return async function runTask(jobId, immediateJobParams, context, req) {
|
||||
return async function runTask(_jobId, immediateJobParams, context, req) {
|
||||
const job = {
|
||||
objectType: 'immediate-search',
|
||||
...immediateJobParams,
|
||||
|
|
|
@ -16,24 +16,16 @@ export const createJobFnFactory: CreateJobFnFactory<
|
|||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob(
|
||||
{ objectType, title, relativeUrl, browserTimezone, layout },
|
||||
context,
|
||||
req
|
||||
) {
|
||||
return async function createJob(jobParams, _context, req) {
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(req.headers);
|
||||
|
||||
validateUrls([relativeUrl]);
|
||||
validateUrls([jobParams.relativeUrl]);
|
||||
|
||||
return {
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(req, logger),
|
||||
objectType,
|
||||
title,
|
||||
relativeUrl,
|
||||
browserTimezone,
|
||||
layout,
|
||||
forceNow: new Date().toISOString(),
|
||||
...jobParams,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -16,24 +16,16 @@ export const createJobFnFactory: CreateJobFnFactory<
|
|||
const config = reporting.getConfig();
|
||||
const crypto = cryptoFactory(config.get('encryptionKey'));
|
||||
|
||||
return async function createJob(
|
||||
{ title, relativeUrls, browserTimezone, layout, objectType },
|
||||
context,
|
||||
req
|
||||
) {
|
||||
return async function createJob(jobParams, _context, req) {
|
||||
const serializedEncryptedHeaders = await crypto.encrypt(req.headers);
|
||||
|
||||
validateUrls(relativeUrls);
|
||||
validateUrls(jobParams.relativeUrls);
|
||||
|
||||
return {
|
||||
headers: serializedEncryptedHeaders,
|
||||
spaceId: reporting.getSpaceId(req, logger),
|
||||
browserTimezone,
|
||||
forceNow: new Date().toISOString(),
|
||||
layout,
|
||||
relativeUrls,
|
||||
title,
|
||||
objectType,
|
||||
...jobParams,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
20
x-pack/plugins/reporting/server/lib/check_params_version.ts
Normal file
20
x-pack/plugins/reporting/server/lib/check_params_version.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { UNVERSIONED_VERSION } from '../../common/constants';
|
||||
import type { BaseParams } from '../../common/types';
|
||||
import type { LevelLogger } from './';
|
||||
|
||||
export function checkParamsVersion(jobParams: BaseParams, logger: LevelLogger) {
|
||||
if (jobParams.version) {
|
||||
logger.debug(`Using reporting job params v${jobParams.version}`);
|
||||
return jobParams.version;
|
||||
}
|
||||
|
||||
logger.warning(`No version provided in report job params. Assuming ${UNVERSIONED_VERSION}`);
|
||||
return UNVERSIONED_VERSION;
|
||||
}
|
|
@ -14,17 +14,28 @@ import {
|
|||
createMockLevelLogger,
|
||||
createMockReportingCore,
|
||||
} from '../test_helpers';
|
||||
import { BasePayload, ReportingRequestHandlerContext } from '../types';
|
||||
import { ReportingRequestHandlerContext } from '../types';
|
||||
import { ExportTypesRegistry, ReportingStore } from './';
|
||||
import { enqueueJobFactory } from './enqueue_job';
|
||||
import { Report } from './store';
|
||||
import { TaskRunResult } from './tasks';
|
||||
|
||||
describe('Enqueue Job', () => {
|
||||
const logger = createMockLevelLogger();
|
||||
let mockReporting: ReportingCore;
|
||||
let mockExportTypesRegistry: ExportTypesRegistry;
|
||||
|
||||
const mockBaseParams = {
|
||||
browserTimezone: 'UTC',
|
||||
headers: 'cool_encrypted_headers',
|
||||
objectType: 'cool_object_type',
|
||||
title: 'cool_title',
|
||||
version: 'unknown' as any,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockBaseParams.version = '7.15.0-test';
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
mockExportTypesRegistry = new ExportTypesRegistry();
|
||||
mockExportTypesRegistry.register({
|
||||
|
@ -34,10 +45,8 @@ describe('Enqueue Job', () => {
|
|||
jobContentEncoding: 'base64',
|
||||
jobContentExtension: 'pdf',
|
||||
validLicenses: ['turquoise'],
|
||||
createJobFnFactory: () => async () =>
|
||||
(({ createJobTest: { test1: 'yes' } } as unknown) as BasePayload),
|
||||
runTaskFnFactory: () => async () =>
|
||||
(({ runParamsTest: { test2: 'yes' } } as unknown) as TaskRunResult),
|
||||
createJobFnFactory: () => async () => mockBaseParams,
|
||||
runTaskFnFactory: jest.fn(),
|
||||
});
|
||||
mockReporting = await createMockReportingCore(createMockConfigSchema());
|
||||
mockReporting.getExportTypesRegistry = () => mockExportTypesRegistry;
|
||||
|
@ -66,26 +75,59 @@ describe('Enqueue Job', () => {
|
|||
const enqueueJob = enqueueJobFactory(mockReporting, logger);
|
||||
const report = await enqueueJob(
|
||||
'printablePdf',
|
||||
{
|
||||
objectType: 'visualization',
|
||||
title: 'cool-viz',
|
||||
},
|
||||
mockBaseParams,
|
||||
false,
|
||||
({} as unknown) as ReportingRequestHandlerContext,
|
||||
({} as unknown) as KibanaRequest
|
||||
);
|
||||
|
||||
expect(report).toMatchObject({
|
||||
_id: expect.any(String),
|
||||
_index: '.reporting-foo-index-234',
|
||||
attempts: 0,
|
||||
created_by: false,
|
||||
created_at: expect.any(String),
|
||||
jobtype: 'printable_pdf',
|
||||
meta: { objectType: 'visualization' },
|
||||
output: null,
|
||||
payload: { createJobTest: { test1: 'yes' } },
|
||||
status: 'pending',
|
||||
});
|
||||
const { _id, created_at: _created_at, ...snapObj } = report;
|
||||
expect(snapObj).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_index": ".reporting-foo-index-234",
|
||||
"_primary_term": undefined,
|
||||
"_seq_no": undefined,
|
||||
"attempts": 0,
|
||||
"browser_type": undefined,
|
||||
"completed_at": undefined,
|
||||
"created_by": false,
|
||||
"jobtype": "printable_pdf",
|
||||
"kibana_id": undefined,
|
||||
"kibana_name": undefined,
|
||||
"max_attempts": undefined,
|
||||
"meta": Object {
|
||||
"layout": undefined,
|
||||
"objectType": "cool_object_type",
|
||||
},
|
||||
"migration_version": "7.14.0",
|
||||
"output": null,
|
||||
"payload": Object {
|
||||
"browserTimezone": "UTC",
|
||||
"headers": "cool_encrypted_headers",
|
||||
"objectType": "cool_object_type",
|
||||
"title": "cool_title",
|
||||
"version": "7.15.0-test",
|
||||
},
|
||||
"process_expiration": undefined,
|
||||
"started_at": undefined,
|
||||
"status": "pending",
|
||||
"timeout": undefined,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('provides a default kibana version field for older POST URLs', async () => {
|
||||
const enqueueJob = enqueueJobFactory(mockReporting, logger);
|
||||
mockBaseParams.version = undefined;
|
||||
const report = await enqueueJob(
|
||||
'printablePdf',
|
||||
mockBaseParams,
|
||||
false,
|
||||
({} as unknown) as ReportingRequestHandlerContext,
|
||||
({} as unknown) as KibanaRequest
|
||||
);
|
||||
|
||||
const { _id, created_at: _created_at, ...snapObj } = report;
|
||||
expect(snapObj.payload.version).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
import { ReportingCore } from '../';
|
||||
import { BaseParams, ReportingUser } from '../types';
|
||||
import { LevelLogger } from './';
|
||||
import { Report } from './store';
|
||||
import type { ReportingRequestHandlerContext } from '../types';
|
||||
import { BaseParams, ReportingUser } from '../types';
|
||||
import { checkParamsVersion, LevelLogger } from './';
|
||||
import { Report } from './store';
|
||||
|
||||
export type EnqueueJobFn = (
|
||||
exportTypeId: string,
|
||||
|
@ -47,6 +47,7 @@ export function enqueueJobFactory(
|
|||
reporting.getStore(),
|
||||
]);
|
||||
|
||||
jobParams.version = checkParamsVersion(jobParams, logger);
|
||||
const job = await createJob!(jobParams, context, request);
|
||||
|
||||
// 1. Add the report to ReportingStore to show as pending
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
export { checkLicense } from './check_license';
|
||||
export { checkParamsVersion } from './check_params_version';
|
||||
export { cryptoFactory } from './crypto';
|
||||
export { ExportTypesRegistry, getExportTypesRegistry } from './export_types_registry';
|
||||
export { LevelLogger } from './level_logger';
|
||||
|
|
|
@ -15,7 +15,13 @@ 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', title: 'cool report' },
|
||||
payload: {
|
||||
headers: 'payload_test_field',
|
||||
objectType: 'testOt',
|
||||
title: 'cool report',
|
||||
version: '7.14.0',
|
||||
browserTimezone: 'UTC',
|
||||
},
|
||||
meta: { objectType: 'test' },
|
||||
timeout: 30000,
|
||||
});
|
||||
|
@ -64,7 +70,13 @@ 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', title: 'hot report' },
|
||||
payload: {
|
||||
headers: 'payload_test_field',
|
||||
objectType: 'testOt',
|
||||
title: 'hot report',
|
||||
version: '7.14.0',
|
||||
browserTimezone: 'UTC',
|
||||
},
|
||||
meta: { objectType: 'stange' },
|
||||
timeout: 30000,
|
||||
});
|
||||
|
|
|
@ -255,6 +255,7 @@ describe('ReportingStore', () => {
|
|||
headers: 'rp_test_headers',
|
||||
objectType: 'testOt',
|
||||
browserTimezone: 'ABC',
|
||||
version: '7.14.0',
|
||||
},
|
||||
timeout: 30000,
|
||||
});
|
||||
|
@ -285,6 +286,7 @@ describe('ReportingStore', () => {
|
|||
headers: 'rp_test_headers',
|
||||
objectType: 'testOt',
|
||||
browserTimezone: 'BCD',
|
||||
version: '7.14.0',
|
||||
},
|
||||
timeout: 30000,
|
||||
});
|
||||
|
@ -315,6 +317,7 @@ describe('ReportingStore', () => {
|
|||
headers: 'rp_test_headers',
|
||||
objectType: 'testOt',
|
||||
browserTimezone: 'CDE',
|
||||
version: '7.14.0',
|
||||
},
|
||||
timeout: 30000,
|
||||
});
|
||||
|
@ -345,6 +348,7 @@ describe('ReportingStore', () => {
|
|||
headers: 'rp_test_headers',
|
||||
objectType: 'testOt',
|
||||
browserTimezone: 'utc',
|
||||
version: '7.14.0',
|
||||
},
|
||||
timeout: 30000,
|
||||
});
|
||||
|
@ -390,6 +394,7 @@ describe('ReportingStore', () => {
|
|||
headers: 'rp_test_headers',
|
||||
objectType: 'testOt',
|
||||
browserTimezone: 'utc',
|
||||
version: '7.14.0',
|
||||
},
|
||||
timeout: 30000,
|
||||
});
|
||||
|
|
|
@ -55,13 +55,14 @@ export function registerGenerateCsvFromSavedObjectImmediate(
|
|||
searchSource: schema.object({}, { unknowns: 'allow' }),
|
||||
browserTimezone: schema.string({ defaultValue: 'UTC' }),
|
||||
title: schema.string(),
|
||||
version: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
tags: kibanaAccessControlTags,
|
||||
},
|
||||
},
|
||||
userHandler(async (user, context, req: CsvFromSavedObjectRequest, res) => {
|
||||
userHandler(async (_user, context, req: CsvFromSavedObjectRequest, res) => {
|
||||
const logger = parentLogger.clone(['csv_searchsource_immediate']);
|
||||
const runTaskFn = runTaskFnFactory(reporting, logger);
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ export function registerGenerateFromJobParams(
|
|||
path: `${BASE_GENERATE}/{p*}`,
|
||||
validate: false,
|
||||
},
|
||||
(context, req, res) => {
|
||||
(_context, _req, res) => {
|
||||
return res.customError({ statusCode: 405, body: 'GET is not allowed' });
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import rison from 'rison-node';
|
||||
import { UnwrapPromise } from '@kbn/utility-types';
|
||||
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
|
||||
import { of } from 'rxjs';
|
||||
|
@ -129,7 +130,7 @@ describe('POST /api/reporting/generate', () => {
|
|||
|
||||
await supertest(httpSetup.server.listener)
|
||||
.post('/api/reporting/generate/TonyHawksProSkater2')
|
||||
.send({ jobParams: `abc` })
|
||||
.send({ jobParams: rison.encode({ title: `abc` }) })
|
||||
.expect(400)
|
||||
.then(({ body }) =>
|
||||
expect(body.message).toMatchInlineSnapshot('"Invalid export-type of TonyHawksProSkater2"')
|
||||
|
@ -145,7 +146,7 @@ describe('POST /api/reporting/generate', () => {
|
|||
|
||||
await supertest(httpSetup.server.listener)
|
||||
.post('/api/reporting/generate/printablePdf')
|
||||
.send({ jobParams: `abc` })
|
||||
.send({ jobParams: rison.encode({ title: `abc` }) })
|
||||
.expect(500);
|
||||
});
|
||||
|
||||
|
@ -157,7 +158,7 @@ describe('POST /api/reporting/generate', () => {
|
|||
|
||||
await supertest(httpSetup.server.listener)
|
||||
.post('/api/reporting/generate/printablePdf')
|
||||
.send({ jobParams: `abc` })
|
||||
.send({ jobParams: rison.encode({ title: `abc` }) })
|
||||
.expect(200)
|
||||
.then(({ body }) => {
|
||||
expect(body).toMatchObject({
|
||||
|
|
|
@ -35,6 +35,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
browserTimezone: 'UTC',
|
||||
title: 'testfooyu78yt90-',
|
||||
version: '7.13.0',
|
||||
} as any
|
||||
)) as supertest.Response;
|
||||
expect(res.status).to.eql(403);
|
||||
|
@ -52,6 +53,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
browserTimezone: 'UTC',
|
||||
title: 'testfooyu78yt90-',
|
||||
version: '7.13.0',
|
||||
} as any
|
||||
)) as supertest.Response;
|
||||
expect(res.status).to.eql(200);
|
||||
|
@ -69,6 +71,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
layout: { id: 'preserve' },
|
||||
relativeUrls: ['/fooyou'],
|
||||
objectType: 'dashboard',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(403);
|
||||
|
@ -84,6 +87,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
layout: { id: 'preserve' },
|
||||
relativeUrls: ['/fooyou'],
|
||||
objectType: 'dashboard',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(200);
|
||||
|
@ -101,6 +105,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
layout: { id: 'preserve' },
|
||||
relativeUrls: ['/fooyou'],
|
||||
objectType: 'visualization',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(403);
|
||||
|
@ -116,6 +121,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
layout: { id: 'preserve' },
|
||||
relativeUrls: ['/fooyou'],
|
||||
objectType: 'visualization',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(200);
|
||||
|
@ -133,6 +139,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
layout: { id: 'preserve' },
|
||||
relativeUrls: ['/fooyou'],
|
||||
objectType: 'canvas',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(403);
|
||||
|
@ -148,6 +155,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
layout: { id: 'preserve' },
|
||||
relativeUrls: ['/fooyou'],
|
||||
objectType: 'canvas',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(200);
|
||||
|
@ -164,6 +172,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
searchSource: {},
|
||||
objectType: 'search',
|
||||
title: 'test disallowed',
|
||||
version: '7.14.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(403);
|
||||
|
@ -183,6 +192,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
index: '5193f870-d861-11e9-a311-0fa548c5f953',
|
||||
} as any,
|
||||
columns: [],
|
||||
version: '7.13.0',
|
||||
}
|
||||
);
|
||||
expect(res.status).to.eql(200);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue