From 512d4fb6ac9c44fffaec628f4fd2b72f80251c75 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 15 Jan 2020 13:38:18 -0500 Subject: [PATCH 01/21] update extra action on rule detail to match design --- .../rule_actions_overflow/index.tsx | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index 0a823ce545d72..075149ba1a2c1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -12,6 +12,7 @@ import { EuiToolTip, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; +import styled from 'styled-components'; import { noop } from 'lodash/fp'; import { useHistory } from 'react-router-dom'; @@ -23,6 +24,17 @@ import { displaySuccessToast, useStateToaster } from '../../../../../components/ import { RuleDownloader } from '../rule_downloader'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; +const MyEuiButtonIcon = styled(EuiButtonIcon)` + &.euiButtonIcon { + svg { + transform: rotate(90deg); + } + border: 1px solid  ${({ theme }) => theme.euiColorPrimary}; + width: 40px; + height: 40px; + } +`; + interface RuleActionsOverflowComponentProps { rule: Rule | null; userHasNoPermissions: boolean; @@ -86,20 +98,29 @@ const RuleActionsOverflowComponent = ({ [rule, userHasNoPermissions] ); + const handlePopoverOpen = useCallback(() => { + setIsPopoverOpen(!isPopoverOpen); + }, [setIsPopoverOpen, isPopoverOpen]); + + const button = useMemo( + () => ( + + + + ), + [handlePopoverOpen, userHasNoPermissions] + ); + return ( <> - setIsPopoverOpen(!isPopoverOpen)} - /> - - } + button={button} closePopover={() => setIsPopoverOpen(false)} id="ruleActionsOverflow" isOpen={isPopoverOpen} From 88b3ff69e4878a842de9ed3e3a7452cc7ad598e8 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 15 Jan 2020 14:12:20 -0500 Subject: [PATCH 02/21] remove experimental label --- .../siem/public/pages/detection_engine/rules/details/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 099006a34920c..51b3eaaf4ab32 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -238,7 +238,6 @@ const RuleDetailsComponent = memo( href: `#${DETECTION_ENGINE_PAGE_NAME}/rules`, text: i18n.BACK_TO_RULES, }} - badgeOptions={{ text: i18n.EXPERIMENTAL }} border subtitle={subTitle} subtitle2={[ From 6adf429419cbd2351f79f1dc00db47c74bf2eb02 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 15 Jan 2020 16:11:50 -0500 Subject: [PATCH 03/21] allow pre-package to be deleted + do not allow wrong user to create pre-packages rules --- .../rules/use_create_packaged_rules.tsx | 79 +++++++++++++++++++ .../signals/use_signal_index.tsx | 2 - .../components/user_info/index.tsx | 51 +++++++----- .../detection_engine/rules/all/columns.tsx | 73 +++++++++-------- .../rule_actions_overflow/index.tsx | 68 ++++++++-------- 5 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx new file mode 100644 index 0000000000000..a67eab289fa6c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState } from 'react'; + +import { createPrepackagedRules } from './api'; + +type Return = [boolean, boolean | null]; + +interface UseCreatePackagedRules { + canUserCRUD: boolean | null; + hasIndexManage: boolean | null; + hasManageApiKey: boolean | null; + isAuthenticated: boolean | null; + isSignalIndexExists: boolean | null; +} + +/** + * Hook for creating the packages rules + * + * @param hasIndexManage boolean + * @param hasManageApiKey boolean + * @param isAuthenticated boolean + * @param isSignalIndexExists boolean + * + */ +export const useCreatePackagedRules = ({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, +}: UseCreatePackagedRules): Return => { + const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function createRules() { + try { + await createPrepackagedRules({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setHasCreatedPackageRules(true); + } + } catch (error) { + if (isSubscribed) { + setHasCreatedPackageRules(false); + } + } + if (isSubscribed) { + setLoading(false); + } + } + if ( + canUserCRUD && + hasIndexManage && + hasManageApiKey && + isAuthenticated && + isSignalIndexExists + ) { + createRules(); + } + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); + + return [loading, hasCreatedPackageRules]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx index 189d8a1bf3f75..c1ee5fd12b8c1 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx @@ -8,7 +8,6 @@ import { useEffect, useState, useRef } from 'react'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import { useStateToaster } from '../../../components/toasters'; -import { createPrepackagedRules } from '../rules'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; import { PostSignalError, SignalIndexError } from './types'; @@ -41,7 +40,6 @@ export const useSignalIndex = (): Return => { if (isSubscribed && signal != null) { setSignalIndexName(signal.name); setSignalIndexExists(true); - createPrepackagedRules({ signal: abortCtrl.signal }); } } catch (error) { if (isSubscribed) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index bbaccb7882484..329220792474d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -10,6 +10,7 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; import { useKibana } from '../../../../lib/kibana'; +import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules'; export interface State { canUserCRUD: boolean | null; @@ -36,33 +37,33 @@ const initialState: State = { export type Action = | { type: 'updateLoading'; loading: boolean } | { - type: 'updateHasManageApiKey'; - hasManageApiKey: boolean | null; - } + type: 'updateHasManageApiKey'; + hasManageApiKey: boolean | null; + } | { - type: 'updateHasIndexManage'; - hasIndexManage: boolean | null; - } + type: 'updateHasIndexManage'; + hasIndexManage: boolean | null; + } | { - type: 'updateHasIndexWrite'; - hasIndexWrite: boolean | null; - } + type: 'updateHasIndexWrite'; + hasIndexWrite: boolean | null; + } | { - type: 'updateIsSignalIndexExists'; - isSignalIndexExists: boolean | null; - } + type: 'updateIsSignalIndexExists'; + isSignalIndexExists: boolean | null; + } | { - type: 'updateIsAuthenticated'; - isAuthenticated: boolean | null; - } + type: 'updateIsAuthenticated'; + isAuthenticated: boolean | null; + } | { - type: 'updateCanUserCRUD'; - canUserCRUD: boolean | null; - } + type: 'updateCanUserCRUD'; + canUserCRUD: boolean | null; + } | { - type: 'updateSignalIndexName'; - signalIndexName: string | null; - }; + type: 'updateSignalIndexName'; + signalIndexName: string | null; + }; export const userInfoReducer = (state: State, action: Action): State => { switch (action.type) { @@ -161,6 +162,14 @@ export const useUserInfo = (): State => { createSignalIndex, ] = useSignalIndex(); + useCreatePackagedRules({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, + }); + const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 91b018eb3078f..129deb6c7c86e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -36,35 +36,34 @@ const getActions = ( dispatchToaster: Dispatch, history: H.History ) => [ - { - description: i18n.EDIT_RULE_SETTINGS, - icon: 'visControls', - name: i18n.EDIT_RULE_SETTINGS, - onClick: (rowItem: TableData) => editRuleAction(rowItem.sourceRule, history), - enabled: (rowItem: TableData) => !rowItem.sourceRule.immutable, - }, - { - description: i18n.DUPLICATE_RULE, - icon: 'copy', - name: i18n.DUPLICATE_RULE, - onClick: (rowItem: TableData) => - duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), - }, - { - description: i18n.EXPORT_RULE, - icon: 'exportAction', - name: i18n.EXPORT_RULE, - onClick: (rowItem: TableData) => exportRulesAction([rowItem.sourceRule], dispatch), - enabled: (rowItem: TableData) => !rowItem.immutable, - }, - { - description: i18n.DELETE_RULE, - icon: 'trash', - name: i18n.DELETE_RULE, - onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), - enabled: (rowItem: TableData) => !rowItem.immutable, - }, -]; + { + description: i18n.EDIT_RULE_SETTINGS, + icon: 'visControls', + name: i18n.EDIT_RULE_SETTINGS, + onClick: (rowItem: TableData) => editRuleAction(rowItem.sourceRule, history), + enabled: (rowItem: TableData) => !rowItem.sourceRule.immutable, + }, + { + description: i18n.DUPLICATE_RULE, + icon: 'copy', + name: i18n.DUPLICATE_RULE, + onClick: (rowItem: TableData) => + duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), + }, + { + description: i18n.EXPORT_RULE, + icon: 'exportAction', + name: i18n.EXPORT_RULE, + onClick: (rowItem: TableData) => exportRulesAction([rowItem.sourceRule], dispatch), + enabled: (rowItem: TableData) => !rowItem.immutable, + }, + { + description: i18n.DELETE_RULE, + icon: 'trash', + name: i18n.DELETE_RULE, + onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), + }, + ]; type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -103,8 +102,8 @@ export const getColumns = ( return value == null ? ( getEmptyTagValue() ) : ( - - ); + + ); }, sortable: true, truncateText: true, @@ -118,12 +117,12 @@ export const getColumns = ( value == null ? 'subdued' : value === 'succeeded' - ? 'success' - : value === 'failed' - ? 'danger' - : value === 'executing' - ? 'warning' - : 'subdued'; + ? 'success' + : value === 'failed' + ? 'danger' + : value === 'executing' + ? 'warning' + : 'subdued'; return ( <> {value ?? getEmptyTagValue()} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index 075149ba1a2c1..9f6435bcaee18 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -60,40 +60,40 @@ const RuleActionsOverflowComponent = ({ () => rule != null ? [ - { - setIsPopoverOpen(false); - await duplicateRuleAction(rule, noop, dispatchToaster); - }} - > - {i18nActions.DUPLICATE_RULE} - , - { - setIsPopoverOpen(false); - setRulesToExport([rule]); - }} - > - {i18nActions.EXPORT_RULE} - , - { - setIsPopoverOpen(false); - await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); - }} - > - {i18nActions.DELETE_RULE} - , - ] + { + setIsPopoverOpen(false); + await duplicateRuleAction(rule, noop, dispatchToaster); + }} + > + {i18nActions.DUPLICATE_RULE} + , + { + setIsPopoverOpen(false); + setRulesToExport([rule]); + }} + > + {i18nActions.EXPORT_RULE} + , + { + setIsPopoverOpen(false); + await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); + }} + > + {i18nActions.DELETE_RULE} + , + ] : [], [rule, userHasNoPermissions] ); From 3084d05727efbff239a8dc3d76f2d7eb2264b795 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 15 Jan 2020 16:39:45 -0500 Subject: [PATCH 04/21] Additional look back minimum value to 1 --- .../components/user_info/index.tsx | 42 +++---- .../detection_engine/rules/all/columns.tsx | 72 +++++------ .../rule_actions_overflow/index.tsx | 68 +++++----- .../components/schedule_item_form/index.tsx | 8 +- .../components/step_schedule_rule/index.tsx | 118 +++++++++--------- 5 files changed, 157 insertions(+), 151 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index 329220792474d..24e14473d40e9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -37,33 +37,33 @@ const initialState: State = { export type Action = | { type: 'updateLoading'; loading: boolean } | { - type: 'updateHasManageApiKey'; - hasManageApiKey: boolean | null; - } + type: 'updateHasManageApiKey'; + hasManageApiKey: boolean | null; + } | { - type: 'updateHasIndexManage'; - hasIndexManage: boolean | null; - } + type: 'updateHasIndexManage'; + hasIndexManage: boolean | null; + } | { - type: 'updateHasIndexWrite'; - hasIndexWrite: boolean | null; - } + type: 'updateHasIndexWrite'; + hasIndexWrite: boolean | null; + } | { - type: 'updateIsSignalIndexExists'; - isSignalIndexExists: boolean | null; - } + type: 'updateIsSignalIndexExists'; + isSignalIndexExists: boolean | null; + } | { - type: 'updateIsAuthenticated'; - isAuthenticated: boolean | null; - } + type: 'updateIsAuthenticated'; + isAuthenticated: boolean | null; + } | { - type: 'updateCanUserCRUD'; - canUserCRUD: boolean | null; - } + type: 'updateCanUserCRUD'; + canUserCRUD: boolean | null; + } | { - type: 'updateSignalIndexName'; - signalIndexName: string | null; - }; + type: 'updateSignalIndexName'; + signalIndexName: string | null; + }; export const userInfoReducer = (state: State, action: Action): State => { switch (action.type) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 129deb6c7c86e..b09e0f582fa90 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -36,34 +36,34 @@ const getActions = ( dispatchToaster: Dispatch, history: H.History ) => [ - { - description: i18n.EDIT_RULE_SETTINGS, - icon: 'visControls', - name: i18n.EDIT_RULE_SETTINGS, - onClick: (rowItem: TableData) => editRuleAction(rowItem.sourceRule, history), - enabled: (rowItem: TableData) => !rowItem.sourceRule.immutable, - }, - { - description: i18n.DUPLICATE_RULE, - icon: 'copy', - name: i18n.DUPLICATE_RULE, - onClick: (rowItem: TableData) => - duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), - }, - { - description: i18n.EXPORT_RULE, - icon: 'exportAction', - name: i18n.EXPORT_RULE, - onClick: (rowItem: TableData) => exportRulesAction([rowItem.sourceRule], dispatch), - enabled: (rowItem: TableData) => !rowItem.immutable, - }, - { - description: i18n.DELETE_RULE, - icon: 'trash', - name: i18n.DELETE_RULE, - onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), - }, - ]; + { + description: i18n.EDIT_RULE_SETTINGS, + icon: 'visControls', + name: i18n.EDIT_RULE_SETTINGS, + onClick: (rowItem: TableData) => editRuleAction(rowItem.sourceRule, history), + enabled: (rowItem: TableData) => !rowItem.sourceRule.immutable, + }, + { + description: i18n.DUPLICATE_RULE, + icon: 'copy', + name: i18n.DUPLICATE_RULE, + onClick: (rowItem: TableData) => + duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), + }, + { + description: i18n.EXPORT_RULE, + icon: 'exportAction', + name: i18n.EXPORT_RULE, + onClick: (rowItem: TableData) => exportRulesAction([rowItem.sourceRule], dispatch), + enabled: (rowItem: TableData) => !rowItem.immutable, + }, + { + description: i18n.DELETE_RULE, + icon: 'trash', + name: i18n.DELETE_RULE, + onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), + }, +]; type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -102,8 +102,8 @@ export const getColumns = ( return value == null ? ( getEmptyTagValue() ) : ( - - ); + + ); }, sortable: true, truncateText: true, @@ -117,12 +117,12 @@ export const getColumns = ( value == null ? 'subdued' : value === 'succeeded' - ? 'success' - : value === 'failed' - ? 'danger' - : value === 'executing' - ? 'warning' - : 'subdued'; + ? 'success' + : value === 'failed' + ? 'danger' + : value === 'executing' + ? 'warning' + : 'subdued'; return ( <> {value ?? getEmptyTagValue()} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index 9f6435bcaee18..5fe4856ef53ff 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -60,40 +60,40 @@ const RuleActionsOverflowComponent = ({ () => rule != null ? [ - { - setIsPopoverOpen(false); - await duplicateRuleAction(rule, noop, dispatchToaster); - }} - > - {i18nActions.DUPLICATE_RULE} - , - { - setIsPopoverOpen(false); - setRulesToExport([rule]); - }} - > - {i18nActions.EXPORT_RULE} - , - { - setIsPopoverOpen(false); - await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); - }} - > - {i18nActions.DELETE_RULE} - , - ] + { + setIsPopoverOpen(false); + await duplicateRuleAction(rule, noop, dispatchToaster); + }} + > + {i18nActions.DUPLICATE_RULE} + , + { + setIsPopoverOpen(false); + setRulesToExport([rule]); + }} + > + {i18nActions.EXPORT_RULE} + , + { + setIsPopoverOpen(false); + await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); + }} + > + {i18nActions.DELETE_RULE} + , + ] : [], [rule, userHasNoPermissions] ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx index 0ef104e6891df..3bde2087f26b1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx @@ -150,7 +150,13 @@ export const ScheduleItem = ({ /> } > - + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx index 92ca83b4a89d9..44a60f4360aba 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx @@ -24,7 +24,7 @@ const stepScheduleDefaultValue = { enabled: true, interval: '5m', isNew: true, - from: '0m', + from: '1m', }; const StepScheduleRuleComponent: FC = ({ @@ -89,65 +89,65 @@ const StepScheduleRuleComponent: FC = ({ ) : ( - <> - -
- - - -
+ <> + +
+ + + +
- {!isUpdateView && ( - <> - - - - - {I18n.COMPLETE_WITHOUT_ACTIVATING} - - - - - {I18n.COMPLETE_WITH_ACTIVATING} - - - - - )} - - ); + {!isUpdateView && ( + <> + + + + + {I18n.COMPLETE_WITHOUT_ACTIVATING} + + + + + {I18n.COMPLETE_WITH_ACTIVATING} + + + + + )} + + ); }; export const StepScheduleRule = memo(StepScheduleRuleComponent); From 578bc79a4c0ae9d4d79d4b6cc43ff870031f86bb Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 15 Jan 2020 20:15:38 -0500 Subject: [PATCH 05/21] fix flow with edit rule --- .../components/step_about_rule/index.tsx | 14 +- .../components/step_define_rule/index.tsx | 16 +-- .../components/step_schedule_rule/index.tsx | 130 +++++++++--------- .../detection_engine/rules/create/index.tsx | 17 ++- 4 files changed, 91 insertions(+), 86 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index 0e03a11776fb7..1ec37b91df860 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -71,14 +71,12 @@ const StepAboutRuleComponent: FC = ({ isNew: false, }; setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } }, [defaultValues]); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx index 6bdef4a69af1e..a40752975dc5a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx @@ -121,14 +121,12 @@ const StepDefineRuleComponent: FC = ({ if (!isEqual(myDefaultValues, myStepData)) { setMyStepData(myDefaultValues); setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig)); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } } }, [defaultValues, indicesConfig]); @@ -152,7 +150,7 @@ const StepDefineRuleComponent: FC = ({ setOpenTimelineSearch(false); }, []); - return isReadOnlyView && myStepData != null ? ( + return isReadOnlyView && myStepData?.queryBar != null ? ( = ({ isNew: false, }; setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } }, [defaultValues]); @@ -89,65 +87,65 @@ const StepScheduleRuleComponent: FC = ({ ) : ( - <> - -
- - - -
+ <> + +
+ + + +
- {!isUpdateView && ( - <> - - - - - {I18n.COMPLETE_WITHOUT_ACTIVATING} - - - - - {I18n.COMPLETE_WITH_ACTIVATING} - - - - - )} - - ); + {!isUpdateView && ( + <> + + + + + {I18n.COMPLETE_WITHOUT_ACTIVATING} + + + + + {I18n.COMPLETE_WITH_ACTIVATING} + + + + + )} + + ); }; export const StepScheduleRule = memo(StepScheduleRuleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index e5656f5b081fb..47d1628b1cbdc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -95,6 +95,7 @@ export const CreateRuleComponent = React.memo(() => { const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); if ([0, 1].includes(stepRuleIdx)) { if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) { + setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); setIsStepRuleInEditView({ ...isStepRuleInReadOnlyView, [step]: true, @@ -203,12 +204,15 @@ export const CreateRuleComponent = React.memo(() => { async (id: RuleStep) => { const activeForm = await stepsForm.current[openAccordionId]?.submit(); if (activeForm != null && activeForm?.isValid) { + stepsData.current[openAccordionId] = { + ...stepsData.current[openAccordionId], + data: activeForm.data, + isValid: activeForm.isValid, + }; setOpenAccordionId(id); - openCloseAccordion(openAccordionId); - setIsStepRuleInEditView({ ...isStepRuleInReadOnlyView, - [openAccordionId]: openAccordionId === RuleStep.scheduleRule ? false : true, + [openAccordionId]: true, [id]: false, }); } @@ -252,6 +256,9 @@ export const CreateRuleComponent = React.memo(() => { { { Date: Thu, 16 Jan 2020 09:44:10 -0500 Subject: [PATCH 06/21] add success toaster when rule is created or updated --- .../rules/components/rule_actions_overflow/index.tsx | 2 +- .../rules/components/step_about_rule/default_value.ts | 3 ++- .../rules/components/step_about_rule/index.tsx | 2 +- .../public/pages/detection_engine/rules/create/index.tsx | 6 +++++- .../pages/detection_engine/rules/create/translations.ts | 6 ++++++ .../public/pages/detection_engine/rules/edit/index.tsx | 7 +++++-- .../pages/detection_engine/rules/edit/translations.ts | 6 ++++++ 7 files changed, 26 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index 5fe4856ef53ff..583f3399bf06b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -19,8 +19,8 @@ import { useHistory } from 'react-router-dom'; import { Rule } from '../../../../../containers/detection_engine/rules'; import * as i18n from './translations'; import * as i18nActions from '../../../rules/translations'; -import { deleteRulesAction, duplicateRuleAction } from '../../all/actions'; import { displaySuccessToast, useStateToaster } from '../../../../../components/toasters'; +import { deleteRulesAction, duplicateRuleAction } from '../../all/actions'; import { RuleDownloader } from '../rule_downloader'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts index 328c4a0f96066..92aca1cecf9f3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts @@ -5,6 +5,7 @@ */ import { AboutStepRule } from '../../types'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/search_super_select/translations'; export const threatsDefault = [ { @@ -25,7 +26,7 @@ export const stepAboutDefaultValue: AboutStepRule = { tags: [], timeline: { id: null, - title: null, + title: DEFAULT_TIMELINE_TITLE, }, threats: threatsDefault, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index 1ec37b91df860..c25613103e34c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -86,7 +86,7 @@ const StepAboutRuleComponent: FC = ({ } }, [form]); - return isReadOnlyView && myStepData != null ? ( + return isReadOnlyView && myStepData.name != null ? ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index 47d1628b1cbdc..92aa533224b8a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -9,10 +9,11 @@ import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; import styled from 'styled-components'; +import { usePersistRule } from '../../../../containers/detection_engine/rules'; import { HeaderPage } from '../../../../components/header_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { WrapperPage } from '../../../../components/wrapper_page'; -import { usePersistRule } from '../../../../containers/detection_engine/rules'; +import { displaySuccessToast, useStateToaster } from '../../../../components/toasters'; import { SpyRoute } from '../../../../utils/route/spy_routes'; import { useUserInfo } from '../../components/user_info'; import { AccordionTitle } from '../components/accordion_title'; @@ -55,6 +56,7 @@ export const CreateRuleComponent = React.memo(() => { canUserCRUD, hasManageApiKey, } = useUserInfo(); + const [, dispatchToaster] = useStateToaster(); const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); @@ -221,6 +223,8 @@ export const CreateRuleComponent = React.memo(() => { ); if (isSaved) { + const ruleName = (stepsData.current[RuleStep.aboutRule].data as AboutStepRule).name; + displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster); return ; } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts index 329bcc286fb70..a2267e83519d5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts @@ -13,3 +13,9 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule. export const EDIT_RULE = i18n.translate('xpack.siem.detectionEngine.createRule.editRuleButton', { defaultMessage: 'Edit', }); + +export const SUCCESSFULLY_CREATED_RULES = (ruleName: string) => + i18n.translate('xpack.siem.detectionEngine.rules.create.successfullyRuleCreatedTitle', { + values: { ruleName }, + defaultMessage: '{ruleName} was created', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index e583461f52439..9b7833afd7f4d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -17,11 +17,12 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Redirect, useParams } from 'react-router-dom'; +import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; import { HeaderPage } from '../../../../components/header_page'; import { WrapperPage } from '../../../../components/wrapper_page'; -import { SpyRoute } from '../../../../utils/route/spy_routes'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; -import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; +import { displaySuccessToast, useStateToaster } from '../../../../components/toasters'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; import { useUserInfo } from '../../components/user_info'; import { FormHook, FormData } from '../components/shared_imports'; import { StepPanel } from '../components/step_panel'; @@ -48,6 +49,7 @@ interface ScheduleStepRuleForm extends StepRuleForm { } export const EditRuleComponent = memo(() => { + const [, dispatchToaster] = useStateToaster(); const { loading: initLoading, isSignalIndexExists, @@ -271,6 +273,7 @@ export const EditRuleComponent = memo(() => { }, []); if (isSaved || (rule != null && rule.immutable)) { + displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); return ; } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts index b81ae58e565f0..1988923a75281 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts @@ -28,3 +28,9 @@ export const SORRY_ERRORS = i18n.translate( export const BACK_TO = i18n.translate('xpack.siem.detectionEngine.editRule.backToDescription', { defaultMessage: 'Back to', }); + +export const SUCCESSFULLY_SAVED_RULE = (ruleName: string) => + i18n.translate('xpack.siem.detectionEngine.rules.update.successfullyRuleSavedTitle', { + values: { ruleName }, + defaultMessage: '{ruleName} was saved', + }); From 3af55811fccbe724a6061a56f5560dc694c54b5e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 16 Jan 2020 10:48:57 -0500 Subject: [PATCH 07/21] Fix Timeline selector loading --- .../timeline/search_super_select/index.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx index c7259edbdc593..009ab141e958e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx @@ -45,11 +45,25 @@ const MyEuiFlexItem = styled(EuiFlexItem)` white-space: nowrap; `; -const EuiSelectableContainer = styled.div` +const EuiSelectableContainer = styled.div<{ loading: boolean }>` .euiSelectable { .euiFormControlLayout__childrenWrapper { display: flex; } + ${({ loading }) => `${ + loading + ? ` + .euiFormControlLayoutIcons { + display: none; + } + .euiFormControlLayoutIcons.euiFormControlLayoutIcons--right { + display: block; + left: 12px; + top: 12px; + }` + : '' + } + `} } `; @@ -265,7 +279,7 @@ const SearchTimelineSuperSelectComponent: React.FC {({ timelines, loading, totalCount }) => ( - + Date: Thu, 16 Jan 2020 11:31:43 -0500 Subject: [PATCH 08/21] review ben doc + change detectin engine to detection even in url --- .../link_to/redirect_to_detection_engine.tsx | 2 +- .../components/navigation/index.test.tsx | 16 +++---- .../public/components/url_state/constants.ts | 4 +- .../public/components/url_state/helpers.ts | 2 +- .../siem/public/components/url_state/types.ts | 2 +- .../components/activity_monitor/index.tsx | 42 +++++++++---------- .../signals_histogram_panel/translations.ts | 2 +- .../detection_engine/detection_engine.tsx | 2 +- .../public/pages/detection_engine/index.tsx | 6 +-- .../rules/all/__mocks__/mock.ts | 4 +- .../detection_engine/rules/all/columns.tsx | 4 +- .../detection_engine/rules/all/helpers.ts | 2 +- .../components/step_schedule_rule/schema.tsx | 9 ++-- .../step_schedule_rule/translations.tsx | 4 +- .../detection_engine/rules/create/index.tsx | 2 +- .../detection_engine/rules/translations.ts | 6 +-- .../pages/detection_engine/translations.ts | 2 +- .../public/pages/home/home_navigations.tsx | 2 +- .../siem/public/pages/home/translations.ts | 2 +- .../plugins/siem/public/pages/home/types.ts | 2 +- 20 files changed, 58 insertions(+), 59 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx index 74aec076ec4d5..a3189833aa399 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx @@ -13,7 +13,7 @@ export type DetectionEngineComponentProps = RouteComponentProps<{ search: string; }>; -export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine'; +export const DETECTION_ENGINE_PAGE_NAME = 'detections'; export const RedirectToDetectionEnginePage = ({ location: { search }, diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index cae209a76fc1c..58f8dcd9bcebb 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -64,12 +64,12 @@ describe('SIEM Navigation', () => { expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, { detailName: undefined, navTabs: { - 'detection-engine': { + detections: { disabled: false, - href: '#/link-to/detection-engine', - id: 'detection-engine', + href: '#/link-to/detections', + id: 'detections', name: 'Detection engine', - urlKey: 'detection-engine', + urlKey: 'detections', }, hosts: { disabled: false, @@ -146,12 +146,12 @@ describe('SIEM Navigation', () => { detailName: undefined, filters: [], navTabs: { - 'detection-engine': { + detections: { disabled: false, - href: '#/link-to/detection-engine', - id: 'detection-engine', + href: '#/link-to/detections', + id: 'detections', name: 'Detection engine', - urlKey: 'detection-engine', + urlKey: 'detections', }, hosts: { disabled: false, diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts b/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts index 2e700e3e23b64..22e8f99658f8d 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts @@ -6,7 +6,7 @@ export enum CONSTANTS { appQuery = 'query', - detectionEnginePage = 'detectionEngine.page', + detectionsPage = 'detections.page', filters = 'filters', hostsDetails = 'hosts.details', hostsPage = 'hosts.page', @@ -20,4 +20,4 @@ export enum CONSTANTS { unknown = 'unknown', } -export type UrlStateType = 'detection-engine' | 'host' | 'network' | 'overview' | 'timeline'; +export type UrlStateType = 'detections' | 'host' | 'network' | 'overview' | 'timeline'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts index aa340b54c1699..bf1f8c824a175 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts @@ -79,7 +79,7 @@ export const getUrlType = (pageName: string): UrlStateType => { } else if (pageName === SiemPageName.network) { return 'network'; } else if (pageName === SiemPageName.detectionEngine) { - return 'detection-engine'; + return 'detections'; } else if (pageName === SiemPageName.timelines) { return 'timeline'; } diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index a48653a7ea6f4..657a1a55a268a 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -24,7 +24,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ ]; export const URL_STATE_KEYS: Record = { - 'detection-engine': [ + detections: [ CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx index 8290da1ba3220..5f017a3a1f67f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx @@ -45,7 +45,7 @@ export const ActivityMonitor = React.memo(() => { { id: 1, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -55,7 +55,7 @@ export const ActivityMonitor = React.memo(() => { { id: 2, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -65,7 +65,7 @@ export const ActivityMonitor = React.memo(() => { { id: 3, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -76,7 +76,7 @@ export const ActivityMonitor = React.memo(() => { { id: 4, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -87,7 +87,7 @@ export const ActivityMonitor = React.memo(() => { { id: 5, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -98,7 +98,7 @@ export const ActivityMonitor = React.memo(() => { { id: 6, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -109,7 +109,7 @@ export const ActivityMonitor = React.memo(() => { { id: 7, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -120,7 +120,7 @@ export const ActivityMonitor = React.memo(() => { { id: 8, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -131,7 +131,7 @@ export const ActivityMonitor = React.memo(() => { { id: 9, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -142,7 +142,7 @@ export const ActivityMonitor = React.memo(() => { { id: 10, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -153,7 +153,7 @@ export const ActivityMonitor = React.memo(() => { { id: 11, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -164,7 +164,7 @@ export const ActivityMonitor = React.memo(() => { { id: 12, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -175,7 +175,7 @@ export const ActivityMonitor = React.memo(() => { { id: 13, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -186,7 +186,7 @@ export const ActivityMonitor = React.memo(() => { { id: 14, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -197,7 +197,7 @@ export const ActivityMonitor = React.memo(() => { { id: 15, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -208,7 +208,7 @@ export const ActivityMonitor = React.memo(() => { { id: 16, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -219,7 +219,7 @@ export const ActivityMonitor = React.memo(() => { { id: 17, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -230,7 +230,7 @@ export const ActivityMonitor = React.memo(() => { { id: 18, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -241,7 +241,7 @@ export const ActivityMonitor = React.memo(() => { { id: 19, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -252,7 +252,7 @@ export const ActivityMonitor = React.memo(() => { { id: 20, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -263,7 +263,7 @@ export const ActivityMonitor = React.memo(() => { { id: 21, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts index 0245b9968cc36..8c88fa4a5dae6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts @@ -86,7 +86,7 @@ export const STACK_BY_USERS = i18n.translate( export const HISTOGRAM_HEADER = i18n.translate( 'xpack.siem.detectionEngine.signals.histogram.headerTitle', { - defaultMessage: 'Signal detection frequency', + defaultMessage: 'Signal count', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 388f667f47fe1..b970b53a76b56 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -111,7 +111,7 @@ const DetectionEngineComponent = React.memo( } title={i18n.PAGE_TITLE} > - + {i18n.BUTTON_MANAGE_RULES} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index c4e83429aebdb..67a2064cb1670 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -14,7 +14,7 @@ import { RuleDetails } from './rules/details'; import { RulesComponent } from './rules'; import { ManageUserInfo } from './components/user_info'; -const detectionEnginePath = `/:pageName(detection-engine)`; +const detectionEnginePath = `/:pageName(detections)`; type Props = Partial> & { url: string }; @@ -37,9 +37,9 @@ export const DetectionEngineContainer = React.memo(() => ( ( - + )} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts index 757c1fabfc9cd..b79b3ed091f16 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -60,7 +60,7 @@ export const mockTableData: TableData[] = [ lastResponse: { type: '—' }, method: 'saved_query', rule: { - href: '#/detection-engine/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', + href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', name: 'Home Grown!', status: 'Status Placeholder', }, @@ -112,7 +112,7 @@ export const mockTableData: TableData[] = [ lastResponse: { type: '—' }, method: 'saved_query', rule: { - href: '#/detection-engine/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', + href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', name: 'Home Grown!', status: 'Status Placeholder', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index b09e0f582fa90..39ef9483cb69b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -86,7 +86,7 @@ export const getColumns = ( field: 'method', name: i18n.COLUMN_METHOD, truncateText: true, - width: '16%', + width: '14%', }, { field: 'severity', @@ -161,7 +161,7 @@ export const getColumns = ( /> ), sortable: true, - width: '85px', + width: '95px', }, ]; const actions: RulesColumns[] = [ diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index 9666b7a5688cf..07a2f2f278987 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -24,7 +24,7 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] immutable: rule.immutable, rule_id: rule.rule_id, rule: { - href: `#/detection-engine/rules/id/${encodeURIComponent(rule.id)}`, + href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`, name: rule.name, status: 'Status Placeholder', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx index 4da17b88b9ad0..2aae772c73bc0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx @@ -14,13 +14,13 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalLabel', { - defaultMessage: 'Rule run interval & look-back', + defaultMessage: 'Runs every', } ), helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText', { - defaultMessage: 'How often and how far back this rule will search specified indices.', + defaultMessage: 'Rules run periodically and detect signal within the specified time frame.', } ), }, @@ -28,15 +28,14 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackLabel', { - defaultMessage: 'Additional look-back', + defaultMessage: 'Additional look-back time', } ), labelAppend: OptionalFieldLabel, helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { - defaultMessage: - 'Add more time to the look-back range in order to prevent potential gaps in signal reporting.', + defaultMessage: 'Adds time to the look-back period to prevent missed signals.', } ), }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx index feaaf4e85b2af..67bcc1af8150b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx @@ -9,13 +9,13 @@ import { i18n } from '@kbn/i18n'; export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate( 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle', { - defaultMessage: 'Complete rule without activating', + defaultMessage: 'Create rule without activating it', } ); export const COMPLETE_WITH_ACTIVATING = i18n.translate( 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle', { - defaultMessage: 'Complete rule & activate', + defaultMessage: 'Create & activate rule', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index 92aa533224b8a..ed40e7d983be6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -232,7 +232,7 @@ export const CreateRuleComponent = React.memo(() => { <> Date: Thu, 16 Jan 2020 11:38:06 -0500 Subject: [PATCH 09/21] Succeeded text size consistency in rule details page --- .../siem/public/pages/detection_engine/rules/details/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 51b3eaaf4ab32..5539654d6b357 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -12,6 +12,7 @@ import { EuiSpacer, EuiHealth, EuiTab, + EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo, useCallback, useMemo, useState } from 'react'; @@ -261,7 +262,7 @@ const RuleDetailsComponent = memo( - {rule?.status ?? getEmptyTagValue()} + {rule?.status ?? getEmptyTagValue()} {rule?.status_date && ( From 5c34050cced5f93bdfb55298dc07095cb6b5da02 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 16 Jan 2020 11:56:21 -0500 Subject: [PATCH 10/21] fix description of threats --- .../rules/components/description_step/helpers.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx index e8b6919165c8b..98fb32d67f9f8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx @@ -63,8 +63,8 @@ export const buildQueryBarDescription = ({ valueLabel={esFilters.getDisplayValueFromFilter(filter, [indexPatterns])} /> ) : ( - - )} + + )} ))} @@ -125,7 +125,7 @@ export const buildThreatsDescription = ({ description: ( {threats.map((threat, index) => { - const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); + const tactic = tacticsOptions.find(t => t.id === threat.tactic.id); return ( @@ -133,7 +133,7 @@ export const buildThreatsDescription = ({ {threat.techniques.map(technique => { - const myTechnique = techniquesOptions.find(t => t.name === technique.name); + const myTechnique = techniquesOptions.find(t => t.id === technique.id); return ( Date: Thu, 16 Jan 2020 13:30:23 -0500 Subject: [PATCH 11/21] fix test --- .../plugins/siem/public/components/navigation/index.test.tsx | 4 ++-- .../rules/components/description_step/helpers.tsx | 4 ++-- .../rule_actions_overflow/__snapshots__/index.test.tsx.snap | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index 58f8dcd9bcebb..b6efc07ad8fe3 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -68,7 +68,7 @@ describe('SIEM Navigation', () => { disabled: false, href: '#/link-to/detections', id: 'detections', - name: 'Detection engine', + name: 'Detections', urlKey: 'detections', }, hosts: { @@ -150,7 +150,7 @@ describe('SIEM Navigation', () => { disabled: false, href: '#/link-to/detections', id: 'detections', - name: 'Detection engine', + name: 'Detections', urlKey: 'detections', }, hosts: { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx index 98fb32d67f9f8..011c008c5b2d2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx @@ -63,8 +63,8 @@ export const buildQueryBarDescription = ({ valueLabel={esFilters.getDisplayValueFromFilter(filter, [indexPatterns])} /> ) : ( - - )} + + )} ))} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap index b981720d4fac0..9bd2fab23ac99 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap @@ -10,7 +10,7 @@ exports[`RuleActionsOverflow renders correctly against snapshot 1`] = ` delay="regular" position="top" > - Date: Thu, 16 Jan 2020 20:38:05 -0500 Subject: [PATCH 12/21] fix type --- .../siem/public/components/header_global/index.tsx | 2 +- .../plugins/siem/public/components/link_to/link_to.tsx | 10 +++++----- .../siem/public/components/url_state/helpers.ts | 9 +++++---- .../plugins/siem/public/components/url_state/types.ts | 2 +- .../siem/public/pages/home/home_navigations.tsx | 4 ++-- x-pack/legacy/plugins/siem/public/pages/home/index.tsx | 2 +- x-pack/legacy/plugins/siem/public/pages/home/types.ts | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx index db6ff7cf55f92..5a286532fabfc 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -55,7 +55,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine display="condensed" navTabs={ hideDetectionEngine - ? pickBy((_, key) => key !== SiemPageName.detectionEngine, navTabs) + ? pickBy((_, key) => key !== SiemPageName.detections, navTabs) : navTabs } /> diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 7c15af3fe642a..3d7d35c6484a4 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -60,26 +60,26 @@ export const LinkToPage = React.memo(({ match }) => ( { return 'host'; } else if (pageName === SiemPageName.network) { return 'network'; - } else if (pageName === SiemPageName.detectionEngine) { + } else if (pageName === SiemPageName.detections) { return 'detections'; } else if (pageName === SiemPageName.timelines) { return 'timeline'; @@ -111,8 +111,8 @@ export const getCurrentLocation = ( return CONSTANTS.networkDetails; } return CONSTANTS.networkPage; - } else if (pageName === SiemPageName.detectionEngine) { - return CONSTANTS.detectionEnginePage; + } else if (pageName === SiemPageName.detections) { + return CONSTANTS.detectionsPage; } else if (pageName === SiemPageName.timelines) { return CONSTANTS.timelinePage; } @@ -129,7 +129,8 @@ export const isKqlForRoute = ( (currentLocation === CONSTANTS.hostsPage && queryLocation === CONSTANTS.hostsPage) || (currentLocation === CONSTANTS.networkPage && queryLocation === CONSTANTS.networkPage) || (currentLocation === CONSTANTS.hostsDetails && queryLocation === CONSTANTS.hostsDetails) || - (currentLocation === CONSTANTS.networkDetails && queryLocation === CONSTANTS.networkDetails) + (currentLocation === CONSTANTS.networkDetails && queryLocation === CONSTANTS.networkDetails) || + (currentLocation === CONSTANTS.detectionsPage && queryLocation === CONSTANTS.detectionsPage) ) { return true; } diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index 657a1a55a268a..be1ae1ad63bd4 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -56,7 +56,7 @@ export const URL_STATE_KEYS: Record = { }; export type LocationTypes = - | CONSTANTS.detectionEnginePage + | CONSTANTS.detectionsPage | CONSTANTS.hostsDetails | CONSTANTS.hostsPage | CONSTANTS.networkDetails diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx index 67762979d9c90..c0e959c5e97fa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx @@ -36,8 +36,8 @@ export const navTabs: SiemNavTab = { disabled: false, urlKey: 'network', }, - [SiemPageName.detectionEngine]: { - id: SiemPageName.detectionEngine, + [SiemPageName.detections]: { + id: SiemPageName.detections, name: i18n.DETECTION_ENGINE, href: getDetectionEngineUrl(), disabled: false, diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx index a545be447796d..b5bfdbde306ca 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx @@ -105,7 +105,7 @@ export const HomePage: React.FC = () => ( )} /> ( )} diff --git a/x-pack/legacy/plugins/siem/public/pages/home/types.ts b/x-pack/legacy/plugins/siem/public/pages/home/types.ts index 78027b197aefb..678de6dbcc128 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/home/types.ts @@ -10,7 +10,7 @@ export enum SiemPageName { overview = 'overview', hosts = 'hosts', network = 'network', - detectionEngine = 'detections', + detections = 'detections', timelines = 'timelines', } @@ -18,7 +18,7 @@ export type SiemNavTabKey = | SiemPageName.overview | SiemPageName.hosts | SiemPageName.network - | SiemPageName.detectionEngine + | SiemPageName.detections | SiemPageName.timelines; export type SiemNavTab = Record; From a7c5dbfc7fd84557e504eadf227755e8818f9115 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 17 Jan 2020 08:24:26 -0500 Subject: [PATCH 13/21] fix internatinalization --- .../plugins/siem/public/pages/detection_engine/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index 594931758755c..512dc7d30f0e0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', { defaultMessage: 'Detections', }); From 593ae78ff0969e519197b2aa20d2acc348c1e4ec Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 20 Jan 2020 19:05:20 -0500 Subject: [PATCH 14/21] adding pre-packaged rules --- .../legacy/plugins/siem/common/constants.ts | 3 +- .../containers/detection_engine/rules/api.ts | 39 +- .../detection_engine/rules/index.ts | 1 + .../detection_engine/rules/translations.ts | 14 + .../rules/use_create_packaged_rules.tsx | 79 ---- .../rules/use_pre_packaged_rules.tsx | 131 ++++++ .../components/user_info/index.tsx | 9 - .../detection_engine/rules/all/index.tsx | 417 ++++++++++-------- .../pre_packaged_rules/load_empty_prompt.tsx | 65 +++ .../pre_packaged_rules/translations.ts | 57 +++ .../pre_packaged_rules/update_callout.tsx | 29 ++ .../detection_engine/rules/details/index.tsx | 2 +- .../pages/detection_engine/rules/helpers.tsx | 42 ++ .../pages/detection_engine/rules/index.tsx | 70 ++- .../detection_engine/rules/translations.ts | 14 + 15 files changed, 686 insertions(+), 286 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index e67b533e46bce..f2ad9700b7f78 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -60,7 +60,8 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`; -export const DETECTION_ENGINE_RULES_STATUS = `${DETECTION_ENGINE_URL}/rules/_find_statuses`; +export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_URL}/rules/_find_statuses`; +export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_URL}/rules/prepackaged/_status`; /** * Default signals index key for kibana.dev.yml diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 1e621363f5f29..fcc0842d3fe4e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -26,7 +26,8 @@ import { throwIfNotOk } from '../../../hooks/api/api'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_RULES_STATUS, + DETECTION_ENGINE_RULES_STATUS_URL, + DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, } from '../../../../common/constants'; import * as i18n from '../../../pages/detection_engine/rules/translations'; @@ -313,6 +314,7 @@ export const exportRules = async ({ * Get Rule Status provided Rule ID * * @param id string of Rule ID's (not rule_id) + * @param signal AbortSignal for cancelling request * * @throws An error if response is not OK */ @@ -324,7 +326,7 @@ export const getRuleStatusById = async ({ signal: AbortSignal; }): Promise> => { const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS}?ids=${encodeURIComponent( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS_URL}?ids=${encodeURIComponent( JSON.stringify([id]) )}`, { @@ -341,3 +343,36 @@ export const getRuleStatusById = async ({ await throwIfNotOk(response); return response.json(); }; + +/** + * Get pre packaged rules Status + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getPrePackagedRulesStatus = async ({ + signal, +}: { + signal: AbortSignal; +}): Promise<{ + rules_installed: number; + rules_not_installed: number; + rules_not_updated: number; +}> => { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL}`, + { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + } + ); + + await throwIfNotOk(response); + return response.json(); +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts index a61cbabd80626..c644ddf30f3ab 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts @@ -10,3 +10,4 @@ export * from './persist_rule'; export * from './types'; export * from './use_rule'; export * from './use_rules'; +export * from './use_pre_packaged_rules'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts index 39efbde2ad5c2..a493e471a9bf6 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts @@ -16,3 +16,17 @@ export const RULE_ADD_FAILURE = i18n.translate( defaultMessage: 'Failed to add Rule', } ); + +export const RULE_PREPACKAGED_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription', + { + defaultMessage: 'Failed to installed pre-packaged rules from elastic', + } +); + +export const RULE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription', + { + defaultMessage: 'Installed pre-packaged rules from elastic', + } +); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx deleted file mode 100644 index a67eab289fa6c..0000000000000 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useEffect, useState } from 'react'; - -import { createPrepackagedRules } from './api'; - -type Return = [boolean, boolean | null]; - -interface UseCreatePackagedRules { - canUserCRUD: boolean | null; - hasIndexManage: boolean | null; - hasManageApiKey: boolean | null; - isAuthenticated: boolean | null; - isSignalIndexExists: boolean | null; -} - -/** - * Hook for creating the packages rules - * - * @param hasIndexManage boolean - * @param hasManageApiKey boolean - * @param isAuthenticated boolean - * @param isSignalIndexExists boolean - * - */ -export const useCreatePackagedRules = ({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, -}: UseCreatePackagedRules): Return => { - const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - setLoading(true); - - async function createRules() { - try { - await createPrepackagedRules({ - signal: abortCtrl.signal, - }); - - if (isSubscribed) { - setHasCreatedPackageRules(true); - } - } catch (error) { - if (isSubscribed) { - setHasCreatedPackageRules(false); - } - } - if (isSubscribed) { - setLoading(false); - } - } - if ( - canUserCRUD && - hasIndexManage && - hasManageApiKey && - isAuthenticated && - isSignalIndexExists - ) { - createRules(); - } - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); - - return [loading, hasCreatedPackageRules]; -}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx new file mode 100644 index 0000000000000..02821c43130e0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useRef } from 'react'; + +import { useStateToaster, displaySuccessToast } from '../../../components/toasters'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; +import * as i18n from './translations'; + +type Func = () => void; + +interface Return { + loading: boolean; + loadingCreatePrePackagedRules: boolean; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; + createPrePackagedRules: Func | null; +} + +interface UsePrePackagedRuleProps { + canUserCRUD: boolean | null; + hasIndexManage: boolean | null; + hasManageApiKey: boolean | null; + isAuthenticated: boolean | null; + isSignalIndexExists: boolean | null; +} + +/** + * Hook for using to get status about pre-packaged Rules from the Detection Engine API + * + * @param hasIndexManage boolean + * @param hasManageApiKey boolean + * @param isAuthenticated boolean + * @param isSignalIndexExists boolean + * + */ +export const usePrePackagedRules = ({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, +}: UsePrePackagedRuleProps): Return => { + const [rulesInstalled, setRulesInstalled] = useState(null); + const [rulesNotInstalled, setRulesNotInstalled] = useState(null); + const [rulesNotUpdated, setRulesNotUpdated] = useState(null); + const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); + const [loading, setLoading] = useState(true); + const createPrePackagedRules = useRef(null); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + async function fetchData() { + try { + setLoading(true); + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + } + } catch (error) { + if (isSubscribed) { + setRulesInstalled(null); + setRulesNotInstalled(null); + setRulesNotUpdated(null); + errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + } + + const createElasticRules = async () => { + try { + if ( + canUserCRUD && + hasIndexManage && + hasManageApiKey && + isAuthenticated && + isSignalIndexExists + ) { + setLoadingCreatePrePackagedRules(true); + await createPrepackagedRules({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + fetchData(); + displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); + } + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoadingCreatePrePackagedRules(false); + } + }; + + fetchData(); + createPrePackagedRules.current = createElasticRules; + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); + + return { + loading, + loadingCreatePrePackagedRules, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + createPrePackagedRules: createPrePackagedRules.current, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index 24e14473d40e9..bbaccb7882484 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -10,7 +10,6 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; import { useKibana } from '../../../../lib/kibana'; -import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules'; export interface State { canUserCRUD: boolean | null; @@ -162,14 +161,6 @@ export const useUserInfo = (): State => { createSignalIndex, ] = useSignalIndex(); - useCreatePackagedRules({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, - }); - const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 4aa6b778582f9..165bedc4a680b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -11,10 +11,12 @@ import { EuiLoadingContent, EuiSpacer, } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { useHistory } from 'react-router-dom'; - import uuid from 'uuid'; + +import { useRules } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../components/header_section'; import { UtilityBar, @@ -23,16 +25,17 @@ import { UtilityBarSection, UtilityBarText, } from '../../../../components/detection_engine/utility_bar'; -import { getColumns } from './columns'; -import { useRules } from '../../../../containers/detection_engine/rules'; +import { useStateToaster } from '../../../../components/toasters'; import { Loader } from '../../../../components/loader'; import { Panel } from '../../../../components/panel'; -import { getBatchItems } from './batch_actions'; +import { PrePackagedRulesPrompt } from '../components/pre_packaged_rules/load_empty_prompt'; +import { RuleDownloader } from '../components/rule_downloader'; +import { getPrePackagedRuleStatus } from '../helpers'; +import * as i18n from '../translations'; import { EuiBasicTableOnChange, TableData } from '../types'; +import { getBatchItems } from './batch_actions'; +import { getColumns } from './columns'; import { allRulesReducer, State } from './reducer'; -import * as i18n from '../translations'; -import { RuleDownloader } from '../components/rule_downloader'; -import { useStateToaster } from '../../../../components/toasters'; const initialState: State = { isLoading: true, @@ -52,6 +55,17 @@ const initialState: State = { }, }; +interface AllRulesProps { + createPrePackagedRules: () => void; + hasNoPermissions: boolean; + importCompleteToggle: boolean; + loading: boolean; + loadingCreatePrePackagedRules: boolean; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; +} + /** * Table Component for displaying all Rules for a given cluster. Provides the ability to filter * by name, sort by enabled, and perform the following actions: @@ -60,191 +74,214 @@ const initialState: State = { * * Delete * * Import/Export */ -export const AllRules = React.memo<{ - hasNoPermissions: boolean; - importCompleteToggle: boolean; - loading: boolean; -}>(({ hasNoPermissions, importCompleteToggle, loading }) => { - const [ - { - exportPayload, - filterOptions, - isLoading, - refreshToggle, - selectedItems, - tableData, - pagination, - }, - dispatch, - ] = useReducer(allRulesReducer, initialState); - const history = useHistory(); - const [isInitialLoad, setIsInitialLoad] = useState(true); - const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); - const [, dispatchToaster] = useStateToaster(); - - const getBatchItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), - [selectedItems, dispatch, dispatchToaster, history] - ); - - const tableOnChangeCallback = useCallback( - ({ page, sort }: EuiBasicTableOnChange) => { - dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: page.index + 1, perPage: page.size }, - }); +export const AllRules = React.memo( + ({ + createPrePackagedRules, + hasNoPermissions, + importCompleteToggle, + loading, + loadingCreatePrePackagedRules, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + }) => { + const [ + { + exportPayload, + filterOptions, + isLoading, + refreshToggle, + selectedItems, + tableData, + pagination, + }, + dispatch, + ] = useReducer(allRulesReducer, initialState); + const history = useHistory(); + const [isInitialLoad, setIsInitialLoad] = useState(true); + const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); + const [, dispatchToaster] = useStateToaster(); + + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const getBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [selectedItems, dispatch, dispatchToaster, history] + ); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: page.index + 1, perPage: page.size }, + }); + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...filterOptions, + sortField: 'enabled', // Only enabled is supported for sorting currently + sortOrder: sort?.direction ?? 'desc', + }, + }); + }, + [dispatch, filterOptions, pagination] + ); + + const columns = useMemo(() => { + return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); + }, [dispatch, dispatchToaster, history]); + + useEffect(() => { + dispatch({ type: 'loading', isLoading: isLoadingRules }); + + if (!isLoadingRules) { + setIsInitialLoad(false); + } + }, [isLoadingRules]); + + useEffect(() => { + if (!isInitialLoad) { + dispatch({ type: 'refresh' }); + } + }, [importCompleteToggle]); + + useEffect(() => { dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...filterOptions, - sortField: 'enabled', // Only enabled is supported for sorting currently - sortOrder: sort?.direction ?? 'desc', + type: 'updateRules', + rules: rulesData.data, + pagination: { + page: rulesData.page, + perPage: rulesData.perPage, + total: rulesData.total, }, }); - }, - [dispatch, filterOptions, pagination] - ); - - const columns = useMemo(() => { - return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); - }, [dispatch, dispatchToaster, history]); - - useEffect(() => { - dispatch({ type: 'loading', isLoading: isLoadingRules }); - - if (!isLoadingRules) { - setIsInitialLoad(false); - } - }, [isLoadingRules]); - - useEffect(() => { - if (!isInitialLoad) { - dispatch({ type: 'refresh' }); - } - }, [importCompleteToggle]); - - useEffect(() => { - dispatch({ - type: 'updateRules', - rules: rulesData.data, - pagination: { - page: rulesData.page, - perPage: rulesData.perPage, - total: rulesData.total, - }, - }); - }, [rulesData]); - - const euiBasicTableSelectionProps = useMemo( - () => ({ - selectable: (item: TableData) => !item.isLoading, - onSelectionChange: (selected: TableData[]) => - dispatch({ type: 'setSelected', selectedItems: selected }), - }), - [] - ); - - return ( - <> - { - dispatchToaster({ - type: 'addToaster', - toast: { - id: uuid.v4(), - title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), - color: 'success', - iconType: 'check', - }, - }); - }} - /> - - - - {isInitialLoad ? ( - - ) : ( - <> - - { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...filterOptions, - filter: filterString, - }, - }); - dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: 1 }, - }); - }} - /> - - - - - - {i18n.SHOWING_RULES(pagination.total ?? 0)} - - - - {i18n.SELECTED_RULES(selectedItems.length)} - {!hasNoPermissions && ( - - {i18n.BATCH_ACTIONS} - - )} - dispatch({ type: 'refresh' })} - > - {i18n.REFRESH} - - - - - - - {(isLoading || loading) && ( - - )} - - )} - - - ); -}); + }, [rulesData]); + + const euiBasicTableSelectionProps = useMemo( + () => ({ + selectable: (item: TableData) => !item.isLoading, + onSelectionChange: (selected: TableData[]) => + dispatch({ type: 'setSelected', selectedItems: selected }), + }), + [] + ); + + return ( + <> + { + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), + color: 'success', + iconType: 'check', + }, + }); + }} + /> + + + + {isInitialLoad ? ( + + ) : ( + <> + + { + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...filterOptions, + filter: filterString, + }, + }); + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: 1 }, + }); + }} + /> + + {(isLoading || loading) && ( + + )} + {isEmpty(tableData) && prePackagedRuleStatus === 'ruleNotInstalled' && ( + + )} + {isEmpty(tableData) && ( + <> + + + + {i18n.SHOWING_RULES(pagination.total ?? 0)} + + + + {i18n.SELECTED_RULES(selectedItems.length)} + {!hasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} + dispatch({ type: 'refresh' })} + > + {i18n.REFRESH} + + + + + + + + )} + + )} + + + ); + } +); AllRules.displayName = 'AllRules'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx new file mode 100644 index 0000000000000..579c31499a582 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; + +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; +import * as i18n from './translations'; + +const EmptyPrompt = styled(EuiEmptyPrompt)` + align-self: center; /* Corrects horizontal centering in IE11 */ +`; + +interface PrePackagedRulesPromptProps { + createPrePackagedRules: () => void; + loading: boolean; + userHasNoPermissions: boolean; +} + +const PrePackagedRulesPromptComponent: React.FC = ({ + createPrePackagedRules, + loading = false, + userHasNoPermissions = true, +}) => { + const handlePreBuiltCreation = useCallback(() => { + createPrePackagedRules(); + }, [createPrePackagedRules]); + return ( + {i18n.PRE_BUILT_TITLE}} + body={

{i18n.PRE_BUILT_MSG}

} + actions={ + + + + {i18n.PRE_BUILT_ACTION} + + + + + {i18n.CREATE_RULE_ACTION} + + + + } + /> + ); +}; + +export const PrePackagedRulesPrompt = memo(PrePackagedRulesPromptComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts new file mode 100644 index 0000000000000..faf1193b1a5e3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PRE_BUILT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptTitle', + { + defaultMessage: 'Load Elastic prebuilt detection rules', + } +); + +export const PRE_BUILT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', + { + defaultMessage: + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met.By default, all prebuilt rules are disabled and you select which rules you want to activate', + } +); + +export const PRE_BUILT_ACTION = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.loadPreBuiltButton', + { + defaultMessage: 'Load prebuilt detection rules', + } +); + +export const CREATE_RULE_ACTION = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.createOwnRuletButton', + { + defaultMessage: 'Create your own rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.updatePrePackagedRulesTitle', + { + defaultMessage: 'Update available for Elastic prebuilt rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_MSG = (updateRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesTitle', { + values: { updateRules }, + defaultMessage: + 'You can update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}}. Note that this will reload deleted Elastic prebuilt rules.', + }); + +export const UPDATE_PREPACKAGED_RULES = (updateRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesButton', { + values: { updateRules }, + defaultMessage: + 'Update {updateRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx new file mode 100644 index 0000000000000..ad42389c4e20a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import * as i18n from './translations'; + +interface UpdatePrePackagedRulesCallOutProps { + numberOfUpdatedRules: number; + updateRules: () => void; +} + +const UpdatePrePackagedRulesCallOutComponent: React.FC = ({ + numberOfUpdatedRules, + updateRules, +}) => ( + +

{i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules)}

+ + {i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules)} + +
+); + +export const UpdatePrePackagedRulesCallOut = memo(UpdatePrePackagedRulesCallOutComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 5539654d6b357..642d52ffefa34 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -194,7 +194,7 @@ const RuleDetailsComponent = memo( onClick={() => setRuleDetailTab(tab.id)} isSelected={tab.id === ruleDetailTab} disabled={tab.disabled} - key={tab.name} + key={tab.id} > {tab.name} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index cc0882dd7e426..0acb1a08fc30b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -67,3 +67,45 @@ export const getStepsData = ({ }; export const useQuery = () => new URLSearchParams(useLocation().search); + +export type PrePackagedRuleStatus = + | 'ruleInstalled' + | 'ruleNotInstalled' + | 'ruleNeedUpdate' + | 'someRuleUninstall' + | 'unknown'; + +export const getPrePackagedRuleStatus = ( + rulesInstalled: number | null, + rulesNotInstalled: number | null, + rulesNotUpdated: number | null +): PrePackagedRuleStatus => { + if (rulesInstalled === 0 && rulesNotInstalled === 0 && rulesNotUpdated === 0) { + return 'ruleNotInstalled'; + } else if ( + rulesInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled === 0 && + rulesNotUpdated === 0 + ) { + return 'ruleInstalled'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'someRuleUninstall'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesNotUpdated != null && + rulesInstalled > 0 && + rulesNotInstalled >= 0 && + rulesNotUpdated > 0 + ) { + return 'ruleNeedUpdate'; + } + return 'unknown'; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index dd46b33ca7257..3f087cbd413e5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -6,20 +6,22 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Redirect } from 'react-router-dom'; +import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect_to_detection_engine'; import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; import { getEmptyTagValue } from '../../../components/empty_value'; import { HeaderPage } from '../../../components/header_page'; import { WrapperPage } from '../../../components/wrapper_page'; import { SpyRoute } from '../../../utils/route/spy_routes'; - +import { useUserInfo } from '../components/user_info'; import { AllRules } from './all'; import { ImportRuleModal } from './components/import_rule_modal'; import { ReadOnlyCallOut } from './components/read_only_callout'; -import { useUserInfo } from '../components/user_info'; +import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/update_callout'; +import { getPrePackagedRuleStatus } from './helpers'; import * as i18n from './translations'; export const RulesComponent = React.memo(() => { @@ -30,8 +32,28 @@ export const RulesComponent = React.memo(() => { isSignalIndexExists, isAuthenticated, canUserCRUD, + hasIndexManage, hasManageApiKey, } = useUserInfo(); + const { + loading: prePackagedRuleLoading, + loadingCreatePrePackagedRules, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + createPrePackagedRules, + } = usePrePackagedRules({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isSignalIndexExists, + isAuthenticated, + }); + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); if ( isSignalIndexExists != null && @@ -43,6 +65,13 @@ export const RulesComponent = React.memo(() => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; const lastCompletedRun = undefined; + + const handleCreatePrePackagedRules = useCallback(() => { + if (createPrePackagedRules != null) { + createPrePackagedRules(); + } + }, [createPrePackagedRules]); + return ( <> {userHasNoPermissions && } @@ -73,6 +102,28 @@ export const RulesComponent = React.memo(() => { title={i18n.PAGE_TITLE} > + {prePackagedRuleStatus === 'ruleNotInstalled' && ( + + + {i18n.LOAD_PREPACKAGED_RULES} + + + )} + {prePackagedRuleStatus === 'someRuleUninstall' && ( + + + {i18n.RELOAD_MISSING_PREPACKAGED_RULES(rulesNotInstalled ?? 0)} + + + )} {
+ {prePackagedRuleStatus === 'ruleNeedUpdate' && ( + + )}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index 284d2f8e93cda..66058d2ecdb81 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -302,3 +302,17 @@ export const UPDATE = i18n.translate('xpack.siem.detectionEngine.rules.updateBut export const DELETE = i18n.translate('xpack.siem.detectionEngine.rules.deleteDescription', { defaultMessage: 'Delete', }); + +export const LOAD_PREPACKAGED_RULES = i18n.translate( + 'xpack.siem.detectionEngine.rules.loadPrePackagedRulesButton', + { + defaultMessage: 'Load Elastic prebuilt rules', + } +); + +export const RELOAD_MISSING_PREPACKAGED_RULES = (missingRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.reloadMissingPrePackagedRulesButton', { + values: { missingRules }, + defaultMessage: + 'Reload {missingRules} deleted Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + }); From 4a885f5c6c7355ccb1e2a5c4125d3c2f09f3c039 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 21 Jan 2020 09:48:20 -0500 Subject: [PATCH 15/21] fix bug + enhance ux --- .../rules/use_create_packaged_rules.tsx | 81 ------------------- .../rules/use_pre_packaged_rules.tsx | 49 ++++++++--- .../detection_engine/rules/use_rules.tsx | 9 ++- .../components/user_info/index.tsx | 9 --- .../detection_engine/rules/all/index.tsx | 22 ++++- .../detection_engine/rules/create/index.tsx | 10 +-- .../pages/detection_engine/rules/helpers.tsx | 7 +- .../pages/detection_engine/rules/index.tsx | 12 ++- 8 files changed, 88 insertions(+), 111 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx deleted file mode 100644 index 592419f879011..0000000000000 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useEffect, useState } from 'react'; - -import { createPrepackagedRules } from './api'; - -type Return = [boolean, boolean | null]; - -interface UseCreatePackagedRules { - canUserCRUD: boolean | null; - hasIndexManage: boolean | null; - hasManageApiKey: boolean | null; - isAuthenticated: boolean | null; - isSignalIndexExists: boolean | null; -} - -/** - * Hook for creating the packages rules - * - * @param canUserCRUD boolean - * @param hasIndexManage boolean - * @param hasManageApiKey boolean - * @param isAuthenticated boolean - * @param isSignalIndexExists boolean - * - * @returns [loading, hasCreatedPackageRules] - */ -export const useCreatePackagedRules = ({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, -}: UseCreatePackagedRules): Return => { - const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - setLoading(true); - - async function createRules() { - try { - await createPrepackagedRules({ - signal: abortCtrl.signal, - }); - - if (isSubscribed) { - setHasCreatedPackageRules(true); - } - } catch (error) { - if (isSubscribed) { - setHasCreatedPackageRules(false); - } - } - if (isSubscribed) { - setLoading(false); - } - } - if ( - canUserCRUD && - hasIndexManage && - hasManageApiKey && - isAuthenticated && - isSignalIndexExists - ) { - createRules(); - } - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); - - return [loading, hasCreatedPackageRules]; -}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 02821c43130e0..30ff73ca6234c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -14,12 +14,13 @@ import * as i18n from './translations'; type Func = () => void; interface Return { + createPrePackagedRules: Func | null; loading: boolean; loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: Func | null; rulesInstalled: number | null; rulesNotInstalled: number | null; rulesNotUpdated: number | null; - createPrePackagedRules: Func | null; } interface UsePrePackagedRuleProps { @@ -52,13 +53,14 @@ export const usePrePackagedRules = ({ const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); const [loading, setLoading] = useState(true); const createPrePackagedRules = useRef(null); + const refetchPrePackagedRules = useRef(null); const [, dispatchToaster] = useStateToaster(); useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); - async function fetchData() { + const fetchPrePackagedRules = async () => { try { setLoading(true); const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ @@ -81,7 +83,7 @@ export const usePrePackagedRules = ({ if (isSubscribed) { setLoading(false); } - } + }; const createElasticRules = async () => { try { @@ -98,22 +100,50 @@ export const usePrePackagedRules = ({ }); if (isSubscribed) { - fetchData(); - displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); + let iterationTryOfFetchingPrePackaagedCount = 0; + let timeoutId = -1; + const stopTimeOut = () => { + if (timeoutId !== -1) { + window.clearTimeout(timeoutId); + } + }; + const reFetch = () => + window.setTimeout(async () => { + iterationTryOfFetchingPrePackaagedCount = + iterationTryOfFetchingPrePackaagedCount + 1; + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + if ( + isSubscribed && + ((prePackagedRuleStatusResponse.rules_not_installed === 0 && + prePackagedRuleStatusResponse.rules_not_updated === 0) || + iterationTryOfFetchingPrePackaagedCount > 100) + ) { + setLoadingCreatePrePackagedRules(false); + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); + stopTimeOut(); + } else { + timeoutId = reFetch(); + } + }, 300); + timeoutId = reFetch(); } } } catch (error) { if (isSubscribed) { + setLoadingCreatePrePackagedRules(false); errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster }); } } - if (isSubscribed) { - setLoadingCreatePrePackagedRules(false); - } }; - fetchData(); + fetchPrePackagedRules(); createPrePackagedRules.current = createElasticRules; + refetchPrePackagedRules.current = fetchPrePackagedRules; return () => { isSubscribed = false; abortCtrl.abort(); @@ -123,6 +153,7 @@ export const usePrePackagedRules = ({ return { loading, loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus: refetchPrePackagedRules.current, rulesInstalled, rulesNotInstalled, rulesNotUpdated, diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index b49dd8d51d4f7..b7f344e02628b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; import { useStateToaster } from '../../../components/toasters'; @@ -12,7 +12,8 @@ import { fetchRules } from './api'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import * as i18n from './translations'; -type Return = [boolean, FetchRulesResponse]; +type Func = () => void; +type Return = [boolean, FetchRulesResponse, Func | null]; /** * Hook for using the list of Rules from the Detection Engine API @@ -32,6 +33,7 @@ export const useRules = ( total: 0, data: [], }); + const reFetchRules = useRef(null); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); @@ -62,6 +64,7 @@ export const useRules = ( } fetchData(); + reFetchRules.current = fetchData; return () => { isSubscribed = false; abortCtrl.abort(); @@ -75,5 +78,5 @@ export const useRules = ( filterOptions.sortOrder, ]); - return [loading, rules]; + return [loading, rules, reFetchRules.current]; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index 24e14473d40e9..bbaccb7882484 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -10,7 +10,6 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; import { useKibana } from '../../../../lib/kibana'; -import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules'; export interface State { canUserCRUD: boolean | null; @@ -162,14 +161,6 @@ export const useUserInfo = (): State => { createSignalIndex, ] = useSignalIndex(); - useCreatePackagedRules({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, - }); - const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 165bedc4a680b..b3ad0956c58b9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -61,6 +61,7 @@ interface AllRulesProps { importCompleteToggle: boolean; loading: boolean; loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: () => void; rulesInstalled: number | null; rulesNotInstalled: number | null; rulesNotUpdated: number | null; @@ -81,6 +82,7 @@ export const AllRules = React.memo( importCompleteToggle, loading, loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, rulesInstalled, rulesNotInstalled, rulesNotUpdated, @@ -99,7 +101,11 @@ export const AllRules = React.memo( ] = useReducer(allRulesReducer, initialState); const history = useHistory(); const [isInitialLoad, setIsInitialLoad] = useState(true); - const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); + const [isLoadingRules, rulesData, reFetchRulesData] = useRules( + pagination, + filterOptions, + refreshToggle + ); const [, dispatchToaster] = useStateToaster(); const prePackagedRuleStatus = getPrePackagedRuleStatus( @@ -153,6 +159,18 @@ export const AllRules = React.memo( } }, [importCompleteToggle]); + useEffect(() => { + if (reFetchRulesData != null) { + reFetchRulesData(); + } + }, [rulesInstalled, rulesNotInstalled, rulesNotUpdated]); + + useEffect(() => { + if (refetchPrePackagedRulesStatus != null && tableData.length > 0) { + refetchPrePackagedRulesStatus(); + } + }, [refetchPrePackagedRulesStatus, tableData]); + useEffect(() => { dispatch({ type: 'updateRules', @@ -229,7 +247,7 @@ export const AllRules = React.memo( userHasNoPermissions={hasNoPermissions} /> )} - {isEmpty(tableData) && ( + {!isEmpty(tableData) && ( <> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index cbc60015d9c87..a261cd57cf31d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -29,10 +29,10 @@ import * as i18n from './translations'; const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; const MyEuiPanel = styled(EuiPanel)<{ - zIndex?: number; + zindex?: number; }>` position: relative; - z-index: ${props => props.zIndex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ + z-index: ${props => props.zindex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ .euiAccordion__iconWrapper { display: none; @@ -237,7 +237,7 @@ export const CreateRuleComponent = React.memo(() => { isLoading={isLoading || loading} title={i18n.PAGE_TITLE} /> - + { - + { - + { - if (rulesInstalled === 0 && rulesNotInstalled === 0 && rulesNotUpdated === 0) { + if ( + rulesNotInstalled != null && + rulesInstalled === 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { return 'ruleNotInstalled'; } else if ( rulesInstalled != null && diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 3f087cbd413e5..3df7cf776f338 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -36,12 +36,13 @@ export const RulesComponent = React.memo(() => { hasManageApiKey, } = useUserInfo(); const { + createPrePackagedRules, loading: prePackagedRuleLoading, loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, rulesInstalled, rulesNotInstalled, rulesNotUpdated, - createPrePackagedRules, } = usePrePackagedRules({ canUserCRUD, hasIndexManage, @@ -72,6 +73,12 @@ export const RulesComponent = React.memo(() => { } }, [createPrePackagedRules]); + const handleRefetchPrePackagedRulesStatus = useCallback(() => { + if (refetchPrePackagedRulesStatus != null) { + refetchPrePackagedRulesStatus(); + } + }, [refetchPrePackagedRulesStatus]); + return ( <> {userHasNoPermissions && } @@ -106,6 +113,7 @@ export const RulesComponent = React.memo(() => { @@ -117,6 +125,7 @@ export const RulesComponent = React.memo(() => { @@ -159,6 +168,7 @@ export const RulesComponent = React.memo(() => { loadingCreatePrePackagedRules={loadingCreatePrePackagedRules} hasNoPermissions={userHasNoPermissions} importCompleteToggle={importCompleteToggle} + refetchPrePackagedRulesStatus={handleRefetchPrePackagedRulesStatus} rulesInstalled={rulesInstalled} rulesNotInstalled={rulesNotInstalled} rulesNotUpdated={rulesNotUpdated} From f816171af925bdd737dc87d82275147239cd2c4b Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 21 Jan 2020 10:08:32 -0500 Subject: [PATCH 16/21] unified icon --- .../rules/components/pre_packaged_rules/load_empty_prompt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx index 579c31499a582..d8537ba83505b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -39,7 +39,7 @@ const PrePackagedRulesPromptComponent: React.FC = ( Date: Tue, 21 Jan 2020 10:29:02 -0500 Subject: [PATCH 17/21] fix i18n --- .../rules/components/pre_packaged_rules/translations.ts | 4 ++-- .../rules/components/pre_packaged_rules/update_callout.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts index faf1193b1a5e3..5f89bd072ebed 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts @@ -43,7 +43,7 @@ export const UPDATE_PREPACKAGED_RULES_TITLE = i18n.translate( ); export const UPDATE_PREPACKAGED_RULES_MSG = (updateRules: number) => - i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesTitle', { + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesMsg', { values: { updateRules }, defaultMessage: 'You can update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}}. Note that this will reload deleted Elastic prebuilt rules.', @@ -53,5 +53,5 @@ export const UPDATE_PREPACKAGED_RULES = (updateRules: number) => i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesButton', { values: { updateRules }, defaultMessage: - 'Update {updateRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + 'Update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}} ', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx index ad42389c4e20a..00322bd7bf150 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx @@ -20,7 +20,7 @@ const UpdatePrePackagedRulesCallOutComponent: React.FC (

{i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules)}

- + {i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules)}
From 5688052cdd0dc74b2b7d6537c808c00a01aab07b Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 21 Jan 2020 17:35:31 -0500 Subject: [PATCH 18/21] fix bugs --- .../containers/detection_engine/rules/api.ts | 2 +- .../rules/use_pre_packaged_rules.tsx | 110 ++++++------ .../detection_engine/rules/use_rules.tsx | 10 +- .../signals/use_privilege_user.tsx | 1 + .../detection_engine/detection_engine.tsx | 34 ++-- .../detection_engine/rules/all/actions.tsx | 7 +- .../detection_engine/rules/all/index.tsx | 162 ++++++++++-------- .../detection_engine/rules/all/reducer.ts | 36 ++-- .../pre_packaged_rules/update_callout.tsx | 4 +- .../detection_engine/rules/create/index.tsx | 20 +-- .../detection_engine/rules/details/index.tsx | 16 +- .../detection_engine/rules/edit/index.tsx | 19 +- .../pages/detection_engine/rules/index.tsx | 37 ++-- 13 files changed, 250 insertions(+), 208 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 750093901ef84..6c9fd5c0ff3bc 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -64,7 +64,7 @@ export const addRule = async ({ rule, signal }: AddRulesProps): Promise export const fetchRules = async ({ filterOptions = { filter: '', - sortField: 'enabled', + sortField: 'name', sortOrder: 'desc', }, pagination = { diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 30ff73ca6234c..abb43f3570e6c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -12,9 +12,9 @@ import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; import * as i18n from './translations'; type Func = () => void; - +export type CreatePreBuiltRules = () => Promise; interface Return { - createPrePackagedRules: Func | null; + createPrePackagedRules: null | CreatePreBuiltRules; loading: boolean; loadingCreatePrePackagedRules: boolean; refetchPrePackagedRulesStatus: Func | null; @@ -52,7 +52,7 @@ export const usePrePackagedRules = ({ const [rulesNotUpdated, setRulesNotUpdated] = useState(null); const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); const [loading, setLoading] = useState(true); - const createPrePackagedRules = useRef(null); + const createPrePackagedRules = useRef(null); const refetchPrePackagedRules = useRef(null); const [, dispatchToaster] = useStateToaster(); @@ -85,60 +85,64 @@ export const usePrePackagedRules = ({ } }; - const createElasticRules = async () => { - try { - if ( - canUserCRUD && - hasIndexManage && - hasManageApiKey && - isAuthenticated && - isSignalIndexExists - ) { - setLoadingCreatePrePackagedRules(true); - await createPrepackagedRules({ - signal: abortCtrl.signal, - }); + const createElasticRules = async (): Promise => { + return new Promise(async resolve => { + try { + if ( + canUserCRUD && + hasIndexManage && + hasManageApiKey && + isAuthenticated && + isSignalIndexExists + ) { + setLoadingCreatePrePackagedRules(true); + await createPrepackagedRules({ + signal: abortCtrl.signal, + }); - if (isSubscribed) { - let iterationTryOfFetchingPrePackaagedCount = 0; - let timeoutId = -1; - const stopTimeOut = () => { - if (timeoutId !== -1) { - window.clearTimeout(timeoutId); - } - }; - const reFetch = () => - window.setTimeout(async () => { - iterationTryOfFetchingPrePackaagedCount = - iterationTryOfFetchingPrePackaagedCount + 1; - const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ - signal: abortCtrl.signal, - }); - if ( - isSubscribed && - ((prePackagedRuleStatusResponse.rules_not_installed === 0 && - prePackagedRuleStatusResponse.rules_not_updated === 0) || - iterationTryOfFetchingPrePackaagedCount > 100) - ) { - setLoadingCreatePrePackagedRules(false); - setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); - setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); - setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); - displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); - stopTimeOut(); - } else { - timeoutId = reFetch(); + if (isSubscribed) { + let iterationTryOfFetchingPrePackagedCount = 0; + let timeoutId = -1; + const stopTimeOut = () => { + if (timeoutId !== -1) { + window.clearTimeout(timeoutId); } - }, 300); - timeoutId = reFetch(); + }; + const reFetch = () => + window.setTimeout(async () => { + iterationTryOfFetchingPrePackagedCount = + iterationTryOfFetchingPrePackagedCount + 1; + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + if ( + isSubscribed && + ((prePackagedRuleStatusResponse.rules_not_installed === 0 && + prePackagedRuleStatusResponse.rules_not_updated === 0) || + iterationTryOfFetchingPrePackagedCount > 100) + ) { + setLoadingCreatePrePackagedRules(false); + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); + stopTimeOut(); + resolve(true); + } else { + timeoutId = reFetch(); + } + }, 300); + timeoutId = reFetch(); + } + } + } catch (error) { + if (isSubscribed) { + setLoadingCreatePrePackagedRules(false); + errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster }); + resolve(false); } } - } catch (error) { - if (isSubscribed) { - setLoadingCreatePrePackagedRules(false); - errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster }); - } - } + }); }; fetchPrePackagedRules(); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index b7f344e02628b..04475ab1bc178 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -20,13 +20,8 @@ type Return = [boolean, FetchRulesResponse, Func | null]; * * @param pagination desired pagination options (e.g. page/perPage) * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) - * @param refetchToggle toggle for refetching data */ -export const useRules = ( - pagination: PaginationOptions, - filterOptions: FilterOptions, - refetchToggle: boolean -): Return => { +export const useRules = (pagination: PaginationOptions, filterOptions: FilterOptions): Return => { const [rules, setRules] = useState({ page: 1, perPage: 20, @@ -40,10 +35,10 @@ export const useRules = ( useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); - setLoading(true); async function fetchData() { try { + setLoading(true); const fetchRulesResult = await fetchRules({ filterOptions, pagination, @@ -70,7 +65,6 @@ export const useRules = ( abortCtrl.abort(); }; }, [ - refetchToggle, pagination.page, pagination.perPage, filterOptions.filter, diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx index 7d0e331200d55..564cf224a9fc8 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -59,6 +59,7 @@ export const usePrivilegeUser = (): Return => { setAuthenticated(false); setHasIndexManage(false); setHasIndexWrite(false); + setHasManageApiKey(false); errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster }); } } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index d9e0377b34060..d7a27a45ff19a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -90,23 +90,6 @@ const DetectionEngineComponent = React.memo( [setAbsoluteRangeDatePicker] ); - if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { - return ( - - - - - ); - } - if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { - return ( - - - - - ); - } - const tabs = useMemo( () => ( @@ -125,6 +108,23 @@ const DetectionEngineComponent = React.memo( [detectionsTabs, tabName] ); + if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { + return ( + + + + + ); + } + if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { + return ( + + + + + ); + } + return ( <> {hasIndexWrite != null && !hasIndexWrite && } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index 435edcab433b6..d6bf8643fff1c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -64,13 +64,12 @@ export const deleteRulesAction = async ( onRuleDeleted?: () => void ) => { try { - dispatch({ type: 'updateLoading', ids, isLoading: true }); + dispatch({ type: 'loading', isLoading: true }); const response = await deleteRules({ ids }); - const { rules, errors } = bucketRulesResponse(response); - - dispatch({ type: 'deleteRules', rules }); + const { errors } = bucketRulesResponse(response); + dispatch({ type: 'refresh' }); if (errors.length > 0) { displayErrorToast( i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index b3ad0956c58b9..011988e090252 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -16,7 +16,7 @@ import React, { useCallback, useEffect, useMemo, useReducer, useState } from 're import { useHistory } from 'react-router-dom'; import uuid from 'uuid'; -import { useRules } from '../../../../containers/detection_engine/rules'; +import { useRules, CreatePreBuiltRules } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../components/header_section'; import { UtilityBar, @@ -56,7 +56,7 @@ const initialState: State = { }; interface AllRulesProps { - createPrePackagedRules: () => void; + createPrePackagedRules: CreatePreBuiltRules | null; hasNoPermissions: boolean; importCompleteToggle: boolean; loading: boolean; @@ -65,6 +65,7 @@ interface AllRulesProps { rulesInstalled: number | null; rulesNotInstalled: number | null; rulesNotUpdated: number | null; + setRefreshRulesData: (refreshRule: () => void) => void; } /** @@ -86,6 +87,7 @@ export const AllRules = React.memo( rulesInstalled, rulesNotInstalled, rulesNotUpdated, + setRefreshRulesData, }) => { const [ { @@ -101,12 +103,9 @@ export const AllRules = React.memo( ] = useReducer(allRulesReducer, initialState); const history = useHistory(); const [isInitialLoad, setIsInitialLoad] = useState(true); - const [isLoadingRules, rulesData, reFetchRulesData] = useRules( - pagination, - filterOptions, - refreshToggle - ); + const [isGlobalLoading, setIsGlobalLoad] = useState(false); const [, dispatchToaster] = useStateToaster(); + const [isLoadingRules, rulesData, reFetchRulesData] = useRules(pagination, filterOptions); const prePackagedRuleStatus = getPrePackagedRuleStatus( rulesInstalled, @@ -147,11 +146,21 @@ export const AllRules = React.memo( useEffect(() => { dispatch({ type: 'loading', isLoading: isLoadingRules }); + }, [isLoadingRules]); - if (!isLoadingRules) { + useEffect(() => { + if (!isLoadingRules && !loading && isInitialLoad) { setIsInitialLoad(false); } - }, [isLoadingRules]); + }, [isInitialLoad, isLoadingRules, loading]); + + useEffect(() => { + if (!isGlobalLoading && (isLoadingRules || isLoading)) { + setIsGlobalLoad(true); + } else if (isGlobalLoading && !isLoadingRules && !isLoading) { + setIsGlobalLoad(false); + } + }, [setIsGlobalLoad, isGlobalLoading, isLoadingRules, isLoading]); useEffect(() => { if (!isInitialLoad) { @@ -163,13 +172,14 @@ export const AllRules = React.memo( if (reFetchRulesData != null) { reFetchRulesData(); } - }, [rulesInstalled, rulesNotInstalled, rulesNotUpdated]); + refetchPrePackagedRulesStatus(); + }, [refreshToggle, reFetchRulesData, refetchPrePackagedRulesStatus]); useEffect(() => { - if (refetchPrePackagedRulesStatus != null && tableData.length > 0) { - refetchPrePackagedRulesStatus(); + if (reFetchRulesData != null) { + setRefreshRulesData(reFetchRulesData); } - }, [refetchPrePackagedRulesStatus, tableData]); + }, [reFetchRulesData, setRefreshRulesData]); useEffect(() => { dispatch({ @@ -183,6 +193,13 @@ export const AllRules = React.memo( }); }, [rulesData]); + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null) { + await createPrePackagedRules(); + dispatch({ type: 'refresh' }); + } + }, [createPrePackagedRules]); + const euiBasicTableSelectionProps = useMemo( () => ({ selectable: (item: TableData) => !item.isLoading, @@ -211,12 +228,10 @@ export const AllRules = React.memo( /> - - {isInitialLoad ? ( - - ) : ( - <> - + + <> + + {rulesInstalled != null && rulesInstalled > 0 && ( ( }); }} /> - - {(isLoading || loading) && ( - - )} - {isEmpty(tableData) && prePackagedRuleStatus === 'ruleNotInstalled' && ( - )} - {!isEmpty(tableData) && ( - <> - - - - {i18n.SHOWING_RULES(pagination.total ?? 0)} - + + {isInitialLoad && isEmpty(tableData) && ( + + )} + {isGlobalLoading && !isEmpty(tableData) && ( + + )} + {isEmpty(tableData) && prePackagedRuleStatus === 'ruleNotInstalled' && ( + + )} + {!isEmpty(tableData) && ( + <> + + + + {i18n.SHOWING_RULES(pagination.total ?? 0)} + - - {i18n.SELECTED_RULES(selectedItems.length)} - {!hasNoPermissions && ( - - {i18n.BATCH_ACTIONS} - - )} + + {i18n.SELECTED_RULES(selectedItems.length)} + {!hasNoPermissions && ( dispatch({ type: 'refresh' })} + iconType="arrowDown" + popoverContent={getBatchItemsPopoverContent} > - {i18n.REFRESH} + {i18n.BATCH_ACTIONS} - - - + )} + dispatch({ type: 'refresh' })} + > + {i18n.REFRESH} + + +
+
- - - )} - - )} + + + )} + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts index 22d6ca2195fe6..74ce8f2847faa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts @@ -58,19 +58,21 @@ export const allRulesReducer = (state: State, action: Action): State => { const ruleIds = state.rules.map(r => r.rule_id); const appendIdx = action.appendRuleId != null ? state.rules.findIndex(r => r.id === action.appendRuleId) : -1; - const updatedRules = action.rules.reduce( - (rules, updatedRule) => - ruleIds.includes(updatedRule.rule_id) - ? rules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)) - : appendIdx !== -1 - ? [ - ...rules.slice(0, appendIdx + 1), - updatedRule, - ...rules.slice(appendIdx + 1, rules.length - 1), - ] - : [...rules, updatedRule], - [...state.rules] - ); + const updatedRules = action.rules.reverse().reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.rule_id)) { + newRules = newRules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)); + } else if (appendIdx !== -1) { + newRules = [ + ...newRules.slice(0, appendIdx + 1), + updatedRule, + ...newRules.slice(appendIdx + 1, newRules.length), + ]; + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); // Update enabled on selectedItems so that batch actions show correct available actions const updatedRuleIdToState = action.rules.reduce>( @@ -88,6 +90,13 @@ export const allRulesReducer = (state: State, action: Action): State => { rules: updatedRules, tableData: formatRules(updatedRules), selectedItems: updatedSelectedItems, + pagination: { + ...state.pagination, + total: + action.appendRuleId != null + ? state.pagination.total + action.rules.length + : state.pagination.total, + }, }; } case 'updatePagination': { @@ -112,6 +121,7 @@ export const allRulesReducer = (state: State, action: Action): State => { ...state, rules: updatedRules, tableData: formatRules(updatedRules), + refreshToggle: !state.refreshToggle, }; } case 'setSelected': { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx index 00322bd7bf150..532187b2ba9aa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx @@ -10,17 +10,19 @@ import { EuiCallOut, EuiButton } from '@elastic/eui'; import * as i18n from './translations'; interface UpdatePrePackagedRulesCallOutProps { + loading: boolean; numberOfUpdatedRules: number; updateRules: () => void; } const UpdatePrePackagedRulesCallOutComponent: React.FC = ({ + loading, numberOfUpdatedRules, updateRules, }) => (

{i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules)}

- + {i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index a261cd57cf31d..6eaaf37c06689 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -80,16 +80,6 @@ export const CreateRuleComponent = React.memo(() => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } else if (userHasNoPermissions) { - return ; - } - const setStepData = useCallback( (step: RuleStep, data: unknown, isValid: boolean) => { stepsData.current[step] = { ...stepsData.current[step], data, isValid }; @@ -228,6 +218,16 @@ export const CreateRuleComponent = React.memo(() => { return ; } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + return ( <> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index a23c681a5aab2..097121200257a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -122,14 +122,6 @@ const RuleDetailsComponent = memo( const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } - const title = isLoading === true || rule === null ? : rule.name; const subTitle = useMemo( () => @@ -228,6 +220,14 @@ const RuleDetailsComponent = memo( [ruleEnabled, setRuleEnabled] ); + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } + return ( <> {hasIndexWrite != null && !hasIndexWrite && } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 9b7833afd7f4d..9dfcda91c14c0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -62,15 +62,6 @@ export const EditRuleComponent = memo(() => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } else if (userHasNoPermissions) { - return ; - } const [initForm, setInitForm] = useState(false); const [myAboutRuleForm, setMyAboutRuleForm] = useState({ @@ -277,6 +268,16 @@ export const EditRuleComponent = memo(() => { return ; } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + return ( <> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 3df7cf776f338..4e36de7dcd5eb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -6,7 +6,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; @@ -24,9 +24,12 @@ import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/u import { getPrePackagedRuleStatus } from './helpers'; import * as i18n from './translations'; +type Func = () => void; + export const RulesComponent = React.memo(() => { const [showImportModal, setShowImportModal] = useState(false); const [importCompleteToggle, setImportCompleteToggle] = useState(false); + const refreshRulesData = useRef(null); const { loading, isSignalIndexExists, @@ -56,22 +59,18 @@ export const RulesComponent = React.memo(() => { rulesNotUpdated ); - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; const lastCompletedRun = undefined; - const handleCreatePrePackagedRules = useCallback(() => { + const handleCreatePrePackagedRules = useCallback(async () => { if (createPrePackagedRules != null) { - createPrePackagedRules(); + await createPrePackagedRules(); + if (refreshRulesData.current != null) { + refreshRulesData.current(); + } } - }, [createPrePackagedRules]); + }, [createPrePackagedRules, refreshRulesData]); const handleRefetchPrePackagedRulesStatus = useCallback(() => { if (refetchPrePackagedRulesStatus != null) { @@ -79,6 +78,18 @@ export const RulesComponent = React.memo(() => { } }, [refetchPrePackagedRulesStatus]); + const handleSetRefreshRulesData = useCallback((refreshRule: Func) => { + refreshRulesData.current = refreshRule; + }, []); + + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } + return ( <> {userHasNoPermissions && } @@ -158,12 +169,13 @@ export const RulesComponent = React.memo(() => { {prePackagedRuleStatus === 'ruleNeedUpdate' && ( )} { rulesInstalled={rulesInstalled} rulesNotInstalled={rulesNotInstalled} rulesNotUpdated={rulesNotUpdated} + setRefreshRulesData={handleSetRefreshRulesData} /> From 5dcc6a0eb3f3bacdad38a9b4bdf2db93d20ca2eb Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 21 Jan 2020 18:05:34 -0500 Subject: [PATCH 19/21] review I --- x-pack/legacy/plugins/siem/common/constants.ts | 4 ++-- .../rules/components/pre_packaged_rules/load_empty_prompt.tsx | 2 +- .../rules/components/pre_packaged_rules/update_callout.tsx | 2 +- .../siem/public/pages/detection_engine/rules/index.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index f2ad9700b7f78..03824dd4e3960 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -60,8 +60,8 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`; -export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_URL}/rules/_find_statuses`; -export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_URL}/rules/prepackaged/_status`; +export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`; +export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`; /** * Default signals index key for kibana.dev.yml diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx index d8537ba83505b..41b7fafd6becd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -39,7 +39,7 @@ const PrePackagedRulesPromptComponent: React.FC = ( ( - +

{i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules)}

{i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules)} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 4e36de7dcd5eb..5cdc7a1d4fa6b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -123,7 +123,7 @@ export const RulesComponent = React.memo(() => { {prePackagedRuleStatus === 'ruleNotInstalled' && ( Date: Tue, 21 Jan 2020 19:05:01 -0500 Subject: [PATCH 20/21] review II --- .../public/pages/detection_engine/rules/all/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 011988e090252..aee1831e8e4c0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -230,8 +230,8 @@ export const AllRules = React.memo( <> - - {rulesInstalled != null && rulesInstalled > 0 && ( + {rulesInstalled != null && rulesInstalled > 0 && ( + ( }); }} /> - )} - + + )} {isInitialLoad && isEmpty(tableData) && ( )} From 4a7fa205ef22a38a17a36443a48f802834a3b1ec Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 21 Jan 2020 19:13:01 -0500 Subject: [PATCH 21/21] add border back --- .../siem/public/pages/detection_engine/rules/all/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index aee1831e8e4c0..677033b258837 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -231,7 +231,7 @@ export const AllRules = React.memo( <> {rulesInstalled != null && rulesInstalled > 0 && ( - +