mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[CANVAS] Moves notify to a canvas service (#63268)
* Moves notify to a canvas service * Typecheck fix
This commit is contained in:
parent
dfddcdd903
commit
4051c94568
24 changed files with 287 additions and 152 deletions
|
@ -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();
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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() });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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('');
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
73
x-pack/legacy/plugins/canvas/public/services/index.ts
Normal file
73
x-pack/legacy/plugins/canvas/public/services/index.ts
Normal 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;
|
57
x-pack/legacy/plugins/canvas/public/services/notify.ts
Normal file
57
x-pack/legacy/plugins/canvas/public/services/notify.ts
Normal 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));
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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));
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue