diff --git a/server/spec/segments.jest.ts b/server/spec/segments.jest.ts index 0e3ae9d..6aaf65f 100644 --- a/server/spec/segments.jest.ts +++ b/server/spec/segments.jest.ts @@ -1,5 +1,5 @@ import { deleteNonDetectedSegments } from '../src/controllers/analytics_controller'; -import * as AnalyticUnit from '../src/models/analytic_unit_model'; +import * as AnalyticUnit from '../src/models/analytic_units'; import * as Segment from '../src/models/segment_model'; import * as _ from 'lodash'; diff --git a/server/src/controllers/analytics_controller.ts b/server/src/controllers/analytics_controller.ts index cc7866a..b32f221 100644 --- a/server/src/controllers/analytics_controller.ts +++ b/server/src/controllers/analytics_controller.ts @@ -2,9 +2,9 @@ import { AnalyticsMessageMethod, AnalyticsMessage } from '../models/analytics_me import { AnalyticsTask, AnalyticsTaskType, AnalyticsTaskId } from '../models/analytics_task_model'; import * as AnalyticUnitCache from '../models/analytic_unit_cache_model'; import * as Segment from '../models/segment_model'; -import * as Threshold from '../models/threshold_model'; -import * as AnalyticUnit from '../models/analytic_unit_model'; +import * as AnalyticUnit from '../models/analytic_units'; import * as Detection from '../models/detection_model'; +import { ThresholdAnalyticUnit } from '../models/analytic_units/threshold_analytic_unit_model'; import { AnalyticsService } from '../services/analytics_service'; import { AlertService } from '../services/alert_service'; import { HASTIC_API_KEY } from '../config'; @@ -226,8 +226,10 @@ export async function runLearning(id: AnalyticUnit.AnalyticUnitId) { segmentObjs = _.concat(segmentObjs, deletedSegmentsObjs); taskPayload.segments = segmentObjs; } else if(detector === AnalyticUnit.DetectorType.THRESHOLD) { - const threshold = await Threshold.findOne(id); - taskPayload.threshold = threshold; + taskPayload.threshold = { + value: (analyticUnit as ThresholdAnalyticUnit).value, + condition: (analyticUnit as ThresholdAnalyticUnit).condition + }; } const range = await getQueryRange(id, detector); @@ -420,11 +422,11 @@ export async function getActiveWebhooks() { return analyticUnits.map(analyticUnit => analyticUnit.id); } -export async function createAnalyticUnitFromObject(obj: any): Promise { +export async function saveAnalyticUnitFromObject(obj: any): Promise { if(obj.datasource !== undefined) { obj.metric.datasource = obj.datasource; } - const unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.AnalyticUnit.fromObject(obj); + const unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.createAnalyticUnitFromObject(obj); const id = await AnalyticUnit.create(unit); return id; @@ -460,14 +462,6 @@ export async function updateSegments( return { addedIds }; } -export async function updateThreshold( - id: AnalyticUnit.AnalyticUnitId, - value: number, - condition: Threshold.Condition -) { - await Threshold.updateThreshold(id, value, condition); -} - export async function runLearningWithDetection(id: AnalyticUnit.AnalyticUnitId) { // TODO: move setting status somehow "inside" learning await AnalyticUnit.setStatus(id, AnalyticUnit.AnalyticUnitStatus.PENDING); diff --git a/server/src/index.ts b/server/src/index.ts index c59de28..28a731c 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,6 +1,5 @@ import { router as analyticUnitsRouter } from './routes/analytic_units_router'; import { router as segmentsRouter } from './routes/segments_router'; -import { router as thresholdRouter } from './routes/threshold_router'; import { router as dataRouter } from './routes/data_router'; import { router as detectionsRouter } from './routes/detections_router'; @@ -56,7 +55,6 @@ async function init() { const rootRouter = new Router(); rootRouter.use('/analyticUnits', analyticUnitsRouter.routes(), analyticUnitsRouter.allowedMethods()); rootRouter.use('/segments', segmentsRouter.routes(), segmentsRouter.allowedMethods()); - rootRouter.use('/threshold', thresholdRouter.routes(), thresholdRouter.allowedMethods()); rootRouter.use('/query', dataRouter.routes(), dataRouter.allowedMethods()); rootRouter.use('/detections', detectionsRouter.routes(), detectionsRouter.allowedMethods()); diff --git a/server/src/migrations.ts b/server/src/migrations.ts index 7b2cd50..cfa4a9d 100644 --- a/server/src/migrations.ts +++ b/server/src/migrations.ts @@ -6,6 +6,7 @@ import * as _ from 'lodash'; const metaDB = makeDBQ(Collection.DB_META); const analyticUnitsDB = makeDBQ(Collection.ANALYTIC_UNITS); const analyticUnitCachesDB = makeDBQ(Collection.ANALYTIC_UNIT_CACHES); +const thresholdsDB = makeDBQ(Collection.THRESHOLD); const DB_META_ID = '0'; @@ -15,7 +16,8 @@ type DbMeta = { const REVISIONS = new Map([ [1, convertPanelUrlToPanelId], - [2, convertUnderscoreToCamelCase] + [2, convertUnderscoreToCamelCase], + [3, integrateThresholdsIntoAnalyticUnits] ]); export async function applyDBMigrations() { @@ -38,9 +40,7 @@ export async function applyDBMigrations() { async function convertPanelUrlToPanelId() { const analyticUnits = await analyticUnitsDB.findMany({ panelUrl: { $exists: true } }); - console.log(`Found ${analyticUnits.length} analytic units with panelUrl field`); if(analyticUnits.length === 0) { - console.log('Nothing to migrate'); return; } @@ -50,7 +50,6 @@ async function convertPanelUrlToPanelId() { .map(analyticUnit => { const parsedPanelUrl = analyticUnit.panelUrl.match(PANEL_URL_REGEX) || analyticUnit.panelUrl.match(NEW_PANEL_URL_REGEX); if(parsedPanelUrl === null) { - console.log(`Cannot parse url: ${analyticUnit.panelUrl}`); return null; } const grafanaUrl = parsedPanelUrl[1]; @@ -95,3 +94,17 @@ async function convertUnderscoreToCamelCase() { await Promise.all(promises); } + +async function integrateThresholdsIntoAnalyticUnits() { + const thresholds = await thresholdsDB.findMany({}); + + const promises = thresholds.map(threshold => + analyticUnitsDB.updateOne(threshold._id, { + value: threshold.value, + condition: threshold.condition + }) + ); + + await Promise.all(promises); + await thresholdsDB.removeMany({}); +} diff --git a/server/src/models/analytic_unit_cache_model.ts b/server/src/models/analytic_unit_cache_model.ts index 0c3fa56..803b1da 100644 --- a/server/src/models/analytic_unit_cache_model.ts +++ b/server/src/models/analytic_unit_cache_model.ts @@ -1,4 +1,4 @@ -import { AnalyticUnitId } from "./analytic_unit_model"; +import { AnalyticUnitId } from './analytic_units'; import { Collection, makeDBQ } from '../services/data_service'; diff --git a/server/src/models/analytic_unit_model.ts b/server/src/models/analytic_unit_model.ts deleted file mode 100644 index 9b83d07..0000000 --- a/server/src/models/analytic_unit_model.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { Collection, makeDBQ, SortingOrder } from '../services/data_service'; - -import { Metric } from 'grafana-datasource-kit'; - -import * as _ from 'lodash'; - -let db = makeDBQ(Collection.ANALYTIC_UNITS); - - -export const ANALYTIC_UNIT_TYPES = { - pattern: [ - { - name: 'General', - value: 'GENERAL' - }, - { - name: 'Peak', - value: 'PEAK' - }, - { - name: 'Trough', - value: 'TROUGH' - }, - { - name: 'Jump', - value: 'JUMP' - }, - { - name: 'Drop', - value: 'DROP' - } - ], - threshold: [ - { - name: 'Threshold', - value: 'THRESHOLD' - } - ] -}; - -export enum DetectorType { - PATTERN = 'pattern', - THRESHOLD = 'threshold' -}; - -export type AnalyticUnitId = string; -export enum AnalyticUnitStatus { - READY = 'READY', - PENDING = 'PENDING', - LEARNING = 'LEARNING', - SUCCESS = 'SUCCESS', - FAILED = 'FAILED' -} - -export type FindManyQuery = { - name?: string, - grafanaUrl?: string, - panelId?: string, - type?: string, - metric?: Metric, - alert?: boolean, - id?: AnalyticUnitId, - lastDetectionTime?: number, - status?: AnalyticUnitStatus, - error?: string, - labeledColor?: string, - deletedColor?: string, - detectorType?: DetectorType, - visible?: boolean -}; - - -export class AnalyticUnit { - constructor( - public name: string, - public grafanaUrl: string, - public panelId: string, - public type: string, - public metric?: Metric, - public alert?: boolean, - public id?: AnalyticUnitId, - public lastDetectionTime?: number, - public status?: AnalyticUnitStatus, - public error?: string, - public labeledColor?: string, - public deletedColor?: string, - public detectorType?: DetectorType, - public visible?: boolean - ) { - - if(name === undefined) { - throw new Error(`Missing field "name"`); - } - if(grafanaUrl === undefined) { - throw new Error(`Missing field "grafanaUrl"`); - } - if(type === undefined) { - throw new Error(`Missing field "type"`); - } - } - - public toObject() { - let metric; - if(this.metric !== undefined) { - metric = this.metric.toObject(); - } - - return { - _id: this.id, - name: this.name, - grafanaUrl: this.grafanaUrl, - panelId: this.panelId, - type: this.type, - metric, - alert: this.alert, - lastDetectionTime: this.lastDetectionTime, - status: this.status, - error: this.error, - labeledColor: this.labeledColor, - deletedColor: this.deletedColor, - detectorType: this.detectorType, - visible: this.visible - }; - } - - public toPanelObject() { - return { - id: this.id, - name: this.name, - type: this.type, - alert: this.alert, - labeledColor: this.labeledColor, - deletedColor: this.deletedColor, - detectorType: this.detectorType, - visible: this.visible - }; - } - - static fromObject(obj: any): AnalyticUnit { - if(obj === undefined) { - throw new Error('obj is undefined'); - } - let metric: Metric; - if(obj.metric !== undefined) { - metric = Metric.fromObject(obj.metric); - } - return new AnalyticUnit( - obj.name, - obj.grafanaUrl, - obj.panelId, - obj.type, - metric, - obj.alert, - obj._id, - obj.lastDetectionTime, - obj.status as AnalyticUnitStatus, - obj.error, - obj.labeledColor, - obj.deletedColor, - obj.detectorType, - obj.visible - ); - } - -} - - -export async function findById(id: AnalyticUnitId): Promise { - let obj = await db.findOne(id); - if(obj === null) { - return null; - } - return AnalyticUnit.fromObject(obj); -} - -export async function findMany(query: FindManyQuery): Promise { - const analyticUnits = await db.findMany(query, { - createdAt: SortingOrder.ASCENDING, - name: SortingOrder.ASCENDING - }); - if(analyticUnits === null) { - return []; - } - return analyticUnits.map(AnalyticUnit.fromObject); -} - - -/** - * Creates and updates new unit.id - * - * @param unit to create - * @returns unit.id - */ -export async function create(unit: AnalyticUnit): Promise { - let obj = unit.toObject(); - return db.insertOne(obj); -} - -export async function remove(id: AnalyticUnitId): Promise { - // TODO: remove it`s segments - // TODO: remove it`s cache - await db.removeOne(id); -} - -export async function update(id: AnalyticUnitId, unit: AnalyticUnit) { - const updateObj = { - name: unit.name, - labeledColor: unit.labeledColor, - deletedColor: unit.deletedColor, - visible: unit.visible - }; - - return db.updateOne(id, updateObj); -} - -export async function setStatus(id: AnalyticUnitId, status: string, error?: string) { - return db.updateOne(id, { status, error }); -} - -export async function setDetectionTime(id: AnalyticUnitId, lastDetectionTime: number) { - return db.updateOne(id, { lastDetectionTime }); -} - -export async function setAlert(id: AnalyticUnitId, alert: boolean) { - return db.updateOne(id, { alert }); -} - -export async function setMetric(id: AnalyticUnitId, metric: Metric) { - return db.updateOne(id, { metric }); -} - -export function getDetectorByType(analyticUnitType: string): DetectorType { - let detector; - _.forOwn(ANALYTIC_UNIT_TYPES, (types, detectorType) => { - if(_.find(types, { value: analyticUnitType }) !== undefined) { - detector = detectorType; - } - }); - - if(detector === undefined) { - throw new Error(`Can't find detector for analytic unit of type "${analyticUnitType}"`); - } - return detector; -} diff --git a/server/src/models/analytic_units/analytic_unit_model.ts b/server/src/models/analytic_units/analytic_unit_model.ts new file mode 100644 index 0000000..325dfc8 --- /dev/null +++ b/server/src/models/analytic_units/analytic_unit_model.ts @@ -0,0 +1,75 @@ +import { AnalyticUnitId, AnalyticUnitStatus, DetectorType } from './types'; + +import { Metric } from 'grafana-datasource-kit'; + + +export abstract class AnalyticUnit { + constructor( + public name: string, + public grafanaUrl: string, + public panelId: string, + // TODO: enum type + // TODO: type -> subType + public type: string, + public metric?: Metric, + public alert?: boolean, + public id?: AnalyticUnitId, + public lastDetectionTime?: number, + public status?: AnalyticUnitStatus, + public error?: string, + public labeledColor?: string, + public deletedColor?: string, + // TODO: detectorType -> type + public detectorType?: DetectorType, + public visible?: boolean + ) { + + if(name === undefined) { + throw new Error(`Missing field "name"`); + } + if(grafanaUrl === undefined) { + throw new Error(`Missing field "grafanaUrl"`); + } + if(type === undefined) { + throw new Error(`Missing field "type"`); + } + } + + public toObject() { + let metric; + if(this.metric !== undefined) { + metric = this.metric.toObject(); + } + + return { + _id: this.id, + name: this.name, + grafanaUrl: this.grafanaUrl, + panelId: this.panelId, + type: this.type, + metric, + alert: this.alert, + lastDetectionTime: this.lastDetectionTime, + status: this.status, + error: this.error, + labeledColor: this.labeledColor, + deletedColor: this.deletedColor, + detectorType: this.detectorType, + visible: this.visible + }; + } + + public toPanelObject() { + return { + id: this.id, + name: this.name, + type: this.type, + alert: this.alert, + labeledColor: this.labeledColor, + deletedColor: this.deletedColor, + detectorType: this.detectorType, + visible: this.visible + }; + } + +} diff --git a/server/src/models/analytic_units/db.ts b/server/src/models/analytic_units/db.ts new file mode 100644 index 0000000..a0369a8 --- /dev/null +++ b/server/src/models/analytic_units/db.ts @@ -0,0 +1,89 @@ +import { createAnalyticUnitFromObject } from './utils'; +import { AnalyticUnit } from './analytic_unit_model'; +import { AnalyticUnitId, FindManyQuery } from './types'; +import { Collection, makeDBQ, SortingOrder } from '../../services/data_service'; + +import { Metric } from 'grafana-datasource-kit'; + +import * as _ from 'lodash'; + + +const db = makeDBQ(Collection.ANALYTIC_UNITS); + +export async function findById(id: AnalyticUnitId): Promise { + let obj = await db.findOne(id); + if (obj === null) { + return null; + } + return createAnalyticUnitFromObject(obj); +} + +export async function findMany(query: FindManyQuery): Promise { + const analyticUnits = await db.findMany(query, { + createdAt: SortingOrder.ASCENDING, + name: SortingOrder.ASCENDING + }); + if (analyticUnits === null) { + return []; + } + return analyticUnits.map(createAnalyticUnitFromObject); +} + + +/** + * Creates and updates new unit.id + * + * @param unit to create + * @returns unit.id + */ +export async function create(unit: AnalyticUnit): Promise { + let obj = unit.toObject(); + return db.insertOne(obj); +} + +export async function remove(id: AnalyticUnitId): Promise { + // TODO: remove it`s segments + // TODO: remove it`s cache + await db.removeOne(id); +} + +/** + * Changes values of analytic unit fields to according values of obj + * + * @param id analytic unit id + * @param obj object with keys and values which need to be updated in analytic unit + */ +export async function update(id: AnalyticUnitId, obj: any) { + const analyticUnitObj = await db.findOne(id); + if(analyticUnitObj === null) { + throw new Error(`Analytic unit ${id} doesn't exist`); + } + + const analyticUnit = createAnalyticUnitFromObject(analyticUnitObj); + let updateObj: any = analyticUnit.toPanelObject(); + delete updateObj.id; + updateObj = _.mapValues(updateObj, (value, key) => { + if(_.has(obj, key)) { + return obj[key]; + } + return value; + }); + + return db.updateOne(id, updateObj); +} + +export async function setStatus(id: AnalyticUnitId, status: string, error?: string) { + return db.updateOne(id, { status, error }); +} + +export async function setDetectionTime(id: AnalyticUnitId, lastDetectionTime: number) { + return db.updateOne(id, { lastDetectionTime }); +} + +export async function setAlert(id: AnalyticUnitId, alert: boolean) { + return db.updateOne(id, { alert }); +} + +export async function setMetric(id: AnalyticUnitId, metric: Metric) { + return db.updateOne(id, { metric }); +} diff --git a/server/src/models/analytic_units/index.ts b/server/src/models/analytic_units/index.ts new file mode 100644 index 0000000..2d9df4a --- /dev/null +++ b/server/src/models/analytic_units/index.ts @@ -0,0 +1,26 @@ +import { createAnalyticUnitFromObject, getDetectorByType } from './utils'; +import { AnalyticUnitId, AnalyticUnitStatus, DetectorType, ANALYTIC_UNIT_TYPES } from './types'; +import { AnalyticUnit } from './analytic_unit_model'; +import { PatternAnalyticUnit } from './pattern_analytic_unit_model'; +import { ThresholdAnalyticUnit } from './threshold_analytic_unit_model'; +import { + findById, + findMany, + create, + remove, + update, + setStatus, + setDetectionTime, + setAlert, + setMetric +} from './db'; + + +export { + AnalyticUnit, PatternAnalyticUnit, ThresholdAnalyticUnit, + AnalyticUnitId, AnalyticUnitStatus, DetectorType, ANALYTIC_UNIT_TYPES, + createAnalyticUnitFromObject, getDetectorByType, + findById, findMany, + create, remove, update, + setStatus, setDetectionTime, setAlert, setMetric +}; diff --git a/server/src/models/analytic_units/pattern_analytic_unit_model.ts b/server/src/models/analytic_units/pattern_analytic_unit_model.ts new file mode 100644 index 0000000..1b109ba --- /dev/null +++ b/server/src/models/analytic_units/pattern_analytic_unit_model.ts @@ -0,0 +1,78 @@ +import { AnalyticUnit } from './analytic_unit_model'; +import { AnalyticUnitId, AnalyticUnitStatus, DetectorType } from './types'; + +import { Metric } from 'grafana-datasource-kit'; + + +export class PatternAnalyticUnit extends AnalyticUnit { + constructor( + name: string, + grafanaUrl: string, + panelId: string, + type: string, + metric?: Metric, + alert?: boolean, + id?: AnalyticUnitId, + lastDetectionTime?: number, + status?: AnalyticUnitStatus, + error?: string, + labeledColor?: string, + deletedColor?: string, + visible?: boolean + ) { + super( + name, + grafanaUrl, + panelId, + type, + metric, + alert, + id, + lastDetectionTime, + status, + error, + labeledColor, + deletedColor, + DetectorType.PATTERN, + visible + ); + } + + toObject() { + const baseObject = super.toObject(); + return { + ...baseObject + }; + } + + toPanelObject() { + const baseObject = super.toPanelObject(); + return { + ...baseObject + }; + } + + static fromObject(obj: any) { + // TODO: remove duplication + let metric: Metric; + if(obj.metric !== undefined) { + metric = Metric.fromObject(obj.metric); + } + + return new PatternAnalyticUnit( + obj.name, + obj.grafanaUrl, + obj.panelId, + obj.type, + metric, + obj.alert, + obj._id, + obj.lastDetectionTime, + obj.status, + obj.error, + obj.labeledColor, + obj.deletedColor, + obj.visible + ); + } +} diff --git a/server/src/models/analytic_units/threshold_analytic_unit_model.ts b/server/src/models/analytic_units/threshold_analytic_unit_model.ts new file mode 100644 index 0000000..f88e986 --- /dev/null +++ b/server/src/models/analytic_units/threshold_analytic_unit_model.ts @@ -0,0 +1,95 @@ +import { AnalyticUnit } from './analytic_unit_model'; +import { AnalyticUnitId, AnalyticUnitStatus, DetectorType } from './types'; + +import { Metric } from 'grafana-datasource-kit'; + + +export enum Condition { + ABOVE = '>', + ABOVE_OR_EQUAL = '>=', + EQUAL = '=', + LESS_OR_EQUAL = '<=', + LESS = '<', + NO_DATA = 'NO_DATA' +}; + +export class ThresholdAnalyticUnit extends AnalyticUnit { + constructor( + name: string, + grafanaUrl: string, + panelId: string, + type: string, + public value: number, + public condition: Condition, + metric?: Metric, + alert?: boolean, + id?: AnalyticUnitId, + lastDetectionTime?: number, + status?: AnalyticUnitStatus, + error?: string, + labeledColor?: string, + deletedColor?: string, + visible?: boolean + ) { + super( + name, + grafanaUrl, + panelId, + type, + metric, + alert, + id, + lastDetectionTime, + status, + error, + labeledColor, + deletedColor, + DetectorType.THRESHOLD, + visible + ); + } + + toObject() { + const baseObject = super.toObject(); + return { + ...baseObject, + value: this.value, + condition: this.condition + }; + } + + toPanelObject() { + const baseObject = super.toPanelObject(); + return { + ...baseObject, + value: this.value, + condition: this.condition + }; + } + + static fromObject(obj: any) { + // TODO: remove duplication + let metric: Metric; + if (obj.metric !== undefined) { + metric = Metric.fromObject(obj.metric); + } + + return new ThresholdAnalyticUnit( + obj.name, + obj.grafanaUrl, + obj.panelId, + obj.type, + obj.value, + obj.condition, + metric, + obj.alert, + obj._id, + obj.lastDetectionTime, + obj.status, + obj.error, + obj.labeledColor, + obj.deletedColor, + obj.visible + ); + } +} diff --git a/server/src/models/analytic_units/types.ts b/server/src/models/analytic_units/types.ts new file mode 100644 index 0000000..1b7e12b --- /dev/null +++ b/server/src/models/analytic_units/types.ts @@ -0,0 +1,64 @@ +import { Metric } from 'grafana-datasource-kit'; + + +export type AnalyticUnitId = string; +export enum AnalyticUnitStatus { + READY = 'READY', + PENDING = 'PENDING', + LEARNING = 'LEARNING', + SUCCESS = 'SUCCESS', + FAILED = 'FAILED' +}; + +export type FindManyQuery = { + name?: string, + grafanaUrl?: string, + panelId?: string, + type?: string, + metric?: Metric, + alert?: boolean, + id?: AnalyticUnitId, + lastDetectionTime?: number, + status?: AnalyticUnitStatus, + error?: string, + labeledColor?: string, + deletedColor?: string, + detectorType?: DetectorType, + visible?: boolean +}; + +export const ANALYTIC_UNIT_TYPES = { + pattern: [ + { + name: 'General', + value: 'GENERAL' + }, + { + name: 'Peak', + value: 'PEAK' + }, + { + name: 'Trough', + value: 'TROUGH' + }, + { + name: 'Jump', + value: 'JUMP' + }, + { + name: 'Drop', + value: 'DROP' + } + ], + threshold: [ + { + name: 'Threshold', + value: 'THRESHOLD' + } + ] +}; + +export enum DetectorType { + PATTERN = 'pattern', + THRESHOLD = 'threshold' +}; diff --git a/server/src/models/analytic_units/utils.ts b/server/src/models/analytic_units/utils.ts new file mode 100644 index 0000000..0c43fd0 --- /dev/null +++ b/server/src/models/analytic_units/utils.ts @@ -0,0 +1,38 @@ +import { DetectorType, ANALYTIC_UNIT_TYPES } from './types'; +import { AnalyticUnit } from './analytic_unit_model'; +import { PatternAnalyticUnit } from './pattern_analytic_unit_model'; +import { ThresholdAnalyticUnit } from './threshold_analytic_unit_model'; + +import * as _ from 'lodash'; + + +export function createAnalyticUnitFromObject(obj: any): AnalyticUnit { + if (obj === undefined) { + throw new Error('obj is undefined'); + } + + const detectorType: DetectorType = obj.detectorType; + switch (detectorType) { + case DetectorType.PATTERN: + return PatternAnalyticUnit.fromObject(obj); + case DetectorType.THRESHOLD: + return ThresholdAnalyticUnit.fromObject(obj); + + default: + throw new Error(`Can't create analytic unit with type "${detectorType}"`); + } +} + +export function getDetectorByType(analyticUnitType: string): DetectorType { + let detector; + _.forOwn(ANALYTIC_UNIT_TYPES, (types, detectorType) => { + if(_.find(types, { value: analyticUnitType }) !== undefined) { + detector = detectorType; + } + }); + + if(detector === undefined) { + throw new Error(`Can't find detector for analytic unit of type "${analyticUnitType}"`); + } + return detector; +} diff --git a/server/src/models/analytics_task_model.ts b/server/src/models/analytics_task_model.ts index 8d1928d..4444561 100644 --- a/server/src/models/analytics_task_model.ts +++ b/server/src/models/analytics_task_model.ts @@ -1,4 +1,4 @@ -import { AnalyticUnitId } from "./analytic_unit_model"; +import { AnalyticUnitId } from './analytic_units'; import { uid } from "../utils/uid"; diff --git a/server/src/models/detection_model.ts b/server/src/models/detection_model.ts index e52a869..cc0efb7 100644 --- a/server/src/models/detection_model.ts +++ b/server/src/models/detection_model.ts @@ -1,4 +1,4 @@ -import { AnalyticUnitId } from './analytic_unit_model'; +import { AnalyticUnitId } from './analytic_units'; import { Collection, makeDBQ } from '../services/data_service'; import * as _ from 'lodash'; diff --git a/server/src/models/segment_model.ts b/server/src/models/segment_model.ts index addd06c..e3cd652 100644 --- a/server/src/models/segment_model.ts +++ b/server/src/models/segment_model.ts @@ -1,4 +1,4 @@ -import { AnalyticUnitId } from './analytic_unit_model'; +import { AnalyticUnitId } from './analytic_units'; import { Collection, makeDBQ } from '../services/data_service'; diff --git a/server/src/models/threshold_model.ts b/server/src/models/threshold_model.ts deleted file mode 100644 index 13217e3..0000000 --- a/server/src/models/threshold_model.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { AnalyticUnitId } from './analytic_unit_model'; - -import { Collection, makeDBQ } from '../services/data_service'; - -import * as _ from 'lodash'; - -let db = makeDBQ(Collection.THRESHOLD); - - -export enum Condition { - ABOVE = '>', - ABOVE_OR_EQUAL = '>=', - EQUAL = '=', - LESS_OR_EQUAL = '<=', - LESS = '<', - NO_DATA = 'NO_DATA' -}; - -export class Threshold { - constructor( - public id: AnalyticUnitId, - public value: number, - public condition: Condition - ) { - if(id === undefined) { - throw new Error('id is undefined'); - } - if(value === undefined) { - throw new Error('condition is undefined'); - } - if(condition === undefined) { - throw new Error('condition is undefined'); - } - } - - public toObject() { - return { - _id: this.id, - value: this.value, - condition: this.condition - }; - } - - static fromObject(obj: any): Threshold { - if(obj === undefined) { - throw new Error('obj is undefined'); - } - return new Threshold(obj._id, +obj.value, obj.condition); - } -} - -export async function findOne(id: AnalyticUnitId): Promise { - const threshold = await db.findOne(id); - if(threshold === null) { - return null; - } - return Threshold.fromObject(threshold); -} - -export async function updateThreshold(id: AnalyticUnitId, value: number, condition: Condition) { - let threshold = await db.findOne(id); - if(threshold === null) { - threshold = new Threshold(id, value, condition); - return db.insertOne(threshold.toObject()); - } - return db.updateOne(id, { value, condition }); -} - -export async function removeThreshold(id: AnalyticUnitId) { - return db.removeOne(id); -} diff --git a/server/src/routes/analytic_units_router.ts b/server/src/routes/analytic_units_router.ts index c282c8a..d9fe0f5 100644 --- a/server/src/routes/analytic_units_router.ts +++ b/server/src/routes/analytic_units_router.ts @@ -1,7 +1,7 @@ import * as AnalyticsController from '../controllers/analytics_controller'; -import * as AnalyticUnit from '../models/analytic_unit_model'; +import * as AnalyticUnit from '../models/analytic_units'; -import { createAnalyticUnitFromObject } from '../controllers/analytics_controller'; +import { saveAnalyticUnitFromObject } from '../controllers/analytics_controller'; import * as Router from 'koa-router'; import * as _ from 'lodash'; @@ -50,7 +50,7 @@ function getTypes(ctx: Router.IRouterContext) { } async function createUnit(ctx: Router.IRouterContext) { - const id = await createAnalyticUnitFromObject(ctx.request.body); + const id = await saveAnalyticUnitFromObject(ctx.request.body); ctx.response.body = { id }; } @@ -61,7 +61,7 @@ async function updateUnit(ctx: Router.IRouterContext) { throw new Error('Cannot update undefined id'); } - AnalyticUnit.update(analyticUnit.id, analyticUnit); + await AnalyticUnit.update(analyticUnit.id, analyticUnit); ctx.response.body = { code: 200, message: 'Success' diff --git a/server/src/routes/data_router.ts b/server/src/routes/data_router.ts index 98e63df..e0107c3 100644 --- a/server/src/routes/data_router.ts +++ b/server/src/routes/data_router.ts @@ -1,4 +1,4 @@ -import * as AnalyticUnit from '../models/analytic_unit_model'; +import * as AnalyticUnit from '../models/analytic_units'; import { HASTIC_API_KEY } from '../config'; import { getGrafanaUrl } from '../utils/grafana'; diff --git a/server/src/routes/detections_router.ts b/server/src/routes/detections_router.ts index 9dd3811..47566bf 100644 --- a/server/src/routes/detections_router.ts +++ b/server/src/routes/detections_router.ts @@ -1,5 +1,5 @@ import * as AnalyticsController from '../controllers/analytics_controller'; -import { AnalyticUnitId } from '../models/analytic_unit_model'; +import { AnalyticUnitId } from '../models/analytic_units'; import { DetectionSpan } from '../models/detection_model'; import * as Router from 'koa-router'; diff --git a/server/src/routes/segments_router.ts b/server/src/routes/segments_router.ts index 40fb545..955ecd8 100644 --- a/server/src/routes/segments_router.ts +++ b/server/src/routes/segments_router.ts @@ -1,6 +1,6 @@ import * as AnalyticsController from '../controllers/analytics_controller'; -import { AnalyticUnitId } from '../models/analytic_unit_model'; +import { AnalyticUnitId } from '../models/analytic_units'; import * as Segment from '../models/segment_model'; import * as Router from 'koa-router'; diff --git a/server/src/routes/threshold_router.ts b/server/src/routes/threshold_router.ts deleted file mode 100644 index ea588f6..0000000 --- a/server/src/routes/threshold_router.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as AnalyticsController from '../controllers/analytics_controller'; - -import { AnalyticUnitId } from '../models/analytic_unit_model'; -import * as Threshold from '../models/threshold_model'; - -import * as Router from 'koa-router'; -import * as _ from 'lodash'; - - -async function getThresholds(ctx: Router.IRouterContext) { - - const ids: AnalyticUnitId[] = ctx.request.query.ids.split(','); - - if(ids === undefined) { - throw new Error('analyticUnitIds (ids) are missing'); - } - - const thresholds = await Promise.all( - _.map(ids, id => Threshold.findOne(id)) - ); - - ctx.response.body = { thresholds }; - -} - -async function updateThreshold(ctx: Router.IRouterContext) { - const { - id, value, condition - } = ctx.request.body as { - id: AnalyticUnitId, value: number, condition: Threshold.Condition - }; - - await AnalyticsController.updateThreshold(id, value, condition); - - ctx.response.body = { - code: 200, - message: 'Success' - }; -} - -export const router = new Router(); - -router.get('/', getThresholds); -router.patch('/', updateThreshold); diff --git a/server/src/services/alert_service.ts b/server/src/services/alert_service.ts index 5b811f7..1e19c14 100644 --- a/server/src/services/alert_service.ts +++ b/server/src/services/alert_service.ts @@ -1,7 +1,7 @@ import { sendAnalyticWebhook, sendInfoWebhook, InfoAlert, AnalyticAlert, WebhookType } from './notification_service'; import * as _ from 'lodash'; -import * as AnalyticUnit from '../models/analytic_unit_model'; +import * as AnalyticUnit from '../models/analytic_units'; import { Segment } from '../models/segment_model'; import { availableReporter } from '../utils/reporter'; diff --git a/server/src/services/data_puller.ts b/server/src/services/data_puller.ts index 8f6d0c3..a877647 100644 --- a/server/src/services/data_puller.ts +++ b/server/src/services/data_puller.ts @@ -1,5 +1,5 @@ import { AnalyticsTask, AnalyticsTaskType } from '../models/analytics_task_model'; -import * as AnalyticUnit from '../models/analytic_unit_model'; +import * as AnalyticUnit from '../models/analytic_units'; import * as AnalyticUnitCache from '../models/analytic_unit_cache_model'; import { AnalyticsService } from './analytics_service'; import { HASTIC_API_KEY } from '../config'; diff --git a/server/src/services/notification_service.ts b/server/src/services/notification_service.ts index 3152be0..708c9bb 100644 --- a/server/src/services/notification_service.ts +++ b/server/src/services/notification_service.ts @@ -1,4 +1,4 @@ -import * as AnalyticUnit from '../models/analytic_unit_model'; +import * as AnalyticUnit from '../models/analytic_units'; import { HASTIC_WEBHOOK_URL, HASTIC_WEBHOOK_TYPE, HASTIC_WEBHOOK_SECRET, HASTIC_INSTANCE_NAME } from '../config'; import axios from 'axios';