From 2defbc217ede5972e6164433a7791e3e65d45554 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 10 Sep 2020 14:49:56 +0200 Subject: [PATCH] determenistic action sorting in actions context menu --- .../public/actions/apply_filter_action.ts | 1 + .../public/lib/actions/apply_filter_action.ts | 1 + .../build_eui_context_menu_panels.test.ts | 81 +++++++++++++++++++ .../build_eui_context_menu_panels.tsx | 12 ++- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.test.ts diff --git a/src/plugins/data/public/actions/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts index a2621e6ce8802f..944da72bd11d1d 100644 --- a/src/plugins/data/public/actions/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -44,6 +44,7 @@ export function createFilterAction( return createAction({ type: ACTION_GLOBAL_APPLY_FILTER, id: ACTION_GLOBAL_APPLY_FILTER, + order: 100, getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('data.filter.applyFilterActionTitle', { diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts index 1cdb5af00e7483..3460203aac29c6 100644 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts +++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts @@ -42,6 +42,7 @@ export function createFilterAction(): ActionByType { return createAction({ type: ACTION_APPLY_FILTER, id: ACTION_APPLY_FILTER, + order: 100, getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('embeddableApi.actions.applyFilterActionTitle', { 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 new file mode 100644 index 00000000000000..a513bb3c95f24e --- /dev/null +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.test.ts @@ -0,0 +1,81 @@ +/* + * 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 { buildContextMenuForActions } from './build_eui_context_menu_panels'; +import { Action, createAction } from '../actions'; + +const createTestAction = ({ + type, + dispayName, + order, +}: { + type: string; + dispayName: string; + order: number; +}) => + createAction({ + type: type as any, // mapping doesn't matter for this test + getDisplayName: () => dispayName, + order, + execute: async () => {}, + }); + +test('contextMenu actions sorting: order, type, displayName', async () => { + const actions: Action[] = [ + createTestAction({ + order: 100, + type: '1', + dispayName: 'a', + }), + createTestAction({ + order: 100, + type: '1', + dispayName: 'b', + }), + createTestAction({ + order: 0, + type: '2', + dispayName: 'c', + }), + createTestAction({ + order: 0, + type: '2', + dispayName: 'd', + }), + createTestAction({ + order: 0, + type: '3', + dispayName: 'aa', + }), + ].sort(() => 0.5 - Math.random()); + + const result = await buildContextMenuForActions({ + actions: actions.map((action) => ({ action, context: {}, trigger: '' as any })), + }); + + expect(result.items?.map((item) => item.name as string)).toMatchInlineSnapshot(` + Array [ + "a", + "b", + "c", + "d", + "aa", + ] + `); +}); 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 b44a07273f4a92..3be1ec781cef6f 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 @@ -20,6 +20,7 @@ import * as React from 'react'; import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; import _ from 'lodash'; +import sortBy from 'lodash/sortBy'; import { i18n } from '@kbn/i18n'; import { uiToReactComponent } from '../../../kibana_react/public'; import { Action } from '../actions'; @@ -46,11 +47,11 @@ interface ActionWithContext { export async function buildContextMenuForActions({ actions, title = defaultTitle, - closeMenu, + closeMenu = () => {}, }: { actions: ActionWithContext[]; title?: string; - closeMenu: () => void; + closeMenu?: () => void; }): Promise { const menuItems = await buildEuiContextMenuPanelItems({ actions, @@ -74,6 +75,13 @@ async function buildEuiContextMenuPanelItems({ actions: ActionWithContext[]; closeMenu: () => void; }) { + actions = sortBy( + actions, + (a) => -1 * (a.action.order ?? 0), + (a) => a.action.type, + (a) => a.action.getDisplayName({ ...a.context, trigger: a.trigger }) + ); + const items: EuiContextMenuPanelItemDescriptor[] = new Array(actions.length); const promises = actions.map(async ({ action, context, trigger }, index) => { const isCompatible = await action.isCompatible({