diff --git a/public/pages/DetectorDetail/utils/constants.ts b/public/pages/DetectorDetail/utils/constants.ts index 797fb5a2..985b84a4 100644 --- a/public/pages/DetectorDetail/utils/constants.ts +++ b/public/pages/DetectorDetail/utils/constants.ts @@ -16,4 +16,63 @@ export enum DETECTOR_DETAIL_TABS { RESULTS = 'results', CONFIGURATIONS = 'configurations', -}; +} + +const DEFAULT_ACTION_ITEM = 'Please restart this detector to retry.'; +// Known causes: +// https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L174-L185 +export const DETECTOR_INIT_FAILURES = Object.freeze({ + NO_TRAINING_DATA: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L801 + keyword: 'Cannot get training data', + cause: 'lack of data ingestion', + actionItem: + 'Please make sure your data ingestion is working. Or increase your detector time interval if data source has infrequent ingestion.', + }, + COLD_START_ERROR: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L811 + keyword: 'Error while cold start', + cause: 'error is found while model initialization', + actionItem: DEFAULT_ACTION_ITEM, + }, + AD_MODEL_MEMORY_REACH_LIMIT: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/ml/ModelManager.java#L272 + keyword: 'AD models memory usage exceeds our limit', + cause: 'lack of memory for detector models', + actionItem: + 'Model of this detector is too large, please reduce the number of features in this detector.', + }, + DETECTOR_MEMORY_REACH_LIMIT: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/ml/ModelManager.java#L783 + keyword: 'Exceeded memory limit', + cause: 'lack of memory', + actionItem: + "Try deleting or stop other detectors that you don't actively use, increase your cluster size, reduce the number of features in this detector, or scale up with an instance type of more memory.", + }, + DATA_INDEX_NOT_FOUND: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L366 + keyword: 'Having trouble querying data: ', + cause: 'data index not found', + actionItem: 'Please make sure your data index does exist.', + }, + ALL_FEATURES_DISABLED: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L368 + keyword: + 'Having trouble querying data because all of your features have been disabled', + cause: 'all features in this detector are disabled', + actionItem: + 'Please enable some of your features and re-start your detector.', + }, + DETECTOR_UNDEFINED: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L230 + keyword: 'AnomalyDetector is not available', + cause: 'your detector is not defined', + actionItem: 'Please make sure your detector is defined.', + }, + UNKNOWN_EXCEPTION: { + //https://github.com/opendistro-for-elasticsearch/anomaly-detection/blob/development/src/main/java/com/amazon/opendistroforelasticsearch/ad/transport/AnomalyResultTransportAction.java#L438 + keyword: 'We might have bug', + cause: 'unknown error', + actionItem: DEFAULT_ACTION_ITEM, + }, +}); diff --git a/public/pages/DetectorDetail/utils/helpers.ts b/public/pages/DetectorDetail/utils/helpers.ts new file mode 100644 index 00000000..6136d9c3 --- /dev/null +++ b/public/pages/DetectorDetail/utils/helpers.ts @@ -0,0 +1,27 @@ +/* + * 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 { DETECTOR_INIT_FAILURES } from './constants'; + +export const getInitFailureMessageAndActionItem = (error: string): object => { + const failureDetails = Object.values(DETECTOR_INIT_FAILURES); + const failureDetail = failureDetails.find(failure => + error.includes(failure.keyword) + ); + if (!failureDetail) { + return DETECTOR_INIT_FAILURES.UNKNOWN_EXCEPTION; + } + return failureDetail; +}; diff --git a/public/pages/DetectorResults/components/DetectorState/DetectorFeatureRequired.tsx b/public/pages/DetectorResults/components/DetectorState/DetectorFeatureRequired.tsx new file mode 100644 index 00000000..7e5ab00a --- /dev/null +++ b/public/pages/DetectorResults/components/DetectorState/DetectorFeatureRequired.tsx @@ -0,0 +1,52 @@ +/* + * 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 } from '@elastic/eui'; +import { Fragment } from 'react'; +import { Detector } from '../../../../models/interfaces'; + +export interface DetectorFeatureRequiredProps { + detector: Detector; +} + +export const DetectorFeatureRequired = ( + props: DetectorFeatureRequiredProps +) => { + return ( + Features are required to run a detector} + body={ + +

+ Specify index fields that you want to find anomalies for by defining + features. Once you define the features, you can preview your + anomalies from a sample feature output. +

