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

Commit

Permalink
add start/stop AD job api (#12)
Browse files Browse the repository at this point in the history
* add start/stop AD job api
* add enabled/disabled time in public interface Detector
* add ad job fields in server Detector type
* fix start/end time as we changed to data_start_time/data_end_time
ylwu-amzn authored Mar 29, 2020
1 parent 01b062b commit e7c4cb2
Showing 7 changed files with 171 additions and 20 deletions.
3 changes: 3 additions & 0 deletions public/models/interfaces.ts
Original file line number Diff line number Diff line change
@@ -98,6 +98,9 @@ export type Detector = {
windowDelay: { period: Schedule };
detectionInterval: { period: Schedule };
uiMetadata: UiMetaData;
enabled?: boolean;
enabledTime?: Date;
disabledTime?: Date;
};

export type DetectorListItem = {
65 changes: 65 additions & 0 deletions public/redux/reducers/ad.ts
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ const GET_DETECTOR_LIST = 'ad/GET_DETECTOR_LIST';
const UPDATE_DETECTOR = 'ad/UPDATE_DETECTOR';
const SEARCH_DETECTOR = 'ad/SEARCH_DETECTOR';
const DELETE_DETECTOR = 'ad/DELETE_DETECTOR';
const START_DETECTOR = 'ad/START_DETECTOR';
const STOP_DETECTOR = 'ad/STOP_DETECTOR';

export interface Detectors {
requesting: boolean;
@@ -92,6 +94,51 @@ const reducer = handleActions<Detectors>(
errorMessage: action.error.data.error,
}),
},
[START_DETECTOR]: {
REQUEST: (state: Detectors): Detectors => {
const newState = { ...state, requesting: true, errorMessage: '' };
return newState;
},
SUCCESS: (state: Detectors, action: APIResponseAction): Detectors => ({
...state,
requesting: false,
detectors: {
...state.detectors,
[action.detectorId]: {
...[action.detectorId],
enabled: true,
},
},
}),
FAILURE: (state: Detectors, action: APIErrorAction): Detectors => ({
...state,
requesting: false,
errorMessage: action.error,
}),
},

[STOP_DETECTOR]: {
REQUEST: (state: Detectors): Detectors => {
const newState = { ...state, requesting: true, errorMessage: '' };
return newState;
},
SUCCESS: (state: Detectors, action: APIResponseAction): Detectors => ({
...state,
requesting: false,
detectors: {
...state.detectors,
[action.detectorId]: {
...[action.detectorId],
enabled: false,
},
},
}),
FAILURE: (state: Detectors, action: APIErrorAction): Detectors => ({
...state,
requesting: false,
errorMessage: action.error,
}),
},
[SEARCH_DETECTOR]: {
REQUEST: (state: Detectors): Detectors => ({
...state,
@@ -233,4 +280,22 @@ export const deleteDetector = (detectorId: string): APIAction => ({
detectorId,
});

export const startDetector = (detectorId: string): APIAction => ({
type: START_DETECTOR,
request: (client: IHttpService) =>
client.post(`..${AD_NODE_API.DETECTOR}/${detectorId}/start`, {
detectorId: detectorId,
}),
detectorId,
});

export const stopDetector = (detectorId: string): APIAction => ({
type: STOP_DETECTOR,
request: (client: IHttpService) =>
client.post(`..${AD_NODE_API.DETECTOR}/${detectorId}/stop`, {
detectorId: detectorId,
}),
detectorId,
});

export default reducer;
28 changes: 27 additions & 1 deletion server/cluster/ad/adPlugin.ts
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ export default function adPlugin(Client: any, config: any, components: any) {
});
ad.getDetector = ca({
url: {
fmt: `${API.DETECTOR_BASE}/<%=detectorId%>`,
fmt: `${API.DETECTOR_BASE}/<%=detectorId%>?job=true`,
req: {
detectorId: {
type: 'string',
@@ -100,4 +100,30 @@ export default function adPlugin(Client: any, config: any, components: any) {
needBody: true,
method: 'POST',
});

ad.startDetector = ca({
url: {
fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_start`,
req: {
detectorId: {
type: 'string',
required: true,
},
},
},
method: 'POST',
});

ad.stopDetector = ca({
url: {
fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_stop`,
req: {
detectorId: {
type: 'string',
required: true,
},
},
},
method: 'POST',
});
}
3 changes: 3 additions & 0 deletions server/models/types.ts
Original file line number Diff line number Diff line change
@@ -62,6 +62,9 @@ export type Detector = {
windowDelay?: { period: Schedule };
detectionInterval?: { period: Schedule };
uiMetadata?: { [key: string]: any };
enabled: boolean;
enabledTime?: Date;
disabledTime?: Date;
};

export type GetDetectorsQueryParams = {
51 changes: 47 additions & 4 deletions server/routes/ad.ts
Original file line number Diff line number Diff line change
@@ -55,6 +55,8 @@ export default function (apiRouter: Router) {
apiRouter.post('/detectors/{detectorId}/preview', previewDetector);
apiRouter.get('/detectors/{detectorId}/results', getAnomalyResults);
apiRouter.delete('/detectors/{detectorId}', deleteDetector);
apiRouter.post('/detectors/{detectorId}/start', startDetector);
apiRouter.post('/detectors/{detectorId}/stop', stopDetector);
}

const deleteDetector = async (
@@ -165,6 +167,7 @@ const getDetector = async (
id: response._id,
primaryTerm: response._primary_term,
seqNo: response._seq_no,
adJob: { ...response.anomaly_detector_job },
};
return {
ok: true,
@@ -176,6 +179,46 @@ const getDetector = async (
}
};

const startDetector = async (
req: Request,
h: ResponseToolkit,
callWithRequest: CallClusterWithRequest
): Promise<ServerResponse<AnomalyResults>> => {
try {
const { detectorId } = req.params;
const response = await callWithRequest(req, 'ad.startDetector', {
detectorId,
});
return {
ok: true,
response: response,
};
} catch (err) {
console.log('Anomaly detector - strartDetector', err);
return { ok: false, error: err.body || err.message };
}
};

const stopDetector = async (
req: Request,
h: ResponseToolkit,
callWithRequest: CallClusterWithRequest
): Promise<ServerResponse<AnomalyResults>> => {
try {
const { detectorId } = req.params;
const response = await callWithRequest(req, 'ad.stopDetector', {
detectorId,
});
return {
ok: true,
response: response,
};
} catch (err) {
console.log('Anomaly detector - stopDetector', err);
return { ok: false, error: err.body || err.message };
}
};

const searchDetector = async (
req: Request,
h: ResponseToolkit,
@@ -366,8 +409,8 @@ const getAnomalyResults = async (
const sortQueryMap = {
anomalyGrade: { anomaly_grade: sortDirection },
confidence: { confidence: sortDirection },
startTime: { start_time: sortDirection },
endTime: { end_time: sortDirection },
startTime: { data_start_time: sortDirection },
endTime: { data_end_time: sortDirection },
} as { [key: string]: object };
let sort = {};
const sortQuery = sortQueryMap[sortField];
@@ -396,8 +439,8 @@ const getAnomalyResults = async (
// Get all detectors from search detector API
const detectorResults: AnomalyResult[] = get(response, 'hits.hits', []).map(
(result: any) => ({
startTime: result._source.start_time,
endTime: result._source.end_time,
startTime: result._source.data_start_time,
endTime: result._source.data_end_time,
confidence: result._source.confidence != null && result._source.confidence > 0 ? Number.parseFloat(result._source.confidence).toFixed(3) : 0,
anomalyGrade: result._source.anomaly_grade != null && result._source.anomaly_grade > 0 ? Number.parseFloat(result._source.anomaly_grade).toFixed(3) : 0
})
16 changes: 8 additions & 8 deletions server/routes/utils/__tests__/adHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -218,10 +218,10 @@ describe('adHelpers', () => {
aggs: {
total_anomalies_in_24hr: {
filter: {
range: { start_time: { gte: 'now-24h', lte: 'now' } },
range: { data_start_time: { gte: 'now-24h', lte: 'now' } },
},
},
latest_anomaly_time: { max: { field: 'start_time' } },
latest_anomaly_time: { max: { field: 'data_start_time' } },
},
},
},
@@ -261,10 +261,10 @@ describe('adHelpers', () => {
aggs: {
total_anomalies_in_24hr: {
filter: {
range: { start_time: { gte: 'now-24h', lte: 'now' } },
range: { data_start_time: { gte: 'now-24h', lte: 'now' } },
},
},
latest_anomaly_time: { max: { field: 'start_time' } },
latest_anomaly_time: { max: { field: 'data_start_time' } },
},
},
},
@@ -301,10 +301,10 @@ describe('adHelpers', () => {
aggs: {
total_anomalies_in_24hr: {
filter: {
range: { start_time: { gte: 'now-24h', lte: 'now' } },
range: { data_start_time: { gte: 'now-24h', lte: 'now' } },
},
},
latest_anomaly_time: { max: { field: 'start_time' } },
latest_anomaly_time: { max: { field: 'data_start_time' } },
},
},
},
@@ -341,10 +341,10 @@ describe('adHelpers', () => {
aggs: {
total_anomalies_in_24hr: {
filter: {
range: { start_time: { gte: 'now-24h', lte: 'now' } },
range: { data_start_time: { gte: 'now-24h', lte: 'now' } },
},
},
latest_anomaly_time: { max: { field: 'start_time' } },
latest_anomaly_time: { max: { field: 'data_start_time' } },
},
},
},
25 changes: 18 additions & 7 deletions server/routes/utils/adHelpers.ts
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ export const convertDetectorKeysToCamelCase = (response: object) => {
'ui_metadata',
'feature_query',
'feature_attributes',
'adJob',
]),
toCamel
),
@@ -56,6 +57,13 @@ export const convertDetectorKeysToCamelCase = (response: object) => {
})
),
uiMetadata: get(response, 'ui_metadata', {}),
enabled: get(response, 'adJob.enabled', false),
enabledTime: get(response, 'adJob.enabled_time')
? new Date(get(response, 'adJob.enabled_time'))
: undefined,
disabledTime: get(response, 'adJob.disabled_time')
? new Date(get(response, 'adJob.disabled_time'))
: undefined,
};
};

@@ -97,9 +105,9 @@ export const getResultAggregationQuery = (
},
aggs: {
total_anomalies_in_24hr: {
filter: { range: { start_time: { gte: 'now-24h', lte: 'now' } } },
filter: { range: { data_start_time: { gte: 'now-24h', lte: 'now' } } },
},
latest_anomaly_time: { max: { field: 'start_time' } },
latest_anomaly_time: { max: { field: 'data_start_time' } },
},
},
},
@@ -118,8 +126,9 @@ export const anomalyResultMapper = (anomalyResults: any[]): AnomalyResults => {
resultData.featureData[feature.featureId] = [];
});
anomalyResults.forEach(({ featureData, ...rest }) => {
const { dataStartTime, dataEndTime, ...others } = rest;
resultData.anomalies.push({
...rest,
...others,
anomalyGrade:
rest.anomalyGrade != null && rest.anomalyGrade > 0
? Number.parseFloat(rest.anomalyGrade).toFixed(3)
@@ -128,15 +137,17 @@ export const anomalyResultMapper = (anomalyResults: any[]): AnomalyResults => {
rest.anomalyGrade != null && rest.anomalyGrade > 0
? Number.parseFloat(rest.confidence).toFixed(3)
: 0,
startTime: rest.dataStartTime,
endTime: rest.dataEndTime,
plotTime:
rest.startTime + Math.floor((rest.endTime - rest.startTime) / 2),
rest.dataStartTime + Math.floor((rest.dataEndTime - rest.dataStartTime) / 2),
});
featureData.forEach((feature: any) => {
resultData.featureData[feature.featureId].push({
startTime: rest.startTime,
endTime: rest.endTime,
startTime: rest.dataStartTime,
endTime: rest.dataEndTime,
plotTime:
rest.startTime + Math.floor((rest.endTime - rest.startTime) / 2),
rest.dataStartTime + Math.floor((rest.dataEndTime - rest.dataStartTime) / 2),
data: feature.data,
});
});

0 comments on commit e7c4cb2

Please sign in to comment.