diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index f85a5912..f026b79c 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -85,6 +85,7 @@ export type UiMetaData = { [key: string]: UiFeature; }; }; + export type Detector = { primaryTerm: number; seqNo: number; @@ -103,7 +104,7 @@ export type Detector = { enabledTime?: number; disabledTime?: number; curState: DETECTOR_STATE; - initializationError: string; + stateError: string; }; export type DetectorListItem = { diff --git a/public/pages/DetectorResults/components/DetectorState/DetectorInitializationFailure.tsx b/public/pages/DetectorResults/components/DetectorState/DetectorInitializationFailure.tsx deleted file mode 100644 index f1733634..00000000 --- a/public/pages/DetectorResults/components/DetectorState/DetectorInitializationFailure.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -import React from 'react'; -import { EuiButton, EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; -import { Fragment } from 'react'; -import { Detector } from '../../../../models/interfaces'; - -export interface DetectorInitializationFailureProps { - detector: Detector; - onStartDetector(): void; - failureDetail: any; - onSwitchToConfiguration(): void; -} - -export const DetectorInitializationFailure = ( - props: DetectorInitializationFailureProps -) => { - return ( - - - -

- {`The detector is not initialized because ${props.failureDetail.cause}`} -

- - } - body={ - -

{`${props.failureDetail.actionItem}`}

-
- } - actions={[ - - View detector configuration - , - - Restart detector - , - ]} - /> - ); -}; diff --git a/public/pages/DetectorResults/components/DetectorState/DetectorInitializing.tsx b/public/pages/DetectorResults/components/DetectorState/DetectorInitializing.tsx deleted file mode 100644 index 2a11d9a3..00000000 --- a/public/pages/DetectorResults/components/DetectorState/DetectorInitializing.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -import React from 'react'; -import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; -import { Fragment } from 'react'; -import { Detector } from '../../../../models/interfaces'; -import { - getDetectorInitializationInfo, - IS_INIT_OVERTIME_FIELD, - INIT_DETAILS_FIELD, - INIT_ERROR_MESSAGE_FIELD, - INIT_ACTION_ITEM_FIELD, -} from '../../utils/utils'; -import { get } from 'lodash'; - -export interface DetectorInitializingProps { - detector: Detector; - onSwitchToConfiguration(): void; -} - -export const DetectorInitializing = (props: DetectorInitializingProps) => { - const initializationInfo = getDetectorInitializationInfo(props.detector); - const isInitOvertime = get(initializationInfo, IS_INIT_OVERTIME_FIELD, false); - const initDetails = get(initializationInfo, INIT_DETAILS_FIELD, {}); - const initErrorMessage = get(initDetails, INIT_ERROR_MESSAGE_FIELD, ''); - const initActionItem = get(initDetails, INIT_ACTION_ITEM_FIELD, ''); - - return ( - - -

The detector is initializing...

- - } - body={ - - {!isInitOvertime - ? [ -

- Based on your latest update to the detector configuration, the - detector is collecting data to generate accurate real-time - anomalies. -

, -

- The longer the detector interval is, the more time this will - take. -

, - ] - : [ -

- {`Detector initialization is not complete because ${initErrorMessage}.`} -

, -

{`${initActionItem}`}

, - ]} -
- } - actions={ - - View detector configuration - - } - /> - ); -}; diff --git a/public/pages/DetectorResults/containers/AnomalyResults.tsx b/public/pages/DetectorResults/containers/AnomalyResults.tsx index cd971485..efb9d8d2 100644 --- a/public/pages/DetectorResults/containers/AnomalyResults.tsx +++ b/public/pages/DetectorResults/containers/AnomalyResults.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { get } from 'lodash'; import React, { useEffect, Fragment } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { RouteComponentProps } from 'react-router'; //@ts-ignore import chrome from 'ui/chrome'; @@ -40,6 +40,9 @@ import { INIT_ERROR_MESSAGE_FIELD, INIT_ACTION_ITEM_FIELD, } from '../utils/utils'; +import { getDetector } from '../../../redux/reducers/ad'; +import { MIN_IN_MILLI_SECS } from '../../../../server/utils/constants'; +import { getInitFailureMessageAndActionItem } from '../../DetectorDetail/utils/helpers'; interface AnomalyResultsProps extends RouteComponentProps { detectorId: string; @@ -48,6 +51,7 @@ interface AnomalyResultsProps extends RouteComponentProps { } export function AnomalyResults(props: AnomalyResultsProps) { + const dispatch = useDispatch(); const detectorId = props.detectorId; const detector = useSelector( (state: AppState) => state.ad.detectors[detectorId] @@ -61,6 +65,19 @@ export function AnomalyResults(props: AnomalyResultsProps) { ]); }, []); + const fetchDetector = async () => { + await dispatch(getDetector(detectorId)); + }; + + useEffect(() => { + if (detector && detector.curState === DETECTOR_STATE.INIT) { + const id = setInterval(fetchDetector, MIN_IN_MILLI_SECS); + return () => { + clearInterval(id); + }; + } + }, [detector]); + const monitors = useSelector((state: AppState) => state.alerting.monitors); const monitor = get(monitors, `${detectorId}.0`); @@ -78,18 +95,23 @@ export function AnomalyResults(props: AnomalyResultsProps) { // @ts-ignore isDetectorPaused && detector.lastUpdateTime > detector.disabledTime; - const isDetectorInitializingAgain = - detector && - detector.curState === DETECTOR_STATE.INIT && - detector.enabled && - detector.disabledTime; + const isDetectorInitializing = + detector && detector.curState === DETECTOR_STATE.INIT; const initializationInfo = getDetectorInitializationInfo(detector); + const isInitOvertime = get(initializationInfo, IS_INIT_OVERTIME_FIELD, false); const initDetails = get(initializationInfo, INIT_DETAILS_FIELD, {}); const initErrorMessage = get(initDetails, INIT_ERROR_MESSAGE_FIELD, ''); const initActionItem = get(initDetails, INIT_ACTION_ITEM_FIELD, ''); + const isInitializingNormally = isDetectorInitializing && !isInitOvertime; + + const isDetectorFailed = + detector && + (detector.curState === DETECTOR_STATE.INIT_FAILURE || + detector.curState === DETECTOR_STATE.UNEXPECTED_FAILURE); + return ( @@ -99,19 +121,39 @@ export function AnomalyResults(props: AnomalyResultsProps) { {isDetectorRunning || isDetectorPaused || - isDetectorInitializingAgain ? ( + isDetectorInitializing || + isDetectorFailed ? ( - {isDetectorUpdated || isDetectorInitializingAgain ? ( + {isDetectorUpdated || + isDetectorInitializing || + isDetectorFailed ? ( {isDetectorUpdated ? ( @@ -119,25 +161,42 @@ export function AnomalyResults(props: AnomalyResultsProps) { Restart the detector to see accurate anomalies based on configuration changes.

- ) : !isInitOvertime ? ( + ) : isInitializingNormally ? (

After the initialization is complete, you will see the anomaly results based on your latest configuration changes.

- ) : ( + ) : isInitOvertime ? (

{`${initActionItem}`}

+ ) : ( + // detector has failure +

{`${get( + getInitFailureMessageAndActionItem( + //@ts-ignore + detector.stateError + ), + 'actionItem', + '' + )}`}

)} View detector configuration - {isDetectorUpdated ? ( + {isDetectorUpdated || isDetectorFailed ? (
- ) : detector && detector.curState !== DETECTOR_STATE.RUNNING ? ( + ) : detector ? ( ) : ( - {`Not available when the detector is ${props.detector.curState.toLowerCase()}.`} + + {'Not available when the detector ' + + `${ + props.detector.curState === DETECTOR_STATE.INIT_FAILURE || + props.detector.curState === DETECTOR_STATE.UNEXPECTED_FAILURE + ? 'initialization has failed.' + : `is ${props.detector.curState.toLowerCase()}.` + }`} + )} diff --git a/public/pages/DetectorResults/containers/DetectorStateDetails.tsx b/public/pages/DetectorResults/containers/DetectorStateDetails.tsx index d9526c61..8ac127a2 100644 --- a/public/pages/DetectorResults/containers/DetectorStateDetails.tsx +++ b/public/pages/DetectorResults/containers/DetectorStateDetails.tsx @@ -13,11 +13,8 @@ * permissions and limitations under the License. */ import React, { useEffect } from 'react'; -import { getInitFailureMessageAndActionItem } from '../../DetectorDetail/utils/helpers'; import { DETECTOR_STATE } from '../../../utils/constants'; import { DetectorStopped } from '../components/DetectorState/DetectorStopped'; -import { DetectorInitializing } from '../components/DetectorState/DetectorInitializing'; -import { DetectorInitializationFailure } from '../components/DetectorState/DetectorInitializationFailure'; import { DetectorFeatureRequired } from '../components/DetectorState/DetectorFeatureRequired'; import { DetectorUnknownState } from '../components/DetectorState/DetectorUnknownState'; import { useDispatch, useSelector } from 'react-redux'; @@ -49,26 +46,6 @@ export const DetectorStateDetails = (props: DetectorStateDetailsProp) => { onSwitchToConfiguration={props.onSwitchToConfiguration} /> ); - case DETECTOR_STATE.INIT: - return ( - - ); - case DETECTOR_STATE.UNEXPECTED_FAILURE: - case DETECTOR_STATE.INIT_FAILURE: - const failureDetail = getInitFailureMessageAndActionItem( - detector.initializationError - ); - return ( - - ); case DETECTOR_STATE.FEATURE_REQUIRED: return ( { return ( detector && detector.curState === DETECTOR_STATE.INIT && - detector.initializationError && - !detector.initializationError.includes(NO_RCF_MODEL_ERROR_MESSAGE) && + detector.stateError && + !detector.stateError.includes(NO_RCF_MODEL_ERROR_MESSAGE) && //@ts-ignore currentTime .subtract( @@ -63,16 +63,14 @@ const getInitOverTimeDetails = (detector: Detector) => { [INIT_ERROR_MESSAGE_FIELD]: '', [INIT_ACTION_ITEM_FIELD]: '', }; - if (!detector.initializationError) { + if (!detector.stateError) { return result; } - if (detector.initializationError.includes(NO_FULL_SHINGLE_ERROR_MESSAGE)) { + if (detector.stateError.includes(NO_FULL_SHINGLE_ERROR_MESSAGE)) { result[INIT_ERROR_MESSAGE_FIELD] = 'of insufficient data'; result[INIT_ACTION_ITEM_FIELD] = DETECTOR_INIT_FAILURES.NO_TRAINING_DATA.actionItem; - } else if ( - detector.initializationError.includes(NO_DATA_IN_WINDOW_ERROR_MESSAGE) - ) { + } else if (detector.stateError.includes(NO_DATA_IN_WINDOW_ERROR_MESSAGE)) { result[INIT_ERROR_MESSAGE_FIELD] = 'no data could be found'; result[INIT_ACTION_ITEM_FIELD] = 'Make sure your source index has sufficient data in the current detector interval and try again.'; diff --git a/public/pages/createDetector/hooks/useFetchDetectorInfo.ts b/public/pages/createDetector/hooks/useFetchDetectorInfo.ts index edc78735..f30e3bd3 100644 --- a/public/pages/createDetector/hooks/useFetchDetectorInfo.ts +++ b/public/pages/createDetector/hooks/useFetchDetectorInfo.ts @@ -26,11 +26,10 @@ import { getMappings } from '../../../redux/reducers/elasticsearch'; // 2. Gets index mapping export const useFetchDetectorInfo = ( detectorId: string -): -{ +): { detector: Detector; hasError: boolean; - isLoadingDetector: boolean; + isLoadingDetector: boolean; } => { const dispatch = useDispatch(); const detector = useSelector( diff --git a/public/redux/reducers/__tests__/utils.ts b/public/redux/reducers/__tests__/utils.ts index c372753c..d936df71 100644 --- a/public/redux/reducers/__tests__/utils.ts +++ b/public/redux/reducers/__tests__/utils.ts @@ -105,7 +105,7 @@ export const getRandomDetector = (isCreate: boolean = true): Detector => { .subtract(1, 'days') .valueOf(), curState: DETECTOR_STATE.INIT, - initializationError: '', + stateError: '', }; }; diff --git a/public/redux/reducers/ad.ts b/public/redux/reducers/ad.ts index 8bf8fe7a..c6d79839 100644 --- a/public/redux/reducers/ad.ts +++ b/public/redux/reducers/ad.ts @@ -112,7 +112,7 @@ const reducer = handleActions( enabled: true, enabledTime: moment().valueOf(), curState: DETECTOR_STATE.INIT, - initializationError: '', + stateError: '', }, }, }), @@ -138,7 +138,7 @@ const reducer = handleActions( enabled: false, disabledTime: moment().valueOf(), curState: DETECTOR_STATE.DISABLED, - initializationError: '', + stateError: '', }, }, }), diff --git a/server/routes/ad.ts b/server/routes/ad.ts index dd562b23..7d27affe 100644 --- a/server/routes/ad.ts +++ b/server/routes/ad.ts @@ -199,7 +199,7 @@ const getDetector = async ( ...(detectorState !== undefined ? { curState: detectorState.state } : {}), ...(detectorState !== undefined ? //@ts-ignore - { initializationError: detectorState.error } + { stateError: detectorState.error } : {}), }; return {