Skip to content

Commit

Permalink
[Dashboard First] Library Notification (#76122)
Browse files Browse the repository at this point in the history
Added a notification to show when an embeddable is saved to the Visualize Library
  • Loading branch information
ThomThomson authored Sep 1, 2020
1 parent d7869de commit 1ae2a14
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 6 deletions.
5 changes: 5 additions & 0 deletions src/plugins/dashboard/public/application/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ export {
UnlinkFromLibraryActionContext,
ACTION_UNLINK_FROM_LIBRARY,
} from './unlink_from_library_action';
export {
LibraryNotificationActionContext,
LibraryNotificationAction,
ACTION_LIBRARY_NOTIFICATION,
} from './library_notification_action';
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { isErrorEmbeddable, ReferenceOrValueEmbeddable } from '../../embeddable_plugin';
import { DashboardContainer } from '../embeddable';
import { getSampleDashboardInput } from '../test_helpers';
import {
CONTACT_CARD_EMBEDDABLE,
ContactCardEmbeddableFactory,
ContactCardEmbeddable,
ContactCardEmbeddableInput,
ContactCardEmbeddableOutput,
} from '../../embeddable_plugin_test_samples';
import { coreMock } from '../../../../../core/public/mocks';
import { CoreStart } from 'kibana/public';
import { LibraryNotificationAction } from '.';
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
import { ViewMode } from '../../../../embeddable/public';

const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
const start = doStart();

let container: DashboardContainer;
let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable;
let coreStart: CoreStart;
beforeEach(async () => {
coreStart = coreMock.createStart();

const containerOptions = {
ExitFullScreenButton: () => null,
SavedObjectFinder: () => null,
application: {} as any,
embeddable: start,
inspector: {} as any,
notifications: {} as any,
overlays: coreStart.overlays,
savedObjectMetaData: {} as any,
uiActions: {} as any,
};

container = new DashboardContainer(getSampleDashboardInput(), containerOptions);

const contactCardEmbeddable = await container.addNewEmbeddable<
ContactCardEmbeddableInput,
ContactCardEmbeddableOutput,
ContactCardEmbeddable
>(CONTACT_CARD_EMBEDDABLE, {
firstName: 'Kibanana',
});

if (isErrorEmbeddable(contactCardEmbeddable)) {
throw new Error('Failed to create embeddable');
}
embeddable = embeddablePluginMock.mockRefOrValEmbeddable<
ContactCardEmbeddable,
ContactCardEmbeddableInput
>(contactCardEmbeddable, {
mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: contactCardEmbeddable.id },
mockedByValueInput: { firstName: 'Kibanana', id: contactCardEmbeddable.id },
});
embeddable.updateInput({ viewMode: ViewMode.EDIT });
});

test('Notification is shown when embeddable on dashboard has reference type input', async () => {
const action = new LibraryNotificationAction();
embeddable.updateInput(await embeddable.getInputAsRefType());
expect(await action.isCompatible({ embeddable })).toBe(true);
});

test('Notification is not shown when embeddable input is by value', async () => {
const action = new LibraryNotificationAction();
embeddable.updateInput(await embeddable.getInputAsValueType());
expect(await action.isCompatible({ embeddable })).toBe(false);
});

test('Notification is not shown when view mode is set to view', async () => {
const action = new LibraryNotificationAction();
embeddable.updateInput(await embeddable.getInputAsRefType());
embeddable.updateInput({ viewMode: ViewMode.VIEW });
expect(await action.isCompatible({ embeddable })).toBe(false);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiBadge } from '@elastic/eui';
import { IEmbeddable, ViewMode, isReferenceOrValueEmbeddable } from '../../embeddable_plugin';
import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin';
import { reactToUiComponent } from '../../../../kibana_react/public';

export const ACTION_LIBRARY_NOTIFICATION = 'ACTION_LIBRARY_NOTIFICATION';

export interface LibraryNotificationActionContext {
embeddable: IEmbeddable;
}

export class LibraryNotificationAction implements ActionByType<typeof ACTION_LIBRARY_NOTIFICATION> {
public readonly id = ACTION_LIBRARY_NOTIFICATION;
public readonly type = ACTION_LIBRARY_NOTIFICATION;
public readonly order = 1;

private displayName = i18n.translate('dashboard.panel.LibraryNotification', {
defaultMessage: 'Library',
});

private icon = 'folderCheck';

public readonly MenuItem = reactToUiComponent(() => (
<EuiBadge
data-test-subj={`embeddablePanelNotification-${this.id}`}
iconType={this.icon}
key={this.id}
style={{ marginTop: '2px', marginRight: '4px' }}
color="hollow"
>
{this.displayName}
</EuiBadge>
));

public getDisplayName({ embeddable }: LibraryNotificationActionContext) {
if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
throw new IncompatibleActionError();
}
return this.displayName;
}

public getIconType({ embeddable }: LibraryNotificationActionContext) {
if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
throw new IncompatibleActionError();
}
return this.icon;
}

public getDisplayNameTooltip = ({ embeddable }: LibraryNotificationActionContext) => {
if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
throw new IncompatibleActionError();
}
return i18n.translate('dashboard.panel.libraryNotification.toolTip', {
defaultMessage:
'This panel is linked to a Library item. Editing the panel might affect other dashboards.',
});
};

public isCompatible = async ({ embeddable }: LibraryNotificationActionContext) => {
return (
embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
isReferenceOrValueEmbeddable(embeddable) &&
embeddable.inputIsRefType(embeddable.getInput())
);
};

public execute = async () => {};
}
17 changes: 12 additions & 5 deletions src/plugins/dashboard/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
EmbeddableStart,
SavedObjectEmbeddableInput,
EmbeddableInput,
PANEL_NOTIFICATION_TRIGGER,
} from '../../embeddable/public';
import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from '../../data/public';
import { SharePluginSetup, SharePluginStart, UrlGeneratorContract } from '../../share/public';
Expand Down Expand Up @@ -83,6 +84,12 @@ import {
ACTION_UNLINK_FROM_LIBRARY,
UnlinkFromLibraryActionContext,
UnlinkFromLibraryAction,
ACTION_ADD_TO_LIBRARY,
AddToLibraryActionContext,
AddToLibraryAction,
ACTION_LIBRARY_NOTIFICATION,
LibraryNotificationActionContext,
LibraryNotificationAction,
} from './application';
import {
createDashboardUrlGenerator,
Expand All @@ -95,11 +102,6 @@ import { addEmbeddableToDashboardUrl } from './url_utils/url_helper';
import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder';
import { UrlGeneratorState } from '../../share/public';
import { AttributeService } from '.';
import {
AddToLibraryAction,
ACTION_ADD_TO_LIBRARY,
AddToLibraryActionContext,
} from './application/actions/add_to_library_action';

declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
Expand Down Expand Up @@ -162,6 +164,7 @@ declare module '../../../plugins/ui_actions/public' {
[ACTION_CLONE_PANEL]: ClonePanelActionContext;
[ACTION_ADD_TO_LIBRARY]: AddToLibraryActionContext;
[ACTION_UNLINK_FROM_LIBRARY]: UnlinkFromLibraryActionContext;
[ACTION_LIBRARY_NOTIFICATION]: LibraryNotificationActionContext;
}
}

Expand Down Expand Up @@ -437,6 +440,10 @@ export class DashboardPlugin
const unlinkFromLibraryAction = new UnlinkFromLibraryAction();
uiActions.registerAction(unlinkFromLibraryAction);
uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id);

const libraryNotificationAction = new LibraryNotificationAction();
uiActions.registerAction(libraryNotificationAction);
uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id);
}

const savedDashboardLoader = createSavedDashboardLoader({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Action } from 'src/plugins/ui_actions/public';
import { PanelOptionsMenu } from './panel_options_menu';
import { IEmbeddable } from '../../embeddables';
import { EmbeddableContext, panelBadgeTrigger, panelNotificationTrigger } from '../../triggers';
import { uiToReactComponent } from '../../../../../kibana_react/public';

export interface PanelHeaderProps {
title?: string;
Expand Down Expand Up @@ -65,7 +66,9 @@ function renderNotifications(
return notifications.map((notification) => {
const context = { embeddable };

let badge = (
let badge = notification.MenuItem ? (
React.createElement(uiToReactComponent(notification.MenuItem))
) : (
<EuiNotificationBadge
data-test-subj={`embeddablePanelNotification-${notification.id}`}
key={notification.id}
Expand Down

0 comments on commit 1ae2a14

Please sign in to comment.