Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Actionable Observability] Display action connector icon in o11y rule details page #132026

Merged
merged 9 commits into from
May 20, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ const triggersActionsUiStartMock = {
get: jest.fn(),
list: jest.fn(),
},
actionTypeRegistry: {
has: jest.fn((x) => true),
register: jest.fn(),
get: jest.fn(),
list: jest.fn(),
},
};
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { ReactWrapper, mount } from 'enzyme';
import { Actions } from './actions';
import { observabilityPublicPluginsStartMock } from '../../../observability_public_plugins_start.mock';
import { kibanaStartMock } from '../../../utils/kibana_react.mock';

const mockUseKibanaReturnValue = kibanaStartMock.startContract();

jest.mock('../../../utils/kibana_react', () => ({
__esModule: true,
useKibana: jest.fn(() => mockUseKibanaReturnValue),
}));

jest.mock('../../../hooks/use_fetch_rule_actions', () => ({
useFetchRuleActions: jest.fn(),
}));

const { useFetchRuleActions } = jest.requireMock('../../../hooks/use_fetch_rule_actions');

describe('Actions', () => {
let wrapper: ReactWrapper<any>;
async function setup() {
const ruleActions = [
{
id: 1,
group: 'metrics.inventory_threshold.fired',
actionTypeId: '.server-log',
},
{
id: 2,
group: 'metrics.inventory_threshold.fired',
actionTypeId: '.slack',
},
];
const allActions = [
{
id: 1,
name: 'Server log',
actionTypeId: '.server-log',
},
{
id: 2,
name: 'Slack',
actionTypeId: '.slack',
},
{
id: 3,
name: 'Email',
actionTypeId: '.email',
},
];
useFetchRuleActions.mockReturnValue({
allActions,
});

const actionTypeRegistryMock =
observabilityPublicPluginsStartMock.createStart().triggersActionsUi.actionTypeRegistry;
actionTypeRegistryMock.list.mockReturnValue([
{ id: '.server-log', iconClass: 'logsApp' },
{ id: '.slack', iconClass: 'logoSlack' },
{ id: '.email', iconClass: 'email' },
{ id: '.index', iconClass: 'indexOpen' },
]);
wrapper = mount(
<Actions ruleActions={ruleActions} actionTypeRegistry={actionTypeRegistryMock} />
);
}

it("renders action connector icons for user's selected rule actions", async () => {
await setup();
wrapper.debug();
expect(wrapper.find('[data-euiicon-type]').length).toBe(2);
expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1);
expect(wrapper.find('[data-euiicon-type="logoSlack"]').length).toBe(1);
expect(wrapper.find('[data-euiicon-type="index"]').length).toBe(0);
expect(wrapper.find('[data-euiicon-type="email"]').length).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,13 @@ import {
EuiLoadingSpinner,
} from '@elastic/eui';
import { intersectionBy } from 'lodash';
import { suspendedComponentWithProps } from '@kbn/triggers-actions-ui-plugin/public';
import { i18n } from '@kbn/i18n';
import { ActionsProps } from '../types';
import { useFetchRuleActions } from '../../../hooks/use_fetch_rule_actions';
import { useKibana } from '../../../utils/kibana_react';

interface MapActionTypeIcon {
[key: string]: string | IconType;
}
const mapActionTypeIcon: MapActionTypeIcon = {
/* TODO: Add the rest of the application logs (SVGs ones) */
'.server-log': 'logsApp',
'.email': 'email',
'.pagerduty': 'apps',
'.index': 'indexOpen',
'.slack': 'logoSlack',
'.webhook': 'logoWebhook',
};
export function Actions({ ruleActions }: ActionsProps) {
export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) {
const {
http,
notifications: { toasts },
Expand All @@ -53,22 +42,31 @@ export function Actions({ ruleActions }: ActionsProps) {
</EuiText>
</EuiFlexItem>
);

function getActionIconClass(actionGroupId?: string): IconType | undefined {
const actionGroup = actionTypeRegistry.list().find((group) => group.id === actionGroupId);
return typeof actionGroup?.iconClass === 'string'
? actionGroup?.iconClass
: suspendedComponentWithProps(actionGroup?.iconClass as React.ComponentType);
}
const actions = intersectionBy(allActions, ruleActions, 'actionTypeId');
if (isLoadingActions) return <EuiLoadingSpinner size="s" />;
return (
<EuiFlexGroup direction="column">
{actions.map((action) => (
<>
<EuiFlexGroup alignItems="baseline">
{actions.map(({ actionTypeId, name }) => (
<React.Fragment key={actionTypeId}>
<EuiFlexGroup alignItems="center" gutterSize="s" component="span">
<EuiFlexItem grow={false}>
<EuiIcon size="m" type={mapActionTypeIcon[action.actionTypeId] ?? 'apps'} />
<EuiIcon size="m" type={getActionIconClass(actionTypeId) ?? 'apps'} />
</EuiFlexItem>
<EuiFlexItem style={{ margin: '0px' }}>
<EuiText size="s">{action.name}</EuiText>
<EuiFlexItem>
<EuiText data-test-subj={`actionConnectorName-${name}`} size="s">
{name}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
</>
</React.Fragment>
))}
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function RuleDetailsPage() {
ruleTypeRegistry,
getRuleStatusDropdown,
getEditAlertFlyout,
actionTypeRegistry,
getRuleEventLogList,
},
application: { capabilities, navigateToUrl },
Expand Down Expand Up @@ -456,7 +457,7 @@ export function RuleDetailsPage() {
})}
</ItemTitleRuleSummary>
<EuiFlexItem grow={3}>
<Actions ruleActions={rule.actions} />
<Actions ruleActions={rule.actions} actionTypeRegistry={actionTypeRegistry} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
*/

import { HttpSetup } from '@kbn/core/public';
import { Rule, RuleSummary, RuleType } from '@kbn/triggers-actions-ui-plugin/public';
import {
Rule,
RuleSummary,
RuleType,
ActionTypeRegistryContract,
} from '@kbn/triggers-actions-ui-plugin/public';

export interface RuleDetailsPathParams {
ruleId: string;
Expand Down Expand Up @@ -63,6 +68,7 @@ export interface ItemValueRuleSummaryProps {
}
export interface ActionsProps {
ruleActions: any[];
actionTypeRegistry: ActionTypeRegistryContract;
}

export const EVENT_LOG_LIST_TAB = 'rule_event_log_list';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@
*/

import React from 'react';
import { LogoProps } from '../types';

const Logo = () => (
const Logo = (props: LogoProps) => (
<svg
width="32px"
height="32px"
viewBox="0 0 32 32"
className="euiIcon euiIcon--xLarge euiCard__icon"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
{...props}
>
<title>x-logo</title>
<defs>
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/triggers_actions_ui/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export { loadRuleAggregations, loadRuleTags } from './application/lib/rule_api/a
export { useLoadRuleTypes } from './application/hooks/use_load_rule_types';
export { loadRule } from './application/lib/rule_api/get_rule';
export { loadAllActions } from './application/lib/action_connector_api';
export { suspendedComponentWithProps } from './application/lib/suspended_component_with_props';
export { loadActionTypes } from './application/lib/action_connector_api/connector_types';
export { NOTIFY_WHEN_OPTIONS } from './application/sections/rule_form/rule_notify_when';
export type { TIME_UNITS } from './application/constants';
Expand Down