Skip to content

Commit

Permalink
Fixes ux issues around ml rules
Browse files Browse the repository at this point in the history
  • Loading branch information
spong committed Apr 3, 2020
1 parent d10889a commit cb23f69
Show file tree
Hide file tree
Showing 17 changed files with 295 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import React from 'react';

import '../../mock/match_media';
import { HeaderGlobal } from './index';
// eslint-disable-next-line @kbn/eslint/module_migration
import routeData from 'react-router';

jest.mock('ui/new_platform');

Expand All @@ -18,8 +20,20 @@ jest.mock('../search_bar', () => ({
SiemSearchBar: () => null,
}));

const mockHostsLocation = {
pathname: '/app/siem#/hosts/allHosts',
hash: '',
search: '',
state: '',
};

describe('HeaderGlobal', () => {
beforeEach(() => {
jest.resetAllMocks();
});

test('it renders', () => {
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockHostsLocation);
const wrapper = shallow(<HeaderGlobal />);

expect(wrapper).toMatchSnapshot();
Expand Down
116 changes: 61 additions & 55 deletions x-pack/legacy/plugins/siem/public/components/header_global/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { pickBy } from 'lodash/fp';
import React from 'react';
import styled, { css } from 'styled-components';

import { useLocation } from 'react-router-dom';
import { gutterTimeline } from '../../lib/helpers';
import { navTabs } from '../../pages/home/home_navigations';
import { SiemPageName } from '../../pages/home/types';
Expand Down Expand Up @@ -36,63 +37,68 @@ FlexItem.displayName = 'FlexItem';
interface HeaderGlobalProps {
hideDetectionEngine?: boolean;
}
export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine = false }) => (
<Wrapper className="siemHeaderGlobal">
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap>
<WithSource sourceId="default">
{({ indicesExist }) => (
<>
<FlexItem>
<EuiFlexGroup alignItems="center" responsive={false}>
<FlexItem grow={false}>
<EuiLink href={getOverviewUrl()}>
<EuiIcon aria-label={i18n.SIEM} type="securityAnalyticsApp" size="l" />
</EuiLink>
</FlexItem>
export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine = false }) => {
const currentLocation = useLocation();

<FlexItem component="nav">
{indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
<SiemNavigation
display="condensed"
navTabs={
hideDetectionEngine
? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
: navTabs
}
/>
) : (
<SiemNavigation
display="condensed"
navTabs={pickBy((_, key) => key === SiemPageName.overview, navTabs)}
/>
)}
</FlexItem>
</EuiFlexGroup>
</FlexItem>

<FlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
{indicesExistOrDataTemporarilyUnavailable(indicesExist) && (
return (
<Wrapper className="siemHeaderGlobal">
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap>
<WithSource sourceId="default">
{({ indicesExist }) => (
<>
<FlexItem>
<EuiFlexGroup alignItems="center" responsive={false}>
<FlexItem grow={false}>
<MlPopover />
<EuiLink href={getOverviewUrl()}>
<EuiIcon aria-label={i18n.SIEM} type="securityAnalyticsApp" size="l" />
</EuiLink>
</FlexItem>

<FlexItem component="nav">
{indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
<SiemNavigation
display="condensed"
navTabs={
hideDetectionEngine
? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
: navTabs
}
/>
) : (
<SiemNavigation
display="condensed"
navTabs={pickBy((_, key) => key === SiemPageName.overview, navTabs)}
/>
)}
</FlexItem>
)}
</EuiFlexGroup>
</FlexItem>

<FlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="add-data"
href="kibana#home/tutorial_directory/siem"
iconType="plusInCircle"
>
{i18n.BUTTON_ADD_DATA}
</EuiButtonEmpty>
</FlexItem>
</EuiFlexGroup>
</FlexItem>
</>
)}
</WithSource>
</EuiFlexGroup>
</Wrapper>
));
<FlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
{indicesExistOrDataTemporarilyUnavailable(indicesExist) &&
currentLocation.pathname.includes(`/${SiemPageName.detections}/`) && (
<FlexItem grow={false}>
<MlPopover />
</FlexItem>
)}

<FlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="add-data"
href="kibana#home/tutorial_directory/siem"
iconType="plusInCircle"
>
{i18n.BUTTON_ADD_DATA}
</EuiButtonEmpty>
</FlexItem>
</EuiFlexGroup>
</FlexItem>
</>
)}
</WithSource>
</EuiFlexGroup>
</Wrapper>
);
});
HeaderGlobal.displayName = 'HeaderGlobal';

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const MlPopover = React.memo(() => {
iconSide="right"
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
>
{i18n.ANOMALY_DETECTION}
{i18n.ML_JOB_SETTINGS}
</EuiButtonEmpty>
}
isOpen={isPopoverOpen}
Expand All @@ -189,14 +189,14 @@ export const MlPopover = React.memo(() => {
dispatch({ type: 'refresh' });
}}
>
{i18n.ANOMALY_DETECTION}
{i18n.ML_JOB_SETTINGS}
</EuiButtonEmpty>
}
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(!isPopoverOpen)}
>
<PopoverContentsDiv data-test-subj="ml-popover-contents">
<EuiPopoverTitle>{i18n.ANOMALY_DETECTION_TITLE}</EuiPopoverTitle>
<EuiPopoverTitle>{i18n.ML_JOB_SETTINGS}</EuiPopoverTitle>
<PopoverDescription />

