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 b01d04c1608b..1f6ba03e966b 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 000000000000..20dc73406c55
--- /dev/null
+++ b/examples/ui_actions_explorer/public/context_menu_examples/panel_group_options_and_context_actions.tsx
@@ -0,0 +1,83 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+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/README.md b/src/plugins/ui_actions/README.md
index 28e3b2d63d2e..4431a47a06ed 100644
--- a/src/plugins/ui_actions/README.md
+++ b/src/plugins/ui_actions/README.md
@@ -97,3 +97,12 @@ Use the UI actions explorer in the Developer examples to learn more about the se
```sh
yarn start --run-examples
```
+
+## Action Properties
+
+Refer to [./public/actions/action.ts](./public/actions/action.ts) for all properties, keeping in mind it extends the [presentable](./public/util/presentable.ts) interface. Here are some properties that provide special functionality and customization.
+
+- `order` is used when there is more than one action matched to a trigger and within context menus. Higher numbers are displayed first.
+- `getDisplayName` is a function that can return either a string or a JSX element. Returning a JSX element allows flexibility with formatting.
+- `getIconType` can be used to add an icon before the display name.
+- `grouping` determines where this item should appear as a submenu. Each group can also contain a category, which is used within context menus to organize similar groups into the same section of the menu. See examples explorer for more details about what this looks like within a context menu.
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 b9afca9fb99c..e70561bea221 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,123 @@ 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" and low order of 10
+// a group with 1 actions with a category of "test-category" and high order of 20
+test('groups with categories and order', 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',
+ order: 10,
+ },
+ ];
+ const grouping3 = [
+ {
+ id: 'test-group-3',
+ getDisplayName: () => 'Test group 3',
+ getIconType: () => 'bell',
+ category: 'test-category',
+ order: 20,
+ },
+ ];
+
+ 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,
+ }),
+ // It is expected that, because there is only 1 action within this group,
+ // it will be added to the mainMenu as a single item, but next to other
+ // groups of the same category. When a group has a category, but only one
+ // item, we just add that single item; otherwise, we add a link to the group
+ 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": "Waldo 1",
+ },
+ Object {
+ "name": "Test group 2",
+ },
+ ],
+ },
+ 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 6d69be1f3faa..81710767e0a9 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,8 @@ type PanelDescriptor = EuiContextMenuPanelDescriptor & {
_level?: number;
_icon?: string;
items: ItemDescriptor[];
+ _category?: string;
+ _order?: number;
};
const onClick = (action: Action, context: ActionExecutionContext