From 86ee80415721fbc9a6bae8aa2e17bb1bc87e652c Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Apr 2020 14:15:34 +0200 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20PANEL=5FNOTIFI?= =?UTF-8?q?CATION=5FTRIGGER?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/bootstrap.ts | 4 ++ src/plugins/embeddable/public/index.ts | 2 + .../public/lib/panel/embeddable_panel.tsx | 45 +++++++++++-------- .../lib/panel/panel_header/panel_header.tsx | 25 ++++++++--- .../public/lib/triggers/triggers.ts | 9 +++- 5 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index c8c4f0b95c458..33cf210763b10 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -31,12 +31,15 @@ import { ACTION_EDIT_PANEL, FilterActionContext, ACTION_APPLY_FILTER, + panelNotificationTrigger, + PANEL_NOTIFICATION_TRIGGER, } from './lib'; declare module '../../ui_actions/public' { export interface TriggerContextMapping { [CONTEXT_MENU_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; + [PANEL_NOTIFICATION_TRIGGER]: EmbeddableContext; } export interface ActionContextMapping { @@ -56,6 +59,7 @@ declare module '../../ui_actions/public' { export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); uiActions.registerTrigger(panelBadgeTrigger); + uiActions.registerTrigger(panelNotificationTrigger); const actionApplyFilter = createFilterAction(); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index be8548f0d9d73..7daa646928551 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -57,6 +57,8 @@ export { OutputSpec, PANEL_BADGE_TRIGGER, panelBadgeTrigger, + PANEL_NOTIFICATION_TRIGGER, + panelNotificationTrigger, PanelNotFoundError, PanelState, PropertySpec, diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index d4d23874cfb19..7d3aa009d2bc7 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -25,7 +25,12 @@ import { CoreStart, OverlayStart } from '../../../../../core/public'; import { toMountPoint } from '../../../../kibana_react/public'; import { Start as InspectorStartContract } from '../inspector'; -import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER, EmbeddableContext } from '../triggers'; +import { + CONTEXT_MENU_TRIGGER, + PANEL_BADGE_TRIGGER, + PANEL_NOTIFICATION_TRIGGER, + EmbeddableContext, +} from '../triggers'; import { IEmbeddable } from '../embeddables/i_embeddable'; import { ViewMode } from '../types'; @@ -65,14 +70,13 @@ interface State { hidePanelTitles: boolean; closeContextMenu: boolean; badges: Array>; - eventCount?: number; + notifications: Array>; } export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; private subscription?: Subscription; - private eventCountSubscription?: Subscription; private mounted: boolean = false; private generateId = htmlIdGenerator(); @@ -92,6 +96,7 @@ export class EmbeddablePanel extends React.Component { hidePanelTitles, closeContextMenu: false, badges: [], + notifications: [], }; this.embeddableRoot = React.createRef(); @@ -113,6 +118,22 @@ export class EmbeddablePanel extends React.Component { }); } + private async refreshNotifications() { + let notifications = await this.props.getActions(PANEL_NOTIFICATION_TRIGGER, { + embeddable: this.props.embeddable, + }); + if (!this.mounted) return; + + const { disabledActions } = this.props.embeddable.getInput(); + if (disabledActions) { + notifications = notifications.filter(badge => disabledActions.indexOf(badge.id) === -1); + } + + this.setState({ + notifications, + }); + } + public UNSAFE_componentWillMount() { this.mounted = true; const { embeddable } = this.props; @@ -125,6 +146,7 @@ export class EmbeddablePanel extends React.Component { }); this.refreshBadges(); + this.refreshNotifications(); } }); @@ -136,6 +158,7 @@ export class EmbeddablePanel extends React.Component { }); this.refreshBadges(); + this.refreshNotifications(); } }); } @@ -146,9 +169,6 @@ export class EmbeddablePanel extends React.Component { if (this.subscription) { this.subscription.unsubscribe(); } - if (this.eventCountSubscription) { - this.eventCountSubscription.unsubscribe(); - } if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } @@ -188,9 +208,9 @@ export class EmbeddablePanel extends React.Component { closeContextMenu={this.state.closeContextMenu} title={title} badges={this.state.badges} + notifications={this.state.notifications} embeddable={this.props.embeddable} headerId={headerId} - eventCount={this.state.eventCount} /> )}
@@ -202,17 +222,6 @@ export class EmbeddablePanel extends React.Component { if (this.embeddableRoot.current) { this.props.embeddable.render(this.embeddableRoot.current); } - - const dynamicActions = (this.props.embeddable.enhancements as any)?.dynamicActions; - if (dynamicActions) { - this.setState({ eventCount: dynamicActions.state.get().events.length }); - this.eventCountSubscription = dynamicActions.state.state$.subscribe( - ({ events }: { events: unknown[] }) => { - if (!this.mounted) return; - this.setState({ eventCount: events.length }); - } - ); - } } closeMyContextMenuPanel = () => { diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 2a856af7ae916..144913ecbac6f 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -39,9 +39,9 @@ export interface PanelHeaderProps { getActionContextMenuPanel: () => Promise; closeContextMenu: boolean; badges: Array>; + notifications: Array>; embeddable: IEmbeddable; headerId?: string; - eventCount?: number; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -58,6 +58,21 @@ function renderBadges(badges: Array>, embeddable: IEmb )); } +function renderNotifications( + notifications: Array>, + embeddable: IEmbeddable +) { + return notifications.map(notification => ( + notification.execute({ embeddable })} + > + {notification.getDisplayName({ embeddable })} + + )); +} + function renderTooltip(description: string) { return ( description !== '' && ( @@ -90,9 +105,9 @@ export function PanelHeader({ getActionContextMenuPanel, closeContextMenu, badges, + notifications, embeddable, headerId, - eventCount, }: PanelHeaderProps) { const viewDescription = getViewDescription(embeddable); const showTitle = !isViewMode || (title && !hidePanelTitles) || viewDescription !== ''; @@ -150,11 +165,7 @@ export function PanelHeader({ )} {renderBadges(badges, embeddable)} - {!isViewMode && !!eventCount && ( - - {eventCount} - - )} + {renderNotifications(notifications, embeddable)} = { id: PANEL_BADGE_TRIGGER, title: 'Panel badges', - description: 'Actions appear in title bar when an embeddable loads in a panel', + description: 'Actions appear in title bar when an embeddable loads in a panel.', +}; + +export const PANEL_NOTIFICATION_TRIGGER = 'PANEL_NOTIFICATION_TRIGGER'; +export const panelNotificationTrigger: Trigger<'PANEL_NOTIFICATION_TRIGGER'> = { + id: PANEL_NOTIFICATION_TRIGGER, + title: 'Panel notifications', + description: 'Actions appear in top-right corner of a panel.', }; From 13c52dbe3361f246047390ef00a8ebbaa538996f Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Apr 2020 15:20:27 +0200 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20PanelNotificat?= =?UTF-8?q?ionsAction=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/service/ui_actions_service.ts | 5 +++- .../public/actions/index.ts | 7 +++++ .../actions/panel_notifications_action.ts | 30 +++++++++++++++++++ .../embeddable_enhanced/public/plugin.ts | 15 +++++++++- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/embeddable_enhanced/public/actions/index.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index f00877f812b24..97d40bc56d1e0 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -23,6 +23,7 @@ import { TriggerToActionsRegistry, TriggerId, TriggerContextMapping, + ActionContextMapping, ActionType, } from '../types'; import { ActionInternal, Action, ActionByType, ActionDefinition, ActionContext } from '../actions'; @@ -138,7 +139,9 @@ export class UiActionsService { triggerId: TType, // The action can accept partial or no context, but if it needs context not provided // by this type of trigger, typescript will complain. yay! - action: ActionByType & Action + action: + | (ActionByType & Action) + | ActionDefinition ): void => { if (!this.actions.has(action.id)) this.registerAction(action); this.attachAction(triggerId, action.id); diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/index.ts b/x-pack/plugins/embeddable_enhanced/public/actions/index.ts new file mode 100644 index 0000000000000..b47abd48fd269 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/actions/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './panel_notifications_action'; diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts new file mode 100644 index 0000000000000..490234817b415 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public'; +import { ViewMode } from '../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddableContext } from '../types'; + +export const ACTION_PANEL_NOTIFICATIONS = 'ACTION_PANEL_NOTIFICATIONS'; + +/** + * This action renders in "edit" mode number of events (dynamic action) a panel + * has attached to it. + */ +export class PanelNotificationsAction implements ActionDefinition { + public readonly id = ACTION_PANEL_NOTIFICATIONS; + + public readonly getDisplayName = (context: EnhancedEmbeddableContext) => { + return String(context.embeddable.enhancements.dynamicActions.state.get().events.length); + }; + + public readonly isCompatible = async ({ embeddable }: EnhancedEmbeddableContext) => { + if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; + return embeddable.enhancements.dynamicActions.state.get().events.length > 0; + }; + + public readonly execute = async () => {}; +} diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index a7a56d2c0d78d..c8ccd3fcd6d54 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -16,14 +16,22 @@ import { IEmbeddable, defaultEmbeddableFactoryProvider, EmbeddableContext, + PANEL_NOTIFICATION_TRIGGER, } from '../../../../src/plugins/embeddable/public'; -import { EnhancedEmbeddable } from './types'; +import { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, AdvancedUiActionsStart, } from '../../advanced_ui_actions/public'; +import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions'; + +declare module '../../../../src/plugins/ui_actions/public' { + export interface ActionContextMapping { + [ACTION_PANEL_NOTIFICATIONS]: EnhancedEmbeddableContext; + } +} export interface SetupDependencies { embeddable: EmbeddableSetup; @@ -50,6 +58,11 @@ export class EmbeddableEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); + plugins.advancedUiActions.addTriggerAction( + PANEL_NOTIFICATION_TRIGGER, + new PanelNotificationsAction() + ); + return {}; } From 7772ba658637674a161709a634a1131ac34e9608 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Apr 2020 16:25:14 +0200 Subject: [PATCH 3/5] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20PanelNotificat?= =?UTF-8?q?ionsAction=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel_notifications_action.test.ts | 75 +++++++++++++++++++ .../actions/panel_notifications_action.ts | 12 ++- 2 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts new file mode 100644 index 0000000000000..839379387e094 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PanelNotificationsAction } from './panel_notifications_action'; +import { EnhancedEmbeddableContext } from '../types'; +import { ViewMode } from '../../../../../src/plugins/embeddable/public'; + +const createContext = (events: unknown[] = [], isEditMode = false): EnhancedEmbeddableContext => + ({ + embeddable: { + getInput: () => + ({ + viewMode: isEditMode ? ViewMode.EDIT : ViewMode.VIEW, + } as unknown), + enhancements: { + dynamicActions: { + state: { + get: () => + ({ + events, + } as unknown), + }, + }, + }, + }, + } as EnhancedEmbeddableContext); + +describe('PanelNotificationsAction', () => { + describe('getDisplayName', () => { + test('returns "0" if embeddable has no events', async () => { + const context = createContext(); + const action = new PanelNotificationsAction(); + + const name = await action.getDisplayName(context); + expect(name).toBe('0'); + }); + + test('returns "2" if embeddable has two events', async () => { + const context = createContext([{}, {}]); + const action = new PanelNotificationsAction(); + + const name = await action.getDisplayName(context); + expect(name).toBe('2'); + }); + }); + + describe('isCompatible', () => { + test('returns false if not in "edit" mode', async () => { + const context = createContext([{}]); + const action = new PanelNotificationsAction(); + + const result = await action.isCompatible(context); + expect(result).toBe(false); + }); + + test('returns true when in "edit" mode', async () => { + const context = createContext([{}], true); + const action = new PanelNotificationsAction(); + + const result = await action.isCompatible(context); + expect(result).toBe(true); + }); + + test('returns false when no embeddable has no events', async () => { + const context = createContext([], true); + const action = new PanelNotificationsAction(); + + const result = await action.isCompatible(context); + expect(result).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts index 490234817b415..19e0ac2a5a6d8 100644 --- a/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts +++ b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts @@ -6,7 +6,7 @@ import { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public'; import { ViewMode } from '../../../../../src/plugins/embeddable/public'; -import { EnhancedEmbeddableContext } from '../types'; +import { EnhancedEmbeddableContext, EnhancedEmbeddable } from '../types'; export const ACTION_PANEL_NOTIFICATIONS = 'ACTION_PANEL_NOTIFICATIONS'; @@ -17,13 +17,17 @@ export const ACTION_PANEL_NOTIFICATIONS = 'ACTION_PANEL_NOTIFICATIONS'; export class PanelNotificationsAction implements ActionDefinition { public readonly id = ACTION_PANEL_NOTIFICATIONS; - public readonly getDisplayName = (context: EnhancedEmbeddableContext) => { - return String(context.embeddable.enhancements.dynamicActions.state.get().events.length); + private getEventCount(embeddable: EnhancedEmbeddable): number { + return embeddable.enhancements.dynamicActions.state.get().events.length; + } + + public readonly getDisplayName = ({ embeddable }: EnhancedEmbeddableContext) => { + return String(this.getEventCount(embeddable)); }; public readonly isCompatible = async ({ embeddable }: EnhancedEmbeddableContext) => { if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; - return embeddable.enhancements.dynamicActions.state.get().events.length > 0; + return this.getEventCount(embeddable) > 0; }; public readonly execute = async () => {}; From 1bad7d24cf633e7dd4ab06722b64861f86fb664a Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 15 Apr 2020 13:02:53 +0200 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20revert=20addTrig?= =?UTF-8?q?gerAction()=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui_actions/public/service/ui_actions_service.ts | 4 +--- x-pack/plugins/embeddable_enhanced/public/plugin.ts | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 97d40bc56d1e0..9d10aac6e0478 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -139,9 +139,7 @@ export class UiActionsService { triggerId: TType, // The action can accept partial or no context, but if it needs context not provided // by this type of trigger, typescript will complain. yay! - action: - | (ActionByType & Action) - | ActionDefinition + action: ActionByType & Action ): void => { if (!this.actions.has(action.id)) this.registerAction(action); this.attachAction(triggerId, action.id); diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index c8ccd3fcd6d54..28503e83c9547 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -58,10 +58,9 @@ export class EmbeddableEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); - plugins.advancedUiActions.addTriggerAction( - PANEL_NOTIFICATION_TRIGGER, - new PanelNotificationsAction() - ); + const panelNotificationAction = new PanelNotificationsAction(); + plugins.advancedUiActions.registerAction(panelNotificationAction); + plugins.advancedUiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); return {}; } From d9c99ec1b0f6dbd68962fa03ae7a4977c88ca680 Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 15 Apr 2020 13:46:21 +0200 Subject: [PATCH 5/5] =?UTF-8?q?style:=20=F0=9F=92=84=20remove=20unused=20i?= =?UTF-8?q?mport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/service/ui_actions_service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 9d10aac6e0478..f00877f812b24 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -23,7 +23,6 @@ import { TriggerToActionsRegistry, TriggerId, TriggerContextMapping, - ActionContextMapping, ActionType, } from '../types'; import { ActionInternal, Action, ActionByType, ActionDefinition, ActionContext } from '../actions';