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

Added comment

Fixed typo
  • Loading branch information
cqliu1 committed Oct 3, 2021
1 parent e669879 commit 02ac36e
Show file tree
Hide file tree
Showing 18 changed files with 184 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
*/

import { ExpressionTypeDefinition } from '../../../../../src/plugins/expressions';
import { EmbeddableInput } from '../../../../../src/plugins/embeddable/common/';
import { EmbeddableInput } from '../../types';
import { EmbeddableTypes } from './embeddable_types';

export const EmbeddableExpressionType = 'embeddable';
export { EmbeddableTypes, EmbeddableInput };

export interface EmbeddableExpression<Input extends EmbeddableInput> {
/**
* The type of the expression result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@
*/

import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { TimeRange } from 'src/plugins/data/public';
import { Filter } from '@kbn/es-query';
import { ExpressionValueFilter } from '../../../types';
import {
EmbeddableExpressionType,
EmbeddableExpression,
EmbeddableInput as Input,
} from '../../expression_types';
import { ExpressionValueFilter, EmbeddableInput } from '../../../types';
import { EmbeddableExpressionType, EmbeddableExpression } from '../../expression_types';
import { getFunctionHelp } from '../../../i18n';
import { SavedObjectReference } from '../../../../../../src/core/types';
import { getQueryFilters } from '../../../common/lib/build_embeddable_filters';
Expand All @@ -29,12 +23,6 @@ const defaultTimeRange = {
to: 'now',
};

export type EmbeddableInput = Input & {
timeRange?: TimeRange;
filters?: Filter[];
savedObjectId: string;
};

const baseEmbeddableInput = {
timeRange: defaultTimeRange,
disableTriggers: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { PaletteOutput } from 'src/plugins/charts/common';
import { Filter as DataFilter } from '@kbn/es-query';
import { TimeRange } from 'src/plugins/data/common';
import { EmbeddableInput } from 'src/plugins/embeddable/common';
import { getQueryFilters } from '../../../common/lib/build_embeddable_filters';
import { ExpressionValueFilter, TimeRange as TimeRangeArg } from '../../../types';
import { ExpressionValueFilter, EmbeddableInput, TimeRange as TimeRangeArg } from '../../../types';
import {
EmbeddableTypes,
EmbeddableExpressionType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ 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 { RendererFactory, EmbeddableInput } from '../../../types';
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';

const { embeddable: strings } = RendererStrings;
Expand Down Expand Up @@ -71,16 +71,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
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/common/lib/embeddable_dataurl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { EmbeddableInput } from '../../canvas_plugin_src/expression_types';
import { EmbeddableInput } from '../../types';

export const encode = (input: Partial<EmbeddableInput>) =>
Buffer.from(JSON.stringify(input)).toString('base64');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ export const AddEmbeddablePanel: React.FunctionComponent<FlyoutProps> = ({
};

// If by-value is enabled, we'll handle both by-reference and by-value embeddables
// with the new generic `embeddable` function
// with the new generic `embeddable` function.
// Otherwise we fallback to the embeddable type specific expressions.
if (isByValueEnabled) {
const config = encode({ id });
const config = encode({ savedObjectId: id });
partialElement.expression = `embeddable config="${config}"
type="${type}"
| render`;
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]);
};
4 changes: 4 additions & 0 deletions x-pack/plugins/canvas/public/components/workpad/workpad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { WorkpadRoutingContext } from '../../routes/workpad';
import { usePlatformService } from '../../services';
import { Workpad as WorkpadComponent, Props } from './workpad.component';
import { State } from '../../../types';
import { useIncomingEmbeddable } from '../hooks';

type ContainerProps = Pick<Props, 'registerLayout' | 'unregisterLayout'>;

Expand Down Expand Up @@ -58,6 +59,9 @@ export const Workpad: FC<ContainerProps> = (props) => {
};
});

const pageId = propsFromState.pages[propsFromState.selectedPageNumber - 1].id;
useIncomingEmbeddable(pageId);

const fetchAllRenderables = useCallback(() => {
dispatch(fetchAllRenderablesAction());
}, [dispatch]);
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,18 @@ export const ElementMenu: FunctionComponent<Props> = ({
closePopover();
},
},
// TODO: Remove this menu option. This is a temporary menu options just for testing,
// will be removed once toolbar is implemented
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,
});
16 changes: 16 additions & 0 deletions x-pack/plugins/canvas/types/embeddables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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 { TimeRange } from 'src/plugins/data/public';
import { Filter } from '@kbn/es-query';
import { EmbeddableInput as Input } from '../../../../src/plugins/embeddable/common/';

export type EmbeddableInput = Input & {
timeRange?: TimeRange;
filters?: Filter[];
savedObjectId?: string;
};
Loading

0 comments on commit 02ac36e

Please sign in to comment.