[dashboard][canvas] Replace VisTypes and VisTypeAliases in Add Panel Menu (#209022)

Closes https://github.com/elastic/kibana/issues/180057

The following items needed to be replaced with add panel actions
* vega - visType
* markdown - visType
* lens - visTypeAlias
* maps - visTypeAlias

As an added benefit, now these actions are displayed in embeddable
examples that uses ADD_PANEL_TRIGGER

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2025-02-10 15:33:24 -07:00 committed by GitHub
parent 6e61f526a7
commit 0ba9c66a4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 418 additions and 488 deletions

View file

@ -32,7 +32,7 @@ export function AddButton({ pageApi, uiActions }: { pageApi: unknown; uiActions:
return (
<EuiContextMenuItem
key={action.id}
icon="share"
icon={action?.getIconType(actionContext) ?? ''}
onClick={() => {
action.execute(actionContext);
setIsPopoverOpen(false);

View file

@ -12,7 +12,10 @@
"browser": true,
"server": true,
"requiredPlugins": [
"data",
"embeddable",
"expressions",
"uiActions",
"visualizations"
],
"requiredBundles": [

View file

@ -0,0 +1,40 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EmbeddableApiContext, apiHasAppContext } from '@kbn/presentation-publishing';
import { ADD_PANEL_ANNOTATION_GROUP } from '@kbn/embeddable-plugin/public';
import { MarkdownStartDependencies } from './plugin';
import { markdownVisType } from './markdown_vis';
export function getAddMarkdownPanelAction(deps: MarkdownStartDependencies) {
return {
id: 'addMarkdownPanelAction',
getIconType: () => markdownVisType.icon,
order: markdownVisType.order,
isCompatible: async () => true,
execute: async ({ embeddable }: EmbeddableApiContext) => {
const stateTransferService = deps.embeddable.getStateTransfer();
stateTransferService.navigateToEditor('visualize', {
path: '#/create?type=markdown',
state: {
originatingApp: apiHasAppContext(embeddable)
? embeddable.getAppContext().currentAppId
: '',
originatingPath: apiHasAppContext(embeddable)
? embeddable.getAppContext().getCurrentPath?.()
: undefined,
searchSessionId: deps.data.search.session.getSessionId(),
},
});
},
grouping: [ADD_PANEL_ANNOTATION_GROUP],
getDisplayName: () => markdownVisType.titleInWizard,
getDisplayNameTooltip: () => markdownVisType.description,
};
}

View file

@ -10,13 +10,12 @@
import { i18n } from '@kbn/i18n';
import { DefaultEditorSize } from '@kbn/vis-default-editor-plugin/public';
import { VisGroups, VisTypeDefinition } from '@kbn/visualizations-plugin/public';
import { VisGroups } from '@kbn/visualizations-plugin/public';
import { MarkdownOptions } from './markdown_options';
import { SettingsOptions } from './settings_options_lazy';
import { toExpressionAst } from './to_ast';
import { MarkdownVisParams } from './types';
export const markdownVisDefinition: VisTypeDefinition<MarkdownVisParams> = {
export const markdownVisType = {
name: 'markdown',
title: 'Markdown',
isAccessible: true,

View file

@ -11,17 +11,25 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/cor
import { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public';
import { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
import { markdownVisDefinition } from './markdown_vis';
import { ADD_PANEL_TRIGGER, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { markdownVisType } from './markdown_vis';
import { createMarkdownVisFn } from './markdown_fn';
import type { ConfigSchema } from '../server/config';
import { getMarkdownVisRenderer } from './markdown_renderer';
/** @internal */
export interface MarkdownPluginSetupDependencies {
interface MarkdownSetupDependencies {
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
visualizations: VisualizationsSetup;
}
export interface MarkdownStartDependencies {
data: DataPublicPluginStart;
embeddable: EmbeddableStart;
uiActions: UiActionsStart;
}
/** @internal */
export class MarkdownPlugin implements Plugin<void, void> {
initializerContext: PluginInitializerContext<ConfigSchema>;
@ -30,13 +38,17 @@ export class MarkdownPlugin implements Plugin<void, void> {
this.initializerContext = initializerContext;
}
public setup(core: CoreSetup, { expressions, visualizations }: MarkdownPluginSetupDependencies) {
visualizations.createBaseVisualization(markdownVisDefinition);
public setup(core: CoreSetup, { expressions, visualizations }: MarkdownSetupDependencies) {
visualizations.createBaseVisualization(markdownVisType);
expressions.registerRenderer(getMarkdownVisRenderer({ getStartDeps: core.getStartServices }));
expressions.registerFunction(createMarkdownVisFn);
}
public start(core: CoreStart) {
// nothing to do here yet
public start(core: CoreStart, deps: MarkdownStartDependencies) {
deps.uiActions.registerActionAsync('addMarkdownAction', async () => {
const { getAddMarkdownPanelAction } = await import('./add_markdown_panel_action');
return getAddMarkdownPanelAction(deps);
});
deps.uiActions.attachAction(ADD_PANEL_TRIGGER, 'addMarkdownAction');
}
}

View file

@ -19,6 +19,10 @@
"@kbn/kibana-react-plugin",
"@kbn/react-kibana-context-render",
"@kbn/core-lifecycle-browser",
"@kbn/presentation-publishing",
"@kbn/embeddable-plugin",
"@kbn/ui-actions-plugin",
"@kbn/data-plugin",
],
"exclude": [
"target/**/*",

View file

@ -13,11 +13,13 @@
"server": true,
"requiredPlugins": [
"data",
"embeddable",
"visualizations",
"mapsEms",
"expressions",
"inspector",
"dataViews",
"uiActions",
"usageCollection"
],
"optionalPlugins": [

View file

@ -0,0 +1,40 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EmbeddableApiContext, apiHasAppContext } from '@kbn/presentation-publishing';
import { ADD_PANEL_VISUALIZATION_GROUP } from '@kbn/embeddable-plugin/public';
import { VegaPluginStartDependencies } from './plugin';
import { vegaVisType } from './vega_type';
export function getAddVegaPanelAction(deps: VegaPluginStartDependencies) {
return {
id: 'addVegaPanelAction',
getIconType: () => vegaVisType.icon as string,
order: 0,
isCompatible: async () => true,
execute: async ({ embeddable }: EmbeddableApiContext) => {
const stateTransferService = deps.embeddable.getStateTransfer();
stateTransferService.navigateToEditor('visualize', {
path: '#/create?type=vega',
state: {
originatingApp: apiHasAppContext(embeddable)
? embeddable.getAppContext().currentAppId
: '',
originatingPath: apiHasAppContext(embeddable)
? embeddable.getAppContext().getCurrentPath?.()
: undefined,
searchSessionId: deps.data.search.session.getSessionId(),
},
});
},
grouping: [ADD_PANEL_VISUALIZATION_GROUP],
getDisplayName: () => vegaVisType.titleInWizard,
getDisplayNameTooltip: () => vegaVisType.description,
};
}

View file

@ -16,6 +16,8 @@ import { Setup as InspectorSetup } from '@kbn/inspector-plugin/public';
import type { MapsEmsPluginPublicStart } from '@kbn/maps-ems-plugin/public';
import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { ADD_PANEL_TRIGGER, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import {
setNotifications,
setData,
@ -28,7 +30,7 @@ import {
} from './services';
import { createVegaFn } from './vega_fn';
import { createVegaTypeDefinition } from './vega_type';
import { vegaVisType } from './vega_type';
import type { IServiceSettings } from './vega_view/vega_map_view/service_settings/service_settings_types';
import type { ConfigSchema } from '../server/config';
@ -57,8 +59,10 @@ export interface VegaPluginSetupDependencies {
/** @internal */
export interface VegaPluginStartDependencies {
data: DataPublicPluginStart;
embeddable: EmbeddableStart;
mapsEms: MapsEmsPluginPublicStart;
dataViews: DataViewsPublicPluginStart;
uiActions: UiActionsStart;
usageCollection: UsageCollectionStart;
}
@ -91,19 +95,27 @@ export class VegaPlugin implements Plugin<void, void> {
expressions.registerFunction(() => createVegaFn(visualizationDependencies));
expressions.registerRenderer(getVegaVisRenderer(visualizationDependencies));
visualizations.createBaseVisualization(createVegaTypeDefinition());
visualizations.createBaseVisualization(vegaVisType);
}
public start(
core: CoreStart,
{ data, mapsEms, dataViews, usageCollection }: VegaPluginStartDependencies
) {
public start(core: CoreStart, deps: VegaPluginStartDependencies) {
setNotifications(core.notifications);
setData(data);
setDataViews(dataViews);
setData(deps.data);
setDataViews(deps.dataViews);
setDocLinks(core.docLinks);
setMapsEms(mapsEms);
setMapsEms(deps.mapsEms);
setThemeService(core.theme);
setUsageCollectionStart(usageCollection);
setUsageCollectionStart(deps.usageCollection);
deps.uiActions.registerActionAsync('addVegaPanelAction', async () => {
const { getAddVegaPanelAction } = await import('./add_vega_panel_action');
return getAddVegaPanelAction(deps);
});
deps.uiActions.attachAction(ADD_PANEL_TRIGGER, 'addVegaPanelAction');
if (deps.uiActions.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
// the create action if the Canvas-specific trigger does indeed exist.
deps.uiActions.attachAction('ADD_CANVAS_ELEMENT_TRIGGER', 'addVegaPanelAction');
}
}
}

View file

@ -26,48 +26,46 @@ import { VegaVisEditorComponent } from './components/vega_vis_editor_lazy';
import type { VisParams } from './vega_fn';
export const createVegaTypeDefinition = (): VisTypeDefinition<VisParams> => {
return {
name: 'vega',
title: 'Vega',
getInfoMessage,
description: i18n.translate('visTypeVega.type.vegaDescription', {
defaultMessage: 'Use the Vega syntax to create new types of visualizations.',
description: 'Vega and Vega-Lite are product names and should not be translated',
}),
icon: 'visVega',
group: VisGroups.PROMOTED,
titleInWizard: i18n.translate('visTypeVega.type.vegaTitleInWizard', {
defaultMessage: 'Custom visualization',
}),
visConfig: { defaults: { spec: getDefaultSpec() } },
editorConfig: {
optionsTemplate: VegaVisEditorComponent,
enableAutoApply: true,
defaultSize: DefaultEditorSize.MEDIUM,
},
toExpressionAst,
options: {
showIndexSelection: false,
showFilterBar: true,
},
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.applyFilter];
},
getUsedIndexPattern: async (visParams) => {
try {
const spec = parse(visParams.spec, { legacyRoot: false, keepWsc: true });
export const vegaVisType: VisTypeDefinition<VisParams> = {
name: 'vega',
title: 'Vega',
getInfoMessage,
description: i18n.translate('visTypeVega.type.vegaDescription', {
defaultMessage: 'Use the Vega syntax to create new types of visualizations.',
description: 'Vega and Vega-Lite are product names and should not be translated',
}),
icon: 'visVega',
group: VisGroups.PROMOTED,
titleInWizard: i18n.translate('visTypeVega.type.vegaTitleInWizard', {
defaultMessage: 'Custom visualization',
}),
visConfig: { defaults: { spec: getDefaultSpec() } },
editorConfig: {
optionsTemplate: VegaVisEditorComponent,
enableAutoApply: true,
defaultSize: DefaultEditorSize.MEDIUM,
},
toExpressionAst,
options: {
showIndexSelection: false,
showFilterBar: true,
},
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.applyFilter];
},
getUsedIndexPattern: async (visParams) => {
try {
const spec = parse(visParams.spec, { legacyRoot: false, keepWsc: true });
return extractIndexPatternsFromSpec(spec);
} catch (e) {
// spec is invalid
}
return [];
},
inspectorAdapters: createInspectorAdapters,
/**
* This is necessary for showing actions bar in top of vega editor
*/
requiresSearch: true,
};
return extractIndexPatternsFromSpec(spec);
} catch (e) {
// spec is invalid
}
return [];
},
inspectorAdapters: createInspectorAdapters,
/**
* This is necessary for showing actions bar in top of vega editor
*/
requiresSearch: true,
};

View file

@ -41,6 +41,9 @@
"@kbn/code-editor",
"@kbn/react-kibana-context-render",
"@kbn/search-types",
"@kbn/presentation-publishing",
"@kbn/embeddable-plugin",
"@kbn/ui-actions-plugin",
],
"exclude": [
"target/**/*",

View file

@ -28,31 +28,21 @@ jest.mock('../../../services/kibana_services', () => ({
execute: () => {},
isCompatible: async () => true,
},
],
},
visualizationsService: {
all: () => [
{
name: 'myPromotedVis',
title: 'myPromotedVis',
order: 0,
description: 'myPromotedVis description',
icon: 'empty',
stage: 'production',
isDeprecated: false,
group: 'promoted',
titleInWizard: 'myPromotedVis title',
},
],
getAliases: () => [
{
name: 'alias visualization',
title: 'Alias Visualization',
order: 0,
description: 'This is a dummy representation of aan aliased visualization.',
icon: 'empty',
stage: 'production',
isDeprecated: false,
id: 'myVis',
type: '',
order: 10,
grouping: [
{
id: 'visualizations',
order: 1000,
getDisplayName: () => 'Visualizations',
},
],
getDisplayName: () => 'myVis',
getIconType: () => 'empty',
execute: () => {},
isCompatible: async () => true,
},
],
},
@ -71,9 +61,8 @@ describe('getMenuItemGroups', () => {
expect(groups.length).toBe(2);
expect(groups[0].title).toBe('Visualizations');
expect(groups[0].items.length).toBe(2); // promoted vis type and vis alias
expect(groups[0].items.length).toBe(1);
expect(groups[1].title).toBe('My group');
expect(groups[1].items.length).toBe(1); // add panel action
expect(groups[1].items.length).toBe(1);
});
});

View file

@ -7,26 +7,15 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { VisGroups } from '@kbn/visualizations-plugin/public';
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
import {
ADD_PANEL_ANNOTATION_GROUP,
ADD_PANEL_OTHER_GROUP,
ADD_PANEL_VISUALIZATION_GROUP,
} from '@kbn/embeddable-plugin/public';
import { ADD_PANEL_OTHER_GROUP } from '@kbn/embeddable-plugin/public';
import type { TracksOverlays } from '@kbn/presentation-containers';
import { PresentableGroup } from '@kbn/ui-actions-browser/src/types';
import { addPanelMenuTrigger } from '@kbn/ui-actions-plugin/public';
import type { HasAppContext } from '@kbn/presentation-publishing';
import { uiActionsService, visualizationsService } from '../../../services/kibana_services';
import { navigateToVisEditor } from './navigate_to_vis_editor';
import { uiActionsService } from '../../../services/kibana_services';
import type { MenuItem, MenuItemGroup } from './types';
const VIS_GROUP_TO_ADD_PANEL_GROUP: Record<string, PresentableGroup> = {
[VisGroups.PROMOTED]: ADD_PANEL_VISUALIZATION_GROUP,
[VisGroups.TOOLS]: ADD_PANEL_ANNOTATION_GROUP,
};
export async function getMenuItemGroups(
api: HasAppContext & TracksOverlays
): Promise<MenuItemGroup[]> {
@ -48,45 +37,6 @@ export async function getMenuItemGroups(
groups[group.id].items.push(item);
}
// add menu items from vis types
visualizationsService.all().forEach((visType) => {
if (visType.disableCreate) return;
const group = VIS_GROUP_TO_ADD_PANEL_GROUP[visType.group];
if (!group) return;
pushItem(group, {
id: visType.name,
name: visType.titleInWizard || visType.title,
isDeprecated: visType.isDeprecated,
icon: visType.icon ?? 'empty',
onClick: () => {
api.clearOverlays();
navigateToVisEditor(api, visType);
},
'data-test-subj': `visType-${visType.name}`,
description: visType.description,
order: visType.order,
});
});
// add menu items from vis alias
visualizationsService.getAliases().forEach((visTypeAlias) => {
if (visTypeAlias.disableCreate) return;
pushItem(ADD_PANEL_VISUALIZATION_GROUP, {
id: visTypeAlias.name,
name: visTypeAlias.title,
icon: visTypeAlias.icon ?? 'empty',
onClick: () => {
api.clearOverlays();
navigateToVisEditor(api, visTypeAlias);
},
'data-test-subj': `visType-${visTypeAlias.name}`,
description: visTypeAlias.description,
order: visTypeAlias.order ?? 0,
});
});
// add menu items from "add panel" actions
(
await uiActionsService.getTriggerCompatibleActions(ADD_PANEL_TRIGGER, { embeddable: api })
).forEach((action) => {

View file

@ -143,7 +143,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
* Text embeddable
*/
it('emits when markup is added', async () => {
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await PageObjects.visEditor.setMarkdownTxt('There is no spoon');
await PageObjects.visEditor.clickGo();
await PageObjects.visualize.saveVisualizationExpectSuccess(MARKDOWN_PANEL_TITLE, {

View file

@ -90,7 +90,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const newTitle = 'test create panel originatingApp';
await dashboard.loadSavedDashboard('few panels');
await dashboard.switchToEditMode();
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await visualize.saveVisualizationExpectSuccess(newTitle, {
saveAsNew: true,
redirectToOrigin: false,

View file

@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }) {
const modifiedMarkdownText = 'Modified markdown text';
const createMarkdownVis = async (title) => {
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await visEditor.setMarkdownTxt(originalMarkdownText);
await visEditor.clickGo();
if (title) {

View file

@ -101,7 +101,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboard.switchToEditMode();
await dashboard.expectExistsQuickSaveOption();
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await visualize.saveVisualizationAndReturn();
await dashboard.waitForRenderComplete();
await dashboard.clickQuickSave();

View file

@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('resolves markdown link', async () => {
await dashboard.gotoDashboardLandingPage();
await dashboard.clickNewDashboard();
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await visEditor.setMarkdownTxt(`[abc](#/dashboard/${testDashboardId})`);
await visEditor.clickGo();
await visualize.saveVisualization('legacy url markdown', { redirectToOrigin: true });

View file

@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
before(async function () {
await dashboard.initTests();
await dashboard.clickNewDashboard();
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await visEditor.setMarkdownTxt(markdown);
await visEditor.clickGo();
});

View file

@ -41,14 +41,19 @@ export class DashboardAddPanelService extends FtrService {
});
}
async clickMarkdownQuickButton() {
async clickAddMarkdownPanel() {
await this.clickEditorMenuButton();
await this.clickVisType('markdown');
await this.clickAddNewPanelFromUIActionLink('Markdown text');
}
async clickMapQuickButton() {
async clickAddMapPanel() {
await this.clickEditorMenuButton();
await this.clickVisType('map');
await this.clickAddNewPanelFromUIActionLink('Maps');
}
async clickAddLensPanel() {
await this.clickEditorMenuButton();
await this.clickAddNewPanelFromUIActionLink('Lens');
}
async clickEditorMenuButton() {
@ -64,11 +69,6 @@ export class DashboardAddPanelService extends FtrService {
await this.testSubjects.missingOrFail('dashboardPanelSelectionFlyout');
}
async clickVisType(visType: string) {
this.log.debug('DashboardAddPanel.clickVisType');
await this.testSubjects.click(`visType-${visType}`);
}
async verifyEmbeddableFactoryGroupExists(groupId: string, expectExist: boolean = true) {
this.log.debug('DashboardAddPanel.verifyEmbeddableFactoryGroupExists');
const testSubject = `dashboardEditorMenu-${groupId}Group`;
@ -84,6 +84,7 @@ export class DashboardAddPanelService extends FtrService {
}
async clickAddNewPanelFromUIActionLink(type: string) {
await this.testSubjects.setValue('dashboardPanelSelectionFlyout__searchInput', type);
await this.testSubjects.click(`create-action-${type}`);
}

View file

@ -93,7 +93,7 @@ export class DashboardVisualizationsService extends FtrService {
if (inViewMode) {
await this.dashboard.switchToEditMode();
}
await this.dashboardAddPanel.clickMarkdownQuickButton();
await this.dashboardAddPanel.clickAddMarkdownPanel();
await this.visEditor.setMarkdownTxt(markdown);
await this.visEditor.clickGo();
await this.visualize.saveVisualizationExpectSuccess(name, {

View file

@ -31,7 +31,6 @@ import {
import { EmbeddableExpression } from '../../expression_types/embeddable';
import { StartDeps } from '../../plugin';
import { embeddableInputToExpression } from './embeddable_input_to_expression';
import { useGetAppContext } from './use_get_app_context';
const { embeddable: strings } = RendererStrings;
@ -54,8 +53,6 @@ const renderReactEmbeddable = ({
}) => {
// wrap in functional component to allow usage of hooks
const RendererWrapper: FC<{}> = () => {
const getAppContext = useGetAppContext(core);
const searchApi = useMemo(() => {
return {
filters$: new BehaviorSubject<Filter[] | undefined>(input.filters),
@ -70,7 +67,6 @@ const renderReactEmbeddable = ({
maybeId={uuid}
getParentApi={(): CanvasContainerApi => ({
...container,
getAppContext,
getSerializedStateForChild: () => ({
rawState: omit(input, ['disableTriggers', 'filters']),
}),

View file

@ -1,27 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMemo } from 'react';
import type { CoreStart } from '@kbn/core/public';
import useObservable from 'react-use/lib/useObservable';
import { CANVAS_APP } from '../../../common/lib';
export function useGetAppContext(core: CoreStart) {
const currentAppId = useObservable(core.application.currentAppId$, undefined);
const getAppContext = useMemo(() => {
return () => ({
getCurrentPath: () => {
const urlToApp = core.application.getUrlForApp(currentAppId ?? CANVAS_APP);
const inAppPath = window.location.pathname.replace(urlToApp, '');
return inAppPath + window.location.search + window.location.hash;
},
currentAppId: CANVAS_APP,
});
}, [currentAppId, core.application]);
return getAppContext;
}

View file

@ -30,7 +30,6 @@
"features",
"inspector",
"presentationUtil",
"visualizations",
"uiActions",
"share",
"contentManagement",

View file

@ -18,6 +18,8 @@ import { METRIC_TYPE, trackCanvasUiMetric } from '../../lib/ui_metric';
// @ts-expect-error unconverted file
import { addElement } from '../../state/actions/elements';
import { getSelectedPage } from '../../state/selectors/workpad';
import { CANVAS_APP } from '../../../common/lib';
import { coreServices } from '../../services/kibana_services';
const reload$ = new Subject<void>();
@ -40,6 +42,14 @@ export const useCanvasApi: () => CanvasContainerApi = () => {
const getCanvasApi = useCallback((): CanvasContainerApi => {
return {
getAppContext: () => ({
getCurrentPath: () => {
const urlToApp = coreServices.application.getUrlForApp(CANVAS_APP);
const inAppPath = window.location.pathname.replace(urlToApp, '');
return inAppPath + window.location.search + window.location.hash;
},
currentAppId: CANVAS_APP,
}),
reload$,
reload: () => {
reload$.next();

View file

@ -8,51 +8,11 @@
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import React from 'react';
import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';
import { EditorMenu } from '../editor_menu.component';
const testVisTypes: BaseVisType[] = [
{ title: 'TSVB', icon: '', description: 'Description of TSVB', name: 'tsvb' } as BaseVisType,
{
titleInWizard: 'Custom visualization',
title: 'Vega',
icon: '',
description: 'Description of Vega',
name: 'vega',
} as BaseVisType,
];
const testVisTypeAliases: VisTypeAlias[] = [
{
title: 'Lens',
alias: {
app: 'lens',
path: 'path/to/lens',
},
icon: 'lensApp',
name: 'lens',
description: 'Description of Lens app',
stage: 'production',
},
{
title: 'Maps',
alias: {
app: 'maps',
path: 'path/to/maps',
},
icon: 'gisApp',
name: 'maps',
description: 'Description of Maps app',
stage: 'production',
},
];
storiesOf('components/WorkpadHeader/EditorMenu', module).add('default', () => (
<EditorMenu
addPanelActions={[]}
promotedVisTypes={testVisTypes}
visTypeAliases={testVisTypeAliases}
createNewVisType={() => action('createNewVisType')}
createNewEmbeddableFromAction={() => action('createNewEmbeddableFromAction')}
/>
));

View file

@ -7,11 +7,10 @@
import React, { FC, useCallback } from 'react';
import { EuiContextMenu, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
import { EuiContextMenu } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar';
import { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public/actions';
import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';
import { addCanvasElementTrigger } from '../../../state/triggers/add_canvas_element_trigger';
import { useCanvasApi } from '../../hooks/use_canvas_api';
@ -25,9 +24,6 @@ const strings = {
interface Props {
addPanelActions: Action[];
promotedVisTypes: BaseVisType[];
visTypeAliases: VisTypeAlias[];
createNewVisType: (visType?: BaseVisType | VisTypeAlias) => () => void;
createNewEmbeddableFromAction: (
action: Action,
context: ActionExecutionContext<object>,
@ -37,38 +33,10 @@ interface Props {
export const EditorMenu: FC<Props> = ({
addPanelActions,
promotedVisTypes,
visTypeAliases,
createNewVisType,
createNewEmbeddableFromAction,
}: Props) => {
const canvasApi = useCanvasApi();
const getVisTypeMenuItem = (visType: BaseVisType): EuiContextMenuPanelItemDescriptor => {
const { name, title, titleInWizard, description, icon = 'empty' } = visType;
return {
name: titleInWizard || title,
icon: icon as string,
onClick: createNewVisType(visType),
'data-test-subj': `visType-${name}`,
toolTipContent: description,
};
};
const getVisTypeAliasMenuItem = (
visTypeAlias: VisTypeAlias
): EuiContextMenuPanelItemDescriptor => {
const { name, title, description, icon = 'empty' } = visTypeAlias;
return {
name: title,
icon,
onClick: createNewVisType(visTypeAlias),
'data-test-subj': `visType-${name}`,
toolTipContent: description,
};
};
const getAddPanelActionMenuItems = useCallback(
(closePopover: () => void) => {
return addPanelActions.map((item) => {
@ -83,6 +51,7 @@ export const EditorMenu: FC<Props> = ({
onClick: createNewEmbeddableFromAction(item, context, closePopover),
'data-test-subj': `create-action-${actionName}`,
toolTipContent: item?.getDisplayNameTooltip?.(context),
order: item.order ?? 0,
};
});
},
@ -92,11 +61,7 @@ export const EditorMenu: FC<Props> = ({
const getEditorMenuPanels = (closePopover: () => void) => [
{
id: 0,
items: [
...visTypeAliases.map(getVisTypeAliasMenuItem),
...getAddPanelActionMenuItems(closePopover),
...promotedVisTypes.map(getVisTypeMenuItem),
],
items: [...getAddPanelActionMenuItems(closePopover).sort((a, b) => b.order - a.order)],
},
];

View file

@ -6,37 +6,14 @@
*/
import React, { FC, useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import {
VisGroups,
type BaseVisType,
type VisTypeAlias,
type VisParams,
} from '@kbn/visualizations-plugin/public';
import { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public/actions';
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric';
import { CANVAS_APP } from '../../../../common/lib';
import { ElementSpec } from '../../../../types';
import { EditorMenu as Component } from './editor_menu.component';
import { useCanvasApi } from '../../hooks/use_canvas_api';
import { ADD_CANVAS_ELEMENT_TRIGGER } from '../../../state/triggers/add_canvas_element_trigger';
import {
embeddableService,
uiActionsService,
visualizationsService,
} from '../../../services/kibana_services';
import { uiActionsService } from '../../../services/kibana_services';
interface Props {
/**
* Handler for adding a selected element to the workpad
*/
addElement: (element: Partial<ElementSpec>) => void;
}
export const EditorMenu: FC<Props> = ({ addElement }) => {
const { pathname, search, hash } = useLocation();
const stateTransferService = embeddableService.getStateTransfer();
export const EditorMenu: FC = () => {
const canvasApi = useCanvasApi();
const [addPanelActions, setAddPanelActions] = useState<Array<Action<object>>>([]);
@ -56,41 +33,6 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
};
}, [canvasApi]);
const createNewVisType = useCallback(
(visType?: BaseVisType | VisTypeAlias) => () => {
let path = '';
let appId = '';
if (visType) {
if (trackCanvasUiMetric) {
trackCanvasUiMetric(METRIC_TYPE.CLICK, `${visType.name}:create`);
}
if (!('alias' in visType)) {
// this visualization is not an alias
appId = 'visualize';
path = `#/create?type=${encodeURIComponent(visType.name)}`;
} else if (visType.alias && 'path' in visType.alias) {
// this visualization **is** an alias, and it has an app to redirect to for creation
appId = visType.alias.app;
path = visType.alias.path;
}
} else {
appId = 'visualize';
path = '#/create?';
}
stateTransferService.navigateToEditor(appId, {
path,
state: {
originatingApp: CANVAS_APP,
originatingPath: `${pathname}${search}${hash}`,
},
});
},
[stateTransferService, pathname, search, hash]
);
const createNewEmbeddableFromAction = useCallback(
(action: Action, context: ActionExecutionContext<object>, closePopover: () => void) =>
(event: React.MouseEvent) => {
@ -110,40 +52,10 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
[]
);
const getVisTypesByGroup = (group: VisGroups): BaseVisType[] =>
visualizationsService
.getByGroup(group)
.sort(({ name: a }: BaseVisType | VisTypeAlias, { name: b }: BaseVisType | VisTypeAlias) => {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
})
.filter(({ disableCreate }: BaseVisType) => !disableCreate);
const visTypeAliases = visualizationsService
.getAliases()
.sort(({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) =>
a === b ? 0 : a ? -1 : 1
)
.filter(({ disableCreate }: VisTypeAlias) => !disableCreate);
const promotedVisTypes = getVisTypesByGroup(VisGroups.PROMOTED);
const legacyVisTypes = getVisTypesByGroup(VisGroups.LEGACY);
return (
<Component
createNewVisType={createNewVisType}
createNewEmbeddableFromAction={createNewEmbeddableFromAction}
promotedVisTypes={([] as Array<BaseVisType<VisParams>>).concat(
promotedVisTypes,
legacyVisTypes
)}
addPanelActions={addPanelActions}
visTypeAliases={visTypeAliases}
/>
);
};

View file

@ -177,7 +177,7 @@ export const WorkpadHeader: FC<Props> = ({
onClick={showEmbedPanel}
data-test-subj="canvas-add-from-library-button"
/>,
<EditorMenu addElement={addElement} />,
<EditorMenu />,
],
}}
</Toolbar>

View file

@ -8,7 +8,6 @@
import { BehaviorSubject } from 'rxjs';
import type { SharePluginSetup } from '@kbn/share-plugin/public';
import { ChartsPluginSetup, ChartsPluginStart } from '@kbn/charts-plugin/public';
import { VisualizationsStart } from '@kbn/visualizations-plugin/public';
import { ReportingStart } from '@kbn/reporting-plugin/public';
import {
CoreSetup,
@ -68,7 +67,6 @@ export interface CanvasStartDeps {
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
presentationUtil: PresentationUtilPluginStart;
visualizations: VisualizationsStart;
spaces?: SpacesPluginStart;
contentManagement: ContentManagementPublicStart;
}

View file

@ -17,7 +17,6 @@ import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/
import type { ReportingStart } from '@kbn/reporting-plugin/public';
import type { SpacesApi } from '@kbn/spaces-plugin/public';
import type { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
import type { CanvasStartDeps } from '../plugin';
@ -33,7 +32,6 @@ export let presentationUtilService: PresentationUtilPluginStart;
export let reportingService: ReportingStart | undefined;
export let spacesService: SpacesApi | undefined;
export let uiActionsService: UiActionsPublicStart;
export let visualizationsService: VisualizationsStart;
const servicesReady$ = new BehaviorSubject(false);
@ -56,7 +54,6 @@ export const setKibanaServices = (
: undefined;
spacesService = deps.spaces;
uiActionsService = deps.uiActions;
visualizationsService = deps.visualizations;
servicesReady$.next(true);
};

View file

@ -20,7 +20,6 @@ import { presentationUtilPluginMock } from '@kbn/presentation-util-plugin/public
import { reportingPluginMock } from '@kbn/reporting-plugin/public/mocks';
import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks';
import { setKibanaServices } from './kibana_services';
import { getId } from '../lib/get_id';
@ -61,7 +60,6 @@ export const setStubKibanaServices = () => {
reporting: reportingPluginMock.createStartContract(),
spaces: spacesPluginMock.createStartContract(),
uiActions: uiActionsPluginMock.createStartContract(),
visualizations: visualizationsPluginMock.createStartContract(),
},
coreMock.createPluginInitializerContext()
);

View file

@ -47,7 +47,6 @@
"@kbn/presentation-util-plugin",
"@kbn/ui-actions-plugin",
"@kbn/usage-collection-plugin",
"@kbn/visualizations-plugin",
"@kbn/features-plugin",
"@kbn/lens-plugin",
"@kbn/maps-plugin",
@ -89,7 +88,8 @@
"@kbn/presentation-containers",
"@kbn/presentation-publishing",
"@kbn/react-kibana-context-render",
"@kbn/search-types"
"@kbn/search-types",
"@kbn/visualizations-plugin"
],
"exclude": ["target/**/*"]
}

View file

@ -29,7 +29,9 @@ export type CanvasContainerApi = PublishesViewMode &
HasDisableTriggers &
HasType &
HasSerializedChildState &
HasAppContext &
PublishesReload &
Partial<PublishesUnifiedSearch> &
Partial<HasAppContext & PublishesUnifiedSearch> & {
reload: () => void;
};

View file

@ -51,3 +51,5 @@ export * from './chart_info_api';
export * from './trigger_actions/open_in_discover_helpers';
export * from './trigger_actions/open_lens_config/create_action_helpers';
export * from './trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers';
export { getAddLensPanelAction } from './trigger_actions/add_lens_panel_action';
export { AddESQLPanelAction } from './trigger_actions/open_lens_config/add_esql_panel_action';

View file

@ -114,9 +114,8 @@ import type {
DatasourceMap,
VisualizationMap,
} from './types';
import { getLensAliasConfig } from './vis_type_alias';
import { lensVisTypeAlias } from './vis_type_alias';
import { createOpenInDiscoverAction } from './trigger_actions/open_in_discover_action';
import { CreateESQLPanelAction } from './trigger_actions/open_lens_config/create_action';
import {
inAppEmbeddableEditTrigger,
IN_APP_EMBEDDABLE_EDIT_TRIGGER,
@ -149,6 +148,7 @@ import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the
import { convertToLensActionFactory } from './trigger_actions/convert_to_lens_action';
import { LensRenderer } from './react_embeddable/renderer/lens_custom_renderer_component';
import { deserializeState } from './react_embeddable/helper';
import { ACTION_CREATE_ESQL_CHART } from './trigger_actions/open_lens_config/constants';
export type { SaveProps } from './app_plugin';
@ -440,7 +440,7 @@ export class LensPlugin {
);
}
visualizations.registerAlias(getLensAliasConfig());
visualizations.registerAlias(lensVisTypeAlias);
uiActionsEnhanced.registerDrilldown(
new OpenInDiscoverDrilldown({
@ -682,15 +682,30 @@ export class LensPlugin {
editLensEmbeddableAction
);
// Displays the add ESQL panel in the dashboard add Panel menu
const createESQLPanelAction = new CreateESQLPanelAction(startDependencies, core, async () => {
if (!this.editorFrameService) {
await this.initEditorFrameService();
}
startDependencies.uiActions.addTriggerActionAsync(
ADD_PANEL_TRIGGER,
ACTION_CREATE_ESQL_CHART,
async () => {
const { AddESQLPanelAction } = await import('./async_services');
return new AddESQLPanelAction(startDependencies, core, async () => {
if (!this.editorFrameService) {
await this.initEditorFrameService();
}
return this.editorFrameService!;
return this.editorFrameService!;
});
}
);
startDependencies.uiActions.registerActionAsync('addLensPanelAction', async () => {
const { getAddLensPanelAction } = await import('./async_services');
return getAddLensPanelAction(startDependencies);
});
startDependencies.uiActions.addTriggerAction(ADD_PANEL_TRIGGER, createESQLPanelAction);
startDependencies.uiActions.attachAction(ADD_PANEL_TRIGGER, 'addLensPanelAction');
if (startDependencies.uiActions.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
// the create action if the Canvas-specific trigger does indeed exist.
startDependencies.uiActions.attachAction('ADD_CANVAS_ELEMENT_TRIGGER', 'addLensPanelAction');
}
const discoverLocator = startDependencies.share?.url.locators.get('DISCOVER_APP_LOCATOR');
if (discoverLocator) {

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EmbeddableApiContext, apiHasAppContext } from '@kbn/presentation-publishing';
import { ADD_PANEL_VISUALIZATION_GROUP } from '@kbn/embeddable-plugin/public';
import { LensPluginStartDependencies } from '../plugin';
import { lensVisTypeAlias } from '../vis_type_alias';
export function getAddLensPanelAction(deps: LensPluginStartDependencies) {
return {
id: 'addLensPanelAction',
getIconType: () => lensVisTypeAlias.icon,
order: lensVisTypeAlias.order,
isCompatible: async () => true,
execute: async ({ embeddable }: EmbeddableApiContext) => {
const stateTransferService = deps.embeddable.getStateTransfer();
stateTransferService.navigateToEditor(lensVisTypeAlias.alias!.app, {
path: lensVisTypeAlias.alias!.path,
state: {
originatingApp: apiHasAppContext(embeddable)
? embeddable.getAppContext().currentAppId
: '',
originatingPath: apiHasAppContext(embeddable)
? embeddable.getAppContext().getCurrentPath?.()
: undefined,
searchSessionId: deps.data.search.session.getSessionId(),
},
});
},
grouping: [ADD_PANEL_VISUALIZATION_GROUP],
getDisplayName: () => lensVisTypeAlias.title,
getDisplayNameTooltip: () => lensVisTypeAlias.description,
};
}

View file

@ -10,7 +10,7 @@ import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks
import type { LensPluginStartDependencies } from '../../plugin';
import type { EditorFrameService } from '../../editor_frame_service';
import { createMockStartDependencies } from '../../editor_frame_service/mocks';
import { CreateESQLPanelAction } from './create_action';
import { AddESQLPanelAction } from './add_esql_panel_action';
describe('create Lens panel action', () => {
const core = coreMock.createStart();
@ -27,13 +27,9 @@ describe('create Lens panel action', () => {
describe('compatibility check', () => {
it('is incompatible if ui setting for ES|QL is off', async () => {
const configurablePanelAction = new CreateESQLPanelAction(
mockStartDependencies,
core,
mockGetEditorFrameService
);
const action = new AddESQLPanelAction(mockStartDependencies, core, mockGetEditorFrameService);
const isCompatible = await configurablePanelAction.isCompatible({
const isCompatible = await action.isCompatible({
embeddable: mockPresentationContainer,
});
@ -51,13 +47,13 @@ describe('create Lens panel action', () => {
},
} as CoreStart;
const createESQLAction = new CreateESQLPanelAction(
const action = new AddESQLPanelAction(
mockStartDependencies,
updatedCore,
mockGetEditorFrameService
);
const isCompatible = await createESQLAction.isCompatible({
const isCompatible = await action.isCompatible({
embeddable: mockPresentationContainer,
});

View file

@ -12,12 +12,10 @@ import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import { ADD_PANEL_VISUALIZATION_GROUP } from '@kbn/embeddable-plugin/public';
import type { LensPluginStartDependencies } from '../../plugin';
import type { EditorFrameService } from '../../editor_frame_service';
import { ACTION_CREATE_ESQL_CHART } from './constants';
import { executeCreateAction, isCreateActionCompatible } from '../../async_services';
const ACTION_CREATE_ESQL_CHART = 'ACTION_CREATE_ESQL_CHART';
export const getAsyncHelpers = async () => await import('../../async_services');
export class CreateESQLPanelAction implements Action<EmbeddableApiContext> {
export class AddESQLPanelAction implements Action<EmbeddableApiContext> {
public type = ACTION_CREATE_ESQL_CHART;
public id = ACTION_CREATE_ESQL_CHART;
public order = 50;
@ -42,15 +40,11 @@ export class CreateESQLPanelAction implements Action<EmbeddableApiContext> {
}
public async isCompatible({ embeddable }: EmbeddableApiContext) {
if (!apiIsPresentationContainer(embeddable)) return false;
const { isCreateActionCompatible } = await getAsyncHelpers();
return isCreateActionCompatible(this.core);
return apiIsPresentationContainer(embeddable) && isCreateActionCompatible(this.core);
}
public async execute({ embeddable }: EmbeddableApiContext) {
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
const { executeCreateAction } = await getAsyncHelpers();
const editorFrameService = await this.getEditorFrameService();
executeCreateAction({

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const ACTION_CREATE_ESQL_CHART = 'ACTION_CREATE_ESQL_CHART';

View file

@ -17,7 +17,7 @@ import {
} from '../common/constants';
import { getLensClient } from './persistence/lens_client';
export const getLensAliasConfig = (): VisTypeAlias => ({
export const lensVisTypeAlias: VisTypeAlias = {
alias: {
path: getBasePath(),
app: APP_ID,
@ -59,4 +59,4 @@ export const getLensAliasConfig = (): VisTypeAlias => ({
},
},
},
});
};

View file

@ -18,48 +18,44 @@ import {
} from '../common/constants';
import { getMapClient } from './content_management';
export function getMapsVisTypeAlias() {
const appDescription = i18n.translate('xpack.maps.visTypeAlias.description', {
export const mapsVisTypeAlias = {
alias: {
app: APP_ID,
path: `/${MAP_PATH}`,
},
name: APP_ID,
title: APP_NAME,
description: i18n.translate('xpack.maps.visTypeAlias.description', {
defaultMessage: 'Create and style maps with multiple layers and indices.',
});
}),
icon: APP_ICON,
stage: 'production' as VisualizationStage,
order: 40,
appExtensions: {
visualizations: {
docTypes: [MAP_SAVED_OBJECT_TYPE],
searchFields: ['title^3'],
client: getMapClient,
toListItem(mapItem: MapItem) {
const { id, type, updatedAt, attributes, managed } = mapItem;
const { title, description } = attributes;
return {
alias: {
app: APP_ID,
path: `/${MAP_PATH}`,
},
name: APP_ID,
title: APP_NAME,
description: appDescription,
icon: APP_ICON,
stage: 'production' as VisualizationStage,
order: 40,
appExtensions: {
visualizations: {
docTypes: [MAP_SAVED_OBJECT_TYPE],
searchFields: ['title^3'],
client: getMapClient,
toListItem(mapItem: MapItem) {
const { id, type, updatedAt, attributes, managed } = mapItem;
const { title, description } = attributes;
return {
id,
title,
description,
updatedAt,
managed,
editor: {
editUrl: getEditPath(id),
editApp: APP_ID,
},
icon: APP_ICON,
stage: 'production' as VisualizationStage,
savedObjectType: type,
typeTitle: APP_NAME,
};
},
return {
id,
title,
description,
updatedAt,
managed,
editor: {
editUrl: getEditPath(id),
editApp: APP_ID,
},
icon: APP_ICON,
stage: 'production' as VisualizationStage,
savedObjectType: type,
typeTitle: APP_NAME,
};
},
},
};
}
},
};

View file

@ -23,7 +23,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public';
import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public';
import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
import { ADD_PANEL_TRIGGER, VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/public';
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
@ -73,7 +73,7 @@ import { filterByMapExtentAction } from './trigger_actions/filter_by_map_extent/
import { synchronizeMovementAction } from './trigger_actions/synchronize_movement/action';
import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_action';
import { APP_NAME, APP_ICON_SOLUTION, APP_ID } from '../common/constants';
import { getMapsVisTypeAlias } from './maps_vis_type_alias';
import { mapsVisTypeAlias } from './maps_vis_type_alias';
import { featureCatalogueEntry } from './feature_catalogue_entry';
import {
setIsCloudEnabled,
@ -192,7 +192,7 @@ export class MapsPlugin
if (plugins.home) {
plugins.home.featureCatalogue.register(featureCatalogueEntry);
}
plugins.visualizations.registerAlias(getMapsVisTypeAlias());
plugins.visualizations.registerAlias(mapsVisTypeAlias);
core.application.register({
id: APP_ID,
@ -257,6 +257,17 @@ export class MapsPlugin
plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, filterByMapExtentAction);
plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, synchronizeMovementAction);
plugins.uiActions.registerActionAsync('addMapPanelAction', async () => {
const { getAddMapPanelAction } = await import('./trigger_actions/add_map_panel_action');
return getAddMapPanelAction(plugins);
});
plugins.uiActions.attachAction(ADD_PANEL_TRIGGER, 'addMapPanelAction');
if (plugins.uiActions.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
// the create action if the Canvas-specific trigger does indeed exist.
plugins.uiActions.attachAction('ADD_CANVAS_ELEMENT_TRIGGER', 'addMapPanelAction');
}
if (!core.application.capabilities.maps_v2.save) {
plugins.visualizations.unRegisterAlias(APP_ID);
}

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EmbeddableApiContext, apiHasAppContext } from '@kbn/presentation-publishing';
import { ADD_PANEL_VISUALIZATION_GROUP } from '@kbn/embeddable-plugin/public';
import { MapsPluginStartDependencies } from '../plugin';
import { mapsVisTypeAlias } from '../maps_vis_type_alias';
export function getAddMapPanelAction(deps: MapsPluginStartDependencies) {
return {
id: 'addMapPanelAction',
getIconType: () => mapsVisTypeAlias.icon,
order: mapsVisTypeAlias.order,
isCompatible: async () => true,
execute: async ({ embeddable }: EmbeddableApiContext) => {
const stateTransferService = deps.embeddable.getStateTransfer();
stateTransferService.navigateToEditor(mapsVisTypeAlias.alias.app, {
path: mapsVisTypeAlias.alias.path,
state: {
originatingApp: apiHasAppContext(embeddable)
? embeddable.getAppContext().currentAppId
: '',
originatingPath: apiHasAppContext(embeddable)
? embeddable.getAppContext().getCurrentPath?.()
: undefined,
searchSessionId: deps.data.search.session.getSessionId(),
},
});
},
grouping: [ADD_PANEL_VISUALIZATION_GROUP],
getDisplayName: () => mapsVisTypeAlias.title,
getDisplayNameTooltip: () => mapsVisTypeAlias.description,
};
}

View file

@ -36,7 +36,7 @@ export default function canvasLensTest({ getService, getPageObjects }: FtrProvid
describe('by-value', () => {
it('creates new lens embeddable', async () => {
await canvas.createNewVis('lens');
await canvas.addNewPanel('Lens');
await lens.goToTimeRange();
await lens.configureDimension({
dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension',

View file

@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('by-value', () => {
it('creates new map embeddable', async () => {
const originalEmbeddableCount = await canvas.getEmbeddableCount();
await canvas.createNewVis('maps');
await canvas.addNewPanel('Maps');
await maps.clickSaveAndReturnButton();
const embeddableCount = await canvas.getEmbeddableCount();
expect(embeddableCount).to.eql(originalEmbeddableCount + 1);

View file

@ -59,30 +59,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('by-value', () => {
it('creates new tsvb embeddable', async () => {
const originalEmbeddableCount = await canvas.getEmbeddableCount();
await canvas.createNewVis('metrics');
await visualize.saveVisualizationAndReturn();
await retry.try(async () => {
const embeddableCount = await canvas.getEmbeddableCount();
expect(embeddableCount).to.eql(originalEmbeddableCount + 1);
});
});
it('edits tsvb by-value embeddable', async () => {
const originalEmbeddableCount = await canvas.getEmbeddableCount();
await dashboardPanelActions.clickEdit();
await visualize.saveVisualizationAndReturn();
await retry.try(async () => {
const embeddableCount = await canvas.getEmbeddableCount();
expect(embeddableCount).to.eql(originalEmbeddableCount);
});
await canvas.deleteSelectedElement();
});
it('creates new vega embeddable', async () => {
const originalEmbeddableCount = await canvas.getEmbeddableCount();
await canvas.createNewVis('vega');
await canvas.addNewPanel('Custom visualization');
await visualize.saveVisualizationAndReturn();
await retry.try(async () => {
const embeddableCount = await canvas.getEmbeddableCount();

View file

@ -173,7 +173,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await dashboard.clickNewDashboard();
await dashboard.waitForRenderComplete();
await dashboardAddPanel.clickMarkdownQuickButton();
await dashboardAddPanel.clickAddMarkdownPanel();
await visEditor.setMarkdownTxt(originalMarkdownText);
await visEditor.clickGo();

View file

@ -30,9 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
if (inViewMode) {
await dashboard.switchToEditMode();
}
await dashboardAddPanel.clickEditorMenuButton();
await testSubjects.setValue('dashboardPanelSelectionFlyout__searchInput', 'maps');
await dashboardAddPanel.clickVisType('maps');
await dashboardAddPanel.clickAddMapPanel();
await maps.clickSaveAndReturnButton();
}

View file

@ -296,8 +296,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// create a new dashboard, then a new visualization in Lens.
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await testSubjects.click('dashboardEditorMenuButton');
await testSubjects.click('visType-lens');
await dashboardAddPanel.clickAddLensPanel();
// Configure it and save to return to the dashboard.
await lens.waitForField('@timestamp');
await lens.configureDimension({

View file

@ -80,7 +80,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickVisType('metrics');
await dashboardAddPanel.clickAddNewPanelFromUIActionLink('metrics');
await testSubjects.click('visualizesaveAndReturnButton');
await panelActions.saveToLibrary(visTitle);

View file

@ -9,12 +9,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const find = getService('find');
const { dashboard, header, maps, visualize } = getPageObjects([
'dashboard',
'header',
'maps',
'visualize',
]);
const { dashboard, header, maps } = getPageObjects(['dashboard', 'header', 'maps']);
const kibanaServer = getService('kibanaServer');
const security = getService('security');
const dashboardAddPanel = getService('dashboardAddPanel');
@ -38,8 +33,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await dashboardAddPanel.clickEditorMenuButton();
await visualize.clickMapsApp();
await dashboardAddPanel.clickAddMapPanel();
await header.waitUntilLoadingHasFinished();
await maps.waitForLayersToLoad();
await maps.clickSaveAndReturnButton();

View file

@ -42,8 +42,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
beforeEach(async () => {
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickVisType('maps');
await dashboardAddPanel.clickAddMapPanel();
await header.waitUntilLoadingHasFinished();
await maps.waitForLayersToLoad();
});

View file

@ -173,10 +173,10 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo
await testSubjects.click('breadcrumb first');
},
async createNewVis(visType: string) {
log.debug('CanvasPage.createNewVisType', visType);
async addNewPanel(actionName: string) {
log.debug('CanvasPage.addNewPanel', actionName);
await testSubjects.click('canvasEditorMenuButton');
await testSubjects.click(`visType-${visType}`);
await testSubjects.click(`create-action-${actionName}`);
},
async getEmbeddableCount() {