From 52b9cdb0f04a0a15620b6588698a72a52c16c60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopycin=CC=81ski?= Date: Fri, 10 Jan 2020 13:40:02 +0100 Subject: [PATCH 1/7] [SIEM] Detection Engine Create Rule Design Review #1 --- .../detection_engine/rules/all/columns.tsx | 19 +- .../components/description_step/helpers.tsx | 59 +-- .../components/description_step/index.tsx | 48 ++- .../rules/components/severity_badge/index.tsx | 32 ++ .../rules/components/step_about_rule/data.tsx | 17 +- .../components/step_about_rule/index.tsx | 362 +++++++++--------- .../components/step_about_rule/schema.tsx | 16 +- .../components/step_content_wrapper/index.tsx | 14 + .../components/step_define_rule/index.tsx | 339 ++++++++-------- .../detection_engine/rules/create/index.tsx | 48 ++- .../detection_engine/rules/details/index.tsx | 2 +- 11 files changed, 517 insertions(+), 439 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx 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 42c4bb1d0ef95..8c75fe05e60d5 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 @@ -8,7 +8,6 @@ import { EuiBadge, - EuiHealth, EuiIconTip, EuiLink, EuiTextColor, @@ -17,7 +16,6 @@ import { } from '@elastic/eui'; import * as H from 'history'; import React from 'react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { getEmptyTagValue } from '../../../../components/empty_value'; import { deleteRulesAction, @@ -32,6 +30,7 @@ import { TableData } from '../types'; import * as i18n from '../translations'; import { PreferenceFormattedDate } from '../../../../components/formatted_date'; import { RuleSwitch } from '../components/rule_switch'; +import { SeverityBadge } from '../components/severity_badge'; const getActions = (dispatch: React.Dispatch, history: H.History) => [ { @@ -88,21 +87,7 @@ export const getColumns = ( { field: 'severity', name: i18n.COLUMN_SEVERITY, - render: (value: TableData['severity']) => ( - - {value} - - ), + render: (value: TableData['severity']) => , truncateText: true, }, { 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 09d0c1131ea10..62cbc4f4a60a0 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 @@ -9,7 +9,6 @@ import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, - EuiHealth, EuiLink, EuiText, EuiListGroup, @@ -27,6 +26,10 @@ import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_ import { FilterLabel } from './filter_label'; import * as i18n from './translations'; import { BuildQueryBarDescription, BuildThreatsDescription, ListItems } from './types'; +import { SeverityBadge } from '../severity_badge'; + +const isNotEmptyArray = (values: string[]) => + !isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0; const EuiBadgeWrap = styled(EuiBadge)` .euiBadge__text { @@ -148,12 +151,34 @@ export const buildThreatsDescription = ({ return []; }; +export const buildUnorderedListArrayDescription = ( + label: string, + field: string, + values: string[] +): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( +
    + {values.map((val: string) => + isEmpty(val) ? null :
  • {val}
  • + )} +
+ ), + }, + ]; + } + return []; +}; + export const buildStringArrayDescription = ( label: string, field: string, values: string[] ): ListItems[] => { - if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { + if (isNotEmptyArray(values)) { return [ { title: label, @@ -174,31 +199,15 @@ export const buildStringArrayDescription = ( return []; }; -export const buildSeverityDescription = (label: string, value: string): ListItems[] => { - return [ - { - title: label, - description: ( - - {value} - - ), - }, - ]; -}; +export const buildSeverityDescription = (label: string, value: string): ListItems[] => [ + { + title: label, + description: , + }, +]; export const buildUrlsDescription = (label: string, values: string[]): ListItems[] => { - if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { + if (isNotEmptyArray(values)) { return [ { title: label, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx index 198756fc2336b..1326b5f9aebb6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiTextArea } from '@elastic/eui'; +import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty, chunk, get, pick } from 'lodash/fp'; import React, { memo, useState } from 'react'; -import styled from 'styled-components'; import { IIndexPattern, @@ -26,6 +25,7 @@ import { buildSeverityDescription, buildStringArrayDescription, buildThreatsDescription, + buildUnorderedListArrayDescription, buildUrlsDescription, } from './helpers'; @@ -36,15 +36,6 @@ interface StepRuleDescriptionProps { schema: FormSchema; } -const EuiFlexItemWidth = styled(EuiFlexItem)<{ direction: string }>` - ${props => (props.direction === 'row' ? 'width : 50%;' : 'width: 100%;')}; -`; - -const MyEuiTextArea = styled(EuiTextArea)` - max-width: 100%; - height: 80px; -`; - const StepRuleDescriptionComponent: React.FC = ({ data, direction = 'row', @@ -62,13 +53,24 @@ const StepRuleDescriptionComponent: React.FC = ({ ], [] ); + + if (direction === 'row') { + return ( + + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => ( + + + + ))} + + ); + } + return ( - - {chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => ( - - - - ))} + + + + ); }; @@ -123,18 +125,28 @@ const getDescriptionItem = ( return [ { title: label, - description: , + description: get(field, value), }, ]; } else if (field === 'references') { const urls: string[] = get(field, value); return buildUrlsDescription(label, urls); + } else if (field === 'falsePositives') { + const values: string[] = get(field, value); + return buildUnorderedListArrayDescription(label, field, values); } else if (Array.isArray(get(field, value))) { const values: string[] = get(field, value); return buildStringArrayDescription(label, field, values); } else if (field === 'severity') { const val: string = get(field, value); return buildSeverityDescription(label, val); + } else if (field === 'riskScore') { + return [ + { + title: label, + description: get(field, value), + }, + ]; } else if (field === 'timeline') { const timeline = get(field, value) as FieldValueTimeline; return [ diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx new file mode 100644 index 0000000000000..09c02dfca56f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx @@ -0,0 +1,32 @@ +/* + * 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 { upperFirst } from 'lodash/fp'; +import React from 'react'; +import { EuiHealth } from '@elastic/eui'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +interface Props { + value: string; +} + +const SeverityBadgeComponent: React.FC = ({ value }) => ( + + {upperFirst(value)} + +); + +export const SeverityBadge = React.memo(SeverityBadgeComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx index 9fb64189ebd1a..269d2d4509508 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import styled from 'styled-components'; import { EuiHealth } from '@elastic/eui'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import React from 'react'; @@ -16,22 +17,30 @@ interface SeverityOptionItem { inputDisplay: React.ReactElement; } +const StyledEuiHealth = styled(EuiHealth)` + line-height: inherit; +`; + export const severityOptions: SeverityOptionItem[] = [ { value: 'low', - inputDisplay: {I18n.LOW}, + inputDisplay: {I18n.LOW}, }, { value: 'medium', - inputDisplay: {I18n.MEDIUM} , + inputDisplay: ( + {I18n.MEDIUM} + ), }, { value: 'high', - inputDisplay: {I18n.HIGH} , + inputDisplay: {I18n.HIGH}, }, { value: 'critical', - inputDisplay: {I18n.CRITICAL} , + inputDisplay: ( + {I18n.CRITICAL} + ), }, ]; 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 8956776dcd3b2..4433c11e037a4 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 @@ -6,7 +6,7 @@ import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual, get } from 'lodash/fp'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; @@ -33,196 +33,196 @@ const TagContainer = styled.div` margin-top: 16px; `; -export const StepAboutRule = memo( - ({ - defaultValues, - descriptionDirection = 'row', - isReadOnlyView, - isUpdateView = false, - isLoading, - setForm, - setStepData, - }) => { - const [myStepData, setMyStepData] = useState(stepAboutDefaultValue); +const StepAboutRuleComponent: FC = ({ + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isUpdateView = false, + isLoading, + setForm, + setStepData, +}) => { + const [myStepData, setMyStepData] = useState(stepAboutDefaultValue); - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); - const onSubmit = useCallback(async () => { - if (setStepData) { - setStepData(RuleStep.aboutRule, null, false); - const { isValid, data } = await form.submit(); - if (isValid) { - setStepData(RuleStep.aboutRule, data, isValid); - setMyStepData({ ...data, isNew: false } as AboutStepRule); - } + const onSubmit = useCallback(async () => { + if (setStepData) { + setStepData(RuleStep.aboutRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.aboutRule, data, isValid); + setMyStepData({ ...data, isNew: false } as AboutStepRule); } - }, [form]); + } + }, [form]); - useEffect(() => { - const { isNew, ...initDefaultValue } = myStepData; - if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { - const myDefaultValues = { - ...defaultValues, - isNew: false, - }; - setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } - }, [defaultValues]); + } + }, [defaultValues]); - useEffect(() => { - if (setForm != null) { - setForm(RuleStep.aboutRule, form); - } - }, [form]); + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.aboutRule, form); + } + }, [form]); - return isReadOnlyView && myStepData != null ? ( - - ) : ( - <> -
- - + return isReadOnlyView && myStepData != null ? ( + + ) : ( + <> + + + + + + + + + + - - - - - - - - - - {({ severity }) => { - const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; - const riskScoreField = form.getFields().riskScore; - if (newRiskScore != null && riskScoreField.value !== newRiskScore) { - riskScoreField.setValue(newRiskScore); - } - return null; - }} - - - {!isUpdateView && ( - <> - - - - - {RuleI18n.CONTINUE} - - - - - )} - - ); - } -); + + + {({ severity }) => { + const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; + const riskScoreField = form.getFields().riskScore; + if (newRiskScore != null && riskScoreField.value !== newRiskScore) { + riskScoreField.setValue(newRiskScore); + } + return null; + }} + + + {!isUpdateView && ( + <> + + + + + {RuleI18n.CONTINUE} + + + + + )} + + ); +}; + +export const StepAboutRule = memo(StepAboutRuleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index 9355f1c8bfefa..fe43ac00ef947 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -23,6 +23,12 @@ import * as I18n from './translations'; const { emptyField } = fieldValidators; +const OptionalFieldLabel = ( + + {RuleI18n.OPTIONAL_FIELD} + +); + export const schema: FormSchema = { name: { type: FIELD_TYPES.TEXT, @@ -108,7 +114,7 @@ export const schema: FormSchema = { defaultMessage: 'Reference URLs', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, validations: [ { validator: ( @@ -136,10 +142,10 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldFalsePositiveLabel', { - defaultMessage: 'False positives', + defaultMessage: 'False positives examples', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, }, threats: { label: i18n.translate( @@ -148,7 +154,7 @@ export const schema: FormSchema = { defaultMessage: 'MITRE ATT&CK', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, validations: [ { validator: ( @@ -184,6 +190,6 @@ export const schema: FormSchema = { 'Type one or more custom identifying tags for this rule. Press enter after each tag to begin a new one.', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, }, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx new file mode 100644 index 0000000000000..956af6fe31f05 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx @@ -0,0 +1,14 @@ +/* + * 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 from 'react'; +import styled from 'styled-components'; + +const StyledDiv = styled.div` + padding-left: 53px; /* to align with the step title */ +`; + +export const StepContentWrapper = React.memo(StyledDiv); 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 ecd2ce442238f..baf9bd702e6cc 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 @@ -12,7 +12,7 @@ import { EuiButton, } from '@elastic/eui'; import { isEmpty, isEqual, get } from 'lodash/fp'; -import React, { memo, useCallback, useState, useEffect } from 'react'; +import React, { FC, memo, useCallback, useState, useEffect } from 'react'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules'; @@ -59,177 +59,178 @@ const getStepDefaultValue = ( } }; -export const StepDefineRule = memo( - ({ - defaultValues, - descriptionDirection = 'row', - isReadOnlyView, - isLoading, - isUpdateView = false, - resizeParentContainer, - setForm, - setStepData, - }) => { - const [openTimelineSearch, setOpenTimelineSearch] = useState(false); - const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false); - const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); - const [mylocalIndicesConfig, setMyLocalIndicesConfig] = useState( - defaultValues != null ? defaultValues.index : indicesConfig ?? [] - ); - const [ - { - browserFields, - indexPatterns: indexPatternQueryBar, - isLoading: indexPatternLoadingQueryBar, - }, - ] = useFetchIndexPatterns(mylocalIndicesConfig); - const [myStepData, setMyStepData] = useState( - getStepDefaultValue(indicesConfig, null) - ); - - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); - - const onSubmit = useCallback(async () => { - if (setStepData) { - setStepData(RuleStep.defineRule, null, false); - const { isValid, data } = await form.submit(); - if (isValid && setStepData) { - setStepData(RuleStep.defineRule, data, isValid); - setMyStepData({ ...data, isNew: false } as DefineStepRule); - } +export const StepDefineRuleComponent: FC = ({ + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isLoading, + isUpdateView = false, + resizeParentContainer, + setForm, + setStepData, +}) => { + const [openTimelineSearch, setOpenTimelineSearch] = useState(false); + const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false); + const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); + const [mylocalIndicesConfig, setMyLocalIndicesConfig] = useState( + defaultValues != null ? defaultValues.index : indicesConfig ?? [] + ); + const [ + { browserFields, indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, + ] = useFetchIndexPatterns(mylocalIndicesConfig); + const [myStepData, setMyStepData] = useState( + getStepDefaultValue(indicesConfig, null) + ); + + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); + + const onSubmit = useCallback(async () => { + if (setStepData) { + setStepData(RuleStep.defineRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid && setStepData) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); } - }, [form]); - - useEffect(() => { - if (indicesConfig != null && defaultValues != null) { - const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); - 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); - } - }); - } + } + }, [form]); + + useEffect(() => { + if (indicesConfig != null && defaultValues != null) { + const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); + 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); + } + }); } } - }, [defaultValues, indicesConfig]); + } + }, [defaultValues, indicesConfig]); - useEffect(() => { - if (setForm != null) { - setForm(RuleStep.defineRule, form); - } - }, [form]); - - const handleResetIndices = useCallback(() => { - const indexField = form.getFields().index; - indexField.setValue(indicesConfig); - }, [form, indicesConfig]); - - const handleOpenTimelineSearch = useCallback(() => { - setOpenTimelineSearch(true); - }, []); - - const handleCloseTimelineSearch = useCallback(() => { - setOpenTimelineSearch(false); - }, []); - - return isReadOnlyView && myStepData != null ? ( - - ) : ( - <> -
- - {i18n.RESET_DEFAULT_INDEX} - - ) : null, - }} - componentProps={{ - idAria: 'detectionEngineStepDefineRuleIndices', - 'data-test-subj': 'detectionEngineStepDefineRuleIndices', - euiFieldProps: { - fullWidth: true, - isDisabled: isLoading, - placeholder: '', - }, - }} - /> - - {i18n.IMPORT_TIMELINE_QUERY} - - ), - }} - component={QueryBarDefineRule} - componentProps={{ - browserFields, - loading: indexPatternLoadingQueryBar, - idAria: 'detectionEngineStepDefineRuleQueryBar', - indexPattern: indexPatternQueryBar, + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.defineRule, form); + } + }, [form]); + + const handleResetIndices = useCallback(() => { + const indexField = form.getFields().index; + indexField.setValue(indicesConfig); + }, [form, indicesConfig]); + + const handleOpenTimelineSearch = useCallback(() => { + setOpenTimelineSearch(true); + }, []); + + const handleCloseTimelineSearch = useCallback(() => { + setOpenTimelineSearch(false); + }, []); + + return isReadOnlyView && myStepData != null ? ( + + ) : ( + <> + + + {i18n.RESET_DEFAULT_INDEX} + + ) : null, + }} + componentProps={{ + idAria: 'detectionEngineStepDefineRuleIndices', + 'data-test-subj': 'detectionEngineStepDefineRuleIndices', + euiFieldProps: { + fullWidth: true, isDisabled: isLoading, - isLoading: indexPatternLoadingQueryBar, - dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', - openTimelineSearch, - onCloseTimelineSearch: handleCloseTimelineSearch, - resizeParentContainer, - }} - /> - - {({ index }) => { - if (index != null) { - if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { - setLocalUseIndicesConfig(true); - } - if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { - setLocalUseIndicesConfig(false); - } - if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { - setMyLocalIndicesConfig(index); - } + placeholder: '', + }, + }} + /> + + {i18n.IMPORT_TIMELINE_QUERY} + + ), + }} + component={QueryBarDefineRule} + componentProps={{ + browserFields, + loading: indexPatternLoadingQueryBar, + idAria: 'detectionEngineStepDefineRuleQueryBar', + indexPattern: indexPatternQueryBar, + isDisabled: isLoading, + isLoading: indexPatternLoadingQueryBar, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + openTimelineSearch, + onCloseTimelineSearch: handleCloseTimelineSearch, + resizeParentContainer, + }} + /> + + {({ index }) => { + if (index != null) { + if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { + setLocalUseIndicesConfig(true); } - return null; - }} - - - {!isUpdateView && ( - <> - - - - - {RuleI18n.CONTINUE} - - - - - )} - - ); - } -); + if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { + setLocalUseIndicesConfig(false); + } + if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { + setMyLocalIndicesConfig(index); + } + } + return null; + }} + + + {!isUpdateView && ( + <> + + + + + {RuleI18n.CONTINUE} + + + + + )} + + ); +}; + +export const StepDefineRule = memo(StepDefineRuleComponent); 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 848b17aadbff4..a07384e52bdb1 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 @@ -19,6 +19,7 @@ import { FormData, FormHook } from '../components/shared_imports'; import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; +import { StepContentWrapper } from '../components/step_content_wrapper'; import * as RuleI18n from '../translations'; import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; @@ -240,13 +241,16 @@ export const CreateRuleComponent = React.memo(() => { } > - setHeightAccordion(height)} - /> + + setHeightAccordion(height)} + descriptionDirection={'column'} + /> + @@ -271,12 +275,15 @@ export const CreateRuleComponent = React.memo(() => { } > - + + + @@ -301,12 +308,15 @@ export const CreateRuleComponent = React.memo(() => { } > - + + + 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 9b6998ab4a132..fdd7557b863e2 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 @@ -216,7 +216,7 @@ const RuleDetailsComponent = memo( {aboutRuleData != null && ( Date: Fri, 10 Jan 2020 15:38:58 +0100 Subject: [PATCH 2/7] cleanup --- .../rules/components/step_about_rule/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts index 052986480e9ab..83090a743fc86 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts @@ -16,7 +16,7 @@ export const ADD_REFERENCE = i18n.translate( export const ADD_FALSE_POSITIVE = i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRuleForm.addFalsePositiveDescription', { - defaultMessage: 'Add false positive', + defaultMessage: 'Add false positive exmaple', } ); From b99674f6ade7ca6627d211be6e9f3f027d09f00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopycin=CC=81ski?= Date: Fri, 10 Jan 2020 15:49:27 +0100 Subject: [PATCH 3/7] cleanup --- .../rules/components/description_step/helpers.tsx | 1 - 1 file changed, 1 deletion(-) 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 62cbc4f4a60a0..8eb1f0496e18c 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 @@ -13,7 +13,6 @@ import { EuiText, EuiListGroup, } from '@elastic/eui'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { isEmpty } from 'lodash/fp'; import React from 'react'; From 7353aaa5bbaeb250f2c3889b892d14a8acc68e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopycin=CC=81ski?= Date: Sat, 11 Jan 2020 14:22:07 +0100 Subject: [PATCH 4/7] fix accordion overflow --- .../rules/components/query_bar/index.tsx | 2 +- .../components/step_define_rule/index.tsx | 4 +-- .../detection_engine/rules/create/index.tsx | 31 ++++++------------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx index 3e39beb6e61b7..46a7a13ec03f1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx @@ -51,7 +51,7 @@ interface QueryBarDefineRuleProps { const StyledEuiFormRow = styled(EuiFormRow)` .kbnTypeahead__items { - max-height: 14vh !important; + max-height: 45vh !important; } .globalQueryBar { padding: 4px 0px 0px 0px; 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 baf9bd702e6cc..3b929fc2005b6 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 @@ -59,13 +59,12 @@ const getStepDefaultValue = ( } }; -export const StepDefineRuleComponent: FC = ({ +const StepDefineRuleComponent: FC = ({ defaultValues, descriptionDirection = 'row', isReadOnlyView, isLoading, isUpdateView = false, - resizeParentContainer, setForm, setStepData, }) => { @@ -192,7 +191,6 @@ export const StepDefineRuleComponent: FC = ({ dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', openTimelineSearch, onCloseTimelineSearch: handleCloseTimelineSearch, - resizeParentContainer, }} /> 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 a07384e52bdb1..682e43d5d4e09 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 @@ -27,26 +27,17 @@ import * as i18n from './translations'; const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; -const ResizeEuiPanel = styled(EuiPanel)<{ - height?: number; +const MyEuiPanel = styled(EuiPanel)<{ + zIndex?: number; }>` + position: relative; + z-index: ${props => props.zIndex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ + .euiAccordion__iconWrapper { display: none; } .euiAccordion__childWrapper { - height: ${props => (props.height !== -1 ? `${props.height}px !important` : 'auto')}; - } - .euiAccordion__button { - cursor: default !important; - &:hover { - text-decoration: none !important; - } - } -`; - -const MyEuiPanel = styled(EuiPanel)` - .euiAccordion__iconWrapper { - display: none; + overflow: visible; } .euiAccordion__button { cursor: default !important; @@ -57,7 +48,6 @@ const MyEuiPanel = styled(EuiPanel)` `; export const CreateRuleComponent = React.memo(() => { - const [heightAccordion, setHeightAccordion] = useState(-1); const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); @@ -220,7 +210,7 @@ export const CreateRuleComponent = React.memo(() => { isLoading={isLoading} title={i18n.PAGE_TITLE} /> - + { isLoading={isLoading} setForm={setStepsForm} setStepData={setStepData} - resizeParentContainer={height => setHeightAccordion(height)} descriptionDirection={'column'} /> - + - + { - + Date: Sun, 12 Jan 2020 18:16:25 +0100 Subject: [PATCH 5/7] fixes --- .../rules/components/add_item_form/index.tsx | 23 +- .../assets/list_tree_icon.svg | 1 + .../components/description_step/helpers.tsx | 87 ++++--- .../rules/components/mitre/index.tsx | 16 +- .../components/schedule_item_form/index.tsx | 31 ++- .../components/step_about_rule/index.tsx | 224 +++++++++--------- .../components/step_about_rule/schema.tsx | 2 +- .../components/step_content_wrapper/index.tsx | 8 +- .../components/step_define_rule/index.tsx | 156 ++++++------ .../components/step_schedule_rule/index.tsx | 186 ++++++++------- .../detection_engine/rules/create/index.tsx | 62 +++-- .../pages/detection_engine/rules/types.ts | 1 + 12 files changed, 441 insertions(+), 356 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx index b3cc81b5cdfcf..0c75da7d8a632 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx @@ -37,6 +37,25 @@ const MyEuiFormRow = styled(EuiFormRow)` } `; +export const MyAddItemButton = styled(EuiButtonEmpty)` + margin-top: 4px; + + &.euiButtonEmpty--xSmall { + font-size: 12px; + } + + .euiIcon { + width: 12px; + height: 12px; + } +`; + +MyAddItemButton.defaultProps = { + flush: 'left', + iconType: 'plusInCircle', + size: 'xs', +}; + export const AddItem = ({ addText, dataTestSubj, @@ -160,9 +179,9 @@ export const AddItem = ({ ); })} - + {addText} - + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg new file mode 100644 index 0000000000000..527d8d445bc03 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file 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 8eb1f0496e18c..e8b6919165c8b 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 @@ -10,8 +10,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, - EuiText, - EuiListGroup, + EuiButtonEmpty, + EuiSpacer, } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; @@ -26,6 +26,7 @@ import { FilterLabel } from './filter_label'; import * as i18n from './translations'; import { BuildQueryBarDescription, BuildThreatsDescription, ListItems } from './types'; import { SeverityBadge } from '../severity_badge'; +import ListTreeIcon from './assets/list_tree_icon.svg'; const isNotEmptyArray = (values: string[]) => !isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0; @@ -99,10 +100,17 @@ const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` } `; -const MyEuiListGroup = styled(EuiListGroup)` - padding: 0px; - .euiListGroupItem__button { - padding: 0px; +const TechniqueLinkItem = styled(EuiButtonEmpty)` + .euiIcon { + width: 8px; + height: 8px; + } +`; + +const ReferenceLinkItem = styled(EuiButtonEmpty)` + .euiIcon { + width: 12px; + height: 12px; } `; @@ -120,28 +128,31 @@ export const buildThreatsDescription = ({ const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); return ( - -
- - {tactic != null ? tactic.text : ''} - -
- { - const myTechnique = techniquesOptions.find(t => t.name === technique.name); - return { - label: myTechnique != null ? myTechnique.label : '', - href: technique.reference, - target: '_blank', - }; - })} - /> -
+ + {tactic != null ? tactic.text : ''} + + + {threat.techniques.map(technique => { + const myTechnique = techniquesOptions.find(t => t.name === technique.name); + return ( + + + {myTechnique != null ? myTechnique.label : ''} + + + ); + })} +
); })} + ), }, @@ -211,17 +222,21 @@ export const buildUrlsDescription = (label: string, values: string[]): ListItems { title: label, description: ( - ({ - label: val, - href: val, - iconType: 'link', - size: 'xs', - target: '_blank', - }))} - /> + + {values.map((val: string) => ( + + + {val} + + + ))} + ), }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx index 2c19e99e90114..f9a22c37cfdf0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx @@ -5,7 +5,6 @@ */ import { - EuiButtonEmpty, EuiButtonIcon, EuiFormRow, EuiSuperSelect, @@ -24,6 +23,7 @@ import * as Rulei18n from '../../translations'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; import { threatsDefault } from '../step_about_rule/default_value'; import { IMitreEnterpriseAttack } from '../../types'; +import { MyAddItemButton } from '../add_item_form'; import { isMitreAttackInvalid } from './helpers'; import * as i18n from './translations'; @@ -134,13 +134,19 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI const getSelectTechniques = (item: IMitreEnterpriseAttack, index: number, disabled: boolean) => { const invalid = isMitreAttackInvalid(item.tactic.name, item.techniques); + const options = techniquesOptions.filter(t => t.tactics.includes(kebabCase(item.tactic.name))); + const selectedOptions = item.techniques.map(technic => ({ + ...technic, + label: `${technic.name} (${technic.id})`, // API doesn't allow for label field + })); + return ( t.tactics.includes(kebabCase(item.tactic.name)))} - selectedOptions={item.techniques} + options={options} + selectedOptions={selectedOptions} onChange={updateTechniques.bind(null, index)} isDisabled={disabled || item.tactic.name === 'none'} fullWidth={true} @@ -202,9 +208,9 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI {values.length - 1 !== index && } ))} - + {i18n.ADD_MITRE_ATTACK} - + ); }; 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 8097c27cddfe8..a524dc079fb03 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 @@ -4,7 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiFieldNumber, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFieldNumber, + EuiFormRow, + EuiSelect, + EuiFormControlLayout, +} from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; @@ -27,9 +34,20 @@ const timeTypeOptions = [ ]; const StyledEuiFormRow = styled(EuiFormRow)` + max-width: none; + .euiFormControlLayout { max-width: 200px !important; } + + .euiFormControlLayout__childrenWrapper > *:first-child { + box-shadow: none; + height: 38px; + } + + .euiFormControlLayout:not(:first-child) { + border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; + } `; const MyEuiSelect = styled(EuiSelect)` @@ -107,7 +125,7 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu data-test-subj={dataTestSubj} describedByIds={idAria ? [idAria] : undefined} > - } - fullWidth - min={0} - onChange={onChangeTimeVal} - value={timeVal} - {...rest} - /> + > + + ); }; 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 4433c11e037a4..0e03a11776fb7 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 @@ -22,6 +22,7 @@ import { isUrlInvalid } from './helpers'; import { schema } from './schema'; import * as I18n from './translations'; import { PickTimeline } from '../pick_timeline'; +import { StepContentWrapper } from '../step_content_wrapper'; const CommonUseField = getUseField({ component: Field }); @@ -34,6 +35,7 @@ const TagContainer = styled.div` `; const StepAboutRuleComponent: FC = ({ + addPadding = false, defaultValues, descriptionDirection = 'row', isReadOnlyView, @@ -87,123 +89,127 @@ const StepAboutRuleComponent: FC = ({ }, [form]); return isReadOnlyView && myStepData != null ? ( - + + + ) : ( <> -
- - - - - - - - - + + - - - {({ severity }) => { - const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; - const riskScoreField = form.getFields().riskScore; - if (newRiskScore != null && riskScoreField.value !== newRiskScore) { - riskScoreField.setValue(newRiskScore); - } - return null; - }} - - + + + + + + + + + + + + {({ severity }) => { + const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; + const riskScoreField = form.getFields().riskScore; + if (newRiskScore != null && riskScoreField.value !== newRiskScore) { + riskScoreField.setValue(newRiskScore); + } + return null; + }} + + + {!isUpdateView && ( <> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index b581f5fce30fa..dbde3431a173c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -142,7 +142,7 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldFalsePositiveLabel', { - defaultMessage: 'False positives examples', + defaultMessage: 'False positive examples', } ), labelAppend: OptionalFieldLabel, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx index 956af6fe31f05..b04a321dab05b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx @@ -7,8 +7,12 @@ import React from 'react'; import styled from 'styled-components'; -const StyledDiv = styled.div` - padding-left: 53px; /* to align with the step title */ +const StyledDiv = styled.div<{ addPadding: boolean }>` + padding-left: ${({ addPadding }) => addPadding && '53px'}; /* to align with the step title */ `; +StyledDiv.defaultProps = { + addPadding: false, +}; + export const StepContentWrapper = React.memo(StyledDiv); 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 3b929fc2005b6..5d0a1c763251a 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 @@ -13,6 +13,7 @@ import { } from '@elastic/eui'; import { isEmpty, isEqual, get } from 'lodash/fp'; import React, { FC, memo, useCallback, useState, useEffect } from 'react'; +import styled from 'styled-components'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules'; @@ -22,6 +23,7 @@ import * as RuleI18n from '../../translations'; import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; import { StepRuleDescription } from '../description_step'; import { QueryBarDefineRule } from '../query_bar'; +import { StepContentWrapper } from '../step_content_wrapper'; import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { schema } from './schema'; import * as i18n from './translations'; @@ -42,6 +44,20 @@ const stepDefineDefaultValue = { }, }; +const MyLabelButton = styled(EuiButtonEmpty)` + height: 18px; + font-size: 12px; + + .euiIcon { + width: 12px; + height: 12px; + } +`; + +MyLabelButton.defaultProps = { + flush: 'right', +}; + const getStepDefaultValue = ( indicesConfig: string[], defaultValues: DefineStepRule | null @@ -60,6 +76,7 @@ const getStepDefaultValue = ( }; const StepDefineRuleComponent: FC = ({ + addPadding = false, defaultValues, descriptionDirection = 'row', isReadOnlyView, @@ -136,80 +153,79 @@ const StepDefineRuleComponent: FC = ({ }, []); return isReadOnlyView && myStepData != null ? ( - + + + ) : ( <> -
- - {i18n.RESET_DEFAULT_INDEX} - - ) : null, - }} - componentProps={{ - idAria: 'detectionEngineStepDefineRuleIndices', - 'data-test-subj': 'detectionEngineStepDefineRuleIndices', - euiFieldProps: { - fullWidth: true, + + + + {i18n.RESET_DEFAULT_INDEX} + + ) : null, + }} + componentProps={{ + idAria: 'detectionEngineStepDefineRuleIndices', + 'data-test-subj': 'detectionEngineStepDefineRuleIndices', + euiFieldProps: { + fullWidth: true, + isDisabled: isLoading, + placeholder: '', + }, + }} + /> + + {i18n.IMPORT_TIMELINE_QUERY} + + ), + }} + component={QueryBarDefineRule} + componentProps={{ + browserFields, + loading: indexPatternLoadingQueryBar, + idAria: 'detectionEngineStepDefineRuleQueryBar', + indexPattern: indexPatternQueryBar, isDisabled: isLoading, - placeholder: '', - }, - }} - /> - - {i18n.IMPORT_TIMELINE_QUERY} - - ), - }} - component={QueryBarDefineRule} - componentProps={{ - browserFields, - loading: indexPatternLoadingQueryBar, - idAria: 'detectionEngineStepDefineRuleQueryBar', - indexPattern: indexPatternQueryBar, - isDisabled: isLoading, - isLoading: indexPatternLoadingQueryBar, - dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', - openTimelineSearch, - onCloseTimelineSearch: handleCloseTimelineSearch, - }} - /> - - {({ index }) => { - if (index != null) { - if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { - setLocalUseIndicesConfig(true); - } - if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { - setLocalUseIndicesConfig(false); + isLoading: indexPatternLoadingQueryBar, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + openTimelineSearch, + onCloseTimelineSearch: handleCloseTimelineSearch, + }} + /> + + {({ index }) => { + if (index != null) { + if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { + setLocalUseIndicesConfig(true); + } + if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { + setLocalUseIndicesConfig(false); + } + if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { + setMyLocalIndicesConfig(index); + } } - if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { - setMyLocalIndicesConfig(index); - } - } - return null; - }} - - + return null; + }} + + + {!isUpdateView && ( <> 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 35b8ca6650bf6..b99201abe8777 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 @@ -6,12 +6,13 @@ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; import { isEqual, get } from 'lodash/fp'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types'; import { StepRuleDescription } from '../description_step'; import { ScheduleItem } from '../schedule_item_form'; import { Form, UseField, useForm } from '../shared_imports'; +import { StepContentWrapper } from '../step_content_wrapper'; import { schema } from './schema'; import * as I18n from './translations'; @@ -26,67 +27,70 @@ const stepScheduleDefaultValue = { from: '0m', }; -export const StepScheduleRule = memo( - ({ - defaultValues, - descriptionDirection = 'row', - isReadOnlyView, - isLoading, - isUpdateView = false, - setStepData, - setForm, - }) => { - const [myStepData, setMyStepData] = useState(stepScheduleDefaultValue); +const StepScheduleRuleComponent: FC = ({ + addPadding = false, + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isLoading, + isUpdateView = false, + setStepData, + setForm, +}) => { + const [myStepData, setMyStepData] = useState(stepScheduleDefaultValue); - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); - const onSubmit = useCallback( - async (enabled: boolean) => { - if (setStepData) { - setStepData(RuleStep.scheduleRule, null, false); - const { isValid: newIsValid, data } = await form.submit(); - if (newIsValid) { - setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); - setMyStepData({ ...data, isNew: false } as ScheduleStepRule); - } - } - }, - [form] - ); - - useEffect(() => { - const { isNew, ...initDefaultValue } = myStepData; - if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { - const myDefaultValues = { - ...defaultValues, - isNew: false, - }; - setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); + const onSubmit = useCallback( + async (enabled: boolean) => { + if (setStepData) { + setStepData(RuleStep.scheduleRule, null, false); + const { isValid: newIsValid, data } = await form.submit(); + if (newIsValid) { + setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); + setMyStepData({ ...data, isNew: false } as ScheduleStepRule); } } - }, [defaultValues]); + }, + [form] + ); - useEffect(() => { - if (setForm != null) { - setForm(RuleStep.scheduleRule, form); + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } - }, [form]); + } + }, [defaultValues]); - return isReadOnlyView && myStepData != null ? ( + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.scheduleRule, form); + } + }, [form]); + + return isReadOnlyView && myStepData != null ? ( + - ) : ( - <> + + ) : ( + <> +
( }} /> +
+ + {!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 b0f6558f7ec9f..e5656f5b081fb 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 @@ -20,7 +20,6 @@ import { FormData, FormHook } from '../components/shared_imports'; import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; -import { StepContentWrapper } from '../components/step_content_wrapper'; import * as RuleI18n from '../translations'; import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; @@ -250,19 +249,18 @@ export const CreateRuleComponent = React.memo(() => { ) } > - - - - + +
- + { ) } > - - - - + + - + { ) } > - - - - + + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts index 541b058951be7..13b328e9061c9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts @@ -57,6 +57,7 @@ export interface RuleStepData { } export interface RuleStepProps { + addPadding?: boolean; descriptionDirection?: 'row' | 'column'; setStepData?: (step: RuleStep, data: unknown, isValid: boolean) => void; isReadOnlyView: boolean; From 70b7a78e333c27ec8126a5707cd7ccecbe5605a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopycin=CC=81ski?= Date: Mon, 13 Jan 2020 12:19:45 +0100 Subject: [PATCH 6/7] icon size --- .../rules/components/step_define_rule/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5d0a1c763251a..6bdef4a69af1e 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 @@ -49,8 +49,8 @@ const MyLabelButton = styled(EuiButtonEmpty)` font-size: 12px; .euiIcon { - width: 12px; - height: 12px; + width: 14px; + height: 14px; } `; From 5d03a4616c710320b6fca92eed8316f4bc3a4e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopycin=CC=81ski?= Date: Mon, 13 Jan 2020 18:11:12 +0100 Subject: [PATCH 7/7] PR comments --- .../components/optional_field_label/index.tsx | 16 ++++++++++++++++ .../components/schedule_item_form/index.tsx | 11 +++++++++-- .../rules/components/step_about_rule/schema.tsx | 10 +--------- .../components/step_schedule_rule/schema.tsx | 6 ++---- 4 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx new file mode 100644 index 0000000000000..0dab87b0a3b74 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx @@ -0,0 +1,16 @@ +/* + * 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 { EuiText } from '@elastic/eui'; +import React from 'react'; + +import * as RuleI18n from '../../translations'; + +export const OptionalFieldLabel = ( + + {RuleI18n.OPTIONAL_FIELD} + +); 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 a524dc079fb03..fa4bea319f859 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 @@ -33,6 +33,13 @@ const timeTypeOptions = [ { value: 'h', text: I18n.HOURS }, ]; +// move optional label to the end of input +const StyledLabelAppend = styled(EuiFlexItem)` + &.euiFlexItem.euiFlexItem--flexGrowZero { + margin-left: 31px; + } +`; + const StyledEuiFormRow = styled(EuiFormRow)` max-width: none; @@ -107,9 +114,9 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu {field.label} - + {field.labelAppend} - +
), [field.label, field.labelAppend] diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index dbde3431a173c..3de0e7605f3d9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -4,11 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -import * as RuleI18n from '../../translations'; import { IMitreEnterpriseAttack } from '../../types'; import { FIELD_TYPES, @@ -18,17 +15,12 @@ import { ERROR_CODE, } from '../shared_imports'; import { isMitreAttackInvalid } from '../mitre/helpers'; +import { OptionalFieldLabel } from '../optional_field_label'; import { isUrlInvalid } from './helpers'; import * as I18n from './translations'; const { emptyField } = fieldValidators; -const OptionalFieldLabel = ( - - {RuleI18n.OPTIONAL_FIELD} - -); - export const schema: FormSchema = { name: { type: FIELD_TYPES.TEXT, 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 31e56265dec42..4da17b88b9ad0 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 @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiText } from '@elastic/eui'; -import React from 'react'; import { i18n } from '@kbn/i18n'; -import * as RuleI18n from '../../translations'; +import { OptionalFieldLabel } from '../optional_field_label'; import { FormSchema } from '../shared_imports'; export const schema: FormSchema = { @@ -33,7 +31,7 @@ export const schema: FormSchema = { defaultMessage: 'Additional look-back', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', {