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 { 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';
@ -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';
describe('getNonIntersectedSpans', function() {
let spanBorders = [3, 5, 6, 8, 10, 20];
it('functional test', function() {
expect(getNonIntersectedSpans(4, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]);
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}]);
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}]);
expect(getNonIntersectedSpans(2, 20, spanBorders)).toEqual([{from: 2, to: 3}, {from: 5, to: 6}, {from: 8, to: 10}]);
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(getNonIntersectedSpans(3, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]);
expect(getNonIntersectedSpans(3, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]);
expect(getNonIntersectedSpans(4, 7, [3, 5, 6, 8])).toEqual([{from: 5, to: 6}]);
function cutSpan(from: number, to: number, cuts: [number, number][]): [number, number][] {
return cutSpanWithSpans(
{ from: from, to: to },
cuts.map(([from, to]) => ({ from, to }))
).map(({ from, to }) => [from, to]);
}
describe('cutSpanWithSpans', function() {
it('should find spans in simple non-intersected borders', function() {
let cutSpans = [[3, 5], [6, 8], [10, 20]] as [number, number][];
expect(cutSpan(4, 11, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(cutSpan(5, 11, cutSpans)).toEqual([[5, 6], [8, 10]]);
expect(cutSpan(4, 10, cutSpans)).toEqual([[5, 6], [8, 10]]);
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() {
expect(getNonIntersectedSpans(4, 10, [])).toEqual([]);
it('should be ready to get not-sorted cuts', function() {
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() {
expect(getNonIntersectedSpans(4, 10, [1, 20])).toEqual([]);
expect(getNonIntersectedSpans(4, 10, [1, 10])).toEqual([]);
expect(getNonIntersectedSpans(4, 10, [4, 20])).toEqual([]);
expect(getNonIntersectedSpans(4, 10, [4, 10])).toEqual([]);
it('should be ready to get overlayed cuts', function() {
expect(cutSpan(0, 20, [[3, 5], [4, 10]])).toEqual([[0,3], [10, 20]]);
});
});

6
server/src/controllers/analytics_controller.ts

@ -10,7 +10,7 @@ import { AlertService } from '../services/alert_service';
import { HASTIC_API_KEY } from '../config';
import { DataPuller } from '../services/data_puller';
import { getGrafanaUrl } from '../utils/grafana';
import { getNonIntersectedSpans } from '../utils/spans';
import { cutSpanWithSpans } from '../utils/spans';
import { queryByMetric, GrafanaUnavailable, DatasourceUnavailable } from 'grafana-datasource-kit';
@ -564,9 +564,7 @@ export async function getDetectionSpans(
}
}
const spanBorders = Detection.getSpanBorders(readySpans);
let newDetectionSpans = getNonIntersectedSpans(from, to, spanBorders);
let newDetectionSpans = cutSpanWithSpans({ from, to }, readySpans);
if(newDetectionSpans.length === 0) {
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 * as _ from 'lodash';
import { getNonIntersectedSpans } from '../utils/spans';
let db = makeDBQ(Collection.DETECTION_SPANS);
@ -137,22 +136,6 @@ export async function insertSpan(span: DetectionSpan) {
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) {
return db.removeMany({ analyticUnitId });
}

87
server/src/utils/spans.ts

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

Loading…
Cancel
Save