mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Embeddables rebuild] Decouple add panel trigger (#176110)
Decouples the `ADD_PANEL_TRIGGER` from the Embeddables framework by making it take a `PresentationContainer` as context instead.
This commit is contained in:
parent
2735882951
commit
23b4b538f8
22 changed files with 391 additions and 310 deletions
|
@ -62,7 +62,7 @@ export class SimpleEmbeddableFactoryDefinition
|
|||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableExamples.migrations.displayName', {
|
||||
defaultMessage: 'hello world',
|
||||
defaultMessage: 'simple migration embeddable',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ import {
|
|||
FilterDebuggerEmbeddableFactory,
|
||||
FilterDebuggerEmbeddableFactoryDefinition,
|
||||
} from './filter_debugger';
|
||||
import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown_react_embeddable';
|
||||
import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown/eui_markdown_react_embeddable';
|
||||
import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action';
|
||||
|
||||
export interface EmbeddableExamplesSetupDependencies {
|
||||
embeddable: EmbeddableSetup;
|
||||
|
@ -54,8 +55,6 @@ export interface EmbeddableExamplesStart {
|
|||
factories: ExampleEmbeddableFactories;
|
||||
}
|
||||
|
||||
registerMarkdownEditorEmbeddable();
|
||||
|
||||
export class EmbeddableExamplesPlugin
|
||||
implements
|
||||
Plugin<
|
||||
|
@ -71,6 +70,9 @@ export class EmbeddableExamplesPlugin
|
|||
core: CoreSetup<EmbeddableExamplesStartDependencies>,
|
||||
deps: EmbeddableExamplesSetupDependencies
|
||||
) {
|
||||
registerMarkdownEditorEmbeddable();
|
||||
registerCreateEuiMarkdownAction(deps.uiActions);
|
||||
|
||||
this.exampleEmbeddableFactories.getHelloWorldEmbeddableFactory =
|
||||
deps.embeddable.registerEmbeddableFactory(
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const EUI_MARKDOWN_ID = 'euiMarkdown';
|
||||
export const ADD_EUI_MARKDOWN_ACTION_ID = 'create_eui_markdown';
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import { ADD_EUI_MARKDOWN_ACTION_ID, EUI_MARKDOWN_ID } from './constants';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Create and register an action which allows this embeddable to be created from
|
||||
// the dashboard toolbar context menu.
|
||||
// -----------------------------------------------------------------------------
|
||||
export const registerCreateEuiMarkdownAction = (uiActions: UiActionsStart) => {
|
||||
uiActions.registerAction<EmbeddableApiContext>({
|
||||
id: ADD_EUI_MARKDOWN_ACTION_ID,
|
||||
getIconType: () => 'editorCodeBlock',
|
||||
isCompatible: async ({ embeddable }) => {
|
||||
return apiIsPresentationContainer(embeddable);
|
||||
},
|
||||
execute: async ({ embeddable }) => {
|
||||
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
|
||||
embeddable.addNewPanel(
|
||||
{
|
||||
panelType: EUI_MARKDOWN_ID,
|
||||
initialState: { content: '# hello world!' },
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
getDisplayName: () =>
|
||||
i18n.translate('embeddableExamples.euiMarkdownEditor.ariaLabel', {
|
||||
defaultMessage: 'EUI Markdown',
|
||||
}),
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_EUI_MARKDOWN_ACTION_ID);
|
||||
};
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiMarkdownEditor, EuiMarkdownFormat } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
initializeReactEmbeddableTitles,
|
||||
initializeReactEmbeddableUuid,
|
||||
ReactEmbeddableFactory,
|
||||
RegisterReactEmbeddable,
|
||||
registerReactEmbeddableFactory,
|
||||
useReactEmbeddableApiHandle,
|
||||
useReactEmbeddableUnsavedChanges,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useInheritedViewMode, useStateFromPublishingSubject } from '@kbn/presentation-publishing';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import React from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { EUI_MARKDOWN_ID } from './constants';
|
||||
import { MarkdownEditorSerializedState, MarkdownEditorApi } from './types';
|
||||
|
||||
export const registerMarkdownEditorEmbeddable = () => {
|
||||
const markdownEmbeddableFactory: ReactEmbeddableFactory<
|
||||
MarkdownEditorSerializedState,
|
||||
MarkdownEditorApi
|
||||
> = {
|
||||
deserializeState: (state) => {
|
||||
/**
|
||||
* Here we can run migrations and inject references.
|
||||
*/
|
||||
return state.rawState as MarkdownEditorSerializedState;
|
||||
},
|
||||
getComponent: async (state, maybeId) => {
|
||||
/**
|
||||
* initialize state (source of truth)
|
||||
*/
|
||||
const uuid = initializeReactEmbeddableUuid(maybeId);
|
||||
const { titlesApi, titleComparators, serializeTitles } =
|
||||
initializeReactEmbeddableTitles(state);
|
||||
const contentSubject = new BehaviorSubject(state.content);
|
||||
|
||||
/**
|
||||
* getComponent is async so you can async import the component or load a saved object here.
|
||||
* the loading will be handed gracefully by the Presentation Container.
|
||||
*/
|
||||
|
||||
return RegisterReactEmbeddable((apiRef) => {
|
||||
/**
|
||||
* Unsaved changes logic is handled automatically by this hook. You only need to provide
|
||||
* a subject, setter, and optional state comparator for each key in your state type.
|
||||
*/
|
||||
const { unsavedChanges, resetUnsavedChanges } = useReactEmbeddableUnsavedChanges(
|
||||
uuid,
|
||||
markdownEmbeddableFactory,
|
||||
{
|
||||
content: [contentSubject, (value) => contentSubject.next(value)],
|
||||
...titleComparators,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish the API. This is what gets forwarded to the Actions framework, and to whatever the
|
||||
* parent of this embeddable is.
|
||||
*/
|
||||
const thisApi = useReactEmbeddableApiHandle(
|
||||
{
|
||||
...titlesApi,
|
||||
unsavedChanges,
|
||||
resetUnsavedChanges,
|
||||
serializeState: async () => {
|
||||
return {
|
||||
rawState: {
|
||||
...serializeTitles(),
|
||||
content: contentSubject.getValue(),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
apiRef,
|
||||
uuid
|
||||
);
|
||||
|
||||
// get state for rendering
|
||||
const content = useStateFromPublishingSubject(contentSubject);
|
||||
const viewMode = useInheritedViewMode(thisApi) ?? 'view';
|
||||
|
||||
return viewMode === 'edit' ? (
|
||||
<EuiMarkdownEditor
|
||||
css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
value={content ?? ''}
|
||||
onChange={(value) => contentSubject.next(value)}
|
||||
aria-label={i18n.translate('embeddableExamples.euiMarkdownEditor.ariaLabel', {
|
||||
defaultMessage: 'Dashboard markdown editor',
|
||||
})}
|
||||
height="full"
|
||||
/>
|
||||
) : (
|
||||
<EuiMarkdownFormat
|
||||
css={css`
|
||||
padding: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
{content ?? ''}
|
||||
</EuiMarkdownFormat>
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Register the defined Embeddable Factory - notice that this isn't defined
|
||||
* on the plugin. Instead, it's a simple imported function. I.E to register an
|
||||
* embeddable, you only need the embeddable plugin in your requiredBundles
|
||||
*/
|
||||
registerReactEmbeddableFactory(EUI_MARKDOWN_ID, markdownEmbeddableFactory);
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
DefaultEmbeddableApi,
|
||||
SerializedReactEmbeddableTitles,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
|
||||
export type MarkdownEditorSerializedState = SerializedReactEmbeddableTitles & {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type MarkdownEditorApi = DefaultEmbeddableApi;
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiMarkdownEditor, EuiMarkdownFormat } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import {
|
||||
ReactEmbeddableFactory,
|
||||
RegisterReactEmbeddable,
|
||||
registerReactEmbeddableFactory,
|
||||
useReactEmbeddableApiHandle,
|
||||
initializeReactEmbeddableUuid,
|
||||
initializeReactEmbeddableTitles,
|
||||
SerializedReactEmbeddableTitles,
|
||||
DefaultEmbeddableApi,
|
||||
useReactEmbeddableUnsavedChanges,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useInheritedViewMode, useStateFromPublishingSubject } from '@kbn/presentation-publishing';
|
||||
import React from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Types for this embeddable
|
||||
// -----------------------------------------------------------------------------
|
||||
type MarkdownEditorSerializedState = SerializedReactEmbeddableTitles & {
|
||||
content: string;
|
||||
};
|
||||
|
||||
type MarkdownEditorApi = DefaultEmbeddableApi;
|
||||
|
||||
const type = 'euiMarkdown';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Define the Embeddable Factory
|
||||
// -----------------------------------------------------------------------------
|
||||
const markdownEmbeddableFactory: ReactEmbeddableFactory<
|
||||
MarkdownEditorSerializedState,
|
||||
MarkdownEditorApi
|
||||
> = {
|
||||
// -----------------------------------------------------------------------------
|
||||
// Deserialize function
|
||||
// -----------------------------------------------------------------------------
|
||||
deserializeState: (state) => {
|
||||
// We could run migrations here.
|
||||
// We should inject references here. References are given as state.references
|
||||
|
||||
return state.rawState as MarkdownEditorSerializedState;
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Register the Embeddable component
|
||||
// -----------------------------------------------------------------------------
|
||||
getComponent: async (state, maybeId) => {
|
||||
/**
|
||||
* initialize state (source of truth)
|
||||
*/
|
||||
const uuid = initializeReactEmbeddableUuid(maybeId);
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeReactEmbeddableTitles(state);
|
||||
const contentSubject = new BehaviorSubject(state.content);
|
||||
|
||||
/**
|
||||
* getComponent is async so you can async import the component or load a saved object here.
|
||||
* the loading will be handed gracefully by the Presentation Container.
|
||||
*/
|
||||
|
||||
return RegisterReactEmbeddable((apiRef) => {
|
||||
/**
|
||||
* Unsaved changes logic is handled automatically by this hook. You only need to provide
|
||||
* a subject, setter, and optional state comparator for each key in your state type.
|
||||
*/
|
||||
const { unsavedChanges, resetUnsavedChanges } = useReactEmbeddableUnsavedChanges(
|
||||
uuid,
|
||||
markdownEmbeddableFactory,
|
||||
{
|
||||
content: [contentSubject, (value) => contentSubject.next(value)],
|
||||
...titleComparators,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish the API. This is what gets forwarded to the Actions framework, and to whatever the
|
||||
* parent of this embeddable is.
|
||||
*/
|
||||
const thisApi = useReactEmbeddableApiHandle(
|
||||
{
|
||||
...titlesApi,
|
||||
unsavedChanges,
|
||||
resetUnsavedChanges,
|
||||
serializeState: async () => {
|
||||
return {
|
||||
rawState: {
|
||||
...serializeTitles(),
|
||||
content: contentSubject.getValue(),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
apiRef,
|
||||
uuid
|
||||
);
|
||||
|
||||
// get state for rendering
|
||||
const content = useStateFromPublishingSubject(contentSubject);
|
||||
const viewMode = useInheritedViewMode(thisApi) ?? 'view';
|
||||
|
||||
return viewMode === 'edit' ? (
|
||||
<EuiMarkdownEditor
|
||||
css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
value={content ?? ''}
|
||||
onChange={(value) => contentSubject.next(value)}
|
||||
aria-label={i18n.translate('dashboard.test.markdownEditor.ariaLabel', {
|
||||
defaultMessage: 'Dashboard markdown editor',
|
||||
})}
|
||||
height="full"
|
||||
/>
|
||||
) : (
|
||||
<EuiMarkdownFormat
|
||||
css={css`
|
||||
padding: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
{content ?? ''}
|
||||
</EuiMarkdownFormat>
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Register the defined Embeddable Factory - notice that this isn't defined
|
||||
// on the plugin. Instead, it's a simple imported function. I.E to register an
|
||||
// Embeddable, you only need the embeddable plugin in your requiredBundles
|
||||
// -----------------------------------------------------------------------------
|
||||
export const registerMarkdownEditorEmbeddable = () =>
|
||||
registerReactEmbeddableFactory(type, markdownEmbeddableFactory);
|
|
@ -20,6 +20,7 @@
|
|||
"@kbn/presentation-publishing",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/i18n",
|
||||
"@kbn/es-query"
|
||||
"@kbn/es-query",
|
||||
"@kbn/presentation-containers"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export const getLastSavedStateSubjectForChild = <StateType extends unknown = unk
|
|||
deserializer?: (state: SerializedPanelState) => StateType
|
||||
): PublishingSubject<StateType | undefined> | undefined => {
|
||||
if (!parentApi) return;
|
||||
const fetchUnsavedChanges = (): StateType | undefined => {
|
||||
const fetchLastSavedState = (): StateType | undefined => {
|
||||
if (!apiPublishesLastSavedState(parentApi)) return;
|
||||
const rawLastSavedState = parentApi.getLastSavedStateForChild(childId);
|
||||
if (rawLastSavedState === undefined) return;
|
||||
|
@ -39,11 +39,11 @@ export const getLastSavedStateSubjectForChild = <StateType extends unknown = unk
|
|||
: (rawLastSavedState.rawState as StateType);
|
||||
};
|
||||
|
||||
const lastSavedStateForChild = new BehaviorSubject<StateType | undefined>(fetchUnsavedChanges());
|
||||
const lastSavedStateForChild = new BehaviorSubject<StateType | undefined>(fetchLastSavedState());
|
||||
if (!apiPublishesLastSavedState(parentApi)) return;
|
||||
parentApi.lastSavedState
|
||||
.pipe(
|
||||
map(() => fetchUnsavedChanges()),
|
||||
map(() => fetchLastSavedState()),
|
||||
filter((rawLastSavedState) => rawLastSavedState !== undefined)
|
||||
)
|
||||
.subscribe(lastSavedStateForChild);
|
||||
|
|
|
@ -11,11 +11,15 @@ import { PublishesLastSavedState } from './last_saved_state';
|
|||
|
||||
export interface PanelPackage {
|
||||
panelType: string;
|
||||
initialState: unknown;
|
||||
initialState?: object;
|
||||
}
|
||||
|
||||
export type PresentationContainer = Partial<PublishesViewMode> &
|
||||
PublishesLastSavedState & {
|
||||
addNewPanel: <ApiType extends unknown = unknown>(
|
||||
panel: PanelPackage,
|
||||
displaySuccessMessage?: boolean
|
||||
) => Promise<ApiType | undefined>;
|
||||
registerPanelApi: <ApiType extends unknown = unknown>(
|
||||
panelId: string,
|
||||
panelApi: ApiType
|
||||
|
@ -31,7 +35,8 @@ export const apiIsPresentationContainer = (
|
|||
return Boolean(
|
||||
(unknownApi as PresentationContainer)?.removePanel !== undefined &&
|
||||
(unknownApi as PresentationContainer)?.registerPanelApi !== undefined &&
|
||||
(unknownApi as PresentationContainer)?.replacePanel !== undefined
|
||||
(unknownApi as PresentationContainer)?.replacePanel !== undefined &&
|
||||
(unknownApi as PresentationContainer)?.addNewPanel !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@ import { PresentationContainer } from './interfaces/presentation_container';
|
|||
|
||||
export const getMockPresentationContainer = (): PresentationContainer => {
|
||||
return {
|
||||
registerPanelApi: jest.fn(),
|
||||
removePanel: jest.fn(),
|
||||
addNewPanel: jest.fn(),
|
||||
replacePanel: jest.fn(),
|
||||
registerPanelApi: jest.fn(),
|
||||
lastSavedState: new Subject<void>(),
|
||||
getLastSavedStateForChild: jest.fn(),
|
||||
};
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
*/
|
||||
|
||||
export interface EmbeddableApiContext {
|
||||
/**
|
||||
* TODO: once all actions are entirely decoupled from the embeddable system, this key should be renamed to "api"
|
||||
* to reflect the fact that this context could contain any api.
|
||||
*/
|
||||
embeddable: unknown;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
|
||||
import { getAddPanelActionMenuItems } from './add_panel_action_menu_items';
|
||||
|
||||
describe('getAddPanelActionMenuItems', () => {
|
||||
|
@ -21,7 +22,11 @@ describe('getAddPanelActionMenuItems', () => {
|
|||
execute: jest.fn(),
|
||||
},
|
||||
];
|
||||
const items = getAddPanelActionMenuItems(registeredActions, jest.fn(), jest.fn(), jest.fn());
|
||||
const items = getAddPanelActionMenuItems(
|
||||
getMockPresentationContainer(),
|
||||
registeredActions,
|
||||
jest.fn()
|
||||
);
|
||||
expect(items).toStrictEqual([
|
||||
{
|
||||
'data-test-subj': 'create-action-Action name',
|
||||
|
@ -34,7 +39,7 @@ describe('getAddPanelActionMenuItems', () => {
|
|||
});
|
||||
|
||||
it('returns empty array if no actions have been registered', async () => {
|
||||
const items = getAddPanelActionMenuItems([], jest.fn(), jest.fn(), jest.fn());
|
||||
const items = getAddPanelActionMenuItems(getMockPresentationContainer(), [], jest.fn());
|
||||
expect(items).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { ActionExecutionContext, Action } from '@kbn/ui-actions-plugin/public';
|
||||
import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { addPanelMenuTrigger } from '../../triggers';
|
||||
|
||||
const onAddPanelActionClick =
|
||||
|
@ -27,16 +27,14 @@ const onAddPanelActionClick =
|
|||
};
|
||||
|
||||
export const getAddPanelActionMenuItems = (
|
||||
api: PresentationContainer,
|
||||
actions: Array<Action<object>> | undefined,
|
||||
createNewEmbeddable: (embeddableFactory: EmbeddableFactory) => void,
|
||||
deleteEmbeddable: (embeddableId: string) => void,
|
||||
closePopover: () => void
|
||||
) => {
|
||||
return (
|
||||
actions?.map((item) => {
|
||||
const context = {
|
||||
createNewEmbeddable,
|
||||
deleteEmbeddable,
|
||||
embeddable: api,
|
||||
trigger: addPanelMenuTrigger,
|
||||
};
|
||||
const actionName = item.getDisplayName(context);
|
||||
|
|
|
@ -11,9 +11,7 @@ import { METRIC_TYPE } from '@kbn/analytics';
|
|||
import { useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar';
|
||||
import { EmbeddableFactory, EmbeddableInput } from '@kbn/embeddable-plugin/public';
|
||||
import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import { isExplicitInputWithAttributes } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings';
|
||||
import { EditorMenu } from './editor_menu';
|
||||
|
@ -21,13 +19,11 @@ import { useDashboardAPI } from '../dashboard_app';
|
|||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { ControlsToolbarButton } from './controls_toolbar_button';
|
||||
import { DASHBOARD_UI_METRIC_ID } from '../../dashboard_constants';
|
||||
import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings';
|
||||
|
||||
export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }) {
|
||||
const {
|
||||
usageCollection,
|
||||
data: { search },
|
||||
notifications: { toasts },
|
||||
embeddable: { getStateTransfer },
|
||||
visualizations: { getAliases: getVisTypeAliases },
|
||||
} = pluginServices.getServices();
|
||||
|
@ -85,74 +81,9 @@ export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }
|
|||
* initialInput: Optional, use it in case you want to pass your own input to the factory
|
||||
* dismissNotification: Optional, if not passed a toast will appear in the dashboard
|
||||
*/
|
||||
const createNewEmbeddable = useCallback(
|
||||
async (
|
||||
embeddableFactory: EmbeddableFactory,
|
||||
initialInput?: Partial<EmbeddableInput>,
|
||||
dismissNotification?: boolean
|
||||
) => {
|
||||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, embeddableFactory.type);
|
||||
}
|
||||
|
||||
let explicitInput: Partial<EmbeddableInput>;
|
||||
let attributes: unknown;
|
||||
try {
|
||||
if (initialInput) {
|
||||
explicitInput = initialInput;
|
||||
} else {
|
||||
const explicitInputReturn = await embeddableFactory.getExplicitInput(
|
||||
undefined,
|
||||
dashboard
|
||||
);
|
||||
if (isExplicitInputWithAttributes(explicitInputReturn)) {
|
||||
explicitInput = explicitInputReturn.newInput;
|
||||
attributes = explicitInputReturn.attributes;
|
||||
} else {
|
||||
explicitInput = explicitInputReturn;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// error likely means user canceled embeddable creation
|
||||
return;
|
||||
}
|
||||
|
||||
const newEmbeddable = await dashboard.addNewEmbeddable(
|
||||
embeddableFactory.type,
|
||||
explicitInput,
|
||||
attributes
|
||||
);
|
||||
|
||||
if (newEmbeddable) {
|
||||
dashboard.setScrollToPanelId(newEmbeddable.id);
|
||||
dashboard.setHighlightPanelId(newEmbeddable.id);
|
||||
|
||||
if (!dismissNotification) {
|
||||
toasts.addSuccess({
|
||||
title: dashboardReplacePanelActionStrings.getSuccessMessage(newEmbeddable.getTitle()),
|
||||
'data-test-subj': 'addEmbeddableToDashboardSuccess',
|
||||
});
|
||||
}
|
||||
}
|
||||
return newEmbeddable;
|
||||
},
|
||||
[trackUiMetric, dashboard, toasts]
|
||||
);
|
||||
|
||||
const deleteEmbeddable = useCallback(
|
||||
(embeddableId: string) => {
|
||||
dashboard.removeEmbeddable(embeddableId);
|
||||
},
|
||||
[dashboard]
|
||||
);
|
||||
|
||||
const extraButtons = [
|
||||
<EditorMenu
|
||||
createNewVisType={createNewVisType}
|
||||
createNewEmbeddable={createNewEmbeddable}
|
||||
deleteEmbeddable={deleteEmbeddable}
|
||||
isDisabled={isDisabled}
|
||||
/>,
|
||||
<EditorMenu createNewVisType={createNewVisType} isDisabled={isDisabled} api={dashboard} />,
|
||||
<AddFromLibraryButton
|
||||
onClick={() => dashboard.addFromLibrary()}
|
||||
size="s"
|
||||
|
|
|
@ -17,25 +17,15 @@ import {
|
|||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar';
|
||||
import type { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { type BaseVisType, VisGroups, type VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { DASHBOARD_APP_ID } from '../../dashboard_constants';
|
||||
import { ADD_PANEL_TRIGGER } from '../../triggers';
|
||||
import { getAddPanelActionMenuItems } from './add_panel_action_menu_items';
|
||||
|
||||
interface Props {
|
||||
isDisabled?: boolean;
|
||||
/** Handler for creating new visualization of a specified type */
|
||||
createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void;
|
||||
/** Handler for creating a new embeddable of a specified type */
|
||||
createNewEmbeddable: (embeddableFactory: EmbeddableFactory) => void;
|
||||
/** Handler for deleting an embeddable */
|
||||
deleteEmbeddable: (embeddableId: string) => void;
|
||||
}
|
||||
|
||||
interface FactoryGroup {
|
||||
id: string;
|
||||
appName: string;
|
||||
|
@ -51,10 +41,14 @@ interface UnwrappedEmbeddableFactory {
|
|||
|
||||
export const EditorMenu = ({
|
||||
createNewVisType,
|
||||
createNewEmbeddable,
|
||||
deleteEmbeddable,
|
||||
isDisabled,
|
||||
}: Props) => {
|
||||
api,
|
||||
}: {
|
||||
api: PresentationContainer;
|
||||
isDisabled?: boolean;
|
||||
/** Handler for creating new visualization of a specified type */
|
||||
createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void;
|
||||
}) => {
|
||||
const isMounted = useRef(false);
|
||||
const {
|
||||
embeddable,
|
||||
|
@ -148,16 +142,15 @@ export const EditorMenu = ({
|
|||
// Retrieve ADD_PANEL_TRIGGER actions
|
||||
useEffect(() => {
|
||||
async function loadPanelActions() {
|
||||
const registeredActions = await uiActions?.getTriggerCompatibleActions?.(
|
||||
ADD_PANEL_TRIGGER,
|
||||
{}
|
||||
);
|
||||
const registeredActions = await uiActions?.getTriggerCompatibleActions?.(ADD_PANEL_TRIGGER, {
|
||||
embeddable: api,
|
||||
});
|
||||
if (isMounted.current) {
|
||||
setAddPanelActions(registeredActions);
|
||||
}
|
||||
}
|
||||
loadPanelActions();
|
||||
}, [uiActions]);
|
||||
}, [uiActions, api]);
|
||||
|
||||
factories.forEach(({ factory }) => {
|
||||
const { grouping } = factory;
|
||||
|
@ -249,7 +242,7 @@ export const EditorMenu = ({
|
|||
toolTipContent,
|
||||
onClick: async () => {
|
||||
closePopover();
|
||||
createNewEmbeddable(factory);
|
||||
api.addNewPanel({ panelType: factory.type }, true);
|
||||
},
|
||||
'data-test-subj': `createNew-${factory.type}`,
|
||||
};
|
||||
|
@ -274,12 +267,7 @@ export const EditorMenu = ({
|
|||
})),
|
||||
|
||||
...promotedVisTypes.map(getVisTypeMenuItem),
|
||||
...getAddPanelActionMenuItems(
|
||||
addPanelActions,
|
||||
createNewEmbeddable,
|
||||
deleteEmbeddable,
|
||||
closePopover
|
||||
),
|
||||
...getAddPanelActionMenuItems(api, addPanelActions, closePopover),
|
||||
];
|
||||
if (aggsBasedVisTypes.length > 0) {
|
||||
initialPanelItems.push({
|
||||
|
|
|
@ -187,7 +187,8 @@ export async function runQuickSave(this: DashboardContainer) {
|
|||
if (managed) return;
|
||||
|
||||
const nextPanels = await serializeAllPanelState(this);
|
||||
let stateToSave: SavedDashboardInput = { ...currentState, panels: nextPanels };
|
||||
const dashboardStateToSave: DashboardContainerInput = { ...currentState, panels: nextPanels };
|
||||
let stateToSave: SavedDashboardInput = dashboardStateToSave;
|
||||
let persistableControlGroupInput: PersistableControlGroupInput | undefined;
|
||||
if (this.controlGroup) {
|
||||
persistableControlGroupInput = this.controlGroup.getPersistableInput();
|
||||
|
@ -200,7 +201,7 @@ export async function runQuickSave(this: DashboardContainer) {
|
|||
saveOptions: {},
|
||||
});
|
||||
|
||||
this.dispatch.setLastSavedInput(currentState);
|
||||
this.dispatch.setLastSavedInput(dashboardStateToSave);
|
||||
this.lastSavedState.next();
|
||||
if (this.controlGroup && persistableControlGroupInput) {
|
||||
this.controlGroup.dispatch.setLastSavedInput(persistableControlGroupInput);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
import { omit } from 'lodash';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
@ -20,6 +21,8 @@ import type { DataView } from '@kbn/data-views-plugin/public';
|
|||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import {
|
||||
Container,
|
||||
EmbeddableFactoryNotFoundError,
|
||||
isExplicitInputWithAttributes,
|
||||
DefaultEmbeddableApi,
|
||||
PanelNotFoundError,
|
||||
ReactEmbeddableParentContext,
|
||||
|
@ -30,8 +33,9 @@ import {
|
|||
type EmbeddableOutput,
|
||||
type IEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import type { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import type { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { PanelPackage } from '@kbn/presentation-containers';
|
||||
import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
|
||||
|
@ -40,7 +44,13 @@ import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-f
|
|||
|
||||
import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE } from '../..';
|
||||
import { DashboardContainerInput, DashboardPanelState } from '../../../common';
|
||||
import { DASHBOARD_APP_ID, DASHBOARD_LOADED_EVENT } from '../../dashboard_constants';
|
||||
import {
|
||||
DASHBOARD_APP_ID,
|
||||
DASHBOARD_LOADED_EVENT,
|
||||
DASHBOARD_UI_METRIC_ID,
|
||||
DEFAULT_PANEL_HEIGHT,
|
||||
DEFAULT_PANEL_WIDTH,
|
||||
} from '../../dashboard_constants';
|
||||
import { DashboardAnalyticsService } from '../../services/analytics/types';
|
||||
import { DashboardCapabilitiesService } from '../../services/dashboard_capabilities/types';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
|
@ -70,6 +80,8 @@ import {
|
|||
dashboardTypeDisplayLowercase,
|
||||
dashboardTypeDisplayName,
|
||||
} from './dashboard_container_factory';
|
||||
import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings';
|
||||
import { panelPlacementStrategies } from '../component/panel_placement/place_new_panel_strategies';
|
||||
|
||||
export interface InheritedChildInput {
|
||||
filters: Filter[];
|
||||
|
@ -142,6 +154,9 @@ export class DashboardContainer
|
|||
private chrome;
|
||||
private customBranding;
|
||||
|
||||
private trackPanelAddMetric:
|
||||
| ((type: string, eventNames: string | string[], count?: number | undefined) => void)
|
||||
| undefined;
|
||||
// new embeddable framework
|
||||
public reactEmbeddableChildren: BehaviorSubject<{ [key: string]: DefaultEmbeddableApi }> =
|
||||
new BehaviorSubject<{ [key: string]: DefaultEmbeddableApi }>({});
|
||||
|
@ -156,9 +171,9 @@ export class DashboardContainer
|
|||
initialComponentState?: DashboardPublicState
|
||||
) {
|
||||
const {
|
||||
usageCollection,
|
||||
embeddable: { getEmbeddableFactory },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
super(
|
||||
{
|
||||
...initialInput,
|
||||
|
@ -168,6 +183,11 @@ export class DashboardContainer
|
|||
parent
|
||||
);
|
||||
|
||||
this.trackPanelAddMetric = usageCollection.reportUiCounter?.bind(
|
||||
usageCollection,
|
||||
DASHBOARD_UI_METRIC_ID
|
||||
);
|
||||
|
||||
({
|
||||
analytics: this.analyticsService,
|
||||
settings: {
|
||||
|
@ -396,6 +416,88 @@ export class DashboardContainer
|
|||
return newId;
|
||||
}
|
||||
|
||||
public async addNewPanel<ApiType extends unknown = unknown>(
|
||||
panelPackage: PanelPackage,
|
||||
displaySuccessMessage?: boolean
|
||||
) {
|
||||
const {
|
||||
notifications: { toasts },
|
||||
embeddable: { getEmbeddableFactory },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const onSuccess = (id?: string, title?: string) => {
|
||||
if (!displaySuccessMessage) return;
|
||||
toasts.addSuccess({
|
||||
title: dashboardReplacePanelActionStrings.getSuccessMessage(title),
|
||||
'data-test-subj': 'addEmbeddableToDashboardSuccess',
|
||||
});
|
||||
this.setScrollToPanelId(id);
|
||||
this.setHighlightPanelId(id);
|
||||
};
|
||||
|
||||
if (this.trackPanelAddMetric) {
|
||||
this.trackPanelAddMetric(METRIC_TYPE.CLICK, panelPackage.panelType);
|
||||
}
|
||||
if (reactEmbeddableRegistryHasKey(panelPackage.panelType)) {
|
||||
const newId = v4();
|
||||
const { newPanelPlacement, otherPanels } = panelPlacementStrategies.findTopLeftMostOpenSpace({
|
||||
currentPanels: this.getInput().panels,
|
||||
height: DEFAULT_PANEL_HEIGHT,
|
||||
width: DEFAULT_PANEL_WIDTH,
|
||||
});
|
||||
const newPanel: DashboardPanelState = {
|
||||
type: panelPackage.panelType,
|
||||
gridData: {
|
||||
...newPanelPlacement,
|
||||
i: newId,
|
||||
},
|
||||
explicitInput: {
|
||||
...panelPackage.initialState,
|
||||
id: newId,
|
||||
},
|
||||
};
|
||||
this.updateInput({ panels: { ...otherPanels, [newId]: newPanel } });
|
||||
onSuccess(newId, newPanel.explicitInput.title);
|
||||
return;
|
||||
}
|
||||
|
||||
const embeddableFactory = getEmbeddableFactory(panelPackage.panelType);
|
||||
if (!embeddableFactory) {
|
||||
throw new EmbeddableFactoryNotFoundError(panelPackage.panelType);
|
||||
}
|
||||
const initialInput = panelPackage.initialState as Partial<EmbeddableInput>;
|
||||
|
||||
let explicitInput: Partial<EmbeddableInput>;
|
||||
let attributes: unknown;
|
||||
try {
|
||||
if (initialInput) {
|
||||
explicitInput = initialInput;
|
||||
} else {
|
||||
const explicitInputReturn = await embeddableFactory.getExplicitInput(undefined, this);
|
||||
if (isExplicitInputWithAttributes(explicitInputReturn)) {
|
||||
explicitInput = explicitInputReturn.newInput;
|
||||
attributes = explicitInputReturn.attributes;
|
||||
} else {
|
||||
explicitInput = explicitInputReturn;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// error likely means user canceled embeddable creation
|
||||
return;
|
||||
}
|
||||
|
||||
const newEmbeddable = await this.addNewEmbeddable(
|
||||
embeddableFactory.type,
|
||||
explicitInput,
|
||||
attributes
|
||||
);
|
||||
|
||||
if (newEmbeddable) {
|
||||
onSuccess(newEmbeddable.id, newEmbeddable.getTitle());
|
||||
}
|
||||
return newEmbeddable as ApiType;
|
||||
}
|
||||
|
||||
public getDashboardPanelFromId = async (panelId: string) => {
|
||||
const panel = this.getInput().panels[panelId];
|
||||
if (reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
|
|
|
@ -131,6 +131,16 @@ export abstract class Container<
|
|||
this.removeEmbeddable(id);
|
||||
}
|
||||
|
||||
public async addNewPanel<ApiType extends unknown = unknown>(
|
||||
panelPackage: PanelPackage
|
||||
): Promise<ApiType | undefined> {
|
||||
const newEmbeddable = await this.addNewEmbeddable(
|
||||
panelPackage.panelType,
|
||||
panelPackage.initialState as Partial<EmbeddableInput>
|
||||
);
|
||||
return newEmbeddable as ApiType;
|
||||
}
|
||||
|
||||
public async replacePanel(idToRemove: string, { panelType, initialState }: PanelPackage) {
|
||||
return await this.replaceEmbeddable(
|
||||
idToRemove,
|
||||
|
|
|
@ -8,16 +8,20 @@ import type { CoreStart } from '@kbn/core/public';
|
|||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import type { LensPluginStartDependencies } from '../../plugin';
|
||||
import { createMockStartDependencies } from '../../editor_frame_service/mocks';
|
||||
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
|
||||
import { CreateESQLPanelAction } from './create_action';
|
||||
|
||||
describe('create Lens panel action', () => {
|
||||
const core = coreMock.createStart();
|
||||
const mockStartDependencies =
|
||||
createMockStartDependencies() as unknown as LensPluginStartDependencies;
|
||||
const mockPresentationContainer = getMockPresentationContainer();
|
||||
describe('compatibility check', () => {
|
||||
it('is incompatible if ui setting for ES|QL is off', async () => {
|
||||
const configurablePanelAction = new CreateESQLPanelAction(mockStartDependencies, core);
|
||||
const isCompatible = await configurablePanelAction.isCompatible();
|
||||
const isCompatible = await configurablePanelAction.isCompatible({
|
||||
embeddable: mockPresentationContainer,
|
||||
});
|
||||
|
||||
expect(isCompatible).toBeFalsy();
|
||||
});
|
||||
|
@ -33,7 +37,9 @@ describe('create Lens panel action', () => {
|
|||
},
|
||||
} as CoreStart;
|
||||
const createESQLAction = new CreateESQLPanelAction(mockStartDependencies, updatedCore);
|
||||
const isCompatible = await createESQLAction.isCompatible();
|
||||
const isCompatible = await createESQLAction.isCompatible({
|
||||
embeddable: mockPresentationContainer,
|
||||
});
|
||||
|
||||
expect(isCompatible).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -6,29 +6,16 @@
|
|||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import type {
|
||||
EmbeddableFactory,
|
||||
EmbeddableInput,
|
||||
IEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import type { LensPluginStartDependencies } from '../../plugin';
|
||||
|
||||
const ACTION_CREATE_ESQL_CHART = 'ACTION_CREATE_ESQL_CHART';
|
||||
|
||||
interface Context {
|
||||
createNewEmbeddable: (
|
||||
embeddableFactory: EmbeddableFactory,
|
||||
initialInput?: Partial<EmbeddableInput>,
|
||||
dismissNotification?: boolean
|
||||
) => Promise<undefined | IEmbeddable>;
|
||||
deleteEmbeddable: (embeddableId: string) => void;
|
||||
initialInput?: Partial<EmbeddableInput>;
|
||||
}
|
||||
|
||||
export const getAsyncHelpers = async () => await import('../../async_services');
|
||||
|
||||
export class CreateESQLPanelAction implements Action<Context> {
|
||||
export class CreateESQLPanelAction implements Action<EmbeddableApiContext> {
|
||||
public type = ACTION_CREATE_ESQL_CHART;
|
||||
public id = ACTION_CREATE_ESQL_CHART;
|
||||
public order = 50;
|
||||
|
@ -49,19 +36,20 @@ export class CreateESQLPanelAction implements Action<Context> {
|
|||
return 'esqlVis';
|
||||
}
|
||||
|
||||
public async isCompatible() {
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!apiIsPresentationContainer(embeddable)) return false;
|
||||
// compatible only when ES|QL advanced setting is enabled
|
||||
const { isCreateActionCompatible } = await getAsyncHelpers();
|
||||
return isCreateActionCompatible(this.core);
|
||||
}
|
||||
|
||||
public async execute({ createNewEmbeddable, deleteEmbeddable }: Context) {
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
|
||||
const { executeCreateAction } = await getAsyncHelpers();
|
||||
executeCreateAction({
|
||||
deps: this.startDependencies,
|
||||
core: this.core,
|
||||
createNewEmbeddable,
|
||||
deleteEmbeddable,
|
||||
api: embeddable,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,9 @@
|
|||
*/
|
||||
import { createGetterSetter } from '@kbn/kibana-utils-plugin/common';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type {
|
||||
EmbeddableFactory,
|
||||
EmbeddableInput,
|
||||
IEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { getESQLAdHocDataview, getIndexForESQLQuery } from '@kbn/esql-utils';
|
||||
import type { Datasource, Visualization } from '../../types';
|
||||
import type { LensPluginStartDependencies } from '../../plugin';
|
||||
|
@ -20,6 +16,7 @@ import { fetchDataFromAggregateQuery } from '../../datasources/text_based/fetch_
|
|||
import { suggestionsApi } from '../../lens_suggestions_api';
|
||||
import { generateId } from '../../id_generator';
|
||||
import { executeEditAction } from './edit_action_helpers';
|
||||
import { Embeddable } from '../../embeddable';
|
||||
|
||||
// datasourceMap and visualizationMap setters/getters
|
||||
export const [getVisualizationMap, setVisualizationMap] = createGetterSetter<
|
||||
|
@ -37,17 +34,11 @@ export function isCreateActionCompatible(core: CoreStart) {
|
|||
export async function executeCreateAction({
|
||||
deps,
|
||||
core,
|
||||
createNewEmbeddable,
|
||||
deleteEmbeddable,
|
||||
api,
|
||||
}: {
|
||||
deps: LensPluginStartDependencies;
|
||||
core: CoreStart;
|
||||
createNewEmbeddable: (
|
||||
embeddableFactory: EmbeddableFactory,
|
||||
initialInput?: Partial<EmbeddableInput>,
|
||||
dismissNotification?: boolean
|
||||
) => Promise<undefined | IEmbeddable>;
|
||||
deleteEmbeddable: (embeddableId: string) => void;
|
||||
api: PresentationContainer;
|
||||
}) {
|
||||
const isCompatibleAction = isCreateActionCompatible(core);
|
||||
const defaultDataView = await deps.dataViews.getDefaultDataView({
|
||||
|
@ -110,20 +101,17 @@ export async function executeCreateAction({
|
|||
dataView,
|
||||
});
|
||||
|
||||
const input = {
|
||||
attributes: attrs,
|
||||
id: generateId(),
|
||||
};
|
||||
const embeddableStart = deps.embeddable;
|
||||
const factory = embeddableStart.getEmbeddableFactory('lens');
|
||||
if (!factory) {
|
||||
return undefined;
|
||||
}
|
||||
const embeddable = await createNewEmbeddable(factory, input, true);
|
||||
const embeddable = await api.addNewPanel<Embeddable>({
|
||||
panelType: 'lens',
|
||||
initialState: {
|
||||
attributes: attrs,
|
||||
id: generateId(),
|
||||
},
|
||||
});
|
||||
// open the flyout if embeddable has been created successfully
|
||||
if (embeddable) {
|
||||
const deletePanel = () => {
|
||||
deleteEmbeddable(embeddable.id);
|
||||
api.removePanel(embeddable.id);
|
||||
};
|
||||
|
||||
executeEditAction({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue