Skip to content

Commit

Permalink
[Reporting] Create reports with full state required to generate the r…
Browse files Browse the repository at this point in the history
…eport (#101048)

* very wip

* - Reached first iteration of reporting body value being saved with
  the report for **PDF**
- Removed v2 of the reporting since it looks like we may be able
  to make a backwards compatible change on existing PDF/PNG
  exports

* reintroduced pdfv2 export type, see https://github.com/elastic/kibana/issues/99890\#issuecomment-851527878

* fix a whol bunch of imports

* mapped out a working version for pdf

* refactor to tuples

* added v2 pdf to export type registry

* a lot of hackery to get reports generated in v2

* added png v2, png reports with locator state

* wip: refactored for loading the saved object on the redirect app URL

* major wip: initial stages of reporting redirect app, need to add a way to generate v2 reports!

* added a way to generate a v2 pdf from the example reporting plugin

* updated reporting example app to read and accept forwarded app state

* added reporting locator and updated server-side route to not use Boom

* removed reporting locator for now, first iteration of reports being generated using the reporting redirect app

* version with PNG working

* moved png/v2 -> png_v2

* moved printable_pdf/v2 -> printable_pdf_v2

* updated share public setup and start mocks

* fix types after merging master

* locator -> locatorParams AND added a new endpoint for getting locator params to client

* fix type import

* fix types

* clean up bad imports

* forceNow required on v2 payloads

* reworked create job interface for PNG task payload and updated consumer code report example for forcenow

* put locatorparams[] back onto the reportsource interface because on baseparams it conflicts with the different export type params

* move getCustomLogo and generatePng to common for export types

* additional import fixes

* urls -> url

* chore: fix and update types and fix jest import mocks

* - refactored v2 behaviour to avoid client-side request for locator
  instead this value is injected pre-page-load so that the
  redirect app can use it
- refactored the interface for the getScreenshot observable
  factory. specifically we now expect 'urlsOrUrlTuples' to be
  passed in. tested with new and old report types.

* updated the reporting example app to use locator migration for v2 report types

* added functionality for setting forceNow

* added forceNow to job payload for v2 report types and fixed shared components for v2

* write the output of v2 reports to stream

* fix types for forceNow

* added tests for execute job

* added comments, organized imports, removed selectors from report params

* fix some type issues

* feedback: removed duplicated PDF code, cleaned screenshot observable function and other minor tweaks

* use variable (not destructured values) and remove unused import

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
jloleysens and kibanamachine authored Aug 12, 2021
1 parent 4d7fd0a commit f08005e
Show file tree
Hide file tree
Showing 73 changed files with 1,553 additions and 95 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ snapshots.js
/x-pack/plugins/canvas/shareable_runtime/build
/x-pack/plugins/canvas/storybook/build
/x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/**
/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/server/lib/pdf/assets/**

# package overrides
/packages/elastic-eslint-config-kibana
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/screenshot_mode/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export interface ScreenshotModePluginSetup {
isScreenshotMode: IsScreenshotMode;

/**
* Set the current environment to screenshot mode. Intended to run in a browser-environment.
* Set the current environment to screenshot mode. Intended to run in a browser-environment, before any other scripts
* on the page have run to ensure that screenshot mode is detected as early as possible.
*/
setScreenshotModeEnabled: () => void;
}
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/share/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const createSetupContract = (): Setup => {
registerUrlGenerator: jest.fn(),
},
url,
navigate: jest.fn(),
};
return setupContract;
};
Expand All @@ -38,6 +39,7 @@ const createStartContract = (): Start => {
getUrlGenerator: jest.fn(),
},
toggleShareContextMenu: jest.fn(),
navigate: jest.fn(),
};
return startContract;
};
Expand Down
22 changes: 19 additions & 3 deletions src/plugins/share/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
UrlGeneratorsStart,
} from './url_generators/url_generator_service';
import { UrlService } from '../common/url_service';
import { RedirectManager } from './url_service';
import { RedirectManager, RedirectOptions } from './url_service';

