[CANVAS] Moves notify to a canvas service (#63268)

* Moves notify to a canvas service

* Typecheck fix
This commit is contained in:
Corey Robertson 2020-04-24 10:53:27 -04:00 committed by GitHub
parent dfddcdd903
commit 4051c94568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 287 additions and 152 deletions

View file

@ -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<any> | 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<string, any>);
ReactDOM.render(
<KibanaContextProvider services={{ ...plugins, ...coreStart }}>
<KibanaContextProvider services={{ ...plugins, ...coreStart, canvas: canvasServices }}>
<I18nProvider>
<Provider store={canvasStore}>
<App />
@ -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();

View file

@ -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');
}
}

View file

@ -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 */

View file

@ -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<ExpressionAstExpression>(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<typeof mapStateToProps>,
dispatchProps: ReturnType<typeof mapDispatchToProps>,
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<any, any>(
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);

View file

@ -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 = {

View file

@ -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<ComponentProps, OwnProps>(
withKibana,
withState('search', 'setSearch', ''),
withState('customElements', 'setCustomElements', []),
connect(mapStateToProps, mapDispatchToProps, mergeProps)

View file

@ -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<ComponentProps, Pick<Props, 'onClose'>
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<ComponentProps, Pick<Props, 'onClose'>
.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:

View file

@ -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<ComponentProps, {}>(
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<ComponentProps, {}>(
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<ComponentProps, {}>(
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);

View file

@ -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';

View file

@ -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');

View file

@ -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;
}

View file

@ -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 = {

View file

@ -249,7 +249,11 @@ export class WorkpadLoader extends React.PureComponent {
return (
<Fragment>
<WorkpadDropzone onUpload={this.onUpload} disabled={createPending || !canUserWrite}>
<WorkpadDropzone
onUpload={this.onUpload}
disabled={createPending || !canUserWrite}
notify={this.props.notify}
>
<EuiBasicTable
items={rows}
itemId="id"
@ -327,7 +331,7 @@ export class WorkpadLoader extends React.PureComponent {
compressed
className="canvasWorkpad__upload--compressed"
initialPromptText={strings.getFilePickerPlaceholder()}
onChange={([file]) => uploadWorkpad(file, this.onUpload)}
onChange={([file]) => uploadWorkpad(file, this.onUpload, this.props.notify)}
accept="application/json"
disabled={createPending || !canUserWrite}
/>

View file

@ -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);

View file

@ -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 };
};
}

View file

@ -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() });
}
};

View file

@ -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 => {

View file

@ -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<IndexPatternAttributes>('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('');
};

View file

@ -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));
},
};

View file

@ -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;
}
}

View file

@ -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<Service> = (
coreSetup: CoreSetup,
coreStart: CoreStart,
canvasSetupPlugins: CanvasSetupDeps,
canvasStartPlugins: CanvasStartDeps
) => Service;
class CanvasServiceProvider<Service> {
private factory: CanvasServiceFactory<Service>;
private service: Service | undefined;
constructor(factory: CanvasServiceFactory<Service>) {
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> = P extends CanvasServiceProvider<infer T> ? T : never;
export const services = {
notify: new CanvasServiceProvider(notifyServiceFactory),
};
export interface CanvasServices {
notify: ServiceFromProvider<typeof services.notify>;
}
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;

View file

@ -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<NotifyService> = (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));
},
};
};

View file

@ -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));

View file

@ -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(),
});
}