From 22e0545d0e89aa39c39f3f6a57467aa16f165679 Mon Sep 17 00:00:00 2001
From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com>
Date: Wed, 26 Jun 2024 22:55:28 +0200
Subject: [PATCH] Redesign the "Add Panel" Experience (#183764)
## Summary
Closes https://github.com/elastic/kibana/issues/144418
This PR introduces changes to the dashboard add panel selection
functionality, so that panel selection would now happen from within a
flyout, and as such panels are now grouped together logically.
With this implementation any panel that is intended to show up within
this new flyout is required to have either been registered leveraging
the ui action trigger `ADD_PANEL_TRIGGER` and have it's `grouping` value
defined or belong to a subset of visualization types (`PROMOTED`,
`TOOLS`, and `LEGACY`) that would automatically get grouped.
It's worth pointing out that because we can't control the order at which
UI actions gets registered, we won't always get the the panel groups in
the same order, for this specific reason ~a new optional property
(`placementPriority`) has been added in~ the property `order` is now
leveraged such that it allows a user registering a UI action define a
relative weight for where they'd like their group to show up. All
registered actions would be rendered in descending order considering all
`order` defined, in the case where no order is defined `0` is assumed
for the group. In addition an action which is registered without a
group, would automatically get assigned into a default group titled
"Other".
The search implemented within the add panel is rudimentary, checking if
the group titles and group item titles contain the input character; when
a group title is matched the entire group is remains highlighted, in the
case that the group isn't matched and it's just the group item, only
said item is highlighted within it's group.
## Visuals
#### Default view
#### Search match view
##### P.S.
This changes also includes changes to the display of certain panels;
- ML group has a new title i.e. *Machine Learning and Analytics*
- In serverless, the observability panels (SLO*) only shows as a
selection choice in the observability project type.
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---------
Co-authored-by: Catherine Liu
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../data_table/create_data_table_action.ts | 8 +-
.../embeddable_examples_grouping.ts | 1 +
.../create_eui_markdown_action.tsx | 8 +-
.../field_list/create_field_list_action.tsx | 4 +-
.../saved_book/create_saved_book_action.tsx | 4 +-
.../register_add_search_panel_action.tsx | 8 +-
.../triggers/dashboard_app_panel_trigger.ts | 8 +-
.../src/triggers/index.ts | 1 +
.../public/dashboard_actions/index.ts | 1 -
.../add_panel_action_menu_items.test.ts | 50 ++-
.../top_nav/add_panel_action_menu_items.ts | 80 ++--
.../dashboard_app/top_nav/editor_menu.scss | 2 +-
.../top_nav/editor_menu.test.tsx | 75 ++--
.../dashboard_app/top_nav/editor_menu.tsx | 369 ++++++++----------
.../open_dashboard_panel_selection_flyout.tsx | 255 ++++++++++++
src/plugins/dashboard/public/plugin.tsx | 7 +-
src/plugins/embeddable/public/index.ts | 2 +
.../lib/embeddables/common/constants.ts | 37 ++
.../lib/embeddables/embeddable_factory.ts | 2 +
.../public/actions/create_image_action.ts | 7 +-
.../public/embeddable/links_embeddable.tsx | 3 +
.../embeddable/links_embeddable_factory.ts | 4 +-
src/plugins/ui_actions/public/index.ts | 3 +
src/plugins/ui_actions/public/plugin.ts | 2 +
.../vis_type_markdown/public/markdown_vis.ts | 1 +
.../timeseries/public/metrics_type.ts | 3 +-
.../public/actions/add_agg_vis_action.ts | 64 +++
.../public/embeddable/constants.ts | 11 +
.../visualizations/public/embeddable/index.ts | 2 +-
src/plugins/visualizations/public/index.ts | 1 +
src/plugins/visualizations/public/plugin.ts | 13 +-
.../public/vis_types/base_vis_type.ts | 2 +
.../visualizations/public/vis_types/types.ts | 2 +
.../public/vis_types/vis_groups_enum.ts | 1 +
.../vis_types/vis_type_alias_registry.ts | 1 +
.../agg_based_selection.tsx | 9 +-
.../group_selection/group_selection.tsx | 3 +
.../public/wizard/new_vis_modal.tsx | 1 +
.../public/wizard/show_new_vis.tsx | 78 ++--
src/plugins/visualizations/tsconfig.json | 3 +-
.../apps/dashboard/group5/empty_dashboard.ts | 2 +-
.../services/dashboard/add_panel.ts | 12 +-
.../ui_actions/create_change_point_chart.tsx | 4 +-
.../plugins/aiops/public/ui_actions/index.ts | 4 +-
.../editor_menu/editor_menu.component.tsx | 4 +-
.../editor_menu/editor_menu.tsx | 13 +-
x-pack/plugins/lens/public/plugin.ts | 3 +-
.../open_lens_config/create_action.tsx | 3 +
x-pack/plugins/lens/public/vis_type_alias.ts | 1 +
.../maps/public/maps_vis_type_alias.ts | 1 +
x-pack/plugins/ml/common/constants/app.ts | 2 +-
.../ui_actions/create_anomaly_chart.tsx | 4 +
.../create_single_metric_viewer.tsx | 2 +
.../ml/public/ui_actions/create_swim_lane.tsx | 23 ++
x-pack/plugins/ml/public/ui_actions/index.ts | 8 +-
.../infra/public/plugin.ts | 7 +-
.../public/embeddable/slo/common/constants.ts | 2 +-
.../slo/public/plugin.ts | 3 +-
.../ui_actions/create_alerts_panel_action.tsx | 1 +
.../ui_actions/create_error_budget_action.tsx | 1 +
.../create_overview_panel_action.tsx | 1 +
.../slo/public/ui_actions/index.ts | 21 +-
.../translations/translations/fr-FR.json | 5 +-
.../translations/translations/ja-JP.json | 5 +-
.../translations/translations/zh-CN.json | 5 +-
.../group2/dashboard_maps_by_value.ts | 1 +
.../slo/embeddables/overview_embeddable.ts | 4 +-
.../services/ml/dashboard_embeddables.ts | 4 +-
68 files changed, 886 insertions(+), 396 deletions(-)
rename src/plugins/dashboard/public/triggers/index.ts => packages/kbn-ui-actions-browser/src/triggers/dashboard_app_panel_trigger.ts (76%)
create mode 100644 src/plugins/dashboard/public/dashboard_app/top_nav/open_dashboard_panel_selection_flyout.tsx
create mode 100644 src/plugins/embeddable/public/lib/embeddables/common/constants.ts
create mode 100644 src/plugins/visualizations/public/actions/add_agg_vis_action.ts
diff --git a/examples/embeddable_examples/public/react_embeddables/data_table/create_data_table_action.ts b/examples/embeddable_examples/public/react_embeddables/data_table/create_data_table_action.ts
index 9971535e148dd..6364073ade676 100644
--- a/examples/embeddable_examples/public/react_embeddables/data_table/create_data_table_action.ts
+++ b/examples/embeddable_examples/public/react_embeddables/data_table/create_data_table_action.ts
@@ -9,7 +9,11 @@
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 {
+ IncompatibleActionError,
+ UiActionsStart,
+ ADD_PANEL_TRIGGER,
+} from '@kbn/ui-actions-plugin/public';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import { ADD_DATA_TABLE_ACTION_ID, DATA_TABLE_ID } from './constants';
@@ -39,5 +43,5 @@ export const registerCreateDataTableAction = (uiActions: UiActionsStart) => {
defaultMessage: 'Data table',
}),
});
- uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_DATA_TABLE_ACTION_ID);
+ uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_DATA_TABLE_ACTION_ID);
};
diff --git a/examples/embeddable_examples/public/react_embeddables/embeddable_examples_grouping.ts b/examples/embeddable_examples/public/react_embeddables/embeddable_examples_grouping.ts
index fa2ecd03b5d25..4c7f52d261fcb 100644
--- a/examples/embeddable_examples/public/react_embeddables/embeddable_examples_grouping.ts
+++ b/examples/embeddable_examples/public/react_embeddables/embeddable_examples_grouping.ts
@@ -10,4 +10,5 @@ export const embeddableExamplesGrouping = {
id: 'embeddableExamples',
getIconType: () => 'documentation',
getDisplayName: () => 'Embeddable examples',
+ order: -10,
};
diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx b/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx
index 81c23a4d960b8..c5eb2d72cc479 100644
--- a/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx
+++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx
@@ -9,7 +9,11 @@
import { i18n } from '@kbn/i18n';
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
-import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
+import {
+ IncompatibleActionError,
+ UiActionsStart,
+ ADD_PANEL_TRIGGER,
+} from '@kbn/ui-actions-plugin/public';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import { ADD_EUI_MARKDOWN_ACTION_ID, EUI_MARKDOWN_ID } from './constants';
import { MarkdownEditorSerializedState } from './types';
@@ -41,7 +45,7 @@ export const registerCreateEuiMarkdownAction = (uiActions: UiActionsStart) => {
defaultMessage: 'EUI Markdown',
}),
});
- uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_EUI_MARKDOWN_ACTION_ID);
+ uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_EUI_MARKDOWN_ACTION_ID);
if (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.
diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx b/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx
index e05868e7737d1..175c3119955a2 100644
--- a/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx
+++ b/examples/embeddable_examples/public/react_embeddables/field_list/create_field_list_action.tsx
@@ -9,7 +9,7 @@
import { i18n } from '@kbn/i18n';
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
-import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
+import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import { ADD_FIELD_LIST_ACTION_ID, FIELD_LIST_ID } from './constants';
@@ -34,5 +34,5 @@ export const registerCreateFieldListAction = (uiActions: UiActionsPublicStart) =
defaultMessage: 'Field list',
}),
});
- uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_FIELD_LIST_ACTION_ID);
+ uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_FIELD_LIST_ACTION_ID);
};
diff --git a/examples/embeddable_examples/public/react_embeddables/saved_book/create_saved_book_action.tsx b/examples/embeddable_examples/public/react_embeddables/saved_book/create_saved_book_action.tsx
index 6916bd38cc28d..eaaa607f76001 100644
--- a/examples/embeddable_examples/public/react_embeddables/saved_book/create_saved_book_action.tsx
+++ b/examples/embeddable_examples/public/react_embeddables/saved_book/create_saved_book_action.tsx
@@ -10,7 +10,7 @@ import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
-import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
+import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import {
@@ -67,5 +67,5 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
defaultMessage: 'Book',
}),
});
- uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_SAVED_BOOK_ACTION_ID);
+ uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_SAVED_BOOK_ACTION_ID);
};
diff --git a/examples/embeddable_examples/public/react_embeddables/search/register_add_search_panel_action.tsx b/examples/embeddable_examples/public/react_embeddables/search/register_add_search_panel_action.tsx
index 945e969187631..1bffd091164b0 100644
--- a/examples/embeddable_examples/public/react_embeddables/search/register_add_search_panel_action.tsx
+++ b/examples/embeddable_examples/public/react_embeddables/search/register_add_search_panel_action.tsx
@@ -8,7 +8,11 @@
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
-import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
+import {
+ IncompatibleActionError,
+ type UiActionsStart,
+ ADD_PANEL_TRIGGER,
+} from '@kbn/ui-actions-plugin/public';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import { ADD_SEARCH_ACTION_ID, SEARCH_EMBEDDABLE_ID } from './constants';
import { SearchSerializedState } from './types';
@@ -33,7 +37,7 @@ export const registerAddSearchPanelAction = (uiActions: UiActionsStart) => {
);
},
});
- uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_SEARCH_ACTION_ID);
+ uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_SEARCH_ACTION_ID);
if (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.
diff --git a/src/plugins/dashboard/public/triggers/index.ts b/packages/kbn-ui-actions-browser/src/triggers/dashboard_app_panel_trigger.ts
similarity index 76%
rename from src/plugins/dashboard/public/triggers/index.ts
rename to packages/kbn-ui-actions-browser/src/triggers/dashboard_app_panel_trigger.ts
index 96dfa814b949a..74e894d2b5563 100644
--- a/src/plugins/dashboard/public/triggers/index.ts
+++ b/packages/kbn-ui-actions-browser/src/triggers/dashboard_app_panel_trigger.ts
@@ -5,16 +5,18 @@
* 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 type { Trigger } from '@kbn/ui-actions-plugin/public';
+import { Trigger } from '.';
export const ADD_PANEL_TRIGGER = 'ADD_PANEL_TRIGGER';
+
export const addPanelMenuTrigger: Trigger = {
id: ADD_PANEL_TRIGGER,
- title: i18n.translate('dashboard.addPanelMenuTrigger.title', {
+ title: i18n.translate('uiActions.triggers.dashboard.addPanelMenu.title', {
defaultMessage: 'Add panel menu',
}),
- description: i18n.translate('dashboard.addPanelMenuTrigger.description', {
+ description: i18n.translate('uiActions.triggers.dashboard.addPanelMenu.description', {
defaultMessage: "A new action will appear to the dashboard's add panel menu",
}),
};
diff --git a/packages/kbn-ui-actions-browser/src/triggers/index.ts b/packages/kbn-ui-actions-browser/src/triggers/index.ts
index 091305791d858..d298be1524411 100644
--- a/packages/kbn-ui-actions-browser/src/triggers/index.ts
+++ b/packages/kbn-ui-actions-browser/src/triggers/index.ts
@@ -11,3 +11,4 @@ export * from './row_click_trigger';
export * from './default_trigger';
export * from './visualize_field_trigger';
export * from './visualize_geo_field_trigger';
+export * from './dashboard_app_panel_trigger';
diff --git a/src/plugins/dashboard/public/dashboard_actions/index.ts b/src/plugins/dashboard/public/dashboard_actions/index.ts
index ecffeaa5643ae..ba93db83247d4 100644
--- a/src/plugins/dashboard/public/dashboard_actions/index.ts
+++ b/src/plugins/dashboard/public/dashboard_actions/index.ts
@@ -8,7 +8,6 @@
import { CoreStart } from '@kbn/core/public';
import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public';
-
import { DashboardStartDependencies } from '../plugin';
import { AddToLibraryAction } from './add_to_library_action';
import { LegacyAddToLibraryAction } from './legacy_add_to_library_action';
diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts
index ed2fadd359db7..180b9b7eb2ff9 100644
--- a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts
+++ b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts
@@ -7,7 +7,7 @@
*/
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
-import { getAddPanelActionMenuItems } from './add_panel_action_menu_items';
+import { getAddPanelActionMenuItemsGroup } from './add_panel_action_menu_items';
describe('getAddPanelActionMenuItems', () => {
it('returns the items correctly', async () => {
@@ -54,39 +54,53 @@ describe('getAddPanelActionMenuItems', () => {
],
},
];
- const [items, grouped] = getAddPanelActionMenuItems(
+ const grouped = getAddPanelActionMenuItemsGroup(
getMockPresentationContainer(),
registeredActions,
jest.fn()
);
- expect(items).toStrictEqual([
- {
- 'data-test-subj': 'create-action-Action name',
- icon: 'pencil',
- name: 'Action name',
- onClick: expect.any(Function),
- toolTipContent: 'Action tooltip',
- },
- ]);
+
expect(grouped).toStrictEqual({
groupedAddPanelAction: {
id: 'groupedAddPanelAction',
title: 'Custom group',
- icon: 'logoElasticsearch',
+ order: 0,
+ 'data-test-subj': 'dashboardEditorMenu-groupedAddPanelActionGroup',
items: [
{
'data-test-subj': 'create-action-Action name 01',
icon: 'pencil',
+ id: 'TEST_ACTION_01',
name: 'Action name 01',
onClick: expect.any(Function),
- toolTipContent: 'Action tooltip',
+ description: 'Action tooltip',
+ order: 0,
},
{
'data-test-subj': 'create-action-Action name',
icon: 'empty',
+ id: 'TEST_ACTION_02',
+ name: 'Action name',
+ onClick: expect.any(Function),
+ description: 'Action tooltip',
+ order: 0,
+ },
+ ],
+ },
+ other: {
+ id: 'other',
+ title: 'Other',
+ order: -1,
+ 'data-test-subj': 'dashboardEditorMenu-otherGroup',
+ items: [
+ {
+ id: 'ACTION_CREATE_ESQL_CHART',
name: 'Action name',
+ icon: 'pencil',
+ description: 'Action tooltip',
onClick: expect.any(Function),
- toolTipContent: 'Action tooltip',
+ 'data-test-subj': 'create-action-Action name',
+ order: 0,
},
],
},
@@ -94,12 +108,8 @@ describe('getAddPanelActionMenuItems', () => {
});
it('returns empty array if no actions have been registered', async () => {
- const [items, grouped] = getAddPanelActionMenuItems(
- getMockPresentationContainer(),
- [],
- jest.fn()
- );
- expect(items).toStrictEqual([]);
+ const grouped = getAddPanelActionMenuItemsGroup(getMockPresentationContainer(), [], jest.fn());
+
expect(grouped).toStrictEqual({});
});
});
diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts
index 678129d0f5808..4e90d94caa388 100644
--- a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts
+++ b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts
@@ -5,13 +5,35 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-import type { ActionExecutionContext, Action } from '@kbn/ui-actions-plugin/public';
+
+import {
+ type ActionExecutionContext,
+ type Action,
+ addPanelMenuTrigger,
+} from '@kbn/ui-actions-plugin/public';
import { PresentationContainer } from '@kbn/presentation-containers';
-import type {
- EuiContextMenuPanelDescriptor,
- EuiContextMenuPanelItemDescriptor,
-} from '@elastic/eui';
-import { addPanelMenuTrigger } from '../../triggers';
+import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
+import type { IconType, CommonProps } from '@elastic/eui';
+import React, { type MouseEventHandler } from 'react';
+
+export interface PanelSelectionMenuItem extends Pick {
+ id: string;
+ name: string;
+ icon: IconType;
+ onClick: MouseEventHandler;
+ description?: string;
+ isDisabled?: boolean;
+ isDeprecated?: boolean;
+ order: number;
+}
+
+export type GroupedAddPanelActions = Pick<
+ PanelSelectionMenuItem,
+ 'id' | 'isDisabled' | 'data-test-subj' | 'order'
+> & {
+ title: string;
+ items: PanelSelectionMenuItem[];
+};
const onAddPanelActionClick =
(action: Action, context: ActionExecutionContext