/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ import { EuiPageBody, EuiPageHeader, EuiPageHeaderSection, EuiFlexItem, EuiFlexGroup, EuiPage, EuiButton, EuiTitle, EuiButtonEmpty, EuiSpacer, EuiText, EuiLink, EuiIcon, } from '@elastic/eui'; import { FormikProps, Formik } from 'formik'; import { get, isEmpty } from 'lodash'; import React, { Fragment, useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RouteComponentProps } from 'react-router-dom'; import { AppState } from '../../../redux/reducers'; import { getMappings } from '../../../redux/reducers/opensearch'; import { useFetchDetectorInfo } from '../../CreateDetectorSteps/hooks/useFetchDetectorInfo'; import { BREADCRUMBS, BASE_DOCS_LINK } from '../../../utils/constants'; import { useHideSideNavBar } from '../../main/hooks/useHideSideNavBar'; import { updateDetector } from '../../../redux/reducers/ad'; import { validateFeatures, focusOnFirstWrongFeature, getCategoryFields, focusOnCategoryField, getShingleSizeFromObject, modelConfigurationToFormik, } from '../utils/helpers'; import { formikToDetector } from '../../ReviewAndCreate/utils/helpers'; import { formikToModelConfiguration } from '../utils/helpers'; import { Features } from '../components/Features'; import { CategoryField } from '../components/CategoryField'; import { AdvancedSettings } from '../components/AdvancedSettings'; import { SampleAnomalies } from './SampleAnomalies'; import { CoreStart } from '../../../../../../src/core/public'; import { CoreServicesContext } from '../../../components/CoreServices/CoreServices'; import { Detector } from '../../../models/interfaces'; import { prettifyErrorMessage } from '../../../../server/utils/helpers'; import { DetectorDefinitionFormikValues } from '../../DefineDetector/models/interfaces'; import { ModelConfigurationFormikValues } from '../models/interfaces'; import { CreateDetectorFormikValues } from '../../CreateDetectorSteps/models/interfaces'; import { DETECTOR_STATE } from '../../../../server/utils/constants'; import { getErrorMessage } from '../../../utils/utils'; interface ConfigureModelRouterProps { detectorId?: string; } interface ConfigureModelProps extends RouteComponentProps { isEdit: boolean; setStep?(stepNumber: number): void; initialValues?: ModelConfigurationFormikValues; setInitialValues?(initialValues: ModelConfigurationFormikValues): void; detectorDefinitionValues?: DetectorDefinitionFormikValues; } export function ConfigureModel(props: ConfigureModelProps) { const core = React.useContext(CoreServicesContext) as CoreStart; const dispatch = useDispatch(); useHideSideNavBar(true, false); const detectorId = get(props, 'match.params.detectorId', ''); const { detector, hasError } = useFetchDetectorInfo(detectorId); const indexDataTypes = useSelector( (state: AppState) => state.opensearch.dataTypes ); const [isHCDetector, setIsHCDetector] = useState( props.initialValues ? props.initialValues.categoryFieldEnabled : false ); const isLoading = useSelector( (state: AppState) => state.opensearch.requesting ); // Jump to top of page on first load useEffect(() => { scroll(0, 0); }, []); // When detector is loaded: get any category fields (if applicable) and // get all index mappings based on detector's selected index useEffect(() => { if (detector && get(detector, 'categoryField', []).length > 0) { setIsHCDetector(true); } if (detector?.indices) { dispatch(getMappings(detector.indices[0])); } }, [detector]); useEffect(() => { if (props.isEdit) { core.chrome.setBreadcrumbs([ BREADCRUMBS.ANOMALY_DETECTOR, BREADCRUMBS.DETECTORS, { text: detector && detector.name ? detector.name : '', href: `#/detectors/${detectorId}`, }, BREADCRUMBS.EDIT_MODEL_CONFIGURATION, ]); } else { core.chrome.setBreadcrumbs([ BREADCRUMBS.ANOMALY_DETECTOR, BREADCRUMBS.DETECTORS, BREADCRUMBS.CREATE_DETECTOR, ]); } }, [detector]); useEffect(() => { if (hasError) { props.history.push('/detectors'); } }, [hasError]); const handleFormValidation = async ( formikProps: FormikProps ) => { if (props.isEdit && detector.curState === DETECTOR_STATE.RUNNING) { core.notifications.toasts.addDanger( 'Detector cannot be updated while it is running' ); } else { formikProps.setSubmitting(true); formikProps.setFieldTouched('featureList'); formikProps.setFieldTouched('categoryField', isHCDetector); formikProps.setFieldTouched('shingleSize'); formikProps.validateForm().then((errors) => { if (isEmpty(errors)) { if (props.isEdit) { // TODO: possibly add logic to also start RT and/or historical from here. Need to think // about adding similar logic from edit detector definition page const detectorToUpdate = formikToModelConfiguration( formikProps.values, detector ); handleUpdateDetector(detectorToUpdate); } else { optionallySaveValues({ ...formikProps.values, categoryFieldEnabled: isHCDetector, }); //@ts-ignore props.setStep(3); } } else { // TODO: can add focus to all components or possibly customize error message too if (get(errors, 'featureList')) { focusOnFirstWrongFeature(errors, formikProps.setFieldTouched); } else if (get(errors, 'categoryField')) { focusOnCategoryField(); } core.notifications.toasts.addDanger( 'One or more input fields is invalid' ); } }); } formikProps.setSubmitting(false); }; const handleUpdateDetector = async (detectorToUpdate: Detector) => { dispatch(updateDetector(detectorId, detectorToUpdate)) .then((response: any) => { core.notifications.toasts.addSuccess( `Detector updated: ${response.response.name}` ); props.history.push(`/detectors/${detectorId}/configurations/`); }) .catch((err: any) => { core.notifications.toasts.addDanger( prettifyErrorMessage( getErrorMessage(err, 'There was a problem updating the detector') ) ); }); }; const optionallySaveValues = (values: ModelConfigurationFormikValues) => { if (props.setInitialValues) { props.setInitialValues(values); } }; const detectorToCreate = props.isEdit ? detector : formikToDetector({ ...props.detectorDefinitionValues, ...props.initialValues, } as CreateDetectorFormikValues); return ( {}} validateOnMount={props.isEdit ? false : true} validate={validateFeatures} > {(formikProps) => (

{props.isEdit ? 'Edit model configuration' : 'Configure model'}{' '}

Set the index fields that you want to find anomalies for by defining the model features. You can also set other model parameters such as category field and shingle size for more granular views. After you set the model features and other optional parameters, you can preview your anomalies from a sample feature output.{' '} Learn more
{!isEmpty(detectorToCreate) ? : null} {!isEmpty(detectorToCreate) ? ( ) : null}
{ if (props.isEdit) { props.history.push( `/detectors/${detectorId}/configurations/` ); } else { props.history.push('/detectors'); } }} > Cancel {props.isEdit ? null : ( { optionallySaveValues({ ...formikProps.values, categoryFieldEnabled: isHCDetector, }); //@ts-ignore props.setStep(1); }} > Previous )} {props.isEdit ? ( { handleFormValidation(formikProps); }} > Save changes ) : ( { handleFormValidation(formikProps); }} > Next )}
)}
); }