mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Redesign the "Add Panel" Experience (#183764)
## Summary Closes https://github.com/elastic/kibana/issues/144418 This PR introduces changes to the dashboard add panel selection functionality, so that panel selection would now happen from within a flyout, and as such panels are now grouped together logically. With this implementation any panel that is intended to show up within this new flyout is required to have either been registered leveraging the ui action trigger `ADD_PANEL_TRIGGER` and have it's `grouping` value defined or belong to a subset of visualization types (`PROMOTED`, `TOOLS`, and `LEGACY`) that would automatically get grouped. It's worth pointing out that because we can't control the order at which UI actions gets registered, we won't always get the the panel groups in the same order, for this specific reason ~a new optional property (`placementPriority`) has been added in~ the property `order` is now leveraged such that it allows a user registering a UI action define a relative weight for where they'd like their group to show up. All registered actions would be rendered in descending order considering all `order` defined, in the case where no order is defined `0` is assumed for the group. In addition an action which is registered without a group, would automatically get assigned into a default group titled "Other". The search implemented within the add panel is rudimentary, checking if the group titles and group item titles contain the input character; when a group title is matched the entire group is remains highlighted, in the case that the group isn't matched and it's just the group item, only said item is highlighted within it's group. ## Visuals #### Default view <img width="2560" alt="Screenshot 2024-06-10 at 17 44 17" src="90aadf82
-684a-4263-aecd-2843c3eff3c1"> #### Search match view <img width="2560" alt="Screenshot 2024-06-10 at 17 45 11" src="5a766f29
-a3b7-40e3-b1f7-8b423073cd87"> ##### P.S. This changes also includes changes to the display of certain panels; - ML group has a new title i.e. *Machine Learning and Analytics* - In serverless, the observability panels (SLO*) only shows as a selection choice in the observability project type. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) <!-- - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) --> - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) <!-- ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --> --------- Co-authored-by: Catherine Liu <catherine.liu@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e4b1f02153
commit
22e0545d0e
68 changed files with 886 additions and 396 deletions
|
@ -9,7 +9,11 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
IncompatibleActionError,
|
||||
UiActionsStart,
|
||||
ADD_PANEL_TRIGGER,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
|
||||
import { ADD_DATA_TABLE_ACTION_ID, DATA_TABLE_ID } from './constants';
|
||||
|
||||
|
@ -39,5 +43,5 @@ export const registerCreateDataTableAction = (uiActions: UiActionsStart) => {
|
|||
defaultMessage: 'Data table',
|
||||
}),
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_DATA_TABLE_ACTION_ID);
|
||||
uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_DATA_TABLE_ACTION_ID);
|
||||
};
|
||||
|
|
|
@ -10,4 +10,5 @@ export const embeddableExamplesGrouping = {
|
|||
id: 'embeddableExamples',
|
||||
getIconType: () => 'documentation',
|
||||
getDisplayName: () => 'Embeddable examples',
|
||||
order: -10,
|
||||
};
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
IncompatibleActionError,
|
||||
UiActionsStart,
|
||||
ADD_PANEL_TRIGGER,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
|
||||
import { ADD_EUI_MARKDOWN_ACTION_ID, EUI_MARKDOWN_ID } from './constants';
|
||||
import { MarkdownEditorSerializedState } from './types';
|
||||
|
@ -41,7 +45,7 @@ export const registerCreateEuiMarkdownAction = (uiActions: UiActionsStart) => {
|
|||
defaultMessage: 'EUI Markdown',
|
||||
}),
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_EUI_MARKDOWN_ACTION_ID);
|
||||
uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_EUI_MARKDOWN_ACTION_ID);
|
||||
if (uiActions.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
|
||||
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
|
||||
// the create action if the Canvas-specific trigger does indeed exist.
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
|
||||
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
|
||||
import { ADD_FIELD_LIST_ACTION_ID, FIELD_LIST_ID } from './constants';
|
||||
|
@ -34,5 +34,5 @@ export const registerCreateFieldListAction = (uiActions: UiActionsPublicStart) =
|
|||
defaultMessage: 'Field list',
|
||||
}),
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_FIELD_LIST_ACTION_ID);
|
||||
uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_FIELD_LIST_ACTION_ID);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ import { CoreStart } from '@kbn/core/public';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
|
||||
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
|
||||
import {
|
||||
|
@ -67,5 +67,5 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
|
|||
defaultMessage: 'Book',
|
||||
}),
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_SAVED_BOOK_ACTION_ID);
|
||||
uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_SAVED_BOOK_ACTION_ID);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
|
||||
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
IncompatibleActionError,
|
||||
type UiActionsStart,
|
||||
ADD_PANEL_TRIGGER,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
|
||||
import { ADD_SEARCH_ACTION_ID, SEARCH_EMBEDDABLE_ID } from './constants';
|
||||
import { SearchSerializedState } from './types';
|
||||
|
@ -33,7 +37,7 @@ export const registerAddSearchPanelAction = (uiActions: UiActionsStart) => {
|
|||
);
|
||||
},
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_SEARCH_ACTION_ID);
|
||||
uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_SEARCH_ACTION_ID);
|
||||
if (uiActions.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
|
||||
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
|
||||
// the create action if the Canvas-specific trigger does indeed exist.
|
||||
|
|
|
@ -5,16 +5,18 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Trigger } from '@kbn/ui-actions-plugin/public';
|
||||
import { Trigger } from '.';
|
||||
|
||||
export const ADD_PANEL_TRIGGER = 'ADD_PANEL_TRIGGER';
|
||||
|
||||
export const addPanelMenuTrigger: Trigger = {
|
||||
id: ADD_PANEL_TRIGGER,
|
||||
title: i18n.translate('dashboard.addPanelMenuTrigger.title', {
|
||||
title: i18n.translate('uiActions.triggers.dashboard.addPanelMenu.title', {
|
||||
defaultMessage: 'Add panel menu',
|
||||
}),
|
||||
description: i18n.translate('dashboard.addPanelMenuTrigger.description', {
|
||||
description: i18n.translate('uiActions.triggers.dashboard.addPanelMenu.description', {
|
||||
defaultMessage: "A new action will appear to the dashboard's add panel menu",
|
||||
}),
|
||||
};
|
|
@ -11,3 +11,4 @@ export * from './row_click_trigger';
|
|||
export * from './default_trigger';
|
||||
export * from './visualize_field_trigger';
|
||||
export * from './visualize_geo_field_trigger';
|
||||
export * from './dashboard_app_panel_trigger';
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { DashboardStartDependencies } from '../plugin';
|
||||
import { AddToLibraryAction } from './add_to_library_action';
|
||||
import { LegacyAddToLibraryAction } from './legacy_add_to_library_action';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
|
||||
import { getAddPanelActionMenuItems } from './add_panel_action_menu_items';
|
||||
import { getAddPanelActionMenuItemsGroup } from './add_panel_action_menu_items';
|
||||
|
||||
describe('getAddPanelActionMenuItems', () => {
|
||||
it('returns the items correctly', async () => {
|
||||
|
@ -54,39 +54,53 @@ describe('getAddPanelActionMenuItems', () => {
|
|||
],
|
||||
},
|
||||
];
|
||||
const [items, grouped] = getAddPanelActionMenuItems(
|
||||
const grouped = getAddPanelActionMenuItemsGroup(
|
||||
getMockPresentationContainer(),
|
||||
registeredActions,
|
||||
jest.fn()
|
||||
);
|
||||
expect(items).toStrictEqual([
|
||||
{
|
||||
'data-test-subj': 'create-action-Action name',
|
||||
icon: 'pencil',
|
||||
name: 'Action name',
|
||||
onClick: expect.any(Function),
|
||||
toolTipContent: 'Action tooltip',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(grouped).toStrictEqual({
|
||||
groupedAddPanelAction: {
|
||||
id: 'groupedAddPanelAction',
|
||||
title: 'Custom group',
|
||||
icon: 'logoElasticsearch',
|
||||
order: 0,
|
||||
'data-test-subj': 'dashboardEditorMenu-groupedAddPanelActionGroup',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'create-action-Action name 01',
|
||||
icon: 'pencil',
|
||||
id: 'TEST_ACTION_01',
|
||||
name: 'Action name 01',
|
||||
onClick: expect.any(Function),
|
||||
toolTipContent: 'Action tooltip',
|
||||
description: 'Action tooltip',
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'create-action-Action name',
|
||||
icon: 'empty',
|
||||
id: 'TEST_ACTION_02',
|
||||
name: 'Action name',
|
||||
onClick: expect.any(Function),
|
||||
toolTipContent: 'Action tooltip',
|
||||
description: 'Action tooltip',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
other: {
|
||||
id: 'other',
|
||||
title: 'Other',
|
||||
order: -1,
|
||||
'data-test-subj': 'dashboardEditorMenu-otherGroup',
|
||||
items: [
|
||||
{
|
||||
id: 'ACTION_CREATE_ESQL_CHART',
|
||||
name: 'Action name',
|
||||
icon: 'pencil',
|
||||
description: 'Action tooltip',
|
||||
onClick: expect.any(Function),
|
||||
'data-test-subj': 'create-action-Action name',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -94,12 +108,8 @@ describe('getAddPanelActionMenuItems', () => {
|
|||
});
|
||||
|
||||
it('returns empty array if no actions have been registered', async () => {
|
||||
const [items, grouped] = getAddPanelActionMenuItems(
|
||||
getMockPresentationContainer(),
|
||||
[],
|
||||
jest.fn()
|
||||
);
|
||||
expect(items).toStrictEqual([]);
|
||||
const grouped = getAddPanelActionMenuItemsGroup(getMockPresentationContainer(), [], jest.fn());
|
||||
|
||||
expect(grouped).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,13 +5,35 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { ActionExecutionContext, Action } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import {
|
||||
type ActionExecutionContext,
|
||||
type Action,
|
||||
addPanelMenuTrigger,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import type {
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
} from '@elastic/eui';
|
||||
import { addPanelMenuTrigger } from '../../triggers';
|
||||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import type { IconType, CommonProps } from '@elastic/eui';
|
||||
import React, { type MouseEventHandler } from 'react';
|
||||
|
||||
export interface PanelSelectionMenuItem extends Pick<CommonProps, 'data-test-subj'> {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: IconType;
|
||||
onClick: MouseEventHandler;
|
||||
description?: string;
|
||||
isDisabled?: boolean;
|
||||
isDeprecated?: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export type GroupedAddPanelActions = Pick<
|
||||
PanelSelectionMenuItem,
|
||||
'id' | 'isDisabled' | 'data-test-subj' | 'order'
|
||||
> & {
|
||||
title: string;
|
||||
items: PanelSelectionMenuItem[];
|
||||
};
|
||||
|
||||
const onAddPanelActionClick =
|
||||
(action: Action, context: ActionExecutionContext<object>, closePopover: () => void) =>
|
||||
|
@ -30,16 +52,11 @@ const onAddPanelActionClick =
|
|||
} else action.execute(context);
|
||||
};
|
||||
|
||||
export type GroupedAddPanelActions = EuiContextMenuPanelDescriptor & {
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
export const getAddPanelActionMenuItems = (
|
||||
export const getAddPanelActionMenuItemsGroup = (
|
||||
api: PresentationContainer,
|
||||
actions: Array<Action<object>> | undefined,
|
||||
closePopover: () => void
|
||||
): [EuiContextMenuPanelItemDescriptor[], Record<string, GroupedAddPanelActions>] => {
|
||||
const ungrouped: EuiContextMenuPanelItemDescriptor[] = [];
|
||||
) => {
|
||||
const grouped: Record<string, GroupedAddPanelActions> = {};
|
||||
|
||||
const context = {
|
||||
|
@ -47,29 +64,31 @@ export const getAddPanelActionMenuItems = (
|
|||
trigger: addPanelMenuTrigger,
|
||||
};
|
||||
|
||||
const getMenuItem = (item: Action<object>) => {
|
||||
const getMenuItem = (item: Action<object>): PanelSelectionMenuItem => {
|
||||
const actionName = item.getDisplayName(context);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
name: actionName,
|
||||
icon:
|
||||
(typeof item.getIconType === 'function' ? item.getIconType(context) : undefined) ?? 'empty',
|
||||
onClick: onAddPanelActionClick(item, context, closePopover),
|
||||
'data-test-subj': `create-action-${actionName}`,
|
||||
toolTipContent: item?.getDisplayNameTooltip?.(context),
|
||||
description: item?.getDisplayNameTooltip?.(context),
|
||||
order: item.order ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
actions?.forEach((item) => {
|
||||
if (Array.isArray(item.grouping)) {
|
||||
item.grouping.forEach((group) => {
|
||||
if (!grouped[group.id]) {
|
||||
grouped[group.id] = {
|
||||
id: group.id,
|
||||
icon:
|
||||
(typeof group.getIconType === 'function' ? group.getIconType(context) : undefined) ??
|
||||
'empty',
|
||||
title: group.getDisplayName ? group.getDisplayName(context) : undefined,
|
||||
const groupId = group.id;
|
||||
if (!grouped[groupId]) {
|
||||
grouped[groupId] = {
|
||||
id: groupId,
|
||||
title: group.getDisplayName ? group.getDisplayName(context) : '',
|
||||
'data-test-subj': `dashboardEditorMenu-${groupId}Group`,
|
||||
order: group.order ?? 0,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
@ -77,9 +96,22 @@ export const getAddPanelActionMenuItems = (
|
|||
grouped[group.id]!.items!.push(getMenuItem(item));
|
||||
});
|
||||
} else {
|
||||
ungrouped.push(getMenuItem(item));
|
||||
// use other group as the default for definitions that don't have a group
|
||||
const fallbackGroup = COMMON_EMBEDDABLE_GROUPING.other;
|
||||
|
||||
if (!grouped[fallbackGroup.id]) {
|
||||
grouped[fallbackGroup.id] = {
|
||||
id: fallbackGroup.id,
|
||||
title: fallbackGroup.getDisplayName?.({ embeddable: api }) || '',
|
||||
'data-test-subj': `dashboardEditorMenu-${fallbackGroup.id}Group`,
|
||||
order: fallbackGroup.order || 0,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
grouped[fallbackGroup.id].items.push(getMenuItem(item));
|
||||
}
|
||||
});
|
||||
|
||||
return [ungrouped, grouped];
|
||||
return grouped;
|
||||
};
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
@include euiOverflowShadow;
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,9 +34,10 @@ describe('mergeGroupedItemsProvider', () => {
|
|||
|
||||
const factoryGroupMap = {
|
||||
group1: {
|
||||
panelId: 'panel1',
|
||||
id: 'panel1',
|
||||
appName: 'App 1',
|
||||
icon: 'icon1',
|
||||
order: 10,
|
||||
factories: [mockFactory],
|
||||
},
|
||||
} as unknown as Record<string, FactoryGroup>;
|
||||
|
@ -46,29 +47,23 @@ describe('mergeGroupedItemsProvider', () => {
|
|||
id: 'panel2',
|
||||
title: 'Panel 2',
|
||||
icon: 'icon2',
|
||||
order: 10,
|
||||
items: [
|
||||
{
|
||||
id: 'addPanelActionId',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
} as unknown as Record<string, GroupedAddPanelActions>;
|
||||
|
||||
it('should merge factoryGroupMap and groupedAddPanelAction correctly', () => {
|
||||
const [initialPanelGroups, additionalPanels] = mergeGroupedItemsProvider(
|
||||
getEmbeddableFactoryMenuItem
|
||||
)(factoryGroupMap, groupedAddPanelAction);
|
||||
const groupedPanels = mergeGroupedItemsProvider(getEmbeddableFactoryMenuItem)(
|
||||
factoryGroupMap,
|
||||
groupedAddPanelAction
|
||||
);
|
||||
|
||||
expect(initialPanelGroups).toEqual([
|
||||
{
|
||||
'data-test-subj': 'dashboardEditorMenu-group1Group',
|
||||
name: 'App 1',
|
||||
icon: 'icon1',
|
||||
panel: 'panel1',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(additionalPanels).toEqual([
|
||||
expect(groupedPanels).toEqual([
|
||||
{
|
||||
id: 'panel1',
|
||||
title: 'App 1',
|
||||
|
@ -76,72 +71,68 @@ describe('mergeGroupedItemsProvider', () => {
|
|||
{
|
||||
icon: 'icon1',
|
||||
name: 'Factory 1',
|
||||
toolTipContent: 'Factory 1 description',
|
||||
id: 'mockFactory',
|
||||
description: 'Factory 1 description',
|
||||
'data-test-subj': 'createNew-mockFactory',
|
||||
onClick: expect.any(Function),
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
id: 'addPanelActionId',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
'data-test-subj': 'dashboardEditorMenu-group1Group',
|
||||
order: 10,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle missing factoryGroup correctly', () => {
|
||||
const [initialPanelGroups, additionalPanels] = mergeGroupedItemsProvider(
|
||||
getEmbeddableFactoryMenuItem
|
||||
)({}, groupedAddPanelAction);
|
||||
const groupedPanels = mergeGroupedItemsProvider(getEmbeddableFactoryMenuItem)(
|
||||
{},
|
||||
groupedAddPanelAction
|
||||
);
|
||||
|
||||
expect(initialPanelGroups).toEqual([
|
||||
{
|
||||
'data-test-subj': 'dashboardEditorMenu-group1Group',
|
||||
name: 'Panel 2',
|
||||
icon: 'icon2',
|
||||
panel: 'panel2',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(additionalPanels).toEqual([
|
||||
expect(groupedPanels).toEqual([
|
||||
{
|
||||
id: 'panel2',
|
||||
icon: 'icon2',
|
||||
title: 'Panel 2',
|
||||
items: [
|
||||
{
|
||||
id: 'addPanelActionId',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
order: 10,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle missing groupedAddPanelAction correctly', () => {
|
||||
const [initialPanelGroups, additionalPanels] = mergeGroupedItemsProvider(
|
||||
getEmbeddableFactoryMenuItem
|
||||
)(factoryGroupMap, {});
|
||||
const groupedPanels = mergeGroupedItemsProvider(getEmbeddableFactoryMenuItem)(
|
||||
factoryGroupMap,
|
||||
{}
|
||||
);
|
||||
|
||||
expect(initialPanelGroups).toEqual([
|
||||
{
|
||||
'data-test-subj': 'dashboardEditorMenu-group1Group',
|
||||
name: 'App 1',
|
||||
icon: 'icon1',
|
||||
panel: 'panel1',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(additionalPanels).toEqual([
|
||||
expect(groupedPanels).toEqual([
|
||||
{
|
||||
id: 'panel1',
|
||||
title: 'App 1',
|
||||
items: [
|
||||
{
|
||||
icon: 'icon1',
|
||||
id: 'mockFactory',
|
||||
name: 'Factory 1',
|
||||
toolTipContent: 'Factory 1 description',
|
||||
description: 'Factory 1 description',
|
||||
'data-test-subj': 'createNew-mockFactory',
|
||||
onClick: expect.any(Function),
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
order: 10,
|
||||
'data-test-subj': 'dashboardEditorMenu-group1Group',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -8,37 +8,30 @@
|
|||
|
||||
import './editor_menu.scss';
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiContextMenu,
|
||||
EuiContextMenuItemIcon,
|
||||
type EuiContextMenuPanelDescriptor,
|
||||
type EuiContextMenuPanelItemDescriptor,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { type IconType } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar';
|
||||
import { type Action, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { ToolbarButton } from '@kbn/shared-ux-button-toolbar';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { type BaseVisType, VisGroups, type VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { EmbeddableFactory, COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { DASHBOARD_APP_ID } from '../../dashboard_constants';
|
||||
import { ADD_PANEL_TRIGGER } from '../../triggers';
|
||||
import {
|
||||
getAddPanelActionMenuItems,
|
||||
getAddPanelActionMenuItemsGroup,
|
||||
type PanelSelectionMenuItem,
|
||||
type GroupedAddPanelActions,
|
||||
} from './add_panel_action_menu_items';
|
||||
import { openDashboardPanelSelectionFlyout } from './open_dashboard_panel_selection_flyout';
|
||||
import type { DashboardServices } from '../../services/types';
|
||||
import { useDashboardAPI } from '../dashboard_app';
|
||||
|
||||
export interface FactoryGroup {
|
||||
id: string;
|
||||
appName: string;
|
||||
icon: EuiContextMenuItemIcon;
|
||||
panelId: number;
|
||||
icon?: IconType;
|
||||
factories: EmbeddableFactory[];
|
||||
order: number;
|
||||
}
|
||||
|
||||
interface UnwrappedEmbeddableFactory {
|
||||
|
@ -49,31 +42,38 @@ interface UnwrappedEmbeddableFactory {
|
|||
export type GetEmbeddableFactoryMenuItem = ReturnType<typeof getEmbeddableFactoryMenuItemProvider>;
|
||||
|
||||
export const getEmbeddableFactoryMenuItemProvider =
|
||||
(api: PresentationContainer, closePopover: () => void) => (factory: EmbeddableFactory) => {
|
||||
(api: PresentationContainer, closePopover: () => void) =>
|
||||
(factory: EmbeddableFactory): PanelSelectionMenuItem => {
|
||||
const icon = factory?.getIconType ? factory.getIconType() : 'empty';
|
||||
|
||||
const toolTipContent = factory?.getDescription ? factory.getDescription() : undefined;
|
||||
|
||||
return {
|
||||
id: factory.type,
|
||||
name: factory.getDisplayName(),
|
||||
icon,
|
||||
toolTipContent,
|
||||
description: factory.getDescription?.(),
|
||||
onClick: async () => {
|
||||
closePopover();
|
||||
api.addNewPanel({ panelType: factory.type }, true);
|
||||
},
|
||||
'data-test-subj': `createNew-${factory.type}`,
|
||||
order: factory.order ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
const sortGroupPanelsByOrder = <T extends { order: number }>(panelGroups: T[]): T[] => {
|
||||
return panelGroups.sort(
|
||||
// larger number sorted to the top
|
||||
(panelGroupA, panelGroupB) => panelGroupB.order - panelGroupA.order
|
||||
);
|
||||
};
|
||||
|
||||
export const mergeGroupedItemsProvider =
|
||||
(getEmbeddableFactoryMenuItem: GetEmbeddableFactoryMenuItem) =>
|
||||
(
|
||||
factoryGroupMap: Record<string, FactoryGroup>,
|
||||
groupedAddPanelAction: Record<string, GroupedAddPanelActions>
|
||||
): [EuiContextMenuPanelItemDescriptor[], EuiContextMenuPanelDescriptor[]] => {
|
||||
const initialPanelGroups: EuiContextMenuPanelItemDescriptor[] = [];
|
||||
const additionalPanels: EuiContextMenuPanelDescriptor[] = [];
|
||||
) => {
|
||||
const panelGroups: GroupedAddPanelActions[] = [];
|
||||
|
||||
new Set(Object.keys(factoryGroupMap).concat(Object.keys(groupedAddPanelAction))).forEach(
|
||||
(groupId) => {
|
||||
|
@ -83,87 +83,60 @@ export const mergeGroupedItemsProvider =
|
|||
const addPanelGroup = groupedAddPanelAction[groupId];
|
||||
|
||||
if (factoryGroup && addPanelGroup) {
|
||||
const panelId = factoryGroup.panelId;
|
||||
|
||||
initialPanelGroups.push({
|
||||
'data-test-subj': dataTestSubj,
|
||||
name: factoryGroup.appName,
|
||||
icon: factoryGroup.icon,
|
||||
panel: panelId,
|
||||
});
|
||||
|
||||
additionalPanels.push({
|
||||
id: panelId,
|
||||
panelGroups.push({
|
||||
id: factoryGroup.id,
|
||||
title: factoryGroup.appName,
|
||||
'data-test-subj': dataTestSubj,
|
||||
order: factoryGroup.order,
|
||||
items: [
|
||||
...factoryGroup.factories.map(getEmbeddableFactoryMenuItem),
|
||||
...(addPanelGroup?.items ?? []),
|
||||
],
|
||||
});
|
||||
} else if (factoryGroup) {
|
||||
const panelId = factoryGroup.panelId;
|
||||
|
||||
initialPanelGroups.push({
|
||||
'data-test-subj': dataTestSubj,
|
||||
name: factoryGroup.appName,
|
||||
icon: factoryGroup.icon,
|
||||
panel: panelId,
|
||||
});
|
||||
|
||||
additionalPanels.push({
|
||||
id: panelId,
|
||||
panelGroups.push({
|
||||
id: factoryGroup.id,
|
||||
title: factoryGroup.appName,
|
||||
'data-test-subj': dataTestSubj,
|
||||
order: factoryGroup.order,
|
||||
items: factoryGroup.factories.map(getEmbeddableFactoryMenuItem),
|
||||
});
|
||||
} else if (addPanelGroup) {
|
||||
const panelId = addPanelGroup.id;
|
||||
|
||||
initialPanelGroups.push({
|
||||
'data-test-subj': dataTestSubj,
|
||||
name: addPanelGroup.title,
|
||||
icon: addPanelGroup.icon,
|
||||
panel: panelId,
|
||||
});
|
||||
|
||||
additionalPanels.push({
|
||||
id: panelId,
|
||||
title: addPanelGroup.title,
|
||||
items: addPanelGroup.items,
|
||||
});
|
||||
panelGroups.push(addPanelGroup);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return [initialPanelGroups, additionalPanels];
|
||||
return panelGroups;
|
||||
};
|
||||
|
||||
export const EditorMenu = ({
|
||||
createNewVisType,
|
||||
isDisabled,
|
||||
api,
|
||||
}: {
|
||||
interface EditorMenuProps {
|
||||
api: PresentationContainer;
|
||||
isDisabled?: boolean;
|
||||
/** Handler for creating new visualization of a specified type */
|
||||
createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void;
|
||||
}) => {
|
||||
}
|
||||
|
||||
export const EditorMenu = ({ createNewVisType, isDisabled, api }: EditorMenuProps) => {
|
||||
const isMounted = useRef(false);
|
||||
const flyoutRef = useRef<ReturnType<DashboardServices['overlays']['openFlyout']>>();
|
||||
const dashboard = useDashboardAPI();
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
flyoutRef.current?.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const {
|
||||
embeddable,
|
||||
visualizations: {
|
||||
getAliases: getVisTypeAliases,
|
||||
getByGroup: getVisTypesByGroup,
|
||||
showNewVisModal,
|
||||
},
|
||||
visualizations: { getAliases: getVisTypeAliases, getByGroup: getVisTypesByGroup },
|
||||
uiActions,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const embeddableFactories = useMemo(
|
||||
() => Array.from(embeddable.getEmbeddableFactories()),
|
||||
[embeddable]
|
||||
);
|
||||
const [unwrappedEmbeddableFactories, setUnwrappedEmbeddableFactories] = useState<
|
||||
UnwrappedEmbeddableFactory[]
|
||||
>([]);
|
||||
|
@ -172,6 +145,11 @@ export const EditorMenu = ({
|
|||
undefined
|
||||
);
|
||||
|
||||
const embeddableFactories = useMemo(
|
||||
() => Array.from(embeddable.getEmbeddableFactories()),
|
||||
[embeddable]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all(
|
||||
embeddableFactories.map<Promise<UnwrappedEmbeddableFactory>>(async (factory) => ({
|
||||
|
@ -183,17 +161,6 @@ export const EditorMenu = ({
|
|||
});
|
||||
}, [embeddableFactories]);
|
||||
|
||||
const createNewAggsBasedVis = useCallback(
|
||||
(visType?: BaseVisType) => () =>
|
||||
showNewVisModal({
|
||||
originatingApp: DASHBOARD_APP_ID,
|
||||
outsideVisualizeApp: true,
|
||||
showAggsSelection: true,
|
||||
selectedVisType: visType,
|
||||
}),
|
||||
[showNewVisModal]
|
||||
);
|
||||
|
||||
const getSortedVisTypesByGroup = (group: VisGroups) =>
|
||||
getVisTypesByGroup(group)
|
||||
.sort((a: BaseVisType | VisTypeAlias, b: BaseVisType | VisTypeAlias) => {
|
||||
|
@ -210,8 +177,9 @@ export const EditorMenu = ({
|
|||
.filter(({ disableCreate }: BaseVisType) => !disableCreate);
|
||||
|
||||
const promotedVisTypes = getSortedVisTypesByGroup(VisGroups.PROMOTED);
|
||||
const aggsBasedVisTypes = getSortedVisTypesByGroup(VisGroups.AGGBASED);
|
||||
const toolVisTypes = getSortedVisTypesByGroup(VisGroups.TOOLS);
|
||||
const legacyVisTypes = getSortedVisTypesByGroup(VisGroups.LEGACY);
|
||||
|
||||
const visTypeAliases = getVisTypeAliases()
|
||||
.sort(({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) =>
|
||||
a === b ? 0 : a ? -1 : 1
|
||||
|
@ -224,18 +192,6 @@ export const EditorMenu = ({
|
|||
);
|
||||
|
||||
const factoryGroupMap: Record<string, FactoryGroup> = {};
|
||||
const ungroupedFactories: EmbeddableFactory[] = [];
|
||||
const aggBasedPanelID = 1;
|
||||
|
||||
let panelCount = 1 + aggBasedPanelID;
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Retrieve ADD_PANEL_TRIGGER actions
|
||||
useEffect(() => {
|
||||
|
@ -243,6 +199,7 @@ export const EditorMenu = ({
|
|||
const registeredActions = await uiActions?.getTriggerCompatibleActions?.(ADD_PANEL_TRIGGER, {
|
||||
embeddable: api,
|
||||
});
|
||||
|
||||
if (isMounted.current) {
|
||||
setAddPanelActions(registeredActions);
|
||||
}
|
||||
|
@ -260,142 +217,160 @@ export const EditorMenu = ({
|
|||
} else {
|
||||
factoryGroupMap[group.id] = {
|
||||
id: group.id,
|
||||
appName: group.getDisplayName ? group.getDisplayName({ embeddable }) : group.id,
|
||||
icon: (group.getIconType
|
||||
? group.getIconType({ embeddable })
|
||||
: 'empty') as EuiContextMenuItemIcon,
|
||||
appName: group.getDisplayName
|
||||
? group.getDisplayName({ embeddable: dashboard })
|
||||
: group.id,
|
||||
icon: group.getIconType?.({ embeddable: dashboard }),
|
||||
factories: [factory],
|
||||
panelId: panelCount,
|
||||
order: group.order ?? 0,
|
||||
};
|
||||
|
||||
panelCount++;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ungroupedFactories.push(factory);
|
||||
const fallbackGroup = COMMON_EMBEDDABLE_GROUPING.other;
|
||||
|
||||
if (!factoryGroupMap[fallbackGroup.id]) {
|
||||
factoryGroupMap[fallbackGroup.id] = {
|
||||
id: fallbackGroup.id,
|
||||
appName: fallbackGroup.getDisplayName
|
||||
? fallbackGroup.getDisplayName({ embeddable: dashboard })
|
||||
: fallbackGroup.id,
|
||||
icon: fallbackGroup.getIconType?.({ embeddable: dashboard }) || 'empty',
|
||||
factories: [],
|
||||
order: fallbackGroup.order ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
factoryGroupMap[fallbackGroup.id].factories.push(factory);
|
||||
}
|
||||
});
|
||||
|
||||
const getVisTypeMenuItem = (visType: BaseVisType): EuiContextMenuPanelItemDescriptor => {
|
||||
const augmentedCreateNewVisType = (
|
||||
visType: Parameters<EditorMenuProps['createNewVisType']>[0],
|
||||
cb: () => void
|
||||
) => {
|
||||
const visClickHandler = createNewVisType(visType);
|
||||
return () => {
|
||||
visClickHandler();
|
||||
cb();
|
||||
};
|
||||
};
|
||||
|
||||
const getVisTypeMenuItem = (
|
||||
onClickCb: () => void,
|
||||
visType: BaseVisType
|
||||
): PanelSelectionMenuItem => {
|
||||
const {
|
||||
name,
|
||||
title,
|
||||
titleInWizard,
|
||||
description,
|
||||
icon = 'empty',
|
||||
group,
|
||||
isDeprecated,
|
||||
order,
|
||||
} = visType;
|
||||
return {
|
||||
name: !isDeprecated ? (
|
||||
titleInWizard || title
|
||||
) : (
|
||||
<EuiFlexGroup wrap responsive={false} gutterSize="s">
|
||||
<EuiFlexItem grow={false}>{titleInWizard || title}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="warning">
|
||||
{i18n.translate('dashboard.editorMenu.deprecatedTag', {
|
||||
defaultMessage: 'Deprecated',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
icon: icon as string,
|
||||
onClick:
|
||||
// not all the agg-based visualizations need to be created via the wizard
|
||||
group === VisGroups.AGGBASED && visType.options.showIndexSelection
|
||||
? createNewAggsBasedVis(visType)
|
||||
: createNewVisType(visType),
|
||||
id: name,
|
||||
name: titleInWizard || title,
|
||||
isDeprecated,
|
||||
icon,
|
||||
onClick: augmentedCreateNewVisType(visType, onClickCb),
|
||||
'data-test-subj': `visType-${name}`,
|
||||
toolTipContent: description,
|
||||
description,
|
||||
order,
|
||||
};
|
||||
};
|
||||
|
||||
const getVisTypeAliasMenuItem = (
|
||||
onClickCb: () => void,
|
||||
visTypeAlias: VisTypeAlias
|
||||
): EuiContextMenuPanelItemDescriptor => {
|
||||
const { name, title, description, icon = 'empty' } = visTypeAlias;
|
||||
): PanelSelectionMenuItem => {
|
||||
const { name, title, description, icon = 'empty', order } = visTypeAlias;
|
||||
|
||||
return {
|
||||
id: name,
|
||||
name: title,
|
||||
icon,
|
||||
onClick: createNewVisType(visTypeAlias),
|
||||
onClick: augmentedCreateNewVisType(visTypeAlias, onClickCb),
|
||||
'data-test-subj': `visType-${name}`,
|
||||
toolTipContent: description,
|
||||
description,
|
||||
order: order ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
const aggsPanelTitle = i18n.translate('dashboard.editorMenu.aggBasedGroupTitle', {
|
||||
defaultMessage: 'Aggregation based',
|
||||
});
|
||||
const getEditorMenuPanels = (closeFlyout: () => void): GroupedAddPanelActions[] => {
|
||||
const getEmbeddableFactoryMenuItem = getEmbeddableFactoryMenuItemProvider(api, closeFlyout);
|
||||
|
||||
const getEditorMenuPanels = (closePopover: () => void): EuiContextMenuPanelDescriptor[] => {
|
||||
const getEmbeddableFactoryMenuItem = getEmbeddableFactoryMenuItemProvider(api, closePopover);
|
||||
|
||||
const [ungroupedAddPanelActions, groupedAddPanelAction] = getAddPanelActionMenuItems(
|
||||
const groupedAddPanelAction = getAddPanelActionMenuItemsGroup(
|
||||
api,
|
||||
addPanelActions,
|
||||
closePopover
|
||||
closeFlyout
|
||||
);
|
||||
|
||||
const [initialPanelGroups, additionalPanels] = mergeGroupedItemsProvider(
|
||||
getEmbeddableFactoryMenuItem
|
||||
)(factoryGroupMap, groupedAddPanelAction);
|
||||
const initialPanelGroups = mergeGroupedItemsProvider(getEmbeddableFactoryMenuItem)(
|
||||
factoryGroupMap,
|
||||
groupedAddPanelAction
|
||||
);
|
||||
|
||||
const initialPanelItems = [
|
||||
...visTypeAliases.map(getVisTypeAliasMenuItem),
|
||||
...ungroupedAddPanelActions,
|
||||
...toolVisTypes.map(getVisTypeMenuItem),
|
||||
...ungroupedFactories.map(getEmbeddableFactoryMenuItem),
|
||||
...initialPanelGroups,
|
||||
...promotedVisTypes.map(getVisTypeMenuItem),
|
||||
];
|
||||
if (aggsBasedVisTypes.length > 0) {
|
||||
initialPanelItems.push({
|
||||
name: aggsPanelTitle,
|
||||
icon: 'visualizeApp',
|
||||
panel: aggBasedPanelID,
|
||||
'data-test-subj': `dashboardEditorAggBasedMenuItem`,
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: 0,
|
||||
items: initialPanelItems,
|
||||
},
|
||||
{
|
||||
id: aggBasedPanelID,
|
||||
title: aggsPanelTitle,
|
||||
items: aggsBasedVisTypes.map(getVisTypeMenuItem),
|
||||
},
|
||||
...additionalPanels,
|
||||
];
|
||||
// enhance panel groups
|
||||
return sortGroupPanelsByOrder<GroupedAddPanelActions>(initialPanelGroups).map((panelGroup) => {
|
||||
switch (panelGroup.id) {
|
||||
case 'visualizations': {
|
||||
return {
|
||||
...panelGroup,
|
||||
items: sortGroupPanelsByOrder<PanelSelectionMenuItem>(
|
||||
(panelGroup.items ?? []).concat(
|
||||
// TODO: actually add grouping to vis type alias so we wouldn't randomly display an unintended item
|
||||
visTypeAliases.map(getVisTypeAliasMenuItem.bind(null, closeFlyout)),
|
||||
promotedVisTypes.map(getVisTypeMenuItem.bind(null, closeFlyout))
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
case COMMON_EMBEDDABLE_GROUPING.legacy.id: {
|
||||
return {
|
||||
...panelGroup,
|
||||
items: sortGroupPanelsByOrder<PanelSelectionMenuItem>(
|
||||
(panelGroup.items ?? []).concat(
|
||||
legacyVisTypes.map(getVisTypeMenuItem.bind(null, closeFlyout))
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
case COMMON_EMBEDDABLE_GROUPING.annotation.id: {
|
||||
return {
|
||||
...panelGroup,
|
||||
items: sortGroupPanelsByOrder<PanelSelectionMenuItem>(
|
||||
(panelGroup.items ?? []).concat(
|
||||
toolVisTypes.map(getVisTypeMenuItem.bind(null, closeFlyout))
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return {
|
||||
...panelGroup,
|
||||
items: sortGroupPanelsByOrder(panelGroup.items),
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolbarPopover
|
||||
zIndex={Number(euiTheme.levels.header) - 1}
|
||||
repositionOnScroll
|
||||
ownFocus
|
||||
<ToolbarButton
|
||||
data-test-subj="dashboardEditorMenuButton"
|
||||
isDisabled={isDisabled}
|
||||
iconType="plusInCircle"
|
||||
label={i18n.translate('dashboard.solutionToolbar.editorMenuButtonLabel', {
|
||||
defaultMessage: 'Add panel',
|
||||
})}
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => {
|
||||
flyoutRef.current = openDashboardPanelSelectionFlyout({
|
||||
getPanels: getEditorMenuPanels,
|
||||
});
|
||||
}}
|
||||
size="s"
|
||||
iconType="plusInCircle"
|
||||
panelPaddingSize="none"
|
||||
data-test-subj="dashboardEditorMenuButton"
|
||||
>
|
||||
{({ closePopover }: { closePopover: () => void }) => (
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={getEditorMenuPanels(closePopover)}
|
||||
className={`dshSolutionToolbar__editorContextMenu`}
|
||||
data-test-subj="dashboardEditorContextMenu"
|
||||
/>
|
||||
)}
|
||||
</ToolbarPopover>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { i18n as i18nFn } from '@kbn/i18n';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiForm,
|
||||
EuiBadge,
|
||||
EuiFormRow,
|
||||
EuiTitle,
|
||||
EuiFieldSearch,
|
||||
useEuiTheme,
|
||||
type EuiFlyoutProps,
|
||||
EuiListGroup,
|
||||
EuiListGroupItem,
|
||||
EuiToolTip,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import type { DashboardServices } from '../../services/types';
|
||||
import type { GroupedAddPanelActions, PanelSelectionMenuItem } from './add_panel_action_menu_items';
|
||||
|
||||
interface OpenDashboardPanelSelectionFlyoutArgs {
|
||||
getPanels: (closePopover: () => void) => GroupedAddPanelActions[];
|
||||
flyoutPanelPaddingSize?: Exclude<EuiFlyoutProps['paddingSize'], 'none'>;
|
||||
}
|
||||
|
||||
interface Props extends Pick<OpenDashboardPanelSelectionFlyoutArgs, 'getPanels'> {
|
||||
/** Handler to close flyout */
|
||||
close: () => void;
|
||||
/** Padding for flyout */
|
||||
paddingSize: Exclude<OpenDashboardPanelSelectionFlyoutArgs['flyoutPanelPaddingSize'], undefined>;
|
||||
}
|
||||
|
||||
export function openDashboardPanelSelectionFlyout({
|
||||
getPanels,
|
||||
flyoutPanelPaddingSize = 'l',
|
||||
}: OpenDashboardPanelSelectionFlyoutArgs) {
|
||||
const {
|
||||
overlays,
|
||||
analytics,
|
||||
settings: { i18n, theme },
|
||||
} = pluginServices.getServices();
|
||||
// eslint-disable-next-line prefer-const
|
||||
let flyoutRef: ReturnType<DashboardServices['overlays']['openFlyout']>;
|
||||
|
||||
const mount = toMountPoint(
|
||||
React.createElement(function () {
|
||||
const closeFlyout = () => flyoutRef.close();
|
||||
return (
|
||||
<DashboardPanelSelectionListFlyout
|
||||
close={closeFlyout}
|
||||
{...{ paddingSize: flyoutPanelPaddingSize, getPanels }}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
{ analytics, theme, i18n }
|
||||
);
|
||||
|
||||
flyoutRef = overlays.openFlyout(mount, {
|
||||
size: 'm',
|
||||
maxWidth: 500,
|
||||
paddingSize: flyoutPanelPaddingSize,
|
||||
'aria-labelledby': 'addPanelsFlyout',
|
||||
'data-test-subj': 'dashboardPanelSelectionFlyout',
|
||||
});
|
||||
|
||||
return flyoutRef;
|
||||
}
|
||||
|
||||
export const DashboardPanelSelectionListFlyout: React.FC<Props> = ({
|
||||
close,
|
||||
getPanels,
|
||||
paddingSize,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const panels = useRef(getPanels(close));
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
const [panelsSearchResult, setPanelsSearchResult] = useState<GroupedAddPanelActions[]>(
|
||||
panels.current
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchTerm) {
|
||||
return setPanelsSearchResult(panels.current);
|
||||
}
|
||||
|
||||
const q = searchTerm.toLowerCase();
|
||||
|
||||
setPanelsSearchResult(
|
||||
orderBy(
|
||||
panels.current.map((panel) => {
|
||||
const groupSearchMatch = panel.title.toLowerCase().includes(q);
|
||||
|
||||
const [groupSearchMatchAgg, items] = panel.items.reduce(
|
||||
(acc, cur) => {
|
||||
const searchMatch = cur.name.toLowerCase().includes(q);
|
||||
|
||||
acc[0] = acc[0] || searchMatch;
|
||||
acc[1].push({
|
||||
...cur,
|
||||
isDisabled: !(groupSearchMatch || searchMatch),
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
[groupSearchMatch, [] as PanelSelectionMenuItem[]]
|
||||
);
|
||||
|
||||
return {
|
||||
...panel,
|
||||
isDisabled: !groupSearchMatchAgg,
|
||||
items,
|
||||
};
|
||||
}),
|
||||
['isDisabled']
|
||||
)
|
||||
);
|
||||
}, [searchTerm]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h1 id="addPanelsFlyout">
|
||||
<FormattedMessage
|
||||
id="dashboard.solutionToolbar.addPanelFlyout.headingText"
|
||||
defaultMessage="Add panel"
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiFlexGroup direction="column" responsive={false} gutterSize="m">
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={{
|
||||
position: 'sticky',
|
||||
top: euiTheme.size[paddingSize],
|
||||
zIndex: 1,
|
||||
boxShadow: `0 -${euiTheme.size[paddingSize]} 0 4px ${euiTheme.colors.emptyShade}`,
|
||||
}}
|
||||
>
|
||||
<EuiForm component="form" fullWidth>
|
||||
<EuiFormRow css={{ backgroundColor: euiTheme.colors.emptyShade }}>
|
||||
<EuiFieldSearch
|
||||
autoFocus
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
}}
|
||||
aria-label={i18nFn.translate(
|
||||
'dashboard.editorMenu.addPanelFlyout.searchLabelText',
|
||||
{ defaultMessage: 'search field for panels' }
|
||||
)}
|
||||
className="nsPanelSelectionFlyout__searchInput"
|
||||
data-test-subj="dashboardPanelSelectionFlyout__searchInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{panelsSearchResult.some(({ isDisabled }) => !isDisabled) ? (
|
||||
panelsSearchResult.map(
|
||||
({ id, title, items, isDisabled, ['data-test-subj']: dataTestSubj }) =>
|
||||
!isDisabled ? (
|
||||
<EuiFlexItem key={id} data-test-subj={dataTestSubj}>
|
||||
<EuiTitle id={`${id}-group`} size="xxs">
|
||||
{typeof title === 'string' ? <h3>{title}</h3> : title}
|
||||
</EuiTitle>
|
||||
<EuiListGroup
|
||||
aria-labelledby={`${id}-group`}
|
||||
size="s"
|
||||
gutterSize="none"
|
||||
maxWidth={false}
|
||||
flush
|
||||
>
|
||||
{items?.map((item, idx) => {
|
||||
return (
|
||||
<EuiListGroupItem
|
||||
key={`${id}.${idx}`}
|
||||
label={
|
||||
<EuiToolTip position="right" content={item.description}>
|
||||
{!item.isDeprecated ? (
|
||||
<EuiText size="s">{item.name}</EuiText>
|
||||
) : (
|
||||
<EuiFlexGroup wrap responsive={false} gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{item.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="warning">
|
||||
<FormattedMessage
|
||||
id="dashboard.editorMenu.deprecatedTag"
|
||||
defaultMessage="Deprecated"
|
||||
/>
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
}
|
||||
onClick={item?.onClick}
|
||||
iconType={item.icon}
|
||||
data-test-subj={item['data-test-subj']}
|
||||
isDisabled={item.isDisabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EuiListGroup>
|
||||
</EuiFlexItem>
|
||||
) : null
|
||||
)
|
||||
) : (
|
||||
<EuiText size="s" textAlign="center">
|
||||
<FormattedMessage
|
||||
id="dashboard.solutionToolbar.addPanelFlyout.noResultsDescription"
|
||||
defaultMessage="No panel types found"
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={close}>
|
||||
<FormattedMessage
|
||||
id="dashboard.solutionToolbar.addPanelFlyout.cancelButtonText"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -30,6 +30,7 @@ import type {
|
|||
UsageCollectionStart,
|
||||
} from '@kbn/usage-collection-plugin/public';
|
||||
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
|
||||
import { type UiActionsSetup, type UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
||||
|
@ -39,7 +40,6 @@ import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
|||
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public';
|
||||
import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
|
@ -70,7 +70,6 @@ import {
|
|||
import { DashboardMountContextProps } from './dashboard_app/types';
|
||||
import type { FindDashboardsService } from './services/dashboard_content_management/types';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import { addPanelMenuTrigger } from './triggers';
|
||||
import { GetPanelPlacementSettings } from './dashboard_container/panel_placement';
|
||||
|
||||
export interface DashboardFeatureFlagConfig {
|
||||
|
@ -167,10 +166,6 @@ export class DashboardPlugin
|
|||
this.dashboardFeatureFlagConfig =
|
||||
this.initializerContext.config.get<DashboardFeatureFlagConfig>();
|
||||
|
||||
// this trigger enables external consumers to register actions for
|
||||
// adding items to the add panel menu
|
||||
uiActions.registerTrigger(addPanelMenuTrigger);
|
||||
|
||||
core.analytics.registerEventType({
|
||||
eventType: 'dashboard_loaded_with_data',
|
||||
schema: {},
|
||||
|
|
|
@ -108,3 +108,5 @@ export {
|
|||
embeddableInputToSubject,
|
||||
embeddableOutputToSubject,
|
||||
} from './lib/embeddables/compatibility/embeddable_compatibility_utils';
|
||||
|
||||
export { COMMON_EMBEDDABLE_GROUPING } from './lib/embeddables/common/constants';
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { UiActionsPresentableGroup } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
export const COMMON_EMBEDDABLE_GROUPING: { [key: string]: UiActionsPresentableGroup<unknown> } = {
|
||||
legacy: {
|
||||
id: 'legacy',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('embeddableApi.common.constants.grouping.legacy', {
|
||||
defaultMessage: 'Legacy',
|
||||
}),
|
||||
order: -2,
|
||||
},
|
||||
annotation: {
|
||||
id: 'annotation-and-navigation',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('embeddableApi.common.constants.grouping.annotations', {
|
||||
defaultMessage: 'Annotations and Navigation',
|
||||
}),
|
||||
},
|
||||
other: {
|
||||
id: 'other',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('embeddableApi.common.constants.grouping.other', {
|
||||
defaultMessage: 'Other',
|
||||
}),
|
||||
getIconType: () => 'empty',
|
||||
order: -1,
|
||||
},
|
||||
};
|
|
@ -148,4 +148,6 @@ export interface EmbeddableFactory<
|
|||
initialInput: TEmbeddableInput,
|
||||
parent?: IContainer
|
||||
): Promise<TEmbeddable | ErrorEmbeddable | undefined>;
|
||||
|
||||
order?: number;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { CanAddNewPanel } from '@kbn/presentation-containers';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
ADD_IMAGE_EMBEDDABLE_ACTION_ID,
|
||||
IMAGE_EMBEDDABLE_TYPE,
|
||||
|
@ -27,6 +28,7 @@ export const registerCreateImageAction = () => {
|
|||
uiActionsService.registerAction<EmbeddableApiContext>({
|
||||
id: ADD_IMAGE_EMBEDDABLE_ACTION_ID,
|
||||
getIconType: () => 'image',
|
||||
order: 20,
|
||||
isCompatible: async ({ embeddable: parentApi }) => {
|
||||
return Boolean(await parentApiIsCompatible(parentApi));
|
||||
},
|
||||
|
@ -45,13 +47,14 @@ export const registerCreateImageAction = () => {
|
|||
// swallow the rejection, since this just means the user closed without saving
|
||||
}
|
||||
},
|
||||
grouping: [COMMON_EMBEDDABLE_GROUPING.annotation],
|
||||
getDisplayName: () =>
|
||||
i18n.translate('imageEmbeddable.imageEmbeddableFactory.displayName', {
|
||||
defaultMessage: 'Image',
|
||||
}),
|
||||
});
|
||||
|
||||
uiActionsService.attachAction('ADD_PANEL_TRIGGER', ADD_IMAGE_EMBEDDABLE_ACTION_ID);
|
||||
uiActionsService.attachAction(ADD_PANEL_TRIGGER, ADD_IMAGE_EMBEDDABLE_ACTION_ID);
|
||||
if (uiActionsService.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
|
||||
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
|
||||
// the create action if the Canvas-specific trigger does indeed exist.
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
Embeddable,
|
||||
ReferenceOrValueEmbeddable,
|
||||
SavedObjectEmbeddableInput,
|
||||
COMMON_EMBEDDABLE_GROUPING,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { CONTENT_ID } from '../../common';
|
||||
|
@ -44,6 +45,8 @@ export class LinksEmbeddable
|
|||
public attributes?: LinksAttributes;
|
||||
public attributes$ = new Subject<LinksAttributes>();
|
||||
|
||||
public grouping = [COMMON_EMBEDDABLE_GROUPING.annotation];
|
||||
|
||||
constructor(
|
||||
config: LinksConfig,
|
||||
initialInput: LinksInput,
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
EmbeddableFactory,
|
||||
EmbeddableFactoryDefinition,
|
||||
ErrorEmbeddable,
|
||||
COMMON_EMBEDDABLE_GROUPING,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
GetMigrationFunctionObjectFn,
|
||||
|
@ -55,7 +56,8 @@ export class LinksFactoryDefinition
|
|||
| ((state: EmbeddableStateWithType, stats: Record<string, any>) => Record<string, any>)
|
||||
| undefined;
|
||||
migrations?: MigrateFunctionsObject | GetMigrationFunctionObjectFn | undefined;
|
||||
grouping?: UiActionsPresentableGrouping<unknown> | undefined;
|
||||
grouping: UiActionsPresentableGrouping<unknown> = [COMMON_EMBEDDABLE_GROUPING.annotation];
|
||||
|
||||
public readonly type = CONTENT_ID;
|
||||
|
||||
public readonly isContainerType = false;
|
||||
|
|
|
@ -24,6 +24,7 @@ export { ActionInternal, createAction, IncompatibleActionError } from './actions
|
|||
export { buildContextMenuForActions } from './context_menu';
|
||||
export type {
|
||||
Presentable as UiActionsPresentable,
|
||||
PresentableGroup as UiActionsPresentableGroup,
|
||||
PresentableGrouping as UiActionsPresentableGrouping,
|
||||
} from '@kbn/ui-actions-browser/src/types';
|
||||
export type { Trigger, RowClickContext } from '@kbn/ui-actions-browser/src/triggers';
|
||||
|
@ -34,6 +35,8 @@ export {
|
|||
visualizeGeoFieldTrigger,
|
||||
ROW_CLICK_TRIGGER,
|
||||
rowClickTrigger,
|
||||
ADD_PANEL_TRIGGER,
|
||||
addPanelMenuTrigger,
|
||||
} from '@kbn/ui-actions-browser/src/triggers';
|
||||
export type { VisualizeFieldContext } from './types';
|
||||
export {
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
rowClickTrigger,
|
||||
visualizeFieldTrigger,
|
||||
visualizeGeoFieldTrigger,
|
||||
addPanelMenuTrigger,
|
||||
} from '@kbn/ui-actions-browser/src/triggers';
|
||||
import { UiActionsService } from './service';
|
||||
import { setAnalytics, setI18n, setTheme } from './services';
|
||||
|
@ -48,6 +49,7 @@ export class UiActionsPlugin
|
|||
constructor(_initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(_core: CoreSetup): UiActionsPublicSetup {
|
||||
this.service.registerTrigger(addPanelMenuTrigger);
|
||||
this.service.registerTrigger(rowClickTrigger);
|
||||
this.service.registerTrigger(visualizeFieldTrigger);
|
||||
this.service.registerTrigger(visualizeGeoFieldTrigger);
|
||||
|
|
|
@ -27,6 +27,7 @@ export const markdownVisDefinition: VisTypeDefinition<MarkdownVisParams> = {
|
|||
description: i18n.translate('visTypeMarkdown.markdownDescription', {
|
||||
defaultMessage: 'Add text and images to your dashboard.',
|
||||
}),
|
||||
order: 30,
|
||||
toExpressionAst,
|
||||
visConfig: {
|
||||
defaults: {
|
||||
|
|
|
@ -104,7 +104,8 @@ export const metricsVisDefinition: VisTypeDefinition<
|
|||
defaultMessage: 'Perform advanced analysis of your time series data.',
|
||||
}),
|
||||
icon: 'visVisualBuilder',
|
||||
group: VisGroups.PROMOTED,
|
||||
group: VisGroups.LEGACY,
|
||||
order: 10,
|
||||
visConfig: {
|
||||
defaults: {
|
||||
id: () => uuidv4(),
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
apiHasAppContext,
|
||||
EmbeddableApiContext,
|
||||
HasType,
|
||||
HasAppContext,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { apiHasType } from '@kbn/presentation-publishing';
|
||||
import { apiCanAddNewPanel, CanAddNewPanel } from '@kbn/presentation-containers';
|
||||
import { showNewVisModal } from '../wizard/show_new_vis';
|
||||
|
||||
const ADD_AGG_VIS_ACTION_ID = 'ADD_AGG_VIS';
|
||||
|
||||
type AddAggVisualizationPanelActionApi = HasType & CanAddNewPanel & HasAppContext;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is AddAggVisualizationPanelActionApi => {
|
||||
return apiHasType(api) && apiCanAddNewPanel(api) && apiHasAppContext(api);
|
||||
};
|
||||
|
||||
export class AddAggVisualizationPanelAction implements Action<EmbeddableApiContext> {
|
||||
public readonly type = ADD_AGG_VIS_ACTION_ID;
|
||||
public readonly id = ADD_AGG_VIS_ACTION_ID;
|
||||
public readonly grouping = [COMMON_EMBEDDABLE_GROUPING.legacy];
|
||||
|
||||
public readonly order = 20;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public getIconType() {
|
||||
return 'visualizeApp';
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('visualizations.uiAction.addAggVis.displayName', {
|
||||
defaultMessage: 'Aggregation based',
|
||||
});
|
||||
}
|
||||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
return isApiCompatible(embeddable);
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext): Promise<void> {
|
||||
if (!isApiCompatible(embeddable)) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
showNewVisModal({
|
||||
originatingApp: embeddable.getAppContext().currentAppId,
|
||||
outsideVisualizeApp: true,
|
||||
showAggsSelection: true,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,3 +7,14 @@
|
|||
*/
|
||||
|
||||
export { VISUALIZE_EMBEDDABLE_TYPE } from '../../common/constants';
|
||||
|
||||
export const COMMON_VISUALIZATION_GROUPING = [
|
||||
{
|
||||
id: 'visualizations',
|
||||
getDisplayName: () => 'Visualizations',
|
||||
getIconType: () => {
|
||||
return 'visGauge';
|
||||
},
|
||||
order: 1000,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
export { VisualizeEmbeddableFactory } from './visualize_embeddable_factory';
|
||||
export { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
|
||||
export { VISUALIZE_EMBEDDABLE_TYPE, COMMON_VISUALIZATION_GROUPING } from './constants';
|
||||
export { VIS_EVENT_TO_TRIGGER } from './events';
|
||||
export { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object';
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ export {
|
|||
apiHasVisualizeConfig,
|
||||
VISUALIZE_EMBEDDABLE_TYPE,
|
||||
VIS_EVENT_TO_TRIGGER,
|
||||
COMMON_VISUALIZATION_GROUPING,
|
||||
} from './embeddable';
|
||||
export { VisualizationContainer } from './components';
|
||||
export { getVisSchemas } from './vis_schemas';
|
||||
|
|
|
@ -36,7 +36,7 @@ import type {
|
|||
ApplicationStart,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/public';
|
||||
import type { UiActionsStart, UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import { UiActionsStart, UiActionsSetup, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import type { SavedObjectsStart } from '@kbn/saved-objects-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type {
|
||||
|
@ -47,7 +47,11 @@ import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
|||
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
EmbeddableSetup,
|
||||
EmbeddableStart,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import type { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public';
|
||||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
|
@ -122,6 +126,7 @@ import {
|
|||
} from '../common/content_management';
|
||||
import { SerializedVisData } from '../common';
|
||||
import { VisualizeByValueInput } from './embeddable/visualize_embeddable';
|
||||
import { AddAggVisualizationPanelAction } from './actions/add_agg_vis_action';
|
||||
|
||||
/**
|
||||
* Interface for this plugin's returned setup/start contracts.
|
||||
|
@ -394,7 +399,9 @@ export class VisualizationsPlugin
|
|||
uiActions.registerTrigger(visualizeEditorTrigger);
|
||||
uiActions.registerTrigger(dashboardVisualizationPanelTrigger);
|
||||
const editInLensAction = new EditInLensAction(data.query.timefilter.timefilter);
|
||||
uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', editInLensAction);
|
||||
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, editInLensAction);
|
||||
const addAggVisAction = new AddAggVisualizationPanelAction();
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addAggVisAction);
|
||||
const embeddableFactory = new VisualizeEmbeddableFactory({ start });
|
||||
embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ const defaultOptions: VisTypeOptions = {
|
|||
export class BaseVisType<TVisParams extends VisParams = VisParams> {
|
||||
public readonly name;
|
||||
public readonly title;
|
||||
public readonly order;
|
||||
public readonly description;
|
||||
public readonly note;
|
||||
public readonly getSupportedTriggers;
|
||||
|
@ -67,6 +68,7 @@ export class BaseVisType<TVisParams extends VisParams = VisParams> {
|
|||
this.title = opts.title;
|
||||
this.icon = opts.icon;
|
||||
this.image = opts.image;
|
||||
this.order = opts.order ?? 0;
|
||||
this.suppressWarnings = opts.suppressWarnings;
|
||||
this.visConfig = defaultsDeep({}, opts.visConfig, { defaults: {} });
|
||||
this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} });
|
||||
|
|
|
@ -217,4 +217,6 @@ export interface VisTypeDefinition<TVisParams extends VisParams> {
|
|||
* have incosistencies in legacy visLib visualizations
|
||||
*/
|
||||
readonly visConfig: Record<string, any>;
|
||||
|
||||
readonly order?: number;
|
||||
}
|
||||
|
|
|
@ -10,4 +10,5 @@ export enum VisGroups {
|
|||
PROMOTED = 'promoted',
|
||||
TOOLS = 'tools',
|
||||
AGGBASED = 'aggbased',
|
||||
LEGACY = 'legacy',
|
||||
}
|
||||
|
|
|
@ -117,6 +117,7 @@ export interface VisTypeAlias {
|
|||
visualizations: VisualizationsAppExtension;
|
||||
[appName: string]: unknown;
|
||||
};
|
||||
order?: number;
|
||||
}
|
||||
|
||||
let registry: VisTypeAlias[] = [];
|
||||
|
|
|
@ -35,6 +35,7 @@ interface VisTypeListEntry {
|
|||
}
|
||||
|
||||
interface AggBasedSelectionProps {
|
||||
openedAsRoot?: boolean;
|
||||
onVisTypeSelected: (visType: BaseVisType) => void;
|
||||
visTypesRegistry: TypesStart;
|
||||
toggleGroups: (flag: boolean) => void;
|
||||
|
@ -58,13 +59,15 @@ class AggBasedSelection extends React.Component<AggBasedSelectionProps, AggBased
|
|||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
<FormattedMessage
|
||||
id="visualizations.newVisWizard.title"
|
||||
defaultMessage="New visualization"
|
||||
id="visualizations.newAggVisWizard.title"
|
||||
defaultMessage="New aggregation based visualization"
|
||||
/>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<DialogNavigation goBack={() => this.props.toggleGroups(true)} />
|
||||
{this.props.openedAsRoot ? null : (
|
||||
<DialogNavigation goBack={() => this.props.toggleGroups(true)} />
|
||||
)}
|
||||
<EuiFieldSearch
|
||||
placeholder="Filter"
|
||||
value={query}
|
||||
|
|
|
@ -57,6 +57,8 @@ function GroupSelection(props: GroupSelectionProps) {
|
|||
[
|
||||
...props.visTypesRegistry.getAliases(),
|
||||
...props.visTypesRegistry.getByGroup(VisGroups.PROMOTED),
|
||||
// Include so TSVB still gets displayed
|
||||
...props.visTypesRegistry.getByGroup(VisGroups.LEGACY),
|
||||
].filter((visDefinition) => {
|
||||
return !visDefinition.disableCreate;
|
||||
}),
|
||||
|
@ -65,6 +67,7 @@ function GroupSelection(props: GroupSelectionProps) {
|
|||
),
|
||||
[props.visTypesRegistry]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiModalHeader>
|
||||
|
|
|
@ -106,6 +106,7 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
visTypesRegistry={this.props.visTypesRegistry}
|
||||
docLinks={this.props.docLinks}
|
||||
toggleGroups={(flag: boolean) => this.setState({ showGroups: flag })}
|
||||
openedAsRoot={this.props.showAggsSelection && !this.props.selectedVisType}
|
||||
/>
|
||||
</EuiModal>
|
||||
);
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { EuiPortal, EuiProgress } from '@elastic/eui';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import {
|
||||
getHttp,
|
||||
getTypes,
|
||||
|
@ -50,47 +49,54 @@ export function showNewVisModal({
|
|||
selectedVisType,
|
||||
}: ShowNewVisModalParams = {}) {
|
||||
const container = document.createElement('div');
|
||||
|
||||
let isClosed = false;
|
||||
|
||||
// initialize variable that will hold reference for unmount
|
||||
// eslint-disable-next-line prefer-const
|
||||
let unmount: ReturnType<ReturnType<typeof toMountPoint>>;
|
||||
|
||||
const handleClose = () => {
|
||||
if (isClosed) return;
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
document.body.removeChild(container);
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
|
||||
onClose?.();
|
||||
unmount?.();
|
||||
isClosed = true;
|
||||
};
|
||||
|
||||
document.body.appendChild(container);
|
||||
const element = (
|
||||
<KibanaRenderContextProvider analytics={getAnalytics()} i18n={getI18n()} theme={getTheme()}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<EuiPortal>
|
||||
<EuiProgress size="xs" position="fixed" />
|
||||
</EuiPortal>
|
||||
}
|
||||
>
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={handleClose}
|
||||
originatingApp={originatingApp}
|
||||
stateTransfer={getEmbeddable().getStateTransfer()}
|
||||
outsideVisualizeApp={outsideVisualizeApp}
|
||||
editorParams={editorParams}
|
||||
visTypesRegistry={getTypes()}
|
||||
contentClient={getContentManagement().client}
|
||||
uiSettings={getUISettings()}
|
||||
addBasePath={getHttp().basePath.prepend}
|
||||
application={getApplication()}
|
||||
docLinks={getDocLinks()}
|
||||
showAggsSelection={showAggsSelection}
|
||||
selectedVisType={selectedVisType}
|
||||
/>
|
||||
</Suspense>
|
||||
</KibanaRenderContextProvider>
|
||||
const mount = toMountPoint(
|
||||
React.createElement(function () {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<EuiPortal>
|
||||
<EuiProgress size="xs" position="fixed" />
|
||||
</EuiPortal>
|
||||
}
|
||||
>
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={handleClose}
|
||||
originatingApp={originatingApp}
|
||||
stateTransfer={getEmbeddable().getStateTransfer()}
|
||||
outsideVisualizeApp={outsideVisualizeApp}
|
||||
editorParams={editorParams}
|
||||
visTypesRegistry={getTypes()}
|
||||
contentClient={getContentManagement().client}
|
||||
uiSettings={getUISettings()}
|
||||
addBasePath={getHttp().basePath.prepend}
|
||||
application={getApplication()}
|
||||
docLinks={getDocLinks()}
|
||||
showAggsSelection={showAggsSelection}
|
||||
selectedVisType={selectedVisType}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}),
|
||||
{ analytics: getAnalytics(), i18n: getI18n(), theme: getTheme() }
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
unmount = mount(container);
|
||||
|
||||
return () => handleClose();
|
||||
}
|
||||
|
|
|
@ -72,7 +72,8 @@
|
|||
"@kbn/presentation-publishing",
|
||||
"@kbn/shared-ux-markdown",
|
||||
"@kbn/react-kibana-context-render",
|
||||
"@kbn/react-kibana-mount"
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/presentation-containers"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should open editor menu when editor button is clicked', async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await testSubjects.existOrFail('dashboardEditorContextMenu');
|
||||
await testSubjects.existOrFail('dashboardPanelSelectionFlyout');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,16 +52,16 @@ export class DashboardAddPanelService extends FtrService {
|
|||
async clickEditorMenuButton() {
|
||||
this.log.debug('DashboardAddPanel.clickEditorMenuButton');
|
||||
await this.testSubjects.click('dashboardEditorMenuButton');
|
||||
await this.testSubjects.existOrFail('dashboardEditorContextMenu');
|
||||
await this.testSubjects.existOrFail('dashboardPanelSelectionFlyout');
|
||||
}
|
||||
|
||||
async expectEditorMenuClosed() {
|
||||
await this.testSubjects.missingOrFail('dashboardEditorContextMenu');
|
||||
await this.testSubjects.missingOrFail('dashboardPanelSelectionFlyout');
|
||||
}
|
||||
|
||||
async clickAggBasedVisualizations() {
|
||||
this.log.debug('DashboardAddPanel.clickEditorMenuAggBasedMenuItem');
|
||||
await this.testSubjects.click('dashboardEditorAggBasedMenuItem');
|
||||
await this.clickAddNewPanelFromUIActionLink('Aggregation based');
|
||||
}
|
||||
|
||||
async clickVisType(visType: string) {
|
||||
|
@ -69,9 +69,9 @@ export class DashboardAddPanelService extends FtrService {
|
|||
await this.testSubjects.click(`visType-${visType}`);
|
||||
}
|
||||
|
||||
async clickEmbeddableFactoryGroupButton(groupId: string) {
|
||||
this.log.debug('DashboardAddPanel.clickEmbeddableFactoryGroupButton');
|
||||
await this.testSubjects.click(`dashboardEditorMenu-${groupId}Group`);
|
||||
async verifyEmbeddableFactoryGroupExists(groupId: string) {
|
||||
this.log.debug('DashboardAddPanel.verifyEmbeddableFactoryGroupExists');
|
||||
await this.testSubjects.existOrFail(`dashboardEditorMenu-${groupId}Group`);
|
||||
}
|
||||
|
||||
async clickAddNewEmbeddableLink(type: string) {
|
||||
|
|
|
@ -34,11 +34,13 @@ export function createAddChangePointChartAction(
|
|||
id: 'ml',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.aiops.navMenu.mlAppNameText', {
|
||||
defaultMessage: 'Machine Learning',
|
||||
defaultMessage: 'Machine Learning and Analytics',
|
||||
}),
|
||||
getIconType: () => 'machineLearningApp',
|
||||
},
|
||||
],
|
||||
order: 10,
|
||||
getIconType: () => 'machineLearningApp',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.aiops.embeddableChangePointChartDisplayName', {
|
||||
defaultMessage: 'Change point detection',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import { type UiActionsSetup, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
categorizeFieldTrigger,
|
||||
|
@ -26,7 +26,7 @@ export function registerAiopsUiActions(
|
|||
const openChangePointInMlAppAction = createOpenChangePointInMlAppAction(coreStart, pluginStart);
|
||||
const addChangePointChartAction = createAddChangePointChartAction(coreStart, pluginStart);
|
||||
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addChangePointChartAction);
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addChangePointChartAction);
|
||||
|
||||
uiActions.registerTrigger(categorizeFieldTrigger);
|
||||
|
||||
|
|
|
@ -158,14 +158,14 @@ export const EditorMenu: FC<Props> = ({
|
|||
items: [
|
||||
...visTypeAliases.map(getVisTypeAliasMenuItem),
|
||||
...getAddPanelActionMenuItems(closePopover),
|
||||
...ungroupedFactories.map(getEmbeddableFactoryMenuItem),
|
||||
...promotedVisTypes.map(getVisTypeMenuItem),
|
||||
...Object.values(factoryGroupMap).map(({ id, appName, icon, panelId }) => ({
|
||||
name: appName,
|
||||
icon,
|
||||
panel: panelId,
|
||||
'data-test-subj': `canvasEditorMenu-${id}Group`,
|
||||
})),
|
||||
...ungroupedFactories.map(getEmbeddableFactoryMenuItem),
|
||||
...promotedVisTypes.map(getVisTypeMenuItem),
|
||||
],
|
||||
},
|
||||
...Object.values(factoryGroupMap).map(
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { BaseVisType, VisGroups, VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import {
|
||||
VisGroups,
|
||||
type BaseVisType,
|
||||
type VisTypeAlias,
|
||||
type VisParams,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import {
|
||||
EmbeddableFactory,
|
||||
EmbeddableFactoryDefinition,
|
||||
|
@ -201,13 +206,17 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
|
|||
.map(({ factory }) => factory);
|
||||
|
||||
const promotedVisTypes = getVisTypesByGroup(VisGroups.PROMOTED);
|
||||
const legacyVisTypes = getVisTypesByGroup(VisGroups.LEGACY);
|
||||
|
||||
return (
|
||||
<Component
|
||||
createNewVisType={createNewVisType}
|
||||
createNewEmbeddableFromFactory={createNewEmbeddableFromFactory}
|
||||
createNewEmbeddableFromAction={createNewEmbeddableFromAction}
|
||||
promotedVisTypes={promotedVisTypes}
|
||||
promotedVisTypes={([] as Array<BaseVisType<VisParams>>).concat(
|
||||
promotedVisTypes,
|
||||
legacyVisTypes
|
||||
)}
|
||||
factories={factories}
|
||||
addPanelActions={addPanelActions}
|
||||
visTypeAliases={visTypeAliases}
|
||||
|
|
|
@ -46,6 +46,7 @@ import {
|
|||
ACTION_VISUALIZE_FIELD,
|
||||
VISUALIZE_FIELD_TRIGGER,
|
||||
VisualizeFieldContext,
|
||||
ADD_PANEL_TRIGGER,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
VISUALIZE_EDITOR_TRIGGER,
|
||||
|
@ -648,7 +649,7 @@ export class LensPlugin {
|
|||
|
||||
// Displays the add ESQL panel in the dashboard add Panel menu
|
||||
const createESQLPanelAction = new CreateESQLPanelAction(startDependencies, core);
|
||||
startDependencies.uiActions.addTriggerAction('ADD_PANEL_TRIGGER', createESQLPanelAction);
|
||||
startDependencies.uiActions.addTriggerAction(ADD_PANEL_TRIGGER, createESQLPanelAction);
|
||||
|
||||
const discoverLocator = startDependencies.share?.url.locators.get('DISCOVER_APP_LOCATOR');
|
||||
if (discoverLocator) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { CoreStart } from '@kbn/core/public';
|
|||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import { COMMON_VISUALIZATION_GROUPING } from '@kbn/visualizations-plugin/public';
|
||||
import type { LensPluginStartDependencies } from '../../plugin';
|
||||
|
||||
const ACTION_CREATE_ESQL_CHART = 'ACTION_CREATE_ESQL_CHART';
|
||||
|
@ -20,6 +21,8 @@ export class CreateESQLPanelAction implements Action<EmbeddableApiContext> {
|
|||
public id = ACTION_CREATE_ESQL_CHART;
|
||||
public order = 50;
|
||||
|
||||
public grouping = COMMON_VISUALIZATION_GROUPING;
|
||||
|
||||
constructor(
|
||||
protected readonly startDependencies: LensPluginStartDependencies,
|
||||
protected readonly core: CoreStart
|
||||
|
|
|
@ -27,6 +27,7 @@ export const getLensAliasConfig = (): VisTypeAlias => ({
|
|||
note: i18n.translate('xpack.lens.visTypeAlias.note', {
|
||||
defaultMessage: 'Recommended for most users.',
|
||||
}),
|
||||
order: 60,
|
||||
icon: 'lensApp',
|
||||
stage: 'production',
|
||||
appExtensions: {
|
||||
|
|
|
@ -33,6 +33,7 @@ export function getMapsVisTypeAlias() {
|
|||
description: appDescription,
|
||||
icon: APP_ICON,
|
||||
stage: 'production' as VisualizationStage,
|
||||
order: 40,
|
||||
appExtensions: {
|
||||
visualizations: {
|
||||
docTypes: [MAP_SAVED_OBJECT_TYPE],
|
||||
|
|
|
@ -11,7 +11,7 @@ export const PLUGIN_ID = 'ml';
|
|||
export const PLUGIN_ICON = 'machineLearningApp';
|
||||
export const PLUGIN_ICON_SOLUTION = 'logoKibana';
|
||||
export const ML_APP_NAME = i18n.translate('xpack.ml.navMenu.mlAppNameText', {
|
||||
defaultMessage: 'Machine Learning',
|
||||
defaultMessage: 'Machine Learning and Analytics',
|
||||
});
|
||||
export const ML_APP_ROUTE = '/app/ml';
|
||||
export const ML_INTERNAL_BASE_PATH = '/internal/ml';
|
||||
|
|
|
@ -41,6 +41,10 @@ export function createAddAnomalyChartsPanelAction(
|
|||
getIconType: () => PLUGIN_ICON,
|
||||
},
|
||||
],
|
||||
order: 30,
|
||||
getIconType(): string {
|
||||
return 'visLine';
|
||||
},
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.ml.components.mlAnomalyExplorerEmbeddable.displayName', {
|
||||
defaultMessage: 'Anomaly chart',
|
||||
|
|
|
@ -42,6 +42,8 @@ export function createAddSingleMetricViewerPanelAction(
|
|||
getIconType: () => PLUGIN_ICON,
|
||||
},
|
||||
],
|
||||
order: 20,
|
||||
getIconType: () => 'visLine',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.ml.components.singleMetricViewerEmbeddable.displayName', {
|
||||
defaultMessage: 'Single metric viewer',
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
|
@ -41,6 +42,28 @@ export function createAddSwimlanePanelAction(
|
|||
getIconType: () => PLUGIN_ICON,
|
||||
},
|
||||
],
|
||||
order: 40,
|
||||
// @ts-expect-error getIconType is typed as string, but EuiIcon accepts ReactComponent for custom icons.
|
||||
// See https://github.com/elastic/kibana/issues/184643
|
||||
getIconType: () => (iconProps) =>
|
||||
(
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...iconProps}
|
||||
>
|
||||
<path
|
||||
d="M1 5V1H5V5H1ZM4 4V2H2V4H4ZM6 5V1H10V5H6ZM9 4V2H7V4H9ZM11 5V1H15V5H11ZM12 4H14V2H12V4ZM1 10V6H5V10H1ZM4 9V7H2V9H4ZM6 10V6H10V10H6ZM9 9V7H7V9H9ZM11 10V6H15V10H11ZM14 9V7H12V9H14ZM1 15V11H5V15H1ZM2 14H4V12H2V14ZM6 15V11H10V15H6ZM7 14H9V12H7V14ZM11 15V11H15V15H11ZM12 14H14V12H12V14Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<rect width="4" height="4" transform="translate(6 1)" fill="currentColor" />
|
||||
<rect width="4" height="4" transform="translate(11 6)" fill="currentColor" />
|
||||
<rect width="4" height="4" transform="translate(6 6)" fill="currentColor" />
|
||||
<rect width="4" height="4" transform="translate(1 11)" fill="currentColor" />
|
||||
</svg>
|
||||
),
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.ml.components.jobAnomalyScoreEmbeddable.displayName', {
|
||||
defaultMessage: 'Anomaly swim lane',
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import type { CoreSetup } from '@kbn/core/public';
|
||||
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||
import { CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER } from '@kbn/ml-ui-actions';
|
||||
import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import { type UiActionsSetup, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import type { MlPluginStart, MlStartDependencies } from '../plugin';
|
||||
import { createApplyEntityFieldFiltersAction } from './apply_entity_filters_action';
|
||||
import { createApplyInfluencerFiltersAction } from './apply_influencer_filters_action';
|
||||
|
@ -67,9 +67,9 @@ export function registerMlUiActions(
|
|||
uiActions.registerAction(addAnomalyChartsPanelAction);
|
||||
|
||||
// Assign triggers
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addSingleMetricViewerPanelAction);
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addSwimlanePanelAction);
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addAnomalyChartsPanelAction);
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addSingleMetricViewerPanelAction);
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addSwimlanePanelAction);
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addAnomalyChartsPanelAction);
|
||||
|
||||
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, openInExplorerAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, openInSingleMetricViewerAction.id);
|
||||
|
|
|
@ -21,7 +21,8 @@ import { BehaviorSubject, combineLatest, from } from 'rxjs';
|
|||
import { map } from 'rxjs';
|
||||
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import type { InfraPublicConfig } from '../common/plugin_config_types';
|
||||
import { createInventoryMetricRuleType } from './alerting/inventory';
|
||||
import { createLogThresholdRuleType } from './alerting/log_threshold';
|
||||
|
@ -400,6 +401,8 @@ export class Plugin implements InfraClientPluginClass {
|
|||
|
||||
plugins.uiActions.registerAction<EmbeddableApiContext>({
|
||||
id: ADD_LOG_STREAM_ACTION_ID,
|
||||
grouping: [COMMON_EMBEDDABLE_GROUPING.legacy],
|
||||
order: 30,
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.infra.logStreamEmbeddable.displayName', {
|
||||
defaultMessage: 'Log stream',
|
||||
|
@ -427,7 +430,7 @@ export class Plugin implements InfraClientPluginClass {
|
|||
);
|
||||
},
|
||||
});
|
||||
plugins.uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_LOG_STREAM_ACTION_ID);
|
||||
plugins.uiActions.attachAction(ADD_PANEL_TRIGGER, ADD_LOG_STREAM_ACTION_ID);
|
||||
|
||||
const startContract: InfraClientStartExports = {
|
||||
inventoryViews,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
export const COMMON_SLO_GROUPING = [
|
||||
{
|
||||
id: 'slos',
|
||||
getDisplayName: () => 'SLOs',
|
||||
getDisplayName: () => 'Observability',
|
||||
getIconType: () => {
|
||||
return 'visGauge';
|
||||
},
|
||||
|
|
|
@ -138,7 +138,8 @@ export class SloPlugin
|
|||
const registerAsyncSloUiActions = async () => {
|
||||
if (pluginsSetup.uiActions) {
|
||||
const { registerSloUiActions } = await import('./ui_actions');
|
||||
registerSloUiActions(pluginsSetup.uiActions, coreSetup);
|
||||
|
||||
registerSloUiActions(coreSetup, pluginsSetup, pluginsStart);
|
||||
}
|
||||
};
|
||||
registerAsyncSloUiActions();
|
||||
|
|
|
@ -26,6 +26,7 @@ export function createAddAlertsPanelAction(
|
|||
id: ADD_SLO_ALERTS_ACTION_ID,
|
||||
grouping: COMMON_SLO_GROUPING,
|
||||
getIconType: () => 'alert',
|
||||
order: 20,
|
||||
isCompatible: async ({ embeddable }) => {
|
||||
return apiIsPresentationContainer(embeddable);
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ export function createAddErrorBudgetPanelAction(
|
|||
return {
|
||||
id: ADD_SLO_ERROR_BUDGET_ACTION_ID,
|
||||
grouping: COMMON_SLO_GROUPING,
|
||||
order: 10,
|
||||
getIconType: () => 'visLine',
|
||||
isCompatible: async ({ embeddable }) => {
|
||||
return apiIsPresentationContainer(embeddable);
|
||||
|
|
|
@ -25,6 +25,7 @@ export function createOverviewPanelAction(
|
|||
return {
|
||||
id: ADD_SLO_OVERVIEW_ACTION_ID,
|
||||
grouping: COMMON_SLO_GROUPING,
|
||||
order: 30,
|
||||
getIconType: () => 'visGauge',
|
||||
isCompatible: async ({ embeddable }) => {
|
||||
return apiIsPresentationContainer(embeddable);
|
||||
|
|
|
@ -5,24 +5,31 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import type { CoreSetup } from '@kbn/core/public';
|
||||
import { createOverviewPanelAction } from './create_overview_panel_action';
|
||||
import { createAddErrorBudgetPanelAction } from './create_error_budget_action';
|
||||
import { createAddAlertsPanelAction } from './create_alerts_panel_action';
|
||||
import { SloPublicPluginsStart, SloPublicStart } from '..';
|
||||
import { SloPublicPluginsStart, SloPublicStart, SloPublicPluginsSetup } from '..';
|
||||
|
||||
export function registerSloUiActions(
|
||||
uiActions: UiActionsSetup,
|
||||
core: CoreSetup<SloPublicPluginsStart, SloPublicStart>
|
||||
core: CoreSetup<SloPublicPluginsStart, SloPublicStart>,
|
||||
pluginsSetup: SloPublicPluginsSetup,
|
||||
pluginsStart: SloPublicPluginsStart
|
||||
) {
|
||||
const { uiActions } = pluginsSetup;
|
||||
const { serverless, cloud } = pluginsStart;
|
||||
|
||||
// Initialize actions
|
||||
const addOverviewPanelAction = createOverviewPanelAction(core.getStartServices);
|
||||
const addErrorBudgetPanelAction = createAddErrorBudgetPanelAction(core.getStartServices);
|
||||
const addAlertsPanelAction = createAddAlertsPanelAction(core.getStartServices);
|
||||
|
||||
// Assign triggers
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addOverviewPanelAction);
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addErrorBudgetPanelAction);
|
||||
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addAlertsPanelAction);
|
||||
// Only register these actions in stateful kibana, and the serverless observability project
|
||||
if (Boolean((serverless && cloud?.serverless.projectType === 'observability') || !serverless)) {
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addOverviewPanelAction);
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addErrorBudgetPanelAction);
|
||||
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addAlertsPanelAction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1192,8 +1192,6 @@
|
|||
"dashboard.actions.downloadOptionsUnsavedFilename": "sans titre",
|
||||
"dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "Minimiser",
|
||||
"dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "Maximiser le panneau",
|
||||
"dashboard.addPanelMenuTrigger.description": "Une nouvelle action apparaîtra dans le menu Ajouter un panneau du tableau de bord",
|
||||
"dashboard.addPanelMenuTrigger.title": "Menu Ajouter un panneau",
|
||||
"dashboard.appLeaveConfirmModal.cancelButtonLabel": "Annuler",
|
||||
"dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "Quitter Dashboard sans enregistrer ?",
|
||||
"dashboard.appLeaveConfirmModal.unsavedChangesTitle": "Modifications non enregistrées",
|
||||
|
@ -1215,7 +1213,6 @@
|
|||
"dashboard.editingToolbar.controlsButtonTitle": "Contrôles",
|
||||
"dashboard.editingToolbar.editControlGroupButtonTitle": "Paramètres",
|
||||
"dashboard.editingToolbar.onlyOneTimeSliderControlMsg": "Le groupe de contrôle contient déjà un contrôle de curseur temporel.",
|
||||
"dashboard.editorMenu.aggBasedGroupTitle": "Basé sur une agrégation",
|
||||
"dashboard.editorMenu.deprecatedTag": "Déclassé",
|
||||
"dashboard.embeddableApi.showSettings.flyout.applyButtonTitle": "Appliquer",
|
||||
"dashboard.embeddableApi.showSettings.flyout.cancelButtonTitle": "Annuler",
|
||||
|
@ -44235,6 +44232,8 @@
|
|||
"uiActions.errors.incompatibleAction": "Action non compatible",
|
||||
"uiActions.triggers.rowClickkDescription": "Un clic sur une ligne de tableau",
|
||||
"uiActions.triggers.rowClickTitle": "Clic sur ligne de tableau",
|
||||
"uiActions.triggers.dashboard.addPanelMenu.description": "Une nouvelle action apparaîtra dans le menu Ajouter un panneau du tableau de bord",
|
||||
"uiActions.triggers.dashboard.addPanelMenu.title": "Menu Ajouter un panneau",
|
||||
"unsavedChangesBadge.contextMenu.openButton": "Afficher les actions disponibles",
|
||||
"unsavedChangesBadge.contextMenu.revertChangesButton": "Restaurer les modifications",
|
||||
"unsavedChangesBadge.contextMenu.revertingChangesButtonStatus": "Annuler les modifications",
|
||||
|
|
|
@ -1192,8 +1192,6 @@
|
|||
"dashboard.actions.downloadOptionsUnsavedFilename": "無題",
|
||||
"dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化",
|
||||
"dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "パネルを最大化",
|
||||
"dashboard.addPanelMenuTrigger.description": "新しいアクションは、ダッシュボードのパネルの追加メニューに表示されます",
|
||||
"dashboard.addPanelMenuTrigger.title": "パネルの追加メニュー",
|
||||
"dashboard.appLeaveConfirmModal.cancelButtonLabel": "キャンセル",
|
||||
"dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "作業を保存せずにダッシュボードから移動しますか?",
|
||||
"dashboard.appLeaveConfirmModal.unsavedChangesTitle": "保存されていない変更",
|
||||
|
@ -1215,7 +1213,6 @@
|
|||
"dashboard.editingToolbar.controlsButtonTitle": "コントロール",
|
||||
"dashboard.editingToolbar.editControlGroupButtonTitle": "設定",
|
||||
"dashboard.editingToolbar.onlyOneTimeSliderControlMsg": "コントロールグループには、すでに時間スライダーコントロールがあります。",
|
||||
"dashboard.editorMenu.aggBasedGroupTitle": "アグリゲーションに基づく",
|
||||
"dashboard.editorMenu.deprecatedTag": "非推奨",
|
||||
"dashboard.embeddableApi.showSettings.flyout.applyButtonTitle": "適用",
|
||||
"dashboard.embeddableApi.showSettings.flyout.cancelButtonTitle": "キャンセル",
|
||||
|
@ -44209,6 +44206,8 @@
|
|||
"uiActions.actionPanel.more": "詳細",
|
||||
"uiActions.actionPanel.title": "オプション",
|
||||
"uiActions.errors.incompatibleAction": "操作に互換性がありません",
|
||||
"uiActions.triggers.dashboard.addPanelMenu.description": "新しいアクションは、ダッシュボードのパネルの追加メニューに表示されます",
|
||||
"uiActions.triggers.dashboard.addPanelMenu.title": "パネルの追加メニュー",
|
||||
"uiActions.triggers.rowClickkDescription": "テーブル行をクリック",
|
||||
"uiActions.triggers.rowClickTitle": "テーブル行クリック",
|
||||
"unsavedChangesBadge.contextMenu.openButton": "使用可能なアクションを表示",
|
||||
|
|
|
@ -1194,8 +1194,6 @@
|
|||
"dashboard.actions.downloadOptionsUnsavedFilename": "未命名",
|
||||
"dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化",
|
||||
"dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "最大化面板",
|
||||
"dashboard.addPanelMenuTrigger.description": "一个新操作将在仪表板的添加面板菜单中显示出来",
|
||||
"dashboard.addPanelMenuTrigger.title": "添加面板菜单",
|
||||
"dashboard.appLeaveConfirmModal.cancelButtonLabel": "取消",
|
||||
"dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "离开有未保存工作的仪表板?",
|
||||
"dashboard.appLeaveConfirmModal.unsavedChangesTitle": "未保存的更改",
|
||||
|
@ -1217,7 +1215,6 @@
|
|||
"dashboard.editingToolbar.controlsButtonTitle": "控件",
|
||||
"dashboard.editingToolbar.editControlGroupButtonTitle": "设置",
|
||||
"dashboard.editingToolbar.onlyOneTimeSliderControlMsg": "控件组已包含时间滑块控件。",
|
||||
"dashboard.editorMenu.aggBasedGroupTitle": "基于聚合",
|
||||
"dashboard.editorMenu.deprecatedTag": "(已过时)",
|
||||
"dashboard.embeddableApi.showSettings.flyout.applyButtonTitle": "应用",
|
||||
"dashboard.embeddableApi.showSettings.flyout.cancelButtonTitle": "取消",
|
||||
|
@ -44257,6 +44254,8 @@
|
|||
"uiActions.actionPanel.more": "更多",
|
||||
"uiActions.actionPanel.title": "选项",
|
||||
"uiActions.errors.incompatibleAction": "操作不兼容",
|
||||
"uiActions.triggers.dashboard.addPanelMenu.description": "一个新操作将在仪表板的添加面板菜单中显示出来",
|
||||
"uiActions.triggers.dashboard.addPanelMenu.title": "添加面板菜单",
|
||||
"uiActions.triggers.rowClickkDescription": "表格行的单击",
|
||||
"uiActions.triggers.rowClickTitle": "表格行单击",
|
||||
"unsavedChangesBadge.contextMenu.openButton": "查看可用操作",
|
||||
|
|
|
@ -33,6 +33,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await PageObjects.dashboard.switchToEditMode();
|
||||
}
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await testSubjects.setValue('dashboardPanelSelectionFlyout__searchInput', 'maps');
|
||||
await dashboardAddPanel.clickVisType('maps');
|
||||
await PageObjects.maps.clickSaveAndReturnButton();
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('Single SLO', function () {
|
||||
it('should open SLO configuration flyout', async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await dashboardAddPanel.clickEmbeddableFactoryGroupButton('slos');
|
||||
await dashboardAddPanel.verifyEmbeddableFactoryGroupExists('slos');
|
||||
await dashboardAddPanel.clickAddNewPanelFromUIActionLink('SLO Overview');
|
||||
await sloUi.common.assertSloOverviewConfigurationExists();
|
||||
});
|
||||
|
@ -67,7 +67,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('Group of SLOs', function () {
|
||||
it('can select Group Overview mode in the Flyout configuration', async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await dashboardAddPanel.clickEmbeddableFactoryGroupButton('slos');
|
||||
await dashboardAddPanel.verifyEmbeddableFactoryGroupExists('slos');
|
||||
await dashboardAddPanel.clickAddNewPanelFromUIActionLink('SLO Overview');
|
||||
await sloUi.common.clickOverviewMode();
|
||||
await sloUi.common.assertSloConfigurationGroupOverviewModeIsSelected();
|
||||
|
|
|
@ -124,9 +124,9 @@ export function MachineLearningDashboardEmbeddablesProvider(
|
|||
};
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await testSubjects.existOrFail('dashboardEditorContextMenu', { timeout: 2000 });
|
||||
await testSubjects.existOrFail('dashboardPanelSelectionFlyout', { timeout: 2000 });
|
||||
|
||||
await dashboardAddPanel.clickEmbeddableFactoryGroupButton('ml');
|
||||
await dashboardAddPanel.verifyEmbeddableFactoryGroupExists('ml');
|
||||
|
||||
await dashboardAddPanel.clickAddNewPanelFromUIActionLink(name[mlEmbeddableType]);
|
||||
await testSubjects.existOrFail('mlAnomalyJobSelectionControls', { timeout: 2000 });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue