From 297dba06041ecc14dc1e3a9468b6d508e5ed0922 Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Thu, 25 Jun 2020 12:39:53 -0400 Subject: [PATCH] Made lens work with the attribute service copied from #68719 --- src/plugins/embeddable/public/index.ts | 2 +- .../lib/actions/edit_panel_action.test.tsx | 2 +- .../public/lib/actions/edit_panel_action.ts | 2 +- .../embeddable_state_transfer.test.ts | 8 +- .../embeddable_state_transfer.ts | 16 +- .../public/lib/state_transfer/index.ts | 2 +- .../public/lib/state_transfer/types.ts | 24 ++- src/plugins/embeddable/public/mocks.tsx | 4 +- .../visualize_embeddable_factory.tsx | 1 + .../public/wizard/new_vis_modal.test.tsx | 2 +- .../public/wizard/new_vis_modal.tsx | 5 +- .../public/wizard/show_new_vis.tsx | 3 + x-pack/plugins/lens/common/constants.ts | 4 +- x-pack/plugins/lens/public/app_plugin/app.tsx | 71 +++++---- .../lens/public/app_plugin/mounter.tsx | 22 +-- .../embeddable/attribute_service.ts | 54 +++++++ .../embeddable/embeddable.tsx | 137 +++++++++++++----- .../embeddable/embeddable_factory.ts | 80 +++++----- 18 files changed, 291 insertions(+), 148 deletions(-) create mode 100644 x-pack/plugins/lens/public/editor_frame_service/embeddable/attribute_service.ts diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 1d1dc79121937b..35fbfe2e0aa38f 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -69,7 +69,7 @@ export { isRangeSelectTriggerContext, isValueClickTriggerContext, EmbeddableStateTransfer, - EmbeddableOriginatingAppState, + EmbeddableEditorState, EmbeddablePackageState, EmbeddableRenderer, EmbeddableRendererProps, diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index 4b602efb027177..594a7ad73c3965 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -59,7 +59,7 @@ test('redirects to app using state transfer', async () => { const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true); embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); await action.execute({ embeddable }); - expect(stateTransferMock.navigateToWithOriginatingApp).toHaveBeenCalledWith('ultraVisualize', { + expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', { path: '/123', state: { originatingApp: 'superCoolCurrentApp' }, }); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index d983dc9f418535..ac046e978c9cb9 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -88,7 +88,7 @@ export class EditPanelAction implements Action { const appTarget = this.getAppTarget(context); if (appTarget) { if (this.stateTransfer && appTarget.state) { - await this.stateTransfer.navigateToWithOriginatingApp(appTarget.app, { + await this.stateTransfer.navigateToEditor(appTarget.app, { path: appTarget.path, state: appTarget.state, }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts index 0d5ae6be68185b..b7dd95ccba32ca 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -38,7 +38,7 @@ describe('embeddable state transfer', () => { }); it('can send an outgoing originating app state', async () => { - await stateTransfer.navigateToWithOriginatingApp(destinationApp, { state: { originatingApp } }); + await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { state: { originatingApp: 'superUltraTestDashboard' }, }); @@ -50,7 +50,7 @@ describe('embeddable state transfer', () => { application.navigateToApp, (historyMock as unknown) as ScopedHistory ); - await stateTransfer.navigateToWithOriginatingApp(destinationApp, { + await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp }, appendToExistingState: true, }); @@ -94,7 +94,7 @@ describe('embeddable state transfer', () => { application.navigateToApp, (historyMock as unknown) as ScopedHistory ); - const fetchedState = stateTransfer.getIncomingOriginatingApp(); + const fetchedState = stateTransfer.getIncomingEditorState(); expect(fetchedState).toEqual({ originatingApp: 'extremeSportsKibana' }); }); @@ -104,7 +104,7 @@ describe('embeddable state transfer', () => { application.navigateToApp, (historyMock as unknown) as ScopedHistory ); - const fetchedState = stateTransfer.getIncomingOriginatingApp(); + const fetchedState = stateTransfer.getIncomingEditorState(); expect(fetchedState).toBeUndefined(); }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index 57b425d2df45c2..f47d40fdc70499 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -20,8 +20,8 @@ import { cloneDeep } from 'lodash'; import { ScopedHistory, ApplicationStart } from '../../../../../core/public'; import { - EmbeddableOriginatingAppState, - isEmbeddableOriginatingAppState, + EmbeddableEditorState, + isEmbeddableEditorState, EmbeddablePackageState, isEmbeddablePackageState, } from './types'; @@ -45,10 +45,10 @@ export class EmbeddableStateTransfer { * @param history - the scoped history to fetch from * @param options.keysToRemoveAfterFetch - an array of keys to be removed from the state after they are retrieved */ - public getIncomingOriginatingApp(options?: { + public getIncomingEditorState(options?: { keysToRemoveAfterFetch?: string[]; - }): EmbeddableOriginatingAppState | undefined { - return this.getIncomingState(isEmbeddableOriginatingAppState, { + }): EmbeddableEditorState | undefined { + return this.getIncomingState(isEmbeddableEditorState, { keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch, }); } @@ -72,15 +72,15 @@ export class EmbeddableStateTransfer { * A wrapper around the {@link ApplicationStart.navigateToApp} method which navigates to the specified appId * with {@link EmbeddableOriginatingAppState | originating app state} */ - public async navigateToWithOriginatingApp( + public async navigateToEditor( appId: string, options?: { path?: string; - state: EmbeddableOriginatingAppState; + state: EmbeddableEditorState; appendToExistingState?: boolean; } ): Promise { - await this.navigateToWithState(appId, options); + await this.navigateToWithState(appId, options); } /** diff --git a/src/plugins/embeddable/public/lib/state_transfer/index.ts b/src/plugins/embeddable/public/lib/state_transfer/index.ts index e51efc5dcca26b..7daa7a0ea81d64 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/index.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/index.ts @@ -18,4 +18,4 @@ */ export { EmbeddableStateTransfer } from './embeddable_state_transfer'; -export { EmbeddableOriginatingAppState, EmbeddablePackageState } from './types'; +export { EmbeddableEditorState, EmbeddablePackageState } from './types'; diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 8eae441d1be23c..3bdb4bfde5f56e 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -17,17 +17,18 @@ * under the License. */ +import { EmbeddableInput } from '..'; + /** * Represents a state package that contains the last active app id. * @public */ -export interface EmbeddableOriginatingAppState { +export interface EmbeddableEditorState { originatingApp: string; + byValueMode?: boolean; } -export function isEmbeddableOriginatingAppState( - state: unknown -): state is EmbeddableOriginatingAppState { +export function isEmbeddableEditorState(state: unknown): state is EmbeddableEditorState { return ensureFieldOfTypeExists('originatingApp', state, 'string'); } @@ -35,15 +36,24 @@ export function isEmbeddableOriginatingAppState( * Represents a state package that contains all fields necessary to create an embeddable in a container. * @public */ -export interface EmbeddablePackageState { +export interface EmbeddablePackageByReferenceState { type: string; id: string; } +export interface EmbeddablePackageByValueState { + input: EmbeddableInput; +} + +export type EmbeddablePackageState = + | EmbeddablePackageByReferenceState + | EmbeddablePackageByValueState; + export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState { return ( - ensureFieldOfTypeExists('type', state, 'string') && - ensureFieldOfTypeExists('id', state, 'string') + (ensureFieldOfTypeExists('type', state, 'string') && + ensureFieldOfTypeExists('id', state, 'string')) || + ensureFieldOfTypeExists('input', state, 'object') ); } diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 49910525c7ab18..6d94af1f228290 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -78,9 +78,9 @@ export const createEmbeddablePanelMock = ({ export const createEmbeddableStateTransferMock = (): Partial => { return { - getIncomingOriginatingApp: jest.fn(), + getIncomingEditorState: jest.fn(), getIncomingEmbeddablePackage: jest.fn(), - navigateToWithOriginatingApp: jest.fn(), + navigateToEditor: jest.fn(), navigateToWithEmbeddablePackage: jest.fn(), }; }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index eb4b66401820f1..72c794528b5460 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -130,6 +130,7 @@ export class VisualizeEmbeddableFactory showNewVisModal({ originatingApp: await this.getCurrentAppId(), outsideVisualizeApp: true, + createByValue: true, }); return undefined; } diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index dd89e98fb8fe53..f48febfef5b437 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -165,7 +165,7 @@ describe('NewVisModal', () => { ); const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); visButton.simulate('click'); - expect(stateTransfer.navigateToWithOriginatingApp).toBeCalledWith('otherApp', { + expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', { path: '#/aliasUrl', state: { originatingApp: 'coolJestTestApp' }, }); diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 84a5bca0ed0edd..d705e3a52e432a 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -43,6 +43,7 @@ interface TypeSelectionProps { application: ApplicationStart; outsideVisualizeApp?: boolean; stateTransfer?: EmbeddableStateTransfer; + createByValue?: boolean; originatingApp?: string; } @@ -172,9 +173,9 @@ class NewVisModal extends React.Component void; originatingApp?: string; outsideVisualizeApp?: boolean; + createByValue?: boolean; } /** @@ -49,6 +50,7 @@ export function showNewVisModal({ onClose, originatingApp, outsideVisualizeApp, + createByValue, }: ShowNewVisModalParams = {}) { const container = document.createElement('div'); let isClosed = false; @@ -69,6 +71,7 @@ export function showNewVisModal({ isOpen={true} onClose={handleClose} originatingApp={originatingApp} + createByValue={createByValue} stateTransfer={getEmbeddable().getStateTransfer()} outsideVisualizeApp={outsideVisualizeApp} editorParams={editorParams} diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index 16397d340d951b..9433828fb248d9 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -13,6 +13,6 @@ export function getBasePath() { return `#/`; } -export function getEditPath(id: string) { - return `#/edit/${encodeURIComponent(id)}`; +export function getEditPath(id: string | undefined) { + return id ? `#/edit/${encodeURIComponent(id)}` : '#/edit/value'; } diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 0ab547bed6d37c..5ebf506c708d1e 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -38,6 +38,7 @@ import { SavedQuery, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; +import { EmbeddableEditorState } from '../../../../../src/plugins/embeddable/public'; interface State { isLoading: boolean; @@ -64,7 +65,7 @@ export function App({ docId, docStorage, redirectTo, - originatingApp, + embeddableEditorIncomingState, navigation, onAppLeave, history, @@ -77,7 +78,7 @@ export function App({ docId?: string; docStorage: SavedObjectStore; redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp?: string | undefined; + embeddableEditorIncomingState?: EmbeddableEditorState; onAppLeave: AppMountParameters['onAppLeave']; history: History; }) { @@ -256,6 +257,7 @@ export function App({ if (!lastKnownDoc) { return; } + const [pinnedFilters, appFilters] = _.partition( lastKnownDoc.state?.filters, esFilters.isFilterPinned @@ -297,32 +299,34 @@ export function App({ ); const newlyCreated: boolean = saveProps.newCopyOnSave || !lastKnownDoc?.id; - docStorage - .save(doc) - .then(({ id }) => { - // Prevents unnecessary network request and disables save button - const newDoc = { ...doc, id }; - setState((s) => ({ - ...s, - isSaveModalVisible: false, - persistedDoc: newDoc, - lastKnownDoc: newDoc, - })); - if (docId !== id || saveProps.returnToOrigin) { - redirectTo(id, saveProps.returnToOrigin, newlyCreated); - } - }) - .catch((e) => { - // eslint-disable-next-line no-console - console.dir(e); - trackUiEvent('save_failed'); - core.notifications.toasts.addDanger( - i18n.translate('xpack.lens.app.docSavingError', { - defaultMessage: 'Error saving document', - }) - ); - setState((s) => ({ ...s, isSaveModalVisible: false })); - }); + if (!embeddableEditorIncomingState?.byValueMode) { + docStorage + .save(doc) + .then(({ id }) => { + // Prevents unnecessary network request and disables save button + const newDoc = { ...doc, id }; + setState((s) => ({ + ...s, + isSaveModalVisible: false, + persistedDoc: newDoc, + lastKnownDoc: newDoc, + })); + if (docId !== id || saveProps.returnToOrigin) { + redirectTo(id, saveProps.returnToOrigin, newlyCreated); + } + }) + .catch((e) => { + // eslint-disable-next-line no-console + console.dir(e); + trackUiEvent('save_failed'); + core.notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docSavingError', { + defaultMessage: 'Error saving document', + }) + ); + setState((s) => ({ ...s, isSaveModalVisible: false })); + }); + } }; const onError = useCallback( @@ -349,7 +353,8 @@ export function App({
{ if (isSaveable && lastKnownDoc) { setState((s) => ({ ...s, isSaveModalVisible: true })); @@ -502,7 +509,7 @@ export function App({
{lastKnownDoc && state.isSaveModalVisible && ( runSave(props)} onClose={() => setState((s) => ({ ...s, isSaveModalVisible: false }))} documentInfo={{ diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7a33241792a58f..41548dd0efa99a 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -37,8 +37,9 @@ export async function mountApp( ); const stateTransfer = embeddable?.getStateTransfer(params.history); - const { originatingApp } = - stateTransfer?.getIncomingOriginatingApp({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; + const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState({ + keysToRemoveAfterFetch: ['originatingApp'], + }); const instance = await createEditorFrame(); @@ -56,17 +57,20 @@ export async function mountApp( ) => { if (!id) { routeProps.history.push('/'); - } else if (!originatingApp) { + } else if (!embeddableEditorIncomingState?.originatingApp) { routeProps.history.push(`/edit/${id}`); - } else if (!!originatingApp && id && returnToOrigin) { + } else if (!!embeddableEditorIncomingState?.originatingApp && id && returnToOrigin) { routeProps.history.push(`/edit/${id}`); if (newlyCreated && stateTransfer) { - stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: LENS_EMBEDDABLE_TYPE }, - }); + stateTransfer.navigateToWithEmbeddablePackage( + embeddableEditorIncomingState?.originatingApp, + { + state: { id, type: LENS_EMBEDDABLE_TYPE }, + } + ); } else { - coreStart.application.navigateToApp(originatingApp); + coreStart.application.navigateToApp(embeddableEditorIncomingState?.originatingApp); } } }; @@ -86,7 +90,7 @@ export async function mountApp( redirectTo={(id, returnToOrigin, newlyCreated) => redirectTo(routeProps, id, returnToOrigin, newlyCreated) } - originatingApp={originatingApp} + embeddableEditorIncomingState={embeddableEditorIncomingState} onAppLeave={params.onAppLeave} history={routeProps.history} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/attribute_service.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/attribute_service.ts new file mode 100644 index 00000000000000..b49e05e8be5384 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/attribute_service.ts @@ -0,0 +1,54 @@ +/* + * 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 { + EmbeddableInput, + SavedObjectEmbeddableInput, + isSavedObjectEmbeddableInput, + IEmbeddable, +} from '../../../../../../src/plugins/embeddable/public'; +import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../../src/core/public'; + +export class AttributeService< + SavedObjectAttributes, + ValType extends EmbeddableInput & { attributes: SavedObjectAttributes }, + RefType extends SavedObjectEmbeddableInput +> { + constructor(private type: string, private savedObjectsClient: SavedObjectsClientContract) {} + + public async unwrapAttributes(input: RefType | ValType): Promise { + if (isSavedObjectEmbeddableInput(input)) { + const savedObject: SimpleSavedObject = await this.savedObjectsClient.get< + SavedObjectAttributes + >(this.type, input.savedObjectId); + return savedObject.attributes; + } + return input.attributes; + } + + public async wrapAttributes( + newAttributes: SavedObjectAttributes, + useRefType: boolean, + embeddable?: IEmbeddable + ): Promise> { + const savedObjectId = + embeddable && isSavedObjectEmbeddableInput(embeddable.getInput()) + ? (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId + : undefined; + + if (useRefType) { + if (savedObjectId) { + await this.savedObjectsClient.update(this.type, savedObjectId, newAttributes); + return { savedObjectId } as RefType; + } else { + const savedItem = await this.savedObjectsClient.create(this.type, newAttributes); + return { savedObjectId: savedItem.id } as RefType; + } + } else { + return { attributes: newAttributes } as ValType; + } + } +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index bbd2b18907e9b4..77d952ab0e4ea2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -13,6 +13,7 @@ import { Query, TimefilterContract, TimeRange, + IndexPattern, } from 'src/plugins/data/public'; import { Subscription } from 'rxjs'; @@ -27,21 +28,25 @@ import { EmbeddableInput, EmbeddableOutput, IContainer, + SavedObjectEmbeddableInput, } from '../../../../../../src/plugins/embeddable/public'; import { DOC_TYPE, Document } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { isLensBrushEvent, isLensFilterEvent } from '../../types'; +import { AttributeService } from './attribute_service'; +import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; +import { getEditPath } from '../../../common'; +import { IBasePath } from '../../../../../../src/core/public'; -export interface LensEmbeddableConfiguration { - savedVis: Document; - editUrl: string; - editPath: string; - editable: boolean; - indexPatterns?: IIndexPattern[]; -} +export type LensSavedObjectAttributes = Omit; -export interface LensEmbeddableInput extends EmbeddableInput { +export type LensEmbeddableInput = LensByValueInput | LensByReferenceInput; + +export type LensByValueInput = { attributes: LensSavedObjectAttributes } & LensInheritedInput; +export type LensByReferenceInput = SavedObjectEmbeddableInput & LensInheritedInput; + +export interface LensInheritedInput extends EmbeddableInput { timeRange?: TimeRange; query?: Query; filters?: Filter[]; @@ -51,12 +56,25 @@ export interface LensEmbeddableOutput extends EmbeddableOutput { indexPatterns?: IIndexPattern[]; } +export interface LensEmbeddableDeps { + attributeService: AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >; + editable: boolean; + indexPatternService: IndexPatternsContract; + expressionRenderer: ReactExpressionRendererType; + timefilter: TimefilterContract; + basePath: IBasePath; + getTrigger?: UiActionsStart['getTrigger'] | undefined; +} + export class Embeddable extends AbstractEmbeddable { type = DOC_TYPE; private expressionRenderer: ReactExpressionRendererType; - private getTrigger: UiActionsStart['getTrigger'] | undefined; - private savedVis: Document; + private savedVis: Document | undefined; private domNode: HTMLElement | Element | undefined; private subscription: Subscription; private autoRefreshFetchSubscription: Subscription; @@ -69,42 +87,36 @@ export class Embeddable extends AbstractEmbeddable this.onContainerStateChanged(input)); - this.onContainerStateChanged(initialInput); + this.expressionRenderer = deps.expressionRenderer; + this.initializeSavedVis(initialInput).then(() => this.onContainerStateChanged(initialInput)); + + this.subscription = this.getInput$().subscribe((input) => { + // await this.documentChanged(input); + this.onContainerStateChanged(input); + }); - this.autoRefreshFetchSubscription = timefilter + this.autoRefreshFetchSubscription = deps.timefilter .getAutoRefreshFetch$() .subscribe(this.reload.bind(this)); } public supportedTriggers() { + if (!this.savedVis) { + return []; + } switch (this.savedVis.visualizationType) { case 'lnsXY': return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; @@ -117,6 +129,17 @@ export class Embeddable extends AbstractEmbeddable !filter.meta.disabled) @@ -133,9 +156,7 @@ export class Embeddable extends AbstractEmbeddable { - if (!this.getTrigger || this.input.disableTriggers) { + if (!this.deps.getTrigger || this.input.disableTriggers) { return; } if (isLensBrushEvent(event)) { - this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ data: event.data, embeddable: this, }); } if (isLensFilterEvent(event)) { - this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ data: event.data, embeddable: this, }); @@ -186,7 +210,7 @@ export class Embeddable extends AbstractEmbeddable { + try { + return await this.deps.indexPatternService.get(id); + } catch (error) { + // Unable to load index pattern, ignore error as the index patterns are only used to + // configure the filter and query bar - there is still a good chance to get the visualization + // to show. + return null; + } + } + ); + const indexPatterns = ( + await Promise.all(promises) + ).filter((indexPattern: IndexPattern | null): indexPattern is IndexPattern => + Boolean(indexPattern) + ); + // passing edit url and index patterns to the output of this embeddable for + // the container to pick them up and use them to configure filter bar and + // config dropdown correctly. + const input = this.getInput(); + const title = input.hidePanelTitles + ? '' + : input.title === undefined + ? this.savedVis.title + : input.title; + const savedObjectId = (input as LensByReferenceInput).savedObjectId; + this.updateOutput({ + ...this.getOutput(), + defaultTitle: this.savedVis.title, + title, + editPath: getEditPath(savedObjectId), + editUrl: this.deps.basePath.prepend(`app/lens${getEditPath(savedObjectId)}`), + indexPatterns, + }); + } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index c23d44aa8e4b60..ef8e2f517decb5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -13,20 +13,23 @@ import { import { i18n } from '@kbn/i18n'; import { IndexPatternsContract, - IndexPattern, TimefilterContract, } from '../../../../../../src/plugins/data/public'; import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { EmbeddableFactoryDefinition, - ErrorEmbeddable, - EmbeddableInput, IContainer, } from '../../../../../../src/plugins/embeddable/public'; -import { Embeddable } from './embeddable'; -import { SavedObjectIndexStore, DOC_TYPE } from '../../persistence'; -import { getEditPath } from '../../../common'; +import { + Embeddable, + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput, + LensEmbeddableInput, +} from './embeddable'; +import { DOC_TYPE } from '../../persistence'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { AttributeService } from './attribute_service'; interface StartServices { timefilter: TimefilterContract; @@ -48,6 +51,12 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { getIconForSavedObject: () => 'lensApp', }; + private attributeService?: AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >; + constructor(private getStartServices: () => Promise) {} public isEditable = async () => { @@ -67,55 +76,44 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { createFromSavedObject = async ( savedObjectId: string, - input: Partial & { id: string }, + input: LensEmbeddableInput, parent?: IContainer ) => { + return this.create(input, parent); + }; + + async create(input: LensEmbeddableInput, parent?: IContainer) { const { - savedObjectsClient, - coreHttp, - indexPatternService, timefilter, expressionRenderer, uiActions, + coreHttp, + indexPatternService, } = await this.getStartServices(); - const store = new SavedObjectIndexStore(savedObjectsClient); - const savedVis = await store.load(savedObjectId); - - const promises = savedVis.state.datasourceMetaData.filterableIndexPatterns.map( - async ({ id }) => { - try { - return await indexPatternService.get(id); - } catch (error) { - // Unable to load index pattern, ignore error as the index patterns are only used to - // configure the filter and query bar - there is still a good chance to get the visualization - // to show. - return null; - } - } - ); - const indexPatterns = ( - await Promise.all(promises) - ).filter((indexPattern: IndexPattern | null): indexPattern is IndexPattern => - Boolean(indexPattern) - ); - return new Embeddable( - timefilter, - expressionRenderer, - uiActions?.getTrigger, { - savedVis, - editPath: getEditPath(savedObjectId), - editUrl: coreHttp.basePath.prepend(`app/lens${getEditPath(savedObjectId)}`), + attributeService: await this.getAttributeService(), + indexPatternService, + timefilter, + expressionRenderer, editable: await this.isEditable(), - indexPatterns, + basePath: coreHttp.basePath, + getTrigger: uiActions?.getTrigger, }, input, parent ); - }; + } - async create(input: EmbeddableInput) { - return new ErrorEmbeddable('Lens can only be created from a saved object', input); + private async getAttributeService() { + const savedObjectsService = (await this.getStartServices()).savedObjectsClient; + if (!this.attributeService) { + this.attributeService = new AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >(this.type, savedObjectsService); + } + return this.attributeService; } }