Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AppServices/Examples] Add the example for Reporting integration #82091

Merged
merged 8 commits into from
Dec 29, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions x-pack/examples/reporting_example/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
root: true,
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
rules: {
'@kbn/eslint/require-license-header': 'off',
},
};
6 changes: 6 additions & 0 deletions x-pack/examples/reporting_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Example Reporting integration!
==============================

Use this example code to understand how to add a "Generate Report" button to a Kibana page.
tsullivan marked this conversation as resolved.
Show resolved Hide resolved

Coming soon: control which users are able to use the button based on Feature Controls.
2 changes: 2 additions & 0 deletions x-pack/examples/reporting_example/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const PLUGIN_ID = 'reportingExample';
export const PLUGIN_NAME = 'reportingExample';
9 changes: 9 additions & 0 deletions x-pack/examples/reporting_example/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "reportingExample",
"version": "1.0.0",
"kibanaVersion": "kibana",
"server": false,
"ui": true,
"optionalPlugins": [],
"requiredPlugins": ["reporting", "developerExamples", "navigation"]
}
18 changes: 18 additions & 0 deletions x-pack/examples/reporting_example/public/application.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
import { StartDeps } from './types';
import { ReportingExampleApp } from './components/app';

export const renderApp = (
coreStart: CoreStart,
startDeps: StartDeps,
{ appBasePath, element }: AppMountParameters
) => {
ReactDOM.render(
<ReportingExampleApp basename={appBasePath} {...coreStart} {...startDeps} />,
element
);

return () => ReactDOM.unmountComponentAtNode(element);
};
130 changes: 130 additions & 0 deletions x-pack/examples/reporting_example/public/components/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
EuiCard,
EuiCode,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiIcon,
EuiPage,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiPanel,
EuiText,
EuiTitle,
} from '@elastic/eui';
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 { 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 {
basename: string;
notifications: CoreStart['notifications'];
http: CoreStart['http'];
navigation: NavigationPublicPluginStart;
reporting: ReportingStart;
}

const sourceLogos = ['Beats', 'Cloud', 'Logging', 'Kibana'];

export const ReportingExampleApp = ({
basename,
notifications,
http,
reporting,
}: ReportingExampleAppDeps) => {
const { getDefaultLayoutSelectors, ReportingAPIClient } = reporting;
const [logos, setLogos] = useState<string[]>([]);

useEffect(() => {
Rx.timer(2200)
.pipe(takeWhile(() => logos.length < sourceLogos.length))
.subscribe(() => {
setLogos([...sourceLogos.slice(0, logos.length + 1)]);
});
});

const getPDFJobParams = (): JobParamsPDF => {
return {
layout: {
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
selectors: getDefaultLayoutSelectors(),
},
relativeUrls: ['/app/reportingExample#/intended-visualization'],
objectType: 'develeloperExample',
title: 'Reporting Developer Example',
};
};

// Render the application DOM.
return (
<Router basename={basename}>
<I18nProvider>
<EuiPage>
<EuiPageBody>
<EuiPageHeader>
<EuiTitle size="l">
<h1>Reporting Example</h1>
</EuiTitle>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
<p>
Use the <EuiCode>ReportingStart.components.ScreenCapturePanel</EuiCode>{' '}
component to add the Reporting panel to your page.
</p>

<EuiHorizontalRule />

<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiPanel>
<reporting.components.ScreenCapturePanel
apiClient={new ReportingAPIClient(http)}
toasts={notifications.toasts}
reportType={constants.PDF_REPORT_TYPE}
getJobParams={getPDFJobParams}
objectId="Visualization:Id:ToEnsure:Visualization:IsSaved"
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>

<EuiHorizontalRule />

<p>
The logos below are in a <EuiCode>data-shared-items-container</EuiCode> element
for Reporting.
</p>

<div data-shared-items-container data-shared-items-count="4">
<EuiFlexGroup gutterSize="l">
{logos.map((item, index) => (
<EuiFlexItem key={index} data-shared-item>
<EuiCard
icon={<EuiIcon size="xxl" type={`logo${item}`} />}
title={`Elastic ${item}`}
description="Example of a card's description. Stick to one or two sentences."
onClick={() => {}}
/>
</EuiFlexItem>
))}
</EuiFlexGroup>
</div>
</EuiText>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>
</Router>
);
};
6 changes: 6 additions & 0 deletions x-pack/examples/reporting_example/public/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ReportingExamplePlugin } from './plugin';

export function plugin() {
return new ReportingExamplePlugin();
}
export { PluginSetup, PluginStart } from './types';
41 changes: 41 additions & 0 deletions x-pack/examples/reporting_example/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
} from '../../../../src/core/public';
import { PLUGIN_NAME } from '../common';
import { SetupDeps, StartDeps } from './types';

export class ReportingExamplePlugin implements Plugin<void, void, {}, {}> {
public setup(core: CoreSetup, { developerExamples, ...depsSetup }: SetupDeps): void {
core.application.register({
id: 'reportingExample',
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
title: PLUGIN_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');
const [coreStart, depsStart] = (await core.getStartServices()) as [
CoreStart,
StartDeps,
unknown
];
// Render the application
return renderApp(coreStart, { ...depsSetup, ...depsStart }, params);
},
});

// Show the app in Developer Examples
developerExamples.register({
appId: 'reportingExample',
title: 'Reporting integration',
description: 'Demonstrate how to put an Export button on a page and generate reports.',
});
}

public start() {}

public stop() {}
}
16 changes: 16 additions & 0 deletions x-pack/examples/reporting_example/public/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DeveloperExamplesSetup } from '../../../../examples/developer_examples/public';
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
import { ReportingStart } from '../../../plugins/reporting/public';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginStart {}

export interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
}
export interface StartDeps {
navigation: NavigationPublicPluginStart;
reporting: ReportingStart;
}
19 changes: 19 additions & 0 deletions x-pack/examples/reporting_example/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target"
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"common/**/*.ts",
"../../../typings/**/*",
],
"exclude": [],
"references": [
{ "path": "../../../src/core/tsconfig.json" }
]
}

1 change: 1 addition & 0 deletions x-pack/plugins/reporting/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { LayoutSelectorDictionary } from './types';

export * as constants from './constants';
tsullivan marked this conversation as resolved.
Show resolved Hide resolved
export { CancellationToken } from './cancellation_token';
export { Poller } from './poller';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ export interface Props {
reportType: string;
layoutId: string | undefined;
objectId?: string;
objectType: string;
getJobParams: () => BaseParams;
options?: ReactElement<any>;
isDirty: boolean;
onClose: () => void;
isDirty?: boolean;
onClose?: () => void;
intl: InjectedIntl;
}

interface State {
isStale: boolean;
absoluteUrl: string;
layoutId: string;
objectType: string;
}

class ReportingPanelContentUi extends Component<Props, State> {
Expand All @@ -40,10 +40,14 @@ class ReportingPanelContentUi extends Component<Props, State> {
constructor(props: Props) {
super(props);

// Get objectType from job params
const { objectType } = props.getJobParams();

this.state = {
isStale: false,
absoluteUrl: this.getAbsoluteReportGenerationUrl(props),
layoutId: '',
objectType,
};
}

Expand Down Expand Up @@ -104,7 +108,7 @@ class ReportingPanelContentUi extends Component<Props, State> {
description="Here 'reportingType' can be 'PDF' or 'CSV'"
values={{
reportingType: this.prettyPrintReportingType(),
objectType: this.props.objectType,
objectType: this.state.objectType,
}}
/>
);
Expand Down Expand Up @@ -209,7 +213,7 @@ class ReportingPanelContentUi extends Component<Props, State> {
id: 'xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle',
defaultMessage: 'Queued report for {objectType}',
},
{ objectType: this.props.objectType }
{ objectType: this.state.objectType }
),
text: toMountPoint(
<FormattedMessage
Expand All @@ -219,7 +223,9 @@ class ReportingPanelContentUi extends Component<Props, State> {
),
'data-test-subj': 'queueReportSuccess',
});
this.props.onClose();
if (this.props.onClose) {
this.props.onClose();
}
})
.catch((error: any) => {
if (error.message === 'not exportable') {
Expand All @@ -229,7 +235,7 @@ class ReportingPanelContentUi extends Component<Props, State> {
id: 'xpack.reporting.panelContent.whatCanBeExportedWarningTitle',
defaultMessage: 'Only saved {objectType} can be exported',
},
{ objectType: this.props.objectType }
{ objectType: this.state.objectType }
),
text: toMountPoint(
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ export interface Props {
toasts: ToastsSetup;
reportType: string;
objectId?: string;
objectType: string;
getJobParams: () => BaseParams;
isDirty: boolean;
onClose: () => void;
isDirty?: boolean;
onClose?: () => void;
}

interface State {
Expand All @@ -32,8 +31,8 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
constructor(props: Props) {
super(props);

const isPreserveLayoutSupported =
props.reportType !== 'png' && props.objectType !== 'visualization';
const { objectType } = props.getJobParams();
const isPreserveLayoutSupported = props.reportType !== 'png' && objectType !== 'visualization';
this.state = {
isPreserveLayoutSupported,
usePrintLayout: false,
Expand All @@ -47,7 +46,6 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
toasts={this.props.toasts}
reportType={this.props.reportType}
layoutId={this.getLayout().id}
objectType={this.props.objectType}
objectId={this.props.objectId}
getJobParams={this.getJobParams}
options={this.renderOptions()}
Expand Down
Loading