export interface ShareSetupDependencies {
securityOss?: SecurityOssPluginSetup;
Expand All @@ -42,6 +42,12 @@ export type SharePluginSetup = ShareMenuRegistrySetup & {
* Utilities to work with URL locators and short URLs.
*/
url: UrlService;

/**
* Accepts serialized values for extracting a locator, migrating state from a provided version against
* the locator, then using the locator to navigate.
*/
navigate(options: RedirectOptions): void;
};

/** @public */
Expand All @@ -57,12 +63,20 @@ export type SharePluginStart = ShareMenuManagerStart & {
* Utilities to work with URL locators and short URLs.
*/
url: UrlService;

/**
* Accepts serialized values for extracting a locator, migrating state from a provided version against
* the locator, then using the locator to navigate.
*/
navigate(options: RedirectOptions): void;
};

export class SharePlugin implements Plugin<SharePluginSetup, SharePluginStart> {
private readonly shareMenuRegistry = new ShareMenuRegistry();
private readonly shareContextMenu = new ShareMenuManager();
private readonly urlGeneratorsService = new UrlGeneratorsService();

private redirectManager?: RedirectManager;
private url?: UrlService;

public setup(core: CoreSetup, plugins: ShareSetupDependencies): SharePluginSetup {
Expand All @@ -87,15 +101,16 @@ export class SharePlugin implements Plugin<SharePluginSetup, SharePluginStart> {
},
});

const redirectManager = new RedirectManager({
this.redirectManager = new RedirectManager({
url: this.url,
});
redirectManager.registerRedirectApp(core);
this.redirectManager.registerRedirectApp(core);

return {
...this.shareMenuRegistry.setup(),
urlGenerators: this.urlGeneratorsService.setup(core),
url: this.url,
navigate: (options: RedirectOptions) => this.redirectManager!.navigate(options),
};
}

Expand All @@ -108,6 +123,7 @@ export class SharePlugin implements Plugin<SharePluginSetup, SharePluginStart> {
),
urlGenerators: this.urlGeneratorsService.start(core),
url: this.url!,
navigate: (options: RedirectOptions) => this.redirectManager!.navigate(options),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export class RedirectManager {

public onMount(urlLocationSearch: string) {
const options = this.parseSearchParams(urlLocationSearch);
this.navigate(options);
}

public navigate(options: RedirectOptions) {
const locator = this.deps.url.locators.get(options.id);

if (!locator) {
Expand Down
6 changes: 6 additions & 0 deletions x-pack/examples/reporting_example/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@

export const PLUGIN_ID = 'reportingExample';
export const PLUGIN_NAME = 'reportingExample';

export {
REPORTING_EXAMPLE_LOCATOR_ID,
ReportingExampleLocatorDefinition,
ReportingExampleLocatorParams,
} from './locator';
30 changes: 30 additions & 0 deletions x-pack/examples/reporting_example/common/locator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { SerializableRecord } from '@kbn/utility-types';
import type { LocatorDefinition } from '../../../../src/plugins/share/public';
import { PLUGIN_ID } from '../common';

export const REPORTING_EXAMPLE_LOCATOR_ID = 'REPORTING_EXAMPLE_LOCATOR_ID';

export type ReportingExampleLocatorParams = SerializableRecord;

export class ReportingExampleLocatorDefinition implements LocatorDefinition<{}> {
public readonly id = REPORTING_EXAMPLE_LOCATOR_ID;

migrations = {
'1.0.0': (state: {}) => ({ ...state, migrated: true }),
};

public readonly getLocation = async (params: {}) => {
return {
app: PLUGIN_ID,
path: '/',
state: params,
};
};
}
2 changes: 1 addition & 1 deletion x-pack/examples/reporting_example/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
},
"description": "Example integration code for applications to feature reports.",
"optionalPlugins": [],
"requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode"]
"requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode", "share"]
}
15 changes: 12 additions & 3 deletions x-pack/examples/reporting_example/public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
import { ReportingExampleApp } from './components/app';
import { SetupDeps, StartDeps } from './types';
import { SetupDeps, StartDeps, MyForwardableState } from './types';

export const renderApp = (
coreStart: CoreStart,
deps: Omit<StartDeps & SetupDeps, 'developerExamples'>,
{ appBasePath, element }: AppMountParameters // FIXME: appBasePath is deprecated
{ appBasePath, element }: AppMountParameters, // FIXME: appBasePath is deprecated
forwardedParams: MyForwardableState
) => {
ReactDOM.render(<ReportingExampleApp basename={appBasePath} {...coreStart} {...deps} />, element);
ReactDOM.render(
<ReportingExampleApp
basename={appBasePath}
{...coreStart}
{...deps}
forwardedParams={forwardedParams}
/>,
element
);

return () => ReactDOM.unmountComponentAtNode(element);
};
117 changes: 109 additions & 8 deletions x-pack/examples/reporting_example/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,29 @@ import {
EuiPopover,
EuiText,
EuiTitle,
EuiCodeBlock,
EuiSpacer,
} from '@elastic/eui';
import moment from 'moment';
import { I18nProvider } from '@kbn/i18n/react';
import React, { useEffect, useState } from 'react';
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 { constants, ReportingStart } from '../../../../../x-pack/plugins/reporting/public';
import type { JobParamsPDFV2 } from '../../../../plugins/reporting/server/export_types/printable_pdf_v2/types';
import type { JobParamsPNGV2 } from '../../../../plugins/reporting/server/export_types/png_v2/types';

import { REPORTING_EXAMPLE_LOCATOR_ID } from '../../common';

import { MyForwardableState } from '../types';

interface ReportingExampleAppProps {
basename: string;
reporting: ReportingStart;
screenshotMode: ScreenshotModePluginSetup;
forwardedParams?: MyForwardableState;
}

const sourceLogos = ['Beats', 'Cloud', 'Logging', 'Kibana'];
Expand All @@ -42,8 +52,12 @@ export const ReportingExampleApp = ({
basename,
reporting,
screenshotMode,
forwardedParams,
}: ReportingExampleAppProps) => {
const { getDefaultLayoutSelectors } = reporting;
useEffect(() => {
// eslint-disable-next-line no-console
console.log('forwardedParams', forwardedParams);
}, [forwardedParams]);

// Context Menu
const [isPopoverOpen, setPopover] = useState(false);
Expand All @@ -70,28 +84,72 @@ export const ReportingExampleApp = ({
return {
layout: {
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
selectors: getDefaultLayoutSelectors(),
},
relativeUrls: ['/app/reportingExample#/intended-visualization'],
objectType: 'develeloperExample',
title: 'Reporting Developer Example',
};
};

const getPDFJobParamsDefaultV2 = (): JobParamsPDFV2 => {
return {
version: '8.0.0',
layout: {
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
},
locatorParams: [
{ id: REPORTING_EXAMPLE_LOCATOR_ID, version: '0.5.0', params: { myTestState: {} } },
],
objectType: 'develeloperExample',
title: 'Reporting Developer Example',
browserTimezone: moment.tz.guess(),
};
};

const getPNGJobParamsDefaultV2 = (): JobParamsPNGV2 => {
return {
version: '8.0.0',
layout: {
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
},
locatorParams: {
id: REPORTING_EXAMPLE_LOCATOR_ID,
version: '0.5.0',
params: { myTestState: {} },
},
objectType: 'develeloperExample',
title: 'Reporting Developer Example',
browserTimezone: moment.tz.guess(),
};
};

const panels = [
{ id: 0, items: [{ name: 'PDF Reports', icon: 'document', panel: 1 }] },
{
id: 0,
items: [
{ name: 'PDF Reports', icon: 'document', panel: 1 },
{ name: 'PNG Reports', icon: 'document', panel: 7 },
],
},
{
id: 1,
initialFocusedItemIndex: 1,
title: 'PDF Reports',
items: [
{ name: 'No Layout Option', icon: 'document', panel: 2 },
{ name: 'Default layout', icon: 'document', panel: 2 },
{ name: 'Default layout V2', icon: 'document', panel: 4 },
{ name: 'Canvas Layout Option', icon: 'canvasApp', panel: 3 },
],
},
{
id: 7,
initialFocusedItemIndex: 0,
title: 'PNG Reports',
items: [{ name: 'Default layout V2', icon: 'document', panel: 5 }],
},
{
id: 2,
title: 'No Layout Option',
title: 'Default layout',
content: (
<reporting.components.ReportingPanelPDF
getJobParams={getPDFJobParamsDefault}
Expand All @@ -110,6 +168,26 @@ export const ReportingExampleApp = ({
/>
),
},
{
id: 4,
title: 'Default layout V2',
content: (
<reporting.components.ReportingPanelPDFV2
getJobParams={getPDFJobParamsDefaultV2}
onClose={closePopover}
/>
),
},
{
id: 5,
title: 'Default layout V2',
content: (
<reporting.components.ReportingPanelPNGV2
getJobParams={getPNGJobParamsDefaultV2}
onClose={closePopover}
/>
),
},
];

return (
Expand All @@ -124,9 +202,11 @@ export const ReportingExampleApp = ({
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiTitle>
<h2>Example of a Sharing menu using components from Reporting</h2>
</EuiTitle>
<EuiSpacer />
<EuiText>
<p>Example of a Sharing menu using components from Reporting</p>

<EuiPopover
id="contextMenuExample"
button={<EuiButton onClick={onButtonClick}>Share</EuiButton>}
Expand All @@ -140,8 +220,29 @@ export const ReportingExampleApp = ({

<EuiHorizontalRule />

<div data-shared-items-container data-shared-items-count="4">
<div data-shared-items-container data-shared-items-count="5">
<EuiFlexGroup gutterSize="l">
<EuiFlexItem data-shared-item>
{forwardedParams ? (
<>
<EuiText>
<p>
<strong>Forwarded app state</strong>
</p>
</EuiText>
<EuiCodeBlock>{JSON.stringify(forwardedParams)}</EuiCodeBlock>
</>
) : (
<>
<EuiText>
<p>
<strong>No forwarded app state found</strong>
</p>
</EuiText>
<EuiCodeBlock>{'{}'}</EuiCodeBlock>
</>
)}
</EuiFlexItem>
{logos.map((item, index) => (
<EuiFlexItem key={index} data-shared-item>
<EuiCard
Expand Down
Loading

0 comments on commit f08005e

Please sign in to comment.