From b98934bbd3d4ddcc6294f33939069f95853b6894 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Sun, 23 Apr 2023 21:15:57 -0700 Subject: [PATCH 01/11] enhance grouping for context menu options Signed-off-by: David Sinclair --- CHANGELOG.md | 47 +----- .../context_menu_examples.tsx | 2 + .../lib/embeddables/embeddable_factory.ts | 2 +- .../public/lib/panel/embeddable_panel.tsx | 13 +- .../ui_actions/public/actions/action.ts | 2 +- .../public/actions/action_internal.ts | 2 +- .../build_eui_context_menu_panels.test.ts | 139 +++++++++++++++++- .../build_eui_context_menu_panels.tsx | 52 +++++-- src/plugins/ui_actions/public/index.ts | 1 + .../ui_actions/public/util/presentable.ts | 2 +- 10 files changed, 200 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad516b6c049..351ec10dd7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [CVE-2023-25166] Bump formula to 3.0.1 ([#3416](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3416)) - [CVE-2023-25653] Bump node-jose to 2.2.0 ([#3445](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3445)) - [CVE-2023-26486][cve-2023-26487] Bump vega from 5.22.1 to 5.23.0 ([#3533](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3533)) -- [CVE-2023-0842] Bump xml2js from 0.4.23 to 0.5.0 ([#3842](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3842)) ### 📈 Features/Enhancements @@ -56,7 +55,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [I18n] Register ru, ru-RU locale ([#2817](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2817)) - Add yarn opensearch arg to setup plugin dependencies ([#2544](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2544)) - [Multi DataSource] Test the connection to an external data source when creating or updating ([#2973](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2973)) -- Add Dashboards-list integrations for Plugins ([#3090](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3090) ) - [Table Visualization] Refactor table visualization using React and DataGrid component ([#2863](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2863)) - [Vis Builder] Add redux store persistence ([#3088](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3088)) - [Multi DataSource] Improve test connection ([#3110](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3110)) @@ -64,20 +62,12 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Optimizer] Increase timeout waiting for the exiting of an optimizer worker ([#3193](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3193)) - [Data] Update `createAggConfig` so that newly created configs can be added to beginning of `aggConfig` array ([#3160](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3160)) - Add disablePrototypePoisoningProtection configuration to prevent JS client from erroring when cluster utilizes JS reserved words ([#2992](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2992)) -- [Multiple DataSource] Add support for SigV4 authentication ([#3058](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3058)) -- Make build scripts find and use the latest version of Node.js that satisfies `engines.node` ([#3467](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3467)) -- [Multiple DataSource] Refactor test connection to support SigV4 auth type ([#3456](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3456)) +- [Multiple DataSource] Add support for SigV4 authentication ([#3058](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3058)) +- Make build scripts find and use the latest version of Node.js that satisfies `engines.node` ([#3467](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3467)) +- [Multiple DataSource] Refactor test connection to support SigV4 auth type ([#3456](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3456)) - [Darwin] Add support for Darwin for running OpenSearch snapshots with `yarn opensearch snapshot` ([#3537](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3537)) - [Vis Builder] Add metric to metric, bucket to bucket aggregation persistence ([#3495](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3495)) - Use mirrors to download Node.js binaries to escape sporadic 404 errors ([#3619](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3619)) -- [Multiple DataSource] Refactor dev tool console to use opensearch-js client to send requests ([#3544](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3544)) -- [Data] Add geo shape filter field ([#3605](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3605)) -- [VisBuilder] Add UI actions handler ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) -- [Dashboard] Indicate that IE is no longer supported ([#3641](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3641)) -- [UI] Add support for comma delimiters in the global filter bar ([#3686](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3686)) -- [Multiple DataSource] Allow create and distinguish index pattern with same name but from different datasources ([#3571](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3571)) -- [Multiple DataSource] Integrate multiple datasource with dev tool console ([#3754](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3754)) -- Add satisfaction survey link to help menu ([#3676] (https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3676)) ### 🐛 Bug Fixes @@ -102,7 +92,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Temporary workaround for task-kill exceptions on Windows when it is passed a pid for a process that is already dead ([#2842](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2842)) - [Vis Builder] Fix empty workspace animation does not work in firefox ([#2853](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2853)) - Bumped `del` version to fix MacOS race condition ([#2847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2873)) -- [Chore] Update deprecated url methods (url.parse(), url.format()) ([#1561](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1561)) - [Build] Fixed "Last Access Time" not being set by `scanCopy` on Windows ([#2964](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2964)) - [Vis Builder] Add global data persistence for vis builder #2896 ([#2896](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2896)) - Update `leaflet-vega` and fix its usage ([#3005](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3005)) @@ -116,15 +105,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multi DataSource]Update test connection button text([#3247](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3247)) - [Region Maps] Add ui setting to configure custom vector map's size parameter([#3399](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3399)) - [Search Telemetry] Fixes search telemetry's observable object that won't be GC-ed([#3390](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3390)) -- Clean up and rebuild `@osd/pm` ([#3570](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3570)) -- [Vega] Add Filter custom label for opensearchDashboardsAddFilter ([#3640](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3640)) -- [Timeline] Fix y-axis label color in dark mode ([#3698](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3698)) -- [VisBuilder] Fix multiple warnings thrown on page load ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) -- [VisBuilder] Fix Firefox legend selection issue ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) -- [VisBuilder] Fix type errors ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) -- [Table Visualization] Fix table rendering empty unused space ([#3797](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3797)) -- [Table Visualization] Fix data table not adjusting height on the initial load ([#3816](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3816)) -- Cleanup unused url ([#3847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3847)) ### 🚞 Infrastructure @@ -138,7 +118,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fix detection of Chrome's version on Darwin during CI ([#3296](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3296)) - Upgrade yarn version to be compatible with @openearch-project/opensearch ([#3443](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3443)) - [CI] Reduce redundancy by using matrix strategy on Windows and Linux workflows ([#3514](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3514)) -- Add an achievement badger to the PR ([#3721](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3721)) ### 📝 Documentation @@ -155,15 +134,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Simplify the in-code instructions for upgrading `re2` ([#3328](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3328)) - [Doc] Add docker dev set up instruction ([#3444](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3444)) - [Doc] UI actions explorer ([#3614](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3614)) -- [Doc] Update SECURITY.md with instructions for nested dependencies and backporting ([#3497](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3497)) -- [Doc] [Console] Fix/update documentation links in Dev Tools console ([#3724](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3724)) -- [Doc] Update DEVELOPER_GUIDE.md with added manual bootstrap timeout solution and max virtual memory error solution with docker ([#3764](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3764)) -- [Doc] Add docker files and instructions for debugging Selenium functional tests ([#3747](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3747)) ### 🛠 Maintenance - Adding @zhongnansu as maintainer. ([#2590](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2590)) -- [Timeline] Update default expressions from `.es(*)` to `.opensearch(*)`. ([2720](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2720)) - Removes `minimatch` manual resolution ([#3019](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3019)) - Remove `github-checks-reporter`, an unused dependency ([#3126](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3126)) - Upgrade `vega-lite` dependency to ^5.6.0 ([#3076](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3076)) @@ -173,18 +147,13 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Relax the Node.js requirement to `^14.20.1` ([3463](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3463)) - Bump the version of Node.js installed by `nvm` to `14.21.3` ([3463](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3463)) - Remove the unused `renovate.json5` file ([3489](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3489)) -- Allow selecting the Node.js binary using `NODE_HOME` and `OSD_NODE_HOME` ([3508](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3508)) -- Bump `styled-components` from 5.3.5 to 5.3.9 ([#3678](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3678)) ### 🪛 Refactoring - [MD] Refactor data source error handling ([#2661](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2661)) - Refactor and improve Discover field summaries ([#2391](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2391)) - [Vis Builder] Removed Hard Coded Strings and Used i18n to transalte([#2867](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2867)) -- [Console] Replace jQuery.ajax with core.http when calling OSD APIs in console ([#3080](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3080)) -- [I18n] Fix Listr type errors and error handlers ([#3629](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3629)) -- [Multiple DataSource] Present the authentication type choices in a drop-down ([#3693](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3693)) -- [Console] Replace jQuery usage in console plugin with native methods ([#3733](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3733)) +- [Console] Replace jQuery.ajax with core.http when calling OSD APIs in console ([#3080]https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3080)) ### 🔩 Tests @@ -198,8 +167,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Adds field unit tests ([#3211](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3211)) - [BWC Tests] Add BWC tests for 2.6.0 ([#3356](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3356)) - Prevent primitive linting limitations from being applied to unit tests found under `src/setup_node_env` ([#3403](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3403)) -- [Tests] Fix unit tests for `get_keystore` ([#3854](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3854)) -- [Tests] Use `scripts/use_node` instead of `node` in functional test plugins ([#3783](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3783)) ## [2.x] @@ -231,6 +198,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multi DataSource] UX enhancement on Data source management stack ([#2521](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2521)) - [Multi DataSource] UX enhancement on Update stored password modal for Data source management stack ([#2532](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2532)) - [Monaco editor] Add json worker support ([#3424](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3424)) +- Enhance grouping for context menus ([#3169](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3169)) ### 🐛 Bug Fixes @@ -239,15 +207,12 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixes management app breadcrumb error ([#2344](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2344)) - [BUG] Fix suggestion list cutoff issue ([#2607](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2607)) - [TSVB] Fixes undefined serial diff aggregation documentation link ([#3503](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3503)) -- [Console] Fix dev tool console autocomplete not loading issue ([#3775](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3775)) -- [Console] Fix dev tool console run command with query parameter error ([#3813](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3813)) ### 🚞 Infrastructure - Add path ignore for markdown files for CI ([#2312](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2312)) - Updating WS scans to ignore BWC artifacts in `cypress` ([#2408](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2408)) - [CI] Run functional test repo as workflow ([#2503](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2503)) -- Add downgrade logic for branch in DocLinkService([#3483](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3483)) ### 📝 Documentation @@ -268,8 +233,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🪛 Refactoring -- [Tech Debt] Clean up docs_link_service organization so that strings are in the right categories. ([#3685](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3685)) - ### 🔩 Tests - Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) 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 d38d80d0fac..b01d04c1608 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 @@ -44,6 +44,8 @@ export const ContextMenuExamples: React.FC = () => {

Below examples show how context menu panels look with varying number of actions and how the actions can be grouped into different panels using grouping field. + Grouping can only be one layer deep. A group needs to have at least two items for grouping + to work. A separator is automatically added between groups.

diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts index 0d337077dd9..bb05e93c439 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts @@ -81,7 +81,7 @@ export interface EmbeddableFactory< * Returns a display name for this type of embeddable. Used in "Create new... " options * in the add panel for containers. */ - getDisplayName(): string; + getDisplayName(): JSX.Element | string; /** * If false, this type of embeddable can't be created with the "createNew" functionality. Instead, diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 616ccb65493..e6e78df164d 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -83,6 +83,12 @@ interface Props { SavedObjectFinder: React.ComponentType; stateTransfer?: EmbeddableStateTransfer; hideHeader?: boolean; + // By default, embeddable.destroy() is called when this component unmounts. + // To prevent this default behavior, set this prop to true. + isDestroyPrevented?: boolean; + // Toggle off the border and shadow applied around the embeddable. + // By default, the embeddable will have a shadow and border around it. + isBorderless?: boolean; } interface State { @@ -200,7 +206,10 @@ export class EmbeddablePanel extends React.Component { if (this.state.errorEmbeddable) { this.state.errorEmbeddable.destroy(); } - this.props.embeddable.destroy(); + + if (!this.props.isDestroyPrevented) { + this.props.embeddable.destroy(); + } } public onFocus = (focusedPanelIndex: string) => { @@ -234,6 +243,8 @@ export class EmbeddablePanel extends React.Component { paddingSize="none" role="figure" aria-labelledby={headerId} + hasBorder={!this.props.isBorderless} + hasShadow={!this.props.isBorderless} > {!this.props.hideHeader && ( * Returns a title to be displayed to the user. * @param context */ - getDisplayName(context: ActionExecutionContext): string; + getDisplayName(context: ActionExecutionContext): JSX.Element | string; /** * `UiComponent` to render when displaying this action as a context menu item. diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index fb39a01ed69..5610051dc3f 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -59,7 +59,7 @@ export class ActionInternal return this.definition.getIconType(context); } - public getDisplayName(context: Context): string { + public getDisplayName(context: Context): JSX.Element | string { if (!this.definition.getDisplayName) return `Action: ${this.id}`; return this.definition.getDisplayName(context); } 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 949595d6093..a22a660a032 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 @@ -36,20 +36,28 @@ const createTestAction = ({ type, dispayName, order, + grouping, }: { type?: string; dispayName: string; order?: number; + grouping?: any[]; }) => createAction({ type: type as any, // mapping doesn't matter for this test getDisplayName: () => dispayName, order, execute: async () => {}, + grouping, }); const resultMapper = (panel: EuiContextMenuPanelDescriptor) => ({ - items: panel.items ? panel.items.map((item) => ({ name: item.name })) : [], + items: panel.items + ? panel.items.map((item) => ({ + ...(item.name ? { name: item.name } : {}), + ...(item.isSeparator ? { isSeparator: true } : {}), + })) + : [], }); test('sorts items in DESC order by "order" field first, then by display name', async () => { @@ -248,3 +256,132 @@ test('hides items behind in "More" submenu if there are more than 4 actions', as ] `); }); + +test('tests groups and separators', async () => { + const grouping1 = [ + { + id: 'test-group', + getDisplayName: () => 'Test group', + getIconType: () => 'bell', + }, + ]; + const grouping2 = [ + { + id: 'test-group-2', + getDisplayName: () => 'Test group 2', + getIconType: () => 'bell', + }, + ]; + const grouping3 = [ + { + id: 'test-group-3', + getDisplayName: () => 'Test group 3', + getIconType: () => 'bell', + }, + { + id: 'test-group-4', + getDisplayName: () => 'Test group 4', + getIconType: () => 'bell', + }, + ]; + + const actions = [ + createTestAction({ + dispayName: 'First action', + }), + createTestAction({ + dispayName: 'Foo 1', + grouping: grouping1, + }), + createTestAction({ + dispayName: 'Foo 2', + grouping: grouping1, + }), + createTestAction({ + dispayName: 'Foo 3', + grouping: grouping1, + }), + createTestAction({ + dispayName: 'Bar 1', + grouping: grouping2, + }), + createTestAction({ + dispayName: 'Bar 2', + grouping: grouping2, + }), + createTestAction({ + dispayName: 'Inner', + 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": "First action", + }, + Object { + "isSeparator": true, + }, + Object { + "name": "Test group", + }, + Object { + "isSeparator": true, + }, + Object { + "name": "Test group 2", + }, + Object { + "isSeparator": true, + }, + Object { + "name": "Test group 4", + }, + ], + }, + Object { + "items": Array [ + Object { + "name": "Foo 1", + }, + Object { + "name": "Foo 2", + }, + Object { + "name": "Foo 3", + }, + ], + }, + Object { + "items": Array [ + Object { + "name": "Bar 1", + }, + Object { + "name": "Bar 2", + }, + ], + }, + Object { + "items": Array [ + Object { + "name": "Test group 4", + }, + ], + }, + Object { + "items": Array [ + Object { + "name": "Inner", + }, + ], + }, + ] + `); +}); 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 054900f52b7..6bde3439c97 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 @@ -157,35 +157,51 @@ export async function buildContextMenuForActions({ const context: ActionExecutionContext = { ...item.context, trigger: item.trigger }; const isCompatible = await item.action.isCompatible(context); if (!isCompatible) return; - let parentPanel = ''; - let currentPanel = ''; + + // Reference to the last group...groups are provided in array order of + // parent to children (or outer to inner) + let parentPanelId = ''; + if (action.grouping) { for (let i = 0; i < action.grouping.length; i++) { const group = action.grouping[i]; - currentPanel = group.id; - if (!panels[currentPanel]) { + const groupId = group.id; + + if (!panels[groupId]) { const name = group.getDisplayName ? group.getDisplayName(context) : group.id; - panels[currentPanel] = { - id: currentPanel, + + // Create panel for group + panels[groupId] = { + id: groupId, title: name, items: [], _level: i, _icon: group.getIconType ? group.getIconType(context) : 'empty', }; - if (parentPanel) { - panels[parentPanel].items!.push({ + + // If there are multiple groups and this is not the first group, + // then add an item to the parent panel relating to this group + if (parentPanelId) { + panels[parentPanelId].items!.push({ name, - panel: currentPanel, + panel: groupId, icon: group.getIconType ? group.getIconType(context) : 'empty', _order: group.order || 0, _title: group.getDisplayName ? group.getDisplayName(context) : '', }); } } - parentPanel = currentPanel; + + // Save the current panel, because this will be used for adding items + // to it from later groups in the array + parentPanelId = groupId; } } - panels[parentPanel || 'mainMenu'].items!.push({ + + // If grouping exists, parentPanelId will be the most-inner group, + // otherwise use the mainMenu panel. + // Add item for action to this most-inner panel or mainMenu. + panels[parentPanelId || 'mainMenu'].items!.push({ name: action.MenuItem ? React.createElement(uiToReactComponent(action.MenuItem), { context }) : action.getDisplayName(context), @@ -197,6 +213,7 @@ export async function buildContextMenuForActions({ _title: action.getDisplayName(context), }); }); + await Promise.all(promises); for (const panel of Object.values(panels)) { @@ -211,10 +228,17 @@ export async function buildContextMenuForActions({ wrapMainPanelItemsIntoSubmenu(panels, 'mainMenu'); for (const panel of Object.values(panels)) { + // If the panel is a root-level panel, such as a group-based panel, + // then create mainMenu item for this panel if (panel._level === 0) { - // TODO: Add separator line here once it is available in EUI. - // See https://github.com/elastic/eui/pull/4018 - if (panel.items.length > 3) { + // Add separator with unique key if needed + if (panels.mainMenu.items.length) { + panels.mainMenu.items.push({ isSeparator: true, key: `${panel.id}separator` }); + } + + // If a panel has more than one child, then allow item's to be grouped + // and link to it in the mainMenu. Otherwise, flatten the group. + if (panel.items.length > 1) { panels.mainMenu.items.push({ name: panel.title || panel.id, icon: panel._icon || 'empty', diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 3560f473d33..f0abdb1c870 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -71,6 +71,7 @@ export { ACTION_VISUALIZE_FIELD, ACTION_VISUALIZE_GEO_FIELD, ACTION_VISUALIZE_LENS_FIELD, + DEFAULT_ACTION, } from './types'; export { ActionByType, diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index 428644e1c2c..aebf79cf913 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -60,7 +60,7 @@ export interface Presentable { /** * Returns a title to be displayed to the user. */ - getDisplayName(context: Context): string; + getDisplayName(context: Context): JSX.Element | string; /** * Returns tooltip text which should be displayed when user hovers this object. From 232fc03cef33bd4e13f30d606e348df5bcbca9a2 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Sun, 23 Apr 2023 21:18:06 -0700 Subject: [PATCH 02/11] change log Signed-off-by: David Sinclair --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 351ec10dd7e..facbf0d924d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [CVE-2023-25166] Bump formula to 3.0.1 ([#3416](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3416)) - [CVE-2023-25653] Bump node-jose to 2.2.0 ([#3445](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3445)) - [CVE-2023-26486][cve-2023-26487] Bump vega from 5.22.1 to 5.23.0 ([#3533](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3533)) +- [CVE-2023-0842] Bump xml2js from 0.4.23 to 0.5.0 ([#3842](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3842)) ### 📈 Features/Enhancements @@ -55,6 +56,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [I18n] Register ru, ru-RU locale ([#2817](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2817)) - Add yarn opensearch arg to setup plugin dependencies ([#2544](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2544)) - [Multi DataSource] Test the connection to an external data source when creating or updating ([#2973](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2973)) +- Add Dashboards-list integrations for Plugins ([#3090](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3090) ) - [Table Visualization] Refactor table visualization using React and DataGrid component ([#2863](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2863)) - [Vis Builder] Add redux store persistence ([#3088](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3088)) - [Multi DataSource] Improve test connection ([#3110](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3110)) @@ -62,12 +64,20 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Optimizer] Increase timeout waiting for the exiting of an optimizer worker ([#3193](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3193)) - [Data] Update `createAggConfig` so that newly created configs can be added to beginning of `aggConfig` array ([#3160](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3160)) - Add disablePrototypePoisoningProtection configuration to prevent JS client from erroring when cluster utilizes JS reserved words ([#2992](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2992)) -- [Multiple DataSource] Add support for SigV4 authentication ([#3058](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3058)) -- Make build scripts find and use the latest version of Node.js that satisfies `engines.node` ([#3467](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3467)) -- [Multiple DataSource] Refactor test connection to support SigV4 auth type ([#3456](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3456)) +- [Multiple DataSource] Add support for SigV4 authentication ([#3058](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3058)) +- Make build scripts find and use the latest version of Node.js that satisfies `engines.node` ([#3467](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3467)) +- [Multiple DataSource] Refactor test connection to support SigV4 auth type ([#3456](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3456)) - [Darwin] Add support for Darwin for running OpenSearch snapshots with `yarn opensearch snapshot` ([#3537](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3537)) - [Vis Builder] Add metric to metric, bucket to bucket aggregation persistence ([#3495](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3495)) - Use mirrors to download Node.js binaries to escape sporadic 404 errors ([#3619](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3619)) +- [Multiple DataSource] Refactor dev tool console to use opensearch-js client to send requests ([#3544](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3544)) +- [Data] Add geo shape filter field ([#3605](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3605)) +- [VisBuilder] Add UI actions handler ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) +- [Dashboard] Indicate that IE is no longer supported ([#3641](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3641)) +- [UI] Add support for comma delimiters in the global filter bar ([#3686](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3686)) +- [Multiple DataSource] Allow create and distinguish index pattern with same name but from different datasources ([#3571](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3571)) +- [Multiple DataSource] Integrate multiple datasource with dev tool console ([#3754](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3754)) +- Add satisfaction survey link to help menu ([#3676] (https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3676)) ### 🐛 Bug Fixes @@ -92,6 +102,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Temporary workaround for task-kill exceptions on Windows when it is passed a pid for a process that is already dead ([#2842](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2842)) - [Vis Builder] Fix empty workspace animation does not work in firefox ([#2853](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2853)) - Bumped `del` version to fix MacOS race condition ([#2847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2873)) +- [Chore] Update deprecated url methods (url.parse(), url.format()) ([#1561](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1561)) - [Build] Fixed "Last Access Time" not being set by `scanCopy` on Windows ([#2964](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2964)) - [Vis Builder] Add global data persistence for vis builder #2896 ([#2896](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2896)) - Update `leaflet-vega` and fix its usage ([#3005](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3005)) @@ -105,6 +116,15 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multi DataSource]Update test connection button text([#3247](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3247)) - [Region Maps] Add ui setting to configure custom vector map's size parameter([#3399](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3399)) - [Search Telemetry] Fixes search telemetry's observable object that won't be GC-ed([#3390](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3390)) +- Clean up and rebuild `@osd/pm` ([#3570](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3570)) +- [Vega] Add Filter custom label for opensearchDashboardsAddFilter ([#3640](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3640)) +- [Timeline] Fix y-axis label color in dark mode ([#3698](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3698)) +- [VisBuilder] Fix multiple warnings thrown on page load ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) +- [VisBuilder] Fix Firefox legend selection issue ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) +- [VisBuilder] Fix type errors ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) +- [Table Visualization] Fix table rendering empty unused space ([#3797](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3797)) +- [Table Visualization] Fix data table not adjusting height on the initial load ([#3816](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3816)) +- Cleanup unused url ([#3847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3847)) ### 🚞 Infrastructure @@ -118,6 +138,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fix detection of Chrome's version on Darwin during CI ([#3296](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3296)) - Upgrade yarn version to be compatible with @openearch-project/opensearch ([#3443](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3443)) - [CI] Reduce redundancy by using matrix strategy on Windows and Linux workflows ([#3514](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3514)) +- Add an achievement badger to the PR ([#3721](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3721)) ### 📝 Documentation @@ -134,10 +155,15 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Simplify the in-code instructions for upgrading `re2` ([#3328](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3328)) - [Doc] Add docker dev set up instruction ([#3444](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3444)) - [Doc] UI actions explorer ([#3614](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3614)) +- [Doc] Update SECURITY.md with instructions for nested dependencies and backporting ([#3497](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3497)) +- [Doc] [Console] Fix/update documentation links in Dev Tools console ([#3724](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3724)) +- [Doc] Update DEVELOPER_GUIDE.md with added manual bootstrap timeout solution and max virtual memory error solution with docker ([#3764](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3764)) +- [Doc] Add docker files and instructions for debugging Selenium functional tests ([#3747](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3747)) ### 🛠 Maintenance - Adding @zhongnansu as maintainer. ([#2590](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2590)) +- [Timeline] Update default expressions from `.es(*)` to `.opensearch(*)`. ([2720](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2720)) - Removes `minimatch` manual resolution ([#3019](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3019)) - Remove `github-checks-reporter`, an unused dependency ([#3126](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3126)) - Upgrade `vega-lite` dependency to ^5.6.0 ([#3076](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3076)) @@ -147,13 +173,18 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Relax the Node.js requirement to `^14.20.1` ([3463](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3463)) - Bump the version of Node.js installed by `nvm` to `14.21.3` ([3463](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3463)) - Remove the unused `renovate.json5` file ([3489](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3489)) +- Allow selecting the Node.js binary using `NODE_HOME` and `OSD_NODE_HOME` ([3508](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3508)) +- Bump `styled-components` from 5.3.5 to 5.3.9 ([#3678](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3678)) ### 🪛 Refactoring - [MD] Refactor data source error handling ([#2661](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2661)) - Refactor and improve Discover field summaries ([#2391](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2391)) - [Vis Builder] Removed Hard Coded Strings and Used i18n to transalte([#2867](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2867)) -- [Console] Replace jQuery.ajax with core.http when calling OSD APIs in console ([#3080]https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3080)) +- [Console] Replace jQuery.ajax with core.http when calling OSD APIs in console ([#3080](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3080)) +- [I18n] Fix Listr type errors and error handlers ([#3629](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3629)) +- [Multiple DataSource] Present the authentication type choices in a drop-down ([#3693](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3693)) +- [Console] Replace jQuery usage in console plugin with native methods ([#3733](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3733)) ### 🔩 Tests @@ -167,6 +198,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Adds field unit tests ([#3211](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3211)) - [BWC Tests] Add BWC tests for 2.6.0 ([#3356](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3356)) - Prevent primitive linting limitations from being applied to unit tests found under `src/setup_node_env` ([#3403](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3403)) +- [Tests] Fix unit tests for `get_keystore` ([#3854](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3854)) +- [Tests] Use `scripts/use_node` instead of `node` in functional test plugins ([#3783](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3783)) ## [2.x] @@ -207,12 +240,15 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixes management app breadcrumb error ([#2344](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2344)) - [BUG] Fix suggestion list cutoff issue ([#2607](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2607)) - [TSVB] Fixes undefined serial diff aggregation documentation link ([#3503](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3503)) +- [Console] Fix dev tool console autocomplete not loading issue ([#3775](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3775)) +- [Console] Fix dev tool console run command with query parameter error ([#3813](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3813)) ### 🚞 Infrastructure - Add path ignore for markdown files for CI ([#2312](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2312)) - Updating WS scans to ignore BWC artifacts in `cypress` ([#2408](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2408)) - [CI] Run functional test repo as workflow ([#2503](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2503)) +- Add downgrade logic for branch in DocLinkService([#3483](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3483)) ### 📝 Documentation @@ -233,6 +269,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🪛 Refactoring +- [Tech Debt] Clean up docs_link_service organization so that strings are in the right categories. ([#3685](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3685)) + ### 🔩 Tests - Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) From 867978fd7e607d1ab2051aaf6bd086c8d91c23d2 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Sun, 23 Apr 2023 21:24:38 -0700 Subject: [PATCH 03/11] remove type export Signed-off-by: David Sinclair --- src/plugins/ui_actions/public/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index f0abdb1c870..3560f473d33 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -71,7 +71,6 @@ export { ACTION_VISUALIZE_FIELD, ACTION_VISUALIZE_GEO_FIELD, ACTION_VISUALIZE_LENS_FIELD, - DEFAULT_ACTION, } from './types'; export { ActionByType, From 41daddda3dc4337f0fd804876c80153cc208ef08 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Sun, 23 Apr 2023 21:24:38 -0700 Subject: [PATCH 04/11] revert border and prevent destroy options Signed-off-by: David Sinclair --- .../embeddable/public/lib/panel/embeddable_panel.tsx | 12 ------------ src/plugins/ui_actions/public/index.ts | 1 - 2 files changed, 13 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index e6e78df164d..35ea24155fe 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -83,12 +83,6 @@ interface Props { SavedObjectFinder: React.ComponentType; stateTransfer?: EmbeddableStateTransfer; hideHeader?: boolean; - // By default, embeddable.destroy() is called when this component unmounts. - // To prevent this default behavior, set this prop to true. - isDestroyPrevented?: boolean; - // Toggle off the border and shadow applied around the embeddable. - // By default, the embeddable will have a shadow and border around it. - isBorderless?: boolean; } interface State { @@ -206,10 +200,6 @@ export class EmbeddablePanel extends React.Component { if (this.state.errorEmbeddable) { this.state.errorEmbeddable.destroy(); } - - if (!this.props.isDestroyPrevented) { - this.props.embeddable.destroy(); - } } public onFocus = (focusedPanelIndex: string) => { @@ -243,8 +233,6 @@ export class EmbeddablePanel extends React.Component { paddingSize="none" role="figure" aria-labelledby={headerId} - hasBorder={!this.props.isBorderless} - hasShadow={!this.props.isBorderless} > {!this.props.hideHeader && ( Date: Sun, 21 May 2023 16:40:31 -0700 Subject: [PATCH 05/11] update comments for building panels Signed-off-by: David Sinclair --- .../build_eui_context_menu_panels.tsx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) 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 6bde3439c97..cf2369f97fc 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 @@ -146,6 +146,7 @@ export async function buildContextMenuForActions({ closeMenu = () => {}, }: BuildContextMenuParams): Promise { const panels: Record = { + // This is the first panel which links out to all others via items property mainMenu: { id: 'mainMenu', title, @@ -158,15 +159,16 @@ export async function buildContextMenuForActions({ const isCompatible = await item.action.isCompatible(context); if (!isCompatible) return; - // Reference to the last group...groups are provided in array order of - // parent to children (or outer to inner) - let parentPanelId = ''; + // Reference to the last/parent/upper group. + // Groups are provided in order of parent to children. + let parentGroupId = ''; if (action.grouping) { for (let i = 0; i < action.grouping.length; i++) { const group = action.grouping[i]; const groupId = group.id; + // If a panel does not exist for the current group, then create it if (!panels[groupId]) { const name = group.getDisplayName ? group.getDisplayName(context) : group.id; @@ -180,9 +182,9 @@ export async function buildContextMenuForActions({ }; // If there are multiple groups and this is not the first group, - // then add an item to the parent panel relating to this group - if (parentPanelId) { - panels[parentPanelId].items!.push({ + // then add an item to the parent group relating to this group + if (parentGroupId) { + panels[parentGroupId].items!.push({ name, panel: groupId, icon: group.getIconType ? group.getIconType(context) : 'empty', @@ -192,16 +194,15 @@ export async function buildContextMenuForActions({ } } - // Save the current panel, because this will be used for adding items - // to it from later groups in the array - parentPanelId = groupId; + // Save the current group, because it will be used as the parent group + // for adding items to it for any additional groups in the array + parentGroupId = groupId; } } - // If grouping exists, parentPanelId will be the most-inner group, - // otherwise use the mainMenu panel. - // Add item for action to this most-inner panel or mainMenu. - panels[parentPanelId || 'mainMenu'].items!.push({ + // Add a context menu item for this action so it shows up on a context menu panel. + // We add this within the parent group or default to the mainMenu panel. + panels[parentGroupId || 'mainMenu'].items!.push({ name: action.MenuItem ? React.createElement(uiToReactComponent(action.MenuItem), { context }) : action.getDisplayName(context), @@ -216,6 +217,7 @@ export async function buildContextMenuForActions({ await Promise.all(promises); + // For each panel, sort items by order and title for (const panel of Object.values(panels)) { const items = panel.items.filter(Boolean) as ItemDescriptor[]; panel.items = _.sortBy( @@ -225,10 +227,12 @@ export async function buildContextMenuForActions({ ); } + // On the mainMenu, before adding in items for other groups, the first 4 items are shown. + // Any additional items are hidden behind a "more" item wrapMainPanelItemsIntoSubmenu(panels, 'mainMenu'); for (const panel of Object.values(panels)) { - // If the panel is a root-level panel, such as a group-based panel, + // If the panel is a root-level panel, such as the parent of a group, // then create mainMenu item for this panel if (panel._level === 0) { // Add separator with unique key if needed @@ -236,7 +240,7 @@ export async function buildContextMenuForActions({ panels.mainMenu.items.push({ isSeparator: true, key: `${panel.id}separator` }); } - // If a panel has more than one child, then allow item's to be grouped + // If a panel has more than one child, then allow items to be grouped // and link to it in the mainMenu. Otherwise, flatten the group. if (panel.items.length > 1) { panels.mainMenu.items.push({ From d9bf726ed0b2da1e632545bb39b3cb1db4c209c0 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Sun, 21 May 2023 17:07:39 -0700 Subject: [PATCH 06/11] build panels tests and more comments Signed-off-by: David Sinclair --- .../build_eui_context_menu_panels.test.ts | 155 ++++++++++++------ .../build_eui_context_menu_panels.tsx | 1 + 2 files changed, 110 insertions(+), 46 deletions(-) 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 a22a660a032..b9afca9fb99 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 @@ -257,7 +257,108 @@ test('hides items behind in "More" submenu if there are more than 4 actions', as `); }); -test('tests groups and separators', async () => { +test('flattening of group with only one action', async () => { + const grouping1 = [ + { + id: 'test-group', + getDisplayName: () => 'Test group', + getIconType: () => 'bell', + }, + ]; + const actions = [ + createTestAction({ + dispayName: 'Foo 1', + }), + createTestAction({ + dispayName: 'Bar 1', + grouping: grouping1, + }), + ]; + 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": "Bar 1", + }, + ], + }, + Object { + "items": Array [ + Object { + "name": "Bar 1", + }, + ], + }, + ] + `); +}); + +test('grouping with only two actions', async () => { + const grouping1 = [ + { + id: 'test-group', + getDisplayName: () => 'Test group', + getIconType: () => 'bell', + }, + ]; + const actions = [ + createTestAction({ + dispayName: 'Foo 1', + }), + createTestAction({ + dispayName: 'Bar 1', + grouping: grouping1, + }), + createTestAction({ + dispayName: 'Bar 2', + grouping: grouping1, + }), + ]; + 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 { + "items": Array [ + Object { + "name": "Bar 1", + }, + Object { + "name": "Bar 2", + }, + ], + }, + ] + `); +}); + +test('groups with deep nesting', async () => { const grouping1 = [ { id: 'test-group', @@ -271,48 +372,29 @@ test('tests groups and separators', async () => { getDisplayName: () => 'Test group 2', getIconType: () => 'bell', }, - ]; - const grouping3 = [ { id: 'test-group-3', getDisplayName: () => 'Test group 3', getIconType: () => 'bell', }, - { - id: 'test-group-4', - getDisplayName: () => 'Test group 4', - getIconType: () => 'bell', - }, ]; const actions = [ - createTestAction({ - dispayName: 'First action', - }), createTestAction({ dispayName: 'Foo 1', - grouping: grouping1, }), createTestAction({ - dispayName: 'Foo 2', + dispayName: 'Bar 1', grouping: grouping1, }), createTestAction({ - dispayName: 'Foo 3', + dispayName: 'Bar 2', grouping: grouping1, }), createTestAction({ - dispayName: 'Bar 1', + dispayName: 'Qux 1', grouping: grouping2, }), - createTestAction({ - dispayName: 'Bar 2', - grouping: grouping2, - }), - createTestAction({ - dispayName: 'Inner', - grouping: grouping3, - }), ]; const menu = await buildContextMenuForActions({ actions: actions.map((action) => ({ action, context: {}, trigger: 'TEST' as any })), @@ -323,7 +405,7 @@ test('tests groups and separators', async () => { Object { "items": Array [ Object { - "name": "First action", + "name": "Foo 1", }, Object { "isSeparator": true, @@ -335,26 +417,7 @@ test('tests groups and separators', async () => { "isSeparator": true, }, Object { - "name": "Test group 2", - }, - Object { - "isSeparator": true, - }, - Object { - "name": "Test group 4", - }, - ], - }, - Object { - "items": Array [ - Object { - "name": "Foo 1", - }, - Object { - "name": "Foo 2", - }, - Object { - "name": "Foo 3", + "name": "Test group 3", }, ], }, @@ -371,14 +434,14 @@ test('tests groups and separators', async () => { Object { "items": Array [ Object { - "name": "Test group 4", + "name": "Test group 3", }, ], }, Object { "items": Array [ Object { - "name": "Inner", + "name": "Qux 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 cf2369f97fc..6d69be1f3fa 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 @@ -242,6 +242,7 @@ export async function buildContextMenuForActions({ // If a panel has more than one child, then allow items to be grouped // and link to it in the mainMenu. Otherwise, flatten the group. + // Note: this only happens on the root level panels, not for inner groups. if (panel.items.length > 1) { panels.mainMenu.items.push({ name: panel.title || panel.id, From 8e7003a85672c8b929d3e4d19d97757dc257be0f Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Thu, 25 May 2023 22:24:41 -0700 Subject: [PATCH 07/11] add category option for context menus Signed-off-by: David Sinclair --- .../context_menu_examples.tsx | 7 +- ...anel_group_options_and_context_actions.tsx | 108 +++++++++++++++++ .../build_eui_context_menu_panels.test.ts | 114 ++++++++++++++++++ .../build_eui_context_menu_panels.tsx | 70 ++++++++--- .../ui_actions/public/util/presentable.ts | 8 ++ 5 files changed, 291 insertions(+), 16 deletions(-) create mode 100644 examples/ui_actions_explorer/public/context_menu_examples/panel_group_options_and_context_actions.tsx 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, close: () => void) => ( @@ -125,7 +126,7 @@ const removeItemMetaFields = (items: ItemDescriptor[]): EuiContextMenuPanelItemD const removePanelMetaFields = (panels: PanelDescriptor[]): EuiContextMenuPanelDescriptor[] => { const euiPanels: EuiContextMenuPanelDescriptor[] = []; for (const panel of panels) { - const { _level: omit, _icon: omit2, ...rest } = panel; + const { _level: omit, _icon: omit2, _category: omit3, ...rest } = panel; euiPanels.push({ ...rest, items: removeItemMetaFields(rest.items) }); } return euiPanels; @@ -179,6 +180,7 @@ export async function buildContextMenuForActions({ items: [], _level: i, _icon: group.getIconType ? group.getIconType(context) : 'empty', + _category: group.category, }; // If there are multiple groups and this is not the first group, @@ -231,30 +233,68 @@ export async function buildContextMenuForActions({ // Any additional items are hidden behind a "more" item wrapMainPanelItemsIntoSubmenu(panels, 'mainMenu'); + // This will be used to store items that eventually are placed into the + // mainMenu panel. Specifying a category allows for placing groups into the + // mainMenu so they appear without the separator between them. + const categories = {}; + for (const panel of Object.values(panels)) { // If the panel is a root-level panel, such as the parent of a group, // then create mainMenu item for this panel if (panel._level === 0) { - // Add separator with unique key if needed - if (panels.mainMenu.items.length) { - panels.mainMenu.items.push({ isSeparator: true, key: `${panel.id}separator` }); - } + // If a category is specified, store either a link to the panel or the + // item within. We will deal with it after looping through all panels. + if (panel._category) { + // Create array to store category items + if (!categories[panel._category]) { + categories[panel._category] = []; + } - // If a panel has more than one child, then allow items to be grouped - // and link to it in the mainMenu. Otherwise, flatten the group. - // Note: this only happens on the root level panels, not for inner groups. - if (panel.items.length > 1) { - panels.mainMenu.items.push({ - name: panel.title || panel.id, - icon: panel._icon || 'empty', - panel: panel.id, - }); + // If multiple items in the panel, store a link to this panel into the category. + // Otherwise, just store the single item into the category. + if (panel.items.length > 1) { + categories[panel._category].push({ + name: panel.title || panel.id, + icon: panel._icon || 'empty', + panel: panel.id, + }); + } else { + categories[panel._category].push(...panel.items); + } } else { - panels.mainMenu.items.push(...panel.items); + // Add separator with unique key if needed + if (panels.mainMenu.items.length) { + panels.mainMenu.items.push({ isSeparator: true, key: `${panel.id}separator` }); + } + + // If a panel has more than one child, then allow items to be grouped + // and link to it in the mainMenu. Otherwise, flatten the group. + // Note: this only happens on the root level panels, not for inner groups. + if (panel.items.length > 1) { + panels.mainMenu.items.push({ + name: panel.title || panel.id, + icon: panel._icon || 'empty', + panel: panel.id, + }); + } else { + panels.mainMenu.items.push(...panel.items); + } } } } + // For each category, add a separator before each one and then add category items. + // This is for the mainMenu panel. + Object.keys(categories).forEach((key) => { + // Add separator with unique key if needed + if (panels.mainMenu.items.length) { + panels.mainMenu.items.push({ isSeparator: true, key: `${key}separator` }); + } + + panels.mainMenu.items.push(...categories[key]); + }); + const panelList = Object.values(panels); + return removePanelMetaFields(panelList); } diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index 428644e1c2c..9aaeada8a16 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -94,6 +94,14 @@ export interface PresentableGroup Pick, 'getDisplayName' | 'getDisplayNameTooltip' | 'getIconType' | 'order'> > { id: string; + /** + * This allows groups to be categorized with other groups. Within a UI action + * context menu, this means that an item, which links to a group, will be + * placed in the menu adjacent to similar items that link to groups of the + * same category. + * See PanelGroupOptionsAndContextActions example to learn more. + */ + category?: string; } export type PresentableGrouping = Array>; From 357964ee1b86a6dd781734acb85ac81a0f868f23 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Thu, 25 May 2023 22:42:45 -0700 Subject: [PATCH 08/11] changelog Signed-off-by: David Sinclair --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b8ae297fc3..9b0e92baad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -253,7 +253,9 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Monaco editor] Add json worker support ([#3424](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3424)) - Enhance grouping for context menus ([#3169](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3169)) - Replace re2 with RegExp in timeline and add unit tests ([#3908](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3908)) +- Add category option within groups for context menus ([#4144](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4144)) +https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4144/files ### 🐛 Bug Fixes - [Viz Builder] Fixes time series for new chart types ([#2309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2309)) From fd7f4b3071c43a2863b750c7379f5142af19c87f Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Fri, 26 May 2023 09:23:22 -0700 Subject: [PATCH 09/11] add order to groups Signed-off-by: David Sinclair --- .../build_eui_context_menu_panels.test.ts | 12 ++++--- .../build_eui_context_menu_panels.tsx | 31 +++++++++++++++---- 2 files changed, 32 insertions(+), 11 deletions(-) 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 28fee3ec03f..b6ab4074544 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 @@ -452,9 +452,9 @@ 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 () => { +// 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', @@ -468,6 +468,7 @@ test('groups with categories', async () => { getDisplayName: () => 'Test group 2', getIconType: () => 'bell', category: 'test-category', + order: 10, }, ]; const grouping3 = [ @@ -476,6 +477,7 @@ test('groups with categories', async () => { getDisplayName: () => 'Test group 3', getIconType: () => 'bell', category: 'test-category', + order: 20, }, ]; @@ -525,10 +527,10 @@ test('groups with categories', async () => { "isSeparator": true, }, Object { - "name": "Test group 2", + "name": "Waldo 1", }, Object { - "name": "Waldo 1", + "name": "Test group 2", }, ], }, 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 ccc5c1c9c6e..97910a5815e 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 @@ -65,6 +65,7 @@ type PanelDescriptor = EuiContextMenuPanelDescriptor & { _icon?: string; items: ItemDescriptor[]; _category?: string; + _order?: number; }; const onClick = (action: Action, context: ActionExecutionContext, close: () => void) => ( @@ -126,7 +127,7 @@ const removeItemMetaFields = (items: ItemDescriptor[]): EuiContextMenuPanelItemD const removePanelMetaFields = (panels: PanelDescriptor[]): EuiContextMenuPanelDescriptor[] => { const euiPanels: EuiContextMenuPanelDescriptor[] = []; for (const panel of panels) { - const { _level: omit, _icon: omit2, _category: omit3, ...rest } = panel; + const { _level: omit, _icon: omit2, _category: omit3, _order: omit4, ...rest } = panel; euiPanels.push({ ...rest, items: removeItemMetaFields(rest.items) }); } return euiPanels; @@ -181,6 +182,7 @@ export async function buildContextMenuForActions({ _level: i, _icon: group.getIconType ? group.getIconType(context) : 'empty', _category: group.category, + _order: group.order, }; // If there are multiple groups and this is not the first group, @@ -254,12 +256,20 @@ export async function buildContextMenuForActions({ // Otherwise, just store the single item into the category. if (panel.items.length > 1) { categories[panel._category].push({ - name: panel.title || panel.id, - icon: panel._icon || 'empty', - panel: panel.id, + order: panel._order, + items: [ + { + name: panel.title || panel.id, + icon: panel._icon || 'empty', + panel: panel.id, + }, + ], }); } else { - categories[panel._category].push(...panel.items); + categories[panel._category].push({ + order: panel._order || 0, + items: panel.items, + }); } } else { // Add separator with unique key if needed @@ -286,12 +296,21 @@ export async function buildContextMenuForActions({ // For each category, add a separator before each one and then add category items. // This is for the mainMenu panel. Object.keys(categories).forEach((key) => { + // Get the items sorted by group order, allowing for groups within categories + // to be ordered. A category consists of an order and its items. + // Higher orders are sorted to the top. + const sortedEntries = categories[key].sort((a, b) => b.order - a.order); + const sortedItems = sortedEntries.reduce( + (items, category) => [...items, ...category.items], + [] + ); + // Add separator with unique key if needed if (panels.mainMenu.items.length) { panels.mainMenu.items.push({ isSeparator: true, key: `${key}separator` }); } - panels.mainMenu.items.push(...categories[key]); + panels.mainMenu.items.push(...sortedItems); }); const panelList = Object.values(panels); From aa89276c3fe1553205ae5a25f9f0212778bd7c38 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Thu, 1 Jun 2023 00:18:44 -0700 Subject: [PATCH 10/11] documentation, shorter copyrighty, minor cleanup Signed-off-by: David Sinclair --- ...anel_group_options_and_context_actions.tsx | 27 +----- src/plugins/ui_actions/README.md | 9 ++ .../build_eui_context_menu_panels.test.ts | 4 + .../build_eui_context_menu_panels.tsx | 96 ++++++++++--------- 4 files changed, 65 insertions(+), 71 deletions(-) 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 index 3847046078d..20dc73406c5 100644 --- 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 @@ -1,31 +1,6 @@ /* + * Copyright OpenSearch Contributors * 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'; diff --git a/src/plugins/ui_actions/README.md b/src/plugins/ui_actions/README.md index 28e3b2d63d2..4431a47a06e 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 b6ab4074544..e70561bea22 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 @@ -501,6 +501,10 @@ test('groups with categories and order', async () => { 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, 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 97910a5815e..81710767e0a 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 @@ -241,54 +241,60 @@ export async function buildContextMenuForActions({ const categories = {}; for (const panel of Object.values(panels)) { - // If the panel is a root-level panel, such as the parent of a group, - // then create mainMenu item for this panel - if (panel._level === 0) { - // If a category is specified, store either a link to the panel or the - // item within. We will deal with it after looping through all panels. - if (panel._category) { - // Create array to store category items - if (!categories[panel._category]) { - categories[panel._category] = []; - } + // Do nothing if not root-level panel, such as the parent of a group + if (panel._level !== 0) { + continue; + } - // If multiple items in the panel, store a link to this panel into the category. - // Otherwise, just store the single item into the category. - if (panel.items.length > 1) { - categories[panel._category].push({ - order: panel._order, - items: [ - { - name: panel.title || panel.id, - icon: panel._icon || 'empty', - panel: panel.id, - }, - ], - }); - } else { - categories[panel._category].push({ - order: panel._order || 0, - items: panel.items, - }); - } + // Proceed to create mainMenu item for this panel + + // If a category is specified, store either a link to the panel or the + // item within to that category. We will deal with the category after + // looping through all panels. + if (panel._category) { + // Create array to store category items + if (!categories[panel._category]) { + categories[panel._category] = []; + } + + // If multiple items in the panel, store a link to this panel into the category. + // Otherwise, just store the single item into the category. + if (panel.items.length > 1) { + categories[panel._category].push({ + order: panel._order, + items: [ + { + name: panel.title || panel.id, + icon: panel._icon || 'empty', + panel: panel.id, + }, + ], + }); } else { - // Add separator with unique key if needed - if (panels.mainMenu.items.length) { - panels.mainMenu.items.push({ isSeparator: true, key: `${panel.id}separator` }); - } + categories[panel._category].push({ + order: panel._order || 0, + items: panel.items, + }); + } + } else { + // If no category, continue with adding items to the mainMenu - // If a panel has more than one child, then allow items to be grouped - // and link to it in the mainMenu. Otherwise, flatten the group. - // Note: this only happens on the root level panels, not for inner groups. - if (panel.items.length > 1) { - panels.mainMenu.items.push({ - name: panel.title || panel.id, - icon: panel._icon || 'empty', - panel: panel.id, - }); - } else { - panels.mainMenu.items.push(...panel.items); - } + // Add separator with unique key if needed + if (panels.mainMenu.items.length) { + panels.mainMenu.items.push({ isSeparator: true, key: `${panel.id}separator` }); + } + + // If a panel has more than one child, then allow items to be grouped + // and link to it in the mainMenu. Otherwise, link to the single item. + // Note: this only happens on the root level panels, not for inner groups. + if (panel.items.length > 1) { + panels.mainMenu.items.push({ + name: panel.title || panel.id, + icon: panel._icon || 'empty', + panel: panel.id, + }); + } else { + panels.mainMenu.items.push(...panel.items); } } } From 5da4b35f21cb9557f4abeed365d61a84bb4e2a55 Mon Sep 17 00:00:00 2001 From: David Sinclair Date: Thu, 1 Jun 2023 00:21:55 -0700 Subject: [PATCH 11/11] changelog Signed-off-by: David Sinclair --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0e92baad5..d5bc55ea796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -255,7 +255,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Replace re2 with RegExp in timeline and add unit tests ([#3908](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3908)) - Add category option within groups for context menus ([#4144](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4144)) -https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4144/files ### 🐛 Bug Fixes - [Viz Builder] Fixes time series for new chart types ([#2309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2309))