Skip to content

Commit 0bdcda8

Browse files
authored
[SIEM] Fixes UX issues around prebuilt ML Rules (#62396)
## Summary This PR fixes a number of UX issues around the new prebuilt `machine_learning` rules when the user does not have the necessary permissions to manage the backing ML Job. Along with #62383, this ensures there is adequate information for the user determine if a rule is not working because the backing job is not running (and helping to prevent this from occurring). This also includes some requested copy changes, including: * Renames `Anomaly Detection` dropdown to `ML job settings` <p align="center"> <img width="500" src="https://user-images.githubusercontent.com/2946766/78320279-57c5a880-7526-11ea-8350-647cbba263a4.png" /> </p> * Updates copy in `ML job settings` dropdown <p align="center"> <img width="500" src="https://user-images.githubusercontent.com/2946766/78320473-cc98e280-7526-11ea-8871-e97661ff5f78.png" /> </p> * Only shows `ML job settings` UI when on `/detections/` routes <p align="center"> <img width="500" src="https://user-images.githubusercontent.com/2946766/78320401-922f4580-7526-11ea-9f97-0ec06526b273.png" /> </p> ### All Rules Changes * Disables the `activate switch` if user does not have permission to enable/disable jobs <p align="center"> <img width="500" src="https://user-images.githubusercontent.com/2946766/78320892-d3742500-7527-11ea-90bb-91fd203480bd.png" /> </p> * Adds warning toast when attempting to activate via bulk actions (if user does not have permission to enable/disable jobs) <p align="center"> <img width="300" src="https://user-images.githubusercontent.com/2946766/78321015-1a621a80-7528-11ea-8ab0-f9fef19240f7.png" /> </p> ### Rule Details Changes * `Machine Learning job` link now links to ML App with table filtered to the relevant job * Disables the `activate switch` if user does not have permission to enable/disable jobs <p align="center"> <img width="500" src="https://user-images.githubusercontent.com/2946766/78321277-c277e380-7528-11ea-99e9-034970a5054e.png" /> </p> ### Create/Edit Rule Changes * If the job selected _is not running_, a warning will be displayed to remind the user to enable the job before running the rule. cc @benskelker @MikePaquette -- this okay copy here? <p align="center"> <img width="500" src="https://user-images.githubusercontent.com/2946766/78321498-63ff3500-7529-11ea-9b09-a87186cbe0ce.png" /> </p> Resolves elastic/siem-team#575 Resolves elastic/siem-team#519 ### 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/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Scheduled time with @benskelker to update docs - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
1 parent 813d6cb commit 0bdcda8

File tree

17 files changed

+305
-131
lines changed

17 files changed

+305
-131
lines changed

x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import React from 'react';
1010
import '../../mock/match_media';
1111
import { HeaderGlobal } from './index';
1212

13+
jest.mock('react-router-dom', () => ({
14+
useLocation: () => ({
15+
pathname: '/app/siem#/hosts/allHosts',
16+
hash: '',
17+
search: '',
18+
state: '',
19+
}),
20+
withRouter: () => jest.fn(),
21+
}));
22+
1323
jest.mock('ui/new_platform');
1424

1525
// Test will fail because we will to need to mock some core services to make the test work
@@ -19,6 +29,10 @@ jest.mock('../search_bar', () => ({
1929
}));
2030

2131
describe('HeaderGlobal', () => {
32+
beforeEach(() => {
33+
jest.resetAllMocks();
34+
});
35+
2236
test('it renders', () => {
2337
const wrapper = shallow(<HeaderGlobal />);
2438

x-pack/legacy/plugins/siem/public/components/header_global/index.tsx

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { pickBy } from 'lodash/fp';
99
import React from 'react';
1010
import styled, { css } from 'styled-components';
1111

12+
import { useLocation } from 'react-router-dom';
1213
import { gutterTimeline } from '../../lib/helpers';
1314
import { navTabs } from '../../pages/home/home_navigations';
1415
import { SiemPageName } from '../../pages/home/types';
@@ -36,63 +37,68 @@ FlexItem.displayName = 'FlexItem';
3637
interface HeaderGlobalProps {
3738
hideDetectionEngine?: boolean;
3839
}
39-
export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine = false }) => (
40-
<Wrapper className="siemHeaderGlobal">
41-
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap>
42-
<WithSource sourceId="default">
43-
{({ indicesExist }) => (
44-
<>
45-
<FlexItem>
46-
<EuiFlexGroup alignItems="center" responsive={false}>
47-
<FlexItem grow={false}>
48-
<EuiLink href={getOverviewUrl()}>
49-
<EuiIcon aria-label={i18n.SIEM} type="securityAnalyticsApp" size="l" />
50-
</EuiLink>
51-
</FlexItem>
40+
export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine = false }) => {
41+
const currentLocation = useLocation();
5242

53-
<FlexItem component="nav">
54-
{indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
55-
<SiemNavigation
56-
display="condensed"
57-
navTabs={
58-
hideDetectionEngine
59-
? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
60-
: navTabs
61-
}
62-
/>
63-
) : (
64-
<SiemNavigation
65-
display="condensed"
66-
navTabs={pickBy((_, key) => key === SiemPageName.overview, navTabs)}
67-
/>
68-
)}
69-
</FlexItem>
70-
</EuiFlexGroup>
71-
</FlexItem>
72-
73-
<FlexItem grow={false}>
74-
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
75-
{indicesExistOrDataTemporarilyUnavailable(indicesExist) && (
43+
return (
44+
<Wrapper className="siemHeaderGlobal">
45+
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap>
46+
<WithSource sourceId="default">
47+
{({ indicesExist }) => (
48+
<>
49+
<FlexItem>
50+
<EuiFlexGroup alignItems="center" responsive={false}>
7651
<FlexItem grow={false}>
77-
<MlPopover />
52+
<EuiLink href={getOverviewUrl()}>
53+
<EuiIcon aria-label={i18n.SIEM} type="securityAnalyticsApp" size="l" />
54+
</EuiLink>
55+
</FlexItem>
56+
57+
<FlexItem component="nav">
58+
{indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
59+
<SiemNavigation
60+
display="condensed"
61+
navTabs={
62+
hideDetectionEngine
63+
? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
64+
: navTabs
65+
}
66+
/>
67+
) : (
68+
<SiemNavigation
69+
display="condensed"
70+
navTabs={pickBy((_, key) => key === SiemPageName.overview, navTabs)}
71+
/>
72+
)}
7873
</FlexItem>
79-
)}
74+
</EuiFlexGroup>
75+
</FlexItem>
8076

81-
<FlexItem grow={false}>
82-
<EuiButtonEmpty
83-
data-test-subj="add-data"
84-
href="kibana#home/tutorial_directory/siem"
85-
iconType="plusInCircle"
86-
>
87-
{i18n.BUTTON_ADD_DATA}
88-
</EuiButtonEmpty>
89-
</FlexItem>
90-
</EuiFlexGroup>
91-
</FlexItem>
92-
</>
93-
)}
94-
</WithSource>
95-
</EuiFlexGroup>
96-
</Wrapper>
97-
));
77+
<FlexItem grow={false}>
78+
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
79+
{indicesExistOrDataTemporarilyUnavailable(indicesExist) &&
80+
currentLocation.pathname.includes(`/${SiemPageName.detections}/`) && (
81+
<FlexItem grow={false}>
82+
<MlPopover />
83+
</FlexItem>
84+
)}
85+
86+
<FlexItem grow={false}>
87+
<EuiButtonEmpty
88+
data-test-subj="add-data"
89+
href="kibana#home/tutorial_directory/siem"
90+
iconType="plusInCircle"
91+
>
92+
{i18n.BUTTON_ADD_DATA}
93+
</EuiButtonEmpty>
94+
</FlexItem>
95+
</EuiFlexGroup>
96+
</FlexItem>
97+
</>
98+
)}
99+
</WithSource>
100+
</EuiFlexGroup>
101+
</Wrapper>
102+
);
103+
});
98104
HeaderGlobal.displayName = 'HeaderGlobal';

