Browse Source

Merge pull request #106 from hastic/NeDB-instead-of-files-for-analytics-#58

Ne db instead of files for analytics #58
pull/1/head
Alexey Velikiy 6 years ago committed by GitHub
parent
commit
42bfc9bb24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      server/.vscode/launch.json
  2. 15
      server/src/config.ts
  3. 2
      server/src/controllers/alerts_controller.ts
  4. 156
      server/src/controllers/analytics_controller.ts
  5. 30
      server/src/controllers/metrics_controler.ts
  6. 77
      server/src/controllers/segments_controller.ts
  7. 12
      server/src/index.ts
  8. 84
      server/src/models/analytic_unit.ts
  9. 98
      server/src/models/analytic_unit_model.ts
  10. 32
      server/src/models/metric_model.ts
  11. 87
      server/src/models/segment_model.ts
  12. 32
      server/src/models/task_model.ts
  13. 94
      server/src/routes/analytic_units_router.ts
  14. 64
      server/src/routes/segments_router.ts
  15. 464
      server/src/services/analytics_service.ts
  16. 257
      server/src/services/data_service.ts
  17. 6
      server/src/services/notification_service.ts
  18. 82
      server/src/services/process_service.ts
  19. 4
      server/tsconfig.json

3
server/.vscode/launch.json vendored

@ -17,7 +17,8 @@
//"outFiles": [ "/home/alex/Projects/hastic-server/server.js" ], //"outFiles": [ "/home/alex/Projects/hastic-server/server.js" ],
"port": 9229, "port": 9229,
"restart": true, "restart": true,
"trace": true "trace": true,
"timeout": 100000
} }
] ]
} }

15
server/src/config.ts

@ -1,5 +1,6 @@
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import { getJsonDataSync } from './services/json_service'; import { getJsonDataSync } from './services/json_service';
@ -10,25 +11,17 @@ export const ANALYTICS_PATH = path.join(__dirname, '../../analytics');
export const DATA_PATH = path.join(__dirname, '../../data'); export const DATA_PATH = path.join(__dirname, '../../data');
export const ANALYTIC_UNITS_DATABASE_PATH = path.join(DATA_PATH, 'analyticUnits.db'); export const ANALYTIC_UNITS_DATABASE_PATH = path.join(DATA_PATH, 'analytic_units.db');
export const METRICS_DATABASE_PATH = path.join(DATA_PATH, 'metrics.db');
export const SEGMENTS_DATABASE_PATH = path.join(DATA_PATH, 'segments.db'); export const SEGMENTS_DATABASE_PATH = path.join(DATA_PATH, 'segments.db');
export const FILES_DATABASE_PATH = path.join(DATA_PATH, 'files.db');
export const DATASETS_PATH = path.join(DATA_PATH, 'datasets');
//export const ANALYTIC_UNITS_PATH = path.join(DATA_PATH, 'analytic_units');
export const MODELS_PATH = path.join(DATA_PATH, 'models');
export const METRICS_PATH = path.join(DATA_PATH, 'metrics');
export const SEGMENTS_PATH = path.join(DATA_PATH, 'segments');
export const HASTIC_PORT = getConfigField('HASTIC_PORT', '8000'); export const HASTIC_PORT = getConfigField('HASTIC_PORT', '8000');
export const ZMQ_CONNECTION_STRING = getConfigField('ZMQ_CONNECTION_STRING', null); export const ZMQ_CONNECTION_STRING = getConfigField('ZMQ_CONNECTION_STRING', null);
export const ZMQ_IPC_PATH = getConfigField('ZMQ_IPC_PATH', path.join('/tmp', 'hastic')); export const ZMQ_IPC_PATH = getConfigField('ZMQ_IPC_PATH', path.join(os.tmpdir(), 'hastic'));
export const ZMQ_DEV_PORT = getConfigField('ZMQ_DEV_PORT', '8002'); export const ZMQ_DEV_PORT = getConfigField('ZMQ_DEV_PORT', '8002');
export const ANLYTICS_PING_INTERVAL = 500; // ms export const ANLYTICS_PING_INTERVAL = 500; // ms
function getConfigField(field, defaultVal?) { function getConfigField(field: string, defaultVal?: any) {
let val = defaultVal; let val = defaultVal;
if(process.env[field] !== undefined) { if(process.env[field] !== undefined) {

2
server/src/controllers/alerts_controller.ts

@ -2,7 +2,7 @@
* Alarting is not supported yet * Alarting is not supported yet
*/ */
throw new console.error("Not supported"); throw new Error('not supported');
// import { runPredict } from './analytics_controller'; // import { runPredict } from './analytics_controller';

156
server/src/controllers/analytics_controller.ts

@ -1,11 +1,10 @@
import * as DataService from '../services/data_service'; import { Task } from '../models/task_model';
import { getTarget } from './metrics_controler'; import * as SegmentsController from '../models/segment_model';
import { getLabeledSegments, insertSegments, removeSegments } from './segments_controller'; import * as AnalyticUnit from '../models/analytic_unit_model';
import * as AnalyticUnit from '../models/analytic_unit'
import { AnalyticsService, AnalyticsMessage } from '../services/analytics_service'; import { AnalyticsService, AnalyticsMessage } from '../services/analytics_service';
const taskMap = {}; const taskMap = new Map<string, any>();
let nextTaskId = 0; let nextTaskId = 0;
let analyticsService: AnalyticsService = undefined; let analyticsService: AnalyticsService = undefined;
@ -16,21 +15,13 @@ function onTaskResult(taskResult: any) {
let status = taskResult.status; let status = taskResult.status;
if(status === 'SUCCESS' || status === 'FAILED') { if(status === 'SUCCESS' || status === 'FAILED') {
if(taskId in taskMap) { if(taskId in taskMap) {
let resolver = taskMap[taskId]; let resolver: any = taskMap.get(taskId);
resolver(taskResult); resolver(taskResult);
delete taskMap[taskId]; taskMap.delete(taskId);
} }
} }
} }
async function onFileSave(payload: any): Promise<any> {
return DataService.saveFile(payload.filename, payload.content);
}
async function onFileLoad(payload: any): Promise<any> {
return DataService.loadFile(payload.filename);
}
async function onMessage(message: AnalyticsMessage) { async function onMessage(message: AnalyticsMessage) {
let responsePayload = null; let responsePayload = null;
let resolvedMethod = false; let resolvedMethod = false;
@ -40,15 +31,6 @@ async function onMessage(message: AnalyticsMessage) {
resolvedMethod = true; resolvedMethod = true;
} }
if(message.method === 'FILE_SAVE') {
responsePayload = await onFileSave(message.payload);
resolvedMethod = true;
}
if(message.method === 'FILE_LOAD') {
responsePayload = await onFileLoad(message.payload);
resolvedMethod = true;
}
if(!resolvedMethod) { if(!resolvedMethod) {
throw new TypeError('Unknown method ' + message.method); throw new TypeError('Unknown method ' + message.method);
} }
@ -68,76 +50,82 @@ export function terminate() {
analyticsService.close(); analyticsService.close();
} }
async function runTask(task): Promise<any> { async function runTask(task: Task): Promise<any> {
let anomaly: AnalyticUnit.AnalyticUnit = AnalyticUnit.findById(task.analyticUnitId); // let anomaly: AnalyticUnit.AnalyticUnit = await AnalyticUnit.findById(task.analyticUnitId);
task.metric = { // task.metric = {
datasource: anomaly.metric.datasource, // datasource: anomaly.metric.datasource,
targets: anomaly.metric.targets.map(getTarget) // targets: anomaly.metric.targets.map(getTarget)
}; // };
task._taskId = nextTaskId++; // task._taskId = nextTaskId++;
await analyticsService.sendTask(task); // await analyticsService.sendTask(task);
return new Promise<void>(resolve => { // return new Promise<void>(resolve => {
taskMap[task._taskId] = resolve; // taskMap[task._taskId] = resolve;
}) // })
} }
export async function runLearning(id: AnalyticUnit.AnalyticUnitId) { export async function runLearning(id: AnalyticUnit.AnalyticUnitId) {
let segments = getLabeledSegments(id); // let segments = getLabeledSegments(id);
AnalyticUnit.setStatus(id, 'LEARNING'); // AnalyticUnit.setStatus(id, 'LEARNING');
let unit = AnalyticUnit.findById(id); // let unit = await AnalyticUnit.findById(id);
let pattern = unit.type; // let pattern = unit.type;
let task = { // let task = {
analyticUnitId: id, // analyticUnitId: id,
type: 'LEARN', // type: 'LEARN',
pattern, // pattern,
segments: segments // segments: segments
}; // };
let result = await runTask(task); // let result = await runTask(task);
if (result.status === 'SUCCESS') { // if (result.status === 'SUCCESS') {
AnalyticUnit.setStatus(id, 'READY'); // AnalyticUnit.setStatus(id, 'READY');
insertSegments(id, result.segments, false); // insertSegments(id, result.segments, false);
AnalyticUnit.setPredictionTime(id, result.lastPredictionTime); // AnalyticUnit.setPredictionTime(id, result.lastPredictionTime);
} else { // } else {
AnalyticUnit.setStatus(id, 'FAILED', result.error); // AnalyticUnit.setStatus(id, 'FAILED', result.error);
} // }
} }
export async function runPredict(id: AnalyticUnit.AnalyticUnitId) { export async function runPredict(id: AnalyticUnit.AnalyticUnitId) {
let unit = AnalyticUnit.findById(id); // let unit = await AnalyticUnit.findById(id);
let pattern = unit.type; // let pattern = unit.type;
let task = { // let task = {
type: 'predict', // type: 'PREDICT',
analyticUnitId: id, // analyticUnitId: id,
pattern, // pattern,
lastPredictionTime: unit.lastPredictionTime // lastPredictionTime: unit.lastPredictionTime
}; // };
let result = await runTask(task); // let result = await runTask(task);
if(result.status === 'FAILED') { // if(result.status === 'FAILED') {
return []; // return [];
} // }
// Merging segments // // Merging segments
let segments = getLabeledSegments(id); // let segments = getLabeledSegments(id);
if(segments.length > 0 && result.segments.length > 0) { // if(segments.length > 0 && result.segments.length > 0) {
let lastOldSegment = segments[segments.length - 1]; // let lastOldSegment = segments[segments.length - 1];
let firstNewSegment = result.segments[0]; // let firstNewSegment = result.segments[0];
if(firstNewSegment.start <= lastOldSegment.finish) { // if(firstNewSegment.start <= lastOldSegment.finish) {
result.segments[0].start = lastOldSegment.start; // result.segments[0].start = lastOldSegment.start;
removeSegments(id, [lastOldSegment.id]); // removeSegments(id, [lastOldSegment.id]);
} // }
} // }
insertSegments(id, result.segments, false); // insertSegments(id, result.segments, false);
AnalyticUnit.setPredictionTime(id, result.lastPredictionTime); // AnalyticUnit.setPredictionTime(id, result.lastPredictionTime);
return result.segments; // return result.segments;
} }
export function isAnalyticReady(): boolean { export function isAnalyticReady(): boolean {
return analyticsService.ready; return analyticsService.ready;
} }
export async function createAnalyticUnitFromObject(obj: any): Promise<AnalyticUnit.AnalyticUnitId> {
let unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.AnalyticUnit.fromObject(obj);
let id = await AnalyticUnit.create(unit);
// runLearning(unit);
return id;
}

30
server/src/controllers/metrics_controler.ts

@ -1,30 +0,0 @@
import { getJsonDataSync, writeJsonDataSync } from '../services/json_service';
import { METRICS_PATH } from '../config';
import * as crypto from 'crypto';
import * as path from 'path';
function saveTargets(targets) {
let metrics = [];
for (let target of targets) {
metrics.push(saveTarget(target));
}
return metrics;
}
function saveTarget(target) {
//const md5 = crypto.createHash('md5')
const targetId = crypto.createHash('md5').update(JSON.stringify(target)).digest('hex');
let filename = path.join(METRICS_PATH, `${targetId}.json`);
writeJsonDataSync(filename, target);
return targetId;
}
function getTarget(targetId) {
let filename = path.join(METRICS_PATH, `${targetId}.json`);
return getJsonDataSync(filename);
}
export { saveTargets, getTarget }

77
server/src/controllers/segments_controller.ts

@ -1,77 +0,0 @@
import { getJsonDataSync, writeJsonDataSync } from '../services/json_service';
import { AnalyticUnitId, findById, save } from '../models/analytic_unit';
import { SEGMENTS_PATH } from '../config';
import * as _ from 'lodash';
import * as path from 'path';
import * as fs from 'fs';
export function getLabeledSegments(id: AnalyticUnitId) {
let filename = path.join(SEGMENTS_PATH, `${id}_labeled.json`);
if(!fs.existsSync(filename)) {
return [];
} else {
let segments = getJsonDataSync(filename);
for(let segment of segments) {
if(segment.labeled === undefined) {
segment.labeled = false;
}
}
return segments;
}
}
export function getPredictedSegments(id: AnalyticUnitId) {
let filename = path.join(SEGMENTS_PATH, `${id}_segments.json`);
let jsonData;
try {
jsonData = getJsonDataSync(filename);
} catch(e) {
console.error(e.message);
jsonData = [];
}
return jsonData;
}
export function saveSegments(id: AnalyticUnitId, segments) {
let filename = path.join(SEGMENTS_PATH, `${id}_labeled.json`);
try {
return writeJsonDataSync(filename, _.uniqBy(segments, 'start'));
} catch(e) {
console.error(e.message);
throw new Error('Can`t write to db');
}
}
export function insertSegments(id: AnalyticUnitId, addedSegments, labeled: boolean) {
// Set status
let info = findById(id);
let segments = getLabeledSegments(id);
let nextId = info.nextId;
let addedIds = []
for (let segment of addedSegments) {
segment.id = nextId;
segment.labeled = labeled;
addedIds.push(nextId);
nextId++;
segments.push(segment);
}
info.nextId = nextId;
saveSegments(id, segments);
save(id, info);
return addedIds;
}
export function removeSegments(id: AnalyticUnitId, removedSegments) {
let segments = getLabeledSegments(id);
for (let segmentId of removedSegments) {
segments = segments.filter(el => el.id !== segmentId);
}
saveSegments(id, segments);
}

12
server/src/index.ts

@ -1,10 +1,9 @@
import { router as anomaliesRouter } from './routes/analytic_units_router'; import { router as anomaliesRouter } from './routes/analytic_units_router';
import { router as segmentsRouter } from './routes/segments_router'; import { router as segmentsRouter } from './routes/segments_router';
//import { router as alertsRouter } from './routes/alerts_router';
import * as AnalyticsController from './controllers/analytics_controller'; import * as AnalyticsController from './controllers/analytics_controller';
import * as Data from './services/data_service';
import * as ProcessService from './services/process_service'; import * as ProcessService from './services/process_service';
import { HASTIC_PORT } from './config'; import { HASTIC_PORT } from './config';
@ -14,19 +13,24 @@ import * as Router from 'koa-router';
import * as bodyParser from 'koa-bodyparser'; import * as bodyParser from 'koa-bodyparser';
Data.checkDataFolders();
AnalyticsController.init(); AnalyticsController.init();
ProcessService.registerExitHandler(AnalyticsController.terminate); ProcessService.registerExitHandler(AnalyticsController.terminate);
var app = new Koa(); var app = new Koa();
app.on('error', (err, ctx) => {
console.log('got server error:');
console.log(err);
});
app.use(bodyParser()) app.use(bodyParser())
app.use(async function(ctx, next) { app.use(async function(ctx, next) {
ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS'); ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next(); await next();
}); });

84
server/src/models/analytic_unit.ts

@ -1,84 +0,0 @@
import { getJsonDataSync, writeJsonDataSync } from '../services/json_service'
import { ANALYTIC_UNITS_PATH } from '../config'
import * as crypto from 'crypto';
import * as path from 'path'
import * as fs from 'fs'
export type Datasource = {
method: string,
data: Object,
params: Object,
type: string,
url: string
}
export type Metric = {
datasource: string,
targets: string[]
}
export type AnalyticUnitId = string;
export type AnalyticUnit = {
id?: AnalyticUnitId,
name: string,
panelUrl: string,
type: string,
metric: Metric,
datasource: Datasource
status: string,
error?: string,
lastPredictionTime: number,
nextId: number
}
export function createItem(item: AnalyticUnit): AnalyticUnitId {
const hashString = item.name + (new Date()).toString();
const newId: AnalyticUnitId = crypto.createHash('md5').update(hashString).digest('hex');
let filename = path.join(ANALYTIC_UNITS_PATH, `${newId}.json`);
if(fs.existsSync(filename)) {
throw new Error(`Can't create item with id ${newId}`);
}
save(newId, item);
item.id = newId;
return newId;
}
export function remove(id: AnalyticUnitId) {
let filename = path.join(ANALYTIC_UNITS_PATH, `${id}.json`);
fs.unlinkSync(filename);
}
export function save(id: AnalyticUnitId, unit: AnalyticUnit) {
let filename = path.join(ANALYTIC_UNITS_PATH, `${id}.json`);
return writeJsonDataSync(filename, unit);
}
// TODO: make async
export function findById(id: AnalyticUnitId): AnalyticUnit {
let filename = path.join(ANALYTIC_UNITS_PATH, `${id}.json`);
if(!fs.existsSync(filename)) {
throw new Error(`Can't find Analytic Unit with id ${id}`);
}
return getJsonDataSync(filename);
}
export function setStatus(predictorId: AnalyticUnitId, status: string, error?: string) {
let info = findById(predictorId);
info.status = status;
if(error !== undefined) {
info.error = error;
} else {
info.error = '';
}
save(predictorId, info);
}
export function setPredictionTime(id: AnalyticUnitId, time: number) {
let info = findById(id);
info.lastPredictionTime = time;
save(id, info);
}

98
server/src/models/analytic_unit_model.ts

@ -0,0 +1,98 @@
import { Metric } from './metric_model';
import { Collection, makeDBQ } from '../services/data_service';
let db = makeDBQ(Collection.ANALYTIC_UNITS);
export type AnalyticUnitId = string;
export class AnalyticUnit {
constructor(
public name: string,
public panelUrl: string,
public type: string,
public metric: Metric,
public id?: AnalyticUnitId,
public lastPredictionTime?: number,
public status?: string,
public error?: string,
) {
if(name === undefined) {
throw new Error(`Missing field "name"`);
}
if(panelUrl === undefined) {
throw new Error(`Missing field "panelUrl"`);
}
if(type === undefined) {
throw new Error(`Missing field "type"`);
}
if(metric === undefined) {
throw new Error(`Missing field "metric"`);
}
}
public toObject() {
return {
_id: this.id,
name: this.name,
panelUrl: this.panelUrl,
type: this.type,
metric: this.metric.toObject(),
lastPredictionTime: this.lastPredictionTime,
status: this.status,
error: this.error
};
}
static fromObject(obj: any): AnalyticUnit {
if(obj === undefined) {
throw new Error('obj is undefined');
}
return new AnalyticUnit(
obj.name,
obj.panelUrl,
obj.type,
Metric.fromObject(obj.metric),
obj.status,
obj.lastPredictionTime,
obj._id,
obj.error,
);
}
}
export async function findById(id: AnalyticUnitId): Promise<AnalyticUnit> {
return AnalyticUnit.fromObject(await db.findOne(id));
}
/**
* Creates and updates new unit.id
*
* @param unit to create
* @returns unit.id
*/
export async function create(unit: AnalyticUnit): Promise<AnalyticUnitId> {
var obj = unit.toObject();
var r = await db.insertOne(obj);
return r;
}
export async function remove(id: AnalyticUnitId): Promise<void> {
await db.removeOne(id);
return;
}
export async function update(id: AnalyticUnitId, unit: AnalyticUnit) {
return db.updateOne(id, unit);
}
export async function setStatus(id: AnalyticUnitId, status: string, error?: string) {
return db.updateOne(id, { status, error });
}
export async function setPredictionTime(id: AnalyticUnitId, lastPredictionTime: number) {
return db.updateOne(id, { lastPredictionTime });
}

32
server/src/models/metric_model.ts

@ -0,0 +1,32 @@
export class Metric {
constructor(public datasource: string, public targets: any[]) {
if(datasource === undefined) {
throw new Error('datasource is undefined');
}
if(targets === undefined) {
throw new Error('targets is undefined');
}
if(targets.length === 0) {
throw new Error('targets is empty');
}
}
public toObject() {
return {
datasource: this.datasource,
targets: this.targets
};
}
static fromObject(obj: any): Metric {
if(obj === undefined) {
throw new Error('obj is undefined');
}
return new Metric(
obj.datasource,
obj.targets
);
}
}

87
server/src/models/segment_model.ts

@ -0,0 +1,87 @@
import { AnalyticUnitId } from './analytic_unit_model';
import { Collection, makeDBQ } from '../services/data_service';
let db = makeDBQ(Collection.SEGMENTS);
export type SegmentId = string;
export class Segment {
constructor(
public analyticUnitId: AnalyticUnitId,
public from: number,
public to: number,
public labeled: boolean,
public id?: SegmentId
) {
if(analyticUnitId === undefined) {
throw new Error('AnalyticUnitId is undefined');
}
if(from === undefined) {
throw new Error('from is undefined');
}
if(isNaN(from)) {
throw new Error('from is NaN');
}
if(to === undefined) {
throw new Error('to is undefined');
}
if(isNaN(to)) {
throw new Error('to is NaN');
}
if(labeled === undefined) {
throw new Error('labeled is undefined');
}
}
public toObject() {
return {
_id: this.id,
analyticUnitId: this.analyticUnitId,
from: this.from,
to: this.to,
labeled: this.labeled
};
}
static fromObject(obj: any): Segment {
if(obj === undefined) {
throw new Error('obj is undefined');
}
return new Segment(
obj.analyticUnitId,
+obj.from, +obj.to,
obj.labeled, obj._id
);
}
}
export type FindManyQuery = {
timeFromGTE?: number,
timeToLTE?: number,
intexGT?: number
}
export async function findMany(id: AnalyticUnitId, query: FindManyQuery): Promise<Segment[]> {
var dbQuery: any = { analyticUnitId: id };
if(query.timeFromGTE !== undefined) {
dbQuery.from = { $gte: query.timeFromGTE };
}
if(query.timeToLTE !== undefined) {
dbQuery.to = { $lte: query.timeToLTE };
}
let segs = await db.findMany(dbQuery);
if(segs === null) {
return [];
}
return segs.map(Segment.fromObject);
}
export async function insertSegments(segments: Segment[]) {
return db.insertMany(segments.map(s => s.toObject()));
}
export function removeSegments(idsToRemove: SegmentId[]) {
return db.removeMany(idsToRemove);
}

32
server/src/models/task_model.ts

@ -0,0 +1,32 @@
import { AnalyticUnitId } from "./analytic_unit_model";
export type TaskId = string;
export class Task {
constructor(
public analyticUnitId: AnalyticUnitId,
public id?: TaskId
) {
if(analyticUnitId === undefined) {
throw new Error('analyticUnitId is undefined');
}
}
public toObject() {
return {
_id: this.id,
analyticUnitId: this.analyticUnitId
};
}
static fromObject(obj: any): Task {
if(obj === undefined) {
throw new Error('obj is undefined');
}
return new Task(
obj.analyticUnitId,
obj._id,
);
}
}

94
server/src/routes/analytic_units_router.ts

@ -1,32 +1,22 @@
import * as Router from 'koa-router'; import * as Router from 'koa-router';
import * as AnalyticUnit from '../models/analytic_unit'; import * as AnalyticUnit from '../models/analytic_unit_model';
import { runLearning } from '../controllers/analytics_controller' import { createAnalyticUnitFromObject } from '../controllers/analytics_controller'
import { saveTargets } from '../controllers/metrics_controler';
async function sendStatus(ctx: Router.IRouterContext) {
try {
let id = ctx.request.query.id;
if(id === undefined) {
throw new Error('Id is undefined');
}
let unit = AnalyticUnit.findById(id);
if(unit.status === undefined) { async function getStatus(ctx: Router.IRouterContext) {
throw new Error('status is undefined'); try {
} ctx.response.body = { status: 'READY', errorMessage: undefined };
ctx.response.body = { status: unit.status, errorMessage: unit.error };
} catch(e) { } catch(e) {
console.error(e); console.error(e);
// TODO: better send 404 when we know than isn`t found // TODO: better send 404 when we know than isn`t found
ctx.response.status = 500; ctx.response.status = 500;
ctx.response.body = { error: 'Can`t return anything' }; ctx.response.body = { error: 'Can`t return anything' };
} }
} }
async function findItem(ctx: Router.IRouterContext) { async function getUnit(ctx: Router.IRouterContext) {
try { try {
let id = ctx.request.query.id; let id = ctx.request.query.id;
@ -34,7 +24,7 @@ async function findItem(ctx: Router.IRouterContext) {
throw new Error('No id param in query'); throw new Error('No id param in query');
} }
let unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.findById(id); let unit: AnalyticUnit.AnalyticUnit = await AnalyticUnit.findById(id);
ctx.response.body = { ctx.response.body = {
name: unit.name, name: unit.name,
@ -50,58 +40,10 @@ async function findItem(ctx: Router.IRouterContext) {
} }
} }
async function createItem(ctx: Router.IRouterContext) { async function createUnit(ctx: Router.IRouterContext) {
try { try {
let newId = await createAnalyticUnitFromObject(ctx.request.body);
let body = ctx.request.body;
if(body.type === undefined) {
throw new Error(`Missing field "type"`);
}
if(body.name === undefined) {
throw new Error(`Missing field "name"`);
}
if(body.panelUrl === undefined) {
throw new Error(`Missing field "panelUrl"`);
}
if(body.metric === undefined) {
throw new Error(`Missing field "datasource"`);
}
if(body.metric.datasource === undefined) {
throw new Error(`Missing field "metric.datasource"`);
}
if(body.metric.targets === undefined) {
throw new Error(`Missing field "metric.targets"`);
}
const metric: AnalyticUnit.Metric = {
datasource: body.metric.datasource,
targets: saveTargets(body.metric.targets)
};
const unit: AnalyticUnit.AnalyticUnit = {
name: body.name,
panelUrl: body.panelUrl,
type: body.type,
datasource: body.datasource,
metric: metric,
status: 'learning',
lastPredictionTime: 0,
nextId: 0
};
let newId = AnalyticUnit.createItem(unit);
if(newId === null) {
ctx.response.status = 403;
ctx.response.body = {
code: 403,
message: 'Item exists'
};
}
ctx.response.body = { id: newId }; ctx.response.body = { id: newId };
runLearning(newId);
} catch(e) { } catch(e) {
ctx.response.status = 500; ctx.response.status = 500;
ctx.response.body = { ctx.response.body = {
@ -109,16 +51,12 @@ async function createItem(ctx: Router.IRouterContext) {
message: `Creation error: ${e.message}` message: `Creation error: ${e.message}`
}; };
} }
} }
function deleteItem(ctx: Router.IRouterContext) { async function deleteUnit(ctx: Router.IRouterContext) {
try { try {
let id = ctx.request.query.id; await AnalyticUnit.remove(ctx.request.query.id);
if(id !== undefined) {
AnalyticUnit.remove(id);
}
ctx.response.body = { ctx.response.body = {
code: 200, code: 200,
message: 'Success' message: 'Success'
@ -135,7 +73,7 @@ function deleteItem(ctx: Router.IRouterContext) {
export var router = new Router(); export var router = new Router();
router.get('/status', sendStatus); router.get('/', getUnit);
router.get('/', findItem); router.get('/status', getStatus);
router.post('/', createItem); router.post('/', createUnit);
router.delete('/', deleteItem); router.delete('/', deleteUnit);

64
server/src/routes/segments_router.ts

@ -1,49 +1,53 @@
import * as Router from 'koa-router'; import * as Router from 'koa-router';
import { AnalyticUnitId } from '../models/analytic_unit'; import { AnalyticUnitId } from '../models/analytic_unit_model';
import { import * as SegmentModel from '../models/segment_model';
getLabeledSegments,
insertSegments,
removeSegments,
} from '../controllers/segments_controller';
import { runLearning } from '../controllers/analytics_controller'; import { runLearning } from '../controllers/analytics_controller';
async function sendSegments(ctx: Router.IRouterContext) { async function getSegments(ctx: Router.IRouterContext) {
let id: AnalyticUnitId = ctx.request.query.id; let id: AnalyticUnitId = ctx.request.query.id;
if(id === undefined || id === '') {
let lastSegmentId = ctx.request.query.lastSegmentId; throw new Error('analyticUnitId (id) is missing');
let timeFrom = ctx.request.query.from;
let timeTo = ctx.request.query.to;
let segments = getLabeledSegments(id);
// Id filtering
if(lastSegmentId !== undefined) {
segments = segments.filter(el => el.id > lastSegmentId);
} }
let query: SegmentModel.FindManyQuery = {};
// Time filtering if(!isNaN(+ctx.request.query.lastSegmentId)) {
if(timeFrom !== undefined) { query.intexGT = +ctx.request.query.lastSegmentId;
segments = segments.filter(el => el.finish > timeFrom);
} }
if(!isNaN(+ctx.request.query.from)) {
if(timeTo !== undefined) { query.timeFromGTE = +ctx.request.query.from;
segments = segments.filter(el => el.start < timeTo); }
if(!isNaN(+ctx.request.query.to)) {
query.timeToLTE = +ctx.request.query.to;
} }
ctx.response.body = { segments } let segments = await SegmentModel.findMany(id, query);
ctx.response.body = { segments };
} }
async function updateSegments(ctx: Router.IRouterContext) { async function updateSegments(ctx: Router.IRouterContext) {
try { try {
let segmentsUpdate = ctx.request.body;
let id = segmentsUpdate.id; let {
let addedIds = insertSegments(id, segmentsUpdate.addedSegments, true); addedSegments, id, removedSegments: removedIds
removeSegments(id, segmentsUpdate.removedSegments); } = ctx.request.body as {
ctx.response.body = { addedIds }; addedSegments: any[], id: AnalyticUnitId, removedSegments: SegmentModel.SegmentId[]
};
let segmentsToInsert: SegmentModel.Segment[] = addedSegments.map(
s => SegmentModel.Segment.fromObject({ analyticUnitId: id, labeled: true, ...s })
);
let [addedIds, removed] = await Promise.all([
SegmentModel.insertSegments(segmentsToInsert),
SegmentModel.removeSegments(removedIds)
]);
ctx.response.body = { addedIds, removed };
runLearning(id); runLearning(id);
} catch(e) { } catch(e) {
ctx.response.status = 500; ctx.response.status = 500;
@ -56,5 +60,5 @@ async function updateSegments(ctx: Router.IRouterContext) {
export const router = new Router(); export const router = new Router();
router.get('/', sendSegments); router.get('/', getSegments);
router.patch('/', updateSegments); router.patch('/', updateSegments);

464
server/src/services/analytics_service.ts

@ -1,232 +1,232 @@
import * as config from '../config'; import * as config from '../config';
const zmq = require('zeromq'); const zmq = require('zeromq');
import * as childProcess from 'child_process' import * as childProcess from 'child_process'
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
export class AnalyticsMessage { export class AnalyticsMessage {
public constructor(public method: string, public payload?: string, public requestId?: number) { public constructor(public method: string, public payload?: string, public requestId?: number) {
} }
static fromJSON(obj: any): AnalyticsMessage { static fromJSON(obj: any): AnalyticsMessage {
if(obj.method === undefined) { if(obj.method === undefined) {
throw new Error('No method in obj:' + obj); throw new Error('No method in obj:' + obj);
} }
return new AnalyticsMessage(obj.method, obj.payload, obj.requestId); return new AnalyticsMessage(obj.method, obj.payload, obj.requestId);
} }
} }
function analyticsMessageFromJson(obj: any): AnalyticsMessage { function analyticsMessageFromJson(obj: any): AnalyticsMessage {
return new AnalyticsMessage(obj); return new AnalyticsMessage(obj);
} }
export class AnalyticsService { export class AnalyticsService {
private _requester: any; private _requester: any;
private _ready: boolean = false; private _ready: boolean = false;
private _pingResponded = false; private _pingResponded = false;
private _zmqConnectionString = null; private _zmqConnectionString: string = null;
private _ipcPath = null; private _ipcPath: string = null;
private _analyticsPinger: NodeJS.Timer = null; private _analyticsPinger: NodeJS.Timer = null;
private _isClosed = false; private _isClosed = false;
constructor(private _onMessage: (message: AnalyticsMessage) => void) { constructor(private _onMessage: (message: AnalyticsMessage) => void) {
this._init(); this._init();
} }
public async sendTask(taskObj: any): Promise<void> { public async sendTask(taskObj: any): Promise<void> {
if(!this._ready) { if(!this._ready) {
return Promise.reject("Analytics is not ready"); return Promise.reject("Analytics is not ready");
} }
let message = { let message = {
method: 'TASK', method: 'TASK',
payload: taskObj payload: taskObj
} }
return this.sendMessage(message); return this.sendMessage(message);
} }
public async sendMessage(message: AnalyticsMessage): Promise<void> { public async sendMessage(message: AnalyticsMessage): Promise<void> {
let strMessage = JSON.stringify(message); let strMessage = JSON.stringify(message);
if(message.method === 'PING') { if(message.method === 'PING') {
strMessage = 'PING'; strMessage = 'PING';
} }
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
this._requester.send(strMessage, undefined, (err) => { this._requester.send(strMessage, undefined, (err: any) => {
if(err) { if(err) {
reject(err); reject(err);
} else { } else {
resolve(); resolve();
} }
}); });
}); });
} }
public close() { public close() {
this._isClosed = true; this._isClosed = true;
console.log('Terminating analytics service...'); console.log('Terminating analytics service...');
clearInterval(this._analyticsPinger); clearInterval(this._analyticsPinger);
if(this._ipcPath !== null) { if(this._ipcPath !== null) {
console.log('Remove ipc path: ' + this._ipcPath); console.log('Remove ipc path: ' + this._ipcPath);
fs.unlinkSync(this._ipcPath); fs.unlinkSync(this._ipcPath);
} }
this._requester.close(); this._requester.close();
console.log('Ok'); console.log('Ok');
} }
public get ready(): boolean { return this._ready; } public get ready(): boolean { return this._ready; }
private async _init() { private async _init() {
this._requester = zmq.socket('pair'); this._requester = zmq.socket('pair');
let productionMode = process.env.NODE_ENV !== 'development'; let productionMode = process.env.NODE_ENV !== 'development';
this._zmqConnectionString = `tcp://127.0.0.1:${config.ZMQ_DEV_PORT}`; // debug mode this._zmqConnectionString = `tcp://127.0.0.1:${config.ZMQ_DEV_PORT}`; // debug mode
if(productionMode) { if(productionMode) {
this._zmqConnectionString = config.ZMQ_CONNECTION_STRING; this._zmqConnectionString = config.ZMQ_CONNECTION_STRING;
if(this._zmqConnectionString === null) { if(this._zmqConnectionString === null) {
var createResult = await AnalyticsService.createIPCAddress(); var createResult = await AnalyticsService.createIPCAddress();
this._zmqConnectionString = createResult.address; this._zmqConnectionString = createResult.address;
this._ipcPath = createResult.file; this._ipcPath = createResult.file;
} }
} }
console.log("Binding to zmq... %s", this._zmqConnectionString); console.log("Binding to zmq... %s", this._zmqConnectionString);
this._requester.connect(this._zmqConnectionString); this._requester.connect(this._zmqConnectionString);
this._requester.on("message", this._onAnalyticsMessage.bind(this)); this._requester.on("message", this._onAnalyticsMessage.bind(this));
console.log('Ok'); console.log('Ok');
if(productionMode) { if(productionMode) {
console.log('Creating analytics process...'); console.log('Creating analytics process...');
try { try {
var cp = await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString); var cp = await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString);
} catch(error) { } catch(error) {
console.error('Can`t run analytics process: %s', error); console.error('Can`t run analytics process: %s', error);
return; return;
} }
console.log('Ok, pid: %s', cp.pid); console.log('Ok, pid: %s', cp.pid);
} }
console.log('Start analytics pinger...'); console.log('Start analytics pinger...');
this._runAlalyticsPinger(); this._runAlalyticsPinger();
console.log('Ok'); console.log('Ok');
} }
/** /**
* Spawns analytics process. Reads process stderr and fails if it isn`t empty. * Spawns analytics process. Reads process stderr and fails if it isn`t empty.
* No need to stop the process later. * No need to stop the process later.
* *
* @returns Creaded child process * @returns Creaded child process
* @throws Process start error or first exception during python start * @throws Process start error or first exception during python start
*/ */
private static async _runAnalyticsProcess(zmqConnectionString: string): Promise<childProcess.ChildProcess> { private static async _runAnalyticsProcess(zmqConnectionString: string): Promise<childProcess.ChildProcess> {
let cp: childProcess.ChildProcess; let cp: childProcess.ChildProcess;
let cpOptions = { let cpOptions = {
cwd: config.ANALYTICS_PATH, cwd: config.ANALYTICS_PATH,
env: { env: {
...process.env, ...process.env,
ZMQ_CONNECTION_STRING: zmqConnectionString ZMQ_CONNECTION_STRING: zmqConnectionString
} }
}; };
if(fs.existsSync(path.join(config.ANALYTICS_PATH, 'dist/worker/worker'))) { if(fs.existsSync(path.join(config.ANALYTICS_PATH, 'dist/worker/worker'))) {
console.log('dist/worker/worker'); console.log('dist/worker/worker');
cp = childProcess.spawn('dist/worker/worker', [], cpOptions); cp = childProcess.spawn('dist/worker/worker', [], cpOptions);
} else { } else {
console.log('python3 server.py'); console.log('python3 server.py');
// If compiled analytics script doesn't exist - fallback to regular python // If compiled analytics script doesn't exist - fallback to regular python
console.log(config.ANALYTICS_PATH); console.log(config.ANALYTICS_PATH);
cp = childProcess.spawn('python3', ['server.py'], cpOptions); cp = childProcess.spawn('python3', ['server.py'], cpOptions);
} }
if(cp.pid === undefined) { if(cp.pid === undefined) {
return new Promise<childProcess.ChildProcess>((resolve, reject) => { return new Promise<childProcess.ChildProcess>((resolve, reject) => {
cp.on('error', reject); cp.on('error', reject);
}); });
} }
return new Promise<childProcess.ChildProcess>((resolve, reject) => { return new Promise<childProcess.ChildProcess>((resolve, reject) => {
var resolved = false; var resolved = false;
cp.stdout.on('data', () => { cp.stdout.on('data', () => {
if(resolved) { if(resolved) {
return; return;
} else { } else {
resolved = true; resolved = true;
} }
resolve(cp); resolve(cp);
}); });
cp.stderr.on('data', function(data) { cp.stderr.on('data', function(data) {
if(resolved) { if(resolved) {
return; return;
} else { } else {
resolved = true; resolved = true;
} }
reject(data); reject(data);
}); });
}); });
} }
private _onAnalyticsUp() { private _onAnalyticsUp() {
console.log('Analytics is up'); console.log('Analytics is up');
} }
private async _onAnalyticsDown() { private async _onAnalyticsDown() {
console.log('Analytics is down'); console.log('Analytics is down');
if(process.env.NODE_ENV !== 'development') { if(process.env.NODE_ENV !== 'development') {
await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString); await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString);
} }
} }
private _onAnalyticsMessage(data: any, error) { private _onAnalyticsMessage(data: any) {
if(data.toString() === 'PONG') { if(data.toString() === 'PONG') {
this._pingResponded = true; this._pingResponded = true;
if(!this._ready) { if(!this._ready) {
this._ready = true; this._ready = true;
this._onAnalyticsUp(); this._onAnalyticsUp();
} }
return; return;
} }
let text = data.toString(); let text = data.toString();
let response; let response;
try { try {
response = JSON.parse(text); response = JSON.parse(text);
} catch (e) { } catch (e) {
console.error("Can`t parse response from analytics as json:"); console.error("Can`t parse response from analytics as json:");
console.error(text); console.error(text);
throw new Error('Unexpected response'); throw new Error('Unexpected response');
} }
this._onMessage(AnalyticsMessage.fromJSON(response)); this._onMessage(AnalyticsMessage.fromJSON(response));
} }
private async _runAlalyticsPinger() { private async _runAlalyticsPinger() {
this._analyticsPinger = setInterval(() => { this._analyticsPinger = setInterval(() => {
if(this._isClosed) { if(this._isClosed) {
return; return;
} }
if(!this._pingResponded && this._ready) { if(!this._pingResponded && this._ready) {
this._ready = false; this._ready = false;
this._onAnalyticsDown(); this._onAnalyticsDown();
} }
this._pingResponded = false; this._pingResponded = false;
// TODO: set life limit for this ping // TODO: set life limit for this ping
this.sendMessage({ method: 'PING' }); this.sendMessage({ method: 'PING' });
}, config.ANLYTICS_PING_INTERVAL); }, config.ANLYTICS_PING_INTERVAL);
} }
private static async createIPCAddress(): Promise<{ address: string, file: string }> { private static async createIPCAddress(): Promise<{ address: string, file: string }> {
let filename = `${process.pid}.ipc` let filename = `${process.pid}.ipc`
let p = path.join(config.ZMQ_IPC_PATH, filename); let p = path.join(config.ZMQ_IPC_PATH, filename);
fs.writeFileSync(p, ''); fs.writeFileSync(p, '');
return Promise.resolve({ address: 'ipc://' + p, file: p }); return Promise.resolve({ address: 'ipc://' + p, file: p });
} }
} }

257
server/src/services/data_service.ts

@ -1,77 +1,180 @@
import * as config from '../config'; import * as config from '../config';
import * as nedb from 'nedb'; import * as nedb from 'nedb';
import * as fs from 'fs'; import * as fs from 'fs';
export const db = {
analyticUnits: new nedb({ filename: config.ANALYTIC_UNITS_DATABASE_PATH, autoload: true }), export enum Collection { ANALYTIC_UNITS, SEGMENTS };
metrics: new nedb({ filename: config.METRICS_DATABASE_PATH, autoload: true }),
segments: new nedb({ filename: config.SEGMENTS_DATABASE_PATH, autoload: true }),
files: new nedb({ filename: config.FILES_DATABASE_PATH, autoload: true }) /**
}; * Class which helps to make queries to your collection
*
* @param { string | object } query: a key as a string or mongodb-style query
let dbUpsertFile = (query: any, updateQuery: any) => { */
return new Promise<void>((resolve, reject) => { export type DBQ = {
db.files.update(query, updateQuery, { upsert: true }, (err: Error) => { findOne: (query: string | object) => Promise<any>,
if(err) { findMany: (query: string[] | object) => Promise<any[]>,
reject(err); insertOne: (document: object) => Promise<string>,
} else { insertMany: (documents: object[]) => Promise<string[]>,
console.log('saved shit with query '); updateOne: (query: string | object, updateQuery: any) => Promise<void>,
console.log(query); removeOne: (query: string) => Promise<boolean>
console.log('saved shit with updateQuery '); removeMany: (query: string[] | object) => Promise<number>
console.log(updateQuery); }
resolve();
} export function makeDBQ(collection: Collection): DBQ {
}); return {
}); findOne: dbFindOne.bind(null, collection),
} findMany: dbFindMany.bind(null, collection),
insertOne: dbInsertOne.bind(null, collection),
let dbLoadFile = (query: any) => { insertMany: dbInsertMany.bind(null, collection),
return new Promise<any>((resolve, reject) => { updateOne: dbUpdateOne.bind(null, collection),
db.files.findOne(query, (err, doc) => { removeOne: dbRemoveOne.bind(null, collection),
if(err) { removeMany: dbRemoveMany.bind(null, collection)
reject(err); }
} else { }
console.log('got shit with query');
console.log(query); function wrapIdToQuery(query: string | object): any {
console.log('doc'); if(typeof query === 'string') {
console.log(doc); return { _id: query };
resolve(doc); }
} return query;
}); }
});
} function wrapIdsToQuery(query: string[] | object): any {
if(Array.isArray(query)) {
function maybeCreate(path: string): void { return { _id: { $in: query } };
if(fs.existsSync(path)) { }
return; return query;
} }
console.log('mkdir: ' + path);
fs.mkdirSync(path); function isEmptyArray(obj: any): boolean {
console.log('exists: ' + fs.existsSync(path)); if(!Array.isArray(obj)) {
} return false;
}
export async function saveFile(filename: string, content: string): Promise<void> { return obj.length == 0;
return dbUpsertFile({ filename } , { filename, content }); }
}
const db = new Map<Collection, nedb>();
export async function loadFile(filename: string): Promise<string> {
let doc = await dbLoadFile({ filename }); let dbInsertOne = (collection: Collection, doc: object) => {
if(doc === null) { return new Promise<string>((resolve, reject) => {
return null; db.get(collection).insert(doc, (err, newDoc: any) => {
} if(err) {
return doc.content; reject(err);
} } else {
resolve(newDoc._id);
export function checkDataFolders(): void { }
[ });
config.DATA_PATH, });
config.DATASETS_PATH, }
config.ANALYTIC_UNITS_PATH,
config.MODELS_PATH, let dbInsertMany = (collection: Collection, docs: object[]) => {
config.METRICS_PATH, if(docs.length === 0) {
config.SEGMENTS_PATH, return Promise.resolve([]);
config.ZMQ_IPC_PATH }
].forEach(maybeCreate); return new Promise<string[]>((resolve, reject) => {
} db.get(collection).insert(docs, (err, newDocs: any[]) => {
if(err) {
reject(err);
} else {
resolve(newDocs.map(d => d._id));
}
});
});
}
let dbUpdateOne = (collection: Collection, query: string | object, updateQuery: object) => {
query = wrapIdToQuery(query);
return new Promise<void>((resolve, reject) => {
db.get(collection).update(query, updateQuery, { /* options */ }, (err: Error) => {
if(err) {
reject(err);
} else {
resolve();
}
});
});
}
let dbFindOne = (collection: Collection, query: string | object) => {
query = wrapIdToQuery(query);
return new Promise<any>((resolve, reject) => {
db.get(collection).findOne(query, (err, doc) => {
if(err) {
reject(err);
} else {
resolve(doc);
}
});
});
}
let dbFindMany = (collection: Collection, query: string[] | object) => {
if(isEmptyArray(query)) {
return Promise.resolve([]);
}
query = wrapIdsToQuery(query);
return new Promise<any[]>((resolve, reject) => {
db.get(collection).find(query, (err, docs: any[]) => {
if(err) {
reject(err);
} else {
resolve(docs);
}
});
});
}
let dbRemoveOne = (collection: Collection, query: string | object) => {
query = wrapIdToQuery(query);
return new Promise<boolean>((resolve, reject) => {
db.get(collection).remove(query, { /* options */ }, (err, numRemoved) => {
if(err) {
reject(err);
} else {
if(numRemoved > 1) {
throw new Error(`Removed ${numRemoved} elements with query: ${JSON.stringify(query)}. Only one is Ok.`);
} else {
resolve(numRemoved == 1);
}
}
});
});
}
let dbRemoveMany = (collection: Collection, query: string[] | object) => {
if(isEmptyArray(query)) {
return Promise.resolve([]);
}
query = wrapIdsToQuery(query);
return new Promise<number>((resolve, reject) => {
db.get(collection).remove(query, { multi: true }, (err, numRemoved) => {
if(err) {
reject(err);
} else {
resolve(numRemoved);
}
});
});
}
function maybeCreateDir(path: string): void {
if(fs.existsSync(path)) {
return;
}
console.log('mkdir: ' + path);
fs.mkdirSync(path);
}
function checkDataFolders(): void {
[
config.DATA_PATH,
config.ZMQ_IPC_PATH
].forEach(maybeCreateDir);
}
checkDataFolders();
// TODO: it's better if models request db which we create if it`s needed
db.set(Collection.ANALYTIC_UNITS, new nedb({ filename: config.ANALYTIC_UNITS_DATABASE_PATH, autoload: true }));
db.set(Collection.SEGMENTS, new nedb({ filename: config.SEGMENTS_DATABASE_PATH, autoload: true }));

6
server/src/services/notification_service.ts

@ -1,11 +1,11 @@
import { findById } from '../models/analytic_unit'; import { findById, AnalyticUnitId } from '../models/analytic_unit_model';
import axios from 'axios'; import axios from 'axios';
// TODO: send notification with payload without dep to AnalyticUnit // TODO: send notification with payload without dep to AnalyticUnit
export async function sendNotification(predictorId, active) { export async function sendNotification(id: AnalyticUnitId, active: boolean) {
let anomalyName = findById(predictorId).name; let anomalyName = (await findById(id)).name
console.log('Notification ' + anomalyName); console.log('Notification ' + anomalyName);
let notification = { let notification = {

82
server/src/services/process_service.ts

@ -1,38 +1,44 @@
var exitHandlers = [] var exitHandlers: (() => void)[] = [];
var exitHandled = false; var exitHandled = false;
/** /**
* Add a callback for closing programm bacause of any reason * Add a callback for closing programm bacause of any reason
* *
* @param callback a sync function * @param callback a sync function
*/ */
export function registerExitHandler(callback: () => void) { export function registerExitHandler(callback: () => void) {
exitHandlers.push(callback); exitHandlers.push(callback);
} }
function exitHandler(options, err) { function exitHandler(options: any, err?: any) {
if(exitHandled) { if(exitHandled) {
return; return;
} }
exitHandled = true; exitHandled = true;
for(let i = 0; i < exitHandlers.length; i++) { for(let i = 0; i < exitHandlers.length; i++) {
exitHandlers[i](); exitHandlers[i]();
} }
console.log('process exit'); console.log('process exit');
process.exit(); process.exit();
} }
//do something when app is closing function catchException(options: any, err: any) {
process.on('exit', exitHandler.bind(null,{cleanup:true})); console.log('Server exception:');
console.log(err);
//catches ctrl+c event exitHandler({ exit: true });
process.on('SIGINT', exitHandler.bind(null, {exit:true})); }
// catches "kill pid" (for example: nodemon restart) //do something when app is closing
process.on('SIGUSR1', exitHandler.bind(null, {exit:true})); process.on('exit', exitHandler.bind(null, { cleanup:true }));
process.on('SIGUSR2', exitHandler.bind(null, {exit:true}));
//catches ctrl+c event
//catches uncaught exceptions process.on('SIGINT', exitHandler.bind(null, { exit:true }));
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, { exit:true }));
process.on('SIGUSR2', exitHandler.bind(null, { exit:true }));
//catches uncaught exceptions
process.on('uncaughtException', catchException.bind(null, { exit:true }));

4
server/tsconfig.json

@ -1,9 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"sourceMap": true, "sourceMap": true,
"noImplicitAny": false,
"module": "commonjs", "module": "commonjs",
"target": "es6", "target": "es6"
"allowJs": true
} }
} }

Loading…
Cancel
Save