[Reporting] Make "ScreenCapturePanel" shareable for Canvas (#100623)

* use ScreenCapturePanel component in Canvas

* use smaller state object

* add comment about canvas-specific shared component

* fix example

* fix toast error

* fix i18n

* fix data-test-subj

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Tim Sullivan 2021-06-09 17:08:33 -07:00 committed by GitHub
parent 274bbdc628
commit 3559d375b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1755 additions and 622 deletions

View file

@ -6,8 +6,9 @@
*/
import {
EuiButton,
EuiCard,
EuiCode,
EuiContextMenu,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
@ -17,7 +18,7 @@ import {
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiPanel,
EuiPopover,
EuiText,
EuiTitle,
} from '@elastic/eui';
@ -50,7 +51,19 @@ export const ReportingExampleApp = ({
reporting,
screenshotMode,
}: ReportingExampleAppDeps) => {
const { getDefaultLayoutSelectors, ReportingAPIClient } = reporting;
const { getDefaultLayoutSelectors } = reporting;
// Context Menu
const [isPopoverOpen, setPopover] = useState(false);
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
// Async Logos
const [logos, setLogos] = useState<string[]>([]);
useEffect(() => {
@ -61,7 +74,7 @@ export const ReportingExampleApp = ({
});
});
const getPDFJobParams = (): JobParamsPDF => {
const getPDFJobParamsDefault = (): JobParamsPDF => {
return {
layout: {
id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
@ -73,7 +86,40 @@ export const ReportingExampleApp = ({
};
};
// Render the application DOM.
const panels = [
{ id: 0, items: [{ name: 'PDF Reports', icon: 'document', panel: 1 }] },
{
id: 1,
initialFocusedItemIndex: 1,
title: 'PDF Reports',
items: [
{ name: 'No Layout Option', icon: 'document', panel: 2 },
{ name: 'Canvas Layout Option', icon: 'canvasApp', panel: 3 },
],
},
{
id: 2,
title: 'No Layout Option',
content: (
<reporting.components.ReportingPanelPDF
getJobParams={getPDFJobParamsDefault}
onClose={closePopover}
/>
),
},
{
id: 3,
title: 'Canvas Layout Option',
content: (
<reporting.components.ReportingPanelPDF
layoutOption="canvas"
getJobParams={getPDFJobParamsDefault}
onClose={closePopover}
/>
),
},
];
return (
<Router basename={basename}>
<I18nProvider>
@ -87,34 +133,21 @@ export const ReportingExampleApp = ({
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
<p>
Use the <EuiCode>ReportingStart.components.ScreenCapturePanel</EuiCode>{' '}
component to add the Reporting panel to your page.
</p>
<p>Example of a Sharing menu using components from Reporting</p>
<EuiPopover
id="contextMenuExample"
button={<EuiButton onClick={onButtonClick}>Share</EuiButton>}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
<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) => (

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { BOLD_MD_TOKEN, CANVAS, HTML, JSON, KIBANA, PDF, POST, URL, ZIP } from './constants';
import { BOLD_MD_TOKEN, CANVAS, HTML, JSON, PDF, URL, ZIP } from './constants';
export const ComponentStrings = {
AddEmbeddableFlyout: {
@ -1418,95 +1418,10 @@ export const ComponentStrings = {
URL,
},
}),
getCopyReportingConfigMessage: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage', {
defaultMessage: 'Copied reporting configuration to clipboard',
}),
getCopyShareConfigMessage: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', {
defaultMessage: 'Copied share markup to clipboard',
}),
getExportPDFErrorTitle: (workpadName: string) =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage', {
defaultMessage: "Failed to create {PDF} for '{workpadName}'",
values: {
PDF,
workpadName,
},
}),
getExportPDFMessage: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFMessage', {
defaultMessage: 'Exporting {PDF}. You can track the progress in Management.',
values: {
PDF,
},
}),
getExportPDFTitle: (workpadName: string) =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFTitle', {
defaultMessage: "{PDF} export of workpad '{workpadName}'",
values: {
PDF,
workpadName,
},
}),
getPDFFullPageLayoutHelpText: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.FullPageLayoutHelpText', {
defaultMessage: 'Remove borders and footer logo',
}),
getPDFFullPageLayoutLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.FullPageLayoutLabel', {
defaultMessage: 'Full page layout',
}),
getPDFPanelAdvancedOptionsLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelAdvancedOptionsLabel', {
defaultMessage: 'Advanced options',
}),
getPDFPanelCopyAriaLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel', {
defaultMessage:
'Alternatively, you can generate a {PDF} from a script or with Watcher by using this {URL}. Press Enter to copy the {URL} to clipboard.',
values: {
PDF,
URL,
},
}),
getPDFPanelCopyButtonLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel', {
defaultMessage: 'Copy {POST} {URL}',
values: {
POST,
URL,
},
}),
getPDFPanelCopyDescription: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription', {
defaultMessage:
'Alternatively, copy this {POST} {URL} to call generation from outside {KIBANA} or from Watcher.',
values: {
POST,
KIBANA,
URL,
},
}),
getPDFPanelGenerateButtonLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel', {
defaultMessage: 'Generate {PDF}',
values: {
PDF,
},
}),
getPDFPanelGenerateDescription: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription', {
defaultMessage:
'{PDF}s can take a minute or two to generate based on the size of your workpad.',
values: {
PDF,
},
}),
getPDFPanelOptionsLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelOptionsLabel', {
defaultMessage: 'Options',
}),
getShareableZipErrorTitle: (workpadName: string) =>
i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', {
defaultMessage:

View file

@ -29,7 +29,6 @@
"kibanaUtils",
"lens",
"maps",
"reporting",
"savedObjects",
"visualizations"
]

View file

@ -1,6 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/WorkpadHeader/ShareMenu default 1`] = `
exports[`Storyshots components/WorkpadHeader/ShareMenu minimal 1`] = `
<div>
<div
className="euiPopover euiPopover--anchorDownLeft"
>
<div
className="euiPopover__anchor"
>
<button
aria-label="Share this workpad"
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
data-test-subj="shareTopNavButton"
disabled={false}
onClick={[Function]}
type="button"
>
<span
className="euiButtonContent euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Share
</span>
</span>
</button>
</div>
</div>
</div>
`;
exports[`Storyshots components/WorkpadHeader/ShareMenu with Reporting 1`] = `
<div>
<div
className="euiPopover euiPopover--anchorDownLeft"

View file

@ -1,36 +0,0 @@
/*
* 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 { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import React from 'react';
import { PDFPanel } from '../pdf_panel';
storiesOf('components/WorkpadHeader/ShareMenu/PDFPanel', module)
.addParameters({
info: {
inline: true,
styles: {
infoBody: {
margin: 20,
},
infoStory: {
margin: '20px 30px',
width: '290px',
},
},
},
})
.add('default', () => (
<div className="euiPanel">
<PDFPanel
getPdfURL={() => 'PDF URL String'}
onCopy={action('onCopy')}
onExport={action('onExport')}
/>
</div>
));

View file

@ -5,19 +5,34 @@
* 2.0.
*/
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { platformService } from '../../../../services/stubs/platform';
import { reportingService } from '../../../../services/stubs/reporting';
import { ShareMenu } from '../share_menu.component';
storiesOf('components/WorkpadHeader/ShareMenu', module).add('default', () => (
storiesOf('components/WorkpadHeader/ShareMenu', module).add('minimal', () => (
<ShareMenu
includeReporting={true}
onCopy={action('onCopy')}
onExport={action('onExport')}
getExportUrl={(type: string) => {
action(`getExportUrl('${type}')`);
return type;
sharingData={{
workpad: { id: 'coolworkpad', name: 'Workpad of Cool', height: 10, width: 7 },
pageCount: 11,
}}
sharingServices={{ basePath: platformService.getBasePathInterface() }}
onExport={action('onExport')}
/>
));
storiesOf('components/WorkpadHeader/ShareMenu', module).add('with Reporting', () => (
<ShareMenu
sharingData={{
workpad: { id: 'coolworkpad', name: 'Workpad of Cool', height: 10, width: 7 },
pageCount: 11,
}}
sharingServices={{
basePath: platformService.getBasePathInterface(),
reporting: reportingService.start,
}}
onExport={action('onExport')}
/>
));

View file

@ -1,98 +0,0 @@
/*
* 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 React, { useState } from 'react';
import {
EuiAccordion,
EuiButton,
EuiFormRow,
EuiHorizontalRule,
EuiSpacer,
EuiSwitch,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { Clipboard } from '../../clipboard';
import { LayoutType } from './utils';
import { ComponentStrings } from '../../../../i18n/components';
const { WorkpadHeaderShareMenu: strings } = ComponentStrings;
interface Props {
/** Retrieve URL that will invoke PDF Report generation. */
getPdfURL: (layout: LayoutType) => string;
/** Handler to invoke when the PDF is exported */
onExport: (layout: LayoutType) => void;
/** Handler to invoke when the URL is copied to the clipboard. */
onCopy: () => void;
}
/**
* A panel displayed in the Export Menu with options in which to generate PDF Reports.
*/
export const PDFPanel = ({ getPdfURL, onExport, onCopy }: Props) => {
const [reportLayout, setReportLayout] = useState<LayoutType>('preserve_layout');
return (
<div className="canvasShareMenu__panelContent">
<EuiText size="s">
<p>{strings.getPDFPanelGenerateDescription()}</p>
</EuiText>
<EuiSpacer size="s" />
<EuiTitle size="xxs">
<h6>{strings.getPDFPanelOptionsLabel()}</h6>
</EuiTitle>
<EuiSpacer size="s" />
<EuiFormRow helpText={strings.getPDFFullPageLayoutHelpText()}>
<EuiSwitch
label={strings.getPDFFullPageLayoutLabel()}
checked={reportLayout === 'canvas'}
onChange={() =>
reportLayout === 'canvas'
? setReportLayout('preserve_layout')
: setReportLayout('canvas')
}
data-test-subj="reportModeToggle"
/>
</EuiFormRow>
<EuiButton
fill
onClick={() => onExport(reportLayout)}
size="s"
style={{ width: '100%' }}
data-test-subj="generateReportButton"
>
{strings.getPDFPanelGenerateButtonLabel()}
</EuiButton>
<EuiHorizontalRule
margin="s"
style={{ width: 'auto', marginLeft: '-16px', marginRight: '-16px' }}
/>
<EuiAccordion
id="advanced-options"
buttonContent={strings.getPDFPanelAdvancedOptionsLabel()}
paddingSize="none"
>
<EuiSpacer size="s" />
<EuiText size="s">
<p>{strings.getPDFPanelCopyDescription()}</p>
</EuiText>
<EuiSpacer size="s" />
<Clipboard content={getPdfURL(reportLayout)} onCopy={onCopy}>
<EuiButton
iconType="copy"
size="s"
style={{ width: '100%' }}
aria-label={strings.getPDFPanelCopyAriaLabel()}
>
{strings.getPDFPanelCopyButtonLabel()}
</EuiButton>
</Clipboard>
</EuiAccordion>
</div>
);
};

View file

@ -5,47 +5,47 @@
* 2.0.
*/
import React, { FunctionComponent, useState } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui';
import { IBasePath } from 'kibana/public';
import PropTypes from 'prop-types';
import React, { FunctionComponent, useState } from 'react';
import { ReportingStart } from '../../../../../reporting/public';
import { ComponentStrings } from '../../../../i18n/components';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { Popover, ClosePopoverFn } from '../../popover';
import { PDFPanel } from './pdf_panel';
import { ClosePopoverFn, Popover } from '../../popover';
import { ShareWebsiteFlyout } from './flyout';
import { LayoutType } from './utils';
import { CanvasWorkpadSharingData, getPdfJobParams } from './utils';
const { WorkpadHeaderShareMenu: strings } = ComponentStrings;
type CopyTypes = 'pdf' | 'reportingConfig';
type ExportTypes = 'pdf' | 'json';
type ExportUrlTypes = 'pdf';
type CloseTypes = 'share';
export type OnCopyFn = (type: CopyTypes) => void;
export type OnExportFn = (type: ExportTypes, layout?: LayoutType) => void;
export type OnExportFn = (type: ExportTypes) => void;
export type OnCloseFn = (type: CloseTypes) => void;
export type GetExportUrlFn = (type: ExportUrlTypes, layout: LayoutType) => string;
export interface Props {
/** Flag to include the Reporting option only if Reporting is enabled */
includeReporting: boolean;
/** Handler to invoke when an export URL is copied to the clipboard. */
onCopy: OnCopyFn;
/** Canvas workpad to export as PDF **/
sharingData: CanvasWorkpadSharingData;
sharingServices: {
/** BasePath dependency **/
basePath: IBasePath;
/** Reporting dependency **/
reporting?: ReportingStart;
};
/** Handler to invoke when an end product is exported. */
onExport: OnExportFn;
/** Handler to retrive an export URL based on the type of export requested. */
getExportUrl: GetExportUrlFn;
}
/**
* The Menu for Exporting a Workpad from Canvas.
*/
export const ShareMenu: FunctionComponent<Props> = ({
includeReporting,
onCopy,
sharingData,
sharingServices: services,
onExport,
getExportUrl,
}) => {
const [showFlyout, setShowFlyout] = useState(false);
@ -53,22 +53,6 @@ export const ShareMenu: FunctionComponent<Props> = ({
setShowFlyout(false);
};
const getPDFPanel = (closePopover: ClosePopoverFn) => {
return (
<PDFPanel
getPdfURL={(layoutType: LayoutType) => getExportUrl('pdf', layoutType)}
onExport={(layoutType) => {
onExport('pdf', layoutType);
closePopover();
}}
onCopy={() => {
onCopy('pdf');
closePopover();
}}
/>
);
};
const getPanelTree = (closePopover: ClosePopoverFn) => ({
id: 0,
items: [
@ -80,14 +64,20 @@ export const ShareMenu: FunctionComponent<Props> = ({
closePopover();
},
},
includeReporting
services.reporting != null
? {
name: strings.getShareDownloadPDFTitle(),
icon: 'document',
panel: {
id: 1,
title: strings.getShareDownloadPDFTitle(),
content: getPDFPanel(closePopover),
content: (
<services.reporting.components.ReportingPanelPDF
getJobParams={() => getPdfJobParams(sharingData, services.basePath)}
layoutOption="canvas"
onClose={closePopover}
/>
),
},
'data-test-subj': 'sharePanel-PDFReports',
}
@ -132,8 +122,5 @@ export const ShareMenu: FunctionComponent<Props> = ({
};
ShareMenu.propTypes = {
includeReporting: PropTypes.bool.isRequired,
onCopy: PropTypes.func.isRequired,
onExport: PropTypes.func.isRequired,
getExportUrl: PropTypes.func.isRequired,
};

View file

@ -7,16 +7,12 @@
import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
import { jobCompletionNotifications } from '../../../../../../plugins/reporting/public';
import { getWorkpad, getPages } from '../../../state/selectors/workpad';
import { getWindow } from '../../../lib/get_window';
import { downloadWorkpad } from '../../../lib/download_workpad';
import { ShareMenu as Component, Props as ComponentProps } from './share_menu.component';
import { getPdfUrl, createPdf } from './utils';
import { State, CanvasWorkpad } from '../../../../types';
import { withServices, WithServicesProps } from '../../../services';
import { ComponentStrings } from '../../../../i18n';
import { CanvasWorkpad, State } from '../../../../types';
import { downloadWorkpad } from '../../../lib/download_workpad';
import { withServices, WithServicesProps } from '../../../services';
import { getPages, getWorkpad } from '../../../state/selectors/workpad';
import { Props as ComponentProps, ShareMenu as Component } from './share_menu.component';
const { WorkpadHeaderShareMenu: strings } = ComponentStrings;
@ -25,17 +21,6 @@ const mapStateToProps = (state: State) => ({
pageCount: getPages(state).length,
});
const getAbsoluteUrl = (path: string) => {
const { location } = getWindow();
if (!location) {
return path;
} // fallback for mocked window object
const { protocol, hostname, port } = location;
return `${protocol}//${hostname}:${port}${path}`;
};
interface Props {
workpad: CanvasWorkpad;
pageCount: number;
@ -45,63 +30,28 @@ export const ShareMenu = compose<ComponentProps, {}>(
connect(mapStateToProps),
withServices,
withProps(
({ workpad, pageCount, services }: Props & WithServicesProps): ComponentProps => ({
includeReporting: services.reporting.includeReporting(),
getExportUrl: (type, layout) => {
if (type === 'pdf') {
const pdfUrl = getPdfUrl(
workpad,
layout,
{ pageCount },
services.platform.getBasePathInterface()
);
return getAbsoluteUrl(pdfUrl);
}
({ workpad, pageCount, services }: Props & WithServicesProps): ComponentProps => {
const {
platform,
reporting: { start: reporting },
} = services;
throw new Error(strings.getUnknownExportErrorMessage(type));
},
onCopy: (type) => {
switch (type) {
case 'pdf':
services.notify.info(strings.getCopyPDFMessage());
break;
case 'reportingConfig':
services.notify.info(strings.getCopyReportingConfigMessage());
break;
default:
throw new Error(strings.getUnknownExportErrorMessage(type));
}
},
onExport: (type, layout) => {
switch (type) {
case 'pdf':
return createPdf(
workpad,
layout || 'preserve_layout',
{ pageCount },
services.platform.getBasePathInterface()
)
.then(({ data }: { data: { job: { id: string } } }) => {
services.notify.info(strings.getExportPDFMessage(), {
title: strings.getExportPDFTitle(workpad.name),
});
// register the job so a completion notification shows up when it's ready
jobCompletionNotifications.add(data.job.id);
})
.catch((err: Error) => {
services.notify.error(err, {
title: strings.getExportPDFErrorTitle(workpad.name),
'data-test-subj': 'queueReportError',
});
});
case 'json':
downloadWorkpad(workpad.id);
return;
default:
throw new Error(strings.getUnknownExportErrorMessage(type));
}
},
})
return {
sharingServices: { basePath: platform.getBasePathInterface(), reporting },
sharingData: { workpad, pageCount },
onExport: (type) => {
switch (type) {
case 'pdf':
// notifications are automatically handled by the Reporting plugin
break;
case 'json':
downloadWorkpad(workpad.id);
return;
default:
throw new Error(strings.getUnknownExportErrorMessage(type));
}
},
};
}
)
)(Component);

View file

@ -7,9 +7,8 @@
jest.mock('../../../../common/lib/fetch');
import { getPdfUrl, createPdf, LayoutType } from './utils';
import { getPdfJobParams } from './utils';
import { workpads } from '../../../../__fixtures__/workpads';
import { fetch } from '../../../../common/lib/fetch';
import { IBasePath } from 'kibana/public';
const basePath = ({
@ -17,33 +16,36 @@ const basePath = ({
get: () => 'basepath/s/spacey',
serverBasePath: `basepath`,
} as unknown) as IBasePath;
const workpad = workpads[0];
const workpadSharingData = { workpad: workpads[0], pageCount: 12 };
test('getPdfUrl returns the correct url for canvas layout', () => {
['canvas', 'preserve_layout'].forEach((layout) => {
const url = getPdfUrl(workpad, layout as LayoutType, { pageCount: 2 }, basePath);
expect(url).toMatchInlineSnapshot(
`"basepath/s/spacey//api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:0,width:0),id:${layout}),objectType:'canvas%20workpad',relativeUrls:!(%2Fs%2Fspacey%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F1,%2Fs%2Fspacey%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F2),title:'base%20workpad')"`
);
});
});
test('createPdf posts to create the pdf with canvas layout', () => {
['canvas', 'preserve_layout'].forEach((layout, index) => {
createPdf(workpad, layout as LayoutType, { pageCount: 2 }, basePath);
expect(fetch.post).toBeCalled();
const args = (fetch.post as jest.MockedFunction<typeof fetch.post>).mock.calls[index];
expect(args[0]).toMatchInlineSnapshot(
`"basepath/s/spacey//api/reporting/generate/printablePdf"`
);
expect(args[1]).toMatchInlineSnapshot(`
Object {
"jobParams": "(browserTimezone:America/New_York,layout:(dimensions:(height:0,width:0),id:${layout}),objectType:'canvas workpad',relativeUrls:!(/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/1,/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/2),title:'base workpad')",
}
`);
});
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,
"width": 0,
},
"id": "canvas",
},
"objectType": "canvas workpad",
"relativeUrls": Array [
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/1",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/2",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/3",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/4",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/5",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/6",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/7",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/8",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/9",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/10",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/11",
"/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/12",
],
"title": "base workpad",
}
`);
});

View file

@ -5,33 +5,24 @@
* 2.0.
*/
import rison from 'rison-node';
import { IBasePath } from 'kibana/public';
import moment from 'moment-timezone';
import { fetch } from '../../../../common/lib/fetch';
import rison from 'rison-node';
import { BaseParams } from '../../../../../reporting/common/types';
import { CanvasWorkpad } from '../../../../types';
import { url } from '../../../../../../../src/plugins/kibana_utils/public';
interface PageCount {
export interface CanvasWorkpadSharingData {
workpad: Pick<CanvasWorkpad, 'id' | 'name' | 'height' | 'width'>;
pageCount: number;
}
export type LayoutType = 'canvas' | 'preserve_layout';
// TODO: get the correct type from Reporting plugin
type JobParamsPDF = BaseParams & { relativeUrls: string[] };
type Arguments = [CanvasWorkpad, LayoutType, PageCount, IBasePath];
interface PdfUrlData {
createPdfUri: string;
createPdfPayload: { jobParams: string };
}
function getPdfUrlParts(
{ id, name: title, width, height }: CanvasWorkpad,
layoutType: LayoutType,
{ pageCount }: PageCount,
export function getPdfJobParams(
{ workpad: { id, name: title, width, height }, pageCount }: CanvasWorkpadSharingData,
basePath: IBasePath
): PdfUrlData {
const reportingEntry = basePath.prepend('/api/reporting/generate');
): JobParamsPDF {
const urlPrefix = basePath.get().replace(basePath.serverBasePath, ''); // for Spaces prefix, which is included in basePath.get()
const canvasEntry = `${urlPrefix}/app/canvas#`;
@ -51,34 +42,14 @@ function getPdfUrlParts(
workpadUrls.push(rison.encode(`${canvasEntry}/export/workpad/pdf/${id}/page/${i}`));
}
const jobParams = {
return {
browserTimezone: moment.tz.guess(),
layout: {
dimensions: { width, height },
id: layoutType,
id: 'canvas',
},
objectType: 'canvas workpad',
relativeUrls: workpadUrls,
title,
};
return {
createPdfUri: `${reportingEntry}/printablePdf`,
createPdfPayload: {
jobParams: rison.encode(jobParams),
},
};
}
export function getPdfUrl(...args: Arguments): string {
const urlParts = getPdfUrlParts(...args);
const param = (key: string, val: any) =>
url.encodeUriQuery(key, true) + (val === true ? '' : '=' + url.encodeUriQuery(val, true));
return `${urlParts.createPdfUri}?${param('jobParams', urlParts.createPdfPayload.jobParams)}`;
}
export function createPdf(...args: Arguments) {
const { createPdfUri, createPdfPayload } = getPdfUrlParts(...args);
return fetch.post(createPdfUri, createPdfPayload);
}

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import { ReportingStart } from '../../../reporting/public';
import { CanvasServiceFactory } from './';
export interface ReportingService {
includeReporting: () => boolean;
start?: ReportingStart;
}
export const reportingServiceFactory: CanvasServiceFactory<ReportingService> = (
@ -18,18 +19,24 @@ export const reportingServiceFactory: CanvasServiceFactory<ReportingService> = (
startPlugins
): ReportingService => {
const { reporting } = startPlugins;
const reportingEnabled = () => ({ start: reporting });
const reportingDisabled = () => ({ start: undefined });
if (!reporting) {
// Reporting is not enabled
return { includeReporting: () => false };
return reportingDisabled();
}
if (reporting.usesUiCapabilities()) {
// Canvas has declared Reporting as a subfeature with the `generatePdf` UI Capability
return {
includeReporting: () => coreStart.application.capabilities.canvas?.generatePdf === true,
};
if (coreStart.application.capabilities.canvas?.generatePdf === true) {
// Canvas has declared Reporting as a subfeature with the `generatePdf` UI Capability
return reportingEnabled();
} else {
return reportingDisabled();
}
}
// Reporting is enabled as an Elasticsearch feature (Legacy/Deprecated)
return { includeReporting: () => true };
// Legacy/Deprecated: Reporting is enabled as an Elasticsearch feature
return reportingEnabled();
};

View file

@ -8,5 +8,16 @@
import { ReportingService } from '../reporting';
export const reportingService: ReportingService = {
includeReporting: () => true,
start: {
usesUiCapabilities: () => true,
components: {
ReportingPanelPDF: () => (null as unknown) as JSX.Element,
},
getDefaultLayoutSelectors: () => ({
screenshot: 'stub',
renderComplete: 'stub',
itemsCountAttribute: 'stub',
timefilterDurationAttribute: 'stub',
}),
},
};

View file

@ -53,6 +53,7 @@ export const UI_SETTINGS_CSV_QUOTE_VALUES = 'csv:quoteValues';
export const UI_SETTINGS_DATEFORMAT_TZ = 'dateFormat:tz';
export const LAYOUT_TYPES = {
CANVAS: 'canvas',
PRESERVE_LAYOUT: 'preserve_layout',
PRINT: 'print',
};

View file

@ -11,3 +11,4 @@ export { getWarningFormulasToast } from './job_warning_formulas';
export { getWarningMaxSizeToast } from './job_warning_max_size';
export { getGeneralErrorToast } from './general_error';
export { ScreenCapturePanelContent } from './screen_capture_panel_content';
export { getSharedComponents } from './shared';

View file

@ -5,7 +5,17 @@
* 2.0.
*/
import { EuiButton, EuiCopy, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui';
import {
EuiAccordion,
EuiButton,
EuiCopy,
EuiForm,
EuiFormRow,
EuiHorizontalRule,
EuiSpacer,
EuiText,
} from '@elastic/eui';
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';
@ -20,14 +30,12 @@ export interface Props {
toasts: ToastsSetup;
reportType: string;
/**
* Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator.
*/
/** 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;
objectId?: string;
getJobParams: () => BaseParams;
options?: ReactElement<any>;
options?: ReactElement<any> | null;
isDirty?: boolean;
onClose?: () => void;
intl: InjectedIntl;
@ -110,50 +118,61 @@ class ReportingPanelContentUi extends Component<Props, State> {
);
}
const reportMsg = (
<FormattedMessage
id="xpack.reporting.panelContent.generationTimeDescription"
defaultMessage="{reportingType}s can take a minute or two to generate based upon the size of your {objectType}."
description="Here 'reportingType' can be 'PDF' or 'CSV'"
values={{
reportingType: this.prettyPrintReportingType(),
objectType: this.state.objectType,
}}
/>
);
return (
<EuiForm className="kbnShareContextMenu__finalPanel" data-test-subj="shareReportingForm">
<EuiText size="s">
<p>{reportMsg}</p>
<p>
<FormattedMessage
id="xpack.reporting.panelContent.generationTimeDescription"
defaultMessage="{reportingType}s can take a minute or two to generate based upon the size of your {objectType}."
description="Here 'reportingType' can be 'PDF' or 'CSV'"
values={{
reportingType: this.prettyPrintReportingType(),
objectType: this.state.objectType,
}}
/>
</p>
</EuiText>
<EuiSpacer size="s" />
{this.props.options}
{this.renderGenerateReportButton(false)}
<EuiSpacer size="s" />
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.reporting.panelContent.howToCallGenerationDescription"
defaultMessage="Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher."
/>
</p>
</EuiText>
<EuiSpacer size="s" />
<EuiHorizontalRule
margin="s"
style={{ width: 'auto', marginLeft: '-16px', marginRight: '-16px' }}
/>
<EuiCopy textToCopy={this.state.absoluteUrl} anchorClassName="eui-displayBlock">
{(copy) => (
<EuiButton fullWidth onClick={copy} size="s">
<EuiAccordion
id="advanced-options"
buttonContent={i18n.translate('xpack.reporting.panelContent.advancedOptions', {
defaultMessage: 'Advanced options',
})}
paddingSize="none"
>
<EuiSpacer size="s" />
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.reporting.panelContent.copyUrlButtonLabel"
defaultMessage="Copy POST URL"
id="xpack.reporting.panelContent.howToCallGenerationDescription"
defaultMessage="Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher."
/>
</EuiButton>
)}
</EuiCopy>
</p>
</EuiText>
<EuiSpacer size="s" />
<EuiCopy textToCopy={this.state.absoluteUrl} anchorClassName="eui-displayBlock">
{(copy) => (
<EuiButton fullWidth onClick={copy} size="s">
<FormattedMessage
id="xpack.reporting.panelContent.copyUrlButtonLabel"
defaultMessage="Copy POST URL"
/>
</EuiButton>
)}
</EuiCopy>
</EuiAccordion>
</EuiForm>
);
}
@ -247,44 +266,12 @@ class ReportingPanelContentUi extends Component<Props, State> {
}
})
.catch((error: any) => {
if (error.message === 'not exportable') {
return this.props.toasts.addWarning({
title: intl.formatMessage(
{
id: 'xpack.reporting.panelContent.whatCanBeExportedWarningTitle',
defaultMessage: 'Only saved {objectType} can be exported',
},
{ objectType: this.state.objectType }
),
text: toMountPoint(
<FormattedMessage
id="xpack.reporting.panelContent.whatCanBeExportedWarningDescription"
defaultMessage="Please save your work first"
/>
),
});
}
const defaultMessage =
error?.res?.status === 403 ? (
<FormattedMessage
id="xpack.reporting.panelContent.noPermissionToGenerateReportDescription"
defaultMessage="You don't have permission to generate this report."
/>
) : (
<FormattedMessage
id="xpack.reporting.panelContent.notification.cantReachServerDescription"
defaultMessage="Can't reach the server. Please try again."
/>
);
this.props.toasts.addDanger({
this.props.toasts.addError(error, {
title: intl.formatMessage({
id: 'xpack.reporting.panelContent.notification.reportingErrorTitle',
defaultMessage: 'Reporting error',
defaultMessage: 'Failed to create report',
}),
text: toMountPoint(error.message || defaultMessage),
'data-test-subj': 'queueReportError',
toastMessage: error.body.message,
});
});
};

View file

@ -0,0 +1,74 @@
/*
* 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 { mount } from 'enzyme';
import React from 'react';
import { IntlProvider } from 'react-intl';
import { coreMock } from '../../../../../src/core/public/mocks';
import { BaseParams } from '../../common/types';
import { ReportingAPIClient } from '../lib/reporting_api_client';
import { ScreenCapturePanelContent } from './screen_capture_panel_content';
const getJobParamsDefault: () => BaseParams = () => ({
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)}
toasts={coreSetup.notifications.toasts}
getJobParams={getJobParamsDefault}
/>
</IntlProvider>
);
expect(component.find('EuiForm')).toMatchSnapshot();
expect(component.text()).not.toMatch('Full page layout');
expect(component.text()).not.toMatch('Optimize for printing');
});
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)}
toasts={coreSetup.notifications.toasts}
getJobParams={getJobParamsDefault}
/>
</IntlProvider>
);
expect(component.find('EuiForm')).toMatchSnapshot();
expect(component.text()).toMatch('Full page layout');
});
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)}
toasts={coreSetup.notifications.toasts}
getJobParams={getJobParamsDefault}
/>
</IntlProvider>
);
expect(component.find('EuiForm')).toMatchSnapshot();
expect(component.text()).toMatch('Optimize for printing');
});

View file

@ -5,11 +5,13 @@
* 2.0.
*/
import { EuiSpacer, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
import moment from 'moment';
import React, { Component } from 'react';
import { ToastsSetup } from 'src/core/public';
import { BaseParams } from '../../common/types';
import { getDefaultLayoutSelectors } from '../../common';
import { BaseParams, LayoutParams } from '../../common/types';
import { ReportingAPIClient } from '../lib/reporting_api_client';
import { ReportingPanelContent } from './reporting_panel_content';
@ -17,33 +19,33 @@ export interface Props {
apiClient: ReportingAPIClient;
toasts: ToastsSetup;
reportType: string;
layoutOption?: 'canvas' | 'print';
objectId?: string;
getJobParams: () => BaseParams;
requiresSavedState: boolean;
isDirty?: boolean;
onClose?: () => void;
}
interface State {
isPreserveLayoutSupported: boolean;
usePrintLayout: boolean;
useCanvasLayout: boolean;
}
export class ScreenCapturePanelContent extends Component<Props, State> {
constructor(props: Props) {
super(props);
const { objectType } = props.getJobParams();
const isPreserveLayoutSupported = props.reportType !== 'png' && objectType !== 'visualization';
this.state = {
isPreserveLayoutSupported,
usePrintLayout: false,
useCanvasLayout: false,
};
}
public render() {
return (
<ReportingPanelContent
requiresSavedState
requiresSavedState={this.props.requiresSavedState}
apiClient={this.props.apiClient}
toasts={this.props.toasts}
reportType={this.props.reportType}
@ -58,9 +60,16 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
}
private renderOptions = () => {
if (this.state.isPreserveLayoutSupported) {
if (this.props.layoutOption === 'print') {
return (
<Fragment>
<EuiFormRow
helpText={
<FormattedMessage
id="xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText"
defaultMessage="Uses multiple pages, showing at most 2 visualizations per page"
/>
}
>
<EuiSwitch
label={
<FormattedMessage
@ -72,43 +81,83 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
onChange={this.handlePrintLayoutChange}
data-test-subj="usePrintLayout"
/>
<EuiSpacer size="s" />
</Fragment>
</EuiFormRow>
);
}
return (
<Fragment>
<EuiSpacer size="s" />
</Fragment>
);
if (this.props.layoutOption === 'canvas') {
return (
<EuiFormRow
helpText={
<FormattedMessage
id="xpack.reporting.screenCapturePanelContent.canvasLayoutHelpText"
defaultMessage="Remove borders and footer logo"
/>
}
>
<EuiSwitch
label={
<FormattedMessage
id="xpack.reporting.screenCapturePanelContent.canvasLayoutLabel"
defaultMessage="Full page layout"
/>
}
checked={this.state.useCanvasLayout}
onChange={this.handleCanvasLayoutChange}
data-test-subj="reportModeToggle"
/>
</EuiFormRow>
);
}
return null;
};
private handlePrintLayoutChange = (evt: EuiSwitchEvent) => {
this.setState({ usePrintLayout: evt.target.checked });
this.setState({ usePrintLayout: evt.target.checked, useCanvasLayout: false });
};
private getLayout = () => {
if (this.state.usePrintLayout) {
return { id: 'print' };
private handleCanvasLayoutChange = (evt: EuiSwitchEvent) => {
this.setState({ useCanvasLayout: evt.target.checked, usePrintLayout: false });
};
private getLayout = (): Required<LayoutParams> => {
const { layout: outerLayout } = this.props.getJobParams();
let dimensions = outerLayout?.dimensions;
if (!dimensions) {
const el = document.querySelector('[data-shared-items-container]');
const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 };
dimensions = { height, width };
}
const el = document.querySelector('[data-shared-items-container]');
const bounds = el ? el.getBoundingClientRect() : { height: 768, width: 1024 };
let selectors = outerLayout?.selectors;
if (!selectors) {
selectors = getDefaultLayoutSelectors();
}
return {
id: this.props.reportType === 'png' ? 'png' : 'preserve_layout',
dimensions: {
height: bounds.height,
width: bounds.width,
},
};
if (this.state.usePrintLayout) {
return { id: 'print', dimensions, selectors };
}
if (this.state.useCanvasLayout) {
return { id: 'canvas', dimensions, selectors };
}
return { id: 'preserve_layout', dimensions, selectors };
};
private getJobParams = () => {
private getJobParams = (): Required<BaseParams> => {
const outerParams = this.props.getJobParams();
let browserTimezone = outerParams.browserTimezone;
if (!browserTimezone) {
browserTimezone = moment.tz.guess();
}
return {
...this.props.getJobParams(),
layout: this.getLayout(),
browserTimezone,
};
};
}

View file

@ -0,0 +1,41 @@
/*
* 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 { CoreSetup } from 'kibana/public';
import React from 'react';
import { ReportingAPIClient } from '../..';
import { PDF_REPORT_TYPE } from '../../../common/constants';
import type { Props as PanelPropsScreenCapture } from '../screen_capture_panel_content';
import { ScreenCapturePanelContent } from '../screen_capture_panel_content_lazy';
interface IncludeOnCloseFn {
onClose: () => void;
}
type PropsPDF = Pick<PanelPropsScreenCapture, 'getJobParams' | 'layoutOption'> & IncludeOnCloseFn;
/*
* As of 7.14, the only shared component is a PDF report that is suited for Canvas integration.
* 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) {
return {
ReportingPanelPDF(props: PropsPDF) {
return (
<ScreenCapturePanelContent
layoutOption={props.layoutOption}
requiresSavedState={false}
reportType={PDF_REPORT_TYPE}
apiClient={new ReportingAPIClient(core.http)}
toasts={core.notifications.toasts}
{...props}
/>
);
},
};
}

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { getSharedComponents } from './get_shared_components';

View file

@ -7,24 +7,20 @@
import { PluginInitializerContext } from 'src/core/public';
import { getDefaultLayoutSelectors } from '../common';
import { ScreenCapturePanelContent } from './components/screen_capture_panel_content';
import * as jobCompletionNotifications from './lib/job_completion_notifications';
import { getSharedComponents } from './components';
import { ReportingAPIClient } from './lib/reporting_api_client';
import { ReportingPublicPlugin } from './plugin';
export interface ReportingSetup {
components: {
ScreenCapturePanel: typeof ScreenCapturePanelContent;
};
getDefaultLayoutSelectors: typeof getDefaultLayoutSelectors;
ReportingAPIClient: typeof ReportingAPIClient;
usesUiCapabilities: () => boolean;
components: ReturnType<typeof getSharedComponents>;
}
export type ReportingStart = ReportingSetup;
export { constants, getDefaultLayoutSelectors } from '../common';
export { ReportingAPIClient, ReportingPublicPlugin as Plugin, jobCompletionNotifications };
export { ReportingAPIClient, ReportingPublicPlugin as Plugin };
export function plugin(initializerContext: PluginInitializerContext) {
return new ReportingPublicPlugin(initializerContext);

View file

@ -0,0 +1,27 @@
/*
* 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 { coreMock } from 'src/core/public/mocks';
import { ReportingSetup } from '.';
import { getDefaultLayoutSelectors } from '../common';
import { getSharedComponents } from './components/shared';
type Setup = jest.Mocked<ReportingSetup>;
const createSetupContract = (): Setup => {
const coreSetup = coreMock.createSetup();
return {
getDefaultLayoutSelectors: jest.fn().mockImplementation(getDefaultLayoutSelectors),
usesUiCapabilities: jest.fn().mockImplementation(() => true),
components: getSharedComponents(coreSetup),
};
};
export const reportingPluginMock = {
createSetupContract,
createStartContract: createSetupContract,
};

View file

@ -29,10 +29,7 @@ import { constants, getDefaultLayoutSelectors } from '../common';
import { durationToNumber } from '../common/schema_utils';
import { JobId, JobSummarySet } from '../common/types';
import { ReportingSetup, ReportingStart } from './';
import {
getGeneralErrorToast,
ScreenCapturePanelContent as ScreenCapturePanel,
} from './components';
import { getGeneralErrorToast, getSharedComponents } from './components';
import { ReportingAPIClient } from './lib/reporting_api_client';
import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler';
import { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action';
@ -86,7 +83,6 @@ export class ReportingPublicPlugin
ReportingPublicPluginSetupDendencies,
ReportingPublicPluginStartDendencies
> {
private readonly contract: ReportingStart;
private readonly stop$ = new Rx.ReplaySubject(1);
private readonly title = i18n.translate('xpack.reporting.management.reportingTitle', {
defaultMessage: 'Reporting',
@ -95,21 +91,30 @@ export class ReportingPublicPlugin
defaultMessage: 'Reporting',
});
private config: ClientConfigType;
private contract?: ReportingSetup;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<ClientConfigType>();
}
this.contract = {
ReportingAPIClient,
components: { ScreenCapturePanel },
getDefaultLayoutSelectors,
usesUiCapabilities: () => this.config.roles?.enabled === false,
};
private getContract(core?: CoreSetup) {
if (core) {
this.contract = {
getDefaultLayoutSelectors,
usesUiCapabilities: () => this.config.roles?.enabled === false,
components: getSharedComponents(core),
};
}
if (!this.contract) {
throw new Error(`Setup error in Reporting plugin!`);
}
return this.contract;
}
public setup(core: CoreSetup, setupDeps: ReportingPublicPluginSetupDendencies) {
const { http, notifications, getStartServices, uiSettings } = core;
const { toasts } = notifications;
const { http, getStartServices, uiSettings } = core;
const {
home,
management,
@ -163,6 +168,9 @@ export class ReportingPublicPlugin
new ReportingCsvPanelAction({ core, startServices$, license$, usesUiCapabilities })
);
const reportingStart = this.getContract(core);
const { toasts } = core.notifications;
share.register(
ReportingCsvShareProvider({
apiClient,
@ -173,6 +181,7 @@ export class ReportingPublicPlugin
usesUiCapabilities,
})
);
share.register(
reportingScreenshotShareProvider({
apiClient,
@ -184,7 +193,7 @@ export class ReportingPublicPlugin
})
);
return this.contract;
return reportingStart;
}
public start(core: CoreStart) {
@ -203,7 +212,7 @@ export class ReportingPublicPlugin
)
.subscribe();
return this.contract;
return this.getContract();
}
public stop() {

View file

@ -65,13 +65,7 @@ export const ReportingCsvShareProvider = ({
? moment.tz.guess()
: uiSettings.get('dateFormat:tz');
const getShareMenuItems = ({
objectType,
objectId,
sharingData,
onClose,
isDirty,
}: ShareContext) => {
const getShareMenuItems = ({ objectType, objectId, sharingData, onClose }: ShareContext) => {
if ('search' !== objectType) {
return [];
}
@ -114,7 +108,6 @@ export const ReportingCsvShareProvider = ({
layoutId={undefined}
objectId={objectId}
getJobParams={getJobParams}
isDirty={isDirty}
onClose={onClose}
/>
),

View file

@ -11,7 +11,7 @@ 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 { ShareContext } from '../../../../../src/plugins/share/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';
@ -167,6 +167,7 @@ export const reportingScreenshotShareProvider = ({
toasts={toasts}
reportType="png"
objectId={objectId}
requiresSavedState={true}
getJobParams={getPngJobParams({
shareableUrl,
apiClient,
@ -203,6 +204,8 @@ export const reportingScreenshotShareProvider = ({
toasts={toasts}
reportType="printablePdf"
objectId={objectId}
requiresSavedState={true}
layoutOption={objectType === 'dashboard' ? 'print' : undefined}
getJobParams={getPdfJobParams({
shareableUrl,
apiClient,

View file

@ -7003,20 +7003,7 @@
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "エレメントを更新",
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "データを更新",
"xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF}生成{URL}がクリップボードにコピーされました。",
"xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage": "レポート構成がクリップボードにコピーされました",
"xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "共有マークアップがクリップボードにコピーされました",
"xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage": "'{workpadName}'の{PDF}を作成できませんでした",
"xpack.canvas.workpadHeaderShareMenu.exportPDFMessage": "{PDF}をエクスポートしています。管理で進捗を確認できます。",
"xpack.canvas.workpadHeaderShareMenu.exportPDFTitle": "ワークパッド '{workpadName}' の {PDF} エクスポート",
"xpack.canvas.workpadHeaderShareMenu.FullPageLayoutHelpText": "枠線とフッターロゴを削除",
"xpack.canvas.workpadHeaderShareMenu.FullPageLayoutLabel": "全ページレイアウト",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelAdvancedOptionsLabel": "高度なオプション",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel": "この {URL} を使用してスクリプトから、または Watcher で {PDF} を生成することもできます。{URL}をクリップボードにコピーするにはEnterキーを押してください。",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel": "{POST} {URL}をコピー",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription": "{POST} {URL}をコピーして{KIBANA}外またはWatcherから生成を実行することもできます。",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel": "{PDF}を生成",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription": "ワークパッドのサイズによって、{PDF} の生成には数分かかる場合があります。",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelOptionsLabel": "オプション",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "{JSON} をダウンロード",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF}レポート",
"xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel": "共有",
@ -17879,14 +17866,10 @@
"xpack.reporting.panelContent.generateButtonLabel": "{reportingType} を生成",
"xpack.reporting.panelContent.generationTimeDescription": "{objectType} のサイズによって、{reportingType} の作成には数分かかる場合があります。",
"xpack.reporting.panelContent.howToCallGenerationDescription": "POST URL をコピーして Kibana 外または ウォッチャー から生成を実行することもできます。",
"xpack.reporting.panelContent.noPermissionToGenerateReportDescription": "このレポートを生成するパーミッションがありません。",
"xpack.reporting.panelContent.notification.cantReachServerDescription": "サーバーと通信できません。再試行してください。",
"xpack.reporting.panelContent.notification.reportingErrorTitle": "レポートエラー",
"xpack.reporting.panelContent.saveWorkDescription": "レポートの生成前に変更内容を保存してください。",
"xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription": "{path}で進捗状況を追跡",
"xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle": "{objectType} のレポートキュー",
"xpack.reporting.panelContent.whatCanBeExportedWarningDescription": "初めに変更内容を保存してください",
"xpack.reporting.panelContent.whatCanBeExportedWarningTitle": "保存された {objectType} のみエクスポートできます",
"xpack.reporting.pdfFooterImageDescription": "PDFのフッターに使用するカスタム画像です",
"xpack.reporting.pdfFooterImageLabel": "PDFフッター画像",
"xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportMessage": "レポートには、スプレッドシートアプリケーションで式と解釈される可能性のある文字が含まれています。",

View file

@ -7051,20 +7051,7 @@
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "刷新元素",
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "刷新数据",
"xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF} 生成 {URL} 已复制到您的剪贴板。",
"xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage": "已将报告配置复制到剪贴板",
"xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "已将共享标记复制到剪贴板",
"xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage": "无法为“{workpadName}”创建 {PDF}",
"xpack.canvas.workpadHeaderShareMenu.exportPDFMessage": "正在导出 {PDF}。可以在“管理”中跟踪进度。",
"xpack.canvas.workpadHeaderShareMenu.exportPDFTitle": "Workpad“{workpadName}”的 {PDF} 导出",
"xpack.canvas.workpadHeaderShareMenu.FullPageLayoutHelpText": "删除边框和页脚徽标",
"xpack.canvas.workpadHeaderShareMenu.FullPageLayoutLabel": "全页面布局",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelAdvancedOptionsLabel": "高级选项",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel": "或者,也可以从脚本或使用此 {URL} 通过 Watcher 生成 {PDF}。按 Enter 键可将 {URL} 复制到剪贴板。",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel": "复制 {POST} {URL}",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription": "或者,复制此 {POST} {URL} 以从 {KIBANA} 外部或从 Watcher 调用生成。",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel": "生成 {PDF}",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription": "{PDF} 可能会花费一两分钟生成,具体取决于 Workpad 的大小。",
"xpack.canvas.workpadHeaderShareMenu.pdfPanelOptionsLabel": "选项",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "下载为 {JSON}",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF} 报告",
"xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel": "共享",
@ -18120,14 +18107,10 @@
"xpack.reporting.panelContent.generateButtonLabel": "生成 {reportingType}",
"xpack.reporting.panelContent.generationTimeDescription": "{reportingType} 可能会花费 1 或 2 分钟生成,取决于 {objectType} 的大小。",
"xpack.reporting.panelContent.howToCallGenerationDescription": "或者,复制此 POST URL 以从 Kibana 外部或从 Watcher 调用生成。",
"xpack.reporting.panelContent.noPermissionToGenerateReportDescription": "您无权生成此报告。",
"xpack.reporting.panelContent.notification.cantReachServerDescription": "无法访问服务器。请重试。",
"xpack.reporting.panelContent.notification.reportingErrorTitle": "报告错误",
"xpack.reporting.panelContent.saveWorkDescription": "请在生成报告之前保存您的工作。",
"xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription": "在 {path} 中跟踪其进度",
"xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle": "已为 {objectType} 排队报告",
"xpack.reporting.panelContent.whatCanBeExportedWarningDescription": "请先保存您的工作",
"xpack.reporting.panelContent.whatCanBeExportedWarningTitle": "只会导出保存的 {objectType}",
"xpack.reporting.pdfFooterImageDescription": "要在 PDF 的页脚中使用的定制图像",
"xpack.reporting.pdfFooterImageLabel": "PDF 页脚图像",
"xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportMessage": "报告包含电子表格应用程序可解释为公式的字符。",

View file

@ -91,7 +91,7 @@ export class ReportingPageObject extends FtrService {
}
async getQueueReportError() {
return await this.testSubjects.exists('queueReportError');
return await this.testSubjects.exists('errorToastMessage');
}
async getGenerateReportButton() {