mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Removed all inter-app communication via url in favour of a new service in the embeddable start contract called the state transfer service.
This commit is contained in:
parent
c3a5536944
commit
8d26b5ce61
32 changed files with 600 additions and 170 deletions
|
@ -75,7 +75,7 @@ import { getDashboardTitle } from './dashboard_strings';
|
|||
import { DashboardAppScope } from './dashboard_app';
|
||||
import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters';
|
||||
import { RenderDeps } from './application';
|
||||
import { IKbnUrlStateStorage, removeQueryParam, unhashUrl } from '../../../kibana_utils/public';
|
||||
import { IKbnUrlStateStorage, unhashUrl } from '../../../kibana_utils/public';
|
||||
import {
|
||||
addFatalError,
|
||||
AngularHttpError,
|
||||
|
@ -132,6 +132,7 @@ export class DashboardAppController {
|
|||
embeddable,
|
||||
share,
|
||||
dashboardCapabilities,
|
||||
scopedHistory,
|
||||
embeddableCapabilities: { visualizeCapabilities, mapsCapabilities },
|
||||
data: { query: queryService },
|
||||
core: {
|
||||
|
@ -425,15 +426,13 @@ export class DashboardAppController {
|
|||
refreshDashboardContainer();
|
||||
});
|
||||
|
||||
// This code needs to be replaced with a better mechanism for adding new embeddables of
|
||||
// any type from the add panel. Likely this will happen via creating a visualization "inline",
|
||||
// without navigating away from the UX.
|
||||
if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
|
||||
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
|
||||
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
|
||||
container.addNewEmbeddable<SavedObjectEmbeddableInput>(type, { savedObjectId: id });
|
||||
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
|
||||
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
|
||||
const incomingState = embeddable
|
||||
.getStateTransfer(scopedHistory())
|
||||
.getIncomingEmbeddablePackage();
|
||||
if (incomingState) {
|
||||
container.addNewEmbeddable<SavedObjectEmbeddableInput>(incomingState.type, {
|
||||
savedObjectId: incomingState.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ import {
|
|||
} from '../../../../kibana_react/public';
|
||||
import { PLACEHOLDER_EMBEDDABLE } from './placeholder';
|
||||
import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement';
|
||||
import { EmbeddableStateTransfer } from '../../../../embeddable/public';
|
||||
|
||||
export interface DashboardContainerInput extends ContainerInput {
|
||||
viewMode: ViewMode;
|
||||
|
@ -98,9 +99,12 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
|
||||
public renderEmpty?: undefined | (() => React.ReactNode);
|
||||
|
||||
private embeddablePanel: EmbeddableStart['EmbeddablePanel'];
|
||||
|
||||
constructor(
|
||||
initialInput: DashboardContainerInput,
|
||||
private readonly options: DashboardContainerOptions,
|
||||
stateTransfer?: EmbeddableStateTransfer,
|
||||
parent?: Container
|
||||
) {
|
||||
super(
|
||||
|
@ -111,6 +115,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
options.embeddable.getEmbeddableFactory,
|
||||
parent
|
||||
);
|
||||
this.embeddablePanel = options.embeddable.getEmbeddablePanel(stateTransfer);
|
||||
}
|
||||
|
||||
protected createNewPanelState<
|
||||
|
@ -186,7 +191,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
<DashboardViewport
|
||||
renderEmpty={this.renderEmpty}
|
||||
container={this}
|
||||
PanelComponent={this.options.embeddable.EmbeddablePanel}
|
||||
PanelComponent={this.embeddablePanel}
|
||||
/>
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { CoreStart, ScopedHistory } from 'src/core/public';
|
||||
import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
import { EmbeddableFactory, EmbeddableStart } from '../../../../embeddable/public';
|
||||
import {
|
||||
|
@ -54,7 +54,10 @@ export class DashboardContainerFactoryDefinition
|
|||
public readonly isContainerType = true;
|
||||
public readonly type = DASHBOARD_CONTAINER_TYPE;
|
||||
|
||||
constructor(private readonly getStartServices: () => Promise<StartServices>) {}
|
||||
constructor(
|
||||
private readonly getStartServices: () => Promise<StartServices>,
|
||||
private getHistory: () => ScopedHistory
|
||||
) {}
|
||||
|
||||
public isEditable = async () => {
|
||||
const { capabilities } = await this.getStartServices();
|
||||
|
@ -81,6 +84,7 @@ export class DashboardContainerFactoryDefinition
|
|||
parent?: Container
|
||||
): Promise<DashboardContainer | ErrorEmbeddable> => {
|
||||
const services = await this.getStartServices();
|
||||
return new DashboardContainer(initialInput, services, parent);
|
||||
const stateTransfer = services.embeddable.getStateTransfer(this.getHistory());
|
||||
return new DashboardContainer(initialInput, services, stateTransfer, parent);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ function prepare(props?: Partial<DashboardGridProps>) {
|
|||
embeddable: {
|
||||
getTriggerCompatibleActions: (() => []) as any,
|
||||
getEmbeddableFactories: start.getEmbeddableFactories,
|
||||
getEmbeddablePanel: jest.fn(),
|
||||
getEmbeddableFactory,
|
||||
} as any,
|
||||
notifications: {} as any,
|
||||
|
|
|
@ -54,6 +54,7 @@ function getProps(
|
|||
application: applicationServiceMock.createStartContract(),
|
||||
embeddable: {
|
||||
getTriggerCompatibleActions: (() => []) as any,
|
||||
getEmbeddablePanel: jest.fn(),
|
||||
getEmbeddableFactories: start.getEmbeddableFactories,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
} as any,
|
||||
|
|
|
@ -26,8 +26,8 @@ import { context } from '../../../../../kibana_react/public';
|
|||
|
||||
export interface DashboardViewportProps {
|
||||
container: DashboardContainer;
|
||||
renderEmpty?: () => React.ReactNode;
|
||||
PanelComponent: EmbeddableStart['EmbeddablePanel'];
|
||||
renderEmpty?: () => React.ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
|
|
@ -206,7 +206,10 @@ export class DashboardPlugin
|
|||
};
|
||||
};
|
||||
|
||||
const factory = new DashboardContainerFactoryDefinition(getStartServices);
|
||||
const factory = new DashboardContainerFactoryDefinition(
|
||||
getStartServices,
|
||||
() => this.currentHistory!
|
||||
);
|
||||
embeddable.registerEmbeddableFactory(factory.type, factory);
|
||||
|
||||
const placeholderFactory = new PlaceholderEmbeddableFactory();
|
||||
|
|
|
@ -22,7 +22,6 @@ import './index.scss';
|
|||
import { PluginInitializerContext } from 'src/core/public';
|
||||
import { EmbeddablePublicPlugin } from './plugin';
|
||||
|
||||
export { EMBEDDABLE_ORIGINATING_APP_PARAM } from './types';
|
||||
export {
|
||||
ACTION_ADD_PANEL,
|
||||
ACTION_APPLY_FILTER,
|
||||
|
@ -69,6 +68,9 @@ export {
|
|||
isSavedObjectEmbeddableInput,
|
||||
isRangeSelectTriggerContext,
|
||||
isValueClickTriggerContext,
|
||||
EmbeddableStateTransfer,
|
||||
EmbeddableOriginatingAppState,
|
||||
EmbeddablePackageState,
|
||||
EmbeddableRenderer,
|
||||
EmbeddableRendererProps,
|
||||
} from './lib';
|
||||
|
@ -82,4 +84,5 @@ export {
|
|||
EmbeddableStart,
|
||||
EmbeddableSetupDependencies,
|
||||
EmbeddableStartDependencies,
|
||||
EmbeddablePanelHOC,
|
||||
} from './plugin';
|
||||
|
|
|
@ -23,11 +23,13 @@ import { ViewMode } from '../types';
|
|||
import { ContactCardEmbeddable } from '../test_samples';
|
||||
import { embeddablePluginMock } from '../../mocks';
|
||||
import { applicationServiceMock } from '../../../../../core/public/mocks';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
const { doStart } = embeddablePluginMock.createInstance();
|
||||
const start = doStart();
|
||||
const getFactory = start.getEmbeddableFactory;
|
||||
const applicationMock = applicationServiceMock.createStartContract();
|
||||
const stateTransferMock = embeddablePluginMock.createStartContract().getStateTransfer();
|
||||
|
||||
class EditableEmbeddable extends Embeddable {
|
||||
public readonly type = 'EDITABLE_EMBEDDABLE';
|
||||
|
@ -43,7 +45,7 @@ class EditableEmbeddable extends Embeddable {
|
|||
}
|
||||
|
||||
test('is compatible when edit url is available, in edit mode and editable', async () => {
|
||||
const action = new EditPanelAction(getFactory, applicationMock);
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
expect(
|
||||
await action.isCompatible({
|
||||
embeddable: new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true),
|
||||
|
@ -51,8 +53,20 @@ test('is compatible when edit url is available, in edit mode and editable', asyn
|
|||
).toBe(true);
|
||||
});
|
||||
|
||||
test('redirects to app using state transfer', async () => {
|
||||
applicationMock.currentAppId$ = of('superCoolCurrentApp');
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
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', {
|
||||
path: '/123',
|
||||
state: { originatingApp: 'superCoolCurrentApp' },
|
||||
});
|
||||
});
|
||||
|
||||
test('getHref returns the edit urls', async () => {
|
||||
const action = new EditPanelAction(getFactory, applicationMock);
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
expect(action.getHref).toBeDefined();
|
||||
|
||||
if (action.getHref) {
|
||||
|
@ -66,7 +80,7 @@ test('getHref returns the edit urls', async () => {
|
|||
});
|
||||
|
||||
test('is not compatible when edit url is not available', async () => {
|
||||
const action = new EditPanelAction(getFactory, applicationMock);
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
const embeddable = new ContactCardEmbeddable(
|
||||
{
|
||||
id: '123',
|
||||
|
@ -85,7 +99,7 @@ test('is not compatible when edit url is not available', async () => {
|
|||
});
|
||||
|
||||
test('is not visible when edit url is available but in view mode', async () => {
|
||||
const action = new EditPanelAction(getFactory, applicationMock);
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
expect(
|
||||
await action.isCompatible({
|
||||
embeddable: new EditableEmbeddable(
|
||||
|
@ -100,7 +114,7 @@ test('is not visible when edit url is available but in view mode', async () => {
|
|||
});
|
||||
|
||||
test('is not compatible when edit url is available, in edit mode, but not editable', async () => {
|
||||
const action = new EditPanelAction(getFactory, applicationMock);
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
expect(
|
||||
await action.isCompatible({
|
||||
embeddable: new EditableEmbeddable(
|
||||
|
|
|
@ -24,7 +24,7 @@ import { take } from 'rxjs/operators';
|
|||
import { ViewMode } from '../types';
|
||||
import { EmbeddableFactoryNotFoundError } from '../errors';
|
||||
import { EmbeddableStart } from '../../plugin';
|
||||
import { EMBEDDABLE_ORIGINATING_APP_PARAM, IEmbeddable } from '../..';
|
||||
import { IEmbeddable, EmbeddableOriginatingAppState, EmbeddableStateTransfer } from '../..';
|
||||
|
||||
export const ACTION_EDIT_PANEL = 'editPanel';
|
||||
|
||||
|
@ -32,6 +32,12 @@ interface ActionContext {
|
|||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
interface NavigationContext {
|
||||
app: string;
|
||||
path: string;
|
||||
state?: EmbeddableOriginatingAppState;
|
||||
}
|
||||
|
||||
export class EditPanelAction implements Action<ActionContext> {
|
||||
public readonly type = ACTION_EDIT_PANEL;
|
||||
public readonly id = ACTION_EDIT_PANEL;
|
||||
|
@ -40,7 +46,8 @@ export class EditPanelAction implements Action<ActionContext> {
|
|||
|
||||
constructor(
|
||||
private readonly getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'],
|
||||
private readonly application: ApplicationStart
|
||||
private readonly application: ApplicationStart,
|
||||
private readonly stateTransfer?: EmbeddableStateTransfer
|
||||
) {
|
||||
if (this.application?.currentAppId$) {
|
||||
this.application.currentAppId$
|
||||
|
@ -79,9 +86,15 @@ export class EditPanelAction implements Action<ActionContext> {
|
|||
|
||||
public async execute(context: ActionContext) {
|
||||
const appTarget = this.getAppTarget(context);
|
||||
|
||||
if (appTarget) {
|
||||
await this.application.navigateToApp(appTarget.app, { path: appTarget.path });
|
||||
if (this.stateTransfer && appTarget.state) {
|
||||
await this.stateTransfer.navigateToWithOriginatingApp(appTarget.app, {
|
||||
path: appTarget.path,
|
||||
state: appTarget.state,
|
||||
});
|
||||
} else {
|
||||
await this.application.navigateToApp(appTarget.app, { path: appTarget.path });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -92,22 +105,17 @@ export class EditPanelAction implements Action<ActionContext> {
|
|||
}
|
||||
}
|
||||
|
||||
public getAppTarget({ embeddable }: ActionContext): { app: string; path: string } | undefined {
|
||||
public getAppTarget({ embeddable }: ActionContext): NavigationContext | undefined {
|
||||
const app = embeddable ? embeddable.getOutput().editApp : undefined;
|
||||
let path = embeddable ? embeddable.getOutput().editPath : undefined;
|
||||
const path = embeddable ? embeddable.getOutput().editPath : undefined;
|
||||
if (app && path) {
|
||||
if (this.currentAppId) {
|
||||
path += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`;
|
||||
}
|
||||
return { app, path };
|
||||
const state = this.currentAppId ? { originatingApp: this.currentAppId } : undefined;
|
||||
return { app, path, state };
|
||||
}
|
||||
}
|
||||
|
||||
public async getHref({ embeddable }: ActionContext): Promise<string> {
|
||||
let editUrl = embeddable ? embeddable.getOutput().editUrl : undefined;
|
||||
if (editUrl && this.currentAppId) {
|
||||
editUrl += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`;
|
||||
}
|
||||
const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined;
|
||||
return editUrl ? editUrl : '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,3 +24,4 @@ export * from './actions';
|
|||
export * from './triggers';
|
||||
export * from './containers';
|
||||
export * from './panel';
|
||||
export * from './state_transfer';
|
||||
|
|
|
@ -43,6 +43,7 @@ import { EditPanelAction } from '../actions';
|
|||
import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal';
|
||||
import { EmbeddableStart } from '../../plugin';
|
||||
import { EmbeddableErrorLabel } from './embeddable_error_label';
|
||||
import { EmbeddableStateTransfer } from '..';
|
||||
|
||||
const sortByOrderField = (
|
||||
{ order: orderA }: { order?: number },
|
||||
|
@ -62,6 +63,7 @@ interface Props {
|
|||
application: CoreStart['application'];
|
||||
inspector: InspectorStartContract;
|
||||
SavedObjectFinder: React.ComponentType<any>;
|
||||
stateTransfer?: EmbeddableStateTransfer;
|
||||
hideHeader?: boolean;
|
||||
}
|
||||
|
||||
|
@ -299,7 +301,11 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
),
|
||||
new InspectPanelAction(this.props.inspector),
|
||||
new RemovePanelAction(),
|
||||
new EditPanelAction(this.props.getEmbeddableFactory, this.props.application),
|
||||
new EditPanelAction(
|
||||
this.props.getEmbeddableFactory,
|
||||
this.props.application,
|
||||
this.props.stateTransfer
|
||||
),
|
||||
];
|
||||
|
||||
const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField);
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { coreMock, scopedHistoryMock } from '../../../../../core/public/mocks';
|
||||
import { EmbeddableStateTransfer } from '.';
|
||||
import { ApplicationStart, ScopedHistory } from '../../../../../core/public';
|
||||
|
||||
function mockHistoryState(state: unknown) {
|
||||
return scopedHistoryMock.create({ state });
|
||||
}
|
||||
|
||||
describe('embeddable state transfer', () => {
|
||||
let application: jest.Mocked<ApplicationStart>;
|
||||
let stateTransfer: EmbeddableStateTransfer;
|
||||
const destinationApp = 'superUltraVisualize';
|
||||
const originatingApp = 'superUltraTestDashboard';
|
||||
|
||||
beforeEach(() => {
|
||||
const core = coreMock.createStart();
|
||||
application = core.application;
|
||||
stateTransfer = new EmbeddableStateTransfer(application.navigateToApp);
|
||||
});
|
||||
|
||||
it('can send an outgoing originating app state', async () => {
|
||||
await stateTransfer.navigateToWithOriginatingApp(destinationApp, { state: { originatingApp } });
|
||||
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
|
||||
state: { originatingApp: 'superUltraTestDashboard' },
|
||||
});
|
||||
});
|
||||
|
||||
it('can send an outgoing originating app state in append mode', async () => {
|
||||
const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' });
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
await stateTransfer.navigateToWithOriginatingApp(destinationApp, {
|
||||
state: { originatingApp },
|
||||
appendToExistingState: true,
|
||||
});
|
||||
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
|
||||
path: undefined,
|
||||
state: {
|
||||
kibanaIsNowForSports: 'extremeSportsKibana',
|
||||
originatingApp: 'superUltraTestDashboard',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can send an outgoing embeddable package state', async () => {
|
||||
await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, {
|
||||
state: { type: 'coolestType', id: '150' },
|
||||
});
|
||||
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
|
||||
state: { type: 'coolestType', id: '150' },
|
||||
});
|
||||
});
|
||||
|
||||
it('can send an outgoing embeddable package state in append mode', async () => {
|
||||
const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' });
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, {
|
||||
state: { type: 'coolestType', id: '150' },
|
||||
appendToExistingState: true,
|
||||
});
|
||||
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
|
||||
path: undefined,
|
||||
state: { kibanaIsNowForSports: 'extremeSportsKibana', type: 'coolestType', id: '150' },
|
||||
});
|
||||
});
|
||||
|
||||
it('can fetch an incoming originating app state', async () => {
|
||||
const historyMock = mockHistoryState({ originatingApp: 'extremeSportsKibana' });
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
const fetchedState = stateTransfer.getIncomingOriginatingApp();
|
||||
expect(fetchedState).toEqual({ originatingApp: 'extremeSportsKibana' });
|
||||
});
|
||||
|
||||
it('returns undefined with originating app state is not in the right shape', async () => {
|
||||
const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' });
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
const fetchedState = stateTransfer.getIncomingOriginatingApp();
|
||||
expect(fetchedState).toBeUndefined();
|
||||
});
|
||||
|
||||
it('can fetch an incoming embeddable package state', async () => {
|
||||
const historyMock = mockHistoryState({ type: 'skisEmbeddable', id: '123' });
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
const fetchedState = stateTransfer.getIncomingEmbeddablePackage();
|
||||
expect(fetchedState).toEqual({ type: 'skisEmbeddable', id: '123' });
|
||||
});
|
||||
|
||||
it('returns undefined when embeddable package is not in the right shape', async () => {
|
||||
const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' });
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
const fetchedState = stateTransfer.getIncomingEmbeddablePackage();
|
||||
expect(fetchedState).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes all keys in the keysToRemoveAfterFetch array', async () => {
|
||||
const historyMock = mockHistoryState({
|
||||
type: 'skisEmbeddable',
|
||||
id: '123',
|
||||
test1: 'test1',
|
||||
test2: 'test2',
|
||||
});
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'id'] });
|
||||
expect(historyMock.replace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: { test1: 'test1', test2: 'test2' } })
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves state as is when no keysToRemove are supplied', async () => {
|
||||
const historyMock = mockHistoryState({
|
||||
type: 'skisEmbeddable',
|
||||
id: '123',
|
||||
test1: 'test1',
|
||||
test2: 'test2',
|
||||
});
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
(historyMock as unknown) as ScopedHistory
|
||||
);
|
||||
stateTransfer.getIncomingEmbeddablePackage();
|
||||
expect(historyMock.location.state).toEqual({
|
||||
type: 'skisEmbeddable',
|
||||
id: '123',
|
||||
test1: 'test1',
|
||||
test2: 'test2',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { ScopedHistory, ApplicationStart } from '../../../../../core/public';
|
||||
import {
|
||||
EmbeddableOriginatingAppState,
|
||||
isEmbeddableOriginatingAppState,
|
||||
EmbeddablePackageState,
|
||||
isEmbeddablePackageState,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* A wrapper around the state object in {@link ScopedHistory | core scoped history} which provides
|
||||
* strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class EmbeddableStateTransfer {
|
||||
constructor(
|
||||
private navigateToApp: ApplicationStart['navigateToApp'],
|
||||
private scopedHistory?: ScopedHistory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Fetches an {@link EmbeddableOriginatingAppState | originating app} argument from the scoped
|
||||
* history's location state.
|
||||
*
|
||||
* @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?: {
|
||||
keysToRemoveAfterFetch?: string[];
|
||||
}): EmbeddableOriginatingAppState | undefined {
|
||||
return this.getIncomingState<EmbeddableOriginatingAppState>(isEmbeddableOriginatingAppState, {
|
||||
keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an {@link EmbeddablePackageState | embeddable package} argument from the scoped
|
||||
* history's location state.
|
||||
*
|
||||
* @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 getIncomingEmbeddablePackage(options?: {
|
||||
keysToRemoveAfterFetch?: string[];
|
||||
}): EmbeddablePackageState | undefined {
|
||||
return this.getIncomingState<EmbeddablePackageState>(isEmbeddablePackageState, {
|
||||
keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around the {@link ApplicationStart.navigateToApp} method which navigates to the specified appId
|
||||
* with {@link EmbeddableOriginatingAppState | originating app state}
|
||||
*/
|
||||
public async navigateToWithOriginatingApp(
|
||||
appId: string,
|
||||
options?: {
|
||||
path?: string;
|
||||
state: EmbeddableOriginatingAppState;
|
||||
appendToExistingState?: boolean;
|
||||
}
|
||||
): Promise<void> {
|
||||
await this.navigateToWithState<EmbeddableOriginatingAppState>(appId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around the {@link ApplicationStart.navigateToApp} method which navigates to the specified appId
|
||||
* with {@link EmbeddablePackageState | embeddable package state}
|
||||
*/
|
||||
public async navigateToWithEmbeddablePackage(
|
||||
appId: string,
|
||||
options?: { path?: string; state: EmbeddablePackageState; appendToExistingState?: boolean }
|
||||
): Promise<void> {
|
||||
await this.navigateToWithState<EmbeddablePackageState>(appId, options);
|
||||
}
|
||||
|
||||
private getIncomingState<IncomingStateType>(
|
||||
guard: (state: unknown) => state is IncomingStateType,
|
||||
options?: {
|
||||
keysToRemoveAfterFetch?: string[];
|
||||
}
|
||||
): IncomingStateType | undefined {
|
||||
if (!this.scopedHistory) {
|
||||
throw new TypeError('ScopedHistory is required to fetch incoming state');
|
||||
}
|
||||
const incomingState = this.scopedHistory.location?.state;
|
||||
const castState =
|
||||
!guard || guard(incomingState) ? (cloneDeep(incomingState) as IncomingStateType) : undefined;
|
||||
if (castState && options?.keysToRemoveAfterFetch) {
|
||||
const stateReplace = { ...(this.scopedHistory.location.state as { [key: string]: unknown }) };
|
||||
options.keysToRemoveAfterFetch.forEach((key: string) => {
|
||||
delete stateReplace[key];
|
||||
});
|
||||
this.scopedHistory.replace({ ...this.scopedHistory.location, state: stateReplace });
|
||||
}
|
||||
return castState;
|
||||
}
|
||||
|
||||
private async navigateToWithState<OutgoingStateType = unknown>(
|
||||
appId: string,
|
||||
options?: { path?: string; state?: OutgoingStateType; appendToExistingState?: boolean }
|
||||
): Promise<void> {
|
||||
const stateObject =
|
||||
options?.appendToExistingState && this.scopedHistory
|
||||
? {
|
||||
...(this.scopedHistory?.location.state as { [key: string]: unknown }),
|
||||
...options.state,
|
||||
}
|
||||
: options?.state;
|
||||
await this.navigateToApp(appId, { path: options?.path, state: stateObject });
|
||||
}
|
||||
}
|
21
src/plugins/embeddable/public/lib/state_transfer/index.ts
Normal file
21
src/plugins/embeddable/public/lib/state_transfer/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { EmbeddableStateTransfer } from './embeddable_state_transfer';
|
||||
export { EmbeddableOriginatingAppState, EmbeddablePackageState } from './types';
|
56
src/plugins/embeddable/public/lib/state_transfer/types.ts
Normal file
56
src/plugins/embeddable/public/lib/state_transfer/types.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a state package that contains the last active app id.
|
||||
* @public
|
||||
*/
|
||||
export interface EmbeddableOriginatingAppState {
|
||||
originatingApp: string;
|
||||
}
|
||||
|
||||
export function isEmbeddableOriginatingAppState(
|
||||
state: unknown
|
||||
): state is EmbeddableOriginatingAppState {
|
||||
return ensureFieldOfTypeExists('originatingApp', state, 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a state package that contains all fields necessary to create an embeddable in a container.
|
||||
* @public
|
||||
*/
|
||||
export interface EmbeddablePackageState {
|
||||
type: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState {
|
||||
return (
|
||||
ensureFieldOfTypeExists('type', state, 'string') &&
|
||||
ensureFieldOfTypeExists('id', state, 'string')
|
||||
);
|
||||
}
|
||||
|
||||
function ensureFieldOfTypeExists(key: string, obj: unknown, type?: string): boolean {
|
||||
return (
|
||||
obj &&
|
||||
key in (obj as { [key: string]: unknown }) &&
|
||||
(!type || typeof (obj as { [key: string]: unknown })[key] === type)
|
||||
);
|
||||
}
|
|
@ -22,6 +22,7 @@ import {
|
|||
EmbeddableSetup,
|
||||
EmbeddableSetupDependencies,
|
||||
EmbeddableStartDependencies,
|
||||
EmbeddableStateTransfer,
|
||||
IEmbeddable,
|
||||
EmbeddablePanel,
|
||||
} from '.';
|
||||
|
@ -75,6 +76,15 @@ export const createEmbeddablePanelMock = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const createEmbeddableStateTransferMock = (): Partial<EmbeddableStateTransfer> => {
|
||||
return {
|
||||
getIncomingOriginatingApp: jest.fn(),
|
||||
getIncomingEmbeddablePackage: jest.fn(),
|
||||
navigateToWithOriginatingApp: jest.fn(),
|
||||
navigateToWithEmbeddablePackage: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const createSetupContract = (): Setup => {
|
||||
const setupContract: Setup = {
|
||||
registerEmbeddableFactory: jest.fn(),
|
||||
|
@ -88,6 +98,8 @@ const createStartContract = (): Start => {
|
|||
getEmbeddableFactories: jest.fn(),
|
||||
getEmbeddableFactory: jest.fn(),
|
||||
EmbeddablePanel: jest.fn(),
|
||||
getEmbeddablePanel: jest.fn(),
|
||||
getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
|
|
@ -20,7 +20,13 @@ import React from 'react';
|
|||
import { getSavedObjectFinder } from '../../saved_objects/public';
|
||||
import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public';
|
||||
import { Start as InspectorStart } from '../../inspector/public';
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
ScopedHistory,
|
||||
} from '../../../core/public';
|
||||
import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types';
|
||||
import { bootstrap } from './bootstrap';
|
||||
import {
|
||||
|
@ -32,6 +38,7 @@ import {
|
|||
EmbeddablePanel,
|
||||
} from './lib';
|
||||
import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
|
||||
import { EmbeddableStateTransfer } from './lib/state_transfer';
|
||||
|
||||
export interface EmbeddableSetupDependencies {
|
||||
uiActions: UiActionsSetup;
|
||||
|
@ -63,9 +70,13 @@ export interface EmbeddableStart {
|
|||
embeddableFactoryId: string
|
||||
) => EmbeddableFactory<I, O, E> | undefined;
|
||||
getEmbeddableFactories: () => IterableIterator<EmbeddableFactory>;
|
||||
EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>;
|
||||
EmbeddablePanel: EmbeddablePanelHOC;
|
||||
getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC;
|
||||
getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer;
|
||||
}
|
||||
|
||||
export type EmbeddablePanelHOC = React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>;
|
||||
|
||||
export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
|
||||
private readonly embeddableFactoryDefinitions: Map<
|
||||
string,
|
||||
|
@ -73,6 +84,7 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
> = new Map();
|
||||
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
|
||||
private customEmbeddableFactoryProvider?: EmbeddableFactoryProvider;
|
||||
private outgoingOnlyStateTransfer: EmbeddableStateTransfer = {} as EmbeddableStateTransfer;
|
||||
private isRegistryReady = false;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
@ -105,31 +117,42 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
: defaultEmbeddableFactoryProvider(def)
|
||||
);
|
||||
});
|
||||
|
||||
this.outgoingOnlyStateTransfer = new EmbeddableStateTransfer(core.application.navigateToApp);
|
||||
this.isRegistryReady = true;
|
||||
|
||||
const getEmbeddablePanelHoc = (stateTransfer?: EmbeddableStateTransfer) => ({
|
||||
embeddable,
|
||||
hideHeader,
|
||||
}: {
|
||||
embeddable: IEmbeddable;
|
||||
hideHeader?: boolean;
|
||||
}) => (
|
||||
<EmbeddablePanel
|
||||
hideHeader={hideHeader}
|
||||
embeddable={embeddable}
|
||||
stateTransfer={stateTransfer ? stateTransfer : this.outgoingOnlyStateTransfer}
|
||||
getActions={uiActions.getTriggerCompatibleActions}
|
||||
getEmbeddableFactory={this.getEmbeddableFactory}
|
||||
getAllEmbeddableFactories={this.getEmbeddableFactories}
|
||||
overlays={core.overlays}
|
||||
notifications={core.notifications}
|
||||
application={core.application}
|
||||
inspector={inspector}
|
||||
SavedObjectFinder={getSavedObjectFinder(core.savedObjects, core.uiSettings)}
|
||||
/>
|
||||
);
|
||||
|
||||
return {
|
||||
getEmbeddableFactory: this.getEmbeddableFactory,
|
||||
getEmbeddableFactories: this.getEmbeddableFactories,
|
||||
EmbeddablePanel: ({
|
||||
embeddable,
|
||||
hideHeader,
|
||||
}: {
|
||||
embeddable: IEmbeddable;
|
||||
hideHeader?: boolean;
|
||||
}) => (
|
||||
<EmbeddablePanel
|
||||
hideHeader={hideHeader}
|
||||
embeddable={embeddable}
|
||||
getActions={uiActions.getTriggerCompatibleActions}
|
||||
getEmbeddableFactory={this.getEmbeddableFactory}
|
||||
getAllEmbeddableFactories={this.getEmbeddableFactories}
|
||||
overlays={core.overlays}
|
||||
notifications={core.notifications}
|
||||
application={core.application}
|
||||
inspector={inspector}
|
||||
SavedObjectFinder={getSavedObjectFinder(core.savedObjects, core.uiSettings)}
|
||||
/>
|
||||
),
|
||||
getStateTransfer: (history?: ScopedHistory) => {
|
||||
return history
|
||||
? new EmbeddableStateTransfer(core.application.navigateToApp, history)
|
||||
: this.outgoingOnlyStateTransfer;
|
||||
},
|
||||
EmbeddablePanel: getEmbeddablePanelHoc(),
|
||||
getEmbeddablePanel: getEmbeddablePanelHoc,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ async function creatHelloWorldContainerAndEmbeddable(
|
|||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
panelComponent: testPanel,
|
||||
});
|
||||
|
||||
const embeddable = await container.addNewEmbeddable<
|
||||
ContactCardEmbeddableInput,
|
||||
ContactCardEmbeddableOutput,
|
||||
|
|
|
@ -26,8 +26,6 @@ import {
|
|||
EmbeddableFactoryDefinition,
|
||||
} from './lib/embeddables';
|
||||
|
||||
export const EMBEDDABLE_ORIGINATING_APP_PARAM = 'embeddableOriginatingApp';
|
||||
|
||||
export type EmbeddableFactoryRegistry = Map<string, EmbeddableFactory>;
|
||||
|
||||
export type EmbeddableFactoryProvider = <
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
EmbeddableOutput,
|
||||
ErrorEmbeddable,
|
||||
IContainer,
|
||||
EMBEDDABLE_ORIGINATING_APP_PARAM,
|
||||
} from '../../../embeddable/public';
|
||||
import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
|
||||
import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable';
|
||||
|
@ -50,7 +49,7 @@ interface VisualizationAttributes extends SavedObjectAttributes {
|
|||
}
|
||||
|
||||
export interface VisualizeEmbeddableFactoryDeps {
|
||||
start: StartServicesGetter<Pick<VisualizationsStartDeps, 'inspector'>>;
|
||||
start: StartServicesGetter<Pick<VisualizationsStartDeps, 'inspector' | 'embeddable'>>;
|
||||
}
|
||||
|
||||
export class VisualizeEmbeddableFactory
|
||||
|
@ -103,15 +102,7 @@ export class VisualizeEmbeddableFactory
|
|||
}
|
||||
|
||||
public async getCurrentAppId() {
|
||||
let currentAppId = await this.deps
|
||||
.start()
|
||||
.core.application.currentAppId$.pipe(first())
|
||||
.toPromise();
|
||||
// TODO: Remove this after https://github.com/elastic/kibana/pull/63443
|
||||
if (currentAppId === 'kibana') {
|
||||
currentAppId += `:${window.location.hash.split(/[\/\?]/)[1]}`;
|
||||
}
|
||||
return currentAppId;
|
||||
return await this.deps.start().core.application.currentAppId$.pipe(first()).toPromise();
|
||||
}
|
||||
|
||||
public async createFromSavedObject(
|
||||
|
@ -136,9 +127,8 @@ export class VisualizeEmbeddableFactory
|
|||
public async create() {
|
||||
// TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up
|
||||
// to allow for in place creation of visualizations without having to navigate away to a new URL.
|
||||
const originatingAppParam = await this.getCurrentAppId();
|
||||
showNewVisModal({
|
||||
editorParams: [`${EMBEDDABLE_ORIGINATING_APP_PARAM}=${originatingAppParam}`],
|
||||
originatingApp: await this.getCurrentAppId(),
|
||||
outsideVisualizeApp: true,
|
||||
});
|
||||
return undefined;
|
||||
|
|
|
@ -22,6 +22,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
|||
import { TypesStart, VisType } from '../vis_types';
|
||||
import { NewVisModal } from './new_vis_modal';
|
||||
import { ApplicationStart, SavedObjectsStart } from '../../../../core/public';
|
||||
import { embeddablePluginMock } from '../../../embeddable/public/mocks';
|
||||
|
||||
describe('NewVisModal', () => {
|
||||
const defaultVisTypeParams = {
|
||||
|
@ -144,30 +145,34 @@ describe('NewVisModal', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('closes and redirects properly if visualization with aliasPath and addToDashboard in editorParams', () => {
|
||||
it('closes and redirects properly if visualization with aliasPath and originatingApp in props', () => {
|
||||
const onClose = jest.fn();
|
||||
const navigateToApp = jest.fn();
|
||||
const stateTransfer = embeddablePluginMock.createStartContract().getStateTransfer();
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
visTypesRegistry={visTypes}
|
||||
editorParams={['foo=true', 'bar=42', 'embeddableOriginatingApp=notAnApp']}
|
||||
editorParams={['foo=true', 'bar=42']}
|
||||
originatingApp={'coolJestTestApp'}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={({ navigateToApp } as unknown) as ApplicationStart}
|
||||
stateTransfer={stateTransfer}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]');
|
||||
visButton.simulate('click');
|
||||
expect(navigateToApp).toBeCalledWith('otherApp', {
|
||||
path: '#/aliasUrl?embeddableOriginatingApp=notAnApp',
|
||||
expect(stateTransfer.navigateToWithOriginatingApp).toBeCalledWith('otherApp', {
|
||||
path: '#/aliasUrl',
|
||||
state: { originatingApp: 'coolJestTestApp' },
|
||||
});
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('closes and redirects properly if visualization with aliasApp and without addToDashboard in editorParams', () => {
|
||||
it('closes and redirects properly if visualization with aliasApp and without originatingApp in props', () => {
|
||||
const onClose = jest.fn();
|
||||
const navigateToApp = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
|
|
|
@ -28,7 +28,7 @@ import { SearchSelection } from './search_selection';
|
|||
import { TypeSelection } from './type_selection';
|
||||
import { TypesStart, VisType, VisTypeAlias } from '../vis_types';
|
||||
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
|
||||
import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../embeddable/public';
|
||||
import { EmbeddableStateTransfer } from '../../../embeddable/public';
|
||||
import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants';
|
||||
|
||||
interface TypeSelectionProps {
|
||||
|
@ -42,6 +42,8 @@ interface TypeSelectionProps {
|
|||
usageCollection?: UsageCollectionSetup;
|
||||
application: ApplicationStart;
|
||||
outsideVisualizeApp?: boolean;
|
||||
stateTransfer?: EmbeddableStateTransfer;
|
||||
originatingApp?: string;
|
||||
}
|
||||
|
||||
interface TypeSelectionState {
|
||||
|
@ -148,14 +150,8 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
let params;
|
||||
if ('aliasPath' in visType) {
|
||||
params = visType.aliasPath;
|
||||
if (this.props.editorParams) {
|
||||
const originatingAppParam = this.props.editorParams?.find((param: string) =>
|
||||
param.startsWith(EMBEDDABLE_ORIGINATING_APP_PARAM)
|
||||
);
|
||||
params = originatingAppParam ? `${params}?${originatingAppParam}` : params;
|
||||
}
|
||||
this.props.onClose();
|
||||
this.props.application.navigateToApp(visType.aliasApp, { path: params });
|
||||
this.navigate(visType.aliasApp, visType.aliasPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -168,13 +164,24 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
|
||||
this.props.onClose();
|
||||
if (this.props.outsideVisualizeApp) {
|
||||
this.props.application.navigateToApp('visualize', {
|
||||
path: `#${basePath}${params.join('&')}`,
|
||||
});
|
||||
this.navigate('visualize', `#${basePath}${params.join('&')}`);
|
||||
} else {
|
||||
location.assign(this.props.addBasePath(`${baseUrl}${params.join('&')}`));
|
||||
}
|
||||
}
|
||||
|
||||
private navigate(appId: string, params: string) {
|
||||
if (this.props.stateTransfer && this.props.originatingApp) {
|
||||
this.props.stateTransfer.navigateToWithOriginatingApp(appId, {
|
||||
path: params,
|
||||
state: { originatingApp: this.props.originatingApp },
|
||||
});
|
||||
} else {
|
||||
this.props.application.navigateToApp(appId, {
|
||||
path: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { NewVisModal };
|
||||
|
|
|
@ -29,11 +29,13 @@ import {
|
|||
getUISettings,
|
||||
getUsageCollector,
|
||||
getApplication,
|
||||
getEmbeddable,
|
||||
} from '../services';
|
||||
|
||||
export interface ShowNewVisModalParams {
|
||||
editorParams?: string[];
|
||||
onClose?: () => void;
|
||||
originatingApp?: string;
|
||||
outsideVisualizeApp?: boolean;
|
||||
}
|
||||
|
||||
|
@ -45,6 +47,7 @@ export interface ShowNewVisModalParams {
|
|||
export function showNewVisModal({
|
||||
editorParams = [],
|
||||
onClose,
|
||||
originatingApp,
|
||||
outsideVisualizeApp,
|
||||
}: ShowNewVisModalParams = {}) {
|
||||
const container = document.createElement('div');
|
||||
|
@ -65,6 +68,8 @@ export function showNewVisModal({
|
|||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={handleClose}
|
||||
originatingApp={originatingApp}
|
||||
stateTransfer={getEmbeddable().getStateTransfer()}
|
||||
outsideVisualizeApp={outsideVisualizeApp}
|
||||
editorParams={editorParams}
|
||||
visTypesRegistry={getTypes()}
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"navigation",
|
||||
"savedObjects",
|
||||
"visualizations",
|
||||
"dashboard"
|
||||
"dashboard",
|
||||
"embeddable"
|
||||
],
|
||||
"optionalPlugins": ["home", "share"]
|
||||
}
|
||||
|
|
|
@ -29,10 +29,8 @@ import { makeStateful, useVisualizeAppState } from './lib';
|
|||
import { VisualizeConstants } from '../visualize_constants';
|
||||
import { getEditBreadcrumbs } from '../breadcrumbs';
|
||||
|
||||
import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../../embeddable/public';
|
||||
|
||||
import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util';
|
||||
import { unhashUrl, removeQueryParam } from '../../../../kibana_utils/public';
|
||||
import { unhashUrl } from '../../../../kibana_utils/public';
|
||||
import { MarkdownSimple, toMountPoint } from '../../../../kibana_react/public';
|
||||
import {
|
||||
addFatalError,
|
||||
|
@ -78,7 +76,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
I18nContext,
|
||||
setActiveUrl,
|
||||
visualizations,
|
||||
dashboard,
|
||||
embeddable,
|
||||
scopedHistory,
|
||||
} = getServices();
|
||||
|
||||
const {
|
||||
|
@ -118,8 +117,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
|
||||
};
|
||||
|
||||
const originatingApp = $route.current.params[EMBEDDABLE_ORIGINATING_APP_PARAM];
|
||||
removeQueryParam(history, EMBEDDABLE_ORIGINATING_APP_PARAM);
|
||||
const { originatingApp } =
|
||||
embeddable.getStateTransfer(scopedHistory()).getIncomingOriginatingApp() || {};
|
||||
$scope.getOriginatingApp = () => originatingApp;
|
||||
|
||||
const visStateToEditorState = () => {
|
||||
|
@ -647,7 +646,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
*/
|
||||
function doSave(saveOptions) {
|
||||
// vis.title was not bound and it's needed to reflect title into visState
|
||||
const firstSave = !Boolean(savedVis.id);
|
||||
const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave;
|
||||
stateContainer.transitions.setVis({
|
||||
title: savedVis.title,
|
||||
type: savedVis.type || stateContainer.getState().vis.type,
|
||||
|
@ -680,16 +679,14 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
// Manually insert a new url so the back button will open the saved visualization.
|
||||
history.replace(appPath);
|
||||
setActiveUrl(appPath);
|
||||
const lastAppType = $scope.getOriginatingApp();
|
||||
|
||||
if (lastAppType === 'dashboards') {
|
||||
const savedVisId = firstSave || savedVis.copyOnSave ? savedVis.id : '';
|
||||
dashboard.addEmbeddableToDashboard({
|
||||
embeddableId: savedVisId,
|
||||
embeddableType: VISUALIZE_EMBEDDABLE_TYPE,
|
||||
});
|
||||
if (newlyCreated && embeddable) {
|
||||
embeddable
|
||||
.getStateTransfer()
|
||||
.navigateToWithEmbeddablePackage($scope.getOriginatingApp(), {
|
||||
state: { id: savedVis.id, type: VISUALIZE_EMBEDDABLE_TYPE },
|
||||
});
|
||||
} else {
|
||||
application.navigateToApp(lastAppType);
|
||||
application.navigateToApp($scope.getOriginatingApp());
|
||||
}
|
||||
} else if (savedVis.id === $route.current.params.id) {
|
||||
chrome.docTitle.change(savedVis.lastSavedTitle);
|
||||
|
|
|
@ -34,8 +34,8 @@ import { DataPublicPluginStart } from '../../data/public';
|
|||
import { VisualizationsStart } from '../../visualizations/public';
|
||||
import { SavedVisualizations } from './application/types';
|
||||
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
||||
import { DashboardStart } from '../../dashboard/public';
|
||||
import { SavedObjectsStart } from '../../saved_objects/public';
|
||||
import { EmbeddableStart } from '../../embeddable/public';
|
||||
|
||||
export interface VisualizeKibanaServices {
|
||||
pluginInitializerContext: PluginInitializerContext;
|
||||
|
@ -52,7 +52,7 @@ export interface VisualizeKibanaServices {
|
|||
kibanaLegacy: KibanaLegacyStart;
|
||||
visualizeCapabilities: any;
|
||||
visualizations: VisualizationsStart;
|
||||
dashboard: DashboardStart;
|
||||
embeddable: EmbeddableStart;
|
||||
I18nContext: I18nStart['Context'];
|
||||
setActiveUrl: (newUrl: string) => void;
|
||||
createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject'];
|
||||
|
|
|
@ -40,16 +40,16 @@ import { VisualizationsStart } from '../../visualizations/public';
|
|||
import { VisualizeConstants } from './application/visualize_constants';
|
||||
import { setServices, VisualizeKibanaServices } from './kibana_services';
|
||||
import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public';
|
||||
import { DashboardStart } from '../../dashboard/public';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
|
||||
import { SavedObjectsStart } from '../../saved_objects/public';
|
||||
import { EmbeddableStart } from '../../embeddable/public';
|
||||
|
||||
export interface VisualizePluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
navigation: NavigationStart;
|
||||
share?: SharePluginStart;
|
||||
visualizations: VisualizationsStart;
|
||||
dashboard: DashboardStart;
|
||||
embeddable: EmbeddableStart;
|
||||
kibanaLegacy: KibanaLegacyStart;
|
||||
savedObjects: SavedObjectsStart;
|
||||
}
|
||||
|
@ -129,11 +129,11 @@ export class VisualizePlugin
|
|||
toastNotifications: coreStart.notifications.toasts,
|
||||
visualizeCapabilities: coreStart.application.capabilities.visualize,
|
||||
visualizations: pluginsStart.visualizations,
|
||||
embeddable: pluginsStart.embeddable,
|
||||
I18nContext: coreStart.i18n.Context,
|
||||
setActiveUrl,
|
||||
createVisEmbeddableFromObject:
|
||||
pluginsStart.visualizations.__LEGACY.createVisEmbeddableFromObject,
|
||||
dashboard: pluginsStart.dashboard,
|
||||
scopedHistory: () => this.currentHistory!,
|
||||
savedObjects: pluginsStart.savedObjects,
|
||||
};
|
||||
|
|
|
@ -124,12 +124,7 @@ describe('Lens App', () => {
|
|||
storage: Storage;
|
||||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (
|
||||
id?: string,
|
||||
returnToOrigin?: boolean,
|
||||
originatingApp?: string | undefined,
|
||||
newlyCreated?: boolean
|
||||
) => void;
|
||||
redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void;
|
||||
originatingApp: string | undefined;
|
||||
onAppLeave: AppMountParameters['onAppLeave'];
|
||||
history: History;
|
||||
|
@ -168,14 +163,7 @@ describe('Lens App', () => {
|
|||
load: jest.fn(),
|
||||
save: jest.fn(),
|
||||
},
|
||||
redirectTo: jest.fn(
|
||||
(
|
||||
id?: string,
|
||||
returnToOrigin?: boolean,
|
||||
originatingApp?: string | undefined,
|
||||
newlyCreated?: boolean
|
||||
) => {}
|
||||
),
|
||||
redirectTo: jest.fn((id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => {}),
|
||||
onAppLeave: jest.fn(),
|
||||
history: createMemoryHistory(),
|
||||
} as unknown) as jest.Mocked<{
|
||||
|
@ -186,12 +174,7 @@ describe('Lens App', () => {
|
|||
storage: Storage;
|
||||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (
|
||||
id?: string,
|
||||
returnToOrigin?: boolean,
|
||||
originatingApp?: string | undefined,
|
||||
newlyCreated?: boolean
|
||||
) => void;
|
||||
redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void;
|
||||
originatingApp: string | undefined;
|
||||
onAppLeave: AppMountParameters['onAppLeave'];
|
||||
history: History;
|
||||
|
@ -533,7 +516,7 @@ describe('Lens App', () => {
|
|||
expression: 'kibana 3',
|
||||
});
|
||||
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, undefined, true);
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true);
|
||||
|
||||
inst.setProps({ docId: 'aaa' });
|
||||
|
||||
|
@ -553,7 +536,7 @@ describe('Lens App', () => {
|
|||
expression: 'kibana 3',
|
||||
});
|
||||
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, undefined, true);
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true);
|
||||
|
||||
inst.setProps({ docId: 'aaa' });
|
||||
|
||||
|
@ -621,7 +604,7 @@ describe('Lens App', () => {
|
|||
title: 'hello there',
|
||||
});
|
||||
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, undefined, true);
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, true);
|
||||
});
|
||||
|
||||
it('saves app filters and does not save pinned filters', async () => {
|
||||
|
|
|
@ -43,7 +43,6 @@ interface State {
|
|||
isLoading: boolean;
|
||||
isSaveModalVisible: boolean;
|
||||
indexPatternsForTopNav: IndexPatternInstance[];
|
||||
originatingApp: string | undefined;
|
||||
persistedDoc?: Document;
|
||||
lastKnownDoc?: Document;
|
||||
|
||||
|
@ -65,7 +64,7 @@ export function App({
|
|||
docId,
|
||||
docStorage,
|
||||
redirectTo,
|
||||
originatingAppFromUrl,
|
||||
originatingApp,
|
||||
navigation,
|
||||
onAppLeave,
|
||||
history,
|
||||
|
@ -77,13 +76,8 @@ export function App({
|
|||
storage: IStorageWrapper;
|
||||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (
|
||||
id?: string,
|
||||
returnToOrigin?: boolean,
|
||||
originatingApp?: string | undefined,
|
||||
newlyCreated?: boolean
|
||||
) => void;
|
||||
originatingAppFromUrl?: string | undefined;
|
||||
redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void;
|
||||
originatingApp?: string | undefined;
|
||||
onAppLeave: AppMountParameters['onAppLeave'];
|
||||
history: History;
|
||||
}) {
|
||||
|
@ -98,7 +92,6 @@ export function App({
|
|||
isSaveModalVisible: false,
|
||||
indexPatternsForTopNav: [],
|
||||
query: { query: '', language },
|
||||
originatingApp: originatingAppFromUrl,
|
||||
dateRange: {
|
||||
fromDate: currentRange.from,
|
||||
toDate: currentRange.to,
|
||||
|
@ -316,7 +309,7 @@ export function App({
|
|||
lastKnownDoc: newDoc,
|
||||
}));
|
||||
if (docId !== id || saveProps.returnToOrigin) {
|
||||
redirectTo(id, saveProps.returnToOrigin, state.originatingApp, newlyCreated);
|
||||
redirectTo(id, saveProps.returnToOrigin, newlyCreated);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
|
@ -356,7 +349,7 @@ export function App({
|
|||
<div className="lnsApp__header">
|
||||
<TopNavMenu
|
||||
config={[
|
||||
...(!!state.originatingApp && lastKnownDoc?.id
|
||||
...(!!originatingApp && lastKnownDoc?.id
|
||||
? [
|
||||
{
|
||||
label: i18n.translate('xpack.lens.app.saveAndReturn', {
|
||||
|
@ -381,14 +374,14 @@ export function App({
|
|||
: []),
|
||||
{
|
||||
label:
|
||||
lastKnownDoc?.id && !!state.originatingApp
|
||||
lastKnownDoc?.id && !!originatingApp
|
||||
? i18n.translate('xpack.lens.app.saveAs', {
|
||||
defaultMessage: 'Save as',
|
||||
})
|
||||
: i18n.translate('xpack.lens.app.save', {
|
||||
defaultMessage: 'Save',
|
||||
}),
|
||||
emphasize: !state.originatingApp || !lastKnownDoc?.id,
|
||||
emphasize: !originatingApp || !lastKnownDoc?.id,
|
||||
run: () => {
|
||||
if (isSaveable && lastKnownDoc) {
|
||||
setState((s) => ({ ...s, isSaveModalVisible: true }));
|
||||
|
@ -509,7 +502,7 @@ export function App({
|
|||
</div>
|
||||
{lastKnownDoc && state.isSaveModalVisible && (
|
||||
<SavedObjectSaveModalOrigin
|
||||
originatingApp={state.originatingApp}
|
||||
originatingApp={originatingApp}
|
||||
onSave={(props) => runSave(props)}
|
||||
onClose={() => setState((s) => ({ ...s, isSaveModalVisible: false }))}
|
||||
documentInfo={{
|
||||
|
|
|
@ -10,9 +10,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
|||
import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { parse } from 'query-string';
|
||||
|
||||
import { removeQueryParam, Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry';
|
||||
|
||||
|
@ -29,7 +28,7 @@ export async function mountApp(
|
|||
createEditorFrame: EditorFrameStart['createInstance']
|
||||
) {
|
||||
const [coreStart, startDependencies] = await core.getStartServices();
|
||||
const { data: dataStart, navigation } = startDependencies;
|
||||
const { data: dataStart, navigation, embeddable } = startDependencies;
|
||||
const savedObjectsClient = coreStart.savedObjects.client;
|
||||
addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks);
|
||||
|
||||
|
@ -37,6 +36,10 @@ export async function mountApp(
|
|||
i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' })
|
||||
);
|
||||
|
||||
const stateTransfer = embeddable?.getStateTransfer(params.history);
|
||||
const { originatingApp } =
|
||||
stateTransfer?.getIncomingOriginatingApp({ keysToRemoveAfterFetch: ['originatingApp'] }) || {};
|
||||
|
||||
const instance = await createEditorFrame();
|
||||
|
||||
setReportManager(
|
||||
|
@ -49,7 +52,6 @@ export async function mountApp(
|
|||
routeProps: RouteComponentProps<{ id?: string }>,
|
||||
id?: string,
|
||||
returnToOrigin?: boolean,
|
||||
originatingApp?: string,
|
||||
newlyCreated?: boolean
|
||||
) => {
|
||||
if (!id) {
|
||||
|
@ -59,11 +61,9 @@ export async function mountApp(
|
|||
} else if (!!originatingApp && id && returnToOrigin) {
|
||||
routeProps.history.push(`/edit/${id}`);
|
||||
|
||||
if (originatingApp === 'dashboards') {
|
||||
const addLensId = newlyCreated ? id : '';
|
||||
startDependencies.dashboard.addEmbeddableToDashboard({
|
||||
embeddableId: addLensId,
|
||||
embeddableType: LENS_EMBEDDABLE_TYPE,
|
||||
if (newlyCreated && stateTransfer) {
|
||||
stateTransfer.navigateToWithEmbeddablePackage(originatingApp, {
|
||||
state: { id, type: LENS_EMBEDDABLE_TYPE },
|
||||
});
|
||||
} else {
|
||||
coreStart.application.navigateToApp(originatingApp);
|
||||
|
@ -73,11 +73,6 @@ export async function mountApp(
|
|||
|
||||
const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
|
||||
trackUiEvent('loaded');
|
||||
const urlParams = parse(routeProps.location.search) as Record<string, string>;
|
||||
const originatingAppFromUrl = urlParams.embeddableOriginatingApp;
|
||||
if (urlParams.embeddableOriginatingApp) {
|
||||
removeQueryParam(routeProps.history, 'embeddableOriginatingApp');
|
||||
}
|
||||
|
||||
return (
|
||||
<App
|
||||
|
@ -88,10 +83,10 @@ export async function mountApp(
|
|||
storage={new Storage(localStorage)}
|
||||
docId={routeProps.match.params.id}
|
||||
docStorage={new SavedObjectIndexStore(savedObjectsClient)}
|
||||
redirectTo={(id, returnToOrigin, originatingApp, newlyCreated) =>
|
||||
redirectTo(routeProps, id, returnToOrigin, originatingApp, newlyCreated)
|
||||
redirectTo={(id, returnToOrigin, newlyCreated) =>
|
||||
redirectTo(routeProps, id, returnToOrigin, newlyCreated)
|
||||
}
|
||||
originatingAppFromUrl={originatingAppFromUrl}
|
||||
originatingApp={originatingApp}
|
||||
onAppLeave={params.onAppLeave}
|
||||
history={routeProps.history}
|
||||
/>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
|
||||
import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
import { EmbeddableSetup } from 'src/plugins/embeddable/public';
|
||||
import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
|
||||
import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public';
|
||||
import { VisualizationsSetup } from 'src/plugins/visualizations/public';
|
||||
import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
|
||||
|
@ -26,7 +26,6 @@ import { EditorFrameStart } from './types';
|
|||
import { getLensAliasConfig } from './vis_type_alias';
|
||||
|
||||
import './index.scss';
|
||||
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
|
||||
|
||||
export interface LensPluginSetupDependencies {
|
||||
kibanaLegacy: KibanaLegacySetup;
|
||||
|
@ -41,7 +40,7 @@ export interface LensPluginStartDependencies {
|
|||
expressions: ExpressionsStart;
|
||||
navigation: NavigationPublicPluginStart;
|
||||
uiActions: UiActionsStart;
|
||||
dashboard: DashboardStart;
|
||||
embeddable: EmbeddableStart;
|
||||
}
|
||||
|
||||
export class LensPlugin {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue