Skip to content

Commit b120cb3

Browse files
[AppServices/Examples] Add the example for Reporting integration (#82091)
* Add developer example for Reporting Refactor Reporting plugin to have shareable services * Update plugin.ts * use constant * add more description to using reporting as a service Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent e699d91 commit b120cb3

File tree

17 files changed

+310
-19
lines changed

17 files changed

+310
-19
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
root: true,
3+
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
4+
rules: {
5+
'@kbn/eslint/require-license-header': 'off',
6+
},
7+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Example Reporting integration!
2+
3+
Use this example code to understand how to add a "Generate Report" button to a
4+
Kibana page. This simple example shows that the end-to-end functionality of
5+
generating a screenshot report of a page just requires you to render a React
6+
component that you import from the Reportinng plugin.
7+
8+
A "reportable" Kibana page is one that has an **alternate version to show the data in a "screenshot-friendly" way**. The alternate version can be reached at a variation of the page's URL that the App team builds.
9+
10+
A "screenshot-friendly" page has **all interactive features turned off**. These are typically notifications, popups, tooltips, controls, autocomplete libraries, etc.
11+
12+
Turning off these features **keeps glitches out of the screenshot**, and makes the server-side headless browser **run faster and use less RAM**.
13+
14+
The URL that Reporting captures is controlled by the application, is a part of
15+
a "jobParams" object that gets passed to the React component imported from
16+
Reporting. The job params give the app control over the end-resulting report:
17+
18+
- Layout
19+
- Page dimensions
20+
- DOM attributes to select where the visualization container(s) is/are. The App team must add the attributes to DOM elements in their app.
21+
- DOM events that the page fires off and signals when the rendering is done. The App team must implement triggering the DOM events around rendering the data in their app.
22+
- Export type definition
23+
- Processes the jobParams into output data, which is stored in Elasticsearch in the Reporting system index.
24+
- Export type definitions are registered with the Reporting plugin at setup time.
25+
26+
The existing export type definitions are PDF, PNG, and CSV. They should be
27+
enough for nearly any use case.
28+
29+
If the existing options are too limited for a future use case, the AppServices
30+
team can assist the App team to implement a custom export type definition of
31+
their own, and register it using the Reporting plugin API **(documentation coming soon)**.
32+
33+
---
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const PLUGIN_ID = 'reportingExample';
2+
export const PLUGIN_NAME = 'reportingExample';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "reportingExample",
3+
"version": "1.0.0",
4+
"kibanaVersion": "kibana",
5+
"server": false,
6+
"ui": true,
7+
"optionalPlugins": [],
8+
"requiredPlugins": ["reporting", "developerExamples", "navigation"]
9+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
4+
import { StartDeps } from './types';
5+
import { ReportingExampleApp } from './components/app';
6+
7+
export const renderApp = (
8+
coreStart: CoreStart,
9+
startDeps: StartDeps,
10+
{ appBasePath, element }: AppMountParameters
11+
) => {
12+
ReactDOM.render(
13+
<ReportingExampleApp basename={appBasePath} {...coreStart} {...startDeps} />,
14+
element
15+
);
16+
17+
return () => ReactDOM.unmountComponentAtNode(element);
18+
};
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import {
2+
EuiCard,
3+
EuiCode,
4+
EuiFlexGroup,
5+
EuiFlexItem,
6+
EuiHorizontalRule,
7+
EuiIcon,
8+
EuiPage,
9+
EuiPageBody,
10+
EuiPageContent,
11+
EuiPageContentBody,
12+
EuiPageHeader,
13+
EuiPanel,
14+
EuiText,
15+
EuiTitle,
16+
} from '@elastic/eui';
17+
import { I18nProvider } from '@kbn/i18n/react';
18+
import React, { useEffect, useState } from 'react';
19+
import { BrowserRouter as Router } from 'react-router-dom';
20+
import * as Rx from 'rxjs';
21+
import { takeWhile } from 'rxjs/operators';
22+
import { CoreStart } from '../../../../../src/core/public';
23+
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
24+
import { constants, ReportingStart } from '../../../../../x-pack/plugins/reporting/public';
25+
import { JobParamsPDF } from '../../../../plugins/reporting/server/export_types/printable_pdf/types';
26+
27+
interface ReportingExampleAppDeps {
28+
basename: string;
29+
notifications: CoreStart['notifications'];
30+
http: CoreStart['http'];
31+
navigation: NavigationPublicPluginStart;
32+
reporting: ReportingStart;
33+
}
34+
35+
const sourceLogos = ['Beats', 'Cloud', 'Logging', 'Kibana'];
36+
37+
export const ReportingExampleApp = ({
38+
basename,
39+
notifications,
40+
http,
41+
reporting,
42+
}: ReportingExampleAppDeps) => {
43+
const { getDefaultLayoutSelectors, ReportingAPIClient } = reporting;
44+
const [logos, setLogos] = useState<string[]>([]);
45+
46+
useEffect(() => {
47+
Rx.timer(2200)
48+
.pipe(takeWhile(() => logos.length < sourceLogos.length))
49+
.subscribe(() => {
50+
setLogos([...sourceLogos.slice(0, logos.length + 1)]);
51+
});
52+
});
53+
54+
const getPDFJobParams = (): JobParamsPDF => {
55+
return {
56+
layout: {
57+
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
58+
selectors: getDefaultLayoutSelectors(),
59+
},
60+
relativeUrls: ['/app/reportingExample#/intended-visualization'],
61+
objectType: 'develeloperExample',
62+
title: 'Reporting Developer Example',
63+
};
64+
};
65+
66+
// Render the application DOM.
67+
return (
68+
<Router basename={basename}>
69+
<I18nProvider>
70+
<EuiPage>
71+
<EuiPageBody>
72+
<EuiPageHeader>
73+
<EuiTitle size="l">
74+
<h1>Reporting Example</h1>
75+
</EuiTitle>
76+
</EuiPageHeader>
77+
<EuiPageContent>
78+
<EuiPageContentBody>
79+
<EuiText>
80+
<p>
81+
Use the <EuiCode>ReportingStart.components.ScreenCapturePanel</EuiCode>{' '}
82+
component to add the Reporting panel to your page.
83+
</p>
84+
85+
<EuiHorizontalRule />
86+
87+
<EuiFlexGroup>
88+
<EuiFlexItem grow={false}>
89+
<EuiPanel>
90+
<reporting.components.ScreenCapturePanel
91+
apiClient={new ReportingAPIClient(http)}
92+
toasts={notifications.toasts}
93+
reportType={constants.PDF_REPORT_TYPE}
94+
getJobParams={getPDFJobParams}
95+
objectId="Visualization:Id:ToEnsure:Visualization:IsSaved"
96+
/>
97+
</EuiPanel>
98+
</EuiFlexItem>
99+
</EuiFlexGroup>
100+
101+
<EuiHorizontalRule />
102+
103+
<p>
104+
The logos below are in a <EuiCode>data-shared-items-container</EuiCode> element
105+
for Reporting.
106+
</p>
107+
108+
<div data-shared-items-container data-shared-items-count="4">
109+
<EuiFlexGroup gutterSize="l">
110+
{logos.map((item, index) => (
111+
<EuiFlexItem key={index} data-shared-item>
112+
<EuiCard
113+
icon={<EuiIcon size="xxl" type={`logo${item}`} />}
114+
title={`Elastic ${item}`}
115+
description="Example of a card's description. Stick to one or two sentences."
116+
onClick={() => {}}
117+
/>
118+
</EuiFlexItem>
119+
))}
120+
</EuiFlexGroup>
121+
</div>
122+
</EuiText>
123+
</EuiPageContentBody>
124+
</EuiPageContent>
125+
</EuiPageBody>
126+
</EuiPage>
127+
</I18nProvider>
128+
</Router>
129+
);
130+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ReportingExamplePlugin } from './plugin';
2+
3+
export function plugin() {
4+
return new ReportingExamplePlugin();
5+
}
6+
export { PluginSetup, PluginStart } from './types';
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
AppMountParameters,
3+
AppNavLinkStatus,
4+
CoreSetup,
5+
CoreStart,
6+
Plugin,
7+
} from '../../../../src/core/public';
8+
import { PLUGIN_ID, PLUGIN_NAME } from '../common';
9+
import { SetupDeps, StartDeps } from './types';
10+
11+
export class ReportingExamplePlugin implements Plugin<void, void, {}, {}> {
12+
public setup(core: CoreSetup, { developerExamples, ...depsSetup }: SetupDeps): void {
13+
core.application.register({
14+
id: PLUGIN_ID,
15+
title: PLUGIN_NAME,
16+
navLinkStatus: AppNavLinkStatus.hidden,
17+
async mount(params: AppMountParameters) {
18+
// Load application bundle
19+
const { renderApp } = await import('./application');
20+
const [coreStart, depsStart] = (await core.getStartServices()) as [
21+
CoreStart,
22+
StartDeps,
23+
unknown
24+
];
25+
// Render the application
26+
return renderApp(coreStart, { ...depsSetup, ...depsStart }, params);
27+
},
28+
});
29+
30+
// Show the app in Developer Examples
31+
developerExamples.register({
32+
appId: 'reportingExample',
33+
title: 'Reporting integration',
34+
description: 'Demonstrate how to put an Export button on a page and generate reports.',
35+
});
36+
}
37+
38+
public start() {}
39+
40+
public stop() {}
41+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { DeveloperExamplesSetup } from '../../../../examples/developer_examples/public';
2+
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
3+
import { ReportingStart } from '../../../plugins/reporting/public';
4+
5+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
6+
export interface PluginSetup {}
7+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
8+
export interface PluginStart {}
9+
10+
export interface SetupDeps {
11+
developerExamples: DeveloperExamplesSetup;
12+
}
13+
export interface StartDeps {
14+
navigation: NavigationPublicPluginStart;
15+
reporting: ReportingStart;
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "./target"
5+
},
6+
"include": [
7+
"index.ts",
8+
"public/**/*.ts",
9+
"public/**/*.tsx",
10+
"server/**/*.ts",
11+
"common/**/*.ts",
12+
"../../../typings/**/*",
13+
],
14+
"exclude": [],
15+
"references": [
16+
{ "path": "../../../src/core/tsconfig.json" }
17+
]
18+
}
19+

0 commit comments

Comments
 (0)