import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey' ; import React , { FC , Fragment , useState , useContext , useEffect } from 'react' ; import { EuiComboBox , EuiFlexItem , EuiFlexGroup , EuiFlexGrid , EuiComboBoxOptionOption , EuiHorizontalRule , EuiTextArea , } from '@elastic/eui' ; import { JobCreatorContext } from '../../../job_creator_context' ; import { AdvancedJobCreator } from '../../../../../common/job_creator' ; import { createFieldOptions , createMlcategoryFieldOption , } from '../../../../../common/job_creator/util/general' ; import { Field , Aggregation , EVENT_RATE_FIELD_ID , mlCategory , } from '../../../../../../../../../common/types/fields' ; import { RichDetector } from '../../../../../common/job_creator/advanced_job_creator' ; import { ModalWrapper } from './modal_wrapper' ; import { detectorToString } from '../../../../../../../util/string_utils' ; import { createBasicDetector } from '../../../../../common/job_creator/util/default_configs' ; import { AggDescription , FieldDescription , ByFieldDescription , OverFieldDescription , PartitionFieldDescription , ExcludeFrequentDescription , DescriptionDescription , } from './descriptions' ; interface Props { payload : ModalPayload ; fields : Field [ ] ; aggs : Aggregation [ ] ; detectorChangeHandler : ( dtr : RichDetector , index ? : number ) => void ; closeModal ( ) : void ; } export interface ModalPayload { detector : RichDetector ; index ? : number ; } const emptyOption = { label : '' , } ; const excludeFrequentOptions = [ { label : 'all' } , { label : 'none' } , { label : 'by' } , { label : 'over' } , ] ; export const AdvancedDetectorModal = ( { payload , fields , aggs , detectorChangeHandler , closeModal , } ) => { const { jobCreator : jc } = useContext ( JobCreatorContext ) ; const jobCreator = jc as AdvancedJobCreator ; const [ detector , setDetector ] = useState ( payload . detector ) ; const [ aggOption , setAggOption ] = useState ( createAggOption ( detector . agg ) ) ; const [ fieldOption , setFieldOption ] = useState ( createFieldOption ( detector . field ) ) ; const [ byFieldOption , setByFieldOption ] = useState ( createFieldOption ( detector . byField ) ) ; const [ overFieldOption , setOverFieldOption ] = useState ( createFieldOption ( detector . overField ) ) ; const [ partitionFieldOption , setPartitionFieldOption ] = useState ( createFieldOption ( detector . partitionField ) ) ; const [ excludeFrequentOption , setExcludeFrequentOption ] = useState ( createExcludeFrequentOption ( detector . excludeFrequent ) ) ; const [ descriptionOption , setDescriptionOption ] = useState ( detector . description || '' ) ; const [ splitFieldsEnabled , setSplitFieldsEnabled ] = useState ( true ) ; const [ excludeFrequentEnabled , setExcludeFrequentEnabled ] = useState ( true ) ; const [ fieldOptionEnabled , setFieldOptionEnabled ] = useState ( true ) ; const { descriptionPlaceholder , setDescriptionPlaceholder } = useDetectorPlaceholder ( detector ) ; const usingScriptFields = jobCreator . additionalFields . length > 0 ; const aggOptions = aggs . filter ( ( agg ) => filterAggs ( agg , usingScriptFields ) ) . map ( createAggOption ) ; const { currentFieldOptions , setCurrentFieldOptions } = useCurrentFieldOptions ( detector . agg , jobCreator . additionalFields , fields ) ; const allFieldOptions = [ ... createFieldOptions ( fields , jobCreator . additionalFields ) , ] . sort ( comboBoxOptionsSort ) ; const splitFieldOptions = [ ... allFieldOptions , ... createMlcategoryFieldOption ( jobCreator . categorizationFieldName ) , ] . sort ( comboBoxOptionsSort ) ; const eventRateField = fields . find ( ( f ) => f . id === EVENT_RATE_FIELD_ID ) ; const onOptionChange = ( func ) => ( selectedOptions ) => { func ( selectedOptions [ 0 ] || emptyOption ) ; } ; function getAgg ( title ) { return aggs . find ( ( a ) => a . id === title ) || null ; } function getField ( title ) { if ( title === mlCategory . id ) { return mlCategory ; } return ( fields . find ( ( f ) => f . id === title ) || jobCreator . additionalFields . find ( ( f ) => f . id === title ) || null ) ; } useEffect ( ( ) => { const agg = getAgg ( aggOption . label ) ; let field = getField ( fieldOption . label ) ; const byField = getField ( byFieldOption . label ) ; const overField = getField ( overFieldOption . label ) ; const partitionField = getField ( partitionFieldOption . label ) ; if ( agg !== null ) { setCurrentFieldOptions ( agg ) ; if ( isFieldlessAgg ( agg ) && eventRateField !== undefined ) { setSplitFieldsEnabled ( true ) ; setFieldOption ( emptyOption ) ; setFieldOptionEnabled ( false ) ; field = eventRateField ; } else { setSplitFieldsEnabled ( field !== null ) ; setFieldOptionEnabled ( true ) ; } setExcludeFrequentEnabled ( byField !== null || overField !== null ) ; } else { setSplitFieldsEnabled ( false ) ; setFieldOptionEnabled ( false ) ; } const dtr = { agg , field , byField , overField , partitionField , excludeFrequent : excludeFrequentOption . label !== '' ? ( excludeFrequentOption . label as estypes . MlExcludeFrequent ) : null , description : descriptionOption !== '' ? descriptionOption : null , customRules : null , useNull : null , } ; setDetector ( dtr ) ; setDescriptionPlaceholder ( dtr ) ; } , [ aggOption , fieldOption , byFieldOption , overFieldOption , partitionFieldOption , excludeFrequentOption , descriptionOption , ] ) ; useEffect ( ( ) => { const agg = getAgg ( aggOption . label ) ; setSplitFieldsEnabled ( aggOption . label !== '' ) ; if ( agg !== null ) { setFieldOptionEnabled ( isFieldlessAgg ( agg ) === false ) ; const byField = getField ( byFieldOption . label ) ; const overField = getField ( overFieldOption . label ) ; setExcludeFrequentEnabled ( byField !== null || overField !== null ) ; } } , [ ] ) ; useEffect ( ( ) => { if ( excludeFrequentEnabled === false ) { setExcludeFrequentOption ( emptyOption ) ; } } , [ excludeFrequentEnabled ] ) ; function onCreateClick ( ) { detectorChangeHandler ( detector , payload . index ) ; } function saveEnabled ( ) { return ( splitFieldsEnabled && ( fieldOptionEnabled === false || ( fieldOptionEnabled === true && fieldOption . label !== '' ) ) ) ; } return ( < ModalWrapper onCreateClick = { onCreateClick } closeModal = { closeModal } saveEnabled = { saveEnabled ( ) } > < Fragment > < EuiFlexGroup > < EuiFlexItem data - test - subj = "mlAdvancedFunctionSelect" > < AggDescription > < EuiComboBox singleSelection = { { asPlainText : true } } options = { aggOptions } selectedOptions = { createSelectedOptions ( aggOption , aggOptions ) } onChange = { onOptionChange ( setAggOption ) } isClearable = { true } / > < /AggDescription> < /EuiFlexItem> < EuiFlexItem data - test - subj = "mlAdvancedFieldSelect" > < FieldDescription > < EuiComboBox singleSelection = { { asPlainText : true } } options = { currentFieldOptions } selectedOptions = { createSelectedOptions ( fieldOption , currentFieldOptions ) } onChange = { onOptionChange ( setFieldOption ) } isClearable = { true } isDisabled = { fieldOptionEnabled === false } /> < /FieldDescription> < /EuiFlexItem> < /EuiFlexGroup> < EuiHorizontalRule margin = "l" / > < EuiFlexGrid columns = { 2 } > < EuiFlexItem data - test - subj = "mlAdvancedByFieldSelect" > < ByFieldDescription > < EuiComboBox singleSelection = { { asPlainText : true } } options = { splitFieldOptions } selectedOptions = { createSelectedOptions ( byFieldOption , splitFieldOptions ) } onChange = { onOptionChange ( setByFieldOption ) } isClearable = { true } isDisabled = { splitFieldsEnabled === false } /> < /ByFieldDescription> < /EuiFlexItem> < EuiFlexItem data - test - subj = "mlAdvancedOverFieldSelect" > < OverFieldDescription > < EuiComboBox singleSelection = { { asPlainText : true } } options = { splitFieldOptions } selectedOptions = { createSelectedOptions ( overFieldOption , splitFieldOptions ) } onChange = { onOptionChange ( setOverFieldOption ) } isClearable = { true } isDisabled = { splitFieldsEnabled === false } /> < /OverFieldDescription> < /EuiFlexItem> < EuiFlexItem data - test - subj = "mlAdvancedPartitionFieldSelect" > < PartitionFieldDescription > < EuiComboBox singleSelection = { { asPlainText : true } } options = { splitFieldOptions } selectedOptions = { createSelectedOptions ( partitionFieldOption , splitFieldOptions ) } onChange = { onOptionChange ( setPartitionFieldOption ) } isClearable = { true } isDisabled = { splitFieldsEnabled === false } /> < /PartitionFieldDescription> < /EuiFlexItem> < EuiFlexItem data - test - subj = "mlAdvancedExcludeFrequentSelect" > < ExcludeFrequentDescription > < EuiComboBox singleSelection = { { asPlainText : true } } options = { excludeFrequentOptions } selectedOptions = { createSelectedOptions ( excludeFrequentOption , excludeFrequentOptions ) } onChange = { onOptionChange ( setExcludeFrequentOption ) } isClearable = { true } isDisabled = { splitFieldsEnabled === false || excludeFrequentEnabled === false } /> < /ExcludeFrequentDescription> < /EuiFlexItem> < /EuiFlexGrid> < EuiHorizontalRule margin = "l" / > < EuiFlexGroup > < EuiFlexItem > < DescriptionDescription > < EuiTextArea rows = { 2 } fullWidth = { true } placeholder = { descriptionPlaceholder } value = { descriptionOption } onChange = { ( e ) => setDescriptionOption ( e . target . value ) } data - test - subj = "mlAdvancedDetectorDescriptionInput" / > < /DescriptionDescription> < /EuiFlexItem> < /EuiFlexGroup> < /Fragment> < /ModalWrapper>); } ; function createAggOption ( agg ) { if ( agg === null ) { return emptyOption ; } return { label : agg . id , } ; } function filterAggs ( agg , usingScriptFields ) { return agg . fields !== undefined && ( usingScriptFields || agg . fields . length ) ; } function createFieldOption ( field ) { if ( field === null ) { return emptyOption ; } return { label : field . name , } ; } function createExcludeFrequentOption ( excludeFrequent ) { if ( excludeFrequent === null ) { return emptyOption ; } return { label : excludeFrequent , } ; } function isFieldlessAgg ( agg ) { return agg . fields && agg . fields . length === 1 && agg . fields [ 0 ] . id === EVENT_RATE_FIELD_ID ; } function useDetectorPlaceholder ( detector ) { const [ descriptionPlaceholder , setDescriptionPlaceholderString ] = useState ( createDefaultDescription ( detector ) ) ; function setDescriptionPlaceholder ( dtr ) { setDescriptionPlaceholderString ( createDefaultDescription ( dtr ) ) ; } return { descriptionPlaceholder , setDescriptionPlaceholder } ; } function createFieldOptionsFromAgg ( agg , additionalFields ) { return createFieldOptions ( agg !== null && agg . fields !== undefined ? agg . fields : [ ] , additionalFields ) ; } function useCurrentFieldOptions ( aggregation , additionalFields , fields ) { const [ currentFieldOptions , setCurrentFieldOptions ] = useState ( createFieldOptionsFromAgg ( aggregation , additionalFields ) ) ; return { currentFieldOptions , setCurrentFieldOptions : ( agg ) => setCurrentFieldOptions ( createFieldOptionsFromAgg ( agg , additionalFields ) ) , } ; } function createDefaultDescription ( dtr ) { if ( dtr . agg === null || dtr . field === null ) { return '' ; } const basicDetector = createBasicDetector ( dtr . agg , dtr . field ) ; basicDetector . by_field_name = dtr . byField ? dtr . byField . id : undefined ; basicDetector . over_field_name = dtr . overField ? dtr . overField . id : undefined ; basicDetector . partition_field_name = dtr . partitionField ? dtr . partitionField . id : undefined ; basicDetector . exclude_frequent = dtr . excludeFrequent ? dtr . excludeFrequent : undefined ; return detectorToString ( basicDetector ) ; } function createSelectedOptions ( selectedOption , options ) { return ( options . length === 1 && options [ 0 ] . label !== selectedOption . label ) || selectedOption . label === '' ? [ ] : [ selectedOption ] ; } function comboBoxOptionsSort ( a , b ) { return a . label . localeCompare ( b . label ) ; }