diff --git a/examples/ui_actions_explorer/public/context_menu_examples/context_menu_examples.tsx b/examples/ui_actions_explorer/public/context_menu_examples/context_menu_examples.tsx
index b01d04c1608..1f6ba03e966 100644
--- a/examples/ui_actions_explorer/public/context_menu_examples/context_menu_examples.tsx
+++ b/examples/ui_actions_explorer/public/context_menu_examples/context_menu_examples.tsx
@@ -36,6 +36,7 @@ import { PanelViewWithSharingLong } from './panel_view_with_sharing_long';
import { PanelEdit } from './panel_edit';
import { PanelEditWithDrilldowns } from './panel_edit_with_drilldowns';
import { PanelEditWithDrilldownsAndContextActions } from './panel_edit_with_drilldowns_and_context_actions';
+import { PanelGroupOptionsAndContextActions } from './panel_group_options_and_context_actions';
export const ContextMenuExamples: React.FC = () => {
return (
@@ -59,7 +60,6 @@ export const ContextMenuExamples: React.FC = () => {
-
@@ -71,6 +71,11 @@ export const ContextMenuExamples: React.FC = () => {
+
+
+
+
+
);
};
diff --git a/examples/ui_actions_explorer/public/context_menu_examples/panel_group_options_and_context_actions.tsx b/examples/ui_actions_explorer/public/context_menu_examples/panel_group_options_and_context_actions.tsx
new file mode 100644
index 00000000000..3847046078d
--- /dev/null
+++ b/examples/ui_actions_explorer/public/context_menu_examples/panel_group_options_and_context_actions.tsx
@@ -0,0 +1,108 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Any modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as React from 'react';
+import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui';
+import useAsync from 'react-use/lib/useAsync';
+import { buildContextMenuForActions, Action } from '../../../../src/plugins/ui_actions/public';
+import { sampleAction } from './util';
+
+export const PanelGroupOptionsAndContextActions: React.FC = () => {
+ const [open, setOpen] = React.useState(false);
+
+ const context = {};
+ const trigger: any = 'TEST_TRIGGER';
+ const drilldownGrouping: Action['grouping'] = [
+ {
+ id: 'drilldowns',
+ getDisplayName: () => 'Uncategorized group',
+ getIconType: () => 'popout',
+ order: 20,
+ },
+ ];
+ const exampleGroup: Action['grouping'] = [
+ {
+ id: 'example',
+ getDisplayName: () => 'Example group',
+ getIconType: () => 'cloudStormy',
+ order: 20,
+ category: 'visAug',
+ },
+ ];
+ const alertingGroup: Action['grouping'] = [
+ {
+ id: 'alerting',
+ getDisplayName: () => 'Alerting',
+ getIconType: () => 'cloudStormy',
+ order: 20,
+ category: 'visAug',
+ },
+ ];
+ const anomaliesGroup: Action['grouping'] = [
+ {
+ id: 'anomalies',
+ getDisplayName: () => 'Anomalies',
+ getIconType: () => 'cloudStormy',
+ order: 30,
+ category: 'visAug',
+ },
+ ];
+ const actions = [
+ sampleAction('test-1', 100, 'Edit visualization', 'pencil'),
+ sampleAction('test-2', 99, 'Clone panel', 'partial'),
+
+ sampleAction('test-9', 10, 'Create drilldown', 'plusInCircle', drilldownGrouping),
+ sampleAction('test-10', 9, 'Manage drilldowns', 'list', drilldownGrouping),
+
+ sampleAction('test-11', 10, 'Example action', 'dashboardApp', exampleGroup),
+ sampleAction('test-11', 10, 'Alertin action 1', 'dashboardApp', alertingGroup),
+ sampleAction('test-12', 9, 'Alertin action 2', 'dashboardApp', alertingGroup),
+ sampleAction('test-13', 8, 'Anomalies 1', 'cloudStormy', anomaliesGroup),
+ sampleAction('test-14', 7, 'Anomalies 2', 'link', anomaliesGroup),
+ ];
+
+ const panels = useAsync(() =>
+ buildContextMenuForActions({
+ actions: actions.map((action) => ({ action, context, trigger })),
+ })
+ );
+
+ return (
+ setOpen((x) => !x)}>Grouping with categories}
+ isOpen={open}
+ panelPaddingSize="none"
+ anchorPosition="downLeft"
+ closePopover={() => setOpen(false)}
+ >
+
+
+ );
+};
diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.test.ts b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.test.ts
index b9afca9fb99..28fee3ec03f 100644
--- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.test.ts
+++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.test.ts
@@ -448,3 +448,117 @@ test('groups with deep nesting', async () => {
]
`);
});
+
+// Tests with:
+// a regular action
+// a group with 2 actions uncategorized
+// a group with 2 actions with a category of "test-category"
+// a group with 1 actions with a category of "test-category"
+test('groups with categories', async () => {
+ const grouping1 = [
+ {
+ id: 'test-group',
+ getDisplayName: () => 'Test group',
+ getIconType: () => 'bell',
+ },
+ ];
+ const grouping2 = [
+ {
+ id: 'test-group-2',
+ getDisplayName: () => 'Test group 2',
+ getIconType: () => 'bell',
+ category: 'test-category',
+ },
+ ];
+ const grouping3 = [
+ {
+ id: 'test-group-3',
+ getDisplayName: () => 'Test group 3',
+ getIconType: () => 'bell',
+ category: 'test-category',
+ },
+ ];
+
+ const actions = [
+ createTestAction({
+ dispayName: 'Foo 1',
+ }),
+ createTestAction({
+ dispayName: 'Bar 1',
+ grouping: grouping1,
+ }),
+ createTestAction({
+ dispayName: 'Bar 2',
+ grouping: grouping1,
+ }),
+ createTestAction({
+ dispayName: 'Qux 1',
+ grouping: grouping2,
+ }),
+ createTestAction({
+ dispayName: 'Qux 2',
+ grouping: grouping2,
+ }),
+ createTestAction({
+ dispayName: 'Waldo 1',
+ grouping: grouping3,
+ }),
+ ];
+ const menu = await buildContextMenuForActions({
+ actions: actions.map((action) => ({ action, context: {}, trigger: 'TEST' as any })),
+ });
+
+ expect(menu.map(resultMapper)).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "items": Array [
+ Object {
+ "name": "Foo 1",
+ },
+ Object {
+ "isSeparator": true,
+ },
+ Object {
+ "name": "Test group",
+ },
+ Object {
+ "isSeparator": true,
+ },
+ Object {
+ "name": "Test group 2",
+ },
+ Object {
+ "name": "Waldo 1",
+ },
+ ],
+ },
+ Object {
+ "items": Array [
+ Object {
+ "name": "Bar 1",
+ },
+ Object {
+ "name": "Bar 2",
+ },
+ ],
+ },
+ Object {
+ "items": Array [
+ Object {
+ "name": "Qux 1",
+ },
+ Object {
+ "name": "Qux 2",
+ },
+ ],
+ },
+ Object {
+ "items": Array [
+ Object {
+ "name": "Waldo 1",
+ },
+ ],
+ },
+ ]
+ `);
+});
diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx
index 6d69be1f3fa..ccc5c1c9c6e 100644
--- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx
+++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx
@@ -64,6 +64,7 @@ type PanelDescriptor = EuiContextMenuPanelDescriptor & {
_level?: number;
_icon?: string;
items: ItemDescriptor[];
+ _category?: string;
};
const onClick = (action: Action, context: ActionExecutionContext