Browse Source

GetNonIntersectedSpans refactoring (#751)

pull/1/head
Coin de Gamma 5 years ago committed by rozetko
parent
commit
e6fd479655
  1. 8
      server/spec/models/detection_model.jest.ts
  2. 66
      server/spec/utils/spans.jest.ts
  3. 6
      server/src/controllers/analytics_controller.ts
  4. 17
      server/src/models/detection_model.ts
  5. 87
      server/src/utils/spans.ts

8
server/spec/models/detection_model.jest.ts

@ -1,5 +1,5 @@
import { TEST_ANALYTIC_UNIT_ID } from '../utils_for_tests/analytic_units'; import { TEST_ANALYTIC_UNIT_ID } from '../utils_for_tests/analytic_units';
import { buildSpans, insertSpans, clearSpansDB, convertSpansToOptions } from '../utils_for_tests/detection_spans'; import { insertSpans, clearSpansDB, convertSpansToOptions } from '../utils_for_tests/detection_spans';
import * as Detection from '../../src/models/detection_model'; import * as Detection from '../../src/models/detection_model';
@ -67,9 +67,3 @@ describe('getIntersectedSpans', () => {
}); });
}); });
describe('getSpanBorders', () => {
it('should sort and find span borders', () => {
const borders = Detection.getSpanBorders(buildSpans(INITIAL_SPANS_CONFIGS));
expect(borders).toEqual([1, 3, 3, 4]);
});
});

66
server/spec/utils/spans.jest.ts

@ -1,35 +1,51 @@
import { getNonIntersectedSpans } from '../../src/utils/spans'; import { cutSpanWithSpans } from '../../src/utils/spans';
import 'jest'; import 'jest';
describe('getNonIntersectedSpans', function() {
function cutSpan(from: number, to: number, cuts: [number, number][]): [number, number][] {
let spanBorders = [3, 5, 6, 8, 10, 20]; return cutSpanWithSpans(
{ from: from, to: to },
it('functional test', function() { cuts.map(([from, to]) => ({ from, to }))
expect(getNonIntersectedSpans(4, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); ).map(({ from, to }) => [from, to]);
expect(getNonIntersectedSpans(5, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); }
expect(getNonIntersectedSpans(4, 10, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]);
expect(getNonIntersectedSpans(5, 10, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); describe('cutSpanWithSpans', function() {
expect(getNonIntersectedSpans(4, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]);
expect(getNonIntersectedSpans(4, 21, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}, {from: 20, to: 21}]); it('should find spans in simple non-intersected borders', function() {
expect(getNonIntersectedSpans(2, 20, spanBorders)).toEqual([{from: 2, to: 3}, {from: 5, to: 6}, {from: 8, to: 10}]); let cutSpans = [[3, 5], [6, 8], [10, 20]] as [number, number][];
expect(getNonIntersectedSpans(2, 21, spanBorders)).toEqual([{from: 2, to: 3}, {from: 5, to: 6}, {from: 8, to: 10}, {from: 20, to: 21}]);
expect(getNonIntersectedSpans(3, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); expect(cutSpan(4, 11, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(getNonIntersectedSpans(3, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); expect(cutSpan(5, 11, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(getNonIntersectedSpans(3, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); expect(cutSpan(4, 10, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(getNonIntersectedSpans(4, 7, [3, 5, 6, 8])).toEqual([{from: 5, to: 6}]); expect(cutSpan(5, 10, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(cutSpan(4, 20, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(cutSpan(4, 21, cutSpans)).toEqual([[5, 6], [8, 10], [20, 21]]);
expect(cutSpan(2, 20, cutSpans)).toEqual([[2, 3], [5, 6], [8, 10]]);
expect(cutSpan(2, 21, cutSpans)).toEqual([[2, 3], [5, 6], [8, 10], [20, 21]]);
expect(cutSpan(3, 11, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(cutSpan(3, 20, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(cutSpan(4, 7, [[3, 5], [6, 8]])).toEqual([[5, 6]]);
});
it('should handle empty input spans list case', function() {
expect(cutSpan(4, 10, [])).toEqual([[4, 10]]);
});
it('should handle case when from and to are inside of one big span', function() {
expect(cutSpan(4, 10, [[1, 20]])).toEqual([]);
expect(cutSpan(4, 10, [[1, 10]])).toEqual([]);
expect(cutSpan(4, 10, [[4, 20]])).toEqual([]);
expect(cutSpan(4, 10, [[4, 10]])).toEqual([]);
}); });
it('empty borders list', function() { it('should be ready to get not-sorted cuts', function() {
expect(getNonIntersectedSpans(4, 10, [])).toEqual([]); expect(cutSpan(0, 20, [[3, 5], [1, 2]])).toEqual([[0, 1], [2, 3], [5, 20]]);
expect(cutSpan(0, 20, [[3, 5], [1, 2], [0.1, 0.5]])).toEqual([[0, 0.1], [0.5, 1], [2, 3], [5, 20]]);
}); });
it('all in span', function() { it('should be ready to get overlayed cuts', function() {
expect(getNonIntersectedSpans(4, 10, [1, 20])).toEqual([]); expect(cutSpan(0, 20, [[3, 5], [4, 10]])).toEqual([[0,3], [10, 20]]);
expect(getNonIntersectedSpans(4, 10, [1, 10])).toEqual([]);
expect(getNonIntersectedSpans(4, 10, [4, 20])).toEqual([]);
expect(getNonIntersectedSpans(4, 10, [4, 10])).toEqual([]);
}); });
}); });

6
server/src/controllers/analytics_controller.ts

@ -10,7 +10,7 @@ import { AlertService } from '../services/alert_service';
import { HASTIC_API_KEY } from '../config'; import { HASTIC_API_KEY } from '../config';
import { DataPuller } from '../services/data_puller'; import { DataPuller } from '../services/data_puller';
import { getGrafanaUrl } from '../utils/grafana'; import { getGrafanaUrl } from '../utils/grafana';
import { getNonIntersectedSpans } from '../utils/spans'; import { cutSpanWithSpans } from '../utils/spans';
import { queryByMetric, GrafanaUnavailable, DatasourceUnavailable } from 'grafana-datasource-kit'; import { queryByMetric, GrafanaUnavailable, DatasourceUnavailable } from 'grafana-datasource-kit';
@ -564,9 +564,7 @@ export async function getDetectionSpans(
} }
} }
const spanBorders = Detection.getSpanBorders(readySpans); let newDetectionSpans = cutSpanWithSpans({ from, to }, readySpans);
let newDetectionSpans = getNonIntersectedSpans(from, to, spanBorders);
if(newDetectionSpans.length === 0) { if(newDetectionSpans.length === 0) {
return [ new Detection.DetectionSpan(analyticUnitId, from, to, Detection.DetectionStatus.READY) ]; return [ new Detection.DetectionSpan(analyticUnitId, from, to, Detection.DetectionStatus.READY) ];
} }

17
server/src/models/detection_model.ts

@ -2,7 +2,6 @@ import { AnalyticUnitId } from './analytic_units';
import { Collection, makeDBQ } from '../services/data_service'; import { Collection, makeDBQ } from '../services/data_service';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { getNonIntersectedSpans } from '../utils/spans';
let db = makeDBQ(Collection.DETECTION_SPANS); let db = makeDBQ(Collection.DETECTION_SPANS);
@ -137,22 +136,6 @@ export async function insertSpan(span: DetectionSpan) {
return db.insertOne(spanToInsert); return db.insertOne(spanToInsert);
} }
/**
* Sorts spans by `from` field and @returns an array of their borders
*/
// TODO: remove after getNonIntersectedSpans refactoring
export function getSpanBorders(spans: DetectionSpan[]): number[] {
let spanBorders: number[] = [];
_.sortBy(spans.map(span => span.toObject()), 'from')
.forEach(span => {
spanBorders.push(span.from);
spanBorders.push(span.to);
});
return spanBorders;
}
export function clearSpans(analyticUnitId: AnalyticUnitId) { export function clearSpans(analyticUnitId: AnalyticUnitId) {
return db.removeMany({ analyticUnitId }); return db.removeMany({ analyticUnitId });
} }

87
server/src/utils/spans.ts

@ -2,53 +2,62 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
export declare type Span = { export declare type Span = {
from: number, from: number,
to: number to: number
} }
// TODO: move from utils and refactor // TODO: move from utils and use generator
export function getNonIntersectedSpans(from: number, to: number, spanBorders: number[]): Span[] { /**
// spanBorders array must be sorted ascending *
let isFromProcessed = false; * @param inputSpan a big span which we will cut
let alreadyDetected = false; * @param cutSpans spans which to cut the inputSpan. Spans can overlay.
let startDetectionRange = null; *
let result: Span[] = []; * @returns array of spans which are holes
*/
for(var border of spanBorders) { export function cutSpanWithSpans(inputSpan: Span, cutSpans: Span[]): Span[] {
if(!isFromProcessed && border >= from) { if(cutSpans.length === 0) {
isFromProcessed = true; return [inputSpan];
if(border === from) { }
if(alreadyDetected) {
startDetectionRange = from;
}
} else {
if(!alreadyDetected) {
startDetectionRange = from;
}
}
}
if(border >= to) { // we sort and merge out cuts to normalize it
if(!alreadyDetected) { cutSpans = _.sortBy(cutSpans, s => s.from);
result.push({ from: startDetectionRange, to }); var mergedSortedCuts =_.reduce(cutSpans,
((acc: Span[], s: Span) => {
if(acc.length === 0) return [s];
let last = acc[acc.length - 1];
if(s.to <= last.to) return acc;
if(s.from <= last.to) {
last.to = s.to;
return acc;
} }
break; acc.push(s);
return acc;
}), []
);
// this is what we get if we cut `mergedSortedCuts` from (-Infinity, Infinity)
var holes = mergedSortedCuts.map((cut, i) => {
let from = -Infinity;
let to = cutSpans[0].from;
if(i > 0) {
from = mergedSortedCuts[i - 1].to;
to = cut.from;
} }
return { from, to }
if(alreadyDetected) { //end of already detected region, start point for new detection }).concat({
startDetectionRange = border; from: mergedSortedCuts[mergedSortedCuts.length - 1].to,
} else { //end of new detection region to: Infinity
if(startDetectionRange !== null) { });
result.push({ from: startDetectionRange, to: border});
} return _(holes).map(c => {
if(c.to <= inputSpan.from) return undefined;
if(inputSpan.to <= c.from) return undefined;
return {
from: Math.max(c.from, inputSpan.from),
to: Math.min(c.to, inputSpan.to),
} }
alreadyDetected = !alreadyDetected; }).compact().value();
}
if(border < to) {
result.push({ from: startDetectionRange, to });
}
return result;
} }

Loading…
Cancel
Save