Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Add progress bar for initialization (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
yizheliu-amazon committed Aug 28, 2020
1 parent b24402e commit 2e7a3fc
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 42 deletions.
6 changes: 6 additions & 0 deletions public/models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export type UiMetaData = {
};
};

export type InitProgress = {
percentageStr: string;
estimatedMinutesLeft: number;
neededShingles: number;
};
export type Detector = {
primaryTerm: number;
seqNo: number;
Expand All @@ -105,6 +110,7 @@ export type Detector = {
disabledTime?: number;
curState: DETECTOR_STATE;
stateError: string;
initProgress?: InitProgress;
};

export type DetectorListItem = {
Expand Down
13 changes: 8 additions & 5 deletions public/pages/DetectorDetail/containers/DetectorDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,10 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
) : detector.enabled &&
detector.curState === DETECTOR_STATE.INIT ? (
<EuiHealth color={DETECTOR_STATE_COLOR.INIT}>
Initializing
{detector.initProgress
? //@ts-ignore
`Initializing (${detector.initProgress.percentageStr} complete)`
: 'Initializing'}
</EuiHealth>
) : detector.curState === DETECTOR_STATE.INIT_FAILURE ||
detector.curState === DETECTOR_STATE.UNEXPECTED_FAILURE ? (
Expand Down Expand Up @@ -343,7 +346,7 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
<EuiFlexGroup>
<EuiFlexItem>
<EuiTabs>
{tabs.map(tab => (
{tabs.map((tab) => (
<EuiTab
onClick={() => {
handleTabChange(tab.route);
Expand Down Expand Up @@ -388,7 +391,7 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
<EuiFieldText
fullWidth={true}
placeholder="delete"
onChange={e => {
onChange={(e) => {
if (e.target.value === 'delete') {
setDetectorDetailModel({
...detectorDetailModel,
Expand Down Expand Up @@ -472,7 +475,7 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
<Route
exact
path="/detectors/:detectorId/results"
render={props => (
render={(props) => (
<AnomalyResults
{...props}
detectorId={detectorId}
Expand All @@ -484,7 +487,7 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
<Route
exact
path="/detectors/:detectorId/configurations"
render={props => (
render={(props) => (
<DetectorConfig
{...props}
detectorId={detectorId}
Expand Down
2 changes: 1 addition & 1 deletion public/pages/DetectorDetail/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { DETECTOR_INIT_FAILURES } from './constants';

export const getInitFailureMessageAndActionItem = (error: string): object => {
const failureDetails = Object.values(DETECTOR_INIT_FAILURES);
const failureDetail = failureDetails.find(failure =>
const failureDetail = failureDetails.find((failure) =>
error.includes(failure.keyword)
);
if (!failureDetail) {
Expand Down
79 changes: 61 additions & 18 deletions public/pages/DetectorResults/containers/AnomalyResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import {
EuiSpacer,
EuiCallOut,
EuiButton,
EuiProgress,
EuiFlexGroup,
EuiFlexItem,
EuiText,
} from '@elastic/eui';
import { get } from 'lodash';
import React, { useEffect, Fragment, useState } from 'react';
Expand Down Expand Up @@ -110,6 +114,13 @@ export function AnomalyResults(props: AnomalyResultsProps) {
const monitors = useSelector((state: AppState) => state.alerting.monitors);
const monitor = get(monitors, `${detectorId}.0`);

const [featureMissingSeverity, setFeatureMissingSeverity] = useState<
MISSING_FEATURE_DATA_SEVERITY
>();

const [featureNamesAtHighSev, setFeatureNamesAtHighSev] = useState(
[] as string[]
);
const isDetectorRunning =
detector && detector.curState === DETECTOR_STATE.RUNNING;

Expand All @@ -127,9 +138,16 @@ export function AnomalyResults(props: AnomalyResultsProps) {
const isDetectorInitializing =
detector && detector.curState === DETECTOR_STATE.INIT;

const initializationInfo = getDetectorInitializationInfo(detector);
const isDetectorMissingData = featureMissingSeverity
? (isDetectorInitializing || isDetectorRunning) &&
featureMissingSeverity > MISSING_FEATURE_DATA_SEVERITY.GREEN
: undefined;

const isInitOvertime = get(initializationInfo, IS_INIT_OVERTIME_FIELD, false);
const initializationInfo = featureMissingSeverity
? getDetectorInitializationInfo(detector)
: undefined;

const isInitOvertime = get(initializationInfo, IS_INIT_OVERTIME_FIELD);
const initDetails = get(initializationInfo, INIT_DETAILS_FIELD, {});
const initErrorMessage = get(initDetails, INIT_ERROR_MESSAGE_FIELD, '');
const initActionItem = get(initDetails, INIT_ACTION_ITEM_FIELD, '');
Expand All @@ -145,21 +163,9 @@ export function AnomalyResults(props: AnomalyResultsProps) {
1
);

const [featureMissingSeverity, setFeatureMissingSeverity] = useState<
MISSING_FEATURE_DATA_SEVERITY
>();

const [featureNamesAtHighSev, setFeatureNamesAtHighSev] = useState(
[] as string[]
);

const isDetectorMissingData = featureMissingSeverity
? (isDetectorInitializing || isDetectorRunning) &&
featureMissingSeverity > MISSING_FEATURE_DATA_SEVERITY.GREEN
: undefined;

const isInitializingNormally =
isDetectorInitializing &&
isInitOvertime != undefined &&
!isInitOvertime &&
isDetectorMissingData != undefined &&
!isDetectorMissingData;
Expand Down Expand Up @@ -273,6 +279,12 @@ export function AnomalyResults(props: AnomalyResultsProps) {
}
};

const getInitProgressMessage = () => {
return detector && isDetectorInitializing && detector.initProgress
? `The detector needs ${detector.initProgress.estimatedMinutesLeft} minutes for initializing. If your data stream is not continuous, it may take even longer. `
: '';
};

const getCalloutContent = () => {
return isDetectorUpdated ? (
<p>
Expand All @@ -281,6 +293,7 @@ export function AnomalyResults(props: AnomalyResultsProps) {
</p>
) : isDetectorMissingData ? (
<p>
{getInitProgressMessage()}
{get(
getFeatureDataMissingMessageAndActionItem(
featureMissingSeverity,
Expand All @@ -292,11 +305,11 @@ export function AnomalyResults(props: AnomalyResultsProps) {
</p>
) : isInitializingNormally ? (
<p>
After the initialization is complete, you will see the anomaly results
based on your latest configuration changes.
{getInitProgressMessage()}After the initialization is complete, you will
see the anomaly results based on your latest configuration changes.
</p>
) : isInitOvertime ? (
<p>{`${initActionItem}`}</p>
<p>{`${getInitProgressMessage()}${initActionItem}`}</p>
) : (
// detector has failure
<p>{`${get(
Expand Down Expand Up @@ -324,6 +337,7 @@ export function AnomalyResults(props: AnomalyResultsProps) {
{isDetectorUpdated ||
isDetectorMissingData ||
isInitializingNormally ||
isInitOvertime ||
isDetectorFailed ? (
<EuiCallOut
title={getCalloutTitle()}
Expand All @@ -332,6 +346,35 @@ export function AnomalyResults(props: AnomalyResultsProps) {
style={{ marginBottom: '20px' }}
>
{getCalloutContent()}
{isDetectorInitializing && detector.initProgress ? (
<div>
<EuiFlexGroup alignItems="center">
<EuiFlexItem
style={{ maxWidth: '20px', marginRight: '5px' }}
>
<EuiText>
{
//@ts-ignore
detector.initProgress.percentageStr
}
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiProgress
//@ts-ignore
value={detector.initProgress.percentageStr.replace(
'%',
''
)}
max={100}
color="primary"
size="xs"
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="l" />
</div>
) : null}
<EuiButton
onClick={props.onSwitchToConfiguration}
color={
Expand Down
1 change: 1 addition & 0 deletions public/redux/reducers/ad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const reducer = handleActions<Detectors>(
disabledTime: moment().valueOf(),
curState: DETECTOR_STATE.DISABLED,
stateError: '',
initProgress: undefined,
},
},
}),
Expand Down
2 changes: 1 addition & 1 deletion server/cluster/ad/adPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export default function adPlugin(Client: any, config: any, components: any) {

ad.detectorProfile = ca({
url: {
fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_profile`,
fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_profile/init_progress,state,error`,
req: {
detectorId: {
type: 'string',
Expand Down
25 changes: 10 additions & 15 deletions server/routes/ad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
getResultAggregationQuery,
getFinalDetectorStates,
getDetectorsWithJob,
getDetectorInitProgress,
} from './utils/adHelpers';
import { set } from 'lodash';

Expand All @@ -55,7 +56,7 @@ type PutDetectorParams = {
body: string;
};

export default function(apiRouter: Router) {
export default function (apiRouter: Router) {
apiRouter.post('/detectors', putDetector);
apiRouter.put('/detectors/{detectorId}', putDetector);
apiRouter.post('/detectors/_search', searchDetector);
Expand Down Expand Up @@ -181,7 +182,6 @@ const getDetector = async (
detectorId: detectorId,
}
);

const detectorStates = getFinalDetectorStates(
[detectorStateResp],
[convertDetectorKeysToCamelCase(response.anomaly_detector)]
Expand All @@ -196,11 +196,12 @@ const getDetector = async (
primaryTerm: response._primary_term,
seqNo: response._seq_no,
adJob: { ...response.anomaly_detector_job },
//@ts-ignore
...(detectorState !== undefined ? { curState: detectorState.state } : {}),
...(detectorState !== undefined
? //@ts-ignore
{ stateError: detectorState.error }
? {
curState: detectorState.state,
stateError: detectorState.error,
initProgress: getDetectorInitProgress(detectorState),
}
: {}),
};
return {
Expand Down Expand Up @@ -333,10 +334,7 @@ const getDetectors = async (
query_string: {
fields: ['name', 'description'],
default_operator: 'AND',
query: `*${search
.trim()
.split(' ')
.join('* *')}*`,
query: `*${search.trim().split(' ').join('* *')}*`,
},
});
}
Expand All @@ -345,10 +343,7 @@ const getDetectors = async (
query_string: {
fields: ['indices'],
default_operator: 'OR',
query: `*${indices
.trim()
.split(' ')
.join('* *')}*`,
query: `*${indices.trim().split(' ').join('* *')}*`,
},
});
}
Expand Down Expand Up @@ -452,7 +447,7 @@ const getDetectors = async (
}

// Get detector state as well: loop through the ids to get each detector's state using profile api
const allIds = finalDetectors.map(detector => detector.id);
const allIds = finalDetectors.map((detector) => detector.id);

const detectorStatePromises = allIds.map(async (id: string) => {
try {
Expand Down
19 changes: 17 additions & 2 deletions server/routes/utils/adHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AnomalyResults } from 'server/models/interfaces';
import { GetDetectorsQueryParams } from '../../models/types';
import { mapKeysDeep, toCamel, toSnake } from '../../utils/helpers';
import { DETECTOR_STATE } from '../../../public/utils/constants';
import { InitProgress } from 'public/models/interfaces';

export const convertDetectorKeysToSnakeCase = (payload: any) => {
return {
Expand Down Expand Up @@ -151,12 +152,26 @@ export const anomalyResultMapper = (anomalyResults: any[]): AnomalyResults => {
return resultData;
};

export const getDetectorInitProgress = (
detectorStateResponse: any
): InitProgress | undefined => {
if (detectorStateResponse.init_progress) {
return {
percentageStr: detectorStateResponse.init_progress.percentage,
estimatedMinutesLeft:
detectorStateResponse.init_progress.estimated_minutes_left,
neededShingles: detectorStateResponse.init_progress.needed_shingles,
};
}
return undefined;
};

export const getFinalDetectorStates = (
detectorStateResponses: any[],
finalDetectors: any[]
) => {
let finalDetectorStates = cloneDeep(detectorStateResponses);
finalDetectorStates.forEach(detectorState => {
finalDetectorStates.forEach((detectorState) => {
//@ts-ignore
detectorState.state = DETECTOR_STATE[detectorState.state];
});
Expand Down Expand Up @@ -198,7 +213,7 @@ export const getDetectorsWithJob = (
): any[] => {
const finalDetectorsWithJobResponses = cloneDeep(detectorsWithJobResponses);
const resultDetectorWithJobs = [] as any[];
finalDetectorsWithJobResponses.forEach(detectorWithJobResponse => {
finalDetectorsWithJobResponses.forEach((detectorWithJobResponse) => {
const resp = {
...detectorWithJobResponse.anomaly_detector,
id: detectorWithJobResponse._id,
Expand Down

0 comments on commit 2e7a3fc

Please sign in to comment.