From 019c3315320a7e3a8c39c2dabb52b3cf0f432bc6 Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Tue, 24 Mar 2020 21:03:50 -0700 Subject: [PATCH 1/6] add start/stop AD job api --- public/models/interfaces.ts | 1 + public/redux/reducers/ad.ts | 65 +++++++++++++++++++++++++++++++++++ server/cluster/ad/adPlugin.ts | 26 ++++++++++++++ server/routes/ad.ts | 42 ++++++++++++++++++++++ 4 files changed, 134 insertions(+) diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index c742dd4b..38137394 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -98,6 +98,7 @@ export type Detector = { windowDelay: { period: Schedule }; detectionInterval: { period: Schedule }; uiMetadata: UiMetaData; + enabled?: boolean; }; export type DetectorListItem = { diff --git a/public/redux/reducers/ad.ts b/public/redux/reducers/ad.ts index 7d4d9458..09f2e0eb 100644 --- a/public/redux/reducers/ad.ts +++ b/public/redux/reducers/ad.ts @@ -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( 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; diff --git a/server/cluster/ad/adPlugin.ts b/server/cluster/ad/adPlugin.ts index ce8c5b3c..19f089f4 100644 --- a/server/cluster/ad/adPlugin.ts +++ b/server/cluster/ad/adPlugin.ts @@ -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', + }); } diff --git a/server/routes/ad.ts b/server/routes/ad.ts index 9eaef539..404276ae 100644 --- a/server/routes/ad.ts +++ b/server/routes/ad.ts @@ -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 ( @@ -176,6 +178,46 @@ const getDetector = async ( } }; +const startDetector = async ( + req: Request, + h: ResponseToolkit, + callWithRequest: CallClusterWithRequest +): Promise> => { + 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> => { + 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, From c519a1b716bd185f31bcb78537bf5d1a1a9f38e2 Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Wed, 25 Mar 2020 11:23:01 -0700 Subject: [PATCH 2/6] add enabled/disabled time in Detector --- public/models/interfaces.ts | 2 ++ server/cluster/ad/adPlugin.ts | 2 +- server/routes/ad.ts | 1 + server/routes/utils/adHelpers.ts | 8 ++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index 38137394..ca31246c 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -99,6 +99,8 @@ export type Detector = { detectionInterval: { period: Schedule }; uiMetadata: UiMetaData; enabled?: boolean; + enabledTime?: Date; + disabledTime?: Date; }; export type DetectorListItem = { diff --git a/server/cluster/ad/adPlugin.ts b/server/cluster/ad/adPlugin.ts index 19f089f4..91cdf582 100644 --- a/server/cluster/ad/adPlugin.ts +++ b/server/cluster/ad/adPlugin.ts @@ -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', diff --git a/server/routes/ad.ts b/server/routes/ad.ts index 404276ae..c4fb0016 100644 --- a/server/routes/ad.ts +++ b/server/routes/ad.ts @@ -167,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, diff --git a/server/routes/utils/adHelpers.ts b/server/routes/utils/adHelpers.ts index a998289c..7ac130a1 100644 --- a/server/routes/utils/adHelpers.ts +++ b/server/routes/utils/adHelpers.ts @@ -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, }; }; From 7910c9ae02ec59a9ebf073a6c0823ccd87a9481b Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Fri, 27 Mar 2020 01:00:30 -0700 Subject: [PATCH 3/6] add ad job fields in server Detector type --- server/models/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/models/types.ts b/server/models/types.ts index 718611fe..452e2a2f 100644 --- a/server/models/types.ts +++ b/server/models/types.ts @@ -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 = { From 7b218115cdc54be5ca925560c9321c65f3de4a4c Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Fri, 27 Mar 2020 12:20:09 -0700 Subject: [PATCH 4/6] fix start/end time as we changed to data_start_time/data_end_time --- server/routes/ad.ts | 8 ++++---- server/routes/utils/__tests__/adHelpers.test.ts | 16 ++++++++-------- server/routes/utils/adHelpers.ts | 17 ++++++++++------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/server/routes/ad.ts b/server/routes/ad.ts index c4fb0016..d152d2e4 100644 --- a/server/routes/ad.ts +++ b/server/routes/ad.ts @@ -409,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]; @@ -439,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 }) diff --git a/server/routes/utils/__tests__/adHelpers.test.ts b/server/routes/utils/__tests__/adHelpers.test.ts index 3fa98f1a..be5e2b89 100644 --- a/server/routes/utils/__tests__/adHelpers.test.ts +++ b/server/routes/utils/__tests__/adHelpers.test.ts @@ -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' } }, }, }, }, diff --git a/server/routes/utils/adHelpers.ts b/server/routes/utils/adHelpers.ts index 7ac130a1..c1ed3b17 100644 --- a/server/routes/utils/adHelpers.ts +++ b/server/routes/utils/adHelpers.ts @@ -105,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' } }, }, }, }, @@ -126,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) @@ -136,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, }); }); From 759120fcd5b8f9a29181d2318087321e5d668afa Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Fri, 27 Mar 2020 12:27:08 -0700 Subject: [PATCH 5/6] remove _ from start/stop api --- public/redux/reducers/ad.ts | 4 ++-- server/cluster/ad/adPlugin.ts | 4 ++-- server/routes/ad.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/redux/reducers/ad.ts b/public/redux/reducers/ad.ts index 09f2e0eb..814ca0b0 100644 --- a/public/redux/reducers/ad.ts +++ b/public/redux/reducers/ad.ts @@ -283,7 +283,7 @@ export const deleteDetector = (detectorId: string): APIAction => ({ export const startDetector = (detectorId: string): APIAction => ({ type: START_DETECTOR, request: (client: IHttpService) => - client.post(`..${AD_NODE_API.DETECTOR}/${detectorId}/_start`, { + client.post(`..${AD_NODE_API.DETECTOR}/${detectorId}/start`, { detectorId: detectorId, }), detectorId, @@ -292,7 +292,7 @@ export const startDetector = (detectorId: string): APIAction => ({ export const stopDetector = (detectorId: string): APIAction => ({ type: STOP_DETECTOR, request: (client: IHttpService) => - client.post(`..${AD_NODE_API.DETECTOR}/${detectorId}/_stop`, { + client.post(`..${AD_NODE_API.DETECTOR}/${detectorId}/stop`, { detectorId: detectorId, }), detectorId, diff --git a/server/cluster/ad/adPlugin.ts b/server/cluster/ad/adPlugin.ts index 91cdf582..9ef22643 100644 --- a/server/cluster/ad/adPlugin.ts +++ b/server/cluster/ad/adPlugin.ts @@ -103,7 +103,7 @@ export default function adPlugin(Client: any, config: any, components: any) { ad.startDetector = ca({ url: { - fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_start`, + fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/start`, req: { detectorId: { type: 'string', @@ -116,7 +116,7 @@ export default function adPlugin(Client: any, config: any, components: any) { ad.stopDetector = ca({ url: { - fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_stop`, + fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/stop`, req: { detectorId: { type: 'string', diff --git a/server/routes/ad.ts b/server/routes/ad.ts index d152d2e4..3b851de8 100644 --- a/server/routes/ad.ts +++ b/server/routes/ad.ts @@ -55,8 +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); + apiRouter.post('/detectors/{detectorId}/start', startDetector); + apiRouter.post('/detectors/{detectorId}/stop', stopDetector); } const deleteDetector = async ( From 700e4682def3a95f7793e06d083e94551bb57f80 Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Sat, 28 Mar 2020 18:26:10 -0700 Subject: [PATCH 6/6] fix wrong url --- server/cluster/ad/adPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/cluster/ad/adPlugin.ts b/server/cluster/ad/adPlugin.ts index 9ef22643..91cdf582 100644 --- a/server/cluster/ad/adPlugin.ts +++ b/server/cluster/ad/adPlugin.ts @@ -103,7 +103,7 @@ export default function adPlugin(Client: any, config: any, components: any) { ad.startDetector = ca({ url: { - fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/start`, + fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_start`, req: { detectorId: { type: 'string', @@ -116,7 +116,7 @@ export default function adPlugin(Client: any, config: any, components: any) { ad.stopDetector = ca({ url: { - fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/stop`, + fmt: `${API.DETECTOR_BASE}/<%=detectorId%>/_stop`, req: { detectorId: { type: 'string',