<EuiSpacer />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PopoverDescriptionComponent = () => (
<EuiText size="s">
<FormattedMessage
id="xpack.siem.components.mlPopup.anomalyDetectionDescription"
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."
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, simply create and tag them with “SIEM” from the {machineLearning} application."
values={{
machineLearning: (
<EuiLink href={`${useBasePath()}/app/ml`} target="_blank">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,10 @@

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

export const ANOMALY_DETECTION = i18n.translate(
'xpack.siem.components.mlPopup.anomalyDetectionButtonLabel',
export const ML_JOB_SETTINGS = i18n.translate(
'xpack.siem.components.mlPopup.mlJobSettingsButtonLabel',
{
defaultMessage: 'Anomaly detection',
}
);

export const ANOMALY_DETECTION_TITLE = i18n.translate(
'xpack.siem.components.mlPopup.anomalyDetectionTitle',
{
defaultMessage: 'Anomaly detection settings',
defaultMessage: 'ML job settings',
}
);

Expand Down
24 changes: 24 additions & 0 deletions x-pack/legacy/plugins/siem/public/components/toasters/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@ export const displayErrorToast = (
});
};

/**
* Displays a warning toast for the provided title and message
*
* @param title warning message to display in toaster and modal
* @param dispatchToaster provided by useStateToaster()
* @param id unique ID if necessary
*/
export const displayWarningToast = (
title: string,
dispatchToaster: React.Dispatch<ActionToaster>,
id: string = uuid.v4()
): void => {
const toast: AppToast = {
id,
title,
color: 'warning',
iconType: 'help',
};
dispatchToaster({
type: 'addToaster',
toast,
});
};

/**
* Displays a success toast for the provided title and message
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import {
enableRulesAction,
exportRulesAction,
} from './actions';
import { ActionToaster } from '../../../../components/toasters';
import { ActionToaster, displayWarningToast } from '../../../../components/toasters';
import { Rule } from '../../../../containers/detection_engine/rules';
import * as detectionI18n from '../../translations';

interface GetBatchItems {
closePopover: () => void;
dispatch: Dispatch<Action>;
dispatchToaster: Dispatch<ActionToaster>;
hasMlPermissions: boolean;
loadingRuleIds: string[];
reFetchRules: (refreshPrePackagedRule?: boolean) => void;
rules: Rule[];
Expand All @@ -31,6 +33,7 @@ export const getBatchItems = ({
closePopover,
dispatch,
dispatchToaster,
hasMlPermissions,
loadingRuleIds,
reFetchRules,
rules,
Expand All @@ -57,7 +60,22 @@ export const getBatchItems = ({
const deactivatedIds = selectedRuleIds.filter(
id => !rules.find(r => r.id === id)?.enabled ?? false
);
await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster);

const deactivatedIdsNoML = deactivatedIds.filter(
id => rules.find(r => r.id === id)?.type !== 'machine_learning' ?? false
);

const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length;
if (!hasMlPermissions && mlRuleCount > 0) {
displayWarningToast(detectionI18n.ML_RULES_UNAVAILABLE(mlRuleCount), dispatchToaster);
}

await enableRulesAction(
hasMlPermissions ? deactivatedIds : deactivatedIdsNoML,
true,
dispatch,
dispatchToaster
);
}}
>
{i18n.BATCH_ACTION_ACTIVATE_SELECTED}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EuiTableActionsColumnType,
EuiText,
EuiHealth,
EuiToolTip,
} from '@elastic/eui';
import { FormattedRelative } from '@kbn/i18n/react';
import * as H from 'history';
Expand All @@ -36,6 +37,7 @@ import {
} from './actions';
import { Action } from './reducer';
import { LocalizedDateTooltip } from '../../../../components/localized_date_tooltip';
import * as detectionI18n from '../../translations';

export const getActions = (
dispatch: React.Dispatch<Action>,
Expand Down Expand Up @@ -88,6 +90,7 @@ interface GetColumns {
dispatch: React.Dispatch<Action>;
dispatchToaster: Dispatch<ActionToaster>;
history: H.History;
hasMlPermissions: boolean;
hasNoPermissions: boolean;
loadingRuleIds: string[];
reFetchRules: (refreshPrePackagedRule?: boolean) => void;
Expand All @@ -98,6 +101,7 @@ export const getColumns = ({
dispatch,
dispatchToaster,
history,
hasMlPermissions,
hasNoPermissions,
loadingRuleIds,
reFetchRules,
Expand Down Expand Up @@ -182,14 +186,23 @@ export const getColumns = ({
field: 'enabled',
name: i18n.COLUMN_ACTIVATE,
render: (value: Rule['enabled'], item: Rule) => (
<RuleSwitch
data-test-subj="enabled"
dispatch={dispatch}
id={item.id}
enabled={item.enabled}
isDisabled={hasNoPermissions}
isLoading={loadingRuleIds.includes(item.id)}
/>
<EuiToolTip
position="top"
content={
item.type === 'machine_learning' && !hasMlPermissions
? detectionI18n.ML_RULES_DISABLED_MESSAGE
: undefined
}
>
<RuleSwitch
data-test-subj="enabled"
dispatch={dispatch}
id={item.id}
enabled={item.enabled}
isDisabled={hasNoPermissions || (item.type === 'machine_learning' && !hasMlPermissions)}
isLoading={loadingRuleIds.includes(item.id)}
/>
</EuiToolTip>
),
sortable: true,
width: '95px',
Expand Down
Loading

0 comments on commit cb23f69

Please sign in to comment.