Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance grouping for context menu options #3991

Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e74ab2d
Fix header icon (#3910) (#3915)
opensearch-trigger-bot[bot] Apr 22, 2023
b98934b
enhance grouping for context menu options
sikhote Apr 24, 2023
232fc03
change log
sikhote Apr 24, 2023
867978f
remove type export
sikhote Apr 24, 2023
ef2cb84
Add server side private IP blocking for data source endpoints validat…
kristenTian Apr 24, 2023
a947240
Docs (Jest): Update jest documentation links (#3931)
joshuarrrr Apr 24, 2023
b97a4b5
Revert "[CCI] Replace jquery usage in console plugin with native meth…
joshuarrrr Apr 24, 2023
0e34c3c
[BUG][Dashboard listing] push to history if dashboard otherwise nav (…
kavilla Apr 25, 2023
66aa122
remove jquery console release note for https://github.com/opensearch-…
joshuarrrr Apr 25, 2023
755f16b
[CCI] Update js-yaml to v4.0.5 (#3770)
andreymyssak Apr 25, 2023
ac2ee3a
Update README.md (#3788)
vagimeli Apr 26, 2023
a8ace28
Bump yaml to 2.2.2 (#3947)
manasvinibs Apr 27, 2023
ca0bb8f
Bump `joi` to v14 to avoid the possibility of prototype poisoning in …
AMoo-Miki May 2, 2023
f5a978d
[Doc] Add communication guide (#3837)
joshuarrrr May 3, 2023
5ea0cbe
Temporarily hardcode chromedriver to 112.0.0 to enable all ftr tests …
ananzh May 5, 2023
6e352ff
Fix wording and duplicate code in embeddable example plugin (#3911)
abbyhu2000 May 5, 2023
0188d05
[CI] setup Chrome and utilize binary path (#3997)
kavilla May 11, 2023
41daddd
revert border and prevent destroy options
sikhote Apr 24, 2023
c5ce04d
Merge branch 'enhance-grouping-for-context-menu-options' of github.co…
sikhote May 15, 2023
967a8f1
revert destroy
sikhote May 15, 2023
575bae7
Merge branch 'enhance-grouping-for-context-menu-options' of github.co…
sikhote May 15, 2023
8121c9d
[Dashboards listing] fix listing limit (#4021)
kavilla May 15, 2023
0e25f2e
[CCI] Fix EUI/OUI type errors (#3798)
Nicksqain May 15, 2023
87e7951
Fix bottom bar visibility using create portal (#3336) (#3978)
SergeyMyssak May 16, 2023
69b1854
Adds threshold to code coverage changes for project (#4040)
ashwin-pc May 16, 2023
873b7f3
Updates PR template for screenshots and test instructions (#4042)
ashwin-pc May 16, 2023
2c33d57
Replace re2 with RegExp in timeline and add unit tests (#3908)
ananzh May 16, 2023
b04e657
[Console] [CCI] Remove unused ul element and its custom styling. (#3993)
curq May 17, 2023
c5058a3
Add 1.3.10 release note (#4060) (#4063)
opensearch-trigger-bot[bot] May 18, 2023
e737790
[Multiple Datasource] Support Amazon OpenSearch Serverless (#3957)
zhongnansu May 19, 2023
39f0f1b
revert getDisplayName changes
sikhote May 15, 2023
649b21a
Merge branch 'enhance-grouping-for-context-menu-options' of github.co…
sikhote May 21, 2023
a25cbc4
update comments for building panels
sikhote May 21, 2023
d9bf726
build panels tests and more comments
sikhote May 22, 2023
83d7b5b
Remove Sass from `tile_map` plugin (#4110)
BSFishy May 23, 2023
61ea841
Design for New Saved Object Service Interface for Custom Repository (…
bandinib-amzn May 23, 2023
4c7fece
Merge branch 'main' into enhance-grouping-for-context-menu-options
joshuarrrr May 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const ContextMenuExamples: React.FC = () => {
<p>
Below examples show how context menu panels look with varying number of actions and how the
actions can be grouped into different panels using <EuiCode>grouping</EuiCode> 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.
</p>

<EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 12 additions & 1 deletion src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ interface Props {
SavedObjectFinder: React.ComponentType<any>;
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 {
Expand Down Expand Up @@ -200,7 +206,10 @@ export class EmbeddablePanel extends React.Component<Props, State> {
if (this.state.errorEmbeddable) {
this.state.errorEmbeddable.destroy();
}
this.props.embeddable.destroy();

if (!this.props.isDestroyPrevented) {
this.props.embeddable.destroy();
}
}

public onFocus = (focusedPanelIndex: string) => {
Expand Down Expand Up @@ -234,6 +243,8 @@ export class EmbeddablePanel extends React.Component<Props, State> {
paddingSize="none"
role="figure"
aria-labelledby={headerId}
hasBorder={!this.props.isBorderless}
hasShadow={!this.props.isBorderless}
>
{!this.props.hideHeader && (
<PanelHeader
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui_actions/public/actions/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export interface Action<Context extends BaseContext = {}, T = ActionType>
* Returns a title to be displayed to the user.
* @param context
*/
getDisplayName(context: ActionExecutionContext<Context>): string;
getDisplayName(context: ActionExecutionContext<Context>): JSX.Element | string;

/**
* `UiComponent` to render when displaying this action as a context menu item.
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui_actions/public/actions/action_internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class ActionInternal<A extends ActionDefinition = ActionDefinition>
return this.definition.getIconType(context);
}

public getDisplayName(context: Context<A>): string {
public getDisplayName(context: Context<A>): JSX.Element | string {
if (!this.definition.getDisplayName) return `Action: ${this.id}`;
return this.definition.getDisplayName(context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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",
},
],
},
]
`);
});
Original file line number Diff line number Diff line change
Expand Up @@ -157,35 +157,51 @@ export async function buildContextMenuForActions({
const context: ActionExecutionContext<object> = { ...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),
Expand All @@ -197,6 +213,7 @@ export async function buildContextMenuForActions({
_title: action.getDisplayName(context),
});
});

await Promise.all(promises);

for (const panel of Object.values(panels)) {
Expand All @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui_actions/public/util/presentable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface Presentable<Context extends object = object> {
/**
* 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.
Expand Down