x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export const MlPopover = React.memo(() => {
117117
iconSide="right"
118118
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
119119
>
120-
{i18n.ANOMALY_DETECTION}
120+
{i18n.ML_JOB_SETTINGS}
121121
</EuiButtonEmpty>
122122
}
123123
isOpen={isPopoverOpen}
@@ -142,14 +142,14 @@ export const MlPopover = React.memo(() => {
142142
dispatch({ type: 'refresh' });
143143
}}
144144
>
145-
{i18n.ANOMALY_DETECTION}
145+
{i18n.ML_JOB_SETTINGS}
146146
</EuiButtonEmpty>
147147
}
148148
isOpen={isPopoverOpen}
149149
closePopover={() => setIsPopoverOpen(!isPopoverOpen)}
150150
>
151151
<PopoverContentsDiv data-test-subj="ml-popover-contents">
152-
<EuiPopoverTitle>{i18n.ANOMALY_DETECTION_TITLE}</EuiPopoverTitle>
152+
<EuiPopoverTitle>{i18n.ML_JOB_SETTINGS}</EuiPopoverTitle>
153153
<PopoverDescription />
154154

155155
<EuiSpacer />

x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const PopoverDescriptionComponent = () => (
1414
<EuiText size="s">
1515
<FormattedMessage
1616
id="xpack.siem.components.mlPopup.anomalyDetectionDescription"
17-
defaultMessage="Run any of the Machine Learning jobs below to view anomalous events throughout the SIEM application. We’ve provided a few common detection jobs to get you started. If you wish to add your own custom jobs, simply create and tag them with “SIEM” from the {machineLearning} application for inclusion here."
17+
defaultMessage="Run any of the Machine Learning jobs below to prepare for creating signal detection rules that produce signals for detected anomalies, and to view anomalous events throughout the SIEM application. We’ve provided a collection of common detection jobs to get you started. If you wish to add your own custom ML jobs, create and add them to the “SIEM” group from the {machineLearning} application."
1818
values={{
1919
machineLearning: (
2020
<EuiLink href={`${useBasePath()}/app/ml`} target="_blank">

x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,10 @@
66

77
import { i18n } from '@kbn/i18n';
88

9-
export const ANOMALY_DETECTION = i18n.translate(
10-
'xpack.siem.components.mlPopup.anomalyDetectionButtonLabel',
9+
export const ML_JOB_SETTINGS = i18n.translate(
10+
'xpack.siem.components.mlPopup.mlJobSettingsButtonLabel',
1111
{
12-
defaultMessage: 'Anomaly detection',
13-
}
14-
);
15-
16-
export const ANOMALY_DETECTION_TITLE = i18n.translate(
17-
'xpack.siem.components.mlPopup.anomalyDetectionTitle',
18-
{
19-
defaultMessage: 'Anomaly detection settings',
12+
defaultMessage: 'ML job settings',
2013
}
2114
);
2215

x-pack/legacy/plugins/siem/public/components/toasters/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@ export const displayErrorToast = (
3737
});
3838
};
3939

40+
/**
41+
* Displays a warning toast for the provided title and message
42+
*
43+
* @param title warning message to display in toaster and modal
44+
* @param dispatchToaster provided by useStateToaster()
45+
* @param id unique ID if necessary
46+
*/
47+
export const displayWarningToast = (
48+
title: string,
49+
dispatchToaster: React.Dispatch<ActionToaster>,
50+
id: string = uuid.v4()
51+
): void => {
52+
const toast: AppToast = {
53+
id,
54+
title,
55+
color: 'warning',
56+
iconType: 'help',
57+
};
58+
dispatchToaster({
59+
type: 'addToaster',
60+
toast,
61+
});
62+
};
63+
4064
/**
4165
* Displays a success toast for the provided title and message
4266
*

x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import {
1414
enableRulesAction,
1515
exportRulesAction,
1616
} from './actions';
17-
import { ActionToaster } from '../../../../components/toasters';
17+
import { ActionToaster, displayWarningToast } from '../../../../components/toasters';
1818
import { Rule } from '../../../../containers/detection_engine/rules';
19+
import * as detectionI18n from '../../translations';
1920

2021
interface GetBatchItems {
2122
closePopover: () => void;
2223
dispatch: Dispatch<Action>;
2324
dispatchToaster: Dispatch<ActionToaster>;
25+
hasMlPermissions: boolean;
2426
loadingRuleIds: string[];
2527
reFetchRules: (refreshPrePackagedRule?: boolean) => void;
2628
rules: Rule[];
@@ -31,6 +33,7 @@ export const getBatchItems = ({
3133
closePopover,
3234
dispatch,
3335
dispatchToaster,
36+
hasMlPermissions,
3437
loadingRuleIds,
3538
reFetchRules,
3639
rules,
@@ -57,7 +60,22 @@ export const getBatchItems = ({
5760
const deactivatedIds = selectedRuleIds.filter(
5861
id => !rules.find(r => r.id === id)?.enabled ?? false
5962
);
60-
await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster);
63+
64+
const deactivatedIdsNoML = deactivatedIds.filter(
65+
id => rules.find(r => r.id === id)?.type !== 'machine_learning' ?? false
66+
);
67+
68+
const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length;
69+
if (!hasMlPermissions && mlRuleCount > 0) {
70+
displayWarningToast(detectionI18n.ML_RULES_UNAVAILABLE(mlRuleCount), dispatchToaster);
71+
}
72+
73+
await enableRulesAction(
74+
hasMlPermissions ? deactivatedIds : deactivatedIdsNoML,
75+
true,
76+
dispatch,
77+
dispatchToaster
78+
);
6179
}}
6280
>
6381
{i18n.BATCH_ACTION_ACTIVATE_SELECTED}

x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
EuiTableActionsColumnType,
1414
EuiText,
1515
EuiHealth,
16+
EuiToolTip,
1617
} from '@elastic/eui';
1718
import { FormattedRelative } from '@kbn/i18n/react';
1819
import * as H from 'history';
@@ -36,6 +37,8 @@ import {
3637
} from './actions';
3738
import { Action } from './reducer';
3839
import { LocalizedDateTooltip } from '../../../../components/localized_date_tooltip';
40+
import * as detectionI18n from '../../translations';
41+
import { isMlRule } from '../../../../../common/detection_engine/ml_helpers';
3942

4043
export const getActions = (
4144
dispatch: React.Dispatch<Action>,
@@ -88,6 +91,7 @@ interface GetColumns {
8891
dispatch: React.Dispatch<Action>;
8992
dispatchToaster: Dispatch<ActionToaster>;
9093
history: H.History;
94+
hasMlPermissions: boolean;
9195
hasNoPermissions: boolean;
9296
loadingRuleIds: string[];
9397
reFetchRules: (refreshPrePackagedRule?: boolean) => void;
@@ -98,6 +102,7 @@ export const getColumns = ({
98102
dispatch,
99103
dispatchToaster,
100104
history,
105+
hasMlPermissions,
101106
hasNoPermissions,
102107
loadingRuleIds,
103108
reFetchRules,
@@ -182,14 +187,25 @@ export const getColumns = ({
182187
field: 'enabled',
183188
name: i18n.COLUMN_ACTIVATE,
184189
render: (value: Rule['enabled'], item: Rule) => (
185-
<RuleSwitch
186-
data-test-subj="enabled"
187-
dispatch={dispatch}
188-
id={item.id}
189-
enabled={item.enabled}
190-
isDisabled={hasNoPermissions}
191-
isLoading={loadingRuleIds.includes(item.id)}
192-
/>
190+
<EuiToolTip
191+
position="top"
192+
content={
193+
isMlRule(item.type) && !hasMlPermissions
194+
? detectionI18n.ML_RULES_DISABLED_MESSAGE
195+
: undefined
196+
}
197+
>
198+
<RuleSwitch
199+
data-test-subj="enabled"
200+
dispatch={dispatch}
201+
id={item.id}
202+
enabled={item.enabled}
203+
isDisabled={
204+
hasNoPermissions || (isMlRule(item.type) && !hasMlPermissions && !item.enabled)
205+
}
206+
isLoading={loadingRuleIds.includes(item.id)}
207+
/>
208+
</EuiToolTip>
193209
),
194210
sortable: true,
195211
width: '95px',

0 commit comments

Comments
 (0)