Disabled actions (#51975)

* feat: disable actions from SIEM by using `disabledActions` list

* feat: filter out actions specified in `disabledActions` input prop

* test: 💍 remove legacy test

* chore: 🤖 remove unused import

* test: 💍 add disabledActions prop tests
This commit is contained in:
Vadim Dalecky 2019-12-04 05:59:53 -08:00 committed by GitHub
parent 085a2af8ec
commit 73651a1b28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 52 deletions

View file

@ -28,6 +28,11 @@ export interface EmbeddableInput {
id: string;
lastReloadRequestTime?: number;
hidePanelTitles?: boolean;
/**
* List of action IDs that this embeddable should not render.
*/
disabledActions?: string[];
}
export interface EmbeddableOutput {

View file

@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { I18nProvider } from '@kbn/i18n/react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
import { IAction, ITrigger } from 'src/plugins/ui_actions/public';
import { IAction, ITrigger, IUiActionsApi } from 'src/plugins/ui_actions/public';
import { Trigger, GetEmbeddableFactory, ViewMode } from '../types';
import { EmbeddableFactory, isErrorEmbeddable } from '../embeddables';
import { EmbeddablePanel } from './embeddable_panel';
@ -42,6 +42,7 @@ import {
} from '../test_samples/embeddables/contact_card/contact_card_embeddable';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { EuiBadge } from '@elastic/eui';
const actionRegistry = new Map<string, IAction>();
const triggerRegistry = new Map<string, ITrigger>();
@ -174,6 +175,105 @@ test('HelloWorldContainer in view mode hides edit mode actions', async () => {
expect(findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`).length).toBe(0);
});
const renderInEditModeAndOpenContextMenu = async (
embeddableInputs: any,
getActions: IUiActionsApi['getTriggerCompatibleActions'] = () => Promise.resolve([])
) => {
const inspector = inspectorPluginMock.createStartContract();
const container = new HelloWorldContainer({ id: '123', panels: {}, viewMode: ViewMode.VIEW }, {
getEmbeddableFactory,
} as any);
const embeddable = await container.addNewEmbeddable<
ContactCardEmbeddableInput,
ContactCardEmbeddableOutput,
ContactCardEmbeddable
>(CONTACT_CARD_EMBEDDABLE, embeddableInputs);
const component = mount(
<I18nProvider>
<EmbeddablePanel
embeddable={embeddable}
getActions={getActions}
getAllEmbeddableFactories={(() => []) as any}
getEmbeddableFactory={(() => undefined) as any}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
SavedObjectFinder={() => null}
/>
</I18nProvider>
);
findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click');
await nextTick();
component.update();
return { component };
};
test('HelloWorldContainer in edit mode hides disabledActions', async () => {
const action = {
id: 'FOO',
type: 'FOO',
getIconType: () => undefined,
getDisplayName: () => 'foo',
isCompatible: async () => true,
execute: async () => {},
};
const getActions = () => Promise.resolve([action]);
const { component: component1 } = await renderInEditModeAndOpenContextMenu(
{
firstName: 'Bob',
},
getActions
);
const { component: component2 } = await renderInEditModeAndOpenContextMenu(
{
firstName: 'Bob',
disabledActions: ['FOO'],
},
getActions
);
const fooContextMenuActionItem1 = findTestSubject(component1, 'embeddablePanelAction-FOO');
const fooContextMenuActionItem2 = findTestSubject(component2, 'embeddablePanelAction-FOO');
expect(fooContextMenuActionItem1.length).toBe(1);
expect(fooContextMenuActionItem2.length).toBe(0);
});
test('HelloWorldContainer hides disabled badges', async () => {
const action = {
id: 'BAR',
type: 'BAR',
getIconType: () => undefined,
getDisplayName: () => 'bar',
isCompatible: async () => true,
execute: async () => {},
};
const getActions = () => Promise.resolve([action]);
const { component: component1 } = await renderInEditModeAndOpenContextMenu(
{
firstName: 'Bob',
},
getActions
);
const { component: component2 } = await renderInEditModeAndOpenContextMenu(
{
firstName: 'Bob',
disabledActions: ['BAR'],
},
getActions
);
expect(component1.find(EuiBadge).length).toBe(1);
expect(component2.find(EuiBadge).length).toBe(0);
});
test('HelloWorldContainer in edit mode shows edit mode actions', async () => {
const inspector = inspectorPluginMock.createStartContract();

View file

@ -91,15 +91,19 @@ export class EmbeddablePanel extends React.Component<Props, State> {
}
private async refreshBadges() {
const badges = await this.props.getActions(PANEL_BADGE_TRIGGER, {
let badges: IAction[] = await this.props.getActions(PANEL_BADGE_TRIGGER, {
embeddable: this.props.embeddable,
});
if (!this.mounted) return;
if (this.mounted) {
this.setState({
badges,
});
const { disabledActions } = this.props.embeddable.getInput();
if (disabledActions) {
badges = badges.filter(badge => disabledActions.indexOf(badge.id) === -1);
}
this.setState({
badges,
});
}
public UNSAFE_componentWillMount() {
@ -200,10 +204,15 @@ export class EmbeddablePanel extends React.Component<Props, State> {
};
private getActionContextMenuPanel = async () => {
const actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, {
let actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, {
embeddable: this.props.embeddable,
});
const { disabledActions } = this.props.embeddable.getInput();
if (disabledActions) {
actions = actions.filter(action => disabledActions.indexOf(action.id) === -1);
}
const createGetUserData = (overlays: OverlayStart) =>
async function getUserData(context: { embeddable: IEmbeddable }) {
return new Promise<{ title: string | undefined }>(resolve => {

View file

@ -23,7 +23,7 @@ import { Loader } from '../loader';
import { useStateToaster } from '../toasters';
import { Embeddable } from './embeddable';
import { EmbeddableHeader } from './embeddable_header';
import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers';
import { createEmbeddable, displayErrorToast } from './embedded_map_helpers';
import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapToolTip } from './map_tool_tip/map_tool_tip';
import * as i18n from './translations';
@ -104,17 +104,6 @@ export const EmbeddedMapComponent = ({
const plugins = useKibanaPlugins();
const core = useKibanaCore();
// Setup embeddables API (i.e. detach extra actions) useEffect
useEffect(() => {
try {
setupEmbeddablesAPI(plugins);
} catch (e) {
displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster);
setIsLoading(false);
setIsError(true);
}
}, []);
// Initial Load useEffect
useEffect(() => {
let isSubscribed = true;

View file

@ -4,10 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers';
import { createEmbeddable, displayErrorToast } from './embedded_map_helpers';
import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers';
import { createPortalNode } from 'react-reverse-portal';
import { PluginsStart } from 'ui/new_platform/new_platform';
jest.mock('ui/new_platform');
jest.mock('../../lib/settings/use_kibana_ui_setting');
@ -45,13 +44,6 @@ describe('embedded_map_helpers', () => {
});
});
describe('setupEmbeddablesAPI', () => {
test('detaches extra UI actions', () => {
setupEmbeddablesAPI((npStart.plugins as unknown) as PluginsStart);
expect(npStart.plugins.uiActions.detachAction).toHaveBeenCalledTimes(2);
});
});
describe('createEmbeddable', () => {
test('attaches refresh action', async () => {
const setQueryMock = jest.fn();

View file

@ -7,14 +7,8 @@
import uuid from 'uuid';
import React from 'react';
import { OutPortal, PortalNode } from 'react-reverse-portal';
import { PluginsStart } from 'ui/new_platform/new_platform';
import { ActionToaster, AppToast } from '../toasters';
import {
CONTEXT_MENU_TRIGGER,
PANEL_BADGE_TRIGGER,
ViewMode,
} from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
import {
IndexPatternMapping,
MapEmbeddable,
@ -53,23 +47,6 @@ export const displayErrorToast = (
});
};
/**
* Temporary Embeddables API configuration override until ability to edit actions is addressed:
* https://github.com/elastic/kibana/issues/43643
*
* @param plugins new platform plugins
*
* @throws Error if trigger/action doesn't exist
*/
export const setupEmbeddablesAPI = (plugins: PluginsStart) => {
try {
plugins.uiActions.detachAction(CONTEXT_MENU_TRIGGER, 'CUSTOM_TIME_RANGE');
plugins.uiActions.detachAction(PANEL_BADGE_TRIGGER, 'CUSTOM_TIME_RANGE_BADGE');
} catch (e) {
throw e;
}
};
/**
* Creates MapEmbeddable with provided initial configuration
*
@ -115,6 +92,7 @@ export const createEmbeddable = async (
openTOCDetails: [],
hideFilterActions: false,
mapCenter: { lon: -1.05469, lat: 15.96133, zoom: 1 },
disabledActions: ['CUSTOM_TIME_RANGE', 'CUSTOM_TIME_RANGE_BADGE'],
};
const renderTooltipContent = ({