diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index 7ebfdda743a97..f746a24e9b261 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -33,6 +33,9 @@ import { ACTION_VALUE_CLICK } from '../../../../../src/plugins/data/public/actio import { init as initStatsReporter } from './lib/ui_metric'; import { CapabilitiesStrings } from '../i18n'; + +import { startServices, stopServices, services } from './services'; + const { ReadOnlyBadge: strings } = CapabilitiesStrings; let restoreAction: ActionByType | undefined; @@ -51,8 +54,14 @@ export const renderApp = ( { element }: AppMountParameters, canvasStore: Store ) => { + const canvasServices = Object.entries(services).reduce((reduction, [key, provider]) => { + reduction[key] = provider.getService(); + + return reduction; + }, {} as Record); + ReactDOM.render( - + @@ -71,6 +80,8 @@ export const initializeCanvas = async ( startPlugins: CanvasStartDeps, registries: SetupRegistries ) => { + startServices(coreSetup, coreStart, setupPlugins, startPlugins); + // Create Store const canvasStore = await createStore(coreSetup, setupPlugins); @@ -130,6 +141,7 @@ export const initializeCanvas = async ( }; export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDeps) => { + stopServices(); destroyRegistries(); resetInterpreter(); diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js b/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js index 718443fcdd990..4e3920bf34f67 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js @@ -6,7 +6,7 @@ import { ErrorStrings } from '../../../i18n'; import * as workpadService from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; +import { notifyService } from '../../services'; import { getBaseBreadcrumb, getWorkpadBreadcrumb, setBreadcrumb } from '../../lib/breadcrumbs'; import { getDefaultWorkpad } from '../../state/defaults'; import { setWorkpad } from '../../state/actions/workpad'; @@ -33,7 +33,9 @@ export const routes = [ dispatch(resetAssets()); router.redirectTo('loadWorkpad', { id: newWorkpad.id, page: 1 }); } catch (err) { - notify.error(err, { title: strings.getCreateFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getCreateFailureErrorMessage() }); router.redirectTo('home'); } }, @@ -59,7 +61,9 @@ export const routes = [ // reset transient properties when changing workpads dispatch(setZoomScale(1)); } catch (err) { - notify.error(err, { title: strings.getLoadFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getLoadFailureErrorMessage() }); return router.redirectTo('home'); } } diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx index 479e9287d7adf..c27f0c002c3d1 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx @@ -15,7 +15,7 @@ import { AssetModal } from './asset_modal'; const { AssetManager: strings } = ComponentStrings; -interface Props { +export interface Props { /** A list of assets, if available */ assetValues: AssetType[]; /** Function to invoke when an asset is selected to be added as an element to the workpad */ diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.js b/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.ts similarity index 63% rename from x-pack/legacy/plugins/canvas/public/components/asset_manager/index.js rename to x-pack/legacy/plugins/canvas/public/components/asset_manager/index.ts index 6c05eec0c3c09..3fd34d6d2a9bb 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/index.ts @@ -8,29 +8,36 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { set, get } from 'lodash'; import { fromExpression, toExpression } from '@kbn/interpreter/common'; -import { notify } from '../../lib/notify'; import { getAssets } from '../../state/selectors/assets'; +// @ts-ignore Untyped local import { removeAsset, createAsset } from '../../state/actions/assets'; +// @ts-ignore Untyped local import { elementsRegistry } from '../../lib/elements_registry'; +// @ts-ignore Untyped local import { addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; import { encode } from '../../../common/lib/dataurl'; import { getId } from '../../lib/get_id'; +// @ts-ignore Untyped Local import { findExistingAsset } from '../../lib/find_existing_asset'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; -import { AssetManager as Component } from './asset_manager'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { WithKibanaProps } from '../../'; +import { AssetManager as Component, Props as AssetManagerProps } from './asset_manager'; -const mapStateToProps = state => ({ +import { State, ExpressionAstExpression, AssetType } from '../../../types'; + +const mapStateToProps = (state: State) => ({ assets: getAssets(state), selectedPage: getSelectedPage(state), }); -const mapDispatchToProps = dispatch => ({ - onAddImageElement: pageId => assetId => { +const mapDispatchToProps = (dispatch: (action: any) => void) => ({ + onAddImageElement: (pageId: string) => (assetId: string) => { const imageElement = elementsRegistry.get('image'); const elementAST = fromExpression(imageElement.expression); const selector = ['chain', '0', 'arguments', 'dataurl']; - const subExp = [ + const subExp: ExpressionAstExpression[] = [ { type: 'expression', chain: [ @@ -44,11 +51,11 @@ const mapDispatchToProps = dispatch => ({ ], }, ]; - const newAST = set(elementAST, selector, subExp); + const newAST = set(elementAST, selector, subExp); imageElement.expression = toExpression(newAST); dispatch(addElement(pageId, imageElement)); }, - onAssetAdd: (type, content) => { + onAssetAdd: (type: string, content: string) => { // make the ID here and pass it into the action const assetId = getId('asset'); dispatch(createAsset(type, content, assetId)); @@ -56,10 +63,14 @@ const mapDispatchToProps = dispatch => ({ // then return the id, so the caller knows the id that will be created return assetId; }, - onAssetDelete: assetId => dispatch(removeAsset(assetId)), + onAssetDelete: (assetId: string) => dispatch(removeAsset(assetId)), }); -const mergeProps = (stateProps, dispatchProps, ownProps) => { +const mergeProps = ( + stateProps: ReturnType, + dispatchProps: ReturnType, + ownProps: AssetManagerProps +) => { const { assets, selectedPage } = stateProps; const { onAssetAdd } = dispatchProps; const assetValues = Object.values(assets); // pull values out of assets object @@ -70,16 +81,16 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { onAddImageElement: dispatchProps.onAddImageElement(stateProps.selectedPage), selectedPage, assetValues, - onAssetAdd: file => { + onAssetAdd: (file: File) => { const [type, subtype] = get(file, 'type', '').split('/'); if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { return encode(file).then(dataurl => { - const type = 'dataurl'; - const existingId = findExistingAsset(type, dataurl, assetValues); + const dataurlType = 'dataurl'; + const existingId = findExistingAsset(dataurlType, dataurl, assetValues); if (existingId) { return existingId; } - return onAssetAdd(type, dataurl); + return onAssetAdd(dataurlType, dataurl); }); } @@ -88,7 +99,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { }; }; -export const AssetManager = compose( +export const AssetManager = compose( connect(mapStateToProps, mapDispatchToProps, mergeProps), - withProps({ onAssetCopy: asset => notify.success(`Copied '${asset.id}' to clipboard`) }) + withKibana, + withProps(({ kibana }: WithKibanaProps) => ({ + onAssetCopy: (asset: AssetType) => + kibana.services.canvas.notify.success(`Copied '${asset.id}' to clipboard`), + })) )(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js b/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js index 68c3ba79dd488..cc234d2287c0c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/render_with_fn/index.js @@ -7,7 +7,7 @@ import { compose, withProps, withPropsOnChange } from 'recompose'; import PropTypes from 'prop-types'; import isEqual from 'react-fast-compare'; -import { notify } from '../../lib/notify'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { RenderWithFn as Component } from './render_with_fn'; import { ElementHandlers } from './lib/handlers'; @@ -19,9 +19,10 @@ export const RenderWithFn = compose( handlers: Object.assign(new ElementHandlers(), handlers), }) ), - withProps({ - onError: notify.error, - }) + withKibana, + withProps(props => ({ + onError: props.kibana.services.canvas.notify.error, + })) )(Component); RenderWithFn.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts index bb088ad4e0de1..60d1d7462daa9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts @@ -11,8 +11,8 @@ import { camelCase } from 'lodash'; // @ts-ignore Untyped local import { cloneSubgraphs } from '../../lib/clone_subgraphs'; import * as customElementService from '../../lib/custom_element_service'; -// @ts-ignore Untyped local -import { notify } from '../../lib/notify'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { WithKibanaProps } from '../../'; // @ts-ignore Untyped local import { selectToplevelNodes } from '../../state/actions/transient'; // @ts-ignore Untyped local @@ -64,7 +64,7 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ const mergeProps = ( stateProps: StateProps, dispatchProps: DispatchProps, - ownProps: OwnPropsWithState + ownProps: OwnPropsWithState & WithKibanaProps ): ComponentProps => { const { pageId } = stateProps; const { onClose, search, setCustomElements } = ownProps; @@ -92,7 +92,9 @@ const mergeProps = ( try { await findCustomElements(); } catch (err) { - notify.error(err, { title: `Couldn't find custom elements` }); + ownProps.kibana.services.canvas.notify.error(err, { + title: `Couldn't find custom elements`, + }); } }, // remove custom element @@ -101,7 +103,9 @@ const mergeProps = ( await customElementService.remove(id); await findCustomElements(); } catch (err) { - notify.error(err, { title: `Couldn't delete custom elements` }); + ownProps.kibana.services.canvas.notify.error(err, { + title: `Couldn't delete custom elements`, + }); } }, // update custom element @@ -115,13 +119,16 @@ const mergeProps = ( }); await findCustomElements(); } catch (err) { - notify.error(err, { title: `Couldn't update custom elements` }); + ownProps.kibana.services.canvas.notify.error(err, { + title: `Couldn't update custom elements`, + }); } }, }; }; export const SavedElementsModal = compose( + withKibana, withState('search', 'setSearch', ''), withState('customElements', 'setCustomElements', []), connect(mapStateToProps, mapDispatchToProps, mergeProps) diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts index 6ab419656a7ee..4377635acac88 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts @@ -12,7 +12,6 @@ import { getRenderedWorkpadExpressions, } from '../../../../state/selectors/workpad'; // @ts-ignore Untyped local -import { notify } from '../../../../lib/notify'; import { downloadRenderedWorkpad, downloadRuntime, @@ -70,7 +69,7 @@ export const ShareWebsiteFlyout = compose unsupportedRenderers, onClose, onCopy: () => { - notify.info(strings.getCopyShareConfigMessage()); + kibana.services.canvas.notify.info(strings.getCopyShareConfigMessage()); }, onDownload: type => { switch (type) { @@ -86,7 +85,9 @@ export const ShareWebsiteFlyout = compose .post(`${basePath}${API_ROUTE_SHAREABLE_ZIP}`, JSON.stringify(renderedWorkpad)) .then(blob => downloadZippedRuntime(blob.data)) .catch((err: Error) => { - notify.error(err, { title: strings.getShareableZipErrorTitle(workpad.name) }); + kibana.services.canvas.notify.error(err, { + title: strings.getShareableZipErrorTitle(workpad.name), + }); }); return; default: diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts index 6b51e5d999e8b..d6565f0e43db7 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/index.ts @@ -8,8 +8,6 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { jobCompletionNotifications } from '../../../../../../../plugins/reporting/public'; import { getWorkpad, getPages } from '../../../state/selectors/workpad'; -// @ts-ignore Untyped local -import { notify } from '../../../lib/notify'; import { getWindow } from '../../../lib/get_window'; import { downloadWorkpad } from '../../../lib/download_workpad'; import { ShareMenu as Component, Props as ComponentProps } from './share_menu'; @@ -59,10 +57,10 @@ export const ShareMenu = compose( onCopy: type => { switch (type) { case 'pdf': - notify.info(strings.getCopyPDFMessage()); + kibana.services.canvas.notify.info(strings.getCopyPDFMessage()); break; case 'reportingConfig': - notify.info(strings.getCopyReportingConfigMessage()); + kibana.services.canvas.notify.info(strings.getCopyReportingConfigMessage()); break; default: throw new Error(strings.getUnknownExportErrorMessage(type)); @@ -73,7 +71,7 @@ export const ShareMenu = compose( case 'pdf': return createPdf(workpad, { pageCount }, kibana.services.http.basePath) .then(({ data }: { data: { job: { id: string } } }) => { - notify.info(strings.getExportPDFMessage(), { + kibana.services.canvas.notify.info(strings.getExportPDFMessage(), { title: strings.getExportPDFTitle(workpad.name), }); @@ -81,7 +79,9 @@ export const ShareMenu = compose( jobCompletionNotifications.add(data.job.id); }) .catch((err: Error) => { - notify.error(err, { title: strings.getExportPDFErrorTitle(workpad.name) }); + kibana.services.canvas.notify.error(err, { + title: strings.getExportPDFErrorTitle(workpad.name), + }); }); case 'json': downloadWorkpad(workpad.id); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts index c5aa8278ecf55..eee613183639c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts @@ -9,8 +9,6 @@ import { compose, withHandlers } from 'recompose'; import { Dispatch } from 'redux'; import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/'; import { zoomHandlerCreators } from '../../../lib/app_handler_creators'; -// @ts-ignore Untyped local -import { notify } from '../../../lib/notify'; import { State, CanvasWorkpadBoundingBox } from '../../../../types'; // @ts-ignore Untyped local import { fetchAllRenderables } from '../../../state/actions/elements'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js index 226ad420535bd..9379379e54d97 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js @@ -9,7 +9,6 @@ import { connect } from 'react-redux'; import { compose, withState, getContext, withHandlers, withProps } from 'recompose'; import moment from 'moment'; import * as workpadService from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; import { canUserWrite } from '../../state/selectors/app'; import { getWorkpad } from '../../state/selectors/workpad'; import { getId } from '../../lib/get_id'; @@ -32,7 +31,11 @@ export const WorkpadLoader = compose( }), connect(mapStateToProps), withState('workpads', 'setWorkpads', null), - withHandlers({ + withKibana, + withProps(({ kibana }) => ({ + notify: kibana.services.canvas.notify, + })), + withHandlers(({ kibana }) => ({ // Workpad creation via navigation createWorkpad: props => async workpad => { // workpad data uploaded, create and load it @@ -41,7 +44,9 @@ export const WorkpadLoader = compose( await workpadService.create(workpad); props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }); } catch (err) { - notify.error(err, { title: errors.getUploadFailureErrorMessage() }); + kibana.services.canvas.notify.error(err, { + title: errors.getUploadFailureErrorMessage(), + }); } return; } @@ -55,7 +60,7 @@ export const WorkpadLoader = compose( const workpads = await workpadService.find(text); setWorkpads(workpads); } catch (err) { - notify.error(err, { title: errors.getFindFailureErrorMessage() }); + kibana.services.canvas.notify.error(err, { title: errors.getFindFailureErrorMessage() }); } }, @@ -71,7 +76,7 @@ export const WorkpadLoader = compose( await workpadService.create(workpad); props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }); } catch (err) { - notify.error(err, { title: errors.getCloneFailureErrorMessage() }); + kibana.services.canvas.notify.error(err, { title: errors.getCloneFailureErrorMessage() }); } }, @@ -92,7 +97,7 @@ export const WorkpadLoader = compose( return Promise.all(removeWorkpads).then(results => { let redirectHome = false; - const [passes, errors] = results.reduce( + const [passes, errored] = results.reduce( ([passes, errors], result) => { if (result.id === loadedWorkpad && !result.err) { redirectHome = true; @@ -116,8 +121,8 @@ export const WorkpadLoader = compose( workpads: remainingWorkpads, }; - if (errors.length > 0) { - notify.error(errors.getDeleteFailureErrorMessage()); + if (errored.length > 0) { + kibana.services.canvas.notify.error(errors.getDeleteFailureErrorMessage()); } setWorkpads(workpadState); @@ -126,11 +131,10 @@ export const WorkpadLoader = compose( props.router.navigateTo('home'); } - return errors.map(({ id }) => id); + return errored.map(({ id }) => id); }); }, - }), - withKibana, + })), withProps(props => ({ formatDate: date => { const dateFormat = props.kibana.services.uiSettings.get('dateFormat'); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js index a7fcf7449ce40..fd25fb03a9ca9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/upload_workpad.js @@ -6,12 +6,11 @@ import { get } from 'lodash'; import { getId } from '../../lib/get_id'; -import { notify } from '../../lib/notify'; import { ErrorStrings } from '../../../i18n'; const { WorkpadFileUpload: errors } = ErrorStrings; -export const uploadWorkpad = (file, onUpload) => { +export const uploadWorkpad = (file, onUpload, notify) => { if (!file) { return; } diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js index ac716d37f532d..ab0c064d5ef07 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js @@ -6,7 +6,6 @@ import PropTypes from 'prop-types'; import { compose, withHandlers } from 'recompose'; -import { notify } from '../../../lib/notify'; import { uploadWorkpad } from '../upload_workpad'; import { ErrorStrings } from '../../../../i18n'; import { WorkpadDropzone as Component } from './workpad_dropzone'; @@ -14,7 +13,7 @@ import { WorkpadDropzone as Component } from './workpad_dropzone'; const { WorkpadFileUpload: errors } = ErrorStrings; export const WorkpadDropzone = compose( - withHandlers({ + withHandlers(({ notify }) => ({ onDropAccepted: ({ onUpload }) => ([file]) => uploadWorkpad(file, onUpload), onDropRejected: () => ([file]) => { notify.warning(errors.getAcceptJSONOnlyErrorMessage(), { @@ -23,7 +22,7 @@ export const WorkpadDropzone = compose( : errors.getFileUploadFailureWithoutFileNameErrorMessage(), }); }, - }) + })) )(Component); WorkpadDropzone.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 04378e5603c4b..cb5af27144c7f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -249,7 +249,11 @@ export class WorkpadLoader extends React.PureComponent { return ( - + uploadWorkpad(file, this.onUpload)} + onChange={([file]) => uploadWorkpad(file, this.onUpload, this.props.notify)} accept="application/json" disabled={createPending || !canUserWrite} /> diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js index 139d0f283bf1a..1890ca1f9d2d6 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/index.js @@ -7,9 +7,9 @@ import PropTypes from 'prop-types'; import { compose, getContext, withHandlers, withProps } from 'recompose'; import * as workpadService from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; import { getId } from '../../lib/get_id'; import { templatesRegistry } from '../../lib/templates_registry'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { WorkpadTemplates as Component } from './workpad_templates'; export const WorkpadTemplates = compose( @@ -19,7 +19,8 @@ export const WorkpadTemplates = compose( withProps(() => ({ templates: templatesRegistry.toJS(), })), - withHandlers({ + withKibana, + withHandlers(({ kibana }) => ({ // Clone workpad given an id cloneWorkpad: props => workpad => { workpad.id = getId('workpad'); @@ -31,7 +32,9 @@ export const WorkpadTemplates = compose( return workpadService .create(workpad) .then(() => props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 })) - .catch(err => notify.error(err, { title: `Couldn't clone workpad template` })); + .catch(err => + kibana.services.canvas.notify.error(err, { title: `Couldn't clone workpad template` }) + ); }, - }) + })) )(Component); diff --git a/x-pack/legacy/plugins/canvas/public/index.ts b/x-pack/legacy/plugins/canvas/public/index.ts index b8358bfe022e6..b053920fec6e4 100644 --- a/x-pack/legacy/plugins/canvas/public/index.ts +++ b/x-pack/legacy/plugins/canvas/public/index.ts @@ -10,6 +10,7 @@ import { CoreStart, } from '../../../../../src/core/public'; import { CanvasSetup, CanvasStart, CanvasSetupDeps, CanvasStartDeps, CanvasPlugin } from './plugin'; +import { CanvasServices } from './services'; export const plugin: PluginInitializer< CanvasSetup, @@ -22,7 +23,7 @@ export const plugin: PluginInitializer< export interface WithKibanaProps { kibana: { - services: CoreStart & CanvasStartDeps; + services: CoreStart & CanvasStartDeps & { canvas: CanvasServices }; }; } diff --git a/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts b/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts index e4866641fd9e1..fb038d8b6ace2 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/download_workpad.ts @@ -6,8 +6,7 @@ import fileSaver from 'file-saver'; import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../common/lib/constants'; import { ErrorStrings } from '../../i18n'; -// @ts-ignore untyped local -import { notify } from './notify'; +import { notifyService } from '../services'; // @ts-ignore untyped local import * as workpadService from './workpad_service'; import { CanvasRenderedWorkpad } from '../../shareable_runtime/types'; @@ -20,7 +19,7 @@ export const downloadWorkpad = async (workpadId: string) => { const jsonBlob = new Blob([JSON.stringify(workpad)], { type: 'application/json' }); fileSaver.saveAs(jsonBlob, `canvas-workpad-${workpad.name}-${workpad.id}.json`); } catch (err) { - notify.error(err, { title: strings.getDownloadFailureErrorMessage() }); + notifyService.getService().error(err, { title: strings.getDownloadFailureErrorMessage() }); } }; @@ -32,7 +31,9 @@ export const downloadRenderedWorkpad = async (renderedWorkpad: CanvasRenderedWor `canvas-embed-workpad-${renderedWorkpad.name}-${renderedWorkpad.id}.json` ); } catch (err) { - notify.error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() }); } }; @@ -42,7 +43,9 @@ export const downloadRuntime = async (basePath: string) => { window.open(path); return; } catch (err) { - notify.error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() }); } }; @@ -51,6 +54,8 @@ export const downloadZippedRuntime = async (data: any) => { const zip = new Blob([data], { type: 'octet/stream' }); fileSaver.saveAs(zip, 'canvas-workpad-embed.zip'); } catch (err) { - notify.error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() }); + notifyService + .getService() + .error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() }); } }; diff --git a/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts index bce6bc51b366c..a8744b4820842 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/element_handler_creators.ts @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Http2ServerResponse } from 'http2'; import { camelCase } from 'lodash'; // @ts-ignore unconverted local file import { getClipboardData, setClipboardData } from './clipboard'; // @ts-ignore unconverted local file import { cloneSubgraphs } from './clone_subgraphs'; -// @ts-ignore unconverted local file -import { notify } from './notify'; +import { notifyService } from '../services'; import * as customElementService from './custom_element_service'; import { getId } from './get_id'; import { PositionedElement } from '../../types'; @@ -86,15 +84,17 @@ export const basicHandlerCreators = { customElementService .create(customElement) .then(() => - notify.success( - `Custom element '${customElement.displayName || customElement.id}' was saved`, - { - 'data-test-subj': 'canvasCustomElementCreate-success', - } - ) + notifyService + .getService() + .success( + `Custom element '${customElement.displayName || customElement.id}' was saved`, + { + 'data-test-subj': 'canvasCustomElementCreate-success', + } + ) ) - .catch((result: Http2ServerResponse) => - notify.warning(result, { + .catch((error: Error) => + notifyService.getService().warning(error, { title: `Custom element '${customElement.displayName || customElement.id}' was not saved`, }) @@ -138,13 +138,13 @@ export const clipboardHandlerCreators = { if (selectedNodes.length) { setClipboardData({ selectedNodes }); removeNodes(selectedNodes.map(extractId), pageId); - notify.success('Cut element to clipboard'); + notifyService.getService().success('Cut element to clipboard'); } }, copyNodes: ({ selectedNodes }: Props) => (): void => { if (selectedNodes.length) { setClipboardData({ selectedNodes }); - notify.success('Copied element to clipboard'); + notifyService.getService().success('Copied element to clipboard'); } }, pasteNodes: ({ insertNodes, pageId, selectToplevelNodes }: Props) => (): void => { diff --git a/x-pack/legacy/plugins/canvas/public/lib/es_service.ts b/x-pack/legacy/plugins/canvas/public/lib/es_service.ts index 32f4fe041423c..6aa4968f29155 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/es_service.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/es_service.ts @@ -10,8 +10,7 @@ import { API_ROUTE } from '../../common/lib/constants'; // @ts-ignore untyped local import { fetch } from '../../common/lib/fetch'; import { ErrorStrings } from '../../i18n'; -// @ts-ignore untyped local -import { notify } from './notify'; +import { notifyService } from '../services'; import { getCoreStart } from '../legacy'; const { esService: strings } = ErrorStrings; @@ -38,7 +37,7 @@ export const getFields = (index = '_all') => { .sort() ) .catch((err: Error) => - notify.error(err, { + notifyService.getService().error(err, { title: strings.getFieldsFetchErrorMessage(index), }) ); @@ -57,7 +56,9 @@ export const getIndices = () => return savedObject.attributes.title; }); }) - .catch((err: Error) => notify.error(err, { title: strings.getIndicesFetchErrorMessage() })); + .catch((err: Error) => + notifyService.getService().error(err, { title: strings.getIndicesFetchErrorMessage() }) + ); export const getDefaultIndex = () => { const defaultIndexId = getAdvancedSettings().get('defaultIndex'); @@ -66,6 +67,10 @@ export const getDefaultIndex = () => { ? getSavedObjectsClient() .get('index-pattern', defaultIndexId) .then(defaultIndex => defaultIndex.attributes.title) - .catch(err => notify.error(err, { title: strings.getDefaultIndexFetchErrorMessage() })) + .catch(err => + notifyService + .getService() + .error(err, { title: strings.getDefaultIndexFetchErrorMessage() }) + ) : Promise.resolve(''); }; diff --git a/x-pack/legacy/plugins/canvas/public/lib/notify.js b/x-pack/legacy/plugins/canvas/public/lib/notify.js deleted file mode 100644 index 64876a02a3c64..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/lib/notify.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; -import { getCoreStart, getStartPlugins } from '../legacy'; - -const getToastNotifications = function() { - return getCoreStart().notifications.toasts; -}; - -const formatMsg = function(...args) { - return getStartPlugins().__LEGACY.formatMsg(...args); -}; - -const getToast = (err, opts = {}) => { - const errData = get(err, 'response') || err; - const errMsg = formatMsg(errData); - const { title, ...rest } = opts; - let text = null; - - if (title) { - text = errMsg; - } - - return { - ...rest, - title: title || errMsg, - text, - }; -}; - -export const notify = { - /* - * @param {(string | Object)} err: message or Error object - * @param {Object} opts: option to override toast title or icon, see https://github.com/elastic/kibana/blob/master/src/legacy/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md - */ - error(err, opts) { - getToastNotifications().addDanger(getToast(err, opts)); - }, - warning(err, opts) { - getToastNotifications().addWarning(getToast(err, opts)); - }, - info(err, opts) { - getToastNotifications().add(getToast(err, opts)); - }, - success(err, opts) { - getToastNotifications().addSuccess(getToast(err, opts)); - }, -}; diff --git a/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts index fbbaf0ccf280e..df338f40e08d9 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts @@ -6,8 +6,7 @@ import { fromExpression, getType } from '@kbn/interpreter/common'; import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public'; -// @ts-ignore Untyped Local -import { notify } from './notify'; +import { notifyService } from '../services'; import { CanvasStartDeps, CanvasSetupDeps } from '../plugin'; @@ -85,7 +84,7 @@ export async function runInterpreter( throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`); } catch (err) { - notify.error(err); + notifyService.getService().error(err); throw err; } } diff --git a/x-pack/legacy/plugins/canvas/public/services/index.ts b/x-pack/legacy/plugins/canvas/public/services/index.ts new file mode 100644 index 0000000000000..12c0a687bf308 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/services/index.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart } from '../../../../../../src/core/public'; +import { CanvasSetupDeps, CanvasStartDeps } from '../plugin'; +import { notifyServiceFactory } from './notify'; + +export type CanvasServiceFactory = ( + coreSetup: CoreSetup, + coreStart: CoreStart, + canvasSetupPlugins: CanvasSetupDeps, + canvasStartPlugins: CanvasStartDeps +) => Service; + +class CanvasServiceProvider { + private factory: CanvasServiceFactory; + private service: Service | undefined; + + constructor(factory: CanvasServiceFactory) { + this.factory = factory; + } + + start( + coreSetup: CoreSetup, + coreStart: CoreStart, + canvasSetupPlugins: CanvasSetupDeps, + canvasStartPlugins: CanvasStartDeps + ) { + this.service = this.factory(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins); + } + + getService(): Service { + if (!this.service) { + throw new Error('Service not ready'); + } + + return this.service; + } + + stop() { + this.service = undefined; + } +} + +export type ServiceFromProvider

= P extends CanvasServiceProvider ? T : never; + +export const services = { + notify: new CanvasServiceProvider(notifyServiceFactory), +}; + +export interface CanvasServices { + notify: ServiceFromProvider; +} + +export const startServices = ( + coreSetup: CoreSetup, + coreStart: CoreStart, + canvasSetupPlugins: CanvasSetupDeps, + canvasStartPlugins: CanvasStartDeps +) => { + Object.entries(services).forEach(([key, provider]) => + provider.start(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins) + ); +}; + +export const stopServices = () => { + Object.entries(services).forEach(([key, provider]) => provider.stop()); +}; + +export const { notify: notifyService } = services; diff --git a/x-pack/legacy/plugins/canvas/public/services/notify.ts b/x-pack/legacy/plugins/canvas/public/services/notify.ts new file mode 100644 index 0000000000000..3e18e2178a818 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/services/notify.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { CanvasServiceFactory } from '.'; +import { formatMsg } from '../../../../../../src/plugins/kibana_legacy/public'; +import { ToastInputFields } from '../../../../../../src/core/public'; + +const getToast = (err: Error | string, opts: ToastInputFields = {}) => { + const errData = (get(err, 'response') || err) as Error | string; + const errMsg = formatMsg(errData); + const { title, ...rest } = opts; + let text; + + if (title) { + text = errMsg; + } + + return { + ...rest, + title: title || errMsg, + text, + }; +}; + +interface NotifyService { + error: (err: string | Error, opts?: ToastInputFields) => void; + warning: (err: string | Error, opts?: ToastInputFields) => void; + info: (err: string | Error, opts?: ToastInputFields) => void; + success: (err: string | Error, opts?: ToastInputFields) => void; +} + +export const notifyServiceFactory: CanvasServiceFactory = (setup, start) => { + const toasts = start.notifications.toasts; + + return { + /* + * @param {(string | Object)} err: message or Error object + * @param {Object} opts: option to override toast title or icon, see https://github.com/elastic/kibana/blob/master/src/legacy/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md + */ + error(err, opts) { + toasts.addDanger(getToast(err, opts)); + }, + warning(err, opts) { + toasts.addWarning(getToast(err, opts)); + }, + info(err, opts) { + toasts.add(getToast(err, opts)); + }, + success(err, opts) { + toasts.addSuccess(getToast(err, opts)); + }, + }; +}; diff --git a/x-pack/legacy/plugins/canvas/public/state/actions/elements.js b/x-pack/legacy/plugins/canvas/public/state/actions/elements.js index 1798aaab22f06..f4a3393b8962d 100644 --- a/x-pack/legacy/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/legacy/plugins/canvas/public/state/actions/elements.js @@ -13,9 +13,9 @@ import { getPages, getNodeById, getNodes, getSelectedPageIndex } from '../select import { getValue as getResolvedArgsValue } from '../selectors/resolved_args'; import { getDefaultElement } from '../defaults'; import { ErrorStrings } from '../../../i18n'; -import { notify } from '../../lib/notify'; import { runInterpreter, interpretAst } from '../../lib/run_interpreter'; import { subMultitree } from '../../lib/aeroelastic/functional'; +import { services } from '../../services'; import { selectToplevelNodes } from './transient'; import * as args from './resolved_args'; @@ -134,7 +134,7 @@ const fetchRenderableWithContextFn = ({ dispatch }, element, ast, context) => { dispatch(getAction(renderable)); }) .catch(err => { - notify.error(err); + services.notify.getService().error(err); dispatch(getAction(err)); }); }; @@ -176,7 +176,7 @@ export const fetchAllRenderables = createThunk( return runInterpreter(ast, null, { castToRender: true }) .then(renderable => ({ path: argumentPath, value: renderable })) .catch(err => { - notify.error(err); + services.notify.getService().error(err); return { path: argumentPath, value: err }; }); }); @@ -293,7 +293,7 @@ const setAst = createThunk('setAst', ({ dispatch }, ast, element, pageId, doRend const expression = toExpression(ast); dispatch(setExpression(expression, element.id, pageId, doRender)); } catch (err) { - notify.error(err); + services.notify.getService().error(err); // TODO: remove this, may have been added just to cause a re-render, but why? dispatch(setExpression(element.expression, element.id, pageId, doRender)); diff --git a/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js b/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js index bcbfc3544981a..a197cdf893244 100644 --- a/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js +++ b/x-pack/legacy/plugins/canvas/public/state/middleware/es_persist.js @@ -14,7 +14,7 @@ import { setAssets, resetAssets } from '../actions/assets'; import * as transientActions from '../actions/transient'; import * as resolvedArgsActions from '../actions/resolved_args'; import { update, updateAssets, updateWorkpad } from '../../lib/workpad_service'; -import { notify } from '../../lib/notify'; +import { services } from '../../services'; import { canUserWrite } from '../selectors/app'; const { esPersist: strings } = ErrorStrings; @@ -62,15 +62,15 @@ export const esPersistMiddleware = ({ getState }) => { const statusCode = err.response && err.response.status; switch (statusCode) { case 400: - return notify.error(err.response, { + return services.notify.getService().error(err.response, { title: strings.getSaveFailureTitle(), }); case 413: - return notify.error(strings.getTooLargeErrorMessage(), { + return services.notify.getService().error(strings.getTooLargeErrorMessage(), { title: strings.getSaveFailureTitle(), }); default: - return notify.error(err, { + return services.notify.getService().error(err, { title: strings.getUpdateFailureTitle(), }); }