+
+ } + actions={[ + + View detector configuration + , + ]} + /> + ); +}; diff --git a/public/pages/DetectorResults/components/DetectorState/DetectorInitializationFailure.tsx b/public/pages/DetectorResults/components/DetectorState/DetectorInitializationFailure.tsx new file mode 100644 index 00000000..0d8d5dda --- /dev/null +++ b/public/pages/DetectorResults/components/DetectorState/DetectorInitializationFailure.tsx @@ -0,0 +1,64 @@ +/* + * 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 cannot be initialized due to ${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 new file mode 100644 index 00000000..09c27673 --- /dev/null +++ b/public/pages/DetectorResults/components/DetectorState/DetectorInitializing.tsx @@ -0,0 +1,58 @@ +/* + * 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'; + +export interface DetectorInitializingProps { + detector: Detector; + onSwitchToConfiguration(): void; +} + +export const DetectorInitializing = (props: DetectorInitializingProps) => { + return ( + + +

The detector is initializing...

+ + } + body={ + +

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

+

+ The longer the detector interval is, the longer the initialization + will take. +

+
+ } + actions={ + + View detector configuration + + } + /> + ); +}; diff --git a/public/pages/DetectorResults/components/DetectorState/DetectorStopped.tsx b/public/pages/DetectorResults/components/DetectorState/DetectorStopped.tsx new file mode 100644 index 00000000..b4c291d2 --- /dev/null +++ b/public/pages/DetectorResults/components/DetectorState/DetectorStopped.tsx @@ -0,0 +1,65 @@ +/* + * 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 } from '@elastic/eui'; +import { Fragment } from 'react'; +import { Detector } from '../../../../models/interfaces'; + +export interface DetectorStoppedProps { + detector: Detector; + onStartDetector(): void; + onSwitchToConfiguration(): void; +} + +export const DetectorStopped = (props: DetectorStoppedProps) => { + return ( + The detector is stopped} + body={ + + {props.detector.enabledTime ? ( +

+ The detector is stopped due to your latest update to the detector + configuration. Run the detector to see anomalies. +

+ ) : ( +

+ The detector is never started. Please start the detector to see + anomalies. +

+ )} +
+ } + actions={[ + + View detector configuration + , + + {props.detector.enabledTime ? 'Restart detector' : 'Start detector'} + , + ]} + /> + ); +}; diff --git a/public/pages/DetectorResults/containers/AnomalyResults.tsx b/public/pages/DetectorResults/containers/AnomalyResults.tsx index 19570b43..ae91e130 100644 --- a/public/pages/DetectorResults/containers/AnomalyResults.tsx +++ b/public/pages/DetectorResults/containers/AnomalyResults.tsx @@ -30,12 +30,14 @@ import { RouteComponentProps } from 'react-router'; //@ts-ignore import chrome from 'ui/chrome'; import { AppState } from '../../../redux/reducers'; -import { BREADCRUMBS } from '../../../utils/constants'; +import { BREADCRUMBS, DETECTOR_STATE } from '../../../utils/constants'; import { AnomalyResultsLiveChart } from './AnomalyResultsLiveChart'; import { AnomalyHistory } from './AnomalyHistory'; +import { DetectorStateDetails } from './DetectorStateDetails'; interface AnomalyResultsProps extends RouteComponentProps { detectorId: string; + onStartDetector(): void; onSwitchToConfiguration(): void; } @@ -60,31 +62,9 @@ export function AnomalyResults(props: AnomalyResultsProps) { - {detector && isEmpty(detector.featureAttributes) ? ( - Features are required to run a detector} - body={ - -

- Specify index fields that you want to find anomalies for by - defining features. Once you define the features, you can - preview your anomalies from a sample feature output. -

-
- } - actions={ - - Add features - - } - /> - ) : ( + { - {detector ? ( + {detector && detector.curState === DETECTOR_STATE.RUNNING ? ( {!detector.enabled && detector.disabledTime && @@ -106,9 +86,7 @@ export function AnomalyResults(props: AnomalyResultsProps) { ) : null} - + + ) : detector && detector.curState !== DETECTOR_STATE.RUNNING ? ( + + + ) : null} - )} + }
diff --git a/public/pages/DetectorResults/containers/DetectorStateDetails.tsx b/public/pages/DetectorResults/containers/DetectorStateDetails.tsx new file mode 100644 index 00000000..49fda364 --- /dev/null +++ b/public/pages/DetectorResults/containers/DetectorStateDetails.tsx @@ -0,0 +1,67 @@ +/* + * 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 { Detector } from '../../../models/interfaces'; +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'; + +export interface DetectorStateDetailsProp { + detector: Detector; + onStartDetector(): void; + onSwitchToConfiguration(): void; +} + +export const DetectorStateDetails = (props: DetectorStateDetailsProp) => { + const currentState = props.detector.curState; + + switch (currentState) { + case DETECTOR_STATE.DISABLED: + return ( + + ); + case DETECTOR_STATE.INIT: + return ( + + ); + case DETECTOR_STATE.INIT_FAILURE: + const failureDetail = getInitFailureMessageAndActionItem( + props.detector.initializationError + ); + return ( + + ); + case DETECTOR_STATE.FEATURE_REQUIRED: + return ; + default: + console.log('Unknown detector state', currentState); + return null; + } +}; diff --git a/server/routes/ad.ts b/server/routes/ad.ts index bdb45dd1..45205071 100644 --- a/server/routes/ad.ts +++ b/server/routes/ad.ts @@ -166,12 +166,36 @@ const getDetector = async ( const response = await callWithRequest(req, 'ad.getDetector', { detectorId, }); + let detectorState; + try { + const detectorStateResp = await callWithRequest( + req, + 'ad.detectorProfile', + { + detectorId: detectorId, + } + ); + + const detectorStates = getFinalDetectorStates( + [detectorStateResp], + [convertDetectorKeysToCamelCase(response.anomaly_detector)] + ); + detectorState = detectorStates[0]; + } catch (err) { + console.log('Anomaly detector - Unable to retrieve detector state', err); + } const resp = { ...response.anomaly_detector, id: response._id, primaryTerm: response._primary_term, seqNo: response._seq_no, adJob: { ...response.anomaly_detector_job }, + //@ts-ignore + ...(detectorState !== undefined ? { curState: detectorState.state } : {}), + ...(detectorState !== undefined + ? //@ts-ignore + { initializationError: detectorState.error } + : {}), }; return { ok: true,