import React , { useCallback , useEffect , useReducer } from 'react' ; import { EuiFlexGroup , EuiFlexItem } from '@elastic/eui' ; import styled from 'styled-components' ; import { HttpStart } from 'kibana/public' ; import { addIdToItem } from '@kbn/securitysolution-utils' ; import { CreateExceptionListItemSchema , ExceptionListItemSchema , ExceptionListType , NamespaceType , ListOperatorEnum as OperatorEnum , ListOperatorTypeEnum as OperatorTypeEnum , OsTypeArray , entriesNested , exceptionListItemSchema , } from '@kbn/securitysolution-io-ts-list-types' ; import { CreateExceptionListItemBuilderSchema , ExceptionsBuilderExceptionItem , OperatorOption , containsValueListEntry , filterExceptionItems , getDefaultEmptyEntry , getDefaultNestedEmptyEntry , getNewExceptionItem , } from '@kbn/securitysolution-list-utils' ; import { DataViewBase } from '@kbn/es-query' ; import type { AutocompleteStart } from '../../../../../../../src/plugins/data/public' ; import { AndOrBadge } from '../and_or_badge' ; import { BuilderExceptionListItemComponent } from './exception_item_renderer' ; import { BuilderLogicButtons } from './logic_buttons' ; import { State , exceptionsBuilderReducer } from './reducer' ; const MyInvisibleAndBadge = styled ( EuiFlexItem ) `
  visibility: hidden;
` ; const MyAndBadge = styled ( AndOrBadge ) `
  & > .euiFlexItem {
    margin: 0;
  }
` ; const MyButtonsContainer = styled ( EuiFlexItem ) `
  margin: 16px 0;
` ; const initialState = { addNested : false , andLogicIncluded : false , disableAnd : false , disableNested : false , disableOr : false , errorExists : 0 , exceptions : [ ] , exceptionsToDelete : [ ] , } ; export interface OnChangeProps { errorExists : boolean ; exceptionItems : Array < ExceptionListItemSchema | CreateExceptionListItemSchema > ; exceptionsToDelete : ExceptionListItemSchema [ ] ; } export interface ExceptionBuilderProps { allowLargeValueLists : boolean ; autocompleteService : AutocompleteStart ; exceptionListItems : ExceptionsBuilderExceptionItem [ ] ; httpService : HttpStart ; osTypes ? : OsTypeArray ; indexPatterns : DataViewBase ; isAndDisabled : boolean ; isNestedDisabled : boolean ; isOrDisabled : boolean ; isOrHidden ? : boolean ; listId : string ; listNamespaceType : NamespaceType ; listType : ExceptionListType ; listTypeSpecificIndexPatternFilter ? : ( pattern : DataViewBase , type : ExceptionListType ) => DataViewBase ; onChange : ( arg : OnChangeProps ) => void ; ruleName : string ; isDisabled ? : boolean ; operatorsList ? : OperatorOption [ ] ; } export const ExceptionBuilderComponent = ( { allowLargeValueLists , autocompleteService , exceptionListItems , httpService , indexPatterns , isAndDisabled , isNestedDisabled , isOrDisabled , isOrHidden = false , listId , listNamespaceType , listType , listTypeSpecificIndexPatternFilter , onChange , ruleName , isDisabled = false , osTypes , operatorsList , } ) : JSX . Element => { const [ { addNested , andLogicIncluded , disableAnd , disableNested , disableOr , errorExists , exceptions , exceptionsToDelete , } , dispatch , ] = useReducer ( exceptionsBuilderReducer ( ) , { ... initialState , disableAnd : isAndDisabled , disableNested : isNestedDisabled , disableOr : isOrDisabled , } ) ; const setErrorsExist = useCallback ( ( hasErrors ) : void => { dispatch ( { errorExists : hasErrors , type : 'setErrorsExist' , } ) ; } , [ dispatch ] ) ; const setUpdateExceptions = useCallback ( ( items ) : void => { dispatch ( { exceptions : items , type : 'setExceptions' , } ) ; } , [ dispatch ] ) ; const setDefaultExceptions = useCallback ( ( item ) : void => { dispatch ( { initialState , lastException : item , type : 'setDefault' , } ) ; } , [ dispatch ] ) ; const setUpdateExceptionsToDelete = useCallback ( ( items ) : void => { dispatch ( { exceptions : items , type : 'setExceptionsToDelete' , } ) ; } , [ dispatch ] ) ; const setUpdateAndDisabled = useCallback ( ( shouldDisable ) : void => { dispatch ( { shouldDisable , type : 'setDisableAnd' , } ) ; } , [ dispatch ] ) ; const setUpdateOrDisabled = useCallback ( ( shouldDisable ) : void => { dispatch ( { shouldDisable , type : 'setDisableOr' , } ) ; } , [ dispatch ] ) ; const setUpdateAddNested = useCallback ( ( shouldAddNested ) : void => { dispatch ( { addNested : shouldAddNested , type : 'setAddNested' , } ) ; } , [ dispatch ] ) ; const handleExceptionItemChange = useCallback ( ( item , index ) : void => { const updatedExceptions = [ ... exceptions . slice ( 0 , index ) , { ... item , } , ... exceptions . slice ( index + 1 ) , ] ; setUpdateExceptions ( updatedExceptions ) ; } , [ setUpdateExceptions , exceptions ] ) ; const handleDeleteExceptionItem = useCallback ( ( item , itemIndex ) : void => { if ( item . entries . length === 0 ) { const updatedExceptions = [ ... exceptions . slice ( 0 , itemIndex ) , ... exceptions . slice ( itemIndex + 1 ) , ] ; if ( updatedExceptions . length === 0 ) { setDefaultExceptions ( item ) ; } else if ( updatedExceptions . length > 0 && exceptionListItemSchema . is ( item ) ) { setUpdateExceptionsToDelete ( [ ... exceptionsToDelete , item ] ) ; } else { setUpdateExceptions ( [ ... exceptions . slice ( 0 , itemIndex ) , ... exceptions . slice ( itemIndex + 1 ) , ] ) ; } } else { handleExceptionItemChange ( item , itemIndex ) ; } } , [ handleExceptionItemChange , setUpdateExceptions , setUpdateExceptionsToDelete , exceptions , exceptionsToDelete , setDefaultExceptions , ] ) ; const handleAddNewExceptionItemEntry = useCallback ( ( isNested = false ) : void => { const lastException = exceptions [ exceptions . length - 1 ] ; const { entries } = lastException ; const updatedException = { ... lastException , entries : [ ... entries , isNested ? getDefaultNestedEmptyEntry ( ) : getDefaultEmptyEntry ( ) ] , } ; setUpdateExceptions ( [ ... exceptions . slice ( 0 , exceptions . length - 1 ) , { ... updatedException } ] ) ; } , [ setUpdateExceptions , exceptions ] ) ; const handleAddNewExceptionItem = useCallback ( ( ) : void => { const newException = getNewExceptionItem ( { listId , namespaceType : listNamespaceType , ruleName , } ) ; setUpdateExceptions ( [ ... exceptions , { ... newException } ] ) ; } , [ setUpdateExceptions , exceptions , listId , listNamespaceType , ruleName ] ) ; const getExceptionListItemId = ( item , index ) : string => { if ( ( item as ExceptionListItemSchema ) . id != null ) { return ( item as ExceptionListItemSchema ) . id ; } else if ( ( item as CreateExceptionListItemBuilderSchema ) . meta . temporaryUuid != null ) { return ( item as CreateExceptionListItemBuilderSchema ) . meta . temporaryUuid ; } else { return `${ index }` ; } } ; const handleAddNestedExceptionItemEntry = useCallback ( ( ) : void => { const lastException = exceptions [ exceptions . length - 1 ] ; const { entries } = lastException ; const lastEntry = entries [ entries . length - 1 ] ; if ( entriesNested . is ( lastEntry ) ) { const updatedException = { ... lastException , entries : [ ... entries . slice ( 0 , entries . length - 1 ) , { ... lastEntry , entries : [ ... lastEntry . entries , addIdToItem ( { field : '' , operator : OperatorEnum . INCLUDED , type : OperatorTypeEnum . MATCH , value : '' , } ) , ] , } , ] , } ; setUpdateExceptions ( [ ... exceptions . slice ( 0 , exceptions . length - 1 ) , { ... updatedException } ] ) ; } else { setUpdateExceptions ( exceptions ) ; } } , [ setUpdateExceptions , exceptions ] ) ; const handleAddNestedClick = useCallback ( ( ) : void => { setUpdateAddNested ( true ) ; setUpdateOrDisabled ( true ) ; setUpdateAndDisabled ( true ) ; handleAddNewExceptionItemEntry ( true ) ; } , [ handleAddNewExceptionItemEntry , setUpdateAndDisabled , setUpdateOrDisabled , setUpdateAddNested , ] ) ; const handleAddClick = useCallback ( ( ) : void => { setUpdateAddNested ( false ) ; setUpdateOrDisabled ( false ) ; handleAddNewExceptionItemEntry ( ) ; } , [ handleAddNewExceptionItemEntry , setUpdateOrDisabled , setUpdateAddNested ] ) ; useEffect ( ( ) => { onChange ( { errorExists : errorExists > 0 , exceptionItems : filterExceptionItems ( exceptions ) , exceptionsToDelete , } ) ; } , [ onChange , exceptionsToDelete , exceptions , errorExists ] ) ; useEffect ( ( ) => { setUpdateExceptions ( [ ] ) ; } , [ osTypes , setUpdateExceptions ] ) ; useEffect ( ( ) => { if ( exceptions . length === 0 || ( exceptions . length === 1 && exceptions [ 0 ] . entries != null && exceptions [ 0 ] . entries . length === 0 ) ) { handleAddNewExceptionItem ( ) ; } } , [ exceptions , handleAddNewExceptionItem ] ) ; useEffect ( ( ) => { if ( exceptionListItems . length > 0 ) { setUpdateExceptions ( exceptionListItems ) ; } } , [ ] ) ; return ( < EuiFlexGroup gutterSize = "s" direction = "column" data - test - subj = "exceptionsBuilderWrapper" > { exceptions . map ( ( exceptionListItem , index ) => ( < EuiFlexItem grow = { 1 } key = { getExceptionListItemId ( exceptionListItem , index ) } > < EuiFlexGroup gutterSize = "s" direction = "column" > { index !== 0 && ( andLogicIncluded ? ( < EuiFlexItem grow = { false } > < EuiFlexGroup gutterSize = "none" direction = "row" > < MyInvisibleAndBadge grow = { false } > < MyAndBadge includeAntennas type = "and" / > < /MyInvisibleAndBadge> < EuiFlexItem grow = { false } > < MyAndBadge type = "or" / > < /EuiFlexItem> < /EuiFlexGroup> < /EuiFlexItem>) : (<EuiFlexItem grow={false}> < MyAndBadge type = "or" / > < /EuiFlexItem>))} < EuiFlexItem grow = { false } > < BuilderExceptionListItemComponent allowLargeValueLists = { allowLargeValueLists } andLogicIncluded = { andLogicIncluded } autocompleteService = { autocompleteService } exceptionItem = { exceptionListItem } exceptionItemIndex = { index } httpService = { httpService } indexPattern = { indexPatterns } isOnlyItem = { exceptions . length === 1 } key = { getExceptionListItemId ( exceptionListItem , index ) } listType = { listType } listTypeSpecificIndexPatternFilter = { listTypeSpecificIndexPatternFilter } onChangeExceptionItem = { handleExceptionItemChange } onDeleteExceptionItem = { handleDeleteExceptionItem } onlyShowListOperators = { containsValueListEntry ( exceptions ) } setErrorsExist = { setErrorsExist } osTypes = { osTypes } isDisabled = { isDisabled } operatorsList = { operatorsList } / > < /EuiFlexItem> < /EuiFlexGroup> < /EuiFlexItem>))} < MyButtonsContainer data - test - subj = { `andOrOperatorButtons` } > < EuiFlexGroup gutterSize = "s" > { andLogicIncluded && ( < MyInvisibleAndBadge grow = { false } > < AndOrBadge includeAntennas type = "and" / > < /MyInvisibleAndBadge>)} < EuiFlexItem grow = { 1 } > < BuilderLogicButtons isOrDisabled = { isOrDisabled ? isOrDisabled : disableOr } isOrHidden = { isOrHidden } isAndDisabled = { isAndDisabled ? isAndDisabled : disableAnd } isNestedDisabled = { isNestedDisabled ? isNestedDisabled : disableNested } isNested = { addNested } showNestedButton onOrClicked = { handleAddNewExceptionItem } onAndClicked = { handleAddClick } onNestedClicked = { handleAddNestedClick } onAddClickWhenNested = { handleAddNestedExceptionItemEntry } / > < /EuiFlexItem> < /EuiFlexGroup> < /MyButtonsContainer> < /EuiFlexGroup>); } ; ExceptionBuilderComponent . displayName = 'ExceptionBuilder' ; export default ExceptionBuilderComponent ;