From d3a728bb4e75a04158566b29af334990ecfe28a0 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 22 Feb 2024 10:41:49 -0700 Subject: [PATCH 01/69] initial commit --- package.json | 1 + packages/kbn-share-modal/ tsconfig.json | 17 +++ packages/kbn-share-modal/README.md | 3 + packages/kbn-share-modal/index.ts | 9 ++ packages/kbn-share-modal/jest.config.js | 13 +++ packages/kbn-share-modal/kibana.jsonc | 5 + packages/kbn-share-modal/modal.tsx | 100 ++++++++++++++++++ packages/kbn-share-modal/package.json | 6 ++ .../impl/src/share_modal.stories.tsx | 7 ++ packages/shared-ux/share_modal/mocks/index.ts | 10 ++ .../share_modal/mocks/src/storybook.ts | 55 ++++++++++ .../shared-ux/share_modal/types/index.d.ts | 23 ++++ .../share/public/components/embed_modal.tsx | 61 +++++++++++ .../share/public/components/link_modal.tsx | 31 ++++++ .../share/public/components/share_tabs.tsx | 85 ++++++++++++--- tsconfig.base.json | 2 + yarn.lock | 4 + 17 files changed, 420 insertions(+), 12 deletions(-) create mode 100644 packages/kbn-share-modal/ tsconfig.json create mode 100644 packages/kbn-share-modal/README.md create mode 100644 packages/kbn-share-modal/index.ts create mode 100644 packages/kbn-share-modal/jest.config.js create mode 100644 packages/kbn-share-modal/kibana.jsonc create mode 100644 packages/kbn-share-modal/modal.tsx create mode 100644 packages/kbn-share-modal/package.json create mode 100644 packages/shared-ux/share_modal/impl/src/share_modal.stories.tsx create mode 100644 packages/shared-ux/share_modal/mocks/index.ts create mode 100644 packages/shared-ux/share_modal/mocks/src/storybook.ts create mode 100644 packages/shared-ux/share_modal/types/index.d.ts create mode 100644 src/plugins/share/public/components/embed_modal.tsx create mode 100644 src/plugins/share/public/components/link_modal.tsx diff --git a/package.json b/package.json index 5bb6a0cd6664d4..4837152e6ebf50 100644 --- a/package.json +++ b/package.json @@ -739,6 +739,7 @@ "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", "@kbn/set-map": "link:packages/kbn-set-map", "@kbn/share-examples-plugin": "link:examples/share_examples", + "@kbn/share-modal": "link:packages/kbn-share-modal", "@kbn/share-plugin": "link:src/plugins/share", "@kbn/shared-svg": "link:packages/kbn-shared-svg", "@kbn/shared-ux-avatar-solution": "link:packages/shared-ux/avatar/solution", diff --git a/packages/kbn-share-modal/ tsconfig.json b/packages/kbn-share-modal/ tsconfig.json new file mode 100644 index 00000000000000..305f5614dd392f --- /dev/null +++ b/packages/kbn-share-modal/ tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] + } \ No newline at end of file diff --git a/packages/kbn-share-modal/README.md b/packages/kbn-share-modal/README.md new file mode 100644 index 00000000000000..5a83a3c9ee268e --- /dev/null +++ b/packages/kbn-share-modal/README.md @@ -0,0 +1,3 @@ +# @kbn/share-modal + +Empty package generated by @kbn/generate \ No newline at end of file diff --git a/packages/kbn-share-modal/index.ts b/packages/kbn-share-modal/index.ts new file mode 100644 index 00000000000000..df4bd9b38c37b1 --- /dev/null +++ b/packages/kbn-share-modal/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export { ShareModal } from './modal'; +export type { ModalProps } from './modal'; diff --git a/packages/kbn-share-modal/jest.config.js b/packages/kbn-share-modal/jest.config.js new file mode 100644 index 00000000000000..bc33a0512a4009 --- /dev/null +++ b/packages/kbn-share-modal/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-share-modal'], +}; diff --git a/packages/kbn-share-modal/kibana.jsonc b/packages/kbn-share-modal/kibana.jsonc new file mode 100644 index 00000000000000..e4ec66f8d76ed4 --- /dev/null +++ b/packages/kbn-share-modal/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/share-modal", + "owner": "@elastic/appex-sharedux" + } \ No newline at end of file diff --git a/packages/kbn-share-modal/modal.tsx b/packages/kbn-share-modal/modal.tsx new file mode 100644 index 00000000000000..d562c76632df3d --- /dev/null +++ b/packages/kbn-share-modal/modal.tsx @@ -0,0 +1,100 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useMemo, ReactElement } from 'react'; +import { + EuiButton, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiTab, + EuiTabs, + EuiForm, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export interface ModalProps { + objectType: string; + onClose: () => void; + tabs: Array<{ id: string; name: string; content: ReactElement }>; + modalBodyDescriptions: Array<{ id: string; description: any }>; +} + +/** + * + */ + +export const ShareModal = ({ onClose, objectType, tabs, modalBodyDescriptions }: ModalProps) => { + const [selectedTabId, setSelectedTabId] = useState('link'); + + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const selectedTabContent = useMemo(() => { + return tabs.find((obj) => obj.id === selectedTabId)?.content; + }, [selectedTabId, tabs]); + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + > + {tab.name} + + )); + }; + + const renderButtons = (id: string) => { + if (id === 'link') { + return ( + + + + ); + } else if (id === 'embed') { + return ( + + + + ); + } else { + return ( + + + + ); + } + }; + + const renderTitle = () => ( + + ); + + return ( + // @ts-ignore + + + {renderTitle()} + + + {renderTabs()} + {selectedTabContent} + + {renderButtons(selectedTabId)} + + ); +}; diff --git a/packages/kbn-share-modal/package.json b/packages/kbn-share-modal/package.json new file mode 100644 index 00000000000000..701442f5ecbe0d --- /dev/null +++ b/packages/kbn-share-modal/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/share-modal", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" + } \ No newline at end of file diff --git a/packages/shared-ux/share_modal/impl/src/share_modal.stories.tsx b/packages/shared-ux/share_modal/impl/src/share_modal.stories.tsx new file mode 100644 index 00000000000000..5c2d5b68ae2e03 --- /dev/null +++ b/packages/shared-ux/share_modal/impl/src/share_modal.stories.tsx @@ -0,0 +1,7 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ diff --git a/packages/shared-ux/share_modal/mocks/index.ts b/packages/shared-ux/share_modal/mocks/index.ts new file mode 100644 index 00000000000000..dd836d1d0ed578 --- /dev/null +++ b/packages/shared-ux/share_modal/mocks/index.ts @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { StorybookMock as ShareModalStorybookMock } from './src/storybook'; +export type { Params as ShareModalStorybookParams } from './src/storybook'; diff --git a/packages/shared-ux/share_modal/mocks/src/storybook.ts b/packages/shared-ux/share_modal/mocks/src/storybook.ts new file mode 100644 index 00000000000000..489bc8e3d10e7f --- /dev/null +++ b/packages/shared-ux/share_modal/mocks/src/storybook.ts @@ -0,0 +1,55 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AbstractStorybookMock, ArgumentParams } from '@kbn/shared-ux-storybook-mock'; +import { ModalProps as ShareModalProps } from '@kbn/share-modal'; + +import { ArgTypes } from '@storybook/react'; +import { ShareModalStorybookMock } from '..'; + +type PropArguments = Pick; +type Arguments = PropArguments; + +/** + * Storybook parameters provided from the controls addon. + */ +export type Params = Record; + +const redirectMock = new ShareModalStorybookMock(); + +export class StorybookMock extends AbstractStorybookMock { + serviceArguments: ArgTypes<{}>; + getServices(params?: ArgumentParams | undefined): {} { + throw new Error('Method not implemented.'); + } + propArguments = { + objectType: { + control: { + type: 'text', + }, + defaultValue: '', + }, + modalBodyDescriptions: { + control: { + type: 'text', + }, + defaultValue: '', + }, + tabs: {}, + }; + + dependencies = []; + + getProps(params?: Params): ShareModalProps { + return { + modalBodyDescriptions: this.getArgumentValue('description', params), + objectType: this.getArgumentValue('objectType', params), + tabs: this.getArgumentValue('tabs', params), + }; + } +} diff --git a/packages/shared-ux/share_modal/types/index.d.ts b/packages/shared-ux/share_modal/types/index.d.ts new file mode 100644 index 00000000000000..427989f9e90c42 --- /dev/null +++ b/packages/shared-ux/share_modal/types/index.d.ts @@ -0,0 +1,23 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ModalProps } from '@kbn/share-modal'; +import { ReactElement } from 'react'; + +/** + * Props for the `ShareModal` pure component. + */ +export type ShareModalComponentProps = Partial< + Pick +> & { + objectType: string; + modalBodyDescription: string; + tabs: Array<{ id: string; name: string; content: ReactElement }>; +}; + +export type ShareModalProps = ShareModalComponentProps; diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx new file mode 100644 index 00000000000000..aa9f7de9b3ccde --- /dev/null +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -0,0 +1,61 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiForm, EuiFormHelpText, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { UrlParamExtension } from '../types'; + +interface EmbedProps { + urlParamExtensions?: UrlParamExtension[]; +} + +export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { + const renderUrlParamExtensions = () => { + if (!urlParamExtensions) { + return; + } + + const setParamValue = + (paramName: string) => + (values: { [queryParam: string]: boolean } = {}): void => { + // const stateUpdate = { + // urlParams: { + // ...this.state.urlParams, + // [paramName]: { + // ...values, + // }, + // }, + // }; + // this.setState(stateUpdate, this.state.useShortUrl ? this.createShortUrl : this.setUrl); + }; + + return ( + + {urlParamExtensions.map(({ paramName, component: UrlParamComponent }) => ( + + + + ))} + + ); + }; + return ( + + + + + + + {renderUrlParamExtensions()} + + ); +}; diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx new file mode 100644 index 00000000000000..b83d150459ce7f --- /dev/null +++ b/src/plugins/share/public/components/link_modal.tsx @@ -0,0 +1,31 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiForm, EuiFormHelpText, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +interface LinkProps { + objectType: string; +} + +export const LinkModal = ({ objectType }: LinkProps) => { + return ( + + + + + + + + ); +}; diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 9c28cf6dc72fc6..4e1491da0812a5 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -8,16 +8,13 @@ import { Capabilities } from '@kbn/core-capabilities-common'; import React from 'react'; -import { EuiModal } from '@elastic/eui'; +import { ShareModal } from '@kbn/share-modal'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; import { LocatorPublic, AnonymousAccessServiceContract } from '../../common'; import { ShareMenuItem, UrlParamExtension, BrowserUrlService } from '../types'; - -export interface ModalTabActionHandler { - id: string; - dataTestSubj: string; - formattedMessageId: string; - defaultMessage: string; -} +import { LinkModal } from './link_modal'; +import { EmbedModal } from './embed_modal'; export interface ShareContextTabProps { allowEmbed: boolean; @@ -40,10 +37,74 @@ export interface ShareContextTabProps { snapshotShareWarning?: string; objectTypeTitle?: string; disabledShareUrl?: boolean; - isDirty: boolean; - isEmbedded: boolean; } -export const ShareMenuTabs = ({ onClose }: ShareContextTabProps) => { - return {'placeholder'}; +// this file is intended to replace share_context_menu +export const ShareMenuTabs = ({ + allowEmbed, + shareMenuItems, + urlService, + onClose, + objectType, + embedUrlParamExtensions, +}: ShareContextTabProps) => { + const getTabs = () => { + const tabs = []; + + tabs.push({ + id: 'link', + name: i18n.translate('share.contextMenu.permalinksLabel', { + defaultMessage: 'Links', + }), + sortOrder: 0, + // do not break functional tests + 'data-test-subj': 'Permalinks', + content: , + }); + if (allowEmbed) { + tabs.push({ + id: 'embed', + name: i18n.translate('share.contextMenu.embedCodeLabel', { + defaultMessage: 'Embed', + }), + sortOrder: 1, + content: , + }); + } + + shareMenuItems.map((item) => { + console.log(item); + }); + return tabs; + }; + + const getModalBodyDescriptions = () => [ + { + id: 'dashboard-link', + description: ( + + ), + }, + { + id: 'dashboard-embed', + description: ( + + ), + }, + ]; + + return ( + + ); }; diff --git a/tsconfig.base.json b/tsconfig.base.json index 674ca428983e6a..0d7d6166603928 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1468,6 +1468,8 @@ "@kbn/set-map/*": ["packages/kbn-set-map/*"], "@kbn/share-examples-plugin": ["examples/share_examples"], "@kbn/share-examples-plugin/*": ["examples/share_examples/*"], + "@kbn/share-modal": ["packages/kbn-share-modal"], + "@kbn/share-modal/*": ["packages/kbn-share-modal/*"], "@kbn/share-plugin": ["src/plugins/share"], "@kbn/share-plugin/*": ["src/plugins/share/*"], "@kbn/shared-svg": ["packages/kbn-shared-svg"], diff --git a/yarn.lock b/yarn.lock index 53ff07f181e2b0..19a80eda83c2d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5983,6 +5983,10 @@ version "0.0.0" uid "" +"@kbn/share-modal@link:packages/kbn-share-modal": + version "0.0.0" + uid "" + "@kbn/share-plugin@link:src/plugins/share": version "0.0.0" uid "" From ea7ebabd00fc098ce072829d51e57ce50df306b4 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 22 Feb 2024 11:38:55 -0700 Subject: [PATCH 02/69] working link preview --- src/plugins/share/kibana.jsonc | 3 +- .../share/public/components/link_modal.tsx | 105 +++++++++++++++++- .../share/public/components/share_tabs.tsx | 4 +- 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/plugins/share/kibana.jsonc b/src/plugins/share/kibana.jsonc index 175264cab790b3..138326a9e9eb0a 100644 --- a/src/plugins/share/kibana.jsonc +++ b/src/plugins/share/kibana.jsonc @@ -7,6 +7,7 @@ "id": "share", "server": true, "browser": true, - "requiredBundles": ["kibanaReact", "kibanaUtils"], + "requiredBundles": [ + ] } } diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index b83d150459ce7f..883ebc4f36bcfc 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -6,15 +6,112 @@ * Side Public License, v 1. */ -import { EuiForm, EuiFormHelpText, EuiSpacer } from '@elastic/eui'; +import { EuiCodeBlock, EuiForm, EuiFormHelpText, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React from 'react'; +import React, { useState } from 'react'; +import { format as formatUrl, parse as parseUrl } from 'url'; interface LinkProps { objectType: string; + objectId: string; + isDirty: boolean; + isEmbedded: boolean; + shareableUrlForSavedObject?: string; + shareableUrl?: string; } -export const LinkModal = ({ objectType }: LinkProps) => { +export const LinkModal = ({ objectType, objectId, isDirty, isEmbedded, shareableUrl, shareableUrlForSavedObject }: LinkProps) => { + const [urlParams, setUrlParams] = useState(); + + + const isNotSaved = () => { + return objectId === undefined || objectId === '' || isDirty; + }; + + const makeUrlEmbeddable = (url: string): string => { + const embedParam = '?embed=true'; + const urlHasQueryString = url.indexOf('?') !== -1; + + if (urlHasQueryString) { + return url.replace('?', `${embedParam}&`); + } + + return `${url}${embedParam}`; + }; + + const getUrlParamExtensions = (url: string): string => { + return urlParams + ? Object.keys(urlParams).reduce((urlAccumulator, key) => { + const urlParam = urlParams[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, url) + : url; + }; + + const updateUrlParams = (url: string) => { + url = isEmbedded ? makeUrlEmbeddable(url) : url; + url = urlParams ? getUrlParamExtensions(url) : url; + + return url; + }; + + const getSavedObjectUrl = () => { + if (isNotSaved()) { + return; + } + + const url = getSnapshotUrl(true); + + const parsedUrl = parseUrl(url); + if (!parsedUrl || !parsedUrl.hash) { + return; + } + + // Get the application route, after the hash, and remove the #. + const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); + + const formattedUrl = formatUrl({ + protocol: parsedUrl.protocol, + auth: parsedUrl.auth, + host: parsedUrl.host, + pathname: parsedUrl.pathname, + hash: formatUrl({ + pathname: parsedAppUrl.pathname, + query: { + // Add global state to the URL so that the iframe doesn't just show the time range + // default. + _g: parsedAppUrl.query._g, + }, + }), + }); + return updateUrlParams(formattedUrl); + }; + + const getSnapshotUrl = (forSavedObject?: boolean) => { + let url = ''; + if (forSavedObject && shareableUrlForSavedObject) { + url = shareableUrlForSavedObject; + } + if (!url) { + url = shareableUrl || window.location.href; + } + return updateUrlParams(url); + }; + + const renderLink = () => { + if (objectType === 'lens') { + return getSavedObjectUrl(); + } else if (objectType === 'dashboard' || objectType === 'search') { + return getSnapshotUrl(); + } + }; return ( @@ -25,6 +122,8 @@ export const LinkModal = ({ objectType }: LinkProps) => { values={{ objectType }} /> + + {renderLink()} ); diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 4e1491da0812a5..f722af3264b78f 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -68,7 +68,9 @@ export const ShareMenuTabs = ({ defaultMessage: 'Embed', }), sortOrder: 1, - content: , + content: ( + + ), }); } From e450c707625ca4b41d1b80b28de898f8d87d7c01 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 22 Feb 2024 12:48:00 -0700 Subject: [PATCH 03/69] link --- .../share/public/components/link_modal.tsx | 43 +++++++++++++------ .../share/public/components/share_tabs.tsx | 21 +++++++-- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 883ebc4f36bcfc..9cb7217e20e120 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -13,17 +13,29 @@ import { format as formatUrl, parse as parseUrl } from 'url'; interface LinkProps { objectType: string; - objectId: string; + objectId?: string; isDirty: boolean; isEmbedded: boolean; - shareableUrlForSavedObject?: string; - shareableUrl?: string; + shareableUrlForSavedObject?: string; + shareableUrl?: string; } -export const LinkModal = ({ objectType, objectId, isDirty, isEmbedded, shareableUrl, shareableUrlForSavedObject }: LinkProps) => { - const [urlParams, setUrlParams] = useState(); - - +interface UrlParams { + [extensionName: string]: { + [queryParam: string]: boolean; + }; +} + +export const LinkModal = ({ + objectType, + objectId, + isDirty, + isEmbedded, + shareableUrl, + shareableUrlForSavedObject, +}: LinkProps) => { + const [urlParams] = useState(undefined); + const isNotSaved = () => { return objectId === undefined || objectId === '' || isDirty; }; @@ -59,7 +71,7 @@ export const LinkModal = ({ objectType, objectId, isDirty, isEmbedded, shareable url = isEmbedded ? makeUrlEmbeddable(url) : url; url = urlParams ? getUrlParamExtensions(url) : url; - return url; + return url.padEnd(0, '.'); }; const getSavedObjectUrl = () => { @@ -106,12 +118,17 @@ export const LinkModal = ({ objectType, objectId, isDirty, isEmbedded, shareable }; const renderLink = () => { - if (objectType === 'lens') { - return getSavedObjectUrl(); - } else if (objectType === 'dashboard' || objectType === 'search') { + if (objectType === 'dashboard' || objectType === 'search') { return getSnapshotUrl(); } + return getSavedObjectUrl(); }; + + const displayLink = () => { + const stringLength = renderLink()?.length; + return stringLength! > 40 ? renderLink()?.slice(0, 40).concat('...') : renderLink(); + }; + return ( @@ -122,8 +139,8 @@ export const LinkModal = ({ objectType, objectId, isDirty, isEmbedded, shareable values={{ objectType }} /> - - {renderLink()} + + renderLink()}>{displayLink()} ); diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index f722af3264b78f..2b21def1cdf235 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -37,6 +37,8 @@ export interface ShareContextTabProps { snapshotShareWarning?: string; objectTypeTitle?: string; disabledShareUrl?: boolean; + isDirty: boolean; + isEmbedded: boolean; } // this file is intended to replace share_context_menu @@ -47,6 +49,9 @@ export const ShareMenuTabs = ({ onClose, objectType, embedUrlParamExtensions, + objectId, + isDirty, + isEmbedded, }: ShareContextTabProps) => { const getTabs = () => { const tabs = []; @@ -59,7 +64,14 @@ export const ShareMenuTabs = ({ sortOrder: 0, // do not break functional tests 'data-test-subj': 'Permalinks', - content: , + content: ( + + ), }); if (allowEmbed) { tabs.push({ @@ -74,8 +86,11 @@ export const ShareMenuTabs = ({ }); } - shareMenuItems.map((item) => { - console.log(item); + shareMenuItems.map(({ shareMenuItem, panel }) => { + tabs.push({ + ...shareMenuItem, + id: panel.id, + }); }); return tabs; }; From 850cc41b6f862f8766dd59daec7365efb7bf2e9a Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 22 Feb 2024 14:18:45 -0700 Subject: [PATCH 04/69] tsconfig --- packages/kbn-share-modal/ tsconfig.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 packages/kbn-share-modal/ tsconfig.json diff --git a/packages/kbn-share-modal/ tsconfig.json b/packages/kbn-share-modal/ tsconfig.json deleted file mode 100644 index 305f5614dd392f..00000000000000 --- a/packages/kbn-share-modal/ tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node" - ] - }, - "include": [ - "**/*.ts" - ], - "exclude": [ - "target/**/*" - ], - "kbn_references": [] - } \ No newline at end of file From fd9e6222ff6388cebdb865a3791a94e4a2230ac1 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 22 Feb 2024 14:19:01 -0700 Subject: [PATCH 05/69] tsconfig --- packages/kbn-share-modal/tsconfig.json | 19 ++++++++++++++++ .../shared-ux/share_modal/impl/tsconfig.json | 22 +++++++++++++++++++ .../shared-ux/share_modal/mocks/tsconfig.json | 18 +++++++++++++++ .../shared-ux/share_modal/types/tsconfig.json | 18 +++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 packages/kbn-share-modal/tsconfig.json create mode 100644 packages/shared-ux/share_modal/impl/tsconfig.json create mode 100644 packages/shared-ux/share_modal/mocks/tsconfig.json create mode 100644 packages/shared-ux/share_modal/types/tsconfig.json diff --git a/packages/kbn-share-modal/tsconfig.json b/packages/kbn-share-modal/tsconfig.json new file mode 100644 index 00000000000000..ebdf23fd714de2 --- /dev/null +++ b/packages/kbn-share-modal/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + ], + }, + "include": [ + "**/*.ts", + ], + "kbn_references": [ + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/packages/shared-ux/share_modal/impl/tsconfig.json b/packages/shared-ux/share_modal/impl/tsconfig.json new file mode 100644 index 00000000000000..6be3891fed8a09 --- /dev/null +++ b/packages/shared-ux/share_modal/impl/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@kbn/ambient-ui-types", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "kbn_references": [ + "@kbn/shared-ux-share-modal-mocks", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/packages/shared-ux/share_modal/mocks/tsconfig.json b/packages/shared-ux/share_modal/mocks/tsconfig.json new file mode 100644 index 00000000000000..693d1ee9ce31e4 --- /dev/null +++ b/packages/shared-ux/share_modal/mocks/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/packages/shared-ux/share_modal/types/tsconfig.json b/packages/shared-ux/share_modal/types/tsconfig.json new file mode 100644 index 00000000000000..693d1ee9ce31e4 --- /dev/null +++ b/packages/shared-ux/share_modal/types/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*", + ] +} From 700e6a412a5774debd473a2858f4cdfcb0898b53 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 22 Feb 2024 15:33:31 -0700 Subject: [PATCH 06/69] copy link on click --- packages/kbn-share-modal/modal.tsx | 14 ++++++++++---- .../share/public/components/link_modal.tsx | 19 +++++++++++-------- .../share/public/components/share_tabs.tsx | 7 ++++++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/kbn-share-modal/modal.tsx b/packages/kbn-share-modal/modal.tsx index d562c76632df3d..0952b3a7fc7899 100644 --- a/packages/kbn-share-modal/modal.tsx +++ b/packages/kbn-share-modal/modal.tsx @@ -17,6 +17,7 @@ import { EuiTab, EuiTabs, EuiForm, + EuiCopy, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,13 +26,14 @@ export interface ModalProps { onClose: () => void; tabs: Array<{ id: string; name: string; content: ReactElement }>; modalBodyDescriptions: Array<{ id: string; description: any }>; + copyData: string; } /** * */ -export const ShareModal = ({ onClose, objectType, tabs, modalBodyDescriptions }: ModalProps) => { +export const ShareModal = ({ onClose, objectType, tabs, copyData }: ModalProps) => { const [selectedTabId, setSelectedTabId] = useState('link'); const onSelectedTabChanged = (id: string) => { @@ -57,9 +59,13 @@ export const ShareModal = ({ onClose, objectType, tabs, modalBodyDescriptions }: const renderButtons = (id: string) => { if (id === 'link') { return ( - - - + + {(copy) => ( + + + + )} + ); } else if (id === 'embed') { return ( diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 9cb7217e20e120..52d4e66b926334 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -8,7 +8,7 @@ import { EuiCodeBlock, EuiForm, EuiFormHelpText, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useState } from 'react'; +import React, { Dispatch, SetStateAction, useState } from 'react'; import { format as formatUrl, parse as parseUrl } from 'url'; interface LinkProps { @@ -18,6 +18,8 @@ interface LinkProps { isEmbedded: boolean; shareableUrlForSavedObject?: string; shareableUrl?: string; + onClose: () => void; + setCopyLinkData: Dispatch>; } interface UrlParams { @@ -33,6 +35,7 @@ export const LinkModal = ({ isEmbedded, shareableUrl, shareableUrlForSavedObject, + setCopyLinkData, }: LinkProps) => { const [urlParams] = useState(undefined); @@ -70,8 +73,8 @@ export const LinkModal = ({ const updateUrlParams = (url: string) => { url = isEmbedded ? makeUrlEmbeddable(url) : url; url = urlParams ? getUrlParamExtensions(url) : url; - - return url.padEnd(0, '.'); + setCopyLinkData(url); + return url; }; const getSavedObjectUrl = () => { @@ -124,10 +127,10 @@ export const LinkModal = ({ return getSavedObjectUrl(); }; - const displayLink = () => { - const stringLength = renderLink()?.length; - return stringLength! > 40 ? renderLink()?.slice(0, 40).concat('...') : renderLink(); - }; + // const displayLink = () => { + // const stringLength = renderLink()?.length; + // return stringLength! > 40 ? renderLink()?.slice(0, 40).concat('...') : renderLink(); + // }; return ( @@ -140,7 +143,7 @@ export const LinkModal = ({ /> - renderLink()}>{displayLink()} + {renderLink()} ); diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 2b21def1cdf235..814584030f70b6 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -7,7 +7,7 @@ */ import { Capabilities } from '@kbn/core-capabilities-common'; -import React from 'react'; +import React, { useState } from 'react'; import { ShareModal } from '@kbn/share-modal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -53,6 +53,8 @@ export const ShareMenuTabs = ({ isDirty, isEmbedded, }: ShareContextTabProps) => { + const [data, setData] = useState(''); + const getTabs = () => { const tabs = []; @@ -70,6 +72,8 @@ export const ShareMenuTabs = ({ objectId={objectId} isDirty={isDirty} isEmbedded={isEmbedded} + onClose={onClose} + setCopyLinkData={setData} /> ), }); @@ -122,6 +126,7 @@ export const ShareMenuTabs = ({ modalBodyDescriptions={getModalBodyDescriptions()} onClose={onClose} tabs={getTabs()} + copyData={data} /> ); }; From 0158da889bb4784f09cb0638491abe47d653a0bc Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 29 Feb 2024 17:37:40 +0100 Subject: [PATCH 07/69] wip --- packages/kbn-share-modal/modal.tsx | 45 +++---------- src/plugins/share/kibana.jsonc | 1 + .../share/public/components/link_modal.tsx | 63 ++++++++++++------- .../share/public/components/share_tabs.tsx | 45 +++++++++++-- 4 files changed, 89 insertions(+), 65 deletions(-) diff --git a/packages/kbn-share-modal/modal.tsx b/packages/kbn-share-modal/modal.tsx index 0952b3a7fc7899..72a239cf422801 100644 --- a/packages/kbn-share-modal/modal.tsx +++ b/packages/kbn-share-modal/modal.tsx @@ -8,42 +8,38 @@ import React, { useState, useMemo, ReactElement } from 'react'; import { - EuiButton, EuiModal, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody, - EuiModalFooter, EuiTab, EuiTabs, EuiForm, - EuiCopy, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; export interface ModalProps { objectType: string; onClose: () => void; - tabs: Array<{ id: string; name: string; content: ReactElement }>; + tabs: Array<{ id: string; name: string; content: ReactElement; sortOrder: number }>; modalBodyDescriptions: Array<{ id: string; description: any }>; - copyData: string; } /** * */ -export const ShareModal = ({ onClose, objectType, tabs, copyData }: ModalProps) => { +export const ShareModal = ({ onClose, objectType, tabs }: ModalProps) => { const [selectedTabId, setSelectedTabId] = useState('link'); + useMemo(() => { + return tabs.find(({ id }) => id === selectedTabId); + }, [selectedTabId, tabs]); + const onSelectedTabChanged = (id: string) => { setSelectedTabId(id); }; - const selectedTabContent = useMemo(() => { - return tabs.find((obj) => obj.id === selectedTabId)?.content; - }, [selectedTabId, tabs]); - const renderTabs = () => { return tabs.map((tab, index) => ( { - if (id === 'link') { - return ( - - {(copy) => ( - - - - )} - - ); - } else if (id === 'embed') { - return ( - - - - ); - } else { - return ( - - - - ); - } - }; - const renderTitle = () => ( {renderTabs()} - {selectedTabContent} + {tabs.find(({ id }) => id === selectedTabId)?.content} - {renderButtons(selectedTabId)} ); }; diff --git a/src/plugins/share/kibana.jsonc b/src/plugins/share/kibana.jsonc index 138326a9e9eb0a..a705a73709730c 100644 --- a/src/plugins/share/kibana.jsonc +++ b/src/plugins/share/kibana.jsonc @@ -8,6 +8,7 @@ "server": true, "browser": true, "requiredBundles": [ + "kibanaUtils" ] } } diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 52d4e66b926334..6a02627d557f7f 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -6,9 +6,17 @@ * Side Public License, v 1. */ -import { EuiCodeBlock, EuiForm, EuiFormHelpText, EuiSpacer } from '@elastic/eui'; +import { + EuiButton, + EuiCodeBlock, + EuiCopy, + EuiForm, + EuiFormHelpText, + EuiModalFooter, + EuiSpacer, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { Dispatch, SetStateAction, useState } from 'react'; +import React, { useState } from 'react'; import { format as formatUrl, parse as parseUrl } from 'url'; interface LinkProps { @@ -19,7 +27,7 @@ interface LinkProps { shareableUrlForSavedObject?: string; shareableUrl?: string; onClose: () => void; - setCopyLinkData: Dispatch>; + action: any; } interface UrlParams { @@ -35,7 +43,7 @@ export const LinkModal = ({ isEmbedded, shareableUrl, shareableUrlForSavedObject, - setCopyLinkData, + action, }: LinkProps) => { const [urlParams] = useState(undefined); @@ -73,7 +81,7 @@ export const LinkModal = ({ const updateUrlParams = (url: string) => { url = isEmbedded ? makeUrlEmbeddable(url) : url; url = urlParams ? getUrlParamExtensions(url) : url; - setCopyLinkData(url); + // setCopyLinkData(url); return url; }; @@ -127,24 +135,35 @@ export const LinkModal = ({ return getSavedObjectUrl(); }; - // const displayLink = () => { - // const stringLength = renderLink()?.length; - // return stringLength! > 40 ? renderLink()?.slice(0, 40).concat('...') : renderLink(); - // }; + const renderButtons = () => { + const { formattedMessageId, defaultMessage } = action; + return ( + + {(copy) => ( + + + + )} + + ); + }; return ( - - - - - - - {renderLink()} - - + <> + + + + + + + {renderLink()} + + + {renderButtons()} + ); }; diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 814584030f70b6..1d0d94b75c7871 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -7,7 +7,7 @@ */ import { Capabilities } from '@kbn/core-capabilities-common'; -import React, { useState } from 'react'; +import React, { useCallback } from 'react'; import { ShareModal } from '@kbn/share-modal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -16,6 +16,13 @@ import { ShareMenuItem, UrlParamExtension, BrowserUrlService } from '../types'; import { LinkModal } from './link_modal'; import { EmbedModal } from './embed_modal'; +export interface ModalTabActionHandler { + id: string; + dataTestSubj: string; + formattedMessageId: string; + defaultMessage: string; +} + export interface ShareContextTabProps { allowEmbed: boolean; allowShortUrl: boolean; @@ -53,7 +60,29 @@ export const ShareMenuTabs = ({ isDirty, isEmbedded, }: ShareContextTabProps) => { - const [data, setData] = useState(''); + const actionHandler = useCallback( + () => [ + { + id: 'link', + dataTestSubj: 'copyShareUrlButton', + formattedMessageId: 'share.link.copyLinkButton', + defaultMessage: 'Copy link', + }, + { + id: 'embed', + dataTestSubj: 'copyEmbedUrlButton', + formattedMessageId: 'share.link.copyEmbedCodeButton', + defaultMessage: 'Copy Embed', + }, + { + id: 'export', + dataTestSubj: 'generateExportButton', + formattedMessageId: 'share.link.generateExportButton', + defaultMessage: 'Generate export', + }, + ], + [] + ); const getTabs = () => { const tabs = []; @@ -66,6 +95,7 @@ export const ShareMenuTabs = ({ sortOrder: 0, // do not break functional tests 'data-test-subj': 'Permalinks', + action: actionHandler().filter(({ id }) => id === 'link')[0], content: ( id === 'link')[0]} /> ), }); @@ -84,8 +114,13 @@ export const ShareMenuTabs = ({ defaultMessage: 'Embed', }), sortOrder: 1, + action: actionHandler().filter(({ id }) => id === 'embed')[0], content: ( - + id === 'embed')[0]} + /> ), }); } @@ -94,6 +129,7 @@ export const ShareMenuTabs = ({ tabs.push({ ...shareMenuItem, id: panel.id, + actionHandler: actionHandler().filter(({ id }) => id === id)[0], }); }); return tabs; @@ -126,7 +162,6 @@ export const ShareMenuTabs = ({ modalBodyDescriptions={getModalBodyDescriptions()} onClose={onClose} tabs={getTabs()} - copyData={data} /> ); }; From a51d6c10425eab738b9eb86c0f304414678f9bd0 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 4 Mar 2024 11:58:25 -0700 Subject: [PATCH 08/69] working on embed modal hooks --- packages/kbn-share-modal/modal.tsx | 2 +- packages/kbn-share-modal/tsconfig.json | 2 +- .../share/public/components/embed_modal.tsx | 201 ++++++++++++++++-- .../share/public/components/link_modal.tsx | 2 +- 4 files changed, 192 insertions(+), 15 deletions(-) diff --git a/packages/kbn-share-modal/modal.tsx b/packages/kbn-share-modal/modal.tsx index 72a239cf422801..61a837505730e7 100644 --- a/packages/kbn-share-modal/modal.tsx +++ b/packages/kbn-share-modal/modal.tsx @@ -61,7 +61,7 @@ export const ShareModal = ({ onClose, objectType, tabs }: ModalProps) => { ); return ( - // @ts-ignore + // @ts-ignore css prop in EuiModal {renderTitle()} diff --git a/packages/kbn-share-modal/tsconfig.json b/packages/kbn-share-modal/tsconfig.json index ebdf23fd714de2..2b21731e92e416 100644 --- a/packages/kbn-share-modal/tsconfig.json +++ b/packages/kbn-share-modal/tsconfig.json @@ -9,7 +9,7 @@ ], }, "include": [ - "**/*.ts", + "**/*.ts*", ], "kbn_references": [ ], diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx index aa9f7de9b3ccde..965b42c8c543e9 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -6,33 +6,210 @@ * Side Public License, v 1. */ -import { EuiForm, EuiFormHelpText, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiCopy, EuiForm, EuiFormHelpText, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { format as formatUrl, parse as parseUrl } from 'url'; +import { AnonymousAccessState } from '../../common'; import { UrlParamExtension } from '../types'; interface EmbedProps { urlParamExtensions?: UrlParamExtension[]; } +interface UrlParams { + [extensionName: string]: { + [queryParam: string]: boolean; + }; +} + +export enum ExportUrlAsType { + EXPORT_URL_AS_SAVED_OBJECT = 'savedObject', + EXPORT_URL_AS_SNAPSHOT = 'snapshot', +} export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { + const isMounted = useMountedState(); + const [urlParams, setUrlParams] = useState(undefined); + const [useShortUrl] = useState(true); + const [exportUrlAs] = useState(ExportUrlAsType.EXPORT_URL_AS_SAVED_OBJECT); + const [url, setUrl] = useState(''); + const [shortUrlCache, setShortUrlCache] = useState(undefined); + const [anonymousAccessParameters] = useState(null); + const [usePublicUrl] = useState(false); + + const getUrlParamExtensions = useCallback( + (tempUrl: string): string => { + return urlParams + ? Object.keys(urlParams).reduce((urlAccumulator, key) => { + const urlParam = urlParams[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, tempUrl) + : tempUrl; + }, + [urlParams] + ); + + // const makeUrlEmbeddable = (tempUrl: string): string => { + // const embedParam = '?embed=true'; + // const urlHasQueryString = tempUrl.indexOf('?') !== -1; + + // if (urlHasQueryString) { + // return tempUrl.replace('?', `${embedParam}&`); + // } + + // return `${tempUrl}${embedParam}`; + // }; + + const updateUrlParams = useCallback( + (tempUrl: string) => { + // tempUrl = isEmbedded ? makeUrlEmbeddable(tempUrl) : tempUrl; + tempUrl = urlParams ? getUrlParamExtensions(tempUrl) : tempUrl; + return tempUrl; + }, + [getUrlParamExtensions, urlParams] + ); + + const getSnapshotUrl = useCallback( + (forSavedObject?: boolean) => { + let tempUrl = ''; + if (forSavedObject && shareableUrlForSavedObject) { + tempUrl = shareableUrlForSavedObject; + } + if (!tempUrl) { + tempUrl = shareableUrl || window.location.href; + } + return updateUrlParams(tempUrl); + }, + [updateUrlParams] + ); + + const getSavedObjectUrl = useCallback(() => { + const tempUrl = getSnapshotUrl(true); + + const parsedUrl = parseUrl(tempUrl); + if (!parsedUrl || !parsedUrl.hash) { + return; + } + + // Get the application route, after the hash, and remove the #. + const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); + + const formattedUrl = formatUrl({ + protocol: parsedUrl.protocol, + auth: parsedUrl.auth, + host: parsedUrl.host, + pathname: parsedUrl.pathname, + hash: formatUrl({ + pathname: parsedAppUrl.pathname, + query: { + // Add global state to the URL so that the iframe doesn't just show the time range + // default. + _g: parsedAppUrl.query._g, + }, + }), + }); + return updateUrlParams(formattedUrl); + }, [getSnapshotUrl, updateUrlParams]); + + const addUrlAnonymousAccessParameters = useCallback( + (tempUrl: string): string => { + if (!anonymousAccessParameters || !usePublicUrl) { + return tempUrl; + } + + const parsedUrl = new URL(tempUrl); + + for (const [name, value] of Object.entries(anonymousAccessParameters)) { + parsedUrl.searchParams.set(name, value); + } + + return parsedUrl.toString(); + }, + [anonymousAccessParameters, usePublicUrl] + ); + + const makeIframeTag = (tempUrl: string) => { + if (!tempUrl) { + return; + } + + return ``; + }; + + const setUrlHelper = useCallback(() => { + let tempUrl: string = ''; + + if (exportUrlAs === ExportUrlAsType.EXPORT_URL_AS_SAVED_OBJECT) { + tempUrl = getSavedObjectUrl() ?? ''; + } else if (useShortUrl && shortUrlCache) { + tempUrl = shortUrlCache; + } else { + tempUrl = getSnapshotUrl(); + } + + if (url) { + tempUrl = addUrlAnonymousAccessParameters(url); + } + + if (isEmbedded) { + tempUrl = makeIframeTag(url) ?? ''; + } + setUrl(tempUrl); + }, [ + addUrlAnonymousAccessParameters, + exportUrlAs, + getSavedObjectUrl, + getSnapshotUrl, + shortUrlCache, + url, + useShortUrl, + ]); + + const resetUrl = useCallback(() => { + if (isMounted()) { + setShortUrlCache(undefined); + setUrlHelper(); + } + }, [isMounted, setUrlHelper]); + + useEffect(() => { + setUrlHelper(); + getUrlParamExtensions(url); + window.addEventListener('hashchange', resetUrl, false); + isMounted(); + }, [getUrlParamExtensions, resetUrl, setUrlHelper, url, isMounted]); + + const renderButtons = () => { + const { dataTestSubj, formattedMessageId, defaultMessage } = action; + return ( + + {(copy) => ( + + + + )} + + ); + }; + const renderUrlParamExtensions = () => { if (!urlParamExtensions) { - return; + return <>; } const setParamValue = (paramName: string) => (values: { [queryParam: string]: boolean } = {}): void => { - // const stateUpdate = { - // urlParams: { - // ...this.state.urlParams, - // [paramName]: { - // ...values, - // }, - // }, - // }; - // this.setState(stateUpdate, this.state.useShortUrl ? this.createShortUrl : this.setUrl); + setUrlParams({ ...urlParams, [paramName]: { ...values } }); + setUrlHelper(); }; return ( diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 6a02627d557f7f..0765c197fa2564 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -138,7 +138,7 @@ export const LinkModal = ({ const renderButtons = () => { const { formattedMessageId, defaultMessage } = action; return ( - + {(copy) => ( From 1988c497cd1c9497fb68ff925802f6cbbef54631 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 4 Mar 2024 13:24:56 -0700 Subject: [PATCH 09/69] fix embed behavior in url --- .../share/public/components/embed_modal.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx index 965b42c8c543e9..2fb5f4206570d5 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -145,31 +145,30 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { }; const setUrlHelper = useCallback(() => { - let tempUrl: string = ''; + let tempUrl: string | undefined; if (exportUrlAs === ExportUrlAsType.EXPORT_URL_AS_SAVED_OBJECT) { - tempUrl = getSavedObjectUrl() ?? ''; + tempUrl = getSavedObjectUrl(); } else if (useShortUrl && shortUrlCache) { tempUrl = shortUrlCache; } else { tempUrl = getSnapshotUrl(); } - if (url) { - tempUrl = addUrlAnonymousAccessParameters(url); + if (tempUrl) { + tempUrl = addUrlAnonymousAccessParameters(tempUrl!); } if (isEmbedded) { - tempUrl = makeIframeTag(url) ?? ''; + tempUrl = makeIframeTag(tempUrl!); } - setUrl(tempUrl); + setUrl(tempUrl!); }, [ addUrlAnonymousAccessParameters, exportUrlAs, getSavedObjectUrl, getSnapshotUrl, shortUrlCache, - url, useShortUrl, ]); @@ -190,7 +189,7 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { const renderButtons = () => { const { dataTestSubj, formattedMessageId, defaultMessage } = action; return ( - + {(copy) => ( From 2f3aaec635f3a0900a456b93a903cd68d4918cef Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:33:05 +0000 Subject: [PATCH 10/69] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-share-modal/tsconfig.json | 1 + packages/shared-ux/share_modal/impl/tsconfig.json | 1 - packages/shared-ux/share_modal/mocks/tsconfig.json | 4 ++++ packages/shared-ux/share_modal/types/tsconfig.json | 3 +++ src/plugins/share/tsconfig.json | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/kbn-share-modal/tsconfig.json b/packages/kbn-share-modal/tsconfig.json index 2b21731e92e416..22f315077fd698 100644 --- a/packages/kbn-share-modal/tsconfig.json +++ b/packages/kbn-share-modal/tsconfig.json @@ -12,6 +12,7 @@ "**/*.ts*", ], "kbn_references": [ + "@kbn/i18n-react", ], "exclude": [ "target/**/*", diff --git a/packages/shared-ux/share_modal/impl/tsconfig.json b/packages/shared-ux/share_modal/impl/tsconfig.json index 6be3891fed8a09..b40bf1cd5de662 100644 --- a/packages/shared-ux/share_modal/impl/tsconfig.json +++ b/packages/shared-ux/share_modal/impl/tsconfig.json @@ -14,7 +14,6 @@ "**/*.tsx", ], "kbn_references": [ - "@kbn/shared-ux-share-modal-mocks", ], "exclude": [ "target/**/*", diff --git a/packages/shared-ux/share_modal/mocks/tsconfig.json b/packages/shared-ux/share_modal/mocks/tsconfig.json index 693d1ee9ce31e4..3721023b860cdc 100644 --- a/packages/shared-ux/share_modal/mocks/tsconfig.json +++ b/packages/shared-ux/share_modal/mocks/tsconfig.json @@ -14,5 +14,9 @@ ], "exclude": [ "target/**/*", + ], + "kbn_references": [ + "@kbn/shared-ux-storybook-mock", + "@kbn/share-modal", ] } diff --git a/packages/shared-ux/share_modal/types/tsconfig.json b/packages/shared-ux/share_modal/types/tsconfig.json index 693d1ee9ce31e4..ec9b61142c9886 100644 --- a/packages/shared-ux/share_modal/types/tsconfig.json +++ b/packages/shared-ux/share_modal/types/tsconfig.json @@ -14,5 +14,8 @@ ], "exclude": [ "target/**/*", + ], + "kbn_references": [ + "@kbn/share-modal", ] } diff --git a/src/plugins/share/tsconfig.json b/src/plugins/share/tsconfig.json index ea5f056f7862a1..b884e2fff7f7ce 100644 --- a/src/plugins/share/tsconfig.json +++ b/src/plugins/share/tsconfig.json @@ -6,7 +6,6 @@ "include": ["common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ "@kbn/core", - "@kbn/kibana-react-plugin", "@kbn/kibana-utils-plugin", "@kbn/utility-types", "@kbn/i18n", @@ -20,6 +19,7 @@ "@kbn/core-chrome-browser", "@kbn/shared-ux-prompt-not-found", "@kbn/core-capabilities-common", + "@kbn/share-modal", "@kbn/react-kibana-mount", ], "exclude": [ From 7c54bbe456667b79a33218a5ca1757f64ce440de Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 4 Mar 2024 13:44:46 -0700 Subject: [PATCH 11/69] design chx --- .../share/public/components/embed_modal.tsx | 48 +++++++++---------- .../share/public/components/link_modal.tsx | 8 ++-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx index 2fb5f4206570d5..8c276d3c0441f0 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -6,7 +6,15 @@ * Side Public License, v 1. */ -import { EuiButton, EuiCopy, EuiForm, EuiFormHelpText, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { + EuiButton, + EuiCopy, + EuiForm, + EuiFormRow, + EuiModalFooter, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; @@ -57,20 +65,8 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { [urlParams] ); - // const makeUrlEmbeddable = (tempUrl: string): string => { - // const embedParam = '?embed=true'; - // const urlHasQueryString = tempUrl.indexOf('?') !== -1; - - // if (urlHasQueryString) { - // return tempUrl.replace('?', `${embedParam}&`); - // } - - // return `${tempUrl}${embedParam}`; - // }; - const updateUrlParams = useCallback( (tempUrl: string) => { - // tempUrl = isEmbedded ? makeUrlEmbeddable(tempUrl) : tempUrl; tempUrl = urlParams ? getUrlParamExtensions(tempUrl) : tempUrl; return tempUrl; }, @@ -222,16 +218,20 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { ); }; return ( - - - - - - - {renderUrlParamExtensions()} - + <> + + + + + + + {renderUrlParamExtensions()} + + + {renderButtons()} + ); }; diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 0765c197fa2564..9d5b8a74f78652 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -11,9 +11,9 @@ import { EuiCodeBlock, EuiCopy, EuiForm, - EuiFormHelpText, EuiModalFooter, EuiSpacer, + EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useState } from 'react'; @@ -151,14 +151,14 @@ export const LinkModal = ({ return ( <> - - + + - + {renderLink()} From 02b5eb97b9266121350507de98141ff42c5b8b0d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:51:41 +0000 Subject: [PATCH 12/69] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c33310d98de5e..33226fde98eff4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -737,6 +737,7 @@ test/plugin_functional/plugins/session_notifications @elastic/kibana-core x-pack/plugins/session_view @elastic/kibana-cloud-security-posture packages/kbn-set-map @elastic/kibana-operations examples/share_examples @elastic/appex-sharedux +packages/kbn-share-modal @elastic/appex-sharedux src/plugins/share @elastic/appex-sharedux packages/kbn-shared-svg @elastic/obs-ux-infra_services-team packages/shared-ux/avatar/solution @elastic/appex-sharedux From 9134ea956c6167251a5ea9db6a7eea145062cdb1 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 5 Mar 2024 08:20:03 -0700 Subject: [PATCH 13/69] fix order of tabs to match figma --- packages/kbn-share-modal/modal.tsx | 6 +--- .../share/public/components/share_tabs.tsx | 29 +++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/kbn-share-modal/modal.tsx b/packages/kbn-share-modal/modal.tsx index 61a837505730e7..6c000c68acc16e 100644 --- a/packages/kbn-share-modal/modal.tsx +++ b/packages/kbn-share-modal/modal.tsx @@ -21,14 +21,10 @@ import { FormattedMessage } from '@kbn/i18n-react'; export interface ModalProps { objectType: string; onClose: () => void; - tabs: Array<{ id: string; name: string; content: ReactElement; sortOrder: number }>; + tabs: Array<{ id: string; name: string; content: ReactElement }>; modalBodyDescriptions: Array<{ id: string; description: any }>; } -/** - * - */ - export const ShareModal = ({ onClose, objectType, tabs }: ModalProps) => { const [selectedTabId, setSelectedTabId] = useState('link'); diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 1d0d94b75c7871..097a0e2849f274 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -60,7 +60,7 @@ export const ShareMenuTabs = ({ isDirty, isEmbedded, }: ShareContextTabProps) => { - const actionHandler = useCallback( + const buttonHandler = useCallback( () => [ { id: 'link', @@ -92,10 +92,9 @@ export const ShareMenuTabs = ({ name: i18n.translate('share.contextMenu.permalinksLabel', { defaultMessage: 'Links', }), - sortOrder: 0, // do not break functional tests 'data-test-subj': 'Permalinks', - action: actionHandler().filter(({ id }) => id === 'link')[0], + action: buttonHandler().filter(({ id }) => id === 'link')[0], content: ( id === 'link')[0]} + action={buttonHandler().filter(({ id }) => id === 'link')[0]} /> ), }); + + shareMenuItems.map(({ shareMenuItem, panel }) => { + tabs.push({ + ...shareMenuItem, + id: panel.id, + buttonHandler: buttonHandler().filter(({ id }) => id === id)[0], + }); + }); + if (allowEmbed) { tabs.push({ id: 'embed', name: i18n.translate('share.contextMenu.embedCodeLabel', { defaultMessage: 'Embed', }), - sortOrder: 1, - action: actionHandler().filter(({ id }) => id === 'embed')[0], + action: buttonHandler().filter(({ id }) => id === 'embed')[0], content: ( id === 'embed')[0]} + action={buttonHandler().filter(({ id }) => id === 'embed')[0]} /> ), }); } - - shareMenuItems.map(({ shareMenuItem, panel }) => { - tabs.push({ - ...shareMenuItem, - id: panel.id, - actionHandler: actionHandler().filter(({ id }) => id === id)[0], - }); - }); return tabs; }; From 656bd50c0f7e3d7a03a0764c16a5125fdb8a87bd Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 6 Mar 2024 10:33:21 -0700 Subject: [PATCH 14/69] short urls when click copy link --- .../share/public/components/link_modal.tsx | 146 ++++++++++++------ .../share/public/components/share_tabs.tsx | 1 + 2 files changed, 96 insertions(+), 51 deletions(-) diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 9d5b8a74f78652..63eaee1c61ae5e 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -16,8 +16,11 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; import { format as formatUrl, parse as parseUrl } from 'url'; +import type { LocatorPublic } from '../../common'; +import { BrowserUrlService } from '../types'; interface LinkProps { objectType: string; @@ -28,6 +31,11 @@ interface LinkProps { shareableUrl?: string; onClose: () => void; action: any; + urlService: BrowserUrlService; + shareableUrlLocatorParams?: { + locator: LocatorPublic; + params: any; + }; } interface UrlParams { @@ -44,55 +52,81 @@ export const LinkModal = ({ shareableUrl, shareableUrlForSavedObject, action, + urlService, + shareableUrlLocatorParams, }: LinkProps) => { + const isMounted = useMountedState(); + const [url, setUrl] = useState(''); const [urlParams] = useState(undefined); + const [shortUrlCache, setShortUrlCache] = useState(undefined); - const isNotSaved = () => { + const isNotSaved = useCallback(() => { return objectId === undefined || objectId === '' || isDirty; - }; + }, [objectId, isDirty]); - const makeUrlEmbeddable = (url: string): string => { + const makeUrlEmbeddable = useCallback((tempUrl: string): string => { const embedParam = '?embed=true'; - const urlHasQueryString = url.indexOf('?') !== -1; + const urlHasQueryString = tempUrl.indexOf('?') !== -1; if (urlHasQueryString) { - return url.replace('?', `${embedParam}&`); + return tempUrl.replace('?', `${embedParam}&`); } - return `${url}${embedParam}`; - }; + return `${tempUrl}${embedParam}`; + }, []); + + const getUrlParamExtensions = useCallback( + (tempUrl: string): string => { + return urlParams + ? Object.keys(urlParams).reduce((urlAccumulator, key) => { + const urlParam = urlParams[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, tempUrl) + : tempUrl; + }, + [urlParams] + ); - const getUrlParamExtensions = (url: string): string => { - return urlParams - ? Object.keys(urlParams).reduce((urlAccumulator, key) => { - const urlParam = urlParams[key]; - return urlParam - ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { - const isQueryParamEnabled = urlParam[queryParam]; - return isQueryParamEnabled - ? queryAccumulator + `&${queryParam}=true` - : queryAccumulator; - }, urlAccumulator) - : urlAccumulator; - }, url) - : url; - }; + const updateUrlParams = useCallback( + (tempUrl: string) => { + tempUrl = isEmbedded ? makeUrlEmbeddable(tempUrl) : tempUrl; + tempUrl = urlParams ? getUrlParamExtensions(tempUrl) : tempUrl; + setUrl(tempUrl); + return tempUrl; + }, + [makeUrlEmbeddable, getUrlParamExtensions, urlParams, isEmbedded] + ); - const updateUrlParams = (url: string) => { - url = isEmbedded ? makeUrlEmbeddable(url) : url; - url = urlParams ? getUrlParamExtensions(url) : url; - // setCopyLinkData(url); - return url; - }; + const getSnapshotUrl = useCallback( + (forSavedObject?: boolean) => { + let tempUrl = ''; + if (forSavedObject && shareableUrlForSavedObject) { + tempUrl = shareableUrlForSavedObject; + } + if (!tempUrl) { + tempUrl = shareableUrl || window.location.href; + } + + return updateUrlParams(tempUrl); + }, + [shareableUrl, shareableUrlForSavedObject, updateUrlParams] + ); - const getSavedObjectUrl = () => { + const getSavedObjectUrl = useCallback(() => { if (isNotSaved()) { return; } - const url = getSnapshotUrl(true); + const tempUrl = getSnapshotUrl(true); - const parsedUrl = parseUrl(url); + const parsedUrl = parseUrl(tempUrl); if (!parsedUrl || !parsedUrl.hash) { return; } @@ -115,32 +149,42 @@ export const LinkModal = ({ }), }); return updateUrlParams(formattedUrl); - }; + }, [getSnapshotUrl, isNotSaved, updateUrlParams]); + + const createShortUrl = useCallback( + async (tempUrl: string) => { + if (!isMounted) return; + const shortUrl = shareableUrlLocatorParams + ? await urlService.shortUrls.get(null).createWithLocator(shareableUrlLocatorParams) + : (await urlService.shortUrls.get(null).createFromLongUrl(tempUrl)).url; + setShortUrlCache(shortUrl as string); + setUrl(shortUrl as string); + }, + [isMounted, shareableUrlLocatorParams, urlService.shortUrls] + ); - const getSnapshotUrl = (forSavedObject?: boolean) => { - let url = ''; - if (forSavedObject && shareableUrlForSavedObject) { - url = shareableUrlForSavedObject; - } - if (!url) { - url = shareableUrl || window.location.href; - } - return updateUrlParams(url); - }; + const setUrlHelper = useCallback(() => { + let tempUrl: string | undefined; - const renderLink = () => { if (objectType === 'dashboard' || objectType === 'search') { - return getSnapshotUrl(); + tempUrl = getSnapshotUrl(); + } else { + tempUrl = getSavedObjectUrl(); } - return getSavedObjectUrl(); - }; + return url === '' ? setUrl(tempUrl!) : createShortUrl(tempUrl!); + }, [getSavedObjectUrl, getSnapshotUrl, createShortUrl, objectType, url]); + + useEffect(() => { + isMounted(); + setUrlHelper(); + }, [isMounted, setUrlHelper]); const renderButtons = () => { - const { formattedMessageId, defaultMessage } = action; + const { formattedMessageId, defaultMessage, dataTestSubj } = action; return ( - + {(copy) => ( - + )} @@ -160,7 +204,7 @@ export const LinkModal = ({ /> - {renderLink()} + {shareableUrl ?? url} {renderButtons()} diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 097a0e2849f274..fcaf1a3b73db80 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -103,6 +103,7 @@ export const ShareMenuTabs = ({ isEmbedded={isEmbedded} onClose={onClose} action={buttonHandler().filter(({ id }) => id === 'link')[0]} + urlService={urlService} /> ), }); From 0c2ec066c5f39390be081fe4867b917990af1d13 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 6 Mar 2024 11:30:55 -0700 Subject: [PATCH 15/69] add package to i18n --- .i18nrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.i18nrc.json b/.i18nrc.json index adac4ebac38115..da2c796733727b 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -105,7 +105,7 @@ "savedObjectsManagement": "src/plugins/saved_objects_management", "searchConnectors": "packages/kbn-search-connectors", "server": "src/legacy/server", - "share": ["src/plugins/share", "packages/kbn-reporting-share"], + "share": ["src/plugins/share", "packages/kbn-reporting-share", "packages/kbn-share-modal"], "sharedUXPackages": "packages/shared-ux", "searchApiPanels": "packages/kbn-search-api-panels/", "searchErrors": "packages/kbn-search-errors", From 504d34f49423c46e718f98b98f7487c8c6ac98ca Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 6 Mar 2024 11:51:24 -0700 Subject: [PATCH 16/69] add padding right to code block --- src/plugins/share/public/components/link_modal.tsx | 4 +++- src/plugins/share/public/services/share_menu_manager.tsx | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 63eaee1c61ae5e..c996d0bfccb940 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -204,7 +204,9 @@ export const LinkModal = ({ /> - {shareableUrl ?? url} + + {shareableUrl ?? url} + {renderButtons()} diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 842b2bce1d62a8..726758a1ba8a96 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -12,7 +12,8 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import { CoreStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; import { EuiWrappingPopover } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n-react'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { ShareMenuItem, ShowShareMenuOptions } from '../types'; import { ShareMenuRegistryStart } from './share_menu_registry'; import { AnonymousAccessServiceContract } from '../../common/anonymous_access'; @@ -114,7 +115,7 @@ export class ShareMenuManager { if (!newVersionEnabled) { const element = ( - + Date: Wed, 6 Mar 2024 11:55:30 -0700 Subject: [PATCH 17/69] fix --- src/plugins/share/public/components/link_modal.tsx | 2 +- src/plugins/share/public/components/share_tabs.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index c996d0bfccb940..1a457ec9a269f8 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -30,7 +30,7 @@ interface LinkProps { shareableUrlForSavedObject?: string; shareableUrl?: string; onClose: () => void; - action: any; + action: { formattedMessageId: string; defaultMessage: string; dataTestSubj: string }; urlService: BrowserUrlService; shareableUrlLocatorParams?: { locator: LocatorPublic; diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index fcaf1a3b73db80..1967b5b78a095f 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -89,7 +89,7 @@ export const ShareMenuTabs = ({ tabs.push({ id: 'link', - name: i18n.translate('share.contextMenu.permalinksLabel', { + name: i18n.translate('share.contextMenu.permalinksTab', { defaultMessage: 'Links', }), // do not break functional tests @@ -119,7 +119,7 @@ export const ShareMenuTabs = ({ if (allowEmbed) { tabs.push({ id: 'embed', - name: i18n.translate('share.contextMenu.embedCodeLabel', { + name: i18n.translate('share.contextMenu.embedCodeTab', { defaultMessage: 'Embed', }), action: buttonHandler().filter(({ id }) => id === 'embed')[0], From 33a3b528912cce873900f77716dad10d8b609d01 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Fri, 8 Mar 2024 14:02:15 -0700 Subject: [PATCH 18/69] wip adapt eyos work to pr --- package.json | 1 + .../impl/src/share_modal.stories.tsx | 0 .../{share_modal => modal}/impl/tsconfig.json | 0 .../{share_modal => modal}/mocks/index.ts | 0 .../mocks/src/storybook.ts | 0 .../mocks/tsconfig.json | 0 packages/shared-ux/modal/tabbed/index.tsx | 10 ++ packages/shared-ux/modal/tabbed/kibana.jsonc | 5 + packages/shared-ux/modal/tabbed/package.json | 6 + .../modal/tabbed/src/context/index.tsx | 152 ++++++++++++++++++ .../modal/tabbed/src/tabbed_modal.tsx | 106 ++++++++++++ .../{share_modal => modal}/types/index.d.ts | 0 .../types/tsconfig.json | 0 .../share/public/components/share_tabs.tsx | 14 +- tsconfig.base.json | 2 + yarn.lock | 4 + 16 files changed, 293 insertions(+), 7 deletions(-) rename packages/shared-ux/{share_modal => modal}/impl/src/share_modal.stories.tsx (100%) rename packages/shared-ux/{share_modal => modal}/impl/tsconfig.json (100%) rename packages/shared-ux/{share_modal => modal}/mocks/index.ts (100%) rename packages/shared-ux/{share_modal => modal}/mocks/src/storybook.ts (100%) rename packages/shared-ux/{share_modal => modal}/mocks/tsconfig.json (100%) create mode 100644 packages/shared-ux/modal/tabbed/index.tsx create mode 100644 packages/shared-ux/modal/tabbed/kibana.jsonc create mode 100644 packages/shared-ux/modal/tabbed/package.json create mode 100644 packages/shared-ux/modal/tabbed/src/context/index.tsx create mode 100644 packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx rename packages/shared-ux/{share_modal => modal}/types/index.d.ts (100%) rename packages/shared-ux/{share_modal => modal}/types/tsconfig.json (100%) diff --git a/package.json b/package.json index 4837152e6ebf50..a5455ed2c6f96b 100644 --- a/package.json +++ b/package.json @@ -789,6 +789,7 @@ "@kbn/shared-ux-router-types": "link:packages/shared-ux/router/types", "@kbn/shared-ux-storybook-config": "link:packages/shared-ux/storybook/config", "@kbn/shared-ux-storybook-mock": "link:packages/shared-ux/storybook/mock", + "@kbn/shared-ux-tabbed-modal": "link:packages/shared-ux/modal/tabbed", "@kbn/shared-ux-utility": "link:packages/kbn-shared-ux-utility", "@kbn/slo-plugin": "link:x-pack/plugins/observability_solution/slo", "@kbn/slo-schema": "link:x-pack/packages/kbn-slo-schema", diff --git a/packages/shared-ux/share_modal/impl/src/share_modal.stories.tsx b/packages/shared-ux/modal/impl/src/share_modal.stories.tsx similarity index 100% rename from packages/shared-ux/share_modal/impl/src/share_modal.stories.tsx rename to packages/shared-ux/modal/impl/src/share_modal.stories.tsx diff --git a/packages/shared-ux/share_modal/impl/tsconfig.json b/packages/shared-ux/modal/impl/tsconfig.json similarity index 100% rename from packages/shared-ux/share_modal/impl/tsconfig.json rename to packages/shared-ux/modal/impl/tsconfig.json diff --git a/packages/shared-ux/share_modal/mocks/index.ts b/packages/shared-ux/modal/mocks/index.ts similarity index 100% rename from packages/shared-ux/share_modal/mocks/index.ts rename to packages/shared-ux/modal/mocks/index.ts diff --git a/packages/shared-ux/share_modal/mocks/src/storybook.ts b/packages/shared-ux/modal/mocks/src/storybook.ts similarity index 100% rename from packages/shared-ux/share_modal/mocks/src/storybook.ts rename to packages/shared-ux/modal/mocks/src/storybook.ts diff --git a/packages/shared-ux/share_modal/mocks/tsconfig.json b/packages/shared-ux/modal/mocks/tsconfig.json similarity index 100% rename from packages/shared-ux/share_modal/mocks/tsconfig.json rename to packages/shared-ux/modal/mocks/tsconfig.json diff --git a/packages/shared-ux/modal/tabbed/index.tsx b/packages/shared-ux/modal/tabbed/index.tsx new file mode 100644 index 00000000000000..574cd435f68ed0 --- /dev/null +++ b/packages/shared-ux/modal/tabbed/index.tsx @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TabbedModal } from './src/tabbed_modal'; +export type { IModalTabDeclaration } from './src/context'; diff --git a/packages/shared-ux/modal/tabbed/kibana.jsonc b/packages/shared-ux/modal/tabbed/kibana.jsonc new file mode 100644 index 00000000000000..4abd1fe7543edc --- /dev/null +++ b/packages/shared-ux/modal/tabbed/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-tabbed-modal", + "owner": "@elastic/appex-sharedux" + } \ No newline at end of file diff --git a/packages/shared-ux/modal/tabbed/package.json b/packages/shared-ux/modal/tabbed/package.json new file mode 100644 index 00000000000000..987db5ef12407f --- /dev/null +++ b/packages/shared-ux/modal/tabbed/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/shared-ux-tabbed-modal", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" + } \ No newline at end of file diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx new file mode 100644 index 00000000000000..202a81123c3ba2 --- /dev/null +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -0,0 +1,152 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { + createContext, + useContext, + useReducer, + useMemo, + useRef, + useCallback, + type PropsWithChildren, + type ReactElement, + type Dispatch, +} from 'react'; +import { type EuiTabProps, type CommonProps } from '@elastic/eui'; + +interface IDispatchAction { + type: string; + payload: any; +} + +export type IModalTabState = Record; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +type IModalMetaState = { + selectedTabId: string | null; +}; + +type IReducer = (state: S, action: IDispatchAction) => S; + +export type IModalTabContent = (props: { + state: S; + dispatch: Dispatch; +}) => ReactElement; + +interface IModalTabActionBtn extends CommonProps { + label: string; + handler: (args: { state: S }) => void; +} + +export interface IModalTabDeclaration extends EuiTabProps { + id: string; + title: string; + initialState?: Partial; + reducer?: IReducer; + content: IModalTabContent; + modalActionBtn: IModalTabActionBtn; +} + +interface IModalContext { + tabs: Array, 'reducer' | 'initialState'>>; + state: { meta: IModalMetaState } & Record; + dispatch: Dispatch; +} + +const ModalContext = createContext({ + tabs: [], + state: { + meta: { + selectedTabId: null, + }, + }, + dispatch: () => {}, +}); + +/** + * @description defines state transition for meta information to manage the modal, meta action types + * must be prefixed with the string 'META_' + */ +const modalMetaReducer: IReducer = (state, action) => { + switch (action.type) { + case 'META_selectedTabId': + return { + ...state, + selectedTabId: action.payload as string, + }; + default: + return state; + } +}; + +export type IModalContextProviderProps>> = + PropsWithChildren<{ + tabs: Tabs; + selectedTabId: Tabs[number]['id']; + }>; + +export function ModalContextProvider>>({ + tabs, + selectedTabId, + children, +}: IModalContextProviderProps) { + const modalTabDefinitions = useRef([]); + + const initialModalState = useRef({ + // instantiate state with default meta information + meta: { + selectedTabId, + }, + }); + + const reducersMap = useMemo( + () => + tabs.reduce((result, { id, reducer, initialState, ...rest }) => { + initialModalState.current[id] = initialState ?? {}; + modalTabDefinitions.current.push({ id, ...rest }); + result[id] = reducer; + return result; + }, {}), + [tabs] + ); + + const combineReducers = useCallback( + function (reducers: Record>) { + return (state: IModalContext['state'], action: IDispatchAction) => { + const newState = { ...state }; + + if (/^meta_/i.test(action.type)) { + newState.meta = modalMetaReducer(newState.meta, action); + } else { + newState[selectedTabId] = reducers[selectedTabId](newState[selectedTabId], action); + } + + return newState; + }; + }, + [selectedTabId] + ); + + const createInitialState = useCallback((state: IModalContext['state']) => { + return state; + }, []); + + const [state, dispatch] = useReducer( + combineReducers(reducersMap), + initialModalState.current, + createInitialState + ); + + return ( + + {children} + + ); +} + +export const useModalContext = () => useContext(ModalContext); diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx new file mode 100644 index 00000000000000..b87c80fb53bd1c --- /dev/null +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -0,0 +1,106 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo, Fragment, type ComponentProps, type FC, useCallback } from 'react'; +import { + EuiButton, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiTabs, + EuiTab, +} from '@elastic/eui'; +import { + ModalContextProvider, + useModalContext, + type IModalTabState, + type IModalTabDeclaration, + type IModalContextProviderProps, +} from './context'; + +interface ITabbedModalInner extends Pick, 'onClose'> { + modalTitle?: string; +} + +const TabbedModalInner: FC = ({ onClose, modalTitle }) => { + const { tabs, state, dispatch } = useModalContext(); + + const selectedTabId = state.meta.selectedTabId; + const selectedTabState = useMemo( + () => (selectedTabId ? state[selectedTabId] : {}), + [selectedTabId, state] + ); + + const { + content: SelectedTabContent, + modalActionBtn: { label, handler }, + } = useMemo(() => { + return tabs.find((obj) => obj.id === selectedTabId)!; + }, [selectedTabId, tabs]); + + const onSelectedTabChanged = (id: string) => { + dispatch({ type: 'META_selectedTabId', payload: id }); + }; + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + prepend={tab.prepend} + append={tab.append} + > + {tab.title} + + )); + }; + + const btnClickHandler = useCallback(() => { + handler({ state: selectedTabState }); + }, [handler, selectedTabState]); + + return ( + + {Boolean(modalTitle) ? ( + + {modalTitle} + + ) : null} + + + {renderTabs()} + {React.createElement(SelectedTabContent, { + state: selectedTabState, + dispatch, + })} + + + + + {label} + + + + ); +}; + +export function TabbedModal>>({ + tabs, + selectedTabId, + ...rest +}: Omit, 'children'> & ITabbedModalInner) { + return ( + + + + ); +} diff --git a/packages/shared-ux/share_modal/types/index.d.ts b/packages/shared-ux/modal/types/index.d.ts similarity index 100% rename from packages/shared-ux/share_modal/types/index.d.ts rename to packages/shared-ux/modal/types/index.d.ts diff --git a/packages/shared-ux/share_modal/types/tsconfig.json b/packages/shared-ux/modal/types/tsconfig.json similarity index 100% rename from packages/shared-ux/share_modal/types/tsconfig.json rename to packages/shared-ux/modal/types/tsconfig.json diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 1967b5b78a095f..c217d8a54025d3 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -8,7 +8,7 @@ import { Capabilities } from '@kbn/core-capabilities-common'; import React, { useCallback } from 'react'; -import { ShareModal } from '@kbn/share-modal'; +import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { LocatorPublic, AnonymousAccessServiceContract } from '../../common'; @@ -86,7 +86,6 @@ export const ShareMenuTabs = ({ const getTabs = () => { const tabs = []; - tabs.push({ id: 'link', name: i18n.translate('share.contextMenu.permalinksTab', { @@ -94,8 +93,9 @@ export const ShareMenuTabs = ({ }), // do not break functional tests 'data-test-subj': 'Permalinks', - action: buttonHandler().filter(({ id }) => id === 'link')[0], - content: ( + modalActionBtn: buttonHandler().filter(({ id }) => id === 'link')[0], + title: `Share this ${objectType}`, + content: () => ( id === 'embed')[0], - content: ( + ModalActionBtn: buttonHandler().filter(({ id }) => id === 'embed')[0], + content: () => ( Date: Mon, 11 Mar 2024 14:35:03 -0600 Subject: [PATCH 19/69] wip refactor to use tabbedModal --- .../modal/tabbed/src/context/index.tsx | 14 +- .../modal/tabbed/src/storybook/setup.ts | 52 +++++ .../modal/tabbed/src/tabbed_modal.stories.tsx | 183 ++++++++++++++++++ .../modal/tabbed/src/tabbed_modal.tsx | 36 ++-- .../share/public/components/embed_modal.tsx | 72 ++++--- .../share/public/components/link_modal.tsx | 69 +++---- .../share/public/components/share_tabs.tsx | 151 ++++++++------- 7 files changed, 413 insertions(+), 164 deletions(-) create mode 100644 packages/shared-ux/modal/tabbed/src/storybook/setup.ts create mode 100644 packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 202a81123c3ba2..9d5771b0bd070a 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -39,16 +39,22 @@ export type IModalTabContent = (props: { }) => ReactElement; interface IModalTabActionBtn extends CommonProps { - label: string; - handler: (args: { state: S }) => void; + id: string; + dataTestSubj: string; + defaultMessage: string; + formattedMessageId: string; + handler?: (args: { state: S }) => void; } export interface IModalTabDeclaration extends EuiTabProps { id: string; - title: string; + title?: string; + name: string; initialState?: Partial; reducer?: IReducer; - content: IModalTabContent; + description?: ReactElement; + 'data-test-subj'?: string; + content?: IModalTabContent; modalActionBtn: IModalTabActionBtn; } diff --git a/packages/shared-ux/modal/tabbed/src/storybook/setup.ts b/packages/shared-ux/modal/tabbed/src/storybook/setup.ts new file mode 100644 index 00000000000000..04f50dc9345bb0 --- /dev/null +++ b/packages/shared-ux/modal/tabbed/src/storybook/setup.ts @@ -0,0 +1,52 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ComponentProps } from 'react'; +import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; + +import TabbedModal from '../..'; + +type TabbedModalProps = ComponentProps; +type TabbedModalServiceArguments = Record; + +type Arguments = TabbedModalProps & TabbedModalServiceArguments; + +/** + * Storybook parameters provided from the controls addon. + */ +export type Params = Record; + +export class StorybookMock extends AbstractStorybookMock< + TabbedModalProps, + TabbedModalServiceArguments, + TabbedModalProps, + TabbedModalServiceArguments +> { + propArguments = { + tabs: { + control: { + type: 'array', + }, + defaultValue: [], + }, + }; + + serviceArguments = {}; + + dependencies = []; + + getProps(params?: Params): TabbedModalProps { + return { + tabs: this.getArgumentValue('tabs', params), + }; + } + + getServices(params: Params): TabbedModalServiceArguments { + return {}; + } +} diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx new file mode 100644 index 00000000000000..1f45d47b26fce4 --- /dev/null +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx @@ -0,0 +1,183 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiText, EuiCheckboxGroup, EuiSpacer, useGeneratedHtmlId } from '@elastic/eui'; +import React, { Fragment } from 'react'; + +import { + StorybookMock as TabbedModalStorybookMock, + type Params as TabbedModalStorybookParams, +} from './storybook/setup'; + +import { TabbedModal } from './tabbed_modal'; +import { IModalTabDeclaration } from './context'; + +export default { + title: 'Modal/Tabbed Modal', + description: 'A controlled modal component that renders tabs', +}; + +const mock = new TabbedModalStorybookMock(); +const argTypes = mock.getArgumentTypes(); + +export const TrivialExample = (params: TabbedModalStorybookParams) => { + return ( + { + return ( + + + +

Click the button to send a message into the void

+
+
+ ); + }, + initialState: { + message: 'Hello World!!', + }, + modalActionBtn: { + id: 'wave', + dataTestSubj: '', + formattedMessageId: '', + defaultMessage: 'Say Hi 👋🏾', + handler: ({ state }) => { + alert(state.message); + }, + }, + }, + ]} + selectedTabId="hello" + onClose={() => {}} + /> + ); +}; + +TrivialExample.argTypes = argTypes; + +export const NonTrivialExample = (params: TabbedModalStorybookParams) => { + const checkboxGroupItemId1 = useGeneratedHtmlId({ + prefix: 'checkboxGroupItem', + suffix: 'first', + }); + const checkboxGroupItemId2 = useGeneratedHtmlId({ + prefix: 'checkboxGroupItem', + suffix: 'second', + }); + const checkboxGroupItemId3 = useGeneratedHtmlId({ + prefix: 'checkboxGroupItem', + suffix: 'third', + }); + + const checkboxes = [ + { + id: checkboxGroupItemId1, + label: 'Margherita', + 'data-test-sub': 'dts_test', + }, + { + id: checkboxGroupItemId2, + label: 'Diavola', + className: 'classNameTest', + }, + { + id: checkboxGroupItemId3, + label: 'Hawaiian Pizza', + disabled: true, + }, + ]; + + enum ACTION_TYPES { + SelectOption, + } + + const pizzaSelector: IModalTabDeclaration<{ + checkboxIdToSelectedMap: Record; + }> = { + id: 'order', + name: 'order', + title: 'Pizza of choice', + initialState: { + checkboxIdToSelectedMap: { + [checkboxGroupItemId2]: true, + }, + }, + reducer(state, action) { + switch (action.type) { + case String(ACTION_TYPES.SelectOption): + return { + ...state, + checkboxIdToSelectedMap: action.payload, + }; + default: + return state; + } + }, + content: ({ state, dispatch }) => { + const { checkboxIdToSelectedMap } = state; + + const onChange = (optionId) => { + const newCheckboxIdToSelectedMap = { + ...checkboxIdToSelectedMap, + ...{ + [optionId]: !checkboxIdToSelectedMap[optionId], + }, + }; + + dispatch({ + type: String(ACTION_TYPES.SelectOption), + payload: newCheckboxIdToSelectedMap, + }); + }; + + return ( + + + +

Select a Pizza (or more)

+
+ + onChange(id)} + /> +
+ ); + }, + modalActionBtn: { + id: 'pizza', + dataTestSubj: '', + formattedMessageId: '', + defaultMessage: 'Order 🍕', + handler: ({ state }) => { + alert(JSON.stringify(state)); + }, + }, + }; + + // TODO: fix type mismatch + return ( + {}} + modalTitle="Non trivial example" + tabs={[pizzaSelector]} + selectedTabId="order" + /> + ); +}; + +NonTrivialExample.argTypes = argTypes; diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index b87c80fb53bd1c..67f6b1ff8d8724 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useMemo, Fragment, type ComponentProps, type FC, useCallback } from 'react'; +import React, { useMemo, Fragment, type ComponentProps, type FC } from 'react'; import { EuiButton, EuiModal, @@ -16,7 +16,9 @@ import { EuiModalHeaderTitle, EuiTabs, EuiTab, + EuiCopy, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ModalContextProvider, useModalContext, @@ -29,7 +31,7 @@ interface ITabbedModalInner extends Pick, 'onClo modalTitle?: string; } -const TabbedModalInner: FC = ({ onClose, modalTitle }) => { +const TabbedModalInner: FC = ({ onClose }) => { const { tabs, state, dispatch } = useModalContext(); const selectedTabId = state.meta.selectedTabId; @@ -40,7 +42,9 @@ const TabbedModalInner: FC = ({ onClose, modalTitle }) => { const { content: SelectedTabContent, - modalActionBtn: { label, handler }, + description: string, + modalActionBtn: { defaultMessage, handler, dataTestSubj, formattedMessageId }, + title, } = useMemo(() => { return tabs.find((obj) => obj.id === selectedTabId)!; }, [selectedTabId, tabs]); @@ -59,22 +63,20 @@ const TabbedModalInner: FC = ({ onClose, modalTitle }) => { prepend={tab.prepend} append={tab.append} > - {tab.title} + {tab.name}
)); }; - const btnClickHandler = useCallback(() => { - handler({ state: selectedTabState }); - }, [handler, selectedTabState]); + // const btnClickHandler = useCallback(() => { + // handler!({ state: selectedTabState }); + // }, [handler, selectedTabState]); return ( - {Boolean(modalTitle) ? ( - - {modalTitle} - - ) : null} + + {title} + {renderTabs()} @@ -85,9 +87,13 @@ const TabbedModalInner: FC = ({ onClose, modalTitle }) => { - - {label} - + + {(copy) => ( + + + + )} + ); diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx index 8c276d3c0441f0..4bacdca3f99cd2 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -6,15 +6,7 @@ * Side Public License, v 1. */ -import { - EuiButton, - EuiCopy, - EuiForm, - EuiFormRow, - EuiModalFooter, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; @@ -24,6 +16,16 @@ import { UrlParamExtension } from '../types'; interface EmbedProps { urlParamExtensions?: UrlParamExtension[]; + shareableUrlLocatorParams?: + | { + locator: LocatorPublic; + params: any; + } + | undefined; + urlService: BrowserUrlService; + shareableUrlForSavedObject?: string; + shareableUrl?: string; + isEmbedded?: boolean; } interface UrlParams { [extensionName: string]: { @@ -36,7 +38,14 @@ export enum ExportUrlAsType { EXPORT_URL_AS_SNAPSHOT = 'snapshot', } -export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { +export const EmbedModal = ({ + urlParamExtensions, + shareableUrlLocatorParams, + urlService, + shareableUrlForSavedObject, + shareableUrl, + isEmbedded, +}: EmbedProps) => { const isMounted = useMountedState(); const [urlParams, setUrlParams] = useState(undefined); const [useShortUrl] = useState(true); @@ -84,7 +93,7 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { } return updateUrlParams(tempUrl); }, - [updateUrlParams] + [updateUrlParams, shareableUrl, shareableUrlForSavedObject] ); const getSavedObjectUrl = useCallback(() => { @@ -166,6 +175,7 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { getSnapshotUrl, shortUrlCache, useShortUrl, + isEmbedded, ]); const resetUrl = useCallback(() => { @@ -182,19 +192,6 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { isMounted(); }, [getUrlParamExtensions, resetUrl, setUrlHelper, url, isMounted]); - const renderButtons = () => { - const { dataTestSubj, formattedMessageId, defaultMessage } = action; - return ( - - {(copy) => ( - - - - )} - - ); - }; - const renderUrlParamExtensions = () => { if (!urlParamExtensions) { return <>; @@ -218,20 +215,17 @@ export const EmbedModal = ({ urlParamExtensions }: EmbedProps) => { ); }; return ( - <> - - - - - - - {renderUrlParamExtensions()} - - - {renderButtons()} - + + + + + + + {renderUrlParamExtensions()} + + ); }; diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/link_modal.tsx index 1a457ec9a269f8..74e9e2e33b805c 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/link_modal.tsx @@ -6,15 +6,7 @@ * Side Public License, v 1. */ -import { - EuiButton, - EuiCodeBlock, - EuiCopy, - EuiForm, - EuiModalFooter, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiCodeBlock, EuiForm, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; @@ -30,7 +22,6 @@ interface LinkProps { shareableUrlForSavedObject?: string; shareableUrl?: string; onClose: () => void; - action: { formattedMessageId: string; defaultMessage: string; dataTestSubj: string }; urlService: BrowserUrlService; shareableUrlLocatorParams?: { locator: LocatorPublic; @@ -51,7 +42,6 @@ export const LinkModal = ({ isEmbedded, shareableUrl, shareableUrlForSavedObject, - action, urlService, shareableUrlLocatorParams, }: LinkProps) => { @@ -179,37 +169,34 @@ export const LinkModal = ({ setUrlHelper(); }, [isMounted, setUrlHelper]); - const renderButtons = () => { - const { formattedMessageId, defaultMessage, dataTestSubj } = action; - return ( - - {(copy) => ( - - - - )} - - ); - }; + // const renderButtons = () => { + // const { formattedMessageId, defaultMessage, dataTestSubj } = action; + // return ( + // + // {(copy) => ( + // + // + // + // )} + // + // ); + // }; return ( - <> - - - - - - - - {shareableUrl ?? url} - - - - {renderButtons()} - + + + + + + + + {shareableUrl ?? url} + + + ); }; diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index c217d8a54025d3..d659a751577d3a 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -7,8 +7,8 @@ */ import { Capabilities } from '@kbn/core-capabilities-common'; -import React, { useCallback } from 'react'; -import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; +import React, { useState } from 'react'; +import { IModalTabDeclaration, TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { LocatorPublic, AnonymousAccessServiceContract } from '../../common'; @@ -60,32 +60,21 @@ export const ShareMenuTabs = ({ isDirty, isEmbedded, }: ShareContextTabProps) => { - const buttonHandler = useCallback( - () => [ - { - id: 'link', - dataTestSubj: 'copyShareUrlButton', - formattedMessageId: 'share.link.copyLinkButton', - defaultMessage: 'Copy link', - }, - { - id: 'embed', - dataTestSubj: 'copyEmbedUrlButton', - formattedMessageId: 'share.link.copyEmbedCodeButton', - defaultMessage: 'Copy Embed', - }, - { - id: 'export', - dataTestSubj: 'generateExportButton', - formattedMessageId: 'share.link.generateExportButton', - defaultMessage: 'Generate export', - }, - ], - [] - ); + const [linkData, setLinkData] = useState(undefined); + const [embedData, setEmbedData] = useState<{} | undefined>(undefined); const getTabs = () => { - const tabs = []; + const tabs: Array< + IModalTabDeclaration<{ + shortUrlCache: string; + url: string; + urlParams(urlParams: any): unknown; + id: string; + dataTestSubj: string; + defaultMessage: string; + handler: () => void; + }> + > = []; tabs.push({ id: 'link', name: i18n.translate('share.contextMenu.permalinksTab', { @@ -93,8 +82,22 @@ export const ShareMenuTabs = ({ }), // do not break functional tests 'data-test-subj': 'Permalinks', - modalActionBtn: buttonHandler().filter(({ id }) => id === 'link')[0], + modalActionBtn: { + id: 'link', + dataTestSubj: 'copyShareUrlButton', + formattedMessageId: 'share.link.copyLinkButton', + defaultMessage: 'Copy link', + handler: ({ state }) => { + setLinkData(state.shortUrlCache ?? state.url); + }, + }, title: `Share this ${objectType}`, + description: ( + + ), content: () => ( id === 'link')[0]} urlService={urlService} /> ), + reducer(state, action) { + switch (action.type) { + default: + return state; + } + }, }); shareMenuItems.map(({ shareMenuItem, panel }) => { tabs.push({ ...shareMenuItem, - id: panel.id, - buttonHandler: buttonHandler().filter(({ id }) => id === id)[0], + id: panel.id.toString(), + title: `Share this ${objectType}`, + modalActionBtn: { + id: 'export', + dataTestSubj: 'generateExportButton', + formattedMessageId: 'share.link.generateExportButton', + defaultMessage: 'Generate export', + }, + reducer(state, action) { + switch (action.type) { + default: + return state; + } + }, }); }); @@ -122,46 +142,47 @@ export const ShareMenuTabs = ({ name: i18n.translate('share.contextMenu.embedCodeTab', { defaultMessage: 'Embed', }), - ModalActionBtn: buttonHandler().filter(({ id }) => id === 'embed')[0], - content: () => ( - id === 'embed')[0]} + description: ( + ), + title: `Share this ${objectType}`, + modalActionBtn: { + id: 'embed', + dataTestSubj: 'copyEmbedUrlButton', + formattedMessageId: 'share.link.copyEmbedCodeButton', + defaultMessage: 'Copy Embed', + handler: ({ state }) => { + setEmbedData(state.urlParams); + }, + }, + content: ({ state, dispatch }) => { + const onChange = (optionId) => { + const newCheckboxIdToSelectedMap = { + embedUrlParamExtensions, + }; + + dispatch({ + type: '0', + payload: newCheckboxIdToSelectedMap, + }); + }; + return ( + + ); + }, + reducer(state, action) { + switch (action.type) { + default: + return state; + } + }, }); } return tabs; }; - const getModalBodyDescriptions = () => [ - { - id: 'dashboard-link', - description: ( - - ), - }, - { - id: 'dashboard-embed', - description: ( - - ), - }, - ]; - - return ( - - ); + return ; }; From a4c6b677aa99c3276019826c74948a8f33a10058 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 11 Mar 2024 15:37:44 -0600 Subject: [PATCH 20/69] wip --- packages/shared-ux/modal/tabbed/src/context/index.tsx | 2 +- packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx | 4 ++-- src/plugins/share/public/components/embed_modal.tsx | 5 +---- src/plugins/share/public/components/share_tabs.tsx | 9 ++++++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 9d5771b0bd070a..2ec9696e280836 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -114,7 +114,7 @@ export function ModalContextProvider tabs.reduce((result, { id, reducer, initialState, ...rest }) => { initialModalState.current[id] = initialState ?? {}; - modalTabDefinitions.current.push({ id, ...rest }); + modalTabDefinitions.current.push({ id, reducer, ...rest }); result[id] = reducer; return result; }, {}), diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 67f6b1ff8d8724..f3fbcfc0fb7be4 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -80,14 +80,14 @@ const TabbedModalInner: FC = ({ onClose }) => { {renderTabs()} - {React.createElement(SelectedTabContent, { + {React.createElement(SelectedTabContent!, { state: selectedTabState, dispatch, })} - + {(copy) => ( diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx index 4bacdca3f99cd2..c13ed84cd56b0b 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { format as formatUrl, parse as parseUrl } from 'url'; -import { AnonymousAccessState } from '../../common'; +import { AnonymousAccessState, LocatorPublic } from '../../common'; import { UrlParamExtension } from '../types'; interface EmbedProps { @@ -22,7 +22,6 @@ interface EmbedProps { params: any; } | undefined; - urlService: BrowserUrlService; shareableUrlForSavedObject?: string; shareableUrl?: string; isEmbedded?: boolean; @@ -40,8 +39,6 @@ export enum ExportUrlAsType { export const EmbedModal = ({ urlParamExtensions, - shareableUrlLocatorParams, - urlService, shareableUrlForSavedObject, shareableUrl, isEmbedded, diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index d659a751577d3a..82c7ea8f33a9e9 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -169,12 +169,15 @@ export const ShareMenuTabs = ({ payload: newCheckboxIdToSelectedMap, }); }; - return ( - - ); + return ; }, reducer(state, action) { switch (action.type) { + case String(1): + return { + ...state, + urlParams: action.payload, + }; default: return state; } From 9c5a774583929328b027c0c05fcff9e51471cf1e Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 12 Mar 2024 08:51:08 -0600 Subject: [PATCH 21/69] wip for collab --- .../modal/tabbed/src/context/index.tsx | 3 +- .../modal/tabbed/src/tabbed_modal.tsx | 49 ++++++++++++++++--- .../share/public/components/embed_modal.tsx | 6 +++ .../share/public/components/share_tabs.tsx | 23 ++++----- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 2ec9696e280836..2bbfc04a93f3c3 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -43,7 +43,8 @@ interface IModalTabActionBtn extends CommonProps { dataTestSubj: string; defaultMessage: string; formattedMessageId: string; - handler?: (args: { state: S }) => void; + handler: (args: { state: S }) => string; + isCopy?: boolean; } export interface IModalTabDeclaration extends EuiTabProps { diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index f3fbcfc0fb7be4..325fefb962859a 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -43,7 +43,7 @@ const TabbedModalInner: FC = ({ onClose }) => { const { content: SelectedTabContent, description: string, - modalActionBtn: { defaultMessage, handler, dataTestSubj, formattedMessageId }, + modalActionBtn: { defaultMessage, handler, dataTestSubj, formattedMessageId, isCopy }, title, } = useMemo(() => { return tabs.find((obj) => obj.id === selectedTabId)!; @@ -68,6 +68,28 @@ const TabbedModalInner: FC = ({ onClose }) => { )); }; + const tempHandler = useMemo( + () => (isCopy ? handler({ state: selectedTabState }) : ''), + [selectedTabState, isCopy] + ); + + const renderButton = () => { + console.log('temp handler', tempHandler); + return isCopy ? ( + + {(copy) => ( + + + + )} + + ) : ( + + + + ); + }; + // const btnClickHandler = useCallback(() => { // handler!({ state: selectedTabState }); // }, [handler, selectedTabState]); @@ -87,13 +109,24 @@ const TabbedModalInner: FC = ({ onClose }) => { - - {(copy) => ( - - - - )} - + {isCopy ? ( + + {(copy) => ( + + + + )} + + ) : ( + + + + )} ); diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/embed_modal.tsx index c13ed84cd56b0b..cc13713a375508 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/embed_modal.tsx @@ -25,6 +25,7 @@ interface EmbedProps { shareableUrlForSavedObject?: string; shareableUrl?: string; isEmbedded?: boolean; + onChange: (optionId: string) => void; } interface UrlParams { [extensionName: string]: { @@ -42,6 +43,7 @@ export const EmbedModal = ({ shareableUrlForSavedObject, shareableUrl, isEmbedded, + onChange, }: EmbedProps) => { const isMounted = useMountedState(); const [urlParams, setUrlParams] = useState(undefined); @@ -52,6 +54,10 @@ export const EmbedModal = ({ const [anonymousAccessParameters] = useState(null); const [usePublicUrl] = useState(false); + useEffect(() => { + onChange(url); + }, [url]); + const getUrlParamExtensions = useCallback( (tempUrl: string): string => { return urlParams diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 82c7ea8f33a9e9..50682ed887ca53 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -11,6 +11,7 @@ import React, { useState } from 'react'; import { IModalTabDeclaration, TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { copyToClipboard } from '@elastic/eui'; import { LocatorPublic, AnonymousAccessServiceContract } from '../../common'; import { ShareMenuItem, UrlParamExtension, BrowserUrlService } from '../types'; import { LinkModal } from './link_modal'; @@ -72,7 +73,7 @@ export const ShareMenuTabs = ({ id: string; dataTestSubj: string; defaultMessage: string; - handler: () => void; + handler: () => string; }> > = []; tabs.push({ @@ -87,9 +88,7 @@ export const ShareMenuTabs = ({ dataTestSubj: 'copyShareUrlButton', formattedMessageId: 'share.link.copyLinkButton', defaultMessage: 'Copy link', - handler: ({ state }) => { - setLinkData(state.shortUrlCache ?? state.url); - }, + handler: ({ state }) => copyToClipboard(state.shortUrlCache), }, title: `Share this ${objectType}`, description: ( @@ -155,28 +154,24 @@ export const ShareMenuTabs = ({ formattedMessageId: 'share.link.copyEmbedCodeButton', defaultMessage: 'Copy Embed', handler: ({ state }) => { - setEmbedData(state.urlParams); + copyToClipboard(state.url); }, }, content: ({ state, dispatch }) => { - const onChange = (optionId) => { - const newCheckboxIdToSelectedMap = { - embedUrlParamExtensions, - }; - + const onChange = (shareUrl: string) => { dispatch({ - type: '0', - payload: newCheckboxIdToSelectedMap, + type: '1', + payload: shareUrl, }); }; - return ; + return ; }, reducer(state, action) { switch (action.type) { case String(1): return { ...state, - urlParams: action.payload, + url: action.payload, }; default: return state; From 4ff4dcd842a57ca57acd1ce564c2adbd299c0ef2 Mon Sep 17 00:00:00 2001 From: Eyo Okon Eyo Date: Tue, 19 Mar 2024 22:57:13 +0100 Subject: [PATCH 22/69] rework implementation for v2 share --- .../modal/tabbed/src/context/index.tsx | 5 +- .../modal/tabbed/src/tabbed_modal.stories.tsx | 1 - .../modal/tabbed/src/tabbed_modal.tsx | 65 ++---- .../share/public/components/context/index.tsx | 33 +++ .../share/public/components/share_tabs.tsx | 204 ++++-------------- .../embed/embed_content.tsx} | 37 ++-- .../public/components/tabs/embed/index.tsx | 76 +++++++ .../public/components/tabs/export/index.tsx | 7 + .../share/public/components/tabs/index.ts | 10 + .../public/components/tabs/link/index.tsx | 97 +++++++++ .../link/link_content.tsx} | 78 +++---- .../public/services/share_menu_manager.tsx | 48 +++-- 12 files changed, 357 insertions(+), 304 deletions(-) create mode 100644 src/plugins/share/public/components/context/index.tsx rename src/plugins/share/public/components/{embed_modal.tsx => tabs/embed/embed_content.tsx} (92%) create mode 100644 src/plugins/share/public/components/tabs/embed/index.tsx create mode 100644 src/plugins/share/public/components/tabs/export/index.tsx create mode 100644 src/plugins/share/public/components/tabs/index.ts create mode 100644 src/plugins/share/public/components/tabs/link/index.tsx rename src/plugins/share/public/components/{link_modal.tsx => tabs/link/link_content.tsx} (76%) diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 2bbfc04a93f3c3..d08e388c9de68d 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -27,7 +27,7 @@ interface IDispatchAction { export type IModalTabState = Record; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type IModalMetaState = { +export type IModalMetaState = { selectedTabId: string | null; }; @@ -43,13 +43,12 @@ interface IModalTabActionBtn extends CommonProps { dataTestSubj: string; defaultMessage: string; formattedMessageId: string; - handler: (args: { state: S }) => string; + handler: (args: { state: S }) => void; isCopy?: boolean; } export interface IModalTabDeclaration extends EuiTabProps { id: string; - title?: string; name: string; initialState?: Partial; reducer?: IReducer; diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx index 1f45d47b26fce4..abb34c6f31edf9 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx @@ -108,7 +108,6 @@ export const NonTrivialExample = (params: TabbedModalStorybookParams) => { }> = { id: 'order', name: 'order', - title: 'Pizza of choice', initialState: { checkboxIdToSelectedMap: { [checkboxGroupItemId2]: true, diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 325fefb962859a..f0781672f5c975 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useMemo, Fragment, type ComponentProps, type FC } from 'react'; +import React, { useMemo, useCallback, Fragment, type ComponentProps, type FC } from 'react'; import { EuiButton, EuiModal, @@ -16,7 +16,6 @@ import { EuiModalHeaderTitle, EuiTabs, EuiTab, - EuiCopy, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -31,7 +30,7 @@ interface ITabbedModalInner extends Pick, 'onClo modalTitle?: string; } -const TabbedModalInner: FC = ({ onClose }) => { +const TabbedModalInner: FC = ({ onClose, modalTitle }) => { const { tabs, state, dispatch } = useModalContext(); const selectedTabId = state.meta.selectedTabId; @@ -42,9 +41,7 @@ const TabbedModalInner: FC = ({ onClose }) => { const { content: SelectedTabContent, - description: string, - modalActionBtn: { defaultMessage, handler, dataTestSubj, formattedMessageId, isCopy }, - title, + modalActionBtn: { defaultMessage, handler, dataTestSubj, formattedMessageId }, } = useMemo(() => { return tabs.find((obj) => obj.id === selectedTabId)!; }, [selectedTabId, tabs]); @@ -68,36 +65,14 @@ const TabbedModalInner: FC = ({ onClose }) => { )); }; - const tempHandler = useMemo( - () => (isCopy ? handler({ state: selectedTabState }) : ''), - [selectedTabState, isCopy] - ); - - const renderButton = () => { - console.log('temp handler', tempHandler); - return isCopy ? ( - - {(copy) => ( - - - - )} - - ) : ( - - - - ); - }; - - // const btnClickHandler = useCallback(() => { - // handler!({ state: selectedTabState }); - // }, [handler, selectedTabState]); + const btnClickHandler = useCallback(() => { + handler!({ state: selectedTabState }); + }, [handler, selectedTabState]); return ( - {title} + {modalTitle} @@ -109,24 +84,14 @@ const TabbedModalInner: FC = ({ onClose }) => { - {isCopy ? ( - - {(copy) => ( - - - - )} - - ) : ( - - - - )} + + + ); diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx new file mode 100644 index 00000000000000..5ec9f8d1734abf --- /dev/null +++ b/src/plugins/share/public/components/context/index.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createContext, useContext } from 'react'; + +import { AnonymousAccessServiceContract } from '../../../common'; +import type { + ShareMenuItem, + UrlParamExtension, + BrowserUrlService, + ShareContext, +} from '../../types'; + +export interface IShareContext extends ShareContext { + allowEmbed: boolean; + allowShortUrl: boolean; + shareMenuItems: ShareMenuItem[]; + embedUrlParamExtensions?: UrlParamExtension[]; + anonymousAccess?: AnonymousAccessServiceContract; + urlService: BrowserUrlService; + snapshotShareWarning?: string; + objectTypeTitle?: string; + isEmbedded: boolean; +} + +export const ShareTabsContext = createContext(null); + +export const useShareTabsContext = () => useContext(ShareTabsContext); diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 50682ed887ca53..6fadd255faf8aa 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -6,181 +6,57 @@ * Side Public License, v 1. */ -import { Capabilities } from '@kbn/core-capabilities-common'; -import React, { useState } from 'react'; -import { IModalTabDeclaration, TabbedModal } from '@kbn/shared-ux-tabbed-modal'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { copyToClipboard } from '@elastic/eui'; -import { LocatorPublic, AnonymousAccessServiceContract } from '../../common'; -import { ShareMenuItem, UrlParamExtension, BrowserUrlService } from '../types'; -import { LinkModal } from './link_modal'; -import { EmbedModal } from './embed_modal'; +import React, { type FC } from 'react'; +import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; + +import { ShareTabsContext, useShareTabsContext, type IShareContext } from './context'; +import { linkTab, embedTab } from './tabs'; + +export const ShareMenuV2: FC<{ shareContext: IShareContext }> = ({ shareContext }) => { + return ( + + + + ); +}; + +// this file is intended to replace share_context_menu +export const ShareMenuTabs = () => { + const shareContext = useShareTabsContext(); -export interface ModalTabActionHandler { - id: string; - dataTestSubj: string; - formattedMessageId: string; - defaultMessage: string; -} + if (!shareContext) { + return null; + } -export interface ShareContextTabProps { - allowEmbed: boolean; - allowShortUrl: boolean; - objectId?: string; - objectType: string; - shareableUrl?: string; - shareableUrlForSavedObject?: string; - shareableUrlLocatorParams?: { - locator: LocatorPublic; - params: any; - }; - shareMenuItems: ShareMenuItem[]; - sharingData: any; - onClose: () => void; - embedUrlParamExtensions?: UrlParamExtension[]; - anonymousAccess?: AnonymousAccessServiceContract; - showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean; - urlService: BrowserUrlService; - snapshotShareWarning?: string; - objectTypeTitle?: string; - disabledShareUrl?: boolean; - isDirty: boolean; - isEmbedded: boolean; -} + const { shareMenuItems, allowEmbed, objectType, onClose } = shareContext; -// this file is intended to replace share_context_menu -export const ShareMenuTabs = ({ - allowEmbed, - shareMenuItems, - urlService, - onClose, - objectType, - embedUrlParamExtensions, - objectId, - isDirty, - isEmbedded, -}: ShareContextTabProps) => { - const [linkData, setLinkData] = useState(undefined); - const [embedData, setEmbedData] = useState<{} | undefined>(undefined); + const tabs = [linkTab, allowEmbed ? embedTab : null].filter(Boolean); - const getTabs = () => { - const tabs: Array< - IModalTabDeclaration<{ - shortUrlCache: string; - url: string; - urlParams(urlParams: any): unknown; - id: string; - dataTestSubj: string; - defaultMessage: string; - handler: () => string; - }> - > = []; + shareMenuItems.forEach(({ shareMenuItem, panel }) => { tabs.push({ - id: 'link', - name: i18n.translate('share.contextMenu.permalinksTab', { - defaultMessage: 'Links', - }), - // do not break functional tests - 'data-test-subj': 'Permalinks', + ...shareMenuItem, + id: panel.id.toString(), modalActionBtn: { - id: 'link', - dataTestSubj: 'copyShareUrlButton', - formattedMessageId: 'share.link.copyLinkButton', - defaultMessage: 'Copy link', - handler: ({ state }) => copyToClipboard(state.shortUrlCache), + id: 'export', + dataTestSubj: 'generateExportButton', + formattedMessageId: 'share.link.generateExportButton', + defaultMessage: 'Generate export', }, - title: `Share this ${objectType}`, - description: ( - - ), - content: () => ( - - ), - reducer(state, action) { + reducer({ state, action }) { switch (action.type) { default: return state; } }, }); - - shareMenuItems.map(({ shareMenuItem, panel }) => { - tabs.push({ - ...shareMenuItem, - id: panel.id.toString(), - title: `Share this ${objectType}`, - modalActionBtn: { - id: 'export', - dataTestSubj: 'generateExportButton', - formattedMessageId: 'share.link.generateExportButton', - defaultMessage: 'Generate export', - }, - reducer(state, action) { - switch (action.type) { - default: - return state; - } - }, - }); - }); - - if (allowEmbed) { - tabs.push({ - id: 'embed', - name: i18n.translate('share.contextMenu.embedCodeTab', { - defaultMessage: 'Embed', - }), - description: ( - - ), - title: `Share this ${objectType}`, - modalActionBtn: { - id: 'embed', - dataTestSubj: 'copyEmbedUrlButton', - formattedMessageId: 'share.link.copyEmbedCodeButton', - defaultMessage: 'Copy Embed', - handler: ({ state }) => { - copyToClipboard(state.url); - }, - }, - content: ({ state, dispatch }) => { - const onChange = (shareUrl: string) => { - dispatch({ - type: '1', - payload: shareUrl, - }); - }; - return ; - }, - reducer(state, action) { - switch (action.type) { - case String(1): - return { - ...state, - url: action.payload, - }; - default: - return state; - } - }, - }); - } - return tabs; - }; - - return ; + }); + + return ( + + ); }; diff --git a/src/plugins/share/public/components/embed_modal.tsx b/src/plugins/share/public/components/tabs/embed/embed_content.tsx similarity index 92% rename from src/plugins/share/public/components/embed_modal.tsx rename to src/plugins/share/public/components/tabs/embed/embed_content.tsx index cc13713a375508..40d339b0bba719 100644 --- a/src/plugins/share/public/components/embed_modal.tsx +++ b/src/plugins/share/public/components/tabs/embed/embed_content.tsx @@ -11,22 +11,21 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { format as formatUrl, parse as parseUrl } from 'url'; -import { AnonymousAccessState, LocatorPublic } from '../../common'; -import { UrlParamExtension } from '../types'; - -interface EmbedProps { - urlParamExtensions?: UrlParamExtension[]; - shareableUrlLocatorParams?: - | { - locator: LocatorPublic; - params: any; - } - | undefined; - shareableUrlForSavedObject?: string; - shareableUrl?: string; - isEmbedded?: boolean; - onChange: (optionId: string) => void; -} +import { AnonymousAccessState } from '../../../../common'; + +import { type IShareContext } from '../../context'; + +type EmbedProps = Pick< + IShareContext, + | 'shareableUrlLocatorParams' + | 'shareableUrlForSavedObject' + | 'shareableUrl' + | 'isEmbedded' + | 'embedUrlParamExtensions' +> & { + onChange: (url: string) => void; +}; + interface UrlParams { [extensionName: string]: { [queryParam: string]: boolean; @@ -38,8 +37,8 @@ export enum ExportUrlAsType { EXPORT_URL_AS_SNAPSHOT = 'snapshot', } -export const EmbedModal = ({ - urlParamExtensions, +export const EmbedContent = ({ + embedUrlParamExtensions: urlParamExtensions, shareableUrlForSavedObject, shareableUrl, isEmbedded, @@ -56,7 +55,7 @@ export const EmbedModal = ({ useEffect(() => { onChange(url); - }, [url]); + }, [url, onChange]); const getUrlParamExtensions = useCallback( (tempUrl: string): string => { diff --git a/src/plugins/share/public/components/tabs/embed/index.tsx b/src/plugins/share/public/components/tabs/embed/index.tsx new file mode 100644 index 00000000000000..f5b17a240277c2 --- /dev/null +++ b/src/plugins/share/public/components/tabs/embed/index.tsx @@ -0,0 +1,76 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { copyToClipboard } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; +import { EmbedContent } from './embed_content'; +import { useShareTabsContext } from '../../context'; + +type IEmbedTab = IModalTabDeclaration<{ url: string }>; + +const embedTabReducer: IEmbedTab['reducer'] = (state, action) => { + switch (action.type) { + case String(1): + return { + ...state, + url: action.payload, + }; + default: + return state; + } +}; + +const EmbedTabContent: NonNullable = ({ dispatch }) => { + const { embedUrlParamExtensions, shareableUrlForSavedObject, shareableUrl, isEmbedded } = + useShareTabsContext()!; + const onChange = (shareUrl: string) => { + dispatch({ + type: '1', + payload: shareUrl, + }); + }; + + return ( + + ); +}; + +export const embedTab: IEmbedTab = { + id: 'embed', + name: i18n.translate('share.contextMenu.embedCodeTab', { + defaultMessage: 'Embed', + }), + description: ( + + ), + reducer: embedTabReducer, + content: EmbedTabContent, + modalActionBtn: { + id: 'embed', + dataTestSubj: 'copyEmbedUrlButton', + formattedMessageId: 'share.link.copyEmbedCodeButton', + defaultMessage: 'Copy Embed', + handler: ({ state }) => { + copyToClipboard(state.url); + }, + }, +}; diff --git a/src/plugins/share/public/components/tabs/export/index.tsx b/src/plugins/share/public/components/tabs/export/index.tsx new file mode 100644 index 00000000000000..5c2d5b68ae2e03 --- /dev/null +++ b/src/plugins/share/public/components/tabs/export/index.tsx @@ -0,0 +1,7 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ diff --git a/src/plugins/share/public/components/tabs/index.ts b/src/plugins/share/public/components/tabs/index.ts new file mode 100644 index 00000000000000..a5855495aaec55 --- /dev/null +++ b/src/plugins/share/public/components/tabs/index.ts @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { linkTab } from './link'; +export { embedTab } from './embed'; diff --git a/src/plugins/share/public/components/tabs/link/index.tsx b/src/plugins/share/public/components/tabs/link/index.tsx new file mode 100644 index 00000000000000..0dedbed1223756 --- /dev/null +++ b/src/plugins/share/public/components/tabs/link/index.tsx @@ -0,0 +1,97 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { copyToClipboard } from '@elastic/eui'; +import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; +import { useShareTabsContext } from '../../context'; +import { LinkContent } from './link_content'; + +type ILinkTab = IModalTabDeclaration<{ + dashboardUrl: string; +}>; + +const LINK_TAB_ACTION = { + SET_DASHBOARD_URL: 'SET_DASHBOARD_URL', +}; + +const linkTabReducer: ILinkTab['reducer'] = ( + state = { + dashboardUrl: '', + }, + action +) => { + switch (action.type) { + case LINK_TAB_ACTION.SET_DASHBOARD_URL: + return { + ...state, + dashboardUrl: action.payload, + }; + default: + return state; + } +}; + +const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => { + const { + objectType, + objectId, + isDirty, + isEmbedded, + shareableUrl, + shareableUrlForSavedObject, + urlService, + shareableUrlLocatorParams, + } = useShareTabsContext()!; + + const setDashboardLink = (url: string) => { + dispatch({ type: LINK_TAB_ACTION.SET_DASHBOARD_URL, payload: url }); + }; + + return ( + + ); +}; + +export const linkTab: ILinkTab = { + id: 'link', + name: i18n.translate('share.contextMenu.permalinksTab', { + defaultMessage: 'Links', + }), + description: ( + + ), + content: LinkTabContent, + reducer: linkTabReducer, + modalActionBtn: { + id: 'link', + dataTestSubj: 'copyShareUrlButton', + formattedMessageId: 'share.link.copyLinkButton', + defaultMessage: 'Copy link', + handler: ({ state }) => { + copyToClipboard(state.dashboardUrl); + }, + }, +}; diff --git a/src/plugins/share/public/components/link_modal.tsx b/src/plugins/share/public/components/tabs/link/link_content.tsx similarity index 76% rename from src/plugins/share/public/components/link_modal.tsx rename to src/plugins/share/public/components/tabs/link/link_content.tsx index 74e9e2e33b805c..b2132eb244663c 100644 --- a/src/plugins/share/public/components/link_modal.tsx +++ b/src/plugins/share/public/components/tabs/link/link_content.tsx @@ -11,23 +11,21 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { format as formatUrl, parse as parseUrl } from 'url'; -import type { LocatorPublic } from '../../common'; -import { BrowserUrlService } from '../types'; - -interface LinkProps { - objectType: string; - objectId?: string; - isDirty: boolean; - isEmbedded: boolean; - shareableUrlForSavedObject?: string; - shareableUrl?: string; - onClose: () => void; - urlService: BrowserUrlService; - shareableUrlLocatorParams?: { - locator: LocatorPublic; - params: any; - }; -} +import { IShareContext } from '../../context'; + +type LinkProps = Pick< + IShareContext, + | 'objectType' + | 'objectId' + | 'isDirty' + | 'isEmbedded' + | 'urlService' + | 'shareableUrl' + | 'shareableUrlForSavedObject' + | 'shareableUrlLocatorParams' +> & { + setDashboardLink: (url: string) => void; +}; interface UrlParams { [extensionName: string]: { @@ -35,7 +33,7 @@ interface UrlParams { }; } -export const LinkModal = ({ +export const LinkContent = ({ objectType, objectId, isDirty, @@ -44,12 +42,17 @@ export const LinkModal = ({ shareableUrlForSavedObject, urlService, shareableUrlLocatorParams, + setDashboardLink, }: LinkProps) => { const isMounted = useMountedState(); const [url, setUrl] = useState(''); const [urlParams] = useState(undefined); const [shortUrlCache, setShortUrlCache] = useState(undefined); + // useEffect(() => { + // setDashboardLink(url); + // }, [setDashboardLink, url]); + const isNotSaved = useCallback(() => { return objectId === undefined || objectId === '' || isDirty; }, [objectId, isDirty]); @@ -67,19 +70,19 @@ export const LinkModal = ({ const getUrlParamExtensions = useCallback( (tempUrl: string): string => { - return urlParams - ? Object.keys(urlParams).reduce((urlAccumulator, key) => { - const urlParam = urlParams[key]; - return urlParam - ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { - const isQueryParamEnabled = urlParam[queryParam]; - return isQueryParamEnabled - ? queryAccumulator + `&${queryParam}=true` - : queryAccumulator; - }, urlAccumulator) - : urlAccumulator; - }, tempUrl) - : tempUrl; + if (!urlParams) return tempUrl; + + return Object.keys(urlParams).reduce((urlAccumulator, key) => { + const urlParam = urlParams[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, tempUrl); }, [urlParams] ); @@ -169,19 +172,6 @@ export const LinkModal = ({ setUrlHelper(); }, [isMounted, setUrlHelper]); - // const renderButtons = () => { - // const { formattedMessageId, defaultMessage, dataTestSubj } = action; - // return ( - // - // {(copy) => ( - // - // - // - // )} - // - // ); - // }; - return ( diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 726758a1ba8a96..7c3d7554c0ba20 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -18,7 +18,7 @@ import { ShareMenuItem, ShowShareMenuOptions } from '../types'; import { ShareMenuRegistryStart } from './share_menu_registry'; import { AnonymousAccessServiceContract } from '../../common/anonymous_access'; import type { BrowserUrlService } from '../types'; -import { ShareMenuTabs } from '../components/share_tabs'; +import { ShareMenuV2 } from '../components/share_tabs'; import { ShareContextMenu } from '../components/share_context_menu'; export class ShareMenuManager { @@ -152,29 +152,31 @@ export class ShareMenuManager { const openModal = () => { const session = overlays.openModal( toMountPoint( - { - onClose(); - session.close(); + { + onClose(); + session.close(); + }, }} - embedUrlParamExtensions={embedUrlParamExtensions} - anonymousAccess={anonymousAccess} - showPublicUrlSwitch={showPublicUrlSwitch} - urlService={urlService} - snapshotShareWarning={snapshotShareWarning} - disabledShareUrl={disabledShareUrl} - isDirty={isDirty} - isEmbedded={allowEmbed} />, { i18n, theme } ), From e4865e8a14d0fced119c2ec4a56fd3191504aef3 Mon Sep 17 00:00:00 2001 From: Eyo Okon Eyo Date: Wed, 20 Mar 2024 17:12:45 +0100 Subject: [PATCH 23/69] fix issue with switching state within tabbed modal --- .../modal/tabbed/src/context/index.tsx | 36 ++++++++++--------- .../modal/tabbed/src/tabbed_modal.tsx | 6 ++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index d08e388c9de68d..5fc529c0164c77 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -93,12 +93,12 @@ const modalMetaReducer: IReducer = (state, action) => { export type IModalContextProviderProps>> = PropsWithChildren<{ tabs: Tabs; - selectedTabId: Tabs[number]['id']; + defaultSelectedTabId: Tabs[number]['id']; }>; export function ModalContextProvider>>({ tabs, - selectedTabId, + defaultSelectedTabId, children, }: IModalContextProviderProps) { const modalTabDefinitions = useRef([]); @@ -106,7 +106,7 @@ export function ModalContextProvider({ // instantiate state with default meta information meta: { - selectedTabId, + selectedTabId: defaultSelectedTabId, }, }); @@ -121,22 +121,24 @@ export function ModalContextProvider>) { - return (state: IModalContext['state'], action: IDispatchAction) => { - const newState = { ...state }; + const combineReducers = useCallback(function ( + reducers: Record> + ) { + return (state: IModalContext['state'], action: IDispatchAction) => { + const newState = { ...state }; - if (/^meta_/i.test(action.type)) { - newState.meta = modalMetaReducer(newState.meta, action); - } else { - newState[selectedTabId] = reducers[selectedTabId](newState[selectedTabId], action); - } + if (/^meta_/i.test(action.type)) { + newState.meta = modalMetaReducer(newState.meta, action); + } else { + const selectedTabId = state.meta.selectedTabId!; - return newState; - }; - }, - [selectedTabId] - ); + newState[selectedTabId] = reducers[selectedTabId](newState[selectedTabId], action); + } + + return newState; + }; + }, + []); const createInitialState = useCallback((state: IModalContext['state']) => { return state; diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index f0781672f5c975..28e74a9c1b98cd 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -66,7 +66,7 @@ const TabbedModalInner: FC = ({ onClose, modalTitle }) => { }; const btnClickHandler = useCallback(() => { - handler!({ state: selectedTabState }); + handler({ state: selectedTabState }); }, [handler, selectedTabState]); return ( @@ -99,11 +99,11 @@ const TabbedModalInner: FC = ({ onClose, modalTitle }) => { export function TabbedModal>>({ tabs, - selectedTabId, + defaultSelectedTabId, ...rest }: Omit, 'children'> & ITabbedModalInner) { return ( - + ); From bf4e6a4512f1f7013690832458ddf2734f647fb0 Mon Sep 17 00:00:00 2001 From: Eyo Okon Eyo Date: Wed, 20 Mar 2024 17:14:34 +0100 Subject: [PATCH 24/69] integrate link and embed modal into modal to receive correct value on copy attempt --- .../share/public/components/share_tabs.tsx | 2 +- .../components/tabs/embed/embed_content.tsx | 5 +++- .../public/components/tabs/embed/index.tsx | 26 ++++++++++++------- .../public/components/tabs/link/index.tsx | 16 +++++++----- .../components/tabs/link/link_content.tsx | 7 ++--- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 6fadd255faf8aa..674d57c571d642 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -56,7 +56,7 @@ export const ShareMenuTabs = () => { modalTitle={`Share this ${objectType}`} onClose={onClose} tabs={tabs} - selectedTabId="link" + defaultSelectedTabId="link" /> ); }; diff --git a/src/plugins/share/public/components/tabs/embed/embed_content.tsx b/src/plugins/share/public/components/tabs/embed/embed_content.tsx index 40d339b0bba719..4405f91cc44df4 100644 --- a/src/plugins/share/public/components/tabs/embed/embed_content.tsx +++ b/src/plugins/share/public/components/tabs/embed/embed_content.tsx @@ -102,6 +102,7 @@ export const EmbedContent = ({ const tempUrl = getSnapshotUrl(true); const parsedUrl = parseUrl(tempUrl); + if (!parsedUrl || !parsedUrl.hash) { return; } @@ -123,6 +124,7 @@ export const EmbedContent = ({ }, }), }); + return updateUrlParams(formattedUrl); }, [getSnapshotUrl, updateUrlParams]); @@ -196,7 +198,7 @@ export const EmbedContent = ({ const renderUrlParamExtensions = () => { if (!urlParamExtensions) { - return <>; + return null; } const setParamValue = @@ -216,6 +218,7 @@ export const EmbedContent = ({ ); }; + return ( diff --git a/src/plugins/share/public/components/tabs/embed/index.tsx b/src/plugins/share/public/components/tabs/embed/index.tsx index f5b17a240277c2..c0fe7e54292682 100644 --- a/src/plugins/share/public/components/tabs/embed/index.tsx +++ b/src/plugins/share/public/components/tabs/embed/index.tsx @@ -6,19 +6,23 @@ * Side Public License, v 1. */ -import React from 'react'; import { i18n } from '@kbn/i18n'; +import React, { useCallback } from 'react'; import { copyToClipboard } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; import { EmbedContent } from './embed_content'; import { useShareTabsContext } from '../../context'; +const EMBED_TAB_ACTIONS = { + SET_EMBED_URL: 'SET_EMBED_URL', +}; + type IEmbedTab = IModalTabDeclaration<{ url: string }>; -const embedTabReducer: IEmbedTab['reducer'] = (state, action) => { +const embedTabReducer: IEmbedTab['reducer'] = (state = { url: '' }, action) => { switch (action.type) { - case String(1): + case EMBED_TAB_ACTIONS.SET_EMBED_URL: return { ...state, url: action.payload, @@ -31,12 +35,16 @@ const embedTabReducer: IEmbedTab['reducer'] = (state, action) => { const EmbedTabContent: NonNullable = ({ dispatch }) => { const { embedUrlParamExtensions, shareableUrlForSavedObject, shareableUrl, isEmbedded } = useShareTabsContext()!; - const onChange = (shareUrl: string) => { - dispatch({ - type: '1', - payload: shareUrl, - }); - }; + + const onChange = useCallback( + (shareUrl: string) => { + dispatch({ + type: EMBED_TAB_ACTIONS.SET_EMBED_URL, + payload: shareUrl, + }); + }, + [dispatch] + ); return ( ; -const LINK_TAB_ACTION = { +const LINK_TAB_ACTIONS = { SET_DASHBOARD_URL: 'SET_DASHBOARD_URL', }; @@ -29,7 +29,7 @@ const linkTabReducer: ILinkTab['reducer'] = ( action ) => { switch (action.type) { - case LINK_TAB_ACTION.SET_DASHBOARD_URL: + case LINK_TAB_ACTIONS.SET_DASHBOARD_URL: return { ...state, dashboardUrl: action.payload, @@ -51,9 +51,12 @@ const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => { shareableUrlLocatorParams, } = useShareTabsContext()!; - const setDashboardLink = (url: string) => { - dispatch({ type: LINK_TAB_ACTION.SET_DASHBOARD_URL, payload: url }); - }; + const setDashboardLink = useCallback( + (url: string) => { + dispatch({ type: LINK_TAB_ACTIONS.SET_DASHBOARD_URL, payload: url }); + }, + [dispatch] + ); return ( { shareableUrlForSavedObject, urlService, shareableUrlLocatorParams, + dashboardLink: state.dashboardUrl, setDashboardLink, }} /> diff --git a/src/plugins/share/public/components/tabs/link/link_content.tsx b/src/plugins/share/public/components/tabs/link/link_content.tsx index b2132eb244663c..ab64698898573b 100644 --- a/src/plugins/share/public/components/tabs/link/link_content.tsx +++ b/src/plugins/share/public/components/tabs/link/link_content.tsx @@ -49,9 +49,10 @@ export const LinkContent = ({ const [urlParams] = useState(undefined); const [shortUrlCache, setShortUrlCache] = useState(undefined); - // useEffect(() => { - // setDashboardLink(url); - // }, [setDashboardLink, url]); + useEffect(() => { + // propagate url updates upwards to tab + setDashboardLink(url); + }, [setDashboardLink, url]); const isNotSaved = useCallback(() => { return objectId === undefined || objectId === '' || isDirty; From ca21906e978595edd5af07285d369e1a38aa2c4e Mon Sep 17 00:00:00 2001 From: Eyo Okon Eyo Date: Wed, 20 Mar 2024 18:25:48 +0100 Subject: [PATCH 25/69] visual styling for share modal --- packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx | 9 +++++++-- src/plugins/share/public/components/share_tabs.tsx | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 28e74a9c1b98cd..70ba265c3cdfa3 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -27,10 +27,11 @@ import { } from './context'; interface ITabbedModalInner extends Pick, 'onClose'> { + modalWidth?: number; modalTitle?: string; } -const TabbedModalInner: FC = ({ onClose, modalTitle }) => { +const TabbedModalInner: FC = ({ onClose, modalTitle, modalWidth }) => { const { tabs, state, dispatch } = useModalContext(); const selectedTabId = state.meta.selectedTabId; @@ -70,7 +71,11 @@ const TabbedModalInner: FC = ({ onClose, modalTitle }) => { }, [handler, selectedTabState]); return ( - + {modalTitle} diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 674d57c571d642..1ccf3ea2f42c76 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -53,9 +53,10 @@ export const ShareMenuTabs = () => { return ( ); From 0bde2f5677de55e617fa83528673dab8fa5d330e Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 21 Mar 2024 15:14:10 -0600 Subject: [PATCH 26/69] test1 --- .../modal/tabbed/src/context/index.tsx | 2 +- .../share/public/components/share_tabs.tsx | 27 +++------------- .../components/tabs/export/export_content.tsx | 19 +++++++++++ .../public/components/tabs/export/index.tsx | 32 +++++++++++++++++++ .../share/public/components/tabs/index.ts | 1 + 5 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 src/plugins/share/public/components/tabs/export/export_content.tsx diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 5fc529c0164c77..3636c63f029bbf 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -55,7 +55,7 @@ export interface IModalTabDeclaration extends EuiTabPr description?: ReactElement; 'data-test-subj'?: string; content?: IModalTabContent; - modalActionBtn: IModalTabActionBtn; + modalActionBtn?: IModalTabActionBtn; } interface IModalContext { diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 1ccf3ea2f42c76..33cbe4e18f773e 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -10,7 +10,7 @@ import React, { type FC } from 'react'; import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { ShareTabsContext, useShareTabsContext, type IShareContext } from './context'; -import { linkTab, embedTab } from './tabs'; +import { linkTab, embedTab, exportTab } from './tabs'; export const ShareMenuV2: FC<{ shareContext: IShareContext }> = ({ shareContext }) => { return ( @@ -28,28 +28,9 @@ export const ShareMenuTabs = () => { return null; } - const { shareMenuItems, allowEmbed, objectType, onClose } = shareContext; - - const tabs = [linkTab, allowEmbed ? embedTab : null].filter(Boolean); - - shareMenuItems.forEach(({ shareMenuItem, panel }) => { - tabs.push({ - ...shareMenuItem, - id: panel.id.toString(), - modalActionBtn: { - id: 'export', - dataTestSubj: 'generateExportButton', - formattedMessageId: 'share.link.generateExportButton', - defaultMessage: 'Generate export', - }, - reducer({ state, action }) { - switch (action.type) { - default: - return state; - } - }, - }); - }); + const { allowEmbed, objectType, onClose } = shareContext; + + const tabs = [linkTab, exportTab, allowEmbed ? embedTab : null].filter(Boolean); return ( & { + setExportType: (exportType: string) => void; +}; + +export const ExportContent = ({ shareMenuItems }: ExportProps) => { + // TODO: use share menu items to build export tab + + return null; +}; diff --git a/src/plugins/share/public/components/tabs/export/index.tsx b/src/plugins/share/public/components/tabs/export/index.tsx index 5c2d5b68ae2e03..5b06ecde245b7f 100644 --- a/src/plugins/share/public/components/tabs/export/index.tsx +++ b/src/plugins/share/public/components/tabs/export/index.tsx @@ -5,3 +5,35 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; +import { useShareTabsContext } from '../../context'; + +type IExportTab = IModalTabDeclaration<{ + exportType: string; +}>; + + +const ExportTabContent: IExportTab['content'] = () => { + const { shareMenuItems } = useShareTabsContext()!; + return shareMenuItems.forEach(({ shareMenuItem, panel }) => + panel.content + ); +}; + +export const exportTab: IExportTab = { + id: 'export', + name: i18n.translate('share.contextMenu.permalinksTab', { + defaultMessage: 'Export', + }), + description: ( + + ), + content: ExportTabContent, +}; diff --git a/src/plugins/share/public/components/tabs/index.ts b/src/plugins/share/public/components/tabs/index.ts index a5855495aaec55..200cb058daba57 100644 --- a/src/plugins/share/public/components/tabs/index.ts +++ b/src/plugins/share/public/components/tabs/index.ts @@ -8,3 +8,4 @@ export { linkTab } from './link'; export { embedTab } from './embed'; +export { exportTab } from './export'; From 7ef4cd4e3708d858729b2f514f05e74872b7a591 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 21 Mar 2024 16:31:43 -0600 Subject: [PATCH 27/69] test2 wip --- .../share_context_menu/csv_export_modal.tsx | 246 +++++++++++ .../share_context_menu/image_export_modal.tsx | 395 ++++++++++++++++++ .../public/share/share_context_menu/index.ts | 3 + .../register_csv_reporting.tsx | 10 +- .../register_pdf_png_reporting.tsx | 134 ++---- .../reporting_panel_content_lazy.tsx | 12 +- .../screen_capture_panel_content.tsx | 87 +--- .../screen_capture_panel_content_lazy.tsx | 2 +- .../share/shared/get_shared_components.tsx | 73 ++-- .../modal/tabbed/src/context/index.tsx | 6 +- .../modal/tabbed/src/tabbed_modal.tsx | 38 +- .../share/public/components/share_tabs.tsx | 17 +- .../components/tabs/export/export_content.tsx | 19 - .../public/components/tabs/export/index.tsx | 39 -- src/plugins/share/public/plugin.ts | 4 + x-pack/plugins/lens/kibana.jsonc | 1 + .../csv_download_panel_content_lazy.tsx | 6 +- .../csv_download_provider.tsx | 27 +- .../export_modal_content.tsx | 353 ++++++++++++++++ x-pack/plugins/lens/public/plugin.ts | 14 +- x-pack/plugins/reporting/public/plugin.ts | 2 + 21 files changed, 1183 insertions(+), 305 deletions(-) create mode 100644 packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx create mode 100644 packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx delete mode 100644 src/plugins/share/public/components/tabs/export/export_content.tsx delete mode 100644 src/plugins/share/public/components/tabs/export/index.tsx create mode 100644 x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx diff --git a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx new file mode 100644 index 00000000000000..24cc6829c3d41c --- /dev/null +++ b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx @@ -0,0 +1,246 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCopy, + EuiModalBody, + EuiModalFooter, + EuiSpacer, + EuiToolTip, + EuiIcon, +} from '@elastic/eui'; +import type { IUiSettingsClient, ThemeServiceSetup, ToastsSetup } from '@kbn/core/public'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import React, { FC, useEffect, useMemo, useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import url from 'url'; + +import { i18n } from '@kbn/i18n'; +import { BaseParams } from '@kbn/reporting-common/types'; +import { ErrorUrlTooLongPanel } from './reporting_panel_content/components'; +import { getMaxUrlLength } from './reporting_panel_content/constants'; +import { ReportingAPIClient } from '../..'; + +export interface CsvModalProps { + apiClient: ReportingAPIClient; + toasts: ToastsSetup; + uiSettings: IUiSettingsClient; + reportType: string; + requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. + getJobParams: (forShareUrl?: boolean) => Omit; + objectId?: string; + isDirty?: boolean; + onClose?: () => void; + theme: ThemeServiceSetup; + objectType: string; +} + +export type Props = CsvModalProps & { intl: InjectedIntl }; + +export const CsvModalContentUI: FC = (props: Props) => { + const isSaved = Boolean(props.objectId); + const { apiClient, getJobParams, intl, toasts, theme, onClose, objectType, reportType } = props; + const isMounted = useMountedState(); + const [createReportingJob, setCreatingReportJob] = useState(false); + const [absoluteUrl, setAbsoluteUrl] = useState(''); + const exceedsMaxLength = absoluteUrl.length >= getMaxUrlLength(); + + const getAbsoluteReportGenerationUrl = useMemo( + () => () => { + const relativePath = apiClient.getReportingPublicJobPath( + reportType, + apiClient.getDecoratedJobParams(getJobParams()) + ); + return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); + }, + [apiClient, getJobParams, reportType] + ); + + useEffect(() => { + const reportingUrl = new URL(window.location.origin); + reportingUrl.pathname = apiClient.getReportingPublicJobPath( + reportType, + apiClient.getDecoratedJobParams(getJobParams()) + ); + setAbsoluteUrl(reportingUrl.toString()); + }, [getAbsoluteReportGenerationUrl, apiClient, getJobParams, reportType]); + + const generateReportingJob = () => { + const decoratedJobParams = apiClient.getDecoratedJobParams(getJobParams()); + setCreatingReportJob(true); + return apiClient + .createReportingJob(reportType, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'share.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme$: theme.theme$ } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + if (isMounted()) { + setCreatingReportJob(false); + } + }) + .catch((error) => { + toasts.addError(error, { + title: intl.formatMessage({ + id: 'share.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + if (isMounted()) { + setCreatingReportJob(false); + } + }); + }; + + const renderCopyURLButton = ({ + isUnsaved, + }: { + isUnsaved: boolean; + exceedsMaxLength: boolean; + }) => { + if (isUnsaved && exceedsMaxLength) { + return ; + } else if (exceedsMaxLength) { + return ; + } + return isUnsaved ? ( + <> + + } + > + + {(copy) => ( + + + + )} + + + + } + > + + + + ) : ( + <> + + {(copy) => ( + + + + )} + + + + + + ); + }; + + return ( + <> + + + + + + {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} + {!isSaved ? ( + + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + + ) : ( + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + )} + + + ); +}; + +export const CsvModalContent = injectI18n(CsvModalContentUI); diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx new file mode 100644 index 00000000000000..3e310e6b84bc78 --- /dev/null +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -0,0 +1,395 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiIcon, + EuiButtonEmpty, + EuiCopy, + EuiFlexGroup, + EuiForm, + EuiFormRow, + EuiModalFooter, + EuiRadioGroup, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, + EuiToolTip, +} from '@elastic/eui'; +import type { IUiSettingsClient, ThemeServiceSetup, ToastsSetup } from '@kbn/core/public'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; +import url from 'url'; +import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { LayoutParams } from '@kbn/screenshotting-plugin/common'; +import type { JobAppParamsPDFV2 } from '@kbn/reporting-export-types-pdf-common'; +import { BaseParams } from '@kbn/reporting-common/types'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { ReportingAPIClient } from '../..'; +import { JobParamsProviderOptions } from '.'; +import { ErrorUrlTooLongPanel } from './reporting_panel_content/components'; +import { getMaxUrlLength } from './reporting_panel_content/constants'; + +export interface ReportingModalProps { + apiClient: ReportingAPIClient; + toasts: ToastsSetup; + uiSettings: IUiSettingsClient; + reportType?: string; + requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. + objectId?: string; + isDirty?: boolean; + onClose: () => void; + theme: ThemeServiceSetup; + layoutOption?: 'print' | 'canvas'; + // canvas breaks if required + jobProviderOptions?: JobParamsProviderOptions; + // needed for canvas + getJobParams?: JobAppParamsPDFV2; + objectType: string; +} + +type AppParams = Omit; + +export type Props = ReportingModalProps & { intl: InjectedIntl }; + +type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'printablePdf'; + +export const ReportingModalContentUI: FC = (props: Props) => { + const { + apiClient, + intl, + toasts, + theme, + onClose, + objectId, + layoutOption, + jobProviderOptions, + objectType, + } = props; + const isSaved = Boolean(objectId); + const [isStale, setIsStale] = useState(false); + const [createReportingJob, setCreatingReportJob] = useState(false); + const [selectedRadio, setSelectedRadio] = useState('printablePdfV2'); + const [usePrintLayout, setPrintLayout] = useState(false); + const [absoluteUrl, setAbsoluteUrl] = useState(''); + const isMounted = useMountedState(); + const exceedsMaxLength = absoluteUrl.length >= getMaxUrlLength(); + + const getJobsParams = useCallback( + (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { + if (!opts) { + return { ...props.getJobParams }; + } + + const { + sharingData: { title, layout, locatorParams }, + } = opts; + + const baseParams = { + objectType, + layout, + title, + }; + + if (type === 'printablePdfV2') { + // multi locator for PDF V2 + return { ...baseParams, locatorParams: [locatorParams] }; + } else if (type === 'pngV2') { + // single locator for PNG V2 + return { ...baseParams, locatorParams }; + } + + // Relative URL must have URL prefix (Spaces ID prefix), but not server basePath + // Replace hashes with original RISON values. + const relativeUrl = opts?.shareableUrl.replace( + window.location.origin + apiClient.getServerBasePath(), + '' + ); + + if (type === 'printablePdf') { + // multi URL for PDF + return { ...baseParams, relativeUrls: [relativeUrl] }; + } + + // single URL for PNG + return { ...baseParams, relativeUrl }; + }, + [apiClient, objectType, props.getJobParams] + ); + + const getLayout = useCallback((): LayoutParams => { + const { layout } = getJobsParams(selectedRadio, jobProviderOptions); + + let dimensions = layout?.dimensions; + + if (!dimensions) { + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + dimensions = { height, width }; + } + if (usePrintLayout) { + return { id: 'print', dimensions }; + } + + return { id: 'preserve_layout', dimensions }; + }, [getJobsParams, jobProviderOptions, selectedRadio, usePrintLayout]); + + const getJobParams = useCallback( + (shareableUrl?: boolean) => { + return { ...getJobsParams(selectedRadio, jobProviderOptions), layout: getLayout() }; + }, + [getJobsParams, getLayout, jobProviderOptions, selectedRadio] + ); + + const getAbsoluteReportGenerationUrl = useMemo( + () => () => { + if (getJobsParams(selectedRadio, jobProviderOptions) !== undefined) { + const relativePath = apiClient.getReportingPublicJobPath( + selectedRadio, + apiClient.getDecoratedJobParams(getJobParams(true) as unknown as AppParams) + ); + return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); + } + }, + [apiClient, getJobParams, selectedRadio, getJobsParams, jobProviderOptions] + ); + + const markAsStale = useCallback(() => { + if (!isMounted) return; + setIsStale(true); + }, [isMounted]); + + useEffect(() => { + getAbsoluteReportGenerationUrl(); + markAsStale(); + }, [markAsStale, getAbsoluteReportGenerationUrl]); + + const generateReportingJob = () => { + const decoratedJobParams = apiClient.getDecoratedJobParams( + getJobParams(false) as unknown as AppParams + ); + setCreatingReportJob(true); + return apiClient + .createReportingJob(selectedRadio, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'xpack.reporting.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme$: theme.theme$ } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + if (isMounted()) { + setCreatingReportJob(false); + } + }) + .catch((error) => { + toasts.addError(error, { + title: intl!.formatMessage({ + id: 'xpack.reporting.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + if (isMounted()) { + setCreatingReportJob(false); + } + }); + }; + + const handlePrintLayoutChange = (evt: EuiSwitchEvent) => { + setPrintLayout(evt.target.checked); + }; + + const renderOptions = () => { + if (layoutOption === 'print' && selectedRadio !== 'pngV2') { + return ( + <> + + } + checked={usePrintLayout} + onChange={handlePrintLayoutChange} + data-test-subj="usePrintLayout" + /> + + } + > + + + + ); + } + return null; + }; + const renderCopyURLButton = ({ + isUnsaved, + }: { + isUnsaved: boolean; + exceedsMaxLength: boolean; + }) => { + if (isUnsaved && exceedsMaxLength) { + return ; + } else if (exceedsMaxLength) { + return ; + } + return ( + <> + {isUnsaved && ( + + } + > + + {(copy) => ( + + + + )} + + + )} + + ) : ( + + ) + } + > + + + + ); + }; + + const saveWarningMessageWithButton = + objectId === undefined || objectId === '' || !isSaved || props.isDirty || isStale ? ( + + + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + + + ) : ( + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + ); + return ( + <> + + + + + { + setSelectedRadio(id as Exclude); + }} + name="image reporting radio group" + idSelected={selectedRadio} + legend={{ + children: ( + + ), + }} + /> + + + + + {renderOptions()} + {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} + {saveWarningMessageWithButton} + + + ); +}; + +export const ReportingModalContent = injectI18n(ReportingModalContentUI); diff --git a/packages/kbn-reporting/public/share/share_context_menu/index.ts b/packages/kbn-reporting/public/share/share_context_menu/index.ts index 22503eae119cb1..242f4d39f57383 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/index.ts +++ b/packages/kbn-reporting/public/share/share_context_menu/index.ts @@ -24,8 +24,11 @@ export interface ExportPanelShareOpts { license: ILicense; application: ApplicationStart; theme: ThemeServiceSetup; + version: string; } +export type ExportModalShareOpts = ExportPanelShareOpts; + export interface ReportingSharingData { title: string; layout: LayoutParams; diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx index 2de8abe723a865..97dce8928f7e07 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx @@ -15,7 +15,7 @@ import type { SearchSourceFields } from '@kbn/data-plugin/common'; import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; import type { ExportPanelShareOpts } from '.'; import { checkLicense } from '../..'; -import { ReportingPanelContent } from './reporting_panel_content_lazy'; +import { CsvModalContent } from './csv_export_modal'; export const reportingCsvShareProvider = ({ apiClient, @@ -85,33 +85,31 @@ export const reportingCsvShareProvider = ({ if (licenseHasCsvReporting && capabilityHasCsvReporting) { const panelTitle = i18n.translate('reporting.share.contextMenu.csvReportsButtonLabel', { - defaultMessage: 'CSV Reports', + defaultMessage: 'Export', }); shareActions.push({ shareMenuItem: { name: panelTitle, - icon: 'document', toolTipContent: licenseToolTipContent, disabled: licenseDisabled, ['data-test-subj']: 'CSVReports', - sortOrder: 1, }, panel: { id: 'csvReportingPanel', title: panelTitle, content: ( - ), }, diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index cd75ab11e764cd..614139c3f92c7a 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -7,53 +7,14 @@ */ import { i18n } from '@kbn/i18n'; -import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; import React from 'react'; -import { ExportPanelShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.'; -import { ReportingAPIClient, checkLicense } from '../..'; -import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy'; - -const getJobParams = - ( - apiClient: ReportingAPIClient, - opts: JobParamsProviderOptions, - type: 'png' | 'pngV2' | 'printablePdf' | 'printablePdfV2' - ) => - () => { - const { - objectType, - sharingData: { title, layout, locatorParams }, - } = opts; - - const baseParams = { - objectType, - layout, - title, - }; - - if (type === 'printablePdfV2') { - // multi locator for PDF V2 - return { ...baseParams, locatorParams: [locatorParams] }; - } else if (type === 'pngV2') { - // single locator for PNG V2 - return { ...baseParams, locatorParams }; - } - - // Relative URL must have URL prefix (Spaces ID prefix), but not server basePath - // Replace hashes with original RISON values. - const relativeUrl = opts.shareableUrl.replace( - window.location.origin + apiClient.getServerBasePath(), - '' - ); - - if (type === 'printablePdf') { - // multi URL for PDF - return { ...baseParams, relativeUrls: [relativeUrl] }; - } +import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; +import { checkLicense } from '../../license_check'; +import { ExportModalShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.'; +import { ReportingModalContent } from './reporting_panel_content_lazy'; - // single URL for PNG - return { ...baseParams, relativeUrl }; - }; +export const isJobV2Params = ({ sharingData }: { sharingData: Record }): boolean => + sharingData.locatorParams != null; export const reportingScreenshotShareProvider = ({ apiClient, @@ -63,7 +24,8 @@ export const reportingScreenshotShareProvider = ({ application, usesUiCapabilities, theme, -}: ExportPanelShareOpts): ShareMenuProvider => { + version, +}: ExportModalShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, objectId, @@ -94,7 +56,8 @@ export const reportingScreenshotShareProvider = ({ if (!licenseHasScreenshotReporting) { return []; } - const isSupportedType = ['dashboard', 'visualization', 'lens'].includes(objectType); + // for lens png pdf and csv are combined into one modal + const isSupportedType = ['dashboard', 'visualization'].includes(objectType); if (!isSupportedType) { return []; @@ -115,93 +78,48 @@ export const reportingScreenshotShareProvider = ({ const { sharingData } = shareOpts as unknown as { sharingData: ReportingSharingData }; const shareActions = []; - const pngPanelTitle = i18n.translate('reporting.share.contextMenu.pngReportsButtonLabel', { - defaultMessage: 'PNG Reports', - }); - const jobProviderOptions: JobParamsProviderOptions = { shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, objectType, sharingData, }; - const isJobV2Params = ({ - sharingData: _sharingData, - }: { - sharingData: Record; - }) => _sharingData.locatorParams != null; const isV2Job = isJobV2Params(jobProviderOptions); const requiresSavedState = !isV2Job; - const pngReportType = isV2Job ? 'pngV2' : 'png'; - - const panelPng = { - shareMenuItem: { - name: pngPanelTitle, - icon: 'document', - toolTipContent: licenseToolTipContent, - disabled: licenseDisabled || sharingData.reportingDisabled, - ['data-test-subj']: 'PNGReports', - sortOrder: 10, - }, - panel: { - id: 'reportingPngPanel', - title: pngPanelTitle, - content: ( - - ), - }, - }; - - const pdfPanelTitle = i18n.translate('reporting.share.contextMenu.pdfReportsButtonLabel', { - defaultMessage: 'PDF Reports', - }); - - const pdfReportType = isV2Job ? 'printablePdfV2' : 'printablePdf'; - - const panelPdf = { + shareActions.push({ shareMenuItem: { - name: pdfPanelTitle, - icon: 'document', + name: i18n.translate('xpack.reporting.shareContextMenu.ExportsButtonLabel', { + defaultMessage: 'Export', + }), toolTipContent: licenseToolTipContent, disabled: licenseDisabled || sharingData.reportingDisabled, - ['data-test-subj']: 'PDFReports', - sortOrder: 10, + ['data-test-subj']: 'imageExports', }, panel: { - id: 'reportingPdfPanel', - title: pdfPanelTitle, + id: 'reportingImageModal', + title: i18n.translate('xpack.reporting.shareContextMenu.ReportsButtonLabel', { + defaultMessage: 'Generate report', + }), content: ( - { + onClose(); + }} theme={theme} + objectType={objectType} /> ), }, - }; - - shareActions.push(panelPng); - shareActions.push(panelPdf); + }); return shareActions; }; diff --git a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx index af4a0fe1a64b22..447975042ec5b7 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx @@ -9,18 +9,18 @@ import * as React from 'react'; import { FC, lazy, Suspense } from 'react'; import { PanelSpinner } from './panel_spinner'; -import type { Props } from './reporting_panel_content'; +import type { ReportingModalProps } from './image_export_modal'; -const LazyComponent = lazy(() => - import('./reporting_panel_content').then(({ ReportingPanelContent }) => ({ - default: ReportingPanelContent, +const LazyModalComponent = lazy(() => + import('./image_export_modal').then(({ ReportingModalContent }) => ({ + default: ReportingModalContent, })) ); -export const ReportingPanelContent: FC> = (props) => { +export const ReportingModalContent: FC = (props) => { return ( }> - + ); }; diff --git a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx index cacdae135b4ef3..e9242a23972636 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx @@ -12,12 +12,9 @@ import React, { Component } from 'react'; import type { LayoutParams } from '@kbn/screenshotting-plugin/common'; import { ReportingPanelContent, ReportingPanelProps } from './reporting_panel_content'; -export interface Props extends ReportingPanelProps { - layoutOption?: 'canvas' | 'print'; -} +export type Props = ReportingPanelProps; interface State { - usePrintLayout: boolean; useCanvasLayout: boolean; } @@ -26,7 +23,6 @@ export class ScreenCapturePanelContent extends Component { super(props); this.state = { - usePrintLayout: false, useCanvasLayout: false, }; } @@ -43,65 +39,32 @@ export class ScreenCapturePanelContent extends Component { } private renderOptions = () => { - if (this.props.layoutOption === 'print') { - return ( - - } - > - - } - checked={this.state.usePrintLayout} - onChange={this.handlePrintLayoutChange} - data-test-subj="usePrintLayout" + return ( + - - ); - } - - if (this.props.layoutOption === 'canvas') { - return ( - + } - > - - } - checked={this.state.useCanvasLayout} - onChange={this.handleCanvasLayoutChange} - data-test-subj="reportModeToggle" - /> - - ); - } - - return null; - }; - - private handlePrintLayoutChange = (evt: EuiSwitchEvent) => { - this.setState({ usePrintLayout: evt.target.checked, useCanvasLayout: false }); + checked={this.state.useCanvasLayout} + onChange={this.handleCanvasLayoutChange} + data-test-subj="reportModeToggle" + /> + + ); }; private handleCanvasLayoutChange = (evt: EuiSwitchEvent) => { - this.setState({ useCanvasLayout: evt.target.checked, usePrintLayout: false }); + this.setState({ useCanvasLayout: evt.target.checked }); }; private getLayout = (): LayoutParams => { @@ -114,15 +77,7 @@ export class ScreenCapturePanelContent extends Component { dimensions = { height, width }; } - if (this.state.usePrintLayout) { - return { id: 'print', dimensions }; - } - - if (this.state.useCanvasLayout) { - return { id: 'canvas', dimensions }; - } - - return { id: 'preserve_layout', dimensions }; + return { id: 'canvas', dimensions }; }; private getJobParams = () => { diff --git a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content_lazy.tsx b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content_lazy.tsx index e153dd4dcb0fe4..9c59b5fa53d27a 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content_lazy.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content_lazy.tsx @@ -17,7 +17,7 @@ const LazyComponent = lazy(() => })) ); -export const ScreenCapturePanelContent: FC = (props) => { +export const ScreenCapturePanelContent: FC> = (props) => { return ( }> diff --git a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx index 0fc0f2ad10ab3a..fa0a39cb064e05 100644 --- a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx +++ b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx @@ -7,23 +7,24 @@ */ import { CoreSetup } from '@kbn/core/public'; +import { BaseParams } from '@kbn/reporting-common/types'; +import { CSV_JOB_TYPE, JobParamsCSV } from '@kbn/reporting-export-types-csv-common'; +import { JobAppParamsPDFV2, PDF_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; import React from 'react'; - -import { PDF_REPORT_TYPE, PDF_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; -import { PNG_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-png-common'; - -import { ReportingAPIClient } from '../../reporting_api_client'; +import { ReportingAPIClient } from '../..'; +import { CsvModalContent } from '../share_context_menu/csv_export_modal'; import { ReportingPanelProps } from '../share_context_menu/reporting_panel_content'; +import { ReportingModalContent } from '../share_context_menu/reporting_panel_content_lazy'; import { ScreenCapturePanelContent } from '../share_context_menu/screen_capture_panel_content_lazy'; - /** * Properties for displaying a share menu with Reporting features. */ export interface ApplicationProps { /** * A function that Reporting calls to get the sharing data from the application. + * Needed for CSV exports and Canvas PDF reports. */ - getJobParams: ReportingPanelProps['getJobParams']; + getJobParams?: JobAppParamsPDFV2 | JobParamsCSV | ReportingPanelProps['getJobParams']; /** * Option to control how the screenshot(s) is/are placed in the PDF @@ -39,27 +40,26 @@ export interface ApplicationProps { * A function to callback when the Reporting panel should be closed */ onClose: () => void; + objectType: string; } -/** - * React components used to display share menus with Reporting features in an application. - */ export interface ReportingPublicComponents { + /** Needed for Canvas PDF reports */ + ReportingPanelPDFV2(props: ApplicationProps): JSX.Element | undefined; /** * An element to display a form to export the page as PDF - * @deprecated */ - ReportingPanelPDF(props: ApplicationProps): JSX.Element; + ReportingModalPDFV2(props: ApplicationProps): JSX.Element; /** - * An element to display a form to export the page as PDF + * An element to display a form to export the page as PNG */ - ReportingPanelPDFV2(props: ApplicationProps): JSX.Element; + ReportingModalPNGV2(props: ApplicationProps): JSX.Element; /** - * An element to display a form to export the page as PNG + * An element to display a form to export the page as CSV */ - ReportingPanelPNGV2(props: ApplicationProps): JSX.Element; + ReportingModalCSV(props: ApplicationProps): JSX.Element; } /** @@ -72,42 +72,65 @@ export function getSharedComponents( apiClient: ReportingAPIClient ): ReportingPublicComponents { return { - ReportingPanelPDF(props: ApplicationProps) { + ReportingPanelPDFV2(props: ApplicationProps) { + const getJobParams = props.getJobParams as ReportingPanelProps['getJobParams']; + if (props.layoutOption === 'canvas') { + return ( + + ); + } + }, + ReportingModalPDFV2(props: ApplicationProps) { + const getJobParams = props.getJobParams as JobAppParamsPDFV2; return ( - ); }, - ReportingPanelPDFV2(props: ApplicationProps) { + ReportingModalPNGV2(props: ApplicationProps) { return ( - ); }, - ReportingPanelPNGV2(props: ApplicationProps) { + ReportingModalCSV(props: ApplicationProps) { + const getJobParams = props.getJobParams as ( + forShareUrl?: boolean + ) => Omit; return ( - ); }, diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 3636c63f029bbf..55333d878550ee 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -59,7 +59,7 @@ export interface IModalTabDeclaration extends EuiTabPr } interface IModalContext { - tabs: Array, 'reducer' | 'initialState'>>; + tabs: Array, 'reducer' | 'initialState'>> | null; state: { meta: IModalMetaState } & Record; dispatch: Dispatch; } @@ -112,9 +112,9 @@ export function ModalContextProvider - tabs.reduce((result, { id, reducer, initialState, ...rest }) => { + tabs?.reduce((result, { id, reducer, initialState, ...rest }) => { initialModalState.current[id] = initialState ?? {}; - modalTabDefinitions.current.push({ id, reducer, ...rest }); + modalTabDefinitions.current?.push({ id, reducer, ...rest }); result[id] = reducer; return result; }, {}), diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 70ba265c3cdfa3..031aa440c6d088 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -40,11 +40,8 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid [selectedTabId, state] ); - const { - content: SelectedTabContent, - modalActionBtn: { defaultMessage, handler, dataTestSubj, formattedMessageId }, - } = useMemo(() => { - return tabs.find((obj) => obj.id === selectedTabId)!; + const { content: SelectedTabContent, modalActionBtn } = useMemo(() => { + return tabs?.find((obj) => obj.id === selectedTabId)!; }, [selectedTabId, tabs]); const onSelectedTabChanged = (id: string) => { @@ -52,7 +49,7 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid }; const renderTabs = () => { - return tabs.map((tab, index) => ( + return tabs?.map((tab, index) => ( onSelectedTabChanged(tab.id)} @@ -67,8 +64,8 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid }; const btnClickHandler = useCallback(() => { - handler({ state: selectedTabState }); - }, [handler, selectedTabState]); + modalActionBtn ? modalActionBtn.handler({ state: selectedTabState }) : null; + }, [selectedTabState, modalActionBtn]); return ( = ({ onClose, modalTitle, modalWid })} - - - - - + {modalActionBtn && ( + + + + + + )} ); }; diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 33cbe4e18f773e..ce67755b16fd4f 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -10,7 +10,7 @@ import React, { type FC } from 'react'; import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { ShareTabsContext, useShareTabsContext, type IShareContext } from './context'; -import { linkTab, embedTab, exportTab } from './tabs'; +import { linkTab, embedTab } from './tabs'; export const ShareMenuV2: FC<{ shareContext: IShareContext }> = ({ shareContext }) => { return ( @@ -28,9 +28,18 @@ export const ShareMenuTabs = () => { return null; } - const { allowEmbed, objectType, onClose } = shareContext; - - const tabs = [linkTab, exportTab, allowEmbed ? embedTab : null].filter(Boolean); + const { allowEmbed, objectType, onClose, shareMenuItems } = shareContext; + + const tabs = [linkTab, allowEmbed ? embedTab : null].filter(Boolean); + if (shareMenuItems) { + shareMenuItems.forEach(({ shareMenuItem, panel }) => { + tabs.push({ + id: panel.id.toString(), + name: shareMenuItem.name, + content: () => panel.content, + }); + }); + } return ( & { - setExportType: (exportType: string) => void; -}; - -export const ExportContent = ({ shareMenuItems }: ExportProps) => { - // TODO: use share menu items to build export tab - - return null; -}; diff --git a/src/plugins/share/public/components/tabs/export/index.tsx b/src/plugins/share/public/components/tabs/export/index.tsx deleted file mode 100644 index 5b06ecde245b7f..00000000000000 --- a/src/plugins/share/public/components/tabs/export/index.tsx +++ /dev/null @@ -1,39 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; -import { useShareTabsContext } from '../../context'; - -type IExportTab = IModalTabDeclaration<{ - exportType: string; -}>; - - -const ExportTabContent: IExportTab['content'] = () => { - const { shareMenuItems } = useShareTabsContext()!; - return shareMenuItems.forEach(({ shareMenuItem, panel }) => - panel.content - ); -}; - -export const exportTab: IExportTab = { - id: 'export', - name: i18n.translate('share.contextMenu.permalinksTab', { - defaultMessage: 'Export', - }), - description: ( - - ), - content: ExportTabContent, -}; diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index ed3567d411654a..79f70783873e60 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -31,6 +31,7 @@ export type SharePublicSetup = ShareMenuRegistrySetup & { * Utilities to work with URL locators and short URLs. */ url: BrowserUrlService; + kibanaVersion: string; /** * Accepts serialized values for extracting a locator, migrating state from a provided version against @@ -79,9 +80,11 @@ export class SharePlugin private redirectManager?: RedirectManager; private url?: BrowserUrlService; private anonymousAccessServiceProvider?: () => AnonymousAccessServiceContract; + kibanaVersion: string; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); + this.kibanaVersion = initializerContext.env.packageInfo.version; } public setup(core: CoreSetup): SharePublicSetup { @@ -127,6 +130,7 @@ export class SharePlugin return { ...this.shareMenuRegistry.setup(), + kibanaVersion: this.kibanaVersion, url: this.url, navigate: (options: RedirectOptions) => this.redirectManager!.navigate(options), setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessServiceContract) => { diff --git a/x-pack/plugins/lens/kibana.jsonc b/x-pack/plugins/lens/kibana.jsonc index 1813264f7ca570..0d5c1854a8b59e 100644 --- a/x-pack/plugins/lens/kibana.jsonc +++ b/x-pack/plugins/lens/kibana.jsonc @@ -57,6 +57,7 @@ "fieldFormats", "charts", "textBasedLanguages", + "esUiShared", ], "extraPublicDirs": [ "common/constants" diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx index dded4f4768a16e..bb805cc335349a 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx @@ -8,10 +8,10 @@ import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import * as React from 'react'; import { FC, lazy, Suspense } from 'react'; -import type { DownloadPanelContentProps } from './csv_download_panel_content'; +import type { ReportingModalProps } from './export_modal_content'; const LazyComponent = lazy(() => - import('./csv_download_panel_content').then(({ DownloadPanelContent }) => ({ + import('./export_modal_content').then(({ ReportingModalContent }) => ({ default: DownloadPanelContent, })) ); @@ -30,7 +30,7 @@ export const PanelSpinner: React.FC = (props) => { ); }; -export const DownloadPanelContent: FC> = (props) => { +export const DownloadPanelContent: FC> = (props) => { return ( }> diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 88c7c8de2c0924..0f39abf132d4c4 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -11,9 +11,11 @@ import { tableHasFormulas } from '@kbn/data-plugin/common'; import { downloadMultipleAs, ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; import { exporters } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { ReportingAPIClient } from '@kbn/reporting-public'; +import { CoreSetup, ToastsSetup } from '@kbn/core/public'; import { FormatFactory } from '../../../common/types'; -import { DownloadPanelContent } from './csv_download_panel_content_lazy'; import { TableInspectorAdapter } from '../../editor_frame_service/types'; +import { ReportingModalContent } from './export_modal_content'; declare global { interface Window { @@ -96,11 +98,19 @@ function getWarnings(activeData: TableInspectorAdapter) { interface DownloadPanelShareOpts { uiSettings: IUiSettingsClient; formatFactoryFn: () => FormatFactory; + reportingApiClient: ReportingAPIClient; + toasts: ToastsSetup; + theme: CoreSetup['theme']; + version: string; } export const downloadCsvShareProvider = ({ uiSettings, formatFactoryFn, + reportingApiClient, + toasts, + theme, + version, }: DownloadPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, sharingData, onClose }: ShareContext) => { if ('lens' !== objectType) { @@ -113,11 +123,10 @@ export const downloadCsvShareProvider = ({ csvEnabled: boolean; columnsSorting?: string[]; }; - const panelTitle = i18n.translate( 'xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel', { - defaultMessage: 'CSV Download', + defaultMessage: 'Export', } ); @@ -125,7 +134,6 @@ export const downloadCsvShareProvider = ({ { shareMenuItem: { name: panelTitle, - icon: 'document', disabled: !csvEnabled, sortOrder: 1, }, @@ -133,9 +141,18 @@ export const downloadCsvShareProvider = ({ id: 'csvDownloadPanel', title: panelTitle, content: ( - { await downloadCSVs({ title, diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx new file mode 100644 index 00000000000000..cc5d8529565a9e --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx @@ -0,0 +1,353 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiCopy, + EuiFlexGroup, + EuiForm, + EuiFormRow, + EuiModalFooter, + EuiRadioGroup, + EuiSpacer, + EuiToolTip, +} from '@elastic/eui'; +import type { IToasts, IUiSettingsClient, ThemeServiceSetup } from '@kbn/core/public'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; +import url from 'url'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { LayoutParams } from '@kbn/screenshotting-plugin/common'; +import { BaseParams } from '@kbn/reporting-common/types'; +import { ReportingAPIClient } from '@kbn/reporting-public'; +import { ErrorUrlTooLongPanel } from '@kbn/reporting-public/share/share_context_menu/reporting_panel_content/components'; +import { JobAppParamsPDFV2 } from '@kbn/reporting-export-types-pdf-common'; +import { JobParamsPNGV2 } from '@kbn/reporting-export-types-png-common'; + +interface ReportingSharingData { + title: string; + layout: LayoutParams; + reportingDisabled?: boolean; + [key: string]: unknown; +} +const CHROMIUM_MAX_URL_LENGTH = 25 * 1000; +const getMaxUrlLength = () => CHROMIUM_MAX_URL_LENGTH; + +interface JobParamsProviderOptions { + sharingData: ReportingSharingData; + shareableUrl: string; + objectType: string; +} +export interface ReportingModalProps { + apiClient: ReportingAPIClient; + toasts: IToasts; + uiSettings: IUiSettingsClient; + reportType?: string; + requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. + objectId?: string; + isDirty?: boolean; + onClose: () => void; + onClick: () => void; + theme: ThemeServiceSetup; + jobProviderOptions?: JobParamsProviderOptions; + getJobParams?: JobAppParamsPDFV2 | JobParamsPNGV2; + objectType: string; + isDisabled: boolean; + warnings: string[]; + columns?: string[]; + version: string; +} + +type AppParams = Omit; + +export type Props = ReportingModalProps & { intl: InjectedIntl }; + +type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'csv'; + +export const ReportingModalContentUI: FC = (props: Props) => { + const { + apiClient, + intl, + toasts, + theme, + onClose, + objectId, + jobProviderOptions, + objectType, + columns, + version, + } = props; + const isSaved = Boolean(objectId); + const [isStale, setIsStale] = useState(false); + const [createReportingJob, setCreatingReportJob] = useState(false); + const [selectedRadio, setSelectedRadio] = useState('printablePdfV2'); + const [absoluteUrl, setAbsoluteUrl] = useState(''); + const isMounted = useMountedState(); + const exceedsMaxLength = absoluteUrl.length >= getMaxUrlLength(); + + const getJobsParams = useCallback( + (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { + if (!opts) { + return { ...props.getJobParams }; + } + + const { + sharingData: { title, layout, locatorParams }, + } = opts; + + const baseParams = { + objectType, + layout, + title, + }; + if (type === 'csv') { + return { + title, + objectType, + locatorParams, + }; + } + + if (type === 'printablePdfV2') { + // multi locator for PDF V2 + return { ...baseParams, locatorParams: [locatorParams] }; + } else if (type === 'pngV2') { + // single locator for PNG V2 + return { ...baseParams, locatorParams }; + } + }, + [props.getJobParams, objectType] + ); + + const getJobParamHelper = useCallback( + (shareableUrl?: boolean) => { + return { ...getJobsParams(selectedRadio, jobProviderOptions) }; + }, + [getJobsParams, jobProviderOptions, selectedRadio] + ); + + const getAbsoluteReportGenerationUrl = useMemo( + () => () => { + if (getJobsParams(selectedRadio, jobProviderOptions) !== undefined) { + const relativePath = apiClient.getReportingPublicJobPath( + selectedRadio, + apiClient.getDecoratedJobParams(getJobParamHelper(true) as unknown as AppParams) + ); + return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); + } + }, + [apiClient, selectedRadio, getJobsParams, jobProviderOptions, getJobParamHelper] + ); + + const markAsStale = useCallback(() => { + if (!isMounted) return; + setIsStale(true); + }, [isMounted]); + + useEffect(() => { + getAbsoluteReportGenerationUrl(); + markAsStale(); + }, [markAsStale, getAbsoluteReportGenerationUrl]); + + const generateReportingJob = () => { + const decoratedJobParams = + selectedRadio !== 'csv' + ? apiClient.getDecoratedJobParams(getJobParamHelper() as unknown as AppParams) + : { + title: props.getJobParams?.title, + columns: columns as string[] | undefined, + searchSource: { + addGlobalTimeFilter: true, + absoluteTime: !absoluteUrl, + }, + objectType, + version, + }; + setCreatingReportJob(true); + return ( + apiClient + // @ts-ignore + .createReportingJob(selectedRadio, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'share.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme$: theme.theme$ } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + if (isMounted()) { + setCreatingReportJob(false); + } + }) + .catch((error) => { + toasts.addError(error, { + title: intl.formatMessage({ + id: 'share.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + if (isMounted()) { + setCreatingReportJob(false); + } + }) + ); + }; + + const renderCopyURLButton = ({ + isUnsaved, + }: { + isUnsaved: boolean; + exceedsMaxLength: boolean; + }) => { + if (isUnsaved) { + if (exceedsMaxLength) { + return ; + } + return ( + + } + > + + {(copy) => ( + + + + )} + + + ); + } else if (exceedsMaxLength) { + return ; + } + return ( + + + {(copy) => ( + + + + )} + + + ); + }; + + const saveWarningMessageWithButton = + objectId === undefined || objectId === '' || !isSaved || props.isDirty || isStale ? ( + + + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + + + ) : ( + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + ); + + return ( + <> + + + { + setSelectedRadio(id as Exclude); + getJobsParams(selectedRadio); + }} + name="image reporting radio group" + idSelected={selectedRadio} + legend={{ + children: File type, + }} + /> + + + {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} + + {saveWarningMessageWithButton} + + ); +}; + +export const ReportingModalContent = injectI18n(ReportingModalContentUI); diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 1c143ed1c69277..0a2167857ee46f 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AppMountParameters, CoreSetup, CoreStart } from '@kbn/core/public'; +import type { AppMountParameters, CoreSetup, CoreStart, HttpStart } from '@kbn/core/public'; import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; import type { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { @@ -64,6 +64,7 @@ import { import { i18n } from '@kbn/i18n'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { registerSavedObjectToPanelMethod } from '@kbn/embeddable-plugin/public'; +import { ReportingAPIClient } from '@kbn/reporting-public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { FormBasedDatasource as FormBasedDatasourceType, @@ -181,6 +182,7 @@ export interface LensPluginStartDependencies { eventAnnotationService: EventAnnotationServiceType; contentManagement: ContentManagementPublicStart; serverless?: ServerlessPluginStart; + http: HttpStart; } export interface LensPublicSetup { @@ -390,11 +392,19 @@ export class LensPlugin { if (share) { this.locator = share.url.locators.create(new LensAppLocatorDefinition()); - + const reportingApiClient = new ReportingAPIClient( + core.http, + core.uiSettings, + share.kibanaVersion + ); share.register( downloadCsvShareProvider({ uiSettings: core.uiSettings, formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, + reportingApiClient, + toasts: core.notifications.toasts, + theme: core.theme, + version: share.kibanaVersion, }) ); } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index 872bf42a9c9432..b8701a4be18757 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -210,6 +210,7 @@ export class ReportingPublicPlugin application, usesUiCapabilities, theme: core.theme, + version: this.kibanaVersion, }) ); @@ -223,6 +224,7 @@ export class ReportingPublicPlugin application, usesUiCapabilities, theme: core.theme, + version: this.kibanaVersion, }) ); } From 569149ce6652823c297ef15e3a81f35637738495 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 21 Mar 2024 19:27:48 -0600 Subject: [PATCH 28/69] working --- src/plugins/share/public/components/share_tabs.tsx | 2 ++ src/plugins/share/public/components/tabs/index.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index ce67755b16fd4f..c803fc8d6b049a 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -36,6 +36,7 @@ export const ShareMenuTabs = () => { tabs.push({ id: panel.id.toString(), name: shareMenuItem.name, + // @ts-ignore content: () => panel.content, }); }); @@ -43,6 +44,7 @@ export const ShareMenuTabs = () => { return ( Date: Fri, 22 Mar 2024 10:00:58 -0600 Subject: [PATCH 29/69] got lens exports to work - pdf layout needs attention --- packages/kbn-generate-csv/src/generate_csv.ts | 1 + packages/kbn-reporting/public/share/index.ts | 1 + .../register_pdf_png_reporting.tsx | 1 - .../csv_download_provider.tsx | 24 ++- .../export_modal_content.tsx | 144 ++++++++---------- x-pack/plugins/lens/public/plugin.ts | 1 - 6 files changed, 82 insertions(+), 90 deletions(-) diff --git a/packages/kbn-generate-csv/src/generate_csv.ts b/packages/kbn-generate-csv/src/generate_csv.ts index c59e4fd40aafb9..84840c9ae9f91a 100644 --- a/packages/kbn-generate-csv/src/generate_csv.ts +++ b/packages/kbn-generate-csv/src/generate_csv.ts @@ -223,6 +223,7 @@ export class CsvGenerator { public async generateData(): Promise { const logger = this.logger; + const [settings, searchSource] = await Promise.all([ getExportSettings( this.clients.uiSettings, diff --git a/packages/kbn-reporting/public/share/index.ts b/packages/kbn-reporting/public/share/index.ts index 2a1c9adc2a7f8b..d728637c336a37 100644 --- a/packages/kbn-reporting/public/share/index.ts +++ b/packages/kbn-reporting/public/share/index.ts @@ -11,3 +11,4 @@ export { reportingScreenshotShareProvider } from './share_context_menu/register_ export { reportingCsvShareProvider } from './share_context_menu/register_csv_reporting'; export { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action'; export type { ReportingPublicComponents } from './shared/get_shared_components'; +export type { JobParamsProviderOptions } from './share_context_menu'; diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 614139c3f92c7a..e84c69cf7aef2e 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -24,7 +24,6 @@ export const reportingScreenshotShareProvider = ({ application, usesUiCapabilities, theme, - version, }: ExportModalShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 0f39abf132d4c4..30d4e99b8abfa7 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { tableHasFormulas } from '@kbn/data-plugin/common'; import { downloadMultipleAs, ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; +import type { JobParamsProviderOptions } from '@kbn/reporting-public/share'; import { exporters } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { ReportingAPIClient } from '@kbn/reporting-public'; @@ -101,7 +102,6 @@ interface DownloadPanelShareOpts { reportingApiClient: ReportingAPIClient; toasts: ToastsSetup; theme: CoreSetup['theme']; - version: string; } export const downloadCsvShareProvider = ({ @@ -110,9 +110,15 @@ export const downloadCsvShareProvider = ({ reportingApiClient, toasts, theme, - version, }: DownloadPanelShareOpts): ShareMenuProvider => { - const getShareMenuItems = ({ objectType, sharingData, onClose }: ShareContext) => { + const getShareMenuItems = ({ + objectType, + sharingData, + onClose, + shareableUrl, + shareableUrlForSavedObject, + isDirty, + }: ShareContext) => { if ('lens' !== objectType) { return []; } @@ -130,6 +136,12 @@ export const downloadCsvShareProvider = ({ } ); + const jobProviderOptions: JobParamsProviderOptions = { + shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, + objectType, + sharingData, + }; + return [ { shareMenuItem: { @@ -151,9 +163,8 @@ export const downloadCsvShareProvider = ({ isDisabled={!csvEnabled} warnings={getWarnings(activeData)} theme={theme} - columns={columnsSorting} - version={version} - onClick={async () => { + jobProviderOptions={jobProviderOptions} + downloadCsvFromLens={async () => { await downloadCSVs({ title, formatFactory: formatFactoryFn(), @@ -161,7 +172,6 @@ export const downloadCsvShareProvider = ({ uiSettings, columnsSorting, }); - onClose?.(); }} /> ), diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx index cc5d8529565a9e..11ef05f6c26f5f 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx @@ -53,22 +53,21 @@ export interface ReportingModalProps { objectId?: string; isDirty?: boolean; onClose: () => void; - onClick: () => void; theme: ThemeServiceSetup; jobProviderOptions?: JobParamsProviderOptions; getJobParams?: JobAppParamsPDFV2 | JobParamsPNGV2; objectType: string; isDisabled: boolean; warnings: string[]; - columns?: string[]; - version: string; + + downloadCsvFromLens: () => void; } type AppParams = Omit; export type Props = ReportingModalProps & { intl: InjectedIntl }; -type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'csv'; +type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'csv_searchsource'; export const ReportingModalContentUI: FC = (props: Props) => { const { @@ -80,8 +79,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { objectId, jobProviderOptions, objectType, - columns, - version, + downloadCsvFromLens, } = props; const isSaved = Boolean(objectId); const [isStale, setIsStale] = useState(false); @@ -106,13 +104,6 @@ export const ReportingModalContentUI: FC = (props: Props) => { layout, title, }; - if (type === 'csv') { - return { - title, - objectType, - locatorParams, - }; - } if (type === 'printablePdfV2') { // multi locator for PDF V2 @@ -156,75 +147,66 @@ export const ReportingModalContentUI: FC = (props: Props) => { }, [markAsStale, getAbsoluteReportGenerationUrl]); const generateReportingJob = () => { - const decoratedJobParams = - selectedRadio !== 'csv' - ? apiClient.getDecoratedJobParams(getJobParamHelper() as unknown as AppParams) - : { - title: props.getJobParams?.title, - columns: columns as string[] | undefined, - searchSource: { - addGlobalTimeFilter: true, - absoluteTime: !absoluteUrl, - }, - objectType, - version, - }; - setCreatingReportJob(true); - return ( - apiClient - // @ts-ignore - .createReportingJob(selectedRadio, decoratedJobParams) - .then(() => { - toasts.addSuccess({ - title: intl.formatMessage( - { - id: 'share.modalContent.successfullyQueuedReportNotificationTitle', - defaultMessage: 'Queued report for {objectType}', - }, - { objectType } - ), - text: toMountPoint( - - - - ), - }} - />, - { theme$: theme.theme$ } - ), - 'data-test-subj': 'queueReportSuccess', - }); - if (onClose) { - onClose(); - } - if (isMounted()) { - setCreatingReportJob(false); - } - }) - .catch((error) => { - toasts.addError(error, { - title: intl.formatMessage({ - id: 'share.modalContent.notification.reportingErrorTitle', - defaultMessage: 'Unable to create report', - }), - toastMessage: ( - // eslint-disable-next-line react/no-danger - - ) as unknown as string, - }); - if (isMounted()) { - setCreatingReportJob(false); - } - }) + if (selectedRadio === 'csv_searchsource') { + return downloadCsvFromLens(); + } + const decoratedJobParams = apiClient.getDecoratedJobParams( + getJobParamHelper() as unknown as AppParams ); + + setCreatingReportJob(true); + return apiClient + .createReportingJob(selectedRadio, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'share.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme$: theme.theme$ } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + if (isMounted()) { + setCreatingReportJob(false); + } + }) + .catch((error) => { + toasts.addError(error, { + title: intl.formatMessage({ + id: 'share.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + if (isMounted()) { + setCreatingReportJob(false); + } + }); }; const renderCopyURLButton = ({ diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 0a2167857ee46f..bc29cbc547ff6a 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -404,7 +404,6 @@ export class LensPlugin { reportingApiClient, toasts: core.notifications.toasts, theme: core.theme, - version: share.kibanaVersion, }) ); } From fe63217622aa27db7ddec0056406b2ebb8812612 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Fri, 22 Mar 2024 10:56:04 -0600 Subject: [PATCH 30/69] fix lens reports --- .../share_context_menu/image_export_modal.tsx | 25 +++++-------------- .../csv_download_provider.tsx | 21 ++++++++++------ .../export_modal_content.tsx | 18 +++++++++---- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 3e310e6b84bc78..82cdea22a66346 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -27,7 +27,6 @@ import url from 'url'; import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { LayoutParams } from '@kbn/screenshotting-plugin/common'; -import type { JobAppParamsPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { BaseParams } from '@kbn/reporting-common/types'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { ReportingAPIClient } from '../..'; @@ -48,8 +47,6 @@ export interface ReportingModalProps { layoutOption?: 'print' | 'canvas'; // canvas breaks if required jobProviderOptions?: JobParamsProviderOptions; - // needed for canvas - getJobParams?: JobAppParamsPDFV2; objectType: string; } @@ -83,7 +80,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { const getJobsParams = useCallback( (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { if (!opts) { - return { ...props.getJobParams }; + return; } const { @@ -111,33 +108,23 @@ export const ReportingModalContentUI: FC = (props: Props) => { '' ); - if (type === 'printablePdf') { - // multi URL for PDF - return { ...baseParams, relativeUrls: [relativeUrl] }; - } - // single URL for PNG return { ...baseParams, relativeUrl }; }, - [apiClient, objectType, props.getJobParams] + [apiClient, objectType] ); const getLayout = useCallback((): LayoutParams => { - const { layout } = getJobsParams(selectedRadio, jobProviderOptions); + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + const dimensions = { height, width }; - let dimensions = layout?.dimensions; - - if (!dimensions) { - const el = document.querySelector('[data-shared-items-container]'); - const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; - dimensions = { height, width }; - } if (usePrintLayout) { return { id: 'print', dimensions }; } return { id: 'preserve_layout', dimensions }; - }, [getJobsParams, jobProviderOptions, selectedRadio, usePrintLayout]); + }, [usePrintLayout]); const getJobParams = useCallback( (shareableUrl?: boolean) => { diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 30d4e99b8abfa7..dde584de6736d0 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -14,6 +14,7 @@ import { exporters } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { ReportingAPIClient } from '@kbn/reporting-public'; import { CoreSetup, ToastsSetup } from '@kbn/core/public'; +import { LayoutParams } from '@kbn/screenshotting-plugin/common'; import { FormatFactory } from '../../../common/types'; import { TableInspectorAdapter } from '../../editor_frame_service/types'; import { ReportingModalContent } from './export_modal_content'; @@ -118,17 +119,22 @@ export const downloadCsvShareProvider = ({ shareableUrl, shareableUrlForSavedObject, isDirty, + ...shareOpts }: ShareContext) => { if ('lens' !== objectType) { return []; } - const { title, activeData, csvEnabled, columnsSorting } = sharingData as { - title: string; - activeData: TableInspectorAdapter; - csvEnabled: boolean; - columnsSorting?: string[]; - }; + const { title, activeData, csvEnabled, columnsSorting, layout, locatorParams } = + sharingData as { + title: string; + activeData: TableInspectorAdapter; + csvEnabled: boolean; + columnsSorting?: string[]; + layout: LayoutParams; + locatorParams: {}; + }; + const panelTitle = i18n.translate( 'xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel', { @@ -139,7 +145,7 @@ export const downloadCsvShareProvider = ({ const jobProviderOptions: JobParamsProviderOptions = { shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, objectType, - sharingData, + sharingData: { title, layout, locatorParams }, }; return [ @@ -164,6 +170,7 @@ export const downloadCsvShareProvider = ({ warnings={getWarnings(activeData)} theme={theme} jobProviderOptions={jobProviderOptions} + isDirty={isDirty} downloadCsvFromLens={async () => { await downloadCSVs({ title, diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx index 11ef05f6c26f5f..70cecd026ccdd2 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx @@ -59,7 +59,6 @@ export interface ReportingModalProps { objectType: string; isDisabled: boolean; warnings: string[]; - downloadCsvFromLens: () => void; } @@ -80,6 +79,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { jobProviderOptions, objectType, downloadCsvFromLens, + getJobParams, } = props; const isSaved = Boolean(objectId); const [isStale, setIsStale] = useState(false); @@ -92,7 +92,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { const getJobsParams = useCallback( (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { if (!opts) { - return { ...props.getJobParams }; + return { ...getJobParams }; } const { @@ -113,14 +113,22 @@ export const ReportingModalContentUI: FC = (props: Props) => { return { ...baseParams, locatorParams }; } }, - [props.getJobParams, objectType] + [getJobParams, objectType] ); + const getLayout = useCallback((): LayoutParams => { + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + const dimensions = { height, width }; + + return { id: 'preserve_layout', dimensions }; + }, []); + const getJobParamHelper = useCallback( (shareableUrl?: boolean) => { - return { ...getJobsParams(selectedRadio, jobProviderOptions) }; + return { ...getJobsParams(selectedRadio, jobProviderOptions), layout: getLayout() }; }, - [getJobsParams, jobProviderOptions, selectedRadio] + [getJobsParams, jobProviderOptions, selectedRadio, getLayout] ); const getAbsoluteReportGenerationUrl = useMemo( From 4a3b8879f4f4c8b3a45324d1839ed8c09d16c35a Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 25 Mar 2024 10:42:37 -0600 Subject: [PATCH 31/69] add licensing to kibana.jsonc for lens --- x-pack/plugins/lens/kibana.jsonc | 3 +- .../csv_download_provider.tsx | 105 ++++++++++++------ x-pack/plugins/lens/public/plugin.ts | 30 +++-- 3 files changed, 91 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/lens/kibana.jsonc b/x-pack/plugins/lens/kibana.jsonc index 0d5c1854a8b59e..f64eb06c30c918 100644 --- a/x-pack/plugins/lens/kibana.jsonc +++ b/x-pack/plugins/lens/kibana.jsonc @@ -36,7 +36,8 @@ "expressionTagcloud", "eventAnnotation", "unifiedSearch", - "contentManagement" + "contentManagement", + "licensing" ], "optionalPlugins": [ "expressionLegacyMetricVis", diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index dde584de6736d0..df00fc3fd78147 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -15,9 +15,11 @@ import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { ReportingAPIClient } from '@kbn/reporting-public'; import { CoreSetup, ToastsSetup } from '@kbn/core/public'; import { LayoutParams } from '@kbn/screenshotting-plugin/common'; +import { ILicense } from '@kbn/licensing-plugin/public'; import { FormatFactory } from '../../../common/types'; import { TableInspectorAdapter } from '../../editor_frame_service/types'; import { ReportingModalContent } from './export_modal_content'; +import { DownloadPanelContent } from './csv_download_panel_content'; declare global { interface Window { @@ -103,6 +105,7 @@ interface DownloadPanelShareOpts { reportingApiClient: ReportingAPIClient; toasts: ToastsSetup; theme: CoreSetup['theme']; + license: ILicense; } export const downloadCsvShareProvider = ({ @@ -111,6 +114,7 @@ export const downloadCsvShareProvider = ({ reportingApiClient, toasts, theme, + license, }: DownloadPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, @@ -119,12 +123,10 @@ export const downloadCsvShareProvider = ({ shareableUrl, shareableUrlForSavedObject, isDirty, - ...shareOpts }: ShareContext) => { if ('lens' !== objectType) { return []; } - const { title, activeData, csvEnabled, columnsSorting, layout, locatorParams } = sharingData as { title: string; @@ -149,41 +151,70 @@ export const downloadCsvShareProvider = ({ }; return [ - { - shareMenuItem: { - name: panelTitle, - disabled: !csvEnabled, - sortOrder: 1, - }, - panel: { - id: 'csvDownloadPanel', - title: panelTitle, - content: ( - { - await downloadCSVs({ - title, - formatFactory: formatFactoryFn(), - activeData, - uiSettings, - columnsSorting, - }); - }} - /> - ), - }, - }, + license.hasAtLeast('gold') + ? { + shareMenuItem: { + name: panelTitle, + disabled: !csvEnabled, + sortOrder: 1, + }, + panel: { + id: 'csvDownloadPanel', + title: panelTitle, + content: ( + { + await downloadCSVs({ + title, + formatFactory: formatFactoryFn(), + activeData, + uiSettings, + columnsSorting, + }); + }} + /> + ), + }, + } + : { + shareMenuItem: { + name: panelTitle, + icon: 'document', + disabled: !csvEnabled, + sortOrder: 1, + }, + panel: { + id: 'csvDownloadPanel', + title: panelTitle, + content: ( + { + await downloadCSVs({ + title, + formatFactory: formatFactoryFn(), + activeData, + uiSettings, + columnsSorting, + }); + onClose?.(); + }} + /> + ), + }, + }, ]; }; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index bc29cbc547ff6a..daf47bba53426e 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -5,6 +5,8 @@ * 2.0. */ +import * as Rx from 'rxjs'; + import type { AppMountParameters, CoreSetup, CoreStart, HttpStart } from '@kbn/core/public'; import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; import type { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -65,6 +67,7 @@ import { i18n } from '@kbn/i18n'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { registerSavedObjectToPanelMethod } from '@kbn/embeddable-plugin/public'; import { ReportingAPIClient } from '@kbn/reporting-public'; +import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { FormBasedDatasource as FormBasedDatasourceType, @@ -183,6 +186,7 @@ export interface LensPluginStartDependencies { contentManagement: ContentManagementPublicStart; serverless?: ServerlessPluginStart; http: HttpStart; + licensing: LicensingPluginStart; } export interface LensPublicSetup { @@ -397,15 +401,23 @@ export class LensPlugin { core.uiSettings, share.kibanaVersion ); - share.register( - downloadCsvShareProvider({ - uiSettings: core.uiSettings, - formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, - reportingApiClient, - toasts: core.notifications.toasts, - theme: core.theme, - }) - ); + + const { getStartServices } = core; + const startServices$ = Rx.from(getStartServices()); + startServices$.subscribe(([, { licensing }]) => { + licensing.license$.subscribe((license) => { + share.register( + downloadCsvShareProvider({ + uiSettings: core.uiSettings, + formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, + reportingApiClient, + toasts: core.notifications.toasts, + theme: core.theme, + license, + }) + ); + }); + }); } visualizations.registerAlias(getLensAliasConfig()); From 9e68156430a5e33752ab06ffbcb906ae2b65c8f1 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 25 Mar 2024 12:32:35 -0600 Subject: [PATCH 32/69] some cleaning --- packages/kbn-optimizer/limits.yml | 4 +-- .../share_context_menu/csv_export_modal.tsx | 29 ++++++++++++------- .../share_context_menu/image_export_modal.tsx | 25 ++++++++++++---- packages/shared-ux/modal/tabbed/tsconfig.json | 23 +++++++++++++++ 4 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 packages/shared-ux/modal/tabbed/tsconfig.json diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 3328cb2b10f870..58f6d46a887c87 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -84,7 +84,7 @@ pageLoadAssetSize: kibanaUsageCollection: 16463 kibanaUtils: 79713 kubernetesSecurity: 77234 - lens: 57135 + lens: 96692 licenseManagement: 41817 licensing: 29004 links: 44490 @@ -115,7 +115,7 @@ pageLoadAssetSize: presentationUtil: 58834 profiling: 36694 remoteClusters: 51327 - reporting: 57003 + reporting: 78653 rollup: 97204 runtimeFields: 41752 savedObjects: 108518 diff --git a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx index 24cc6829c3d41c..c48bccbe7210da 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx @@ -82,20 +82,20 @@ export const CsvModalContentUI: FC = (props: Props) => { toasts.addSuccess({ title: intl.formatMessage( { - id: 'share.modalContent.successfullyQueuedReportNotificationTitle', + id: 'reporting.share.modalContent.successfullyQueuedReportNotificationTitle', defaultMessage: 'Queued report for {objectType}', }, { objectType } ), text: toMountPoint( @@ -116,7 +116,7 @@ export const CsvModalContentUI: FC = (props: Props) => { .catch((error) => { toasts.addError(error, { title: intl.formatMessage({ - id: 'share.modalContent.notification.reportingErrorTitle', + id: 'reporting.share.modalContent.notification.reportingErrorTitle', defaultMessage: 'Unable to create report', }), toastMessage: ( @@ -146,7 +146,7 @@ export const CsvModalContentUI: FC = (props: Props) => { } @@ -161,7 +161,7 @@ export const CsvModalContentUI: FC = (props: Props) => { data-test-subj="shareReportingCopyURL" > @@ -171,7 +171,7 @@ export const CsvModalContentUI: FC = (props: Props) => { } @@ -208,7 +208,10 @@ export const CsvModalContentUI: FC = (props: Props) => { <> - + {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} @@ -224,7 +227,10 @@ export const CsvModalContentUI: FC = (props: Props) => { data-test-subj="generateReportButton" isLoading={Boolean(createReportingJob)} > - + ) : ( @@ -235,7 +241,10 @@ export const CsvModalContentUI: FC = (props: Props) => { data-test-subj="generateReportButton" isLoading={Boolean(createReportingJob)} > - + )} diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 82cdea22a66346..3e310e6b84bc78 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -27,6 +27,7 @@ import url from 'url'; import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { LayoutParams } from '@kbn/screenshotting-plugin/common'; +import type { JobAppParamsPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { BaseParams } from '@kbn/reporting-common/types'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { ReportingAPIClient } from '../..'; @@ -47,6 +48,8 @@ export interface ReportingModalProps { layoutOption?: 'print' | 'canvas'; // canvas breaks if required jobProviderOptions?: JobParamsProviderOptions; + // needed for canvas + getJobParams?: JobAppParamsPDFV2; objectType: string; } @@ -80,7 +83,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { const getJobsParams = useCallback( (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { if (!opts) { - return; + return { ...props.getJobParams }; } const { @@ -108,23 +111,33 @@ export const ReportingModalContentUI: FC = (props: Props) => { '' ); + if (type === 'printablePdf') { + // multi URL for PDF + return { ...baseParams, relativeUrls: [relativeUrl] }; + } + // single URL for PNG return { ...baseParams, relativeUrl }; }, - [apiClient, objectType] + [apiClient, objectType, props.getJobParams] ); const getLayout = useCallback((): LayoutParams => { - const el = document.querySelector('[data-shared-items-container]'); - const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; - const dimensions = { height, width }; + const { layout } = getJobsParams(selectedRadio, jobProviderOptions); + let dimensions = layout?.dimensions; + + if (!dimensions) { + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + dimensions = { height, width }; + } if (usePrintLayout) { return { id: 'print', dimensions }; } return { id: 'preserve_layout', dimensions }; - }, [usePrintLayout]); + }, [getJobsParams, jobProviderOptions, selectedRadio, usePrintLayout]); const getJobParams = useCallback( (shareableUrl?: boolean) => { diff --git a/packages/shared-ux/modal/tabbed/tsconfig.json b/packages/shared-ux/modal/tabbed/tsconfig.json new file mode 100644 index 00000000000000..7042c59410ebe4 --- /dev/null +++ b/packages/shared-ux/modal/tabbed/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/shared-ux-storybook-mock", + "@kbn/share-modal", + ] + } + \ No newline at end of file From 0a0e5414e55c852ab1fbc45279bc9758d1f26e2d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 25 Mar 2024 18:39:56 +0000 Subject: [PATCH 33/69] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/shared-ux/modal/tabbed/tsconfig.json | 41 +++++++++---------- src/plugins/share/tsconfig.json | 3 +- x-pack/plugins/lens/tsconfig.json | 8 +++- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/tsconfig.json b/packages/shared-ux/modal/tabbed/tsconfig.json index 7042c59410ebe4..11a2ca5a85ab37 100644 --- a/packages/shared-ux/modal/tabbed/tsconfig.json +++ b/packages/shared-ux/modal/tabbed/tsconfig.json @@ -1,23 +1,22 @@ { - "extends": "../../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node", - "react" - ] - }, - "include": [ - "**/*.ts", - "**/*.tsx", - ], - "exclude": [ - "target/**/*", - ], - "kbn_references": [ - "@kbn/shared-ux-storybook-mock", - "@kbn/share-modal", + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" ] - } - \ No newline at end of file + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/shared-ux-storybook-mock", + "@kbn/i18n-react", + ] +} diff --git a/src/plugins/share/tsconfig.json b/src/plugins/share/tsconfig.json index b884e2fff7f7ce..49a25e75fbef05 100644 --- a/src/plugins/share/tsconfig.json +++ b/src/plugins/share/tsconfig.json @@ -18,9 +18,8 @@ "@kbn/shared-ux-error-boundary", "@kbn/core-chrome-browser", "@kbn/shared-ux-prompt-not-found", - "@kbn/core-capabilities-common", - "@kbn/share-modal", "@kbn/react-kibana-mount", + "@kbn/shared-ux-tabbed-modal", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index b6875ae3303ff8..8c9ba4aea42356 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -112,7 +112,13 @@ "@kbn/presentation-publishing", "@kbn/saved-objects-finder-plugin", "@kbn/unified-data-table", - "@kbn/shared-ux-markdown" + "@kbn/shared-ux-markdown", + "@kbn/reporting-public", + "@kbn/screenshotting-plugin", + "@kbn/licensing-plugin", + "@kbn/reporting-common", + "@kbn/reporting-export-types-pdf-common", + "@kbn/reporting-export-types-png-common" ], "exclude": ["target/**/*"] } From 853cb548914e55a50b163b9aa37fba19276f35c7 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 25 Mar 2024 13:44:33 -0600 Subject: [PATCH 34/69] fix buttons fordashboard --- .../share_context_menu/image_export_modal.tsx | 67 ++++++++----------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 3e310e6b84bc78..f0578e5c1e6de5 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -275,46 +275,37 @@ export const ReportingModalContentUI: FC = (props: Props) => { } return ( <> - {isUnsaved && ( - - } - > - - {(copy) => ( - - - - )} - - - )} - ) : ( - - ) + + } + > + + {(copy) => ( + + + + )} + + + } > From 83bb65a1ddfdab432f27ebbec5cef697bac7e957 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:07:57 +0000 Subject: [PATCH 35/69] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 66dbe95b566924..487841c5f6090b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -788,6 +788,7 @@ packages/shared-ux/router/mocks @elastic/appex-sharedux packages/shared-ux/router/types @elastic/appex-sharedux packages/shared-ux/storybook/config @elastic/appex-sharedux packages/shared-ux/storybook/mock @elastic/appex-sharedux +packages/shared-ux/modal/tabbed @elastic/appex-sharedux packages/kbn-shared-ux-utility @elastic/appex-sharedux x-pack/plugins/observability_solution/slo @elastic/obs-ux-management-team x-pack/packages/kbn-slo-schema @elastic/obs-ux-management-team From 3eb21bcf7f789128329609edbdeae68ee09b442a Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 26 Mar 2024 07:50:51 -0600 Subject: [PATCH 36/69] i18n updates --- .../share_context_menu/image_export_modal.tsx | 16 ++++++++-------- .../register_pdf_png_reporting.tsx | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index f0578e5c1e6de5..65e5985e5ea00a 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -180,20 +180,20 @@ export const ReportingModalContentUI: FC = (props: Props) => { toasts.addSuccess({ title: intl.formatMessage( { - id: 'xpack.reporting.modalContent.successfullyQueuedReportNotificationTitle', + id: 'reporting.modalContent.successfullyQueuedReportNotificationTitle', defaultMessage: 'Queued report for {objectType}', }, { objectType } ), text: toMountPoint( @@ -214,7 +214,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { .catch((error) => { toasts.addError(error, { title: intl!.formatMessage({ - id: 'xpack.reporting.modalContent.notification.reportingErrorTitle', + id: 'reporting.modalContent.notification.reportingErrorTitle', defaultMessage: 'Unable to create report', }), toastMessage: ( @@ -239,7 +239,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { } @@ -250,7 +250,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { } @@ -326,7 +326,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { isLoading={Boolean(createReportingJob)} > @@ -341,7 +341,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { isLoading={Boolean(createReportingJob)} > diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index e84c69cf7aef2e..87d6b2e98121da 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -88,7 +88,7 @@ export const reportingScreenshotShareProvider = ({ shareActions.push({ shareMenuItem: { - name: i18n.translate('xpack.reporting.shareContextMenu.ExportsButtonLabel', { + name: i18n.translate('reporting.shareContextMenu.ExportsButtonLabel', { defaultMessage: 'Export', }), toolTipContent: licenseToolTipContent, @@ -97,7 +97,7 @@ export const reportingScreenshotShareProvider = ({ }, panel: { id: 'reportingImageModal', - title: i18n.translate('xpack.reporting.shareContextMenu.ReportsButtonLabel', { + title: i18n.translate('reporting.shareContextMenu.ReportsButtonLabel', { defaultMessage: 'Generate report', }), content: ( From cd68a9b77c29d24c3f56495e7d448c95c1068063 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 26 Mar 2024 08:48:35 -0600 Subject: [PATCH 37/69] fix some i18n but still issues with buttons not being string literals --- .../public/share/share_context_menu/csv_export_modal.tsx | 4 ++-- .../public/share/share_context_menu/image_export_modal.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx index c48bccbe7210da..b97e9153d01d47 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx @@ -161,7 +161,7 @@ export const CsvModalContentUI: FC = (props: Props) => { data-test-subj="shareReportingCopyURL" > @@ -191,7 +191,7 @@ export const CsvModalContentUI: FC = (props: Props) => { data-test-subj="shareReportingCopyURL" > diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 65e5985e5ea00a..6d94d12d5e5e55 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -278,7 +278,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { } @@ -293,7 +293,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { data-test-subj="shareReportingCopyURL" > @@ -341,7 +341,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { isLoading={Boolean(createReportingJob)} > From d365a1308fba806337949340e3b91d8080e797db Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 26 Mar 2024 17:17:11 -0600 Subject: [PATCH 38/69] remove old share modal component package --- .github/CODEOWNERS | 1 - package.json | 1 - packages/kbn-share-modal/README.md | 3 -- packages/kbn-share-modal/index.ts | 9 ---- packages/kbn-share-modal/jest.config.js | 13 ----- packages/kbn-share-modal/kibana.jsonc | 5 -- packages/kbn-share-modal/modal.tsx | 71 ------------------------- packages/kbn-share-modal/package.json | 6 --- packages/kbn-share-modal/tsconfig.json | 20 ------- src/plugins/share/public/mocks.ts | 1 + tsconfig.base.json | 2 - x-pack/plugins/lens/public/plugin.ts | 2 +- yarn.lock | 4 -- 13 files changed, 2 insertions(+), 136 deletions(-) delete mode 100644 packages/kbn-share-modal/README.md delete mode 100644 packages/kbn-share-modal/index.ts delete mode 100644 packages/kbn-share-modal/jest.config.js delete mode 100644 packages/kbn-share-modal/kibana.jsonc delete mode 100644 packages/kbn-share-modal/modal.tsx delete mode 100644 packages/kbn-share-modal/package.json delete mode 100644 packages/kbn-share-modal/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 32921013c1ad60..07fa8996776094 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -739,7 +739,6 @@ test/plugin_functional/plugins/session_notifications @elastic/kibana-core x-pack/plugins/session_view @elastic/kibana-cloud-security-posture packages/kbn-set-map @elastic/kibana-operations examples/share_examples @elastic/appex-sharedux -packages/kbn-share-modal @elastic/appex-sharedux src/plugins/share @elastic/appex-sharedux packages/kbn-shared-svg @elastic/obs-ux-infra_services-team packages/shared-ux/avatar/solution @elastic/appex-sharedux diff --git a/package.json b/package.json index 37dde23fbc2c39..64a490e2fc0506 100644 --- a/package.json +++ b/package.json @@ -741,7 +741,6 @@ "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", "@kbn/set-map": "link:packages/kbn-set-map", "@kbn/share-examples-plugin": "link:examples/share_examples", - "@kbn/share-modal": "link:packages/kbn-share-modal", "@kbn/share-plugin": "link:src/plugins/share", "@kbn/shared-svg": "link:packages/kbn-shared-svg", "@kbn/shared-ux-avatar-solution": "link:packages/shared-ux/avatar/solution", diff --git a/packages/kbn-share-modal/README.md b/packages/kbn-share-modal/README.md deleted file mode 100644 index 5a83a3c9ee268e..00000000000000 --- a/packages/kbn-share-modal/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @kbn/share-modal - -Empty package generated by @kbn/generate \ No newline at end of file diff --git a/packages/kbn-share-modal/index.ts b/packages/kbn-share-modal/index.ts deleted file mode 100644 index df4bd9b38c37b1..00000000000000 --- a/packages/kbn-share-modal/index.ts +++ /dev/null @@ -1,9 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -export { ShareModal } from './modal'; -export type { ModalProps } from './modal'; diff --git a/packages/kbn-share-modal/jest.config.js b/packages/kbn-share-modal/jest.config.js deleted file mode 100644 index bc33a0512a4009..00000000000000 --- a/packages/kbn-share-modal/jest.config.js +++ /dev/null @@ -1,13 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-share-modal'], -}; diff --git a/packages/kbn-share-modal/kibana.jsonc b/packages/kbn-share-modal/kibana.jsonc deleted file mode 100644 index e4ec66f8d76ed4..00000000000000 --- a/packages/kbn-share-modal/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/share-modal", - "owner": "@elastic/appex-sharedux" - } \ No newline at end of file diff --git a/packages/kbn-share-modal/modal.tsx b/packages/kbn-share-modal/modal.tsx deleted file mode 100644 index 6c000c68acc16e..00000000000000 --- a/packages/kbn-share-modal/modal.tsx +++ /dev/null @@ -1,71 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useState, useMemo, ReactElement } from 'react'; -import { - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiTab, - EuiTabs, - EuiForm, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -export interface ModalProps { - objectType: string; - onClose: () => void; - tabs: Array<{ id: string; name: string; content: ReactElement }>; - modalBodyDescriptions: Array<{ id: string; description: any }>; -} - -export const ShareModal = ({ onClose, objectType, tabs }: ModalProps) => { - const [selectedTabId, setSelectedTabId] = useState('link'); - - useMemo(() => { - return tabs.find(({ id }) => id === selectedTabId); - }, [selectedTabId, tabs]); - - const onSelectedTabChanged = (id: string) => { - setSelectedTabId(id); - }; - - const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - > - {tab.name} - - )); - }; - - const renderTitle = () => ( - - ); - - return ( - // @ts-ignore css prop in EuiModal - - - {renderTitle()} - - - {renderTabs()} - {tabs.find(({ id }) => id === selectedTabId)?.content} - - - ); -}; diff --git a/packages/kbn-share-modal/package.json b/packages/kbn-share-modal/package.json deleted file mode 100644 index 701442f5ecbe0d..00000000000000 --- a/packages/kbn-share-modal/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/share-modal", - "private": true, - "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" - } \ No newline at end of file diff --git a/packages/kbn-share-modal/tsconfig.json b/packages/kbn-share-modal/tsconfig.json deleted file mode 100644 index 22f315077fd698..00000000000000 --- a/packages/kbn-share-modal/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node", - "react", - ], - }, - "include": [ - "**/*.ts*", - ], - "kbn_references": [ - "@kbn/i18n-react", - ], - "exclude": [ - "target/**/*", - ] -} diff --git a/src/plugins/share/public/mocks.ts b/src/plugins/share/public/mocks.ts index f7fd6b8aa0734a..faa45d31d277a3 100644 --- a/src/plugins/share/public/mocks.ts +++ b/src/plugins/share/public/mocks.ts @@ -42,6 +42,7 @@ const createSetupContract = (): Setup => { url, navigate: jest.fn(), setAnonymousAccessServiceProvider: jest.fn(), + kibanaVersion: '', }; return setupContract; }; diff --git a/tsconfig.base.json b/tsconfig.base.json index 9c47be0f1962c9..65740da1becd31 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1472,8 +1472,6 @@ "@kbn/set-map/*": ["packages/kbn-set-map/*"], "@kbn/share-examples-plugin": ["examples/share_examples"], "@kbn/share-examples-plugin/*": ["examples/share_examples/*"], - "@kbn/share-modal": ["packages/kbn-share-modal"], - "@kbn/share-modal/*": ["packages/kbn-share-modal/*"], "@kbn/share-plugin": ["src/plugins/share"], "@kbn/share-plugin/*": ["src/plugins/share/*"], "@kbn/shared-svg": ["packages/kbn-shared-svg"], diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index daf47bba53426e..1b5e22516095a2 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -406,7 +406,7 @@ export class LensPlugin { const startServices$ = Rx.from(getStartServices()); startServices$.subscribe(([, { licensing }]) => { licensing.license$.subscribe((license) => { - share.register( + return share.register( downloadCsvShareProvider({ uiSettings: core.uiSettings, formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, diff --git a/yarn.lock b/yarn.lock index d9a9f9f4617df8..3a38492bf5637c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5990,10 +5990,6 @@ version "0.0.0" uid "" -"@kbn/share-modal@link:packages/kbn-share-modal": - version "0.0.0" - uid "" - "@kbn/share-plugin@link:src/plugins/share": version "0.0.0" uid "" From e98ef9ad3932e3f6498f73eee501e6c9e191994f Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 26 Mar 2024 18:42:12 -0600 Subject: [PATCH 39/69] wip refactor reporting in lens --- .../share_context_menu/image_export_modal.tsx | 18 +++- .../register_pdf_png_reporting.tsx | 18 +++- x-pack/plugins/lens/kibana.jsonc | 2 - .../csv_download_panel_content_lazy.tsx | 6 +- .../csv_download_provider.tsx | 92 ++++--------------- .../app_plugin/csv_download_provider/index.ts | 8 ++ .../plugins/lens/public/app_plugin/index.ts | 1 + x-pack/plugins/lens/public/index.ts | 1 + x-pack/plugins/lens/public/plugin.ts | 12 +-- x-pack/plugins/reporting/kibana.jsonc | 3 +- 10 files changed, 64 insertions(+), 97 deletions(-) create mode 100644 x-pack/plugins/lens/public/app_plugin/csv_download_provider/index.ts diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 6d94d12d5e5e55..690c893c274914 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -51,6 +51,7 @@ export interface ReportingModalProps { // needed for canvas getJobParams?: JobAppParamsPDFV2; objectType: string; + downloadCsvFromLens: () => void; } type AppParams = Omit; @@ -346,6 +347,18 @@ export const ReportingModalContentUI: FC = (props: Props) => { /> ); + + const radioOptions = + objectType === 'lens' + ? [ + { id: 'printablePdfV2', label: 'PDF' }, + { id: 'pngV2', label: 'PNG', 'data-test-subj': 'pngReportOption' }, + { id: 'csv', label: 'CSV', 'data-test-subj': 'lensCSVReport' }, + ] + : [ + { id: 'printablePdfV2', label: 'PDF' }, + { id: 'pngV2', label: 'PNG', 'data-test-subj': 'pngReportOption' }, + ]; return ( <> @@ -356,10 +369,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { { setSelectedRadio(id as Exclude); }} diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 87d6b2e98121da..9fc0dd186e6876 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -9,6 +9,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; +import { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; +import { downloadCSVs } from '@kbn/lens-plugin/public'; import { checkLicense } from '../../license_check'; import { ExportModalShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.'; import { ReportingModalContent } from './reporting_panel_content_lazy'; @@ -56,7 +58,7 @@ export const reportingScreenshotShareProvider = ({ return []; } // for lens png pdf and csv are combined into one modal - const isSupportedType = ['dashboard', 'visualization'].includes(objectType); + const isSupportedType = ['dashboard', 'visualization', 'lens'].includes(objectType); if (!isSupportedType) { return []; @@ -85,6 +87,11 @@ export const reportingScreenshotShareProvider = ({ const isV2Job = isJobV2Params(jobProviderOptions); const requiresSavedState = !isV2Job; + const { title, activeData, columnsSorting } = sharingData as unknown as { + title: string; + activeData: TableInspectorAdapter; + columnsSorting?: string[]; + }; shareActions.push({ shareMenuItem: { @@ -115,6 +122,15 @@ export const reportingScreenshotShareProvider = ({ }} theme={theme} objectType={objectType} + downloadCsvFromLens={async () => { + return await downloadCSVs({ + title, + formatFactory: formatFactoryFn(), + activeData, + uiSettings, + columnsSorting, + }); + }} /> ), }, diff --git a/x-pack/plugins/lens/kibana.jsonc b/x-pack/plugins/lens/kibana.jsonc index f64eb06c30c918..f3a84a8953c302 100644 --- a/x-pack/plugins/lens/kibana.jsonc +++ b/x-pack/plugins/lens/kibana.jsonc @@ -37,7 +37,6 @@ "eventAnnotation", "unifiedSearch", "contentManagement", - "licensing" ], "optionalPlugins": [ "expressionLegacyMetricVis", @@ -58,7 +57,6 @@ "fieldFormats", "charts", "textBasedLanguages", - "esUiShared", ], "extraPublicDirs": [ "common/constants" diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx index bb805cc335349a..dded4f4768a16e 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content_lazy.tsx @@ -8,10 +8,10 @@ import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import * as React from 'react'; import { FC, lazy, Suspense } from 'react'; -import type { ReportingModalProps } from './export_modal_content'; +import type { DownloadPanelContentProps } from './csv_download_panel_content'; const LazyComponent = lazy(() => - import('./export_modal_content').then(({ ReportingModalContent }) => ({ + import('./csv_download_panel_content').then(({ DownloadPanelContent }) => ({ default: DownloadPanelContent, })) ); @@ -30,7 +30,7 @@ export const PanelSpinner: React.FC = (props) => { ); }; -export const DownloadPanelContent: FC> = (props) => { +export const DownloadPanelContent: FC> = (props) => { return ( }> diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index df00fc3fd78147..3a187e3782d0ff 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -9,17 +9,12 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { tableHasFormulas } from '@kbn/data-plugin/common'; import { downloadMultipleAs, ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; -import type { JobParamsProviderOptions } from '@kbn/reporting-public/share'; import { exporters } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import { ReportingAPIClient } from '@kbn/reporting-public'; -import { CoreSetup, ToastsSetup } from '@kbn/core/public'; -import { LayoutParams } from '@kbn/screenshotting-plugin/common'; import { ILicense } from '@kbn/licensing-plugin/public'; import { FormatFactory } from '../../../common/types'; +import { DownloadPanelContent } from './csv_download_panel_content_lazy'; import { TableInspectorAdapter } from '../../editor_frame_service/types'; -import { ReportingModalContent } from './export_modal_content'; -import { DownloadPanelContent } from './csv_download_panel_content'; declare global { interface Window { @@ -31,7 +26,7 @@ declare global { } } -async function downloadCSVs({ +export const downloadCSVs = async ({ activeData, title, formatFactory, @@ -43,7 +38,7 @@ async function downloadCSVs({ formatFactory: FormatFactory; uiSettings: IUiSettingsClient; columnsSorting?: string[]; -}) { +}) => { if (!activeData) { if (window.ELASTIC_LENS_CSV_DOWNLOAD_DEBUG) { window.ELASTIC_LENS_CSV_CONTENT = undefined; @@ -78,7 +73,7 @@ async function downloadCSVs({ if (content) { downloadMultipleAs(content); } -} +}; function getWarnings(activeData: TableInspectorAdapter) { const messages = []; @@ -102,92 +97,38 @@ function getWarnings(activeData: TableInspectorAdapter) { interface DownloadPanelShareOpts { uiSettings: IUiSettingsClient; formatFactoryFn: () => FormatFactory; - reportingApiClient: ReportingAPIClient; - toasts: ToastsSetup; - theme: CoreSetup['theme']; license: ILicense; } export const downloadCsvShareProvider = ({ uiSettings, formatFactoryFn, - reportingApiClient, - toasts, - theme, license, }: DownloadPanelShareOpts): ShareMenuProvider => { - const getShareMenuItems = ({ - objectType, - sharingData, - onClose, - shareableUrl, - shareableUrlForSavedObject, - isDirty, - }: ShareContext) => { + const getShareMenuItems = ({ objectType, sharingData, onClose }: ShareContext) => { if ('lens' !== objectType) { return []; } - const { title, activeData, csvEnabled, columnsSorting, layout, locatorParams } = - sharingData as { - title: string; - activeData: TableInspectorAdapter; - csvEnabled: boolean; - columnsSorting?: string[]; - layout: LayoutParams; - locatorParams: {}; - }; + + const { title, activeData, csvEnabled, columnsSorting } = sharingData as { + title: string; + activeData: TableInspectorAdapter; + csvEnabled: boolean; + columnsSorting?: string[]; + }; const panelTitle = i18n.translate( 'xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel', { - defaultMessage: 'Export', + defaultMessage: 'CSV Download', } ); - const jobProviderOptions: JobParamsProviderOptions = { - shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, - objectType, - sharingData: { title, layout, locatorParams }, - }; + const atLeastGold = license.hasAtLeast('gold'); return [ - license.hasAtLeast('gold') + atLeastGold ? { - shareMenuItem: { - name: panelTitle, - disabled: !csvEnabled, - sortOrder: 1, - }, - panel: { - id: 'csvDownloadPanel', - title: panelTitle, - content: ( - { - await downloadCSVs({ - title, - formatFactory: formatFactoryFn(), - activeData, - uiSettings, - columnsSorting, - }); - }} - /> - ), - }, - } - : { shareMenuItem: { name: panelTitle, icon: 'document', @@ -214,7 +155,8 @@ export const downloadCsvShareProvider = ({ /> ), }, - }, + } + : null, ]; }; diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/index.ts b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/index.ts new file mode 100644 index 00000000000000..f212685f96a996 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/index.ts @@ -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 { downloadCSVs } from './csv_download_provider'; diff --git a/x-pack/plugins/lens/public/app_plugin/index.ts b/x-pack/plugins/lens/public/app_plugin/index.ts index 250e1299fd1289..a3f9b0c962b711 100644 --- a/x-pack/plugins/lens/public/app_plugin/index.ts +++ b/x-pack/plugins/lens/public/app_plugin/index.ts @@ -6,3 +6,4 @@ */ export * from './app'; +export { downloadCSVs } from './csv_download_provider'; diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 0088e434cb72d9..06e244664f8e87 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -121,6 +121,7 @@ export type { ChartInfo } from './chart_info_api'; export { layerTypes } from '../common/layer_types'; export { LENS_EMBEDDABLE_TYPE } from '../common/constants'; +export { downloadCSVs } from './app_plugin'; export type { LensPublicStart, LensPublicSetup, LensSuggestionsApi } from './plugin'; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 1b5e22516095a2..39e38c83c26a6a 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -7,7 +7,7 @@ import * as Rx from 'rxjs'; -import type { AppMountParameters, CoreSetup, CoreStart, HttpStart } from '@kbn/core/public'; +import type { AppMountParameters, CoreSetup, CoreStart } from '@kbn/core/public'; import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; import type { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { @@ -66,7 +66,6 @@ import { import { i18n } from '@kbn/i18n'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { registerSavedObjectToPanelMethod } from '@kbn/embeddable-plugin/public'; -import { ReportingAPIClient } from '@kbn/reporting-public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { @@ -185,7 +184,6 @@ export interface LensPluginStartDependencies { eventAnnotationService: EventAnnotationServiceType; contentManagement: ContentManagementPublicStart; serverless?: ServerlessPluginStart; - http: HttpStart; licensing: LicensingPluginStart; } @@ -396,11 +394,6 @@ export class LensPlugin { if (share) { this.locator = share.url.locators.create(new LensAppLocatorDefinition()); - const reportingApiClient = new ReportingAPIClient( - core.http, - core.uiSettings, - share.kibanaVersion - ); const { getStartServices } = core; const startServices$ = Rx.from(getStartServices()); @@ -410,9 +403,6 @@ export class LensPlugin { downloadCsvShareProvider({ uiSettings: core.uiSettings, formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, - reportingApiClient, - toasts: core.notifications.toasts, - theme: core.theme, license, }) ); diff --git a/x-pack/plugins/reporting/kibana.jsonc b/x-pack/plugins/reporting/kibana.jsonc index be398a1d868d93..bd0f44aad4a81f 100644 --- a/x-pack/plugins/reporting/kibana.jsonc +++ b/x-pack/plugins/reporting/kibana.jsonc @@ -22,7 +22,8 @@ "taskManager", "screenshotMode", "share", - "features" + "features", + "lens" ], "optionalPlugins": [ "security", From 54dfbb17a075d9c89e157184f4955bcfbc5d10ae Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 08:03:12 -0600 Subject: [PATCH 40/69] fix lens licensing for rendering csv only when not gold --- src/plugins/share/public/types.ts | 2 +- x-pack/plugins/lens/kibana.jsonc | 1 + .../csv_download_panel_content.tsx | 48 +++++++++---------- .../csv_download_provider.tsx | 4 +- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index 1832d0a72ed922..3dd584a88af4a9 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -85,7 +85,7 @@ export interface ShareMenuItem { export interface ShareMenuProvider { readonly id: string; - getShareMenuItems: (context: ShareContext) => ShareMenuItem[]; + getShareMenuItems: (context: ShareContext) => ShareMenuItem[] | null; } interface UrlParamExtensionProps { diff --git a/x-pack/plugins/lens/kibana.jsonc b/x-pack/plugins/lens/kibana.jsonc index f3a84a8953c302..b66fe6de8cc64a 100644 --- a/x-pack/plugins/lens/kibana.jsonc +++ b/x-pack/plugins/lens/kibana.jsonc @@ -37,6 +37,7 @@ "eventAnnotation", "unifiedSearch", "contentManagement", + "licensing" ], "optionalPlugins": [ "expressionLegacyMetricVis", diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx index 59d76f78123fcc..f6a41437c4528b 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButton, EuiForm, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButton, EuiForm, EuiModalFooter, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -21,32 +21,32 @@ export function DownloadPanelContent({ warnings = [], }: DownloadPanelContentProps) { return ( - - -

+ <> + + -

- {warnings.map((warning, i) => ( -

{warning}

- ))} -
- - - - -
+ {warnings.map((warning, i) => ( +

{warning}

+ ))} + + +
+ + + + + + ); } diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 3a187e3782d0ff..44cf5405b9d143 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -120,14 +120,14 @@ export const downloadCsvShareProvider = ({ const panelTitle = i18n.translate( 'xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel', { - defaultMessage: 'CSV Download', + defaultMessage: 'Export', } ); const atLeastGold = license.hasAtLeast('gold'); return [ - atLeastGold + !atLeastGold ? { shareMenuItem: { name: panelTitle, From ffa2dee1959cdd6f8a72b87f24c4090ccc10d0c1 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 09:56:08 -0600 Subject: [PATCH 41/69] design changes and more lens work --- .../public/share/share_context_menu/image_export_modal.tsx | 7 ++++--- src/plugins/share/public/types.ts | 6 ++++-- .../csv_download_provider/csv_download_panel_content.tsx | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 690c893c274914..8a8a4eabd83ab0 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -71,8 +71,9 @@ export const ReportingModalContentUI: FC = (props: Props) => { layoutOption, jobProviderOptions, objectType, + isDirty, } = props; - const isSaved = Boolean(objectId); + const isSaved = Boolean(objectId) || !isDirty; const [isStale, setIsStale] = useState(false); const [createReportingJob, setCreatingReportJob] = useState(false); const [selectedRadio, setSelectedRadio] = useState('printablePdfV2'); @@ -382,10 +383,10 @@ export const ReportingModalContentUI: FC = (props: Props) => { }} /> - + + {renderOptions()} - {renderOptions()} {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} {saveWarningMessageWithButton} diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index 3dd584a88af4a9..cbb8bf098a5160 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -84,8 +84,10 @@ export interface ShareMenuItem { * */ export interface ShareMenuProvider { readonly id: string; - - getShareMenuItems: (context: ShareContext) => ShareMenuItem[] | null; + /** + * Null is set for cases where Lens has its less than gold licensing and csv is permitted + */ + getShareMenuItems: (context: ShareContext) => Array; } interface UrlParamExtensionProps { diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx index f6a41437c4528b..0b877363ad55f2 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_panel_content.tsx @@ -43,7 +43,7 @@ export function DownloadPanelContent({ > From 4d3bc5846ab3183aef546684c45b0e7f28ef59e7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:09:39 +0000 Subject: [PATCH 42/69] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-reporting/public/tsconfig.json | 1 + packages/shared-ux/modal/mocks/tsconfig.json | 1 - packages/shared-ux/modal/types/tsconfig.json | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/kbn-reporting/public/tsconfig.json b/packages/kbn-reporting/public/tsconfig.json index 83f8f2f9d5d5ea..2b51c4f4dc979c 100644 --- a/packages/kbn-reporting/public/tsconfig.json +++ b/packages/kbn-reporting/public/tsconfig.json @@ -36,5 +36,6 @@ "@kbn/embeddable-plugin", "@kbn/ui-actions-plugin", "@kbn/react-kibana-mount", + "@kbn/lens-plugin", ] } diff --git a/packages/shared-ux/modal/mocks/tsconfig.json b/packages/shared-ux/modal/mocks/tsconfig.json index 3721023b860cdc..699a2149dd8a30 100644 --- a/packages/shared-ux/modal/mocks/tsconfig.json +++ b/packages/shared-ux/modal/mocks/tsconfig.json @@ -17,6 +17,5 @@ ], "kbn_references": [ "@kbn/shared-ux-storybook-mock", - "@kbn/share-modal", ] } diff --git a/packages/shared-ux/modal/types/tsconfig.json b/packages/shared-ux/modal/types/tsconfig.json index ec9b61142c9886..c9bef4516921f2 100644 --- a/packages/shared-ux/modal/types/tsconfig.json +++ b/packages/shared-ux/modal/types/tsconfig.json @@ -16,6 +16,5 @@ "target/**/*", ], "kbn_references": [ - "@kbn/share-modal", ] } From 110f06361651e302ea5f564834e320b1e420589b Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 10:13:17 -0600 Subject: [PATCH 43/69] fix lens rendering of image-export-modal --- .../share/public/components/share_tabs.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index c803fc8d6b049a..ac00b52035e292 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -32,14 +32,17 @@ export const ShareMenuTabs = () => { const tabs = [linkTab, allowEmbed ? embedTab : null].filter(Boolean); if (shareMenuItems) { - shareMenuItems.forEach(({ shareMenuItem, panel }) => { - tabs.push({ - id: panel.id.toString(), - name: shareMenuItem.name, - // @ts-ignore - content: () => panel.content, + shareMenuItems + // need to filter out the null shareMenuItem from Lens and just use the reporting image modal that includes CSV for Lens + .filter((item) => item !== null) + .forEach(({ shareMenuItem, panel }) => { + tabs.push({ + id: panel.id.toString(), + name: shareMenuItem.name, + // @ts-ignore + content: () => panel.content, + }); }); - }); } return ( From cc276429ca48688e75ea5b5ee2bc880e6db2f159 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 10:25:52 -0600 Subject: [PATCH 44/69] lens working --- .../public/share/share_context_menu/image_export_modal.tsx | 6 +++++- .../kbn-reporting/public/share/share_context_menu/index.ts | 2 ++ .../share_context_menu/register_pdf_png_reporting.tsx | 1 + .../public/share/shared/get_shared_components.tsx | 1 + x-pack/plugins/reporting/public/plugin.ts | 7 ++++++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 8a8a4eabd83ab0..ef5e2fc21b1e32 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -58,7 +58,7 @@ type AppParams = Omit; export type Props = ReportingModalProps & { intl: InjectedIntl }; -type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'printablePdf'; +type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'printablePdf' | 'csv'; export const ReportingModalContentUI: FC = (props: Props) => { const { @@ -72,6 +72,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { jobProviderOptions, objectType, isDirty, + downloadCsvFromLens, } = props; const isSaved = Boolean(objectId) || !isDirty; const [isStale, setIsStale] = useState(false); @@ -172,6 +173,9 @@ export const ReportingModalContentUI: FC = (props: Props) => { }, [markAsStale, getAbsoluteReportGenerationUrl]); const generateReportingJob = () => { + if (selectedRadio === 'csv') { + return downloadCsvFromLens(); + } const decoratedJobParams = apiClient.getDecoratedJobParams( getJobParams(false) as unknown as AppParams ); diff --git a/packages/kbn-reporting/public/share/share_context_menu/index.ts b/packages/kbn-reporting/public/share/share_context_menu/index.ts index 242f4d39f57383..468dcf9c352c54 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/index.ts +++ b/packages/kbn-reporting/public/share/share_context_menu/index.ts @@ -12,6 +12,7 @@ import type { ThemeServiceSetup, ToastsSetup, } from '@kbn/core/public'; +import { FormatFactory } from '@kbn/field-formats-plugin/common'; import { ILicense } from '@kbn/licensing-plugin/public'; import type { LayoutParams } from '@kbn/screenshotting-plugin/common'; import type { ReportingAPIClient } from '../../reporting_api_client'; @@ -25,6 +26,7 @@ export interface ExportPanelShareOpts { application: ApplicationStart; theme: ThemeServiceSetup; version: string; + formatFactoryFn: () => FormatFactory; } export type ExportModalShareOpts = ExportPanelShareOpts; diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 9fc0dd186e6876..f595da43e9f41d 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -26,6 +26,7 @@ export const reportingScreenshotShareProvider = ({ application, usesUiCapabilities, theme, + formatFactoryFn, }: ExportModalShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, diff --git a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx index fa0a39cb064e05..23d22de5e0bdb5 100644 --- a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx +++ b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx @@ -41,6 +41,7 @@ export interface ApplicationProps { */ onClose: () => void; objectType: string; + downloadCsvFromLens: () => void; } export interface ReportingPublicComponents { diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index bb22df0d296235..fc8c04d7323eff 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -35,6 +35,7 @@ import { reportingCsvShareProvider, reportingScreenshotShareProvider, } from '@kbn/reporting-public/share'; +import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ReportingSetup, ReportingStart } from '.'; import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler'; @@ -53,6 +54,8 @@ export interface ReportingPublicPluginStartDependencies { licensing: LicensingPluginStart; uiActions: UiActionsStart; share: SharePluginStart; + // needed for lens csv + fieldFormats: FieldFormatsStart; } /** @@ -205,7 +208,7 @@ export class ReportingPublicPlugin const reportingStart = this.getContract(core); const { toasts } = core.notifications; - startServices$.subscribe(([{ application }, { licensing }]) => { + startServices$.subscribe(([{ application }, { licensing, fieldFormats }]) => { licensing.license$.subscribe((license) => { shareSetup.register( reportingCsvShareProvider({ @@ -217,6 +220,7 @@ export class ReportingPublicPlugin usesUiCapabilities, theme: core.theme, version: this.kibanaVersion, + formatFactoryFn: () => fieldFormats.deserialize, }) ); @@ -231,6 +235,7 @@ export class ReportingPublicPlugin usesUiCapabilities, theme: core.theme, version: this.kibanaVersion, + formatFactoryFn: () => fieldFormats.deserialize, }) ); } From 3da70f88db1c229d8a9f9c3a746ce1dd3da59d46 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:33:42 +0000 Subject: [PATCH 45/69] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-reporting/public/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-reporting/public/tsconfig.json b/packages/kbn-reporting/public/tsconfig.json index 2b51c4f4dc979c..9fed83427f8975 100644 --- a/packages/kbn-reporting/public/tsconfig.json +++ b/packages/kbn-reporting/public/tsconfig.json @@ -37,5 +37,6 @@ "@kbn/ui-actions-plugin", "@kbn/react-kibana-mount", "@kbn/lens-plugin", + "@kbn/field-formats-plugin", ] } From f2d05ea772d1b81a68466829f6bc32a23c564a3c Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 11:01:38 -0600 Subject: [PATCH 46/69] fix circ dep --- package.json | 1 + .../get_csv_panel_actions/index.ts | 9 + .../get_csv_panel_actions/kibana.jsonc | 5 + .../get_csv_panel_actions/package.json | 6 + .../get_csv_panel_action.test.ts | 4 +- .../panel_actions/get_csv_panel_action.tsx | 6 +- .../panel_actions/strings.tsx | 2 +- .../get_csv_panel_actions/tsconfig.json | 31 ++ packages/kbn-reporting/public/share/index.ts | 1 - packages/kbn-reporting/public/tsconfig.json | 6 - tsconfig.base.json | 2 + .../export_modal_content.tsx | 343 ------------------ x-pack/plugins/lens/tsconfig.json | 5 - yarn.lock | 4 + 14 files changed, 64 insertions(+), 361 deletions(-) create mode 100644 packages/kbn-reporting/get_csv_panel_actions/index.ts create mode 100644 packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc create mode 100644 packages/kbn-reporting/get_csv_panel_actions/package.json rename packages/kbn-reporting/{public/share => get_csv_panel_actions}/panel_actions/get_csv_panel_action.test.ts (98%) rename packages/kbn-reporting/{public/share => get_csv_panel_actions}/panel_actions/get_csv_panel_action.tsx (97%) rename packages/kbn-reporting/{public/share => get_csv_panel_actions}/panel_actions/strings.tsx (97%) create mode 100644 packages/kbn-reporting/get_csv_panel_actions/tsconfig.json delete mode 100644 x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx diff --git a/package.json b/package.json index 9432f82a13416d..cdd8cc051dca14 100644 --- a/package.json +++ b/package.json @@ -645,6 +645,7 @@ "@kbn/repo-info": "link:packages/kbn-repo-info", "@kbn/repo-packages": "link:packages/kbn-repo-packages", "@kbn/reporting-common": "link:packages/kbn-reporting/common", + "@kbn/reporting-csv-share-panel": "link:packages/kbn-reporting/get_csv_panel_actions", "@kbn/reporting-example-plugin": "link:x-pack/examples/reporting_example", "@kbn/reporting-export-types-csv": "link:packages/kbn-reporting/export_types/csv", "@kbn/reporting-export-types-csv-common": "link:packages/kbn-reporting/export_types/csv_common", diff --git a/packages/kbn-reporting/get_csv_panel_actions/index.ts b/packages/kbn-reporting/get_csv_panel_actions/index.ts new file mode 100644 index 00000000000000..c15c604dc859eb --- /dev/null +++ b/packages/kbn-reporting/get_csv_panel_actions/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action'; diff --git a/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc b/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc new file mode 100644 index 00000000000000..a37c3dbc2d61bb --- /dev/null +++ b/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/reporting-csv-share-panel", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/kbn-reporting/get_csv_panel_actions/package.json b/packages/kbn-reporting/get_csv_panel_actions/package.json new file mode 100644 index 00000000000000..6d08ab617829a2 --- /dev/null +++ b/packages/kbn-reporting/get_csv_panel_actions/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/reporting-share-csv", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-reporting/public/share/panel_actions/get_csv_panel_action.test.ts b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts similarity index 98% rename from packages/kbn-reporting/public/share/panel_actions/get_csv_panel_action.test.ts rename to packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts index c009bb4be965d7..b057d81cfc877f 100644 --- a/packages/kbn-reporting/public/share/panel_actions/get_csv_panel_action.test.ts +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts @@ -16,8 +16,8 @@ import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { LicenseCheckState } from '@kbn/licensing-plugin/public'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { ReportingAPIClient } from '../..'; -import type { ClientConfigType } from '../../types'; +import { ReportingAPIClient } from '@kbn/reporting-public'; +import type { ClientConfigType } from '@kbn/reporting-public/types'; import { ActionContext, type PanelActionDependencies, diff --git a/packages/kbn-reporting/public/share/panel_actions/get_csv_panel_action.tsx b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx similarity index 97% rename from packages/kbn-reporting/public/share/panel_actions/get_csv_panel_action.tsx rename to packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx index 96ed679a1e18e9..cd34d64a8429aa 100644 --- a/packages/kbn-reporting/public/share/panel_actions/get_csv_panel_action.tsx +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx @@ -28,9 +28,9 @@ import type { UiActionsActionDefinition as ActionDefinition } from '@kbn/ui-acti import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { CSV_REPORTING_ACTION, JobAppParamsCSV } from '@kbn/reporting-export-types-csv-common'; -import type { ClientConfigType } from '../../types'; -import { checkLicense } from '../../license_check'; -import type { ReportingAPIClient } from '../../reporting_api_client'; +import type { ClientConfigType } from '@kbn/reporting-public/types'; +import { checkLicense } from '@kbn/reporting-public/license_check'; +import type { ReportingAPIClient } from '@kbn/reporting-public/reporting_api_client'; import { getI18nStrings } from './strings'; function isSavedSearchEmbeddable( diff --git a/packages/kbn-reporting/public/share/panel_actions/strings.tsx b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/strings.tsx similarity index 97% rename from packages/kbn-reporting/public/share/panel_actions/strings.tsx rename to packages/kbn-reporting/get_csv_panel_actions/panel_actions/strings.tsx index 729d0265d42067..d06ff717dbe1ff 100644 --- a/packages/kbn-reporting/public/share/panel_actions/strings.tsx +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/strings.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { ReportingAPIClient } from '../../reporting_api_client'; +import type { ReportingAPIClient } from '@kbn/reporting-public/reporting_api_client'; interface I18nStrings { displayName: string; diff --git a/packages/kbn-reporting/get_csv_panel_actions/tsconfig.json b/packages/kbn-reporting/get_csv_panel_actions/tsconfig.json new file mode 100644 index 00000000000000..39839a3e9768d7 --- /dev/null +++ b/packages/kbn-reporting/get_csv_panel_actions/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", "**/*.tsx" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/data-plugin", + "@kbn/i18n", + "@kbn/reporting-export-types-csv-common", + "@kbn/licensing-plugin", + "@kbn/i18n-react", + "@kbn/discover-utils", + "@kbn/saved-search-plugin", + "@kbn/discover-plugin", + "@kbn/embeddable-plugin", + "@kbn/ui-actions-plugin", + "@kbn/react-kibana-mount", + "@kbn/reporting-public", + ] +} diff --git a/packages/kbn-reporting/public/share/index.ts b/packages/kbn-reporting/public/share/index.ts index d728637c336a37..37bc87b31939d3 100644 --- a/packages/kbn-reporting/public/share/index.ts +++ b/packages/kbn-reporting/public/share/index.ts @@ -9,6 +9,5 @@ export { getSharedComponents } from './shared'; export { reportingScreenshotShareProvider } from './share_context_menu/register_pdf_png_reporting'; export { reportingCsvShareProvider } from './share_context_menu/register_csv_reporting'; -export { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action'; export type { ReportingPublicComponents } from './shared/get_shared_components'; export type { JobParamsProviderOptions } from './share_context_menu'; diff --git a/packages/kbn-reporting/public/tsconfig.json b/packages/kbn-reporting/public/tsconfig.json index 9fed83427f8975..5f19085403f4a4 100644 --- a/packages/kbn-reporting/public/tsconfig.json +++ b/packages/kbn-reporting/public/tsconfig.json @@ -30,12 +30,6 @@ "@kbn/screenshotting-plugin", "@kbn/i18n-react", "@kbn/test-jest-helpers", - "@kbn/discover-utils", - "@kbn/saved-search-plugin", - "@kbn/discover-plugin", - "@kbn/embeddable-plugin", - "@kbn/ui-actions-plugin", - "@kbn/react-kibana-mount", "@kbn/lens-plugin", "@kbn/field-formats-plugin", ] diff --git a/tsconfig.base.json b/tsconfig.base.json index a660f2024148cc..82933b180a138d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1276,6 +1276,8 @@ "@kbn/repo-source-classifier-cli/*": ["packages/kbn-repo-source-classifier-cli/*"], "@kbn/reporting-common": ["packages/kbn-reporting/common"], "@kbn/reporting-common/*": ["packages/kbn-reporting/common/*"], + "@kbn/reporting-csv-share-panel": ["packages/kbn-reporting/get_csv_panel_actions"], + "@kbn/reporting-csv-share-panel/*": ["packages/kbn-reporting/get_csv_panel_actions/*"], "@kbn/reporting-example-plugin": ["x-pack/examples/reporting_example"], "@kbn/reporting-example-plugin/*": ["x-pack/examples/reporting_example/*"], "@kbn/reporting-export-types-csv": ["packages/kbn-reporting/export_types/csv"], diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx deleted file mode 100644 index 70cecd026ccdd2..00000000000000 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/export_modal_content.tsx +++ /dev/null @@ -1,343 +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 { - EuiButton, - EuiButtonEmpty, - EuiCopy, - EuiFlexGroup, - EuiForm, - EuiFormRow, - EuiModalFooter, - EuiRadioGroup, - EuiSpacer, - EuiToolTip, -} from '@elastic/eui'; -import type { IToasts, IUiSettingsClient, ThemeServiceSetup } from '@kbn/core/public'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; -import url from 'url'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; -import useMountedState from 'react-use/lib/useMountedState'; -import { LayoutParams } from '@kbn/screenshotting-plugin/common'; -import { BaseParams } from '@kbn/reporting-common/types'; -import { ReportingAPIClient } from '@kbn/reporting-public'; -import { ErrorUrlTooLongPanel } from '@kbn/reporting-public/share/share_context_menu/reporting_panel_content/components'; -import { JobAppParamsPDFV2 } from '@kbn/reporting-export-types-pdf-common'; -import { JobParamsPNGV2 } from '@kbn/reporting-export-types-png-common'; - -interface ReportingSharingData { - title: string; - layout: LayoutParams; - reportingDisabled?: boolean; - [key: string]: unknown; -} -const CHROMIUM_MAX_URL_LENGTH = 25 * 1000; -const getMaxUrlLength = () => CHROMIUM_MAX_URL_LENGTH; - -interface JobParamsProviderOptions { - sharingData: ReportingSharingData; - shareableUrl: string; - objectType: string; -} -export interface ReportingModalProps { - apiClient: ReportingAPIClient; - toasts: IToasts; - uiSettings: IUiSettingsClient; - reportType?: string; - requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. - objectId?: string; - isDirty?: boolean; - onClose: () => void; - theme: ThemeServiceSetup; - jobProviderOptions?: JobParamsProviderOptions; - getJobParams?: JobAppParamsPDFV2 | JobParamsPNGV2; - objectType: string; - isDisabled: boolean; - warnings: string[]; - downloadCsvFromLens: () => void; -} - -type AppParams = Omit; - -export type Props = ReportingModalProps & { intl: InjectedIntl }; - -type AllowedImageExportType = 'pngV2' | 'printablePdfV2' | 'csv_searchsource'; - -export const ReportingModalContentUI: FC = (props: Props) => { - const { - apiClient, - intl, - toasts, - theme, - onClose, - objectId, - jobProviderOptions, - objectType, - downloadCsvFromLens, - getJobParams, - } = props; - const isSaved = Boolean(objectId); - const [isStale, setIsStale] = useState(false); - const [createReportingJob, setCreatingReportJob] = useState(false); - const [selectedRadio, setSelectedRadio] = useState('printablePdfV2'); - const [absoluteUrl, setAbsoluteUrl] = useState(''); - const isMounted = useMountedState(); - const exceedsMaxLength = absoluteUrl.length >= getMaxUrlLength(); - - const getJobsParams = useCallback( - (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { - if (!opts) { - return { ...getJobParams }; - } - - const { - sharingData: { title, layout, locatorParams }, - } = opts; - - const baseParams = { - objectType, - layout, - title, - }; - - if (type === 'printablePdfV2') { - // multi locator for PDF V2 - return { ...baseParams, locatorParams: [locatorParams] }; - } else if (type === 'pngV2') { - // single locator for PNG V2 - return { ...baseParams, locatorParams }; - } - }, - [getJobParams, objectType] - ); - - const getLayout = useCallback((): LayoutParams => { - const el = document.querySelector('[data-shared-items-container]'); - const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; - const dimensions = { height, width }; - - return { id: 'preserve_layout', dimensions }; - }, []); - - const getJobParamHelper = useCallback( - (shareableUrl?: boolean) => { - return { ...getJobsParams(selectedRadio, jobProviderOptions), layout: getLayout() }; - }, - [getJobsParams, jobProviderOptions, selectedRadio, getLayout] - ); - - const getAbsoluteReportGenerationUrl = useMemo( - () => () => { - if (getJobsParams(selectedRadio, jobProviderOptions) !== undefined) { - const relativePath = apiClient.getReportingPublicJobPath( - selectedRadio, - apiClient.getDecoratedJobParams(getJobParamHelper(true) as unknown as AppParams) - ); - return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); - } - }, - [apiClient, selectedRadio, getJobsParams, jobProviderOptions, getJobParamHelper] - ); - - const markAsStale = useCallback(() => { - if (!isMounted) return; - setIsStale(true); - }, [isMounted]); - - useEffect(() => { - getAbsoluteReportGenerationUrl(); - markAsStale(); - }, [markAsStale, getAbsoluteReportGenerationUrl]); - - const generateReportingJob = () => { - if (selectedRadio === 'csv_searchsource') { - return downloadCsvFromLens(); - } - const decoratedJobParams = apiClient.getDecoratedJobParams( - getJobParamHelper() as unknown as AppParams - ); - - setCreatingReportJob(true); - return apiClient - .createReportingJob(selectedRadio, decoratedJobParams) - .then(() => { - toasts.addSuccess({ - title: intl.formatMessage( - { - id: 'share.modalContent.successfullyQueuedReportNotificationTitle', - defaultMessage: 'Queued report for {objectType}', - }, - { objectType } - ), - text: toMountPoint( - - - - ), - }} - />, - { theme$: theme.theme$ } - ), - 'data-test-subj': 'queueReportSuccess', - }); - if (onClose) { - onClose(); - } - if (isMounted()) { - setCreatingReportJob(false); - } - }) - .catch((error) => { - toasts.addError(error, { - title: intl.formatMessage({ - id: 'share.modalContent.notification.reportingErrorTitle', - defaultMessage: 'Unable to create report', - }), - toastMessage: ( - // eslint-disable-next-line react/no-danger - - ) as unknown as string, - }); - if (isMounted()) { - setCreatingReportJob(false); - } - }); - }; - - const renderCopyURLButton = ({ - isUnsaved, - }: { - isUnsaved: boolean; - exceedsMaxLength: boolean; - }) => { - if (isUnsaved) { - if (exceedsMaxLength) { - return ; - } - return ( - - } - > - - {(copy) => ( - - - - )} - - - ); - } else if (exceedsMaxLength) { - return ; - } - return ( - - - {(copy) => ( - - - - )} - - - ); - }; - - const saveWarningMessageWithButton = - objectId === undefined || objectId === '' || !isSaved || props.isDirty || isStale ? ( - - - generateReportingJob()} - data-test-subj="generateReportButton" - isLoading={Boolean(createReportingJob)} - > - - - - - ) : ( - generateReportingJob()} - data-test-subj="generateReportButton" - isLoading={Boolean(createReportingJob)} - > - - - ); - - return ( - <> - - - { - setSelectedRadio(id as Exclude); - getJobsParams(selectedRadio); - }} - name="image reporting radio group" - idSelected={selectedRadio} - legend={{ - children: File type, - }} - /> - - - {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} - - {saveWarningMessageWithButton} - - ); -}; - -export const ReportingModalContent = injectI18n(ReportingModalContentUI); diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 22cd63645a3c4d..99806418a2605e 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -112,12 +112,7 @@ "@kbn/saved-objects-finder-plugin", "@kbn/unified-data-table", "@kbn/shared-ux-markdown", - "@kbn/reporting-public", - "@kbn/screenshotting-plugin", "@kbn/licensing-plugin", - "@kbn/reporting-common", - "@kbn/reporting-export-types-pdf-common", - "@kbn/reporting-export-types-png-common" ], "exclude": ["target/**/*"] } diff --git a/yarn.lock b/yarn.lock index d608ddebb88edf..a8d989e657b596 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5598,6 +5598,10 @@ version "0.0.0" uid "" +"@kbn/reporting-csv-share-panel@link:packages/kbn-reporting/get_csv_panel_actions": + version "0.0.0" + uid "" + "@kbn/reporting-example-plugin@link:x-pack/examples/reporting_example": version "0.0.0" uid "" From e7927af84737ccae1f194ac57a726c6bb46a3b12 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:11:06 +0000 Subject: [PATCH 47/69] [CI] Auto-commit changed files from 'node scripts/lint_packages --fix' --- packages/kbn-reporting/get_csv_panel_actions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-reporting/get_csv_panel_actions/package.json b/packages/kbn-reporting/get_csv_panel_actions/package.json index 6d08ab617829a2..3842e94ea75517 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/package.json +++ b/packages/kbn-reporting/get_csv_panel_actions/package.json @@ -1,5 +1,5 @@ { - "name": "@kbn/reporting-share-csv", + "name": "@kbn/reporting-csv-share-panel", "private": true, "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0" From 1b7fa3feb19d6baa04ae723f542a1e26b5aabcd9 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 11:19:59 -0600 Subject: [PATCH 48/69] fix imports --- x-pack/plugins/lens/public/plugin.ts | 8 ++++---- x-pack/plugins/reporting/public/plugin.ts | 8 ++++---- x-pack/plugins/reporting/tsconfig.json | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 16631631b4fb50..ecab881be1e99f 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import * as Rx from 'rxjs'; +import { from } from 'rxjs'; import type { AppMountParameters, CoreSetup, CoreStart } from '@kbn/core/public'; import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; @@ -182,7 +182,7 @@ export interface LensPluginStartDependencies { eventAnnotationService: EventAnnotationServiceType; contentManagement: ContentManagementPublicStart; serverless?: ServerlessPluginStart; - licensing: LicensingPluginStart; + licensing?: LicensingPluginStart; } export interface LensPublicSetup { @@ -394,9 +394,9 @@ export class LensPlugin { this.locator = share.url.locators.create(new LensAppLocatorDefinition()); const { getStartServices } = core; - const startServices$ = Rx.from(getStartServices()); + const startServices$ = from(getStartServices()); startServices$.subscribe(([, { licensing }]) => { - licensing.license$.subscribe((license) => { + licensing?.license$.subscribe((license) => { return share.register( downloadCsvShareProvider({ uiSettings: core.uiSettings, diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index fc8c04d7323eff..02a42c06aa47d3 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import * as Rx from 'rxjs'; +import { from, ReplaySubject } from 'rxjs'; import { CoreSetup, @@ -30,11 +30,11 @@ import type { ClientConfigType } from '@kbn/reporting-public'; import { ReportingAPIClient } from '@kbn/reporting-public'; import { - ReportingCsvPanelAction, getSharedComponents, reportingCsvShareProvider, reportingScreenshotShareProvider, } from '@kbn/reporting-public/share'; +import { ReportingCsvPanelAction } from '@kbn/reporting-csv-share-panel'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ReportingSetup, ReportingStart } from '.'; import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler'; @@ -73,7 +73,7 @@ export class ReportingPublicPlugin { private kibanaVersion: string; private apiClient?: ReportingAPIClient; - private readonly stop$ = new Rx.ReplaySubject(1); + private readonly stop$ = new ReplaySubject(1); private readonly title = i18n.translate('xpack.reporting.management.reportingTitle', { defaultMessage: 'Reporting', }); @@ -126,7 +126,7 @@ export class ReportingPublicPlugin uiActions: uiActionsSetup, } = setupDeps; - const startServices$ = Rx.from(getStartServices()); + const startServices$ = from(getStartServices()); const usesUiCapabilities = !this.config.roles.enabled; const apiClient = this.getApiClient(core.http, core.uiSettings); diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json index c5a5e32008ae09..867233ad463b0c 100644 --- a/x-pack/plugins/reporting/tsconfig.json +++ b/x-pack/plugins/reporting/tsconfig.json @@ -49,6 +49,7 @@ "@kbn/core-http-request-handler-context-server", "@kbn/reporting-public", "@kbn/analytics-client", + "@kbn/reporting-csv-share-panel", ], "exclude": [ "target/**/*", From 014b8ca5417ba40d5e405bbcc426564bb4c204b4 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:28:31 +0000 Subject: [PATCH 49/69] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c82ac1a9823c43..b0d2c0e3bb7d45 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -641,6 +641,7 @@ packages/kbn-repo-path @elastic/kibana-operations packages/kbn-repo-source-classifier @elastic/kibana-operations packages/kbn-repo-source-classifier-cli @elastic/kibana-operations packages/kbn-reporting/common @elastic/appex-sharedux +packages/kbn-reporting/get_csv_panel_actions @elastic/appex-sharedux x-pack/examples/reporting_example @elastic/appex-sharedux packages/kbn-reporting/export_types/csv @elastic/appex-sharedux packages/kbn-reporting/export_types/csv_common @elastic/appex-sharedux From 6c8615c84b0cf6576c1004993f1de140a3bd03b0 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 13:23:17 -0600 Subject: [PATCH 50/69] design fixes --- .../share_context_menu/image_export_modal.tsx | 123 ++++++++++-------- .../modal/tabbed/src/tabbed_modal.tsx | 8 +- 2 files changed, 74 insertions(+), 57 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index ef5e2fc21b1e32..ba00c4d9e8d0ef 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -12,14 +12,13 @@ import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, - EuiForm, - EuiFormRow, EuiModalFooter, EuiRadioGroup, EuiSpacer, EuiSwitch, EuiSwitchEvent, EuiToolTip, + EuiText, } from '@elastic/eui'; import type { IUiSettingsClient, ThemeServiceSetup, ToastsSetup } from '@kbn/core/public'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; @@ -244,11 +243,14 @@ export const ReportingModalContentUI: FC = (props: Props) => { <> + + + } + css={{ display: 'block' }} checked={usePrintLayout} onChange={handlePrintLayoutChange} data-test-subj="usePrintLayout" @@ -268,38 +270,39 @@ export const ReportingModalContentUI: FC = (props: Props) => { } return null; }; - const renderCopyURLButton = ({ - isUnsaved, - }: { - isUnsaved: boolean; - exceedsMaxLength: boolean; - }) => { - if (isUnsaved && exceedsMaxLength) { - return ; - } else if (exceedsMaxLength) { - return ; + const renderCopyURLButton = useCallback(() => { + if (exceedsMaxLength) { + return ; } + return ( <> + isDirty ? ( + + ) : ( + + ) } > {(copy) => ( @@ -318,11 +321,11 @@ export const ReportingModalContentUI: FC = (props: Props) => { ); - }; + }, [absoluteUrl, exceedsMaxLength, isDirty]); const saveWarningMessageWithButton = - objectId === undefined || objectId === '' || !isSaved || props.isDirty || isStale ? ( - + objectId === undefined || objectId === '' || !isSaved || isDirty || isStale ? ( + <> = (props: Props) => { /> - + ) : ( = (props: Props) => { ]; return ( <> - - + + + + { + setSelectedRadio(id as Exclude); + }} + name="image reporting radio group" + idSelected={selectedRadio} + legend={{ + children: , + }} /> - - - { - setSelectedRadio(id as Exclude); - }} - name="image reporting radio group" - idSelected={selectedRadio} - legend={{ - children: ( - - ), - }} - /> - - + + + {renderOptions()} - - - {renderCopyURLButton({ isUnsaved: !isSaved, exceedsMaxLength })} - {saveWarningMessageWithButton} + {renderCopyURLButton()} + {objectType === 'dashboard' ? ( + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + ) : ( + saveWarningMessageWithButton + )} ); diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 031aa440c6d088..cce41a4fe1d0d3 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -64,15 +64,11 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid }; const btnClickHandler = useCallback(() => { - modalActionBtn ? modalActionBtn.handler({ state: selectedTabState }) : null; + modalActionBtn && modalActionBtn.handler({ state: selectedTabState }); }, [selectedTabState, modalActionBtn]); return ( - + {modalTitle} From f240814ceba06724160e2d860089301d63fc8a7a Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 14:44:00 -0600 Subject: [PATCH 51/69] lint change --- packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index cce41a4fe1d0d3..0f634b7dc81536 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useMemo, useCallback, Fragment, type ComponentProps, type FC } from 'react'; +import React, { useMemo, Fragment, type ComponentProps, type FC } from 'react'; import { EuiButton, EuiModal, @@ -63,10 +63,6 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid )); }; - const btnClickHandler = useCallback(() => { - modalActionBtn && modalActionBtn.handler({ state: selectedTabState }); - }, [selectedTabState, modalActionBtn]); - return ( @@ -87,7 +83,9 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid fill data-test-subj={modalActionBtn.dataTestSubj} data-share-url={state.url} - onClick={btnClickHandler} + onClick={() => { + modalActionBtn.handler({ state: selectedTabState }); + }} > Date: Wed, 27 Mar 2024 15:00:17 -0600 Subject: [PATCH 52/69] add jest config file --- .../get_csv_panel_actions/jest.config.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/kbn-reporting/get_csv_panel_actions/jest.config.js diff --git a/packages/kbn-reporting/get_csv_panel_actions/jest.config.js b/packages/kbn-reporting/get_csv_panel_actions/jest.config.js new file mode 100644 index 00000000000000..3322e94a6aa4dc --- /dev/null +++ b/packages/kbn-reporting/get_csv_panel_actions/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/kbn-reporting/get_csv_panel_actions'], +}; From 2475cdcf88c8c5bc9fe8615f0ac439fa4a569342 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 27 Mar 2024 15:40:21 -0600 Subject: [PATCH 53/69] fix --- .../public/share/share_context_menu/csv_export_modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx index b97e9153d01d47..45fe583b8bfba7 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx @@ -191,8 +191,8 @@ export const CsvModalContentUI: FC = (props: Props) => { data-test-subj="shareReportingCopyURL" > )} From 91b99f8dee8bdfb95e6de7f347443c2c6af5c42f Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 07:20:04 -0600 Subject: [PATCH 54/69] styling change --- .../share/share_context_menu/image_export_modal.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index ba00c4d9e8d0ef..765fea092f6466 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -390,8 +390,9 @@ export const ReportingModalContentUI: FC = (props: Props) => { = (props: Props) => { data-test-subj="generateReportButton" isLoading={Boolean(createReportingJob)} > - + + + ) : ( saveWarningMessageWithButton From 31428d8719b8aa76b0bdbed0f864feab87350f80 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 11:10:26 -0600 Subject: [PATCH 55/69] disable csv if not saved --- .../public/share/share_context_menu/csv_export_modal.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx index 45fe583b8bfba7..fbe8dfad1fa4a1 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/csv_export_modal.tsx @@ -47,7 +47,8 @@ export type Props = CsvModalProps & { intl: InjectedIntl }; export const CsvModalContentUI: FC = (props: Props) => { const isSaved = Boolean(props.objectId); - const { apiClient, getJobParams, intl, toasts, theme, onClose, objectType, reportType } = props; + const { apiClient, getJobParams, intl, toasts, theme, onClose, objectType, reportType, isDirty } = + props; const isMounted = useMountedState(); const [createReportingJob, setCreatingReportJob] = useState(false); const [absoluteUrl, setAbsoluteUrl] = useState(''); @@ -222,7 +223,7 @@ export const CsvModalContentUI: FC = (props: Props) => { })} > generateReportingJob()} data-test-subj="generateReportButton" isLoading={Boolean(createReportingJob)} @@ -235,7 +236,7 @@ export const CsvModalContentUI: FC = (props: Props) => {
) : ( generateReportingJob()} data-test-subj="generateReportButton" From b8688e7bbb42719d0135df27e3922f8073954a56 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 11:36:40 -0600 Subject: [PATCH 56/69] refactored tabbed_modal --- .../modal/tabbed/src/context/index.tsx | 111 +++++++++--------- .../modal/tabbed/src/tabbed_modal.tsx | 54 ++++++--- .../share/public/components/share_tabs.tsx | 1 - .../public/components/tabs/embed/index.tsx | 16 ++- .../public/components/tabs/link/index.tsx | 13 +- 5 files changed, 104 insertions(+), 91 deletions(-) diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 55333d878550ee..943b67f7110282 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -14,71 +14,59 @@ import React, { useRef, useCallback, type PropsWithChildren, - type ReactElement, type Dispatch, } from 'react'; -import { type EuiTabProps, type CommonProps } from '@elastic/eui'; +import { once } from 'lodash'; interface IDispatchAction { type: string; payload: any; } -export type IModalTabState = Record; +export type IDispatchFunction = Dispatch; -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type IModalMetaState = { +export interface IMetaState { selectedTabId: string | null; -}; - -type IReducer = (state: S, action: IDispatchAction) => S; - -export type IModalTabContent = (props: { - state: S; - dispatch: Dispatch; -}) => ReactElement; - -interface IModalTabActionBtn extends CommonProps { - id: string; - dataTestSubj: string; - defaultMessage: string; - formattedMessageId: string; - handler: (args: { state: S }) => void; - isCopy?: boolean; } -export interface IModalTabDeclaration extends EuiTabProps { +type IReducer = (state: S, action: IDispatchAction) => S; + +export interface ITabDeclaration { id: string; name: string; initialState?: Partial; reducer?: IReducer; - description?: ReactElement; - 'data-test-subj'?: string; - content?: IModalTabContent; - modalActionBtn?: IModalTabActionBtn; } -interface IModalContext { - tabs: Array, 'reducer' | 'initialState'>> | null; - state: { meta: IModalMetaState } & Record; +interface IModalContext>>> { + tabs: Array>; + state: { + meta: IMetaState; + [index: string]: any; + }; dispatch: Dispatch; } -const ModalContext = createContext({ - tabs: [], - state: { - meta: { - selectedTabId: null, +const createStateContext = once(>>>() => + createContext({ + tabs: [], + state: { + meta: { + selectedTabId: null, + }, }, - }, - dispatch: () => {}, -}); + dispatch: () => {}, + } as IModalContext) +); + +export const useModalContext = >>>() => + useContext(createStateContext()); /** * @description defines state transition for meta information to manage the modal, meta action types * must be prefixed with the string 'META_' */ -const modalMetaReducer: IReducer = (state, action) => { +const modalMetaReducer: IReducer = (state, action) => { switch (action.type) { case 'META_selectedTabId': return { @@ -90,20 +78,30 @@ const modalMetaReducer: IReducer = (state, action) => { } }; -export type IModalContextProviderProps>> = +export type IModalContextProviderProps>>> = PropsWithChildren<{ + /** + * Array of tab declaration to be rendered into the modal that will be rendered + */ tabs: Tabs; + /** + * ID of the tab we'd like the modal to have selected on render + */ defaultSelectedTabId: Tabs[number]['id']; }>; -export function ModalContextProvider>>({ +export function ModalContextProvider>>>({ tabs, defaultSelectedTabId, children, }: IModalContextProviderProps) { - const modalTabDefinitions = useRef([]); + const ModalContext = createStateContext(); + + type IModalInstanceContext = IModalContext; - const initialModalState = useRef({ + const modalTabDefinitions = useRef([]); + + const initialModalState = useRef({ // instantiate state with default meta information meta: { selectedTabId: defaultSelectedTabId, @@ -112,35 +110,36 @@ export function ModalContextProvider - tabs?.reduce((result, { id, reducer, initialState, ...rest }) => { - initialModalState.current[id] = initialState ?? {}; - modalTabDefinitions.current?.push({ id, reducer, ...rest }); - result[id] = reducer; + tabs.reduce((result, { reducer, initialState, ...rest }) => { + initialModalState.current[rest.id] = initialState ?? {}; + // @ts-ignore + modalTabDefinitions.current.push({ ...rest }); + result[rest.id] = reducer; return result; - }, {}), + }, {} as Record), [tabs] ); - const combineReducers = useCallback(function ( - reducers: Record> - ) { - return (state: IModalContext['state'], action: IDispatchAction) => { + const combineReducers = useCallback(function (reducers: Record) { + return (state: IModalInstanceContext['state'], action: IDispatchAction) => { const newState = { ...state }; if (/^meta_/i.test(action.type)) { newState.meta = modalMetaReducer(newState.meta, action); } else { const selectedTabId = state.meta.selectedTabId!; + const selectedTabReducer = reducers[selectedTabId]; - newState[selectedTabId] = reducers[selectedTabId](newState[selectedTabId], action); + if (selectedTabReducer) { + newState[selectedTabId] = selectedTabReducer(newState[selectedTabId], action); + } } return newState; }; - }, - []); + }, []); - const createInitialState = useCallback((state: IModalContext['state']) => { + const createInitialState = useCallback((state: IModalInstanceContext['state']) => { return state; }, []); @@ -156,5 +155,3 @@ export function ModalContextProvider ); } - -export const useModalContext = () => useContext(ModalContext); diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 0f634b7dc81536..f4551e393a4236 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useMemo, Fragment, type ComponentProps, type FC } from 'react'; +import React, { useMemo, Fragment, type ComponentProps, type FC, type ReactElement } from 'react'; import { EuiButton, EuiModal, @@ -16,23 +16,45 @@ import { EuiModalHeaderTitle, EuiTabs, EuiTab, + type EuiTabProps, + type CommonProps, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; import { ModalContextProvider, useModalContext, - type IModalTabState, - type IModalTabDeclaration, + type ITabDeclaration, + type IDispatchFunction, type IModalContextProviderProps, } from './context'; +export type IModalTabContent = (props: { + state: S | null; + dispatch: IDispatchFunction; +}) => ReactElement; + +interface IModalTabActionBtn extends CommonProps { + id: string; + dataTestSubj: string; + label: string; + handler?: (args: { state: S }) => void; + isCopy?: boolean; +} + +export interface IModalTabDeclaration extends EuiTabProps, ITabDeclaration { + description?: string; + 'data-test-subj'?: string; + content: IModalTabContent; + modalActionBtn: IModalTabActionBtn; +} + interface ITabbedModalInner extends Pick, 'onClose'> { modalWidth?: number; modalTitle?: string; } const TabbedModalInner: FC = ({ onClose, modalTitle, modalWidth }) => { - const { tabs, state, dispatch } = useModalContext(); + const { tabs, state, dispatch } = + useModalContext>>>(); const selectedTabId = state.meta.selectedTabId; const selectedTabState = useMemo( @@ -41,7 +63,7 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid ); const { content: SelectedTabContent, modalActionBtn } = useMemo(() => { - return tabs?.find((obj) => obj.id === selectedTabId)!; + return tabs.find((obj) => obj.id === selectedTabId)!; }, [selectedTabId, tabs]); const onSelectedTabChanged = (id: string) => { @@ -49,7 +71,7 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid }; const renderTabs = () => { - return tabs?.map((tab, index) => ( + return tabs.map((tab, index) => ( onSelectedTabChanged(tab.id)} @@ -64,14 +86,18 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid }; return ( - + {modalTitle} {renderTabs()} - {React.createElement(SelectedTabContent!, { + {React.createElement(SelectedTabContent, { state: selectedTabState, dispatch, })} @@ -84,13 +110,11 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid data-test-subj={modalActionBtn.dataTestSubj} data-share-url={state.url} onClick={() => { - modalActionBtn.handler({ state: selectedTabState }); + // @ts-ignore null for export modal on state since it's not using the buttons + modalActionBtn!.handler!({ state: selectedTabId }); }} > - + {modalActionBtn.label} )} @@ -98,7 +122,7 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid ); }; -export function TabbedModal>>({ +export function TabbedModal>>({ tabs, defaultSelectedTabId, ...rest diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index ac00b52035e292..554c90930dde9b 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -47,7 +47,6 @@ export const ShareMenuTabs = () => { return ( - ), + description: i18n.translate('share.dashboard.embed.description', { + defaultMessage: + 'Embed this dashboard into another webpage. Select which menu items to include in the embeddable view.', + }), reducer: embedTabReducer, content: EmbedTabContent, modalActionBtn: { id: 'embed', dataTestSubj: 'copyEmbedUrlButton', - formattedMessageId: 'share.link.copyEmbedCodeButton', - defaultMessage: 'Copy Embed', + label: i18n.translate('share.link.copyEmbedCodeButton', { + defaultMessage: 'Copy Embed', + }), handler: ({ state }) => { copyToClipboard(state.url); }, diff --git a/src/plugins/share/public/components/tabs/link/index.tsx b/src/plugins/share/public/components/tabs/link/index.tsx index c25928c681d790..b3267e277c340a 100644 --- a/src/plugins/share/public/components/tabs/link/index.tsx +++ b/src/plugins/share/public/components/tabs/link/index.tsx @@ -8,7 +8,6 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { copyToClipboard } from '@elastic/eui'; import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; import { useShareTabsContext } from '../../context'; @@ -81,19 +80,15 @@ export const linkTab: ILinkTab = { name: i18n.translate('share.contextMenu.permalinksTab', { defaultMessage: 'Links', }), - description: ( - - ), + description: i18n.translate('share.dashboard.link.description', { + defaultMessage: 'Share a direct link to this search.', + }), content: LinkTabContent, reducer: linkTabReducer, modalActionBtn: { id: 'link', dataTestSubj: 'copyShareUrlButton', - formattedMessageId: 'share.link.copyLinkButton', - defaultMessage: 'Copy link', + label: i18n.translate('share.link.copyLinkButton', { defaultMessage: 'Copy link' }), handler: ({ state }) => { copyToClipboard(state.dashboardUrl); }, From 96c5193a786c65cba21b56b260aa4b95a3191eaf Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:45:11 +0000 Subject: [PATCH 57/69] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/shared-ux/modal/tabbed/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared-ux/modal/tabbed/tsconfig.json b/packages/shared-ux/modal/tabbed/tsconfig.json index 11a2ca5a85ab37..699a2149dd8a30 100644 --- a/packages/shared-ux/modal/tabbed/tsconfig.json +++ b/packages/shared-ux/modal/tabbed/tsconfig.json @@ -17,6 +17,5 @@ ], "kbn_references": [ "@kbn/shared-ux-storybook-mock", - "@kbn/i18n-react", ] } From 19f904be644794f936cad051106fdc505dbaa4f7 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 14:35:52 -0600 Subject: [PATCH 58/69] clean up types --- ...screen_capture_panel_content.test.tsx.snap | 335 ------------------ .../screen_capture_panel_content.test.tsx | 43 +-- .../shared-ux/modal/mocks/src/storybook.ts | 55 --- packages/shared-ux/modal/tabbed/index.tsx | 2 +- .../modal/tabbed/src/storybook/setup.ts | 2 +- .../modal/tabbed/src/tabbed_modal.stories.tsx | 182 ---------- packages/shared-ux/modal/types/index.d.ts | 1 - .../public/components/tabs/embed/index.tsx | 4 +- 8 files changed, 5 insertions(+), 619 deletions(-) delete mode 100644 packages/shared-ux/modal/mocks/src/storybook.ts delete mode 100644 packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx diff --git a/packages/kbn-reporting/public/share/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap b/packages/kbn-reporting/public/share/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap index 16b666d519c467..5b8144201517e6 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap +++ b/packages/kbn-reporting/public/share/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap @@ -196,338 +196,3 @@ exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout `; - -exports[`ScreenCapturePanelContent properly renders a view with "print" layout option 1`] = ` -
-
-

- - Analytical Apps can take a minute or two to generate based upon the size of your test-object-type. - -

-
-
-
-
-
- - - - Optimize for printing - - -
-
- - Uses multiple pages, showing at most 2 visualizations per page - -
-
-
- -
-
-
- - -
-
-
-
-
-

- - Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. - -

-
-
-
-

-

-
-
-
-

- - Save your work before copying this URL. - -

-
-
-
-
-
-
-
-
-`; - -exports[`ScreenCapturePanelContent renders the default view properly 1`] = ` -
-
-

- - Analytical Apps can take a minute or two to generate based upon the size of your test-object-type. - -

-
-
- -
-
-
- - -
-
-
-
-
-

- - Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. - -

-
-
-
-

-

-
-
-
-

- - Save your work before copying this URL. - -

-
-
-
-
-
-
-
-
-`; diff --git a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx index 42d599da196226..b9fe7e97aed5ae 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx @@ -30,30 +30,10 @@ const getJobParamsDefault = () => ({ const theme = themeServiceMock.createSetupContract(); -test('ScreenCapturePanelContent renders the default view properly', () => { - const component = mount( - - - - ); - expect(component.find('EuiForm').render()).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 component = mount( { - const component = mount( - - - - ); - expect(component.find('EuiForm').render()).toMatchSnapshot(); - expect(component.text()).toMatch('Optimize for printing'); -}); - test('ScreenCapturePanelContent decorated job params are visible in the POST URL', () => { const component = mount( @@ -144,6 +103,6 @@ test('ScreenCapturePanelContent decorated job params are visible in the POST URL ); expect(component.find('EuiCopy').prop('textToCopy')).toMatchInlineSnapshot( - `"http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Apreserve_layout%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%2Cversion%3A%277.15.0%27%29"` + `"http://localhost/api/reporting/generate/Analytical%20App?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A768%2Cwidth%3A1024%29%2Cid%3Acanvas%29%2CobjectType%3Atest-object-type%2Ctitle%3A%27Test%20Report%20Title%27%2Cversion%3A%277.15.0%27%29"` ); }); diff --git a/packages/shared-ux/modal/mocks/src/storybook.ts b/packages/shared-ux/modal/mocks/src/storybook.ts deleted file mode 100644 index 489bc8e3d10e7f..00000000000000 --- a/packages/shared-ux/modal/mocks/src/storybook.ts +++ /dev/null @@ -1,55 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { AbstractStorybookMock, ArgumentParams } from '@kbn/shared-ux-storybook-mock'; -import { ModalProps as ShareModalProps } from '@kbn/share-modal'; - -import { ArgTypes } from '@storybook/react'; -import { ShareModalStorybookMock } from '..'; - -type PropArguments = Pick; -type Arguments = PropArguments; - -/** - * Storybook parameters provided from the controls addon. - */ -export type Params = Record; - -const redirectMock = new ShareModalStorybookMock(); - -export class StorybookMock extends AbstractStorybookMock { - serviceArguments: ArgTypes<{}>; - getServices(params?: ArgumentParams | undefined): {} { - throw new Error('Method not implemented.'); - } - propArguments = { - objectType: { - control: { - type: 'text', - }, - defaultValue: '', - }, - modalBodyDescriptions: { - control: { - type: 'text', - }, - defaultValue: '', - }, - tabs: {}, - }; - - dependencies = []; - - getProps(params?: Params): ShareModalProps { - return { - modalBodyDescriptions: this.getArgumentValue('description', params), - objectType: this.getArgumentValue('objectType', params), - tabs: this.getArgumentValue('tabs', params), - }; - } -} diff --git a/packages/shared-ux/modal/tabbed/index.tsx b/packages/shared-ux/modal/tabbed/index.tsx index 574cd435f68ed0..7ba578418283c4 100644 --- a/packages/shared-ux/modal/tabbed/index.tsx +++ b/packages/shared-ux/modal/tabbed/index.tsx @@ -7,4 +7,4 @@ */ export { TabbedModal } from './src/tabbed_modal'; -export type { IModalTabDeclaration } from './src/context'; +export type { ITabDeclaration } from './src/context'; diff --git a/packages/shared-ux/modal/tabbed/src/storybook/setup.ts b/packages/shared-ux/modal/tabbed/src/storybook/setup.ts index 04f50dc9345bb0..d63612b748f8c8 100644 --- a/packages/shared-ux/modal/tabbed/src/storybook/setup.ts +++ b/packages/shared-ux/modal/tabbed/src/storybook/setup.ts @@ -9,7 +9,7 @@ import type { ComponentProps } from 'react'; import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; -import TabbedModal from '../..'; +import { TabbedModal } from '../..'; type TabbedModalProps = ComponentProps; type TabbedModalServiceArguments = Record; diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx deleted file mode 100644 index abb34c6f31edf9..00000000000000 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx +++ /dev/null @@ -1,182 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EuiText, EuiCheckboxGroup, EuiSpacer, useGeneratedHtmlId } from '@elastic/eui'; -import React, { Fragment } from 'react'; - -import { - StorybookMock as TabbedModalStorybookMock, - type Params as TabbedModalStorybookParams, -} from './storybook/setup'; - -import { TabbedModal } from './tabbed_modal'; -import { IModalTabDeclaration } from './context'; - -export default { - title: 'Modal/Tabbed Modal', - description: 'A controlled modal component that renders tabs', -}; - -const mock = new TabbedModalStorybookMock(); -const argTypes = mock.getArgumentTypes(); - -export const TrivialExample = (params: TabbedModalStorybookParams) => { - return ( - { - return ( - - - -

Click the button to send a message into the void

-
-
- ); - }, - initialState: { - message: 'Hello World!!', - }, - modalActionBtn: { - id: 'wave', - dataTestSubj: '', - formattedMessageId: '', - defaultMessage: 'Say Hi 👋🏾', - handler: ({ state }) => { - alert(state.message); - }, - }, - }, - ]} - selectedTabId="hello" - onClose={() => {}} - /> - ); -}; - -TrivialExample.argTypes = argTypes; - -export const NonTrivialExample = (params: TabbedModalStorybookParams) => { - const checkboxGroupItemId1 = useGeneratedHtmlId({ - prefix: 'checkboxGroupItem', - suffix: 'first', - }); - const checkboxGroupItemId2 = useGeneratedHtmlId({ - prefix: 'checkboxGroupItem', - suffix: 'second', - }); - const checkboxGroupItemId3 = useGeneratedHtmlId({ - prefix: 'checkboxGroupItem', - suffix: 'third', - }); - - const checkboxes = [ - { - id: checkboxGroupItemId1, - label: 'Margherita', - 'data-test-sub': 'dts_test', - }, - { - id: checkboxGroupItemId2, - label: 'Diavola', - className: 'classNameTest', - }, - { - id: checkboxGroupItemId3, - label: 'Hawaiian Pizza', - disabled: true, - }, - ]; - - enum ACTION_TYPES { - SelectOption, - } - - const pizzaSelector: IModalTabDeclaration<{ - checkboxIdToSelectedMap: Record; - }> = { - id: 'order', - name: 'order', - initialState: { - checkboxIdToSelectedMap: { - [checkboxGroupItemId2]: true, - }, - }, - reducer(state, action) { - switch (action.type) { - case String(ACTION_TYPES.SelectOption): - return { - ...state, - checkboxIdToSelectedMap: action.payload, - }; - default: - return state; - } - }, - content: ({ state, dispatch }) => { - const { checkboxIdToSelectedMap } = state; - - const onChange = (optionId) => { - const newCheckboxIdToSelectedMap = { - ...checkboxIdToSelectedMap, - ...{ - [optionId]: !checkboxIdToSelectedMap[optionId], - }, - }; - - dispatch({ - type: String(ACTION_TYPES.SelectOption), - payload: newCheckboxIdToSelectedMap, - }); - }; - - return ( - - - -

Select a Pizza (or more)

-
- - onChange(id)} - /> -
- ); - }, - modalActionBtn: { - id: 'pizza', - dataTestSubj: '', - formattedMessageId: '', - defaultMessage: 'Order 🍕', - handler: ({ state }) => { - alert(JSON.stringify(state)); - }, - }, - }; - - // TODO: fix type mismatch - return ( - {}} - modalTitle="Non trivial example" - tabs={[pizzaSelector]} - selectedTabId="order" - /> - ); -}; - -NonTrivialExample.argTypes = argTypes; diff --git a/packages/shared-ux/modal/types/index.d.ts b/packages/shared-ux/modal/types/index.d.ts index 427989f9e90c42..b6d5a930ab18ed 100644 --- a/packages/shared-ux/modal/types/index.d.ts +++ b/packages/shared-ux/modal/types/index.d.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { ModalProps } from '@kbn/share-modal'; import { ReactElement } from 'react'; /** diff --git a/src/plugins/share/public/components/tabs/embed/index.tsx b/src/plugins/share/public/components/tabs/embed/index.tsx index 9c4ea9c2acf48a..ca95c1f1718d11 100644 --- a/src/plugins/share/public/components/tabs/embed/index.tsx +++ b/src/plugins/share/public/components/tabs/embed/index.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React, { useCallback } from 'react'; import { copyToClipboard } from '@elastic/eui'; -import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; +import { type ITabDeclaration } from '@kbn/shared-ux-tabbed-modal'; import { EmbedContent } from './embed_content'; import { useShareTabsContext } from '../../context'; @@ -17,7 +17,7 @@ const EMBED_TAB_ACTIONS = { SET_EMBED_URL: 'SET_EMBED_URL', }; -type IEmbedTab = IModalTabDeclaration<{ url: string }>; +type IEmbedTab = ITabDeclaration<{ url: string }>; const embedTabReducer: IEmbedTab['reducer'] = (state = { url: '' }, action) => { switch (action.type) { From a50db5c04720e1ba294211065b0094dd03668a53 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 14:40:26 -0600 Subject: [PATCH 59/69] delete files not related to this pr --- .../modal/tabbed/src/storybook/setup.ts | 52 ------------------- packages/shared-ux/modal/types/index.d.ts | 22 -------- packages/shared-ux/modal/types/tsconfig.json | 20 ------- 3 files changed, 94 deletions(-) delete mode 100644 packages/shared-ux/modal/tabbed/src/storybook/setup.ts delete mode 100644 packages/shared-ux/modal/types/index.d.ts delete mode 100644 packages/shared-ux/modal/types/tsconfig.json diff --git a/packages/shared-ux/modal/tabbed/src/storybook/setup.ts b/packages/shared-ux/modal/tabbed/src/storybook/setup.ts deleted file mode 100644 index d63612b748f8c8..00000000000000 --- a/packages/shared-ux/modal/tabbed/src/storybook/setup.ts +++ /dev/null @@ -1,52 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { ComponentProps } from 'react'; -import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; - -import { TabbedModal } from '../..'; - -type TabbedModalProps = ComponentProps; -type TabbedModalServiceArguments = Record; - -type Arguments = TabbedModalProps & TabbedModalServiceArguments; - -/** - * Storybook parameters provided from the controls addon. - */ -export type Params = Record; - -export class StorybookMock extends AbstractStorybookMock< - TabbedModalProps, - TabbedModalServiceArguments, - TabbedModalProps, - TabbedModalServiceArguments -> { - propArguments = { - tabs: { - control: { - type: 'array', - }, - defaultValue: [], - }, - }; - - serviceArguments = {}; - - dependencies = []; - - getProps(params?: Params): TabbedModalProps { - return { - tabs: this.getArgumentValue('tabs', params), - }; - } - - getServices(params: Params): TabbedModalServiceArguments { - return {}; - } -} diff --git a/packages/shared-ux/modal/types/index.d.ts b/packages/shared-ux/modal/types/index.d.ts deleted file mode 100644 index b6d5a930ab18ed..00000000000000 --- a/packages/shared-ux/modal/types/index.d.ts +++ /dev/null @@ -1,22 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ReactElement } from 'react'; - -/** - * Props for the `ShareModal` pure component. - */ -export type ShareModalComponentProps = Partial< - Pick -> & { - objectType: string; - modalBodyDescription: string; - tabs: Array<{ id: string; name: string; content: ReactElement }>; -}; - -export type ShareModalProps = ShareModalComponentProps; diff --git a/packages/shared-ux/modal/types/tsconfig.json b/packages/shared-ux/modal/types/tsconfig.json deleted file mode 100644 index c9bef4516921f2..00000000000000 --- a/packages/shared-ux/modal/types/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node", - "react" - ] - }, - "include": [ - "**/*.ts", - "**/*.tsx", - ], - "exclude": [ - "target/**/*", - ], - "kbn_references": [ - ] -} From 8ebf6af4b4e3839d3db93c4ee735c21147ec30eb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:48:15 +0000 Subject: [PATCH 60/69] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/shared-ux/modal/mocks/tsconfig.json | 1 - packages/shared-ux/modal/tabbed/tsconfig.json | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/shared-ux/modal/mocks/tsconfig.json b/packages/shared-ux/modal/mocks/tsconfig.json index 699a2149dd8a30..c9bef4516921f2 100644 --- a/packages/shared-ux/modal/mocks/tsconfig.json +++ b/packages/shared-ux/modal/mocks/tsconfig.json @@ -16,6 +16,5 @@ "target/**/*", ], "kbn_references": [ - "@kbn/shared-ux-storybook-mock", ] } diff --git a/packages/shared-ux/modal/tabbed/tsconfig.json b/packages/shared-ux/modal/tabbed/tsconfig.json index 699a2149dd8a30..c9bef4516921f2 100644 --- a/packages/shared-ux/modal/tabbed/tsconfig.json +++ b/packages/shared-ux/modal/tabbed/tsconfig.json @@ -16,6 +16,5 @@ "target/**/*", ], "kbn_references": [ - "@kbn/shared-ux-storybook-mock", ] } From f08d49409cd6767257e60cff20e684b04f9f171b Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 19:19:14 -0600 Subject: [PATCH 61/69] remove download csv from lens to avoid bundle size and put in share --- .../register_pdf_png_reporting.tsx | 3 +- src/plugins/share/kibana.jsonc | 3 +- src/plugins/share/public/index.ts | 2 +- .../share/public/lib/download_csv_lens.ts | 71 +++++++++++++++++++ src/plugins/share/tsconfig.json | 4 ++ .../plugins/lens/public/app_plugin/index.ts | 1 - x-pack/plugins/lens/public/index.ts | 1 - 7 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/plugins/share/public/lib/download_csv_lens.ts diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index f595da43e9f41d..14495b78d5e7e8 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -8,9 +8,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; +import { ShareContext, ShareMenuProvider, downloadCSVs } from '@kbn/share-plugin/public'; import { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; -import { downloadCSVs } from '@kbn/lens-plugin/public'; import { checkLicense } from '../../license_check'; import { ExportModalShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.'; import { ReportingModalContent } from './reporting_panel_content_lazy'; diff --git a/src/plugins/share/kibana.jsonc b/src/plugins/share/kibana.jsonc index a705a73709730c..b50ddd5ccf466c 100644 --- a/src/plugins/share/kibana.jsonc +++ b/src/plugins/share/kibana.jsonc @@ -8,7 +8,8 @@ "server": true, "browser": true, "requiredBundles": [ - "kibanaUtils" + "kibanaUtils", + "data" ] } } diff --git a/src/plugins/share/public/index.ts b/src/plugins/share/public/index.ts index f212081133b2da..88702a1b9fc6e4 100644 --- a/src/plugins/share/public/index.ts +++ b/src/plugins/share/public/index.ts @@ -32,7 +32,7 @@ export type { RedirectOptions } from '../common/url_service'; export { useLocatorUrl } from '../common/url_service/locators/use_locator_url'; import { SharePlugin } from './plugin'; - +export { downloadCSVs } from './lib/download_csv_lens'; export { downloadMultipleAs, downloadFileAs } from './lib/download_as'; export type { DownloadableContent } from './lib/download_as'; diff --git a/src/plugins/share/public/lib/download_csv_lens.ts b/src/plugins/share/public/lib/download_csv_lens.ts new file mode 100644 index 00000000000000..390bf74389cf85 --- /dev/null +++ b/src/plugins/share/public/lib/download_csv_lens.ts @@ -0,0 +1,71 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { exporters } from '@kbn/data-plugin/public'; +import type { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; +import type { FormatFactory } from '@kbn/field-formats-plugin/common'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { downloadMultipleAs } from './download_as'; + +declare global { + interface Window { + /** + * Debug setting to test CSV download + */ + ELASTIC_LENS_CSV_DOWNLOAD_DEBUG?: boolean; + ELASTIC_LENS_CSV_CONTENT?: Record; + } +} + +export async function downloadCSVs({ + activeData, + title, + formatFactory, + uiSettings, + columnsSorting, +}: { + title: string; + activeData: TableInspectorAdapter; + formatFactory: FormatFactory; + uiSettings: IUiSettingsClient; + columnsSorting?: string[]; +}) { + if (!activeData) { + if (window.ELASTIC_LENS_CSV_DOWNLOAD_DEBUG) { + window.ELASTIC_LENS_CSV_CONTENT = undefined; + } + return; + } + const datatables = Object.values(activeData); + const content = datatables.reduce>( + (memo, datatable, i) => { + // skip empty datatables + if (datatable) { + const postFix = datatables.length > 1 ? `-${i + 1}` : ''; + + memo[`${title}${postFix}.csv`] = { + content: exporters.datatableToCSV(datatable, { + csvSeparator: uiSettings.get('csv:separator', ','), + quoteValues: uiSettings.get('csv:quoteValues', true), + formatFactory, + escapeFormulaValues: false, + columnsSorting, + }), + type: exporters.CSV_MIME_TYPE, + }; + } + return memo; + }, + {} + ); + if (window.ELASTIC_LENS_CSV_DOWNLOAD_DEBUG) { + window.ELASTIC_LENS_CSV_CONTENT = content; + } + if (content) { + downloadMultipleAs(content); + } +} diff --git a/src/plugins/share/tsconfig.json b/src/plugins/share/tsconfig.json index 49a25e75fbef05..47e875216de4bd 100644 --- a/src/plugins/share/tsconfig.json +++ b/src/plugins/share/tsconfig.json @@ -20,6 +20,10 @@ "@kbn/shared-ux-prompt-not-found", "@kbn/react-kibana-mount", "@kbn/shared-ux-tabbed-modal", + "@kbn/data-plugin", + "@kbn/lens-plugin", + "@kbn/field-formats-plugin", + "@kbn/core-ui-settings-browser", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/lens/public/app_plugin/index.ts b/x-pack/plugins/lens/public/app_plugin/index.ts index a3f9b0c962b711..250e1299fd1289 100644 --- a/x-pack/plugins/lens/public/app_plugin/index.ts +++ b/x-pack/plugins/lens/public/app_plugin/index.ts @@ -6,4 +6,3 @@ */ export * from './app'; -export { downloadCSVs } from './csv_download_provider'; diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 06e244664f8e87..0088e434cb72d9 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -121,7 +121,6 @@ export type { ChartInfo } from './chart_info_api'; export { layerTypes } from '../common/layer_types'; export { LENS_EMBEDDABLE_TYPE } from '../common/constants'; -export { downloadCSVs } from './app_plugin'; export type { LensPublicStart, LensPublicSetup, LensSuggestionsApi } from './plugin'; From de84c0a80aa96487a0bdef601f7fc430cce36436 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 28 Mar 2024 19:28:16 -0600 Subject: [PATCH 62/69] cleanup --- src/plugins/share/public/components/context/index.tsx | 3 ++- src/plugins/share/public/components/share_context_menu.tsx | 2 +- src/plugins/share/public/services/share_menu_manager.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx index 5ec9f8d1734abf..0fb12b050b5bde 100644 --- a/src/plugins/share/public/components/context/index.tsx +++ b/src/plugins/share/public/components/context/index.tsx @@ -19,7 +19,8 @@ import type { export interface IShareContext extends ShareContext { allowEmbed: boolean; allowShortUrl: boolean; - shareMenuItems: ShareMenuItem[]; + // lens can return null when the licensing is less than gold + shareMenuItems: Array; embedUrlParamExtensions?: UrlParamExtension[]; anonymousAccess?: AnonymousAccessServiceContract; urlService: BrowserUrlService; diff --git a/src/plugins/share/public/components/share_context_menu.tsx b/src/plugins/share/public/components/share_context_menu.tsx index 0e73437914bc92..feb2ae00e52fd4 100644 --- a/src/plugins/share/public/components/share_context_menu.tsx +++ b/src/plugins/share/public/components/share_context_menu.tsx @@ -31,7 +31,7 @@ export interface ShareContextMenuProps { locator: LocatorPublic; params: any; }; - shareMenuItems: ShareMenuItem[]; + shareMenuItems: Array; sharingData: any; onClose: () => void; embedUrlParamExtensions?: UrlParamExtension[]; diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 7c3d7554c0ba20..ecdddcfef3b74a 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -94,7 +94,7 @@ export class ShareMenuManager { newVersionEnabled, }: ShowShareMenuOptions & { anchorElement: HTMLElement; - menuItems: ShareMenuItem[]; + menuItems: Array; urlService: BrowserUrlService; anonymousAccess: AnonymousAccessServiceContract | undefined; theme: ThemeServiceStart; From 0f4403a4665db1408668df80f761a5a1f83d3080 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Fri, 29 Mar 2024 10:50:27 -0600 Subject: [PATCH 63/69] put download csv back to avoid circ dependency with the data plugin --- .../register_pdf_png_reporting.tsx | 3 +- src/plugins/share/kibana.jsonc | 1 - src/plugins/share/public/index.ts | 1 - .../share/public/lib/download_csv_lens.ts | 71 ------------------- src/plugins/share/tsconfig.json | 4 -- x-pack/plugins/lens/public/index.ts | 1 + 6 files changed, 3 insertions(+), 78 deletions(-) delete mode 100644 src/plugins/share/public/lib/download_csv_lens.ts diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 14495b78d5e7e8..4b9d5afcd4b9c5 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -8,7 +8,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { ShareContext, ShareMenuProvider, downloadCSVs } from '@kbn/share-plugin/public'; +import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; +import { downloadCSVs } from '@kbn/lens-plugin/public'; import { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; import { checkLicense } from '../../license_check'; import { ExportModalShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.'; diff --git a/src/plugins/share/kibana.jsonc b/src/plugins/share/kibana.jsonc index b50ddd5ccf466c..083b3f3f03238f 100644 --- a/src/plugins/share/kibana.jsonc +++ b/src/plugins/share/kibana.jsonc @@ -9,7 +9,6 @@ "browser": true, "requiredBundles": [ "kibanaUtils", - "data" ] } } diff --git a/src/plugins/share/public/index.ts b/src/plugins/share/public/index.ts index 88702a1b9fc6e4..160cd6f6d7ca8e 100644 --- a/src/plugins/share/public/index.ts +++ b/src/plugins/share/public/index.ts @@ -32,7 +32,6 @@ export type { RedirectOptions } from '../common/url_service'; export { useLocatorUrl } from '../common/url_service/locators/use_locator_url'; import { SharePlugin } from './plugin'; -export { downloadCSVs } from './lib/download_csv_lens'; export { downloadMultipleAs, downloadFileAs } from './lib/download_as'; export type { DownloadableContent } from './lib/download_as'; diff --git a/src/plugins/share/public/lib/download_csv_lens.ts b/src/plugins/share/public/lib/download_csv_lens.ts deleted file mode 100644 index 390bf74389cf85..00000000000000 --- a/src/plugins/share/public/lib/download_csv_lens.ts +++ /dev/null @@ -1,71 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import { exporters } from '@kbn/data-plugin/public'; -import type { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; -import type { FormatFactory } from '@kbn/field-formats-plugin/common'; -import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import { downloadMultipleAs } from './download_as'; - -declare global { - interface Window { - /** - * Debug setting to test CSV download - */ - ELASTIC_LENS_CSV_DOWNLOAD_DEBUG?: boolean; - ELASTIC_LENS_CSV_CONTENT?: Record; - } -} - -export async function downloadCSVs({ - activeData, - title, - formatFactory, - uiSettings, - columnsSorting, -}: { - title: string; - activeData: TableInspectorAdapter; - formatFactory: FormatFactory; - uiSettings: IUiSettingsClient; - columnsSorting?: string[]; -}) { - if (!activeData) { - if (window.ELASTIC_LENS_CSV_DOWNLOAD_DEBUG) { - window.ELASTIC_LENS_CSV_CONTENT = undefined; - } - return; - } - const datatables = Object.values(activeData); - const content = datatables.reduce>( - (memo, datatable, i) => { - // skip empty datatables - if (datatable) { - const postFix = datatables.length > 1 ? `-${i + 1}` : ''; - - memo[`${title}${postFix}.csv`] = { - content: exporters.datatableToCSV(datatable, { - csvSeparator: uiSettings.get('csv:separator', ','), - quoteValues: uiSettings.get('csv:quoteValues', true), - formatFactory, - escapeFormulaValues: false, - columnsSorting, - }), - type: exporters.CSV_MIME_TYPE, - }; - } - return memo; - }, - {} - ); - if (window.ELASTIC_LENS_CSV_DOWNLOAD_DEBUG) { - window.ELASTIC_LENS_CSV_CONTENT = content; - } - if (content) { - downloadMultipleAs(content); - } -} diff --git a/src/plugins/share/tsconfig.json b/src/plugins/share/tsconfig.json index 47e875216de4bd..49a25e75fbef05 100644 --- a/src/plugins/share/tsconfig.json +++ b/src/plugins/share/tsconfig.json @@ -20,10 +20,6 @@ "@kbn/shared-ux-prompt-not-found", "@kbn/react-kibana-mount", "@kbn/shared-ux-tabbed-modal", - "@kbn/data-plugin", - "@kbn/lens-plugin", - "@kbn/field-formats-plugin", - "@kbn/core-ui-settings-browser", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 0088e434cb72d9..b188a973ca2aa1 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -121,6 +121,7 @@ export type { ChartInfo } from './chart_info_api'; export { layerTypes } from '../common/layer_types'; export { LENS_EMBEDDABLE_TYPE } from '../common/constants'; +export { downloadCSVs } from './app_plugin/csv_download_provider/csv_download_provider'; export type { LensPublicStart, LensPublicSetup, LensSuggestionsApi } from './plugin'; From 2d0e9c88caacf0efd802631478ce6347999e4c7d Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 2 Apr 2024 08:21:41 -0600 Subject: [PATCH 64/69] refactor new register method for new share redesign --- packages/kbn-reporting/public/share/index.ts | 5 +- .../share_context_menu/image_export_modal.tsx | 25 +-- .../public/share/share_context_menu/index.ts | 12 +- .../register_pdf_png_reporting.tsx | 211 +++++++++++++++++- .../reporting_modal_content_lazy.tsx | 26 +++ .../reporting_panel_content_lazy.tsx | 12 +- .../screen_capture_panel_content.tsx | 87 ++++++-- .../share/shared/get_shared_components.tsx | 7 +- .../share/public/components/share_tabs.tsx | 5 +- .../components/tabs/link/link_content.tsx | 2 +- src/plugins/share/public/plugin.ts | 10 + .../public/services/share_menu_registry.ts | 12 + x-pack/plugins/lens/public/plugin.ts | 17 +- x-pack/plugins/reporting/public/plugin.ts | 45 ++-- 14 files changed, 396 insertions(+), 80 deletions(-) create mode 100644 packages/kbn-reporting/public/share/share_context_menu/reporting_modal_content_lazy.tsx diff --git a/packages/kbn-reporting/public/share/index.ts b/packages/kbn-reporting/public/share/index.ts index 37bc87b31939d3..d55575b1102cce 100644 --- a/packages/kbn-reporting/public/share/index.ts +++ b/packages/kbn-reporting/public/share/index.ts @@ -7,7 +7,10 @@ */ export { getSharedComponents } from './shared'; -export { reportingScreenshotShareProvider } from './share_context_menu/register_pdf_png_reporting'; +export { + reportingScreenshotShareProvider, + reportingExportModalProvider, +} from './share_context_menu/register_pdf_png_reporting'; export { reportingCsvShareProvider } from './share_context_menu/register_csv_reporting'; export type { ReportingPublicComponents } from './shared/get_shared_components'; export type { JobParamsProviderOptions } from './share_context_menu'; diff --git a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx index 765fea092f6466..ee844dd685e743 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/image_export_modal.tsx @@ -26,7 +26,6 @@ import url from 'url'; import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; import { LayoutParams } from '@kbn/screenshotting-plugin/common'; -import type { JobAppParamsPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { BaseParams } from '@kbn/reporting-common/types'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { ReportingAPIClient } from '../..'; @@ -45,12 +44,9 @@ export interface ReportingModalProps { onClose: () => void; theme: ThemeServiceSetup; layoutOption?: 'print' | 'canvas'; - // canvas breaks if required jobProviderOptions?: JobParamsProviderOptions; - // needed for canvas - getJobParams?: JobAppParamsPDFV2; objectType: string; - downloadCsvFromLens: () => void; + downloadCsvFromLens?: () => void; } type AppParams = Omit; @@ -85,7 +81,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { const getJobsParams = useCallback( (type: AllowedImageExportType, opts?: JobParamsProviderOptions) => { if (!opts) { - return { ...props.getJobParams }; + return; } const { @@ -121,25 +117,20 @@ export const ReportingModalContentUI: FC = (props: Props) => { // single URL for PNG return { ...baseParams, relativeUrl }; }, - [apiClient, objectType, props.getJobParams] + [apiClient, objectType] ); const getLayout = useCallback((): LayoutParams => { - const { layout } = getJobsParams(selectedRadio, jobProviderOptions); + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + const dimensions = { height, width }; - let dimensions = layout?.dimensions; - - if (!dimensions) { - const el = document.querySelector('[data-shared-items-container]'); - const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; - dimensions = { height, width }; - } if (usePrintLayout) { return { id: 'print', dimensions }; } return { id: 'preserve_layout', dimensions }; - }, [getJobsParams, jobProviderOptions, selectedRadio, usePrintLayout]); + }, [usePrintLayout]); const getJobParams = useCallback( (shareableUrl?: boolean) => { @@ -172,7 +163,7 @@ export const ReportingModalContentUI: FC = (props: Props) => { }, [markAsStale, getAbsoluteReportGenerationUrl]); const generateReportingJob = () => { - if (selectedRadio === 'csv') { + if (selectedRadio === 'csv' && downloadCsvFromLens) { return downloadCsvFromLens(); } const decoratedJobParams = apiClient.getDecoratedJobParams( diff --git a/packages/kbn-reporting/public/share/share_context_menu/index.ts b/packages/kbn-reporting/public/share/share_context_menu/index.ts index 468dcf9c352c54..351d50b29c86ef 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/index.ts +++ b/packages/kbn-reporting/public/share/share_context_menu/index.ts @@ -17,7 +17,7 @@ import { ILicense } from '@kbn/licensing-plugin/public'; import type { LayoutParams } from '@kbn/screenshotting-plugin/common'; import type { ReportingAPIClient } from '../../reporting_api_client'; -export interface ExportPanelShareOpts { +export interface ExportModalShareOpts { apiClient: ReportingAPIClient; toasts: ToastsSetup; uiSettings: IUiSettingsClient; @@ -29,7 +29,15 @@ export interface ExportPanelShareOpts { formatFactoryFn: () => FormatFactory; } -export type ExportModalShareOpts = ExportPanelShareOpts; +export interface ExportPanelShareOpts { + apiClient: ReportingAPIClient; + toasts: ToastsSetup; + uiSettings: IUiSettingsClient; + usesUiCapabilities: boolean; + license: ILicense; + application: ApplicationStart; + theme: ThemeServiceSetup; +} export interface ReportingSharingData { title: string; diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 4b9d5afcd4b9c5..c3b25967de71f9 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -12,13 +12,218 @@ import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; import { downloadCSVs } from '@kbn/lens-plugin/public'; import { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; import { checkLicense } from '../../license_check'; -import { ExportModalShareOpts, JobParamsProviderOptions, ReportingSharingData } from '.'; -import { ReportingModalContent } from './reporting_panel_content_lazy'; +import { + ExportModalShareOpts, + ExportPanelShareOpts, + JobParamsProviderOptions, + ReportingSharingData, +} from '.'; +import { ReportingModalContent } from './reporting_modal_content_lazy'; +import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy'; +import { ReportingAPIClient } from '../../reporting_api_client'; + +const getJobParams = + ( + apiClient: ReportingAPIClient, + opts: JobParamsProviderOptions, + type: 'png' | 'pngV2' | 'printablePdf' | 'printablePdfV2' + ) => + () => { + const { + objectType, + sharingData: { title, layout, locatorParams }, + } = opts; + + const baseParams = { + objectType, + layout, + title, + }; + + if (type === 'printablePdfV2') { + // multi locator for PDF V2 + return { ...baseParams, locatorParams: [locatorParams] }; + } else if (type === 'pngV2') { + // single locator for PNG V2 + return { ...baseParams, locatorParams }; + } + + // Relative URL must have URL prefix (Spaces ID prefix), but not server basePath + // Replace hashes with original RISON values. + const relativeUrl = opts.shareableUrl.replace( + window.location.origin + apiClient.getServerBasePath(), + '' + ); + + if (type === 'printablePdf') { + // multi URL for PDF + return { ...baseParams, relativeUrls: [relativeUrl] }; + } + + // single URL for PNG + return { ...baseParams, relativeUrl }; + }; + +export const reportingScreenshotShareProvider = ({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme, +}: ExportPanelShareOpts): ShareMenuProvider => { + const getShareMenuItems = ({ + objectType, + objectId, + isDirty, + onClose, + shareableUrl, + shareableUrlForSavedObject, + ...shareOpts + }: ShareContext) => { + const { enableLinks, showLinks, message } = checkLicense(license.check('reporting', 'gold')); + const licenseToolTipContent = message; + const licenseHasScreenshotReporting = showLinks; + const licenseDisabled = !enableLinks; + + let capabilityHasDashboardScreenshotReporting = false; + let capabilityHasVisualizeScreenshotReporting = false; + if (usesUiCapabilities) { + capabilityHasDashboardScreenshotReporting = + application.capabilities.dashboard?.generateScreenshot === true; + capabilityHasVisualizeScreenshotReporting = + application.capabilities.visualize?.generateScreenshot === true; + } else { + // deprecated + capabilityHasDashboardScreenshotReporting = true; + capabilityHasVisualizeScreenshotReporting = true; + } + + if (!licenseHasScreenshotReporting) { + return []; + } + const isSupportedType = ['dashboard', 'visualization', 'lens'].includes(objectType); + + if (!isSupportedType) { + return []; + } + + if (objectType === 'dashboard' && !capabilityHasDashboardScreenshotReporting) { + return []; + } + + if ( + isSupportedType && + !capabilityHasVisualizeScreenshotReporting && + !capabilityHasDashboardScreenshotReporting + ) { + return []; + } + + const { sharingData } = shareOpts as unknown as { sharingData: ReportingSharingData }; + const shareActions = []; + + const pngPanelTitle = i18n.translate('reporting.share.contextMenu.pngReportsButtonLabel', { + defaultMessage: 'PNG Reports', + }); + + const jobProviderOptions: JobParamsProviderOptions = { + shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, + objectType, + sharingData, + }; + const isJobV2Params = ({ + sharingData: _sharingData, + }: { + sharingData: Record; + }) => _sharingData.locatorParams != null; + + const isV2Job = isJobV2Params(jobProviderOptions); + const requiresSavedState = !isV2Job; + + const pngReportType = isV2Job ? 'pngV2' : 'png'; + + const panelPng = { + shareMenuItem: { + name: pngPanelTitle, + icon: 'document', + toolTipContent: licenseToolTipContent, + disabled: licenseDisabled || sharingData.reportingDisabled, + ['data-test-subj']: 'PNGReports', + sortOrder: 10, + }, + panel: { + id: 'reportingPngPanel', + title: pngPanelTitle, + content: ( + + ), + }, + }; + + const pdfPanelTitle = i18n.translate('reporting.share.contextMenu.pdfReportsButtonLabel', { + defaultMessage: 'PDF Reports', + }); + + const pdfReportType = isV2Job ? 'printablePdfV2' : 'printablePdf'; + + const panelPdf = { + shareMenuItem: { + name: pdfPanelTitle, + icon: 'document', + toolTipContent: licenseToolTipContent, + disabled: licenseDisabled || sharingData.reportingDisabled, + ['data-test-subj']: 'PDFReports', + sortOrder: 10, + }, + panel: { + id: 'reportingPdfPanel', + title: pdfPanelTitle, + content: ( + + ), + }, + }; + + shareActions.push(panelPng); + shareActions.push(panelPdf); + return shareActions; + }; + + return { + id: 'screenCaptureReports', + getShareMenuItems, + }; +}; export const isJobV2Params = ({ sharingData }: { sharingData: Record }): boolean => sharingData.locatorParams != null; -export const reportingScreenshotShareProvider = ({ +export const reportingExportModalProvider = ({ apiClient, toasts, uiSettings, diff --git a/packages/kbn-reporting/public/share/share_context_menu/reporting_modal_content_lazy.tsx b/packages/kbn-reporting/public/share/share_context_menu/reporting_modal_content_lazy.tsx new file mode 100644 index 00000000000000..447975042ec5b7 --- /dev/null +++ b/packages/kbn-reporting/public/share/share_context_menu/reporting_modal_content_lazy.tsx @@ -0,0 +1,26 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as React from 'react'; +import { FC, lazy, Suspense } from 'react'; +import { PanelSpinner } from './panel_spinner'; +import type { ReportingModalProps } from './image_export_modal'; + +const LazyModalComponent = lazy(() => + import('./image_export_modal').then(({ ReportingModalContent }) => ({ + default: ReportingModalContent, + })) +); + +export const ReportingModalContent: FC = (props) => { + return ( + }> + + + ); +}; diff --git a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx index 447975042ec5b7..af4a0fe1a64b22 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content_lazy.tsx @@ -9,18 +9,18 @@ import * as React from 'react'; import { FC, lazy, Suspense } from 'react'; import { PanelSpinner } from './panel_spinner'; -import type { ReportingModalProps } from './image_export_modal'; +import type { Props } from './reporting_panel_content'; -const LazyModalComponent = lazy(() => - import('./image_export_modal').then(({ ReportingModalContent }) => ({ - default: ReportingModalContent, +const LazyComponent = lazy(() => + import('./reporting_panel_content').then(({ ReportingPanelContent }) => ({ + default: ReportingPanelContent, })) ); -export const ReportingModalContent: FC = (props) => { +export const ReportingPanelContent: FC> = (props) => { return ( }> - + ); }; diff --git a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx index e9242a23972636..cacdae135b4ef3 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.tsx @@ -12,9 +12,12 @@ import React, { Component } from 'react'; import type { LayoutParams } from '@kbn/screenshotting-plugin/common'; import { ReportingPanelContent, ReportingPanelProps } from './reporting_panel_content'; -export type Props = ReportingPanelProps; +export interface Props extends ReportingPanelProps { + layoutOption?: 'canvas' | 'print'; +} interface State { + usePrintLayout: boolean; useCanvasLayout: boolean; } @@ -23,6 +26,7 @@ export class ScreenCapturePanelContent extends Component { super(props); this.state = { + usePrintLayout: false, useCanvasLayout: false, }; } @@ -39,32 +43,65 @@ export class ScreenCapturePanelContent extends Component { } private renderOptions = () => { - return ( - + } + > + + } + checked={this.state.usePrintLayout} + onChange={this.handlePrintLayoutChange} + data-test-subj="usePrintLayout" /> - } - > - + ); + } + + if (this.props.layoutOption === 'canvas') { + return ( + } - checked={this.state.useCanvasLayout} - onChange={this.handleCanvasLayoutChange} - data-test-subj="reportModeToggle" - /> - - ); + > + + } + checked={this.state.useCanvasLayout} + onChange={this.handleCanvasLayoutChange} + data-test-subj="reportModeToggle" + /> + + ); + } + + return null; + }; + + private handlePrintLayoutChange = (evt: EuiSwitchEvent) => { + this.setState({ usePrintLayout: evt.target.checked, useCanvasLayout: false }); }; private handleCanvasLayoutChange = (evt: EuiSwitchEvent) => { - this.setState({ useCanvasLayout: evt.target.checked }); + this.setState({ useCanvasLayout: evt.target.checked, usePrintLayout: false }); }; private getLayout = (): LayoutParams => { @@ -77,7 +114,15 @@ export class ScreenCapturePanelContent extends Component { dimensions = { height, width }; } - return { id: 'canvas', dimensions }; + if (this.state.usePrintLayout) { + return { id: 'print', dimensions }; + } + + if (this.state.useCanvasLayout) { + return { id: 'canvas', dimensions }; + } + + return { id: 'preserve_layout', dimensions }; }; private getJobParams = () => { diff --git a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx index 23d22de5e0bdb5..83841fb0ef867d 100644 --- a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx +++ b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx @@ -14,7 +14,7 @@ import React from 'react'; import { ReportingAPIClient } from '../..'; import { CsvModalContent } from '../share_context_menu/csv_export_modal'; import { ReportingPanelProps } from '../share_context_menu/reporting_panel_content'; -import { ReportingModalContent } from '../share_context_menu/reporting_panel_content_lazy'; +import { ReportingModalContent } from '../share_context_menu/reporting_modal_content_lazy'; import { ScreenCapturePanelContent } from '../share_context_menu/screen_capture_panel_content_lazy'; /** * Properties for displaying a share menu with Reporting features. @@ -41,7 +41,7 @@ export interface ApplicationProps { */ onClose: () => void; objectType: string; - downloadCsvFromLens: () => void; + downloadCsvFromLens?: () => void; } export interface ReportingPublicComponents { @@ -92,7 +92,6 @@ export function getSharedComponents( } }, ReportingModalPDFV2(props: ApplicationProps) { - const getJobParams = props.getJobParams as JobAppParamsPDFV2; return ( ); }, @@ -114,7 +112,6 @@ export function getSharedComponents( uiSettings={core.uiSettings} theme={core.theme} {...props} - getJobParams={undefined} /> ); }, diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 554c90930dde9b..61355e14b3ae15 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -30,7 +30,7 @@ export const ShareMenuTabs = () => { const { allowEmbed, objectType, onClose, shareMenuItems } = shareContext; - const tabs = [linkTab, allowEmbed ? embedTab : null].filter(Boolean); + const tabs = [linkTab]; if (shareMenuItems) { shareMenuItems // need to filter out the null shareMenuItem from Lens and just use the reporting image modal that includes CSV for Lens @@ -44,6 +44,9 @@ export const ShareMenuTabs = () => { }); }); } + if (allowEmbed) { + tabs.push(embedTab); + } return ( (''); const [urlParams] = useState(undefined); - const [shortUrlCache, setShortUrlCache] = useState(undefined); + const [, setShortUrlCache] = useState(undefined); useEffect(() => { // propagate url updates upwards to tab diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 79f70783873e60..2d824d0945daea 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -31,6 +31,10 @@ export type SharePublicSetup = ShareMenuRegistrySetup & { * Utilities to work with URL locators and short URLs. */ url: BrowserUrlService; + + /** + * this plugin exposes the kibana version for other plugins needing to pass a Reporting API client + */ kibanaVersion: string; /** @@ -43,6 +47,11 @@ export type SharePublicSetup = ShareMenuRegistrySetup & { * Sets the provider for the anonymous access service; this is consumed by the Security plugin to avoid a circular dependency. */ setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessServiceContract) => void; + /** + * Allows for canvas to register the older versioned way whereas reporting for Discover/Lens/Dashboard + * can use the new share version and show the share context modals + */ + isNewVersion: boolean; }; /** @public */ @@ -139,6 +148,7 @@ export class SharePlugin } this.anonymousAccessServiceProvider = provider; }, + isNewVersion: this.config.new_version.enabled, }; } diff --git a/src/plugins/share/public/services/share_menu_registry.ts b/src/plugins/share/public/services/share_menu_registry.ts index 2f4d2e7527fda3..ceddf61f8c3ba1 100644 --- a/src/plugins/share/public/services/share_menu_registry.ts +++ b/src/plugins/share/public/services/share_menu_registry.ts @@ -29,6 +29,18 @@ export class ShareMenuRegistry { this.shareMenuProviders.set(shareMenuProvider.id, shareMenuProvider); }, + registerModalOptions: (newVersion: boolean, shareMenuProvider: ShareMenuProvider) => { + if (newVersion === false) { + throw new Error(`Share menu provider is not the updated version.`); + } + + if (this.shareMenuProviders.has(shareMenuProvider.id)) { + throw new Error( + `Share menu provider with id [${shareMenuProvider.id}] has already been registered. Use a unique id.` + ); + } + this.shareMenuProviders.set(shareMenuProvider.id, shareMenuProvider); + }, }; } diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index ecab881be1e99f..1299922a6885ed 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -397,13 +397,16 @@ export class LensPlugin { const startServices$ = from(getStartServices()); startServices$.subscribe(([, { licensing }]) => { licensing?.license$.subscribe((license) => { - return share.register( - downloadCsvShareProvider({ - uiSettings: core.uiSettings, - formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, - license, - }) - ); + const atLeastGold = license.hasAtLeast('gold'); + if (atLeastGold) { + return share.register( + downloadCsvShareProvider({ + uiSettings: core.uiSettings, + formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, + license, + }) + ); + } }); }); } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index 02a42c06aa47d3..b444fe91847e74 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -33,6 +33,7 @@ import { getSharedComponents, reportingCsvShareProvider, reportingScreenshotShareProvider, + reportingExportModalProvider, } from '@kbn/reporting-public/share'; import { ReportingCsvPanelAction } from '@kbn/reporting-csv-share-panel'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -219,29 +220,41 @@ export class ReportingPublicPlugin application, usesUiCapabilities, theme: core.theme, - version: this.kibanaVersion, - formatFactoryFn: () => fieldFormats.deserialize, }) ); if (this.config.export_types.pdf.enabled || this.config.export_types.png.enabled) { - shareSetup.register( - reportingScreenshotShareProvider({ - apiClient, - toasts, - uiSettings, - license, - application, - usesUiCapabilities, - theme: core.theme, - version: this.kibanaVersion, - formatFactoryFn: () => fieldFormats.deserialize, - }) - ); + if (!shareSetup.isNewVersion) { + shareSetup.register( + reportingScreenshotShareProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + }) + ); + } else { + shareSetup.registerModalOptions( + shareSetup.isNewVersion, + reportingExportModalProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + version: this.kibanaVersion, + formatFactoryFn: () => fieldFormats.deserialize, + }) + ); + } } }); }); - return reportingStart; } From 44b83937377407352a6855edc97c1ddb3e9ccef9 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 2 Apr 2024 11:49:21 -0600 Subject: [PATCH 65/69] refactor the registry --- .../public/services/share_menu_registry.ts | 12 ----- x-pack/plugins/reporting/public/plugin.ts | 53 +++++++++---------- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/plugins/share/public/services/share_menu_registry.ts b/src/plugins/share/public/services/share_menu_registry.ts index ceddf61f8c3ba1..2f4d2e7527fda3 100644 --- a/src/plugins/share/public/services/share_menu_registry.ts +++ b/src/plugins/share/public/services/share_menu_registry.ts @@ -29,18 +29,6 @@ export class ShareMenuRegistry { this.shareMenuProviders.set(shareMenuProvider.id, shareMenuProvider); }, - registerModalOptions: (newVersion: boolean, shareMenuProvider: ShareMenuProvider) => { - if (newVersion === false) { - throw new Error(`Share menu provider is not the updated version.`); - } - - if (this.shareMenuProviders.has(shareMenuProvider.id)) { - throw new Error( - `Share menu provider with id [${shareMenuProvider.id}] has already been registered. Use a unique id.` - ); - } - this.shareMenuProviders.set(shareMenuProvider.id, shareMenuProvider); - }, }; } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index b444fe91847e74..ed3ff31fa2ef8e 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -224,34 +224,31 @@ export class ReportingPublicPlugin ); if (this.config.export_types.pdf.enabled || this.config.export_types.png.enabled) { - if (!shareSetup.isNewVersion) { - shareSetup.register( - reportingScreenshotShareProvider({ - apiClient, - toasts, - uiSettings, - license, - application, - usesUiCapabilities, - theme: core.theme, - }) - ); - } else { - shareSetup.registerModalOptions( - shareSetup.isNewVersion, - reportingExportModalProvider({ - apiClient, - toasts, - uiSettings, - license, - application, - usesUiCapabilities, - theme: core.theme, - version: this.kibanaVersion, - formatFactoryFn: () => fieldFormats.deserialize, - }) - ); - } + shareSetup.register( + reportingScreenshotShareProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + }) + ); + + shareSetup.register( + reportingExportModalProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + version: this.kibanaVersion, + formatFactoryFn: () => fieldFormats.deserialize, + }) + ); } }); }); From a6fab4c57c1af6d0e32e85dfd847647c9433bb30 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 2 Apr 2024 16:05:24 -0600 Subject: [PATCH 66/69] refactor to send to share plugin wip --- .../public/share/share_context_menu/index.ts | 3 - .../register_csv_reporting.tsx | 53 +++---- .../register_pdf_png_reporting.tsx | 62 +++------ .../share/shared/get_report_generate.tsx | 131 ++++++++++++++++++ .../modal/tabbed/src/tabbed_modal.tsx | 28 ++-- .../share/public/components/context/index.tsx | 5 +- .../public/components/share_context_menu.tsx | 2 +- .../share/public/components/share_tabs.tsx | 44 ++++-- .../components/tabs/embed/embed_content.tsx | 7 +- .../components/tabs/export/export_content.tsx | 101 ++++++++++++++ .../share/public/components/tabs/index.ts | 1 + src/plugins/share/public/plugin.ts | 2 +- .../public/services/share_menu_manager.tsx | 3 +- src/plugins/share/public/types.ts | 21 ++- .../csv_download_provider.tsx | 59 +++----- x-pack/plugins/lens/public/plugin.ts | 1 - x-pack/plugins/reporting/public/plugin.ts | 48 +++---- 17 files changed, 393 insertions(+), 178 deletions(-) create mode 100644 packages/kbn-reporting/public/share/shared/get_report_generate.tsx create mode 100644 src/plugins/share/public/components/tabs/export/export_content.tsx diff --git a/packages/kbn-reporting/public/share/share_context_menu/index.ts b/packages/kbn-reporting/public/share/share_context_menu/index.ts index 351d50b29c86ef..134fff4f5e1375 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/index.ts +++ b/packages/kbn-reporting/public/share/share_context_menu/index.ts @@ -12,7 +12,6 @@ import type { ThemeServiceSetup, ToastsSetup, } from '@kbn/core/public'; -import { FormatFactory } from '@kbn/field-formats-plugin/common'; import { ILicense } from '@kbn/licensing-plugin/public'; import type { LayoutParams } from '@kbn/screenshotting-plugin/common'; import type { ReportingAPIClient } from '../../reporting_api_client'; @@ -25,8 +24,6 @@ export interface ExportModalShareOpts { license: ILicense; application: ApplicationStart; theme: ThemeServiceSetup; - version: string; - formatFactoryFn: () => FormatFactory; } export interface ExportPanelShareOpts { diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx index 97dce8928f7e07..82444a2c3a631f 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx @@ -13,19 +13,17 @@ import { CSV_JOB_TYPE, CSV_JOB_TYPE_V2 } from '@kbn/reporting-export-types-csv-c import type { SearchSourceFields } from '@kbn/data-plugin/common'; import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; -import type { ExportPanelShareOpts } from '.'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { ExportModalShareOpts } from '.'; import { checkLicense } from '../..'; -import { CsvModalContent } from './csv_export_modal'; +import { generateReportingJobCSV } from '../shared/get_report_generate'; export const reportingCsvShareProvider = ({ apiClient, - toasts, - uiSettings, application, license, usesUiCapabilities, - theme, -}: ExportPanelShareOpts): ShareMenuProvider => { +}: ExportModalShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, objectId, sharingData, onClose }: ShareContext) => { if ('search' !== objectType) { return []; @@ -75,7 +73,6 @@ export const reportingCsvShareProvider = ({ const licenseHasCsvReporting = licenseCheck.showLinks; const licenseDisabled = !licenseCheck.enableLinks; - // TODO: add abstractions in ExportTypeRegistry to use here? let capabilityHasCsvReporting = false; if (usesUiCapabilities) { capabilityHasCsvReporting = application.capabilities.discover?.generateCsv === true; @@ -93,26 +90,32 @@ export const reportingCsvShareProvider = ({ name: panelTitle, toolTipContent: licenseToolTipContent, disabled: licenseDisabled, - ['data-test-subj']: 'CSVReports', + ['data-test-subj']: 'Export', }, - panel: { - id: 'csvReportingPanel', - title: panelTitle, - content: ( - - ), + tabType: 'Export', + helpText: ( + + ), + reportType: [reportType], + copyURLButton: { + id: 'reporting.share.modalContent.csv.copyUrlButtonLabel', + dataTestSubj: 'shareReportingCopyURL', + label: 'Post URL', }, + generateReportButton: ( + + ), + getJobParams, + createReportingJob: generateReportingJobCSV, + reportingAPIClient: apiClient, }); } diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index c3b25967de71f9..a316db61810526 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -9,8 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; -import { downloadCSVs } from '@kbn/lens-plugin/public'; -import { TableInspectorAdapter } from '@kbn/lens-plugin/public/editor_frame_service/types'; +import { FormattedMessage } from '@kbn/i18n-react'; import { checkLicense } from '../../license_check'; import { ExportModalShareOpts, @@ -18,9 +17,9 @@ import { JobParamsProviderOptions, ReportingSharingData, } from '.'; -import { ReportingModalContent } from './reporting_modal_content_lazy'; import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy'; import { ReportingAPIClient } from '../../reporting_api_client'; +import { generateReportingJobPNGPDF } from '../shared/get_report_generate'; const getJobParams = ( @@ -64,6 +63,9 @@ const getJobParams = return { ...baseParams, relativeUrl }; }; +/** + * This is used by Canvas + */ export const reportingScreenshotShareProvider = ({ apiClient, toasts, @@ -225,13 +227,9 @@ export const isJobV2Params = ({ sharingData }: { sharingData: Record { const getShareMenuItems = ({ objectType, @@ -293,11 +291,6 @@ export const reportingExportModalProvider = ({ const isV2Job = isJobV2Params(jobProviderOptions); const requiresSavedState = !isV2Job; - const { title, activeData, columnsSorting } = sharingData as unknown as { - title: string; - activeData: TableInspectorAdapter; - columnsSorting?: string[]; - }; shareActions.push({ shareMenuItem: { @@ -308,39 +301,20 @@ export const reportingExportModalProvider = ({ disabled: licenseDisabled || sharingData.reportingDisabled, ['data-test-subj']: 'imageExports', }, - panel: { - id: 'reportingImageModal', - title: i18n.translate('reporting.shareContextMenu.ReportsButtonLabel', { - defaultMessage: 'Generate report', - }), - content: ( - { - onClose(); - }} - theme={theme} - objectType={objectType} - downloadCsvFromLens={async () => { - return await downloadCSVs({ - title, - formatFactory: formatFactoryFn(), - activeData, - uiSettings, - columnsSorting, - }); - }} - /> - ), - }, + tabType: 'Export', + reportType: [jobProviderOptions], + reportingAPIClient: apiClient, + requiresSavedState, + generateReportButton: ( + + ), + createReportingJob: generateReportingJobPNGPDF, + getJobParams, }); + return shareActions; }; diff --git a/packages/kbn-reporting/public/share/shared/get_report_generate.tsx b/packages/kbn-reporting/public/share/shared/get_report_generate.tsx new file mode 100644 index 00000000000000..f7725eb1d64e79 --- /dev/null +++ b/packages/kbn-reporting/public/share/shared/get_report_generate.tsx @@ -0,0 +1,131 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ThemeServiceSetup, ToastsSetup } from '@kbn/core/public'; +import { FormattedMessage, InjectedIntl } from '@kbn/i18n-react'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import type { ReportingAPIClient } from '../..'; + +export const generateReportingJobPNGPDF = ( + intl: InjectedIntl, + apiClient: ReportingAPIClient, + getJobParams: () => any, + reportType: string, + toasts: ToastsSetup, + objectType: string, + onClose: () => void, + theme: ThemeServiceSetup +) => { + const decoratedJobParams = apiClient.getDecoratedJobParams(getJobParams()); + return apiClient + .createReportingJob(reportType, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'reporting.share.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme$: theme.theme$ } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + }) + .catch((error: any) => { + toasts.addError(error, { + title: intl.formatMessage({ + id: 'reporting.share.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + }); +}; + +export const generateReportingJobCSV = ( + intl: InjectedIntl, + apiClient: ReportingAPIClient, + getJobParams: () => any, + reportType: string, + toasts: ToastsSetup, + objectType: string, + onClose: () => void, + theme: ThemeServiceSetup +) => { + const decoratedJobParams = apiClient.getDecoratedJobParams(getJobParams()); + return apiClient + .createReportingJob(reportType, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'reporting.share.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme$: theme.theme$ } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + }) + .catch((error) => { + toasts.addError(error, { + title: intl.formatMessage({ + id: 'reporting.share.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + }); +}; diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index f4551e393a4236..06a1288634c8d9 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -71,18 +71,21 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid }; const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - prepend={tab.prepend} - append={tab.append} - > - {tab.name} - - )); + return tabs.map((tab, index) => { + console.log(tab); + return ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + prepend={tab.prepend} + append={tab.append} + > + {tab.name} + + ); + }); }; return ( @@ -110,7 +113,6 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid data-test-subj={modalActionBtn.dataTestSubj} data-share-url={state.url} onClick={() => { - // @ts-ignore null for export modal on state since it's not using the buttons modalActionBtn!.handler!({ state: selectedTabId }); }} > diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx index 0fb12b050b5bde..5f4d716d8c5d48 100644 --- a/src/plugins/share/public/components/context/index.tsx +++ b/src/plugins/share/public/components/context/index.tsx @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ThemeServiceSetup } from '@kbn/core-theme-browser'; import { createContext, useContext } from 'react'; import { AnonymousAccessServiceContract } from '../../../common'; @@ -19,14 +20,14 @@ import type { export interface IShareContext extends ShareContext { allowEmbed: boolean; allowShortUrl: boolean; - // lens can return null when the licensing is less than gold - shareMenuItems: Array; + shareMenuItems: ShareMenuItem[]; embedUrlParamExtensions?: UrlParamExtension[]; anonymousAccess?: AnonymousAccessServiceContract; urlService: BrowserUrlService; snapshotShareWarning?: string; objectTypeTitle?: string; isEmbedded: boolean; + theme: ThemeServiceSetup; } export const ShareTabsContext = createContext(null); diff --git a/src/plugins/share/public/components/share_context_menu.tsx b/src/plugins/share/public/components/share_context_menu.tsx index feb2ae00e52fd4..0e73437914bc92 100644 --- a/src/plugins/share/public/components/share_context_menu.tsx +++ b/src/plugins/share/public/components/share_context_menu.tsx @@ -31,7 +31,7 @@ export interface ShareContextMenuProps { locator: LocatorPublic; params: any; }; - shareMenuItems: Array; + shareMenuItems: ShareMenuItem[]; sharingData: any; onClose: () => void; embedUrlParamExtensions?: UrlParamExtension[]; diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 61355e14b3ae15..892272faf4c2a8 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -10,7 +10,7 @@ import React, { type FC } from 'react'; import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { ShareTabsContext, useShareTabsContext, type IShareContext } from './context'; -import { linkTab, embedTab } from './tabs'; +import { linkTab, embedTab, ExportContent } from './tabs'; export const ShareMenuV2: FC<{ shareContext: IShareContext }> = ({ shareContext }) => { return ( @@ -28,22 +28,40 @@ export const ShareMenuTabs = () => { return null; } - const { allowEmbed, objectType, onClose, shareMenuItems } = shareContext; - + const { allowEmbed, objectType, onClose, shareMenuItems, isDirty, objectId, theme } = + shareContext; + console.log(shareMenuItems); const tabs = [linkTab]; if (shareMenuItems) { - shareMenuItems - // need to filter out the null shareMenuItem from Lens and just use the reporting image modal that includes CSV for Lens - .filter((item) => item !== null) - .forEach(({ shareMenuItem, panel }) => { - tabs.push({ - id: panel.id.toString(), - name: shareMenuItem.name, - // @ts-ignore - content: () => panel.content, - }); + shareMenuItems.forEach((shareMenuItem) => { + console.log(shareMenuItem); + const { getJobParams, createReportingJob, helpText, reportType, reportingAPIClient } = + shareMenuItem; + const exportContent = ( + // @ts-ignore showing undefined but there is a check to make sure share menu items exist + + ); + tabs.push({ + id: shareMenuItem.tabType, + name: shareMenuItem.tabType, + content: exportContent, }); + }); } + if (allowEmbed) { tabs.push(embedTab); } diff --git a/src/plugins/share/public/components/tabs/embed/embed_content.tsx b/src/plugins/share/public/components/tabs/embed/embed_content.tsx index 4405f91cc44df4..380dea99fb4785 100644 --- a/src/plugins/share/public/components/tabs/embed/embed_content.tsx +++ b/src/plugins/share/public/components/tabs/embed/embed_content.tsx @@ -53,10 +53,6 @@ export const EmbedContent = ({ const [anonymousAccessParameters] = useState(null); const [usePublicUrl] = useState(false); - useEffect(() => { - onChange(url); - }, [url, onChange]); - const getUrlParamExtensions = useCallback( (tempUrl: string): string => { return urlParams @@ -193,8 +189,9 @@ export const EmbedContent = ({ setUrlHelper(); getUrlParamExtensions(url); window.addEventListener('hashchange', resetUrl, false); + onChange(url); isMounted(); - }, [getUrlParamExtensions, resetUrl, setUrlHelper, url, isMounted]); + }, [getUrlParamExtensions, resetUrl, setUrlHelper, url, isMounted, onChange]); const renderUrlParamExtensions = () => { if (!urlParamExtensions) { diff --git a/src/plugins/share/public/components/tabs/export/export_content.tsx b/src/plugins/share/public/components/tabs/export/export_content.tsx new file mode 100644 index 00000000000000..acd48eeb869f6c --- /dev/null +++ b/src/plugins/share/public/components/tabs/export/export_content.tsx @@ -0,0 +1,101 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { ReportingAPIClient } from '@kbn/reporting-public'; + +import { type IShareContext } from '../../context'; + +type ExportProps = Pick & { + reportingAPIClient: ReportingAPIClient; + getJobParams: Function; + createReportingJob: Function; + helpText: FormattedMessage; + reportType: string; +}; + +/** + * Based on {@link URL_MAX_LENGTH} exported from core/public. + */ +const CHROMIUM_MAX_URL_LENGTH = 25 * 1000; + +export const getMaxUrlLength = () => CHROMIUM_MAX_URL_LENGTH; + +export const ExportContent = ({ + getJobParams, + theme, + reportingAPIClient, + createReportingJob, + helpText, + isDirty, + objectId, + reportType, +}: ExportProps) => { + // const isSaved = Boolean(objectId); + // const isMounted = useMountedState(); + // const [absoluteUrl, setAbsoluteUrl] = useState(''); + + // const exceedsMaxLength = absoluteUrl.length >= getMaxUrlLength(); + + // const getAbsoluteReportGenerationUrl = useMemo( + // () => () => { + // const relativePath = reportingAPIClient.getReportingPublicJobPath( + // reportType, + // reportingAPIClient.getDecoratedJobParams(getJobParams()) + // ); + // return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); + // }, + // [reportingAPIClient, getJobParams, reportType] + // ); + + // useEffect(() => { + // const reportingUrl = new URL(window.location.origin); + // reportingUrl.pathname = reportingAPIClient.getReportingPublicJobPath( + // reportType, + // reportingAPIClient.getDecoratedJobParams(getJobParams()) + // ); + // setAbsoluteUrl(reportingUrl.toString()); + // }, [getAbsoluteReportGenerationUrl, reportingAPIClient, getJobParams, reportType]); + + return ( + <> + test + {/* {helpText} + + {!isSaved ? ( + + createReportingJob(reportingAPIClient)} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + > + + + + ) : ( + createReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(createReportingJob)} + /> + )} + */} + + ); +}; diff --git a/src/plugins/share/public/components/tabs/index.ts b/src/plugins/share/public/components/tabs/index.ts index a5855495aaec55..952e6031e696c0 100644 --- a/src/plugins/share/public/components/tabs/index.ts +++ b/src/plugins/share/public/components/tabs/index.ts @@ -8,3 +8,4 @@ export { linkTab } from './link'; export { embedTab } from './embed'; +export { ExportContent } from './export/export_content'; diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 2d824d0945daea..73eaabe75843c2 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -89,7 +89,7 @@ export class SharePlugin private redirectManager?: RedirectManager; private url?: BrowserUrlService; private anonymousAccessServiceProvider?: () => AnonymousAccessServiceContract; - kibanaVersion: string; + private kibanaVersion: string; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index ecdddcfef3b74a..7cdfcc3f31b174 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -94,7 +94,7 @@ export class ShareMenuManager { newVersionEnabled, }: ShowShareMenuOptions & { anchorElement: HTMLElement; - menuItems: Array; + menuItems: ShareMenuItem[]; urlService: BrowserUrlService; anonymousAccess: AnonymousAccessServiceContract | undefined; theme: ThemeServiceStart; @@ -172,6 +172,7 @@ export class ShareMenuManager { isDirty, isEmbedded: allowEmbed, shareMenuItems: menuItems, + theme, onClose: () => { onClose(); session.close(); diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index cbb8bf098a5160..ec1c9fdb66d0d9 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ -import { ComponentType } from 'react'; +import { ComponentType, ReactElement } from 'react'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiContextMenuPanelItemDescriptorEntry } from '@elastic/eui/src/components/context_menu/context_menu'; import type { Capabilities } from '@kbn/core/public'; +import type { JobParamsProviderOptions } from '@kbn/reporting-public/share/share_context_menu'; +import { ReportingAPIClient } from '@kbn/reporting-public'; import type { UrlService, LocatorPublic } from '../common/url_service'; import type { BrowserShortUrlClientFactoryCreateParams } from './url_service/short_urls/short_url_client_factory'; import type { BrowserShortUrlClient } from './url_service/short_urls/short_url_client'; @@ -72,7 +74,17 @@ export interface ShareContextMenuPanelItem * */ export interface ShareMenuItem { shareMenuItem: ShareContextMenuPanelItem; - panel: EuiContextMenuPanelDescriptor; + // needed for Canvas + panel?: EuiContextMenuPanelDescriptor; + reportType?: Array; + tabType?: string; + requresSavedState?: boolean; + helpText?: ReactElement; + copyURLButton?: { id: string; dataTestSubj: string; label: string }; + generateReportButton?: ReactElement; + getJobParams?: Function; + createReportingJob?: Function; + reportingAPIClient?: ReportingAPIClient; } /** @@ -84,10 +96,7 @@ export interface ShareMenuItem { * */ export interface ShareMenuProvider { readonly id: string; - /** - * Null is set for cases where Lens has its less than gold licensing and csv is permitted - */ - getShareMenuItems: (context: ShareContext) => Array; + getShareMenuItems: (context: ShareContext) => ShareMenuItem[]; } interface UrlParamExtensionProps { diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 44cf5405b9d143..39200edf3a9646 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -6,14 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import React from 'react'; import { tableHasFormulas } from '@kbn/data-plugin/common'; import { downloadMultipleAs, ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; import { exporters } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import { ILicense } from '@kbn/licensing-plugin/public'; import { FormatFactory } from '../../../common/types'; -import { DownloadPanelContent } from './csv_download_panel_content_lazy'; import { TableInspectorAdapter } from '../../editor_frame_service/types'; declare global { @@ -97,13 +94,11 @@ function getWarnings(activeData: TableInspectorAdapter) { interface DownloadPanelShareOpts { uiSettings: IUiSettingsClient; formatFactoryFn: () => FormatFactory; - license: ILicense; } export const downloadCsvShareProvider = ({ uiSettings, formatFactoryFn, - license, }: DownloadPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, sharingData, onClose }: ShareContext) => { if ('lens' !== objectType) { @@ -124,39 +119,29 @@ export const downloadCsvShareProvider = ({ } ); - const atLeastGold = license.hasAtLeast('gold'); - return [ - !atLeastGold - ? { - shareMenuItem: { - name: panelTitle, - icon: 'document', - disabled: !csvEnabled, - sortOrder: 1, - }, - panel: { - id: 'csvDownloadPanel', - title: panelTitle, - content: ( - { - await downloadCSVs({ - title, - formatFactory: formatFactoryFn(), - activeData, - uiSettings, - columnsSorting, - }); - onClose?.(); - }} - /> - ), - }, - } - : null, + { + shareMenuItem: { + name: panelTitle, + icon: 'document', + disabled: !csvEnabled, + sortOrder: 1, + }, + id: 'lens csv', + tabType: 'Export', + name: 'Export', + reportType: ['CSV'], + isDisabled: !csvEnabled, + createReportingJob: async () => { + await downloadCSVs({ + title, + formatFactory: formatFactoryFn(), + activeData, + uiSettings, + columnsSorting, + }); + }, + }, ]; }; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 1299922a6885ed..671f84f2b9a82b 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -403,7 +403,6 @@ export class LensPlugin { downloadCsvShareProvider({ uiSettings: core.uiSettings, formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, - license, }) ); } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index ed3ff31fa2ef8e..e8b3ed796778dd 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -32,7 +32,6 @@ import { ReportingAPIClient } from '@kbn/reporting-public'; import { getSharedComponents, reportingCsvShareProvider, - reportingScreenshotShareProvider, reportingExportModalProvider, } from '@kbn/reporting-public/share'; import { ReportingCsvPanelAction } from '@kbn/reporting-csv-share-panel'; @@ -224,31 +223,28 @@ export class ReportingPublicPlugin ); if (this.config.export_types.pdf.enabled || this.config.export_types.png.enabled) { - shareSetup.register( - reportingScreenshotShareProvider({ - apiClient, - toasts, - uiSettings, - license, - application, - usesUiCapabilities, - theme: core.theme, - }) - ); - - shareSetup.register( - reportingExportModalProvider({ - apiClient, - toasts, - uiSettings, - license, - application, - usesUiCapabilities, - theme: core.theme, - version: this.kibanaVersion, - formatFactoryFn: () => fieldFormats.deserialize, - }) - ); + // shareSetup.register( + // reportingScreenshotShareProvider({ + // apiClient, + // toasts, + // uiSettings, + // license, + // application, + // usesUiCapabilities, + // theme: core.theme, + // }) + reportingExportModalProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + version: this.kibanaVersion, + formatFactoryFn: () => fieldFormats.deserialize, + }); + // ); } }); }); From 3c736bf14324bb47a0a4089c2a91ff90b31fd490 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 2 Apr 2024 20:51:53 -0600 Subject: [PATCH 67/69] got the export tab modal rendering --- packages/shared-ux/modal/tabbed/index.tsx | 2 +- .../modal/tabbed/src/context/index.tsx | 2 +- .../modal/tabbed/src/tabbed_modal.tsx | 30 ++++++---- .../share/public/components/share_tabs.tsx | 39 +++--------- .../components/tabs/export/export_content.tsx | 9 +-- .../public/components/tabs/export/index.tsx | 59 +++++++++++++++++++ .../share/public/components/tabs/index.ts | 2 +- .../public/components/tabs/link/index.tsx | 7 +-- .../csv_download_provider.tsx | 1 + 9 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 src/plugins/share/public/components/tabs/export/index.tsx diff --git a/packages/shared-ux/modal/tabbed/index.tsx b/packages/shared-ux/modal/tabbed/index.tsx index 7ba578418283c4..6620f2633b5560 100644 --- a/packages/shared-ux/modal/tabbed/index.tsx +++ b/packages/shared-ux/modal/tabbed/index.tsx @@ -7,4 +7,4 @@ */ export { TabbedModal } from './src/tabbed_modal'; -export type { ITabDeclaration } from './src/context'; +export type { IModalTabDeclaration } from './src/tabbed_modal'; diff --git a/packages/shared-ux/modal/tabbed/src/context/index.tsx b/packages/shared-ux/modal/tabbed/src/context/index.tsx index 943b67f7110282..9e7f6d7effb839 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.tsx @@ -44,7 +44,7 @@ interface IModalContext>>> { meta: IMetaState; [index: string]: any; }; - dispatch: Dispatch; + dispatch?: Dispatch; } const createStateContext = once(>>>() => diff --git a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx index 06a1288634c8d9..cb53a86310efb9 100644 --- a/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx +++ b/packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx @@ -6,7 +6,14 @@ * Side Public License, v 1. */ -import React, { useMemo, Fragment, type ComponentProps, type FC, type ReactElement } from 'react'; +import React, { + useMemo, + Fragment, + type ComponentProps, + type FC, + type ReactElement, + useCallback, +} from 'react'; import { EuiButton, EuiModal, @@ -28,8 +35,8 @@ import { } from './context'; export type IModalTabContent = (props: { - state: S | null; - dispatch: IDispatchFunction; + state?: S; + dispatch?: IDispatchFunction; }) => ReactElement; interface IModalTabActionBtn extends CommonProps { @@ -44,7 +51,7 @@ export interface IModalTabDeclaration extends EuiTabProps, ITabDeclarati description?: string; 'data-test-subj'?: string; content: IModalTabContent; - modalActionBtn: IModalTabActionBtn; + modalActionBtn?: IModalTabActionBtn; } interface ITabbedModalInner extends Pick, 'onClose'> { @@ -66,13 +73,15 @@ const TabbedModalInner: FC = ({ onClose, modalTitle, modalWid return tabs.find((obj) => obj.id === selectedTabId)!; }, [selectedTabId, tabs]); - const onSelectedTabChanged = (id: string) => { - dispatch({ type: 'META_selectedTabId', payload: id }); - }; + const onSelectedTabChanged = useCallback( + (id: string) => { + dispatch!({ type: 'META_selectedTabId', payload: id }); + }, + [dispatch] + ); - const renderTabs = () => { + const renderTabs = useCallback(() => { return tabs.map((tab, index) => { - console.log(tab); return ( = ({ onClose, modalTitle, modalWid ); }); - }; + }, [onSelectedTabChanged, selectedTabId, tabs]); return ( = ({ onClose, modalTitle, modalWid data-test-subj={modalActionBtn.dataTestSubj} data-share-url={state.url} onClick={() => { + // @ts-ignore state will not be null because of the modalActionBtn check modalActionBtn!.handler!({ state: selectedTabId }); }} > diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 892272faf4c2a8..f2b0ad7f5bd422 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -10,7 +10,7 @@ import React, { type FC } from 'react'; import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; import { ShareTabsContext, useShareTabsContext, type IShareContext } from './context'; -import { linkTab, embedTab, ExportContent } from './tabs'; +import { linkTab, embedTab, exportTab } from './tabs'; export const ShareMenuV2: FC<{ shareContext: IShareContext }> = ({ shareContext }) => { return ( @@ -28,38 +28,13 @@ export const ShareMenuTabs = () => { return null; } - const { allowEmbed, objectType, onClose, shareMenuItems, isDirty, objectId, theme } = - shareContext; - console.log(shareMenuItems); - const tabs = [linkTab]; + const { allowEmbed, objectType, onClose, shareMenuItems } = shareContext; + const tabs = []; + + tabs.push(linkTab); + if (shareMenuItems) { - shareMenuItems.forEach((shareMenuItem) => { - console.log(shareMenuItem); - const { getJobParams, createReportingJob, helpText, reportType, reportingAPIClient } = - shareMenuItem; - const exportContent = ( - // @ts-ignore showing undefined but there is a check to make sure share menu items exist - - ); - tabs.push({ - id: shareMenuItem.tabType, - name: shareMenuItem.tabType, - content: exportContent, - }); - }); + tabs.push(exportTab); } if (allowEmbed) { diff --git a/src/plugins/share/public/components/tabs/export/export_content.tsx b/src/plugins/share/public/components/tabs/export/export_content.tsx index acd48eeb869f6c..116cdae185b039 100644 --- a/src/plugins/share/public/components/tabs/export/export_content.tsx +++ b/src/plugins/share/public/components/tabs/export/export_content.tsx @@ -10,6 +10,8 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { ReportingAPIClient } from '@kbn/reporting-public'; +import { EuiButton, EuiModalBody, EuiModalFooter, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { type IShareContext } from '../../context'; type ExportProps = Pick & { @@ -37,7 +39,7 @@ export const ExportContent = ({ objectId, reportType, }: ExportProps) => { - // const isSaved = Boolean(objectId); + const isSaved = Boolean(objectId); // const isMounted = useMountedState(); // const [absoluteUrl, setAbsoluteUrl] = useState(''); @@ -65,8 +67,7 @@ export const ExportContent = ({ return ( <> - test - {/* {helpText} + {helpText} {!isSaved ? ( )} - */} + ); }; diff --git a/src/plugins/share/public/components/tabs/export/index.tsx b/src/plugins/share/public/components/tabs/export/index.tsx new file mode 100644 index 00000000000000..4078a198bf4ab2 --- /dev/null +++ b/src/plugins/share/public/components/tabs/export/index.tsx @@ -0,0 +1,59 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; +import { ExportContent } from './export_content'; +import { useShareTabsContext } from '../../context'; + +type IExportTab = IModalTabDeclaration; + +const exportTabReducer: IExportTab['reducer'] = (state, action) => { + switch (action.type) { + default: + return state; + } +}; + +const ExportTabContent: NonNullable = ({ state, dispatch }) => { + const { shareMenuItems, objectType, isDirty, objectId, theme, onClose } = useShareTabsContext()!; + + return shareMenuItems.map((shareMenuItem, index) => { + const { getJobParams, createReportingJob, helpText, reportType, reportingAPIClient } = + shareMenuItem; + + return ( + // @ts-ignore props show undefined because of v1 share design modal needed the props to be optional for congruency with Canvas + + ); + }); +}; + +export const exportTab: IExportTab = { + id: 'export', + name: i18n.translate('share.contextMenu.exportCodeTab', { + defaultMessage: 'Export', + }), + reducer: exportTabReducer, + content: ExportTabContent, +}; diff --git a/src/plugins/share/public/components/tabs/index.ts b/src/plugins/share/public/components/tabs/index.ts index 952e6031e696c0..200cb058daba57 100644 --- a/src/plugins/share/public/components/tabs/index.ts +++ b/src/plugins/share/public/components/tabs/index.ts @@ -8,4 +8,4 @@ export { linkTab } from './link'; export { embedTab } from './embed'; -export { ExportContent } from './export/export_content'; +export { exportTab } from './export'; diff --git a/src/plugins/share/public/components/tabs/link/index.tsx b/src/plugins/share/public/components/tabs/link/index.tsx index b3267e277c340a..5ea8b15c3c8835 100644 --- a/src/plugins/share/public/components/tabs/link/index.tsx +++ b/src/plugins/share/public/components/tabs/link/index.tsx @@ -52,7 +52,7 @@ const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => { const setDashboardLink = useCallback( (url: string) => { - dispatch({ type: LINK_TAB_ACTIONS.SET_DASHBOARD_URL, payload: url }); + dispatch!({ type: LINK_TAB_ACTIONS.SET_DASHBOARD_URL, payload: url }); }, [dispatch] ); @@ -68,7 +68,7 @@ const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => { shareableUrlForSavedObject, urlService, shareableUrlLocatorParams, - dashboardLink: state.dashboardUrl, + dashboardLink: state?.dashboardUrl, setDashboardLink, }} /> @@ -80,9 +80,6 @@ export const linkTab: ILinkTab = { name: i18n.translate('share.contextMenu.permalinksTab', { defaultMessage: 'Links', }), - description: i18n.translate('share.dashboard.link.description', { - defaultMessage: 'Share a direct link to this search.', - }), content: LinkTabContent, reducer: linkTabReducer, modalActionBtn: { diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 39200edf3a9646..d530e09c973829 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -132,6 +132,7 @@ export const downloadCsvShareProvider = ({ name: 'Export', reportType: ['CSV'], isDisabled: !csvEnabled, + warnings: getWarnings(activeData), createReportingJob: async () => { await downloadCSVs({ title, From 24cad86773d5b586d1f4bdc0bec94adf979373b5 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 3 Apr 2024 15:42:40 -0600 Subject: [PATCH 68/69] wip reports dont work but modals set up --- .../register_pdf_png_reporting.tsx | 13 +- .../share/shared/get_report_generate.tsx | 11 +- .../share/public/components/context/index.tsx | 2 + .../components/tabs/export/export_content.tsx | 430 +++++++++++++++--- .../public/components/tabs/export/index.tsx | 38 +- .../components/tabs/link/link_content.tsx | 2 +- .../public/services/share_menu_manager.tsx | 1 + src/plugins/share/public/types.ts | 11 +- .../csv_download_provider.tsx | 78 ++-- x-pack/plugins/lens/public/plugin.ts | 1 + x-pack/plugins/reporting/public/plugin.ts | 46 +- 11 files changed, 493 insertions(+), 140 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index a316db61810526..05b59d4bff51a5 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -230,6 +230,8 @@ export const reportingExportModalProvider = ({ license, application, usesUiCapabilities, + toasts, + theme, }: ExportModalShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, @@ -302,9 +304,15 @@ export const reportingExportModalProvider = ({ ['data-test-subj']: 'imageExports', }, tabType: 'Export', - reportType: [jobProviderOptions], + jobProviderOptions, reportingAPIClient: apiClient, requiresSavedState, + helpText: ( + + ), generateReportButton: ( any, - reportType: string, - toasts: ToastsSetup, - objectType: string, - onClose: () => void, - theme: ThemeServiceSetup -) => { +export const generateReportingJobPNGPDF = () => { const decoratedJobParams = apiClient.getDecoratedJobParams(getJobParams()); return apiClient .createReportingJob(reportType, decoratedJobParams) diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx index 5f4d716d8c5d48..f9ca62637a6cbd 100644 --- a/src/plugins/share/public/components/context/index.tsx +++ b/src/plugins/share/public/components/context/index.tsx @@ -7,6 +7,7 @@ */ import { ThemeServiceSetup } from '@kbn/core-theme-browser'; +import { I18nStart } from '@kbn/core/public'; import { createContext, useContext } from 'react'; import { AnonymousAccessServiceContract } from '../../../common'; @@ -28,6 +29,7 @@ export interface IShareContext extends ShareContext { objectTypeTitle?: string; isEmbedded: boolean; theme: ThemeServiceSetup; + i18n: I18nStart; } export const ShareTabsContext = createContext(null); diff --git a/src/plugins/share/public/components/tabs/export/export_content.tsx b/src/plugins/share/public/components/tabs/export/export_content.tsx index 116cdae185b039..95b442694c5ee5 100644 --- a/src/plugins/share/public/components/tabs/export/export_content.tsx +++ b/src/plugins/share/public/components/tabs/export/export_content.tsx @@ -6,96 +6,392 @@ * Side Public License, v 1. */ -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { FormattedMessage, InjectedIntl } from '@kbn/i18n-react'; import type { ReportingAPIClient } from '@kbn/reporting-public'; -import { EuiButton, EuiModalBody, EuiModalFooter, EuiToolTip } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiCopy, + EuiFlexGroup, + EuiForm, + EuiIcon, + EuiModalFooter, + EuiRadioGroup, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, + EuiText, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import useMountedState from 'react-use/lib/useMountedState'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import url from 'url'; +import { LayoutParams } from '@kbn/screenshotting-plugin/common'; +import { JobParamsProviderOptions } from '@kbn/reporting-public/share/share_context_menu'; +import { BaseParams } from '@kbn/reporting-common/types'; +import { ThemeServiceSetup, ToastsSetup } from '@kbn/core/public'; import { type IShareContext } from '../../context'; -type ExportProps = Pick & { +type ExportProps = Pick< + IShareContext, + 'isDirty' | 'objectId' | 'objectType' | 'onClose' | 'i18n' +> & { reportingAPIClient: ReportingAPIClient; getJobParams: Function; - createReportingJob: Function; helpText: FormattedMessage; - reportType: string; + generateReportButton: FormattedMessage; + jobProviderOptions?: JobParamsProviderOptions; + layoutOption?: 'print'; + toasts: ToastsSetup; + theme: ThemeServiceSetup; + downloadCSVLens: Function; }; -/** - * Based on {@link URL_MAX_LENGTH} exported from core/public. - */ -const CHROMIUM_MAX_URL_LENGTH = 25 * 1000; - -export const getMaxUrlLength = () => CHROMIUM_MAX_URL_LENGTH; +type AllowedExports = 'pngV2' | 'printablePdfV2' | 'csv'; +type AppParams = Omit; +type Props = ExportProps & { intl: InjectedIntl }; export const ExportContent = ({ getJobParams, theme, reportingAPIClient, - createReportingJob, helpText, isDirty, objectId, - reportType, -}: ExportProps) => { - const isSaved = Boolean(objectId); - // const isMounted = useMountedState(); - // const [absoluteUrl, setAbsoluteUrl] = useState(''); - - // const exceedsMaxLength = absoluteUrl.length >= getMaxUrlLength(); - - // const getAbsoluteReportGenerationUrl = useMemo( - // () => () => { - // const relativePath = reportingAPIClient.getReportingPublicJobPath( - // reportType, - // reportingAPIClient.getDecoratedJobParams(getJobParams()) - // ); - // return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); - // }, - // [reportingAPIClient, getJobParams, reportType] - // ); - - // useEffect(() => { - // const reportingUrl = new URL(window.location.origin); - // reportingUrl.pathname = reportingAPIClient.getReportingPublicJobPath( - // reportType, - // reportingAPIClient.getDecoratedJobParams(getJobParams()) - // ); - // setAbsoluteUrl(reportingUrl.toString()); - // }, [getAbsoluteReportGenerationUrl, reportingAPIClient, getJobParams, reportType]); - - return ( - <> - {helpText} - - {!isSaved ? ( + objectType, + generateReportButton, + jobProviderOptions, + layoutOption, + intl, + toasts, + onClose, + downloadCSVLens, + i18n: i18nStart, +}: Props) => { + const isSaved = Boolean(objectId) || !isDirty; + const [, setIsStale] = useState(false); + const [isCreatingReport, setIsCreatingReport] = useState(false); + const [selectedRadio, setSelectedRadio] = useState('printablePdfV2'); + const [usePrintLayout, setPrintLayout] = useState(false); + const [absoluteUrl, setAbsoluteUrl] = useState(''); + const isMounted = useMountedState(); + + const getJobsParamsForImageExports = useCallback( + (type: AllowedExports, opts?: JobParamsProviderOptions) => { + if (!opts) { + return; + } + + const { + sharingData: { title, layout, locatorParams }, + } = opts; + + const baseParams = { + objectType, + layout, + title, + }; + + if (type === 'printablePdfV2') { + // multi locator for PDF V2 + return { ...baseParams, locatorParams: [locatorParams] }; + } else if (type === 'pngV2') { + // single locator for PNG V2 + return { ...baseParams, locatorParams }; + } + + // Relative URL must have URL prefix (Spaces ID prefix), but not server basePath + // Replace hashes with original RISON values. + const relativeUrl = opts?.shareableUrl.replace( + window.location.origin + reportingAPIClient.getServerBasePath(), + '' + ); + + // single URL for PNG + return { ...baseParams, relativeUrl }; + }, + [reportingAPIClient, objectType] + ); + + const getLayout = useCallback((): LayoutParams => { + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + const dimensions = { height, width }; + + if (usePrintLayout) { + return { id: 'print', dimensions }; + } + + return { id: 'preserve_layout', dimensions }; + }, [usePrintLayout]); + + const getJobParamsImages = useCallback( + (shareableUrl?: boolean) => { + return { + ...getJobsParamsForImageExports(selectedRadio, jobProviderOptions), + layout: getLayout(), + }; + }, + [getJobsParamsForImageExports, getLayout, jobProviderOptions, selectedRadio] + ); + + const getAbsoluteReportGenerationUrl = useMemo( + () => () => { + if (getJobsParamsForImageExports(selectedRadio, jobProviderOptions) !== undefined) { + const relativePath = reportingAPIClient.getReportingPublicJobPath( + selectedRadio, + reportingAPIClient.getDecoratedJobParams(getJobParamsImages(true) as unknown as AppParams) + ); + return setAbsoluteUrl(url.resolve(window.location.href, relativePath)); + } + }, + [ + reportingAPIClient, + getJobParamsImages, + selectedRadio, + getJobsParamsForImageExports, + jobProviderOptions, + ] + ); + + const markAsStale = useCallback(() => { + if (!isMounted) return; + setIsStale(true); + }, [isMounted]); + + useEffect(() => { + getAbsoluteReportGenerationUrl(); + markAsStale(); + }, [markAsStale, getAbsoluteReportGenerationUrl]); + const handlePrintLayoutChange = (evt: EuiSwitchEvent) => { + setPrintLayout(evt.target.checked); + }; + + const renderLayoutOptionsSwitch = () => { + if (layoutOption === ('print' as const) && selectedRadio !== 'pngV2') { + return ( + <> + + + + } + css={{ display: 'block' }} + checked={usePrintLayout} + onChange={handlePrintLayoutChange} + data-test-subj="usePrintLayout" + /> - createReportingJob(reportingAPIClient)} - data-test-subj="generateReportButton" - isLoading={Boolean(createReportingJob)} - > + content={ - + } + > + - ) : ( - createReportingJob()} - data-test-subj="generateReportButton" - isLoading={Boolean(createReportingJob)} + + ); + } + }; + const renderCopyURLButton = useCallback(() => { + return ( + <> + + ) : ( + + ) + } + > + + {(copy) => ( + + + + )} + + + + } + > + + + + ); + }, [absoluteUrl, isDirty]); + + const generateReportingJob = () => { + if (selectedRadio === 'csv') { + return downloadCSVLens(); + } + const decoratedJobParams = reportingAPIClient.getDecoratedJobParams( + getJobParams(false) as unknown as AppParams + ); + setIsCreatingReport(true); + return reportingAPIClient + .createReportingJob(selectedRadio, decoratedJobParams) + .then(() => { + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'reporting.modalContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + { theme, i18n: i18nStart } + ), + 'data-test-subj': 'queueReportSuccess', + }); + if (onClose) { + onClose(); + } + if (isMounted()) { + setIsCreatingReport(false); + } + }) + .catch((error) => { + toasts.addError(error, { + title: intl.formatMessage({ + id: 'reporting.modalContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: ( + // eslint-disable-next-line react/no-danger + + ) as unknown as string, + }); + if (isMounted()) { + setIsCreatingReport(false); + } + }); + }; + + const renderGenerateReportButton = !isSaved ? ( + + + {generateReportButton} + + + ) : ( + generateReportingJob()} + data-test-subj="generateReportButton" + isLoading={Boolean(isCreatingReport)} + > + {generateReportButton} + + ); + + const radioOptions = + objectType === 'lens' + ? [ + { id: 'printablePdfV2', label: 'PDF' }, + { id: 'pngV2', label: 'PNG', 'data-test-subj': 'pngReportOption' }, + { id: 'csv', label: 'CSV', 'data-test-subj': 'lensCSVReport' }, + ] + : [ + { id: 'printablePdfV2', label: 'PDF' }, + { id: 'pngV2', label: 'PNG', 'data-test-subj': 'pngReportOption' }, + ]; + + const renderRadioOptions = () => { + if (objectType === 'dashboard' || objectType === 'lens') { + return ( + + { + setSelectedRadio(id as AllowedExports); + }} + name="image reporting radio group" + idSelected={selectedRadio} + legend={{ + children: ( + + ), + }} /> - )} + + ); + } + }; + + return objectType === 'lens' && !helpText ? null : ( + <> + + + {helpText} + + {renderRadioOptions()} + + + + {renderLayoutOptionsSwitch()} + {renderCopyURLButton()} + {renderGenerateReportButton} ); diff --git a/src/plugins/share/public/components/tabs/export/index.tsx b/src/plugins/share/public/components/tabs/export/index.tsx index 4078a198bf4ab2..b092675ca4981d 100644 --- a/src/plugins/share/public/components/tabs/export/index.tsx +++ b/src/plugins/share/public/components/tabs/export/index.tsx @@ -21,33 +21,56 @@ const exportTabReducer: IExportTab['reducer'] = (state, action) => { } }; -const ExportTabContent: NonNullable = ({ state, dispatch }) => { - const { shareMenuItems, objectType, isDirty, objectId, theme, onClose } = useShareTabsContext()!; +function ExportTabContent() { + const { + shareMenuItems, + objectType, + isDirty, + objectId, + onClose, + i18n: i18nStart, + } = useShareTabsContext()!; return shareMenuItems.map((shareMenuItem, index) => { - const { getJobParams, createReportingJob, helpText, reportType, reportingAPIClient } = - shareMenuItem; + if (objectType === 'lens' && shareMenuItem.content) { + return shareMenuItem.content; + } + const { + getJobParams, + jobProviderOptions, + helpText, + layoutOption, + reportingAPIClient, + generateReportButton, + toasts, + theme, + downloadCSVLens, + } = shareMenuItem; return ( // @ts-ignore props show undefined because of v1 share design modal needed the props to be optional for congruency with Canvas ); }); -}; +} export const exportTab: IExportTab = { id: 'export', @@ -55,5 +78,6 @@ export const exportTab: IExportTab = { defaultMessage: 'Export', }), reducer: exportTabReducer, + // @ts-ignore content: ExportTabContent, }; diff --git a/src/plugins/share/public/components/tabs/link/link_content.tsx b/src/plugins/share/public/components/tabs/link/link_content.tsx index 32d5596841de9d..5b850a92486111 100644 --- a/src/plugins/share/public/components/tabs/link/link_content.tsx +++ b/src/plugins/share/public/components/tabs/link/link_content.tsx @@ -165,7 +165,7 @@ export const LinkContent = ({ } else { tempUrl = getSavedObjectUrl(); } - return url === '' ? setUrl(tempUrl!) : createShortUrl(tempUrl!); + return url === '' || objectType === 'lens' ? setUrl(tempUrl!) : createShortUrl(tempUrl!); }, [getSavedObjectUrl, getSnapshotUrl, createShortUrl, objectType, url]); useEffect(() => { diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 7cdfcc3f31b174..e35fa99a38bd0a 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -154,6 +154,7 @@ export class ShareMenuManager { toMountPoint( ; @@ -83,8 +83,13 @@ export interface ShareMenuItem { copyURLButton?: { id: string; dataTestSubj: string; label: string }; generateReportButton?: ReactElement; getJobParams?: Function; - createReportingJob?: Function; reportingAPIClient?: ReportingAPIClient; + jobProviderOptions?: JobParamsProviderOptions; + layoutOption?: 'print'; + toasts?: IToasts; + theme?: ThemeServiceSetup; + downloadCSVLens?: Function; + content?: ReactElement; } /** diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index d530e09c973829..ae8a8855cc409e 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -6,11 +6,13 @@ */ import { i18n } from '@kbn/i18n'; +import React from 'react'; import { tableHasFormulas } from '@kbn/data-plugin/common'; import { downloadMultipleAs, ShareContext, ShareMenuProvider } from '@kbn/share-plugin/public'; import { exporters } from '@kbn/data-plugin/public'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { FormatFactory } from '../../../common/types'; +import { DownloadPanelContent } from './csv_download_panel_content_lazy'; import { TableInspectorAdapter } from '../../editor_frame_service/types'; declare global { @@ -23,7 +25,7 @@ declare global { } } -export const downloadCSVs = async ({ +async function downloadCSVs({ activeData, title, formatFactory, @@ -35,7 +37,7 @@ export const downloadCSVs = async ({ formatFactory: FormatFactory; uiSettings: IUiSettingsClient; columnsSorting?: string[]; -}) => { +}) { if (!activeData) { if (window.ELASTIC_LENS_CSV_DOWNLOAD_DEBUG) { window.ELASTIC_LENS_CSV_CONTENT = undefined; @@ -70,7 +72,7 @@ export const downloadCSVs = async ({ if (content) { downloadMultipleAs(content); } -}; +} function getWarnings(activeData: TableInspectorAdapter) { const messages = []; @@ -94,11 +96,13 @@ function getWarnings(activeData: TableInspectorAdapter) { interface DownloadPanelShareOpts { uiSettings: IUiSettingsClient; formatFactoryFn: () => FormatFactory; + atLeastGold: boolean; } export const downloadCsvShareProvider = ({ uiSettings, formatFactoryFn, + atLeastGold, }: DownloadPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, sharingData, onClose }: ShareContext) => { if ('lens' !== objectType) { @@ -115,35 +119,51 @@ export const downloadCsvShareProvider = ({ const panelTitle = i18n.translate( 'xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel', { - defaultMessage: 'Export', + defaultMessage: 'CSV Download', } ); - return [ - { - shareMenuItem: { - name: panelTitle, - icon: 'document', - disabled: !csvEnabled, - sortOrder: 1, - }, - id: 'lens csv', - tabType: 'Export', - name: 'Export', - reportType: ['CSV'], - isDisabled: !csvEnabled, - warnings: getWarnings(activeData), - createReportingJob: async () => { - await downloadCSVs({ - title, - formatFactory: formatFactoryFn(), - activeData, - uiSettings, - columnsSorting, - }); - }, - }, - ]; + return atLeastGold + ? [ + { + downloadCSVLens: async () => { + await downloadCSVs({ + title, + formatFactory: formatFactoryFn(), + activeData, + uiSettings, + columnsSorting, + }); + onClose?.(); + }, + }, + ] + : [ + { + shareMenuItem: { + name: panelTitle, + icon: 'document', + disabled: !csvEnabled, + sortOrder: 1, + }, + content: ( + { + await downloadCSVs({ + title, + formatFactory: formatFactoryFn(), + activeData, + uiSettings, + columnsSorting, + }); + onClose?.(); + }} + /> + ), + }, + ]; }; return { diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 671f84f2b9a82b..4bf133f5064dcd 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -403,6 +403,7 @@ export class LensPlugin { downloadCsvShareProvider({ uiSettings: core.uiSettings, formatFactoryFn: () => startServices().plugins.fieldFormats.deserialize, + atLeastGold, }) ); } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index e8b3ed796778dd..b5d5ddc4046615 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -33,6 +33,7 @@ import { getSharedComponents, reportingCsvShareProvider, reportingExportModalProvider, + reportingScreenshotShareProvider, } from '@kbn/reporting-public/share'; import { ReportingCsvPanelAction } from '@kbn/reporting-csv-share-panel'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -223,28 +224,29 @@ export class ReportingPublicPlugin ); if (this.config.export_types.pdf.enabled || this.config.export_types.png.enabled) { - // shareSetup.register( - // reportingScreenshotShareProvider({ - // apiClient, - // toasts, - // uiSettings, - // license, - // application, - // usesUiCapabilities, - // theme: core.theme, - // }) - reportingExportModalProvider({ - apiClient, - toasts, - uiSettings, - license, - application, - usesUiCapabilities, - theme: core.theme, - version: this.kibanaVersion, - formatFactoryFn: () => fieldFormats.deserialize, - }); - // ); + shareSetup.register( + reportingExportModalProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + }) + ); + + shareSetup.register( + reportingScreenshotShareProvider({ + apiClient, + toasts, + uiSettings, + license, + application, + usesUiCapabilities, + theme: core.theme, + }) + ); } }); }); From 61d5405bd36c97b8080225bb53b2d41fd781e063 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 3 Apr 2024 16:49:10 -0600 Subject: [PATCH 69/69] wip reporting is working except for console errors and lens csv --- .../register_csv_reporting.tsx | 2 +- .../register_pdf_png_reporting.tsx | 9 ++++++- .../share/public/components/context/index.tsx | 3 ++- .../components/tabs/export/export_content.tsx | 24 +++++++++++++++---- .../public/components/tabs/export/index.tsx | 4 +++- .../public/services/share_menu_manager.tsx | 6 ++++- src/plugins/share/public/types.ts | 2 +- .../csv_download_provider.tsx | 1 + 8 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx index 82444a2c3a631f..617c8c3518f04d 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx @@ -113,7 +113,7 @@ export const reportingCsvShareProvider = ({ defaultMessage="Generate CSV" /> ), - getJobParams, + getJobParams: [{ id: reportType, handler: getJobParams }], createReportingJob: generateReportingJobCSV, reportingAPIClient: apiClient, }); diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 05b59d4bff51a5..aa9e882765b604 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -306,6 +306,7 @@ export const reportingExportModalProvider = ({ tabType: 'Export', jobProviderOptions, reportingAPIClient: apiClient, + reportType: ['printablePdfV2', 'pngV2'], requiresSavedState, helpText: ( ), createReportingJob: generateReportingJobPNGPDF, - getJobParams, + getJobParams: [ + { id: 'pngV2', handler: getJobParams(apiClient, jobProviderOptions, 'pngV2') }, + { + id: 'printablePdfV2', + handler: getJobParams(apiClient, jobProviderOptions, 'printablePdfV2'), + }, + ], layoutOption: objectType === 'dashboard' ? ('print' as const) : undefined, toasts, theme, diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx index f9ca62637a6cbd..74822b180d73d3 100644 --- a/src/plugins/share/public/components/context/index.tsx +++ b/src/plugins/share/public/components/context/index.tsx @@ -7,7 +7,7 @@ */ import { ThemeServiceSetup } from '@kbn/core-theme-browser'; -import { I18nStart } from '@kbn/core/public'; +import { I18nStart, ToastsSetup } from '@kbn/core/public'; import { createContext, useContext } from 'react'; import { AnonymousAccessServiceContract } from '../../../common'; @@ -30,6 +30,7 @@ export interface IShareContext extends ShareContext { isEmbedded: boolean; theme: ThemeServiceSetup; i18n: I18nStart; + toasts: ToastsSetup; } export const ShareTabsContext = createContext(null); diff --git a/src/plugins/share/public/components/tabs/export/export_content.tsx b/src/plugins/share/public/components/tabs/export/export_content.tsx index 95b442694c5ee5..f9f9a738e0ca77 100644 --- a/src/plugins/share/public/components/tabs/export/export_content.tsx +++ b/src/plugins/share/public/components/tabs/export/export_content.tsx @@ -40,7 +40,7 @@ type ExportProps = Pick< 'isDirty' | 'objectId' | 'objectType' | 'onClose' | 'i18n' > & { reportingAPIClient: ReportingAPIClient; - getJobParams: Function; + getJobParams: Array<{ id: string; handler: Function }>; helpText: FormattedMessage; generateReportButton: FormattedMessage; jobProviderOptions?: JobParamsProviderOptions; @@ -48,9 +48,10 @@ type ExportProps = Pick< toasts: ToastsSetup; theme: ThemeServiceSetup; downloadCSVLens: Function; + reportType: string[]; }; -type AllowedExports = 'pngV2' | 'printablePdfV2' | 'csv'; +type AllowedExports = 'pngV2' | 'printablePdfV2' | 'csv_v2' | 'csv_searchsource' | 'csv'; type AppParams = Omit; type Props = ExportProps & { intl: InjectedIntl }; @@ -70,11 +71,15 @@ export const ExportContent = ({ onClose, downloadCSVLens, i18n: i18nStart, + reportType, }: Props) => { + const csvReportTypeCheck = reportType[0]; const isSaved = Boolean(objectId) || !isDirty; const [, setIsStale] = useState(false); const [isCreatingReport, setIsCreatingReport] = useState(false); - const [selectedRadio, setSelectedRadio] = useState('printablePdfV2'); + const [selectedRadio, setSelectedRadio] = useState( + csvReportTypeCheck as AllowedExports + ); const [usePrintLayout, setPrintLayout] = useState(false); const [absoluteUrl, setAbsoluteUrl] = useState(''); const isMounted = useMountedState(); @@ -166,6 +171,7 @@ export const ExportContent = ({ getAbsoluteReportGenerationUrl(); markAsStale(); }, [markAsStale, getAbsoluteReportGenerationUrl]); + const handlePrintLayoutChange = (evt: EuiSwitchEvent) => { setPrintLayout(evt.target.checked); }; @@ -255,8 +261,14 @@ export const ExportContent = ({ if (selectedRadio === 'csv') { return downloadCSVLens(); } + if (csvReportTypeCheck === 'csv_searchsource' || csvReportTypeCheck === 'csv_v2') { + setSelectedRadio(csvReportTypeCheck); + } + // // get the appropriate jobParams for either printablePdfV2 or pngV2 + // const [{handler}] = getJobParams.filter((val) => val.id === selectedRadio) + const decoratedJobParams = reportingAPIClient.getDecoratedJobParams( - getJobParams(false) as unknown as AppParams + getJobParamsImages(false) as unknown as AppParams ); setIsCreatingReport(true); return reportingAPIClient @@ -291,6 +303,7 @@ export const ExportContent = ({ }); if (onClose) { onClose(); + setIsCreatingReport(false); } if (isMounted()) { setIsCreatingReport(false); @@ -298,7 +311,7 @@ export const ExportContent = ({ }) .catch((error) => { toasts.addError(error, { - title: intl.formatMessage({ + title: intl!.formatMessage({ id: 'reporting.modalContent.notification.reportingErrorTitle', defaultMessage: 'Unable to create report', }), @@ -359,6 +372,7 @@ export const ExportContent = ({ options={radioOptions} onChange={(id) => { setSelectedRadio(id as AllowedExports); + getAbsoluteReportGenerationUrl(); }} name="image reporting radio group" idSelected={selectedRadio} diff --git a/src/plugins/share/public/components/tabs/export/index.tsx b/src/plugins/share/public/components/tabs/export/index.tsx index b092675ca4981d..419f1d1932515c 100644 --- a/src/plugins/share/public/components/tabs/export/index.tsx +++ b/src/plugins/share/public/components/tabs/export/index.tsx @@ -29,6 +29,7 @@ function ExportTabContent() { objectId, onClose, i18n: i18nStart, + toasts, } = useShareTabsContext()!; return shareMenuItems.map((shareMenuItem, index) => { @@ -43,9 +44,9 @@ function ExportTabContent() { layoutOption, reportingAPIClient, generateReportButton, - toasts, theme, downloadCSVLens, + reportType, } = shareMenuItem; return ( // @ts-ignore props show undefined because of v1 share design modal needed the props to be optional for congruency with Canvas @@ -65,6 +66,7 @@ function ExportTabContent() { toasts, downloadCSVLens, i18nStart, + reportType, }} key={index} /> diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index e35fa99a38bd0a..d9e149255a8ad0 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -9,7 +9,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { toMountPoint } from '@kbn/react-kibana-mount'; -import { CoreStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; +import { CoreStart, OverlayStart, ThemeServiceStart, ToastsSetup } from '@kbn/core/public'; import { EuiWrappingPopover } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n-react'; @@ -58,6 +58,7 @@ export class ShareMenuManager { overlays: core.overlays, i18n: core.i18n, newVersionEnabled, + toasts: core.notifications.toasts, }); }, }; @@ -92,6 +93,7 @@ export class ShareMenuManager { i18n, isDirty, newVersionEnabled, + toasts, }: ShowShareMenuOptions & { anchorElement: HTMLElement; menuItems: ShareMenuItem[]; @@ -103,6 +105,7 @@ export class ShareMenuManager { i18n: CoreStart['i18n']; isDirty: boolean; newVersionEnabled: boolean; + toasts: ToastsSetup; }) { if (this.isOpen) { onClose(); @@ -174,6 +177,7 @@ export class ShareMenuManager { isEmbedded: allowEmbed, shareMenuItems: menuItems, theme, + toasts, onClose: () => { onClose(); session.close(); diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index 21d6d26d9f171e..53ec39287d120e 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -82,7 +82,7 @@ export interface ShareMenuItem { helpText?: ReactElement; copyURLButton?: { id: string; dataTestSubj: string; label: string }; generateReportButton?: ReactElement; - getJobParams?: Function; + getJobParams?: Array<{ id: string; handler: Function }>; reportingAPIClient?: ReportingAPIClient; jobProviderOptions?: JobParamsProviderOptions; layoutOption?: 'print'; diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index ae8a8855cc409e..319be3ea40d37d 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -136,6 +136,7 @@ export const downloadCsvShareProvider = ({ }); onClose?.(); }, + reportType: ['csv'], }, ] : [