Skip to content

Commit

Permalink
Adds save and return flow to Canvas
Browse files Browse the repository at this point in the history
Updates existing embeddable with updates on edit

Select incoming embeddable element on load

Fixed ts errors

Added use incoming embeddable hook

Fixed eslint errors
  • Loading branch information
cqliu1 committed Sep 27, 2021
1 parent 180f47e commit 2657a68
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { getFunctionHelp } from '../../../i18n';
import { SavedObjectReference } from '../../../../../../src/core/types';
import { getQueryFilters } from '../../../common/lib/build_embeddable_filters';
import { decode } from '../../../common/lib/embeddable_dataurl';
import { decode, encode } from '../../../common/lib/embeddable_dataurl';

interface Arguments {
config: string;
Expand All @@ -32,7 +32,7 @@ const defaultTimeRange = {
export type EmbeddableInput = Input & {
timeRange?: TimeRange;
filters?: Filter[];
savedObjectId: string;
savedObjectId?: string;
};

const baseEmbeddableInput = {
Expand Down Expand Up @@ -73,7 +73,6 @@ export function embeddable(): ExpressionFunctionDefinition<
type: EmbeddableExpressionType,
fn: (input, args) => {
const filters = input ? input.and : [];

const embeddableInput = decode(args.config) as EmbeddableInput;

return {
Expand All @@ -89,28 +88,29 @@ export function embeddable(): ExpressionFunctionDefinition<
},

extract(state) {
const input = decode(state.config[0] as string);
const refName = 'embeddable.id';
const refType = 'embeddable.embeddableType';

const references: SavedObjectReference[] = [
{
name: refName,
type: refType,
id: state.id[0] as string,
type: state.type[0] as string,
id: input.savedObjectId as string,
},
];

return {
state: {
...state,
id: [refName],
},
state,
references,
};
},

inject(state, references) {
const reference = references.find((ref) => ref.name === 'embeddable.id');
if (reference) {
state.id[0] = reference.id;
const input = decode(state.config[0] as string);
input.savedObjectId = reference.id;
state.config[0] = encode(input);
}
return state;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import {
IEmbeddable,
EmbeddableFactory,
EmbeddableFactoryNotFoundError,
isErrorEmbeddable,
} from '../../../../../../src/plugins/embeddable/public';
import { EmbeddableExpression } from '../../expression_types/embeddable';
import { RendererStrings } from '../../../i18n';
import { embeddableInputToExpression } from './embeddable_input_to_expression';
import { EmbeddableInput } from '../../expression_types';
import { RendererFactory } from '../../../types';
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';
import { EmbeddableInput } from '../../functions/external/embeddable';

const { embeddable: strings } = RendererStrings;

Expand Down Expand Up @@ -71,16 +72,27 @@ export const embeddableRendererFactory = (
throw new EmbeddableFactoryNotFoundError(embeddableType);
}

const embeddablePromise = factory
.createFromSavedObject(input.id, input)
.then((embeddable) => {
// stores embeddable in registrey
embeddablesRegistry[uniqueId] = embeddable;
return embeddable;
});
embeddablesRegistry[uniqueId] = embeddablePromise;

const embeddableObject = await (async () => embeddablePromise)();
const embeddableInput = { ...input, id: uniqueId };

const embeddablePromise = input.savedObjectId
? factory
.createFromSavedObject(input.savedObjectId, embeddableInput)
.then((embeddable) => {
// stores embeddable in registrey
embeddablesRegistry[uniqueId] = embeddable;
return embeddable;
})
: factory.create(embeddableInput).then((embeddable) => {
if (!embeddable || isErrorEmbeddable(embeddable)) {
return;
}
// stores embeddable in registry
embeddablesRegistry[uniqueId] = embeddable as IEmbeddable;
return embeddable;
});
embeddablesRegistry[uniqueId] = embeddablePromise as Promise<IEmbeddable>;

const embeddableObject = (await (async () => embeddablePromise)()) as IEmbeddable;

const palettes = await plugins.charts.palettes.getPalettes();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
*/

export { useDownloadWorkpad, useDownloadRenderedWorkpad } from './use_download_workpad';

export { useIncomingEmbeddable } from './use_incoming_embeddable';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { CANVAS_APP } from '../../../../common/lib';
import { encode } from '../../../../common/lib/embeddable_dataurl';
import { useEmbeddablesService, useLabsService } from '../../../services';
// @ts-expect-error unconverted file
import { addElement } from '../../../state/actions/elements';
// @ts-expect-error unconverted file
import { selectToplevelNodes } from '../../../state/actions/transient';

import {
updateEmbeddableExpression,
fetchEmbeddableRenderable,
} from '../../../state/actions/embeddable';
import { clearValue } from '../../../state/actions/resolved_args';

export const useIncomingEmbeddable = (pageId: string) => {
const embeddablesService = useEmbeddablesService();
const labsService = useLabsService();
const dispatch = useDispatch();
const isByValueEnabled = labsService.isProjectEnabled('labs:canvas:byValueEmbeddable');
const stateTransferService = embeddablesService.getStateTransfer();

// fetch incoming embeddable from state transfer service.
const incomingEmbeddable = stateTransferService.getIncomingEmbeddablePackage(CANVAS_APP, true);

useEffect(() => {
if (isByValueEnabled && incomingEmbeddable) {
const { embeddableId, input, type } = incomingEmbeddable;

const config = encode(input);
const expression = `embeddable config="${config}"
type="${type}"
| render`;

if (embeddableId) {
// clear out resolved arg for old embeddable
const argumentPath = [embeddableId, 'expressionRenderable'];
dispatch(clearValue({ path: argumentPath }));

// update existing embeddable expression
dispatch(
updateEmbeddableExpression({ elementId: embeddableId, embeddableExpression: expression })
);

// update resolved args
dispatch(fetchEmbeddableRenderable(embeddableId));

// select new embeddable element
dispatch(selectToplevelNodes([embeddableId]));
} else {
dispatch(addElement(pageId, { expression }));
}
}
}, [dispatch, pageId, incomingEmbeddable, isByValueEnabled]);
};
7 changes: 6 additions & 1 deletion x-pack/plugins/canvas/public/components/workpad/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { pure, compose, withState, withProps, getContext, withHandlers } from 'recompose';
import useObservable from 'react-use/lib/useObservable';
import { useIncomingEmbeddable } from '../hooks';
import { transitionsRegistry } from '../../lib/transitions_registry';
import { fetchAllRenderables } from '../../state/actions/elements';
import { setZoomScale } from '../../state/actions/transient';
Expand Down Expand Up @@ -55,13 +56,15 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
};

const AddContexts = (props) => {
const { pages, selectedPageNumber } = props;
const { isFullscreen, setFullscreen, undo, redo, autoplayInterval } =
useContext(WorkpadRoutingContext);

const platformService = usePlatformService();

const hasHeaderBanner = useObservable(platformService.hasHeaderBanner$());

const pageId = pages[selectedPageNumber - 1].id;

const setFullscreenWithEffect = useCallback(
(fullscreen) => {
setFullscreen(fullscreen);
Expand All @@ -77,6 +80,8 @@ const AddContexts = (props) => {
[setFullscreen, autoplayInterval]
);

useIncomingEmbeddable(pageId);

return (
<WorkpadComponent
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,6 @@ storiesOf('components/WorkpadHeader/ElementMenu', module).add('default', () => (
elements={testElements}
addElement={action('addElement')}
renderEmbedPanel={mockRenderEmbedPanel}
createNewEmbeddable={action('createNewEmbeddable')}
/>
));
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ElementSpec } from '../../../../types';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { AssetManager } from '../../asset_manager';
import { SavedElementsModal } from '../../saved_elements_modal';
import { useLabsService } from '../../../services';

interface CategorizedElementLists {
[key: string]: ElementSpec[];
Expand Down Expand Up @@ -129,13 +130,20 @@ export interface Props {
* Renders embeddable flyout
*/
renderEmbedPanel: (onClose: () => void) => JSX.Element;
/**
* Crete new embeddable
*/
createNewEmbeddable: () => void;
}

export const ElementMenu: FunctionComponent<Props> = ({
elements,
addElement,
renderEmbedPanel,
createNewEmbeddable,
}) => {
const labsService = useLabsService();
const isByValueEnabled = labsService.isProjectEnabled('labs:canvas:byValueEmbeddable');
const [isAssetModalVisible, setAssetModalVisible] = useState(false);
const [isEmbedPanelVisible, setEmbedPanelVisible] = useState(false);
const [isSavedElementsModalVisible, setSavedElementsModalVisible] = useState(false);
Expand Down Expand Up @@ -223,6 +231,16 @@ export const ElementMenu: FunctionComponent<Props> = ({
closePopover();
},
},
isByValueEnabled
? {
name: 'Lens',
icon: <EuiIcon type="lensApp" size="m" />,
onClick: () => {
createNewEmbeddable();
closePopover();
},
}
: undefined,
],
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
* 2.0.
*/

import React from 'react';
import React, { useCallback } from 'react';
import { connect } from 'react-redux';
import { useLocation } from 'react-router-dom';

import { compose, withProps } from 'recompose';
import { Dispatch } from 'redux';
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../../public/lib/ui_metric';
import { CANVAS_APP } from '../../../../common/lib';
import { State, ElementSpec } from '../../../../types';
// @ts-expect-error untyped local
import { elementsRegistry } from '../../../lib/elements_registry';
Expand All @@ -17,6 +21,7 @@ import { ElementMenu as Component, Props as ComponentProps } from './element_men
import { addElement } from '../../../state/actions/elements';
import { getSelectedPage } from '../../../state/selectors/workpad';
import { AddEmbeddablePanel } from '../../embeddable_flyout';
import { useEmbeddablesService } from '../../../services';

interface StateProps {
pageId: string;
Expand All @@ -42,7 +47,32 @@ const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps) => ({
renderEmbedPanel: (onClose: () => void) => <AddEmbeddablePanel onClose={onClose} />,
});

const ElementMenuComponent = (props: ComponentProps) => {
const embeddablesService = useEmbeddablesService();
const stateTransferService = embeddablesService.getStateTransfer();
const { pathname, search } = useLocation();

const createNewEmbeddable = useCallback(() => {
const path = '#/';
const appId = 'lens';

if (trackCanvasUiMetric) {
trackCanvasUiMetric(METRIC_TYPE.CLICK, `${appId}:create`);
}

stateTransferService.navigateToEditor(appId, {
path,
state: {
originatingApp: CANVAS_APP,
originatingPath: `#/${pathname}${search}`,
},
});
}, [pathname, search, stateTransferService]);

return <Component {...props} createNewEmbeddable={createNewEmbeddable} />;
};

export const ElementMenu = compose<ComponentProps, {}>(
connect(mapStateToProps, mapDispatchToProps, mergeProps),
withProps(() => ({ elements: elementsRegistry.toJS() }))
)(Component);
)(ElementMenuComponent);
12 changes: 7 additions & 5 deletions x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ export const useWorkpad = (
useEffect(() => {
(async () => {
try {
const { assets, ...workpad } = await workpadService.get(workpadId);
dispatch(setAssets(assets));
dispatch(setWorkpad(workpad, { loadPages }));
dispatch(setZoomScale(1));
if (storedWorkpad.id !== workpadId) {
const { assets, ...workpad } = await workpadService.get(workpadId);
dispatch(setAssets(assets));
dispatch(setWorkpad(workpad, { loadPages }));
dispatch(setZoomScale(1));
}
} catch (e) {
setError(e);
}
})();
}, [workpadId, dispatch, setError, loadPages, workpadService]);
}, [workpadId, dispatch, setError, loadPages, workpadService, storedWorkpad.id]);

return [storedWorkpad.id === workpadId ? storedWorkpad : undefined, error];
};
6 changes: 5 additions & 1 deletion x-pack/plugins/canvas/public/services/embeddables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
* 2.0.
*/

import { EmbeddableFactory } from '../../../../../src/plugins/embeddable/public';
import {
EmbeddableFactory,
EmbeddableStateTransfer,
} from '../../../../../src/plugins/embeddable/public';

export interface CanvasEmbeddablesService {
getEmbeddableFactories: () => IterableIterator<EmbeddableFactory>;
getStateTransfer: () => EmbeddableStateTransfer;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export type EmbeddablesServiceFactory = KibanaPluginServiceFactory<

export const embeddablesServiceFactory: EmbeddablesServiceFactory = ({ startPlugins }) => ({
getEmbeddableFactories: startPlugins.embeddable.getEmbeddableFactories,
getStateTransfer: startPlugins.embeddable.getStateTransfer,
});
1 change: 1 addition & 0 deletions x-pack/plugins/canvas/public/services/stubs/embeddables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ const noop = (..._args: any[]): any => {};

export const embeddablesServiceFactory: EmbeddablesServiceFactory = () => ({
getEmbeddableFactories: noop,
getStateTransfer: noop,
});

0 comments on commit 2657a68

Please sign in to comment.