diff --git a/src/panel/graph_panel/controllers/analytic_controller.ts b/src/panel/graph_panel/controllers/analytic_controller.ts index 4da6805..e27ffc4 100644 --- a/src/panel/graph_panel/controllers/analytic_controller.ts +++ b/src/panel/graph_panel/controllers/analytic_controller.ts @@ -5,8 +5,10 @@ import { AnalyticService } from '../services/analytic_service'; import { AnalyticUnitId, AnalyticUnit, AnalyticSegment, AnalyticSegmentsSearcher, AnalyticSegmentPair, - LabelingMode + LabelingMode, + DetectorType } from '../models/analytic_units/analytic_unit'; +import { AnomalyAnalyticUnit } from '../models/analytic_units/anomaly_analytic_unit'; import { AnalyticUnitsSet } from '../models/analytic_units/analytic_units_set'; import { MetricExpanded } from '../models/metric'; import { DatasourceRequest } from '../models/datasource'; @@ -325,7 +327,6 @@ export class AnalyticController { analyticUnit.segments.clear(); analyticUnit.detectionSpans = []; analyticUnit.status = null; - await this.saveAnalyticUnit(analyticUnit); await this._analyticService.runDetect(analyticUnitId, from, to); this._runStatusWaiter(analyticUnit); } @@ -503,13 +504,13 @@ export class AnalyticController { } async getHSR(from: number, to: number): Promise { - // Returns HSR (Hastic Signal Representation) for inspected analytic unit - // Returns null when there is no analytic units in Inspect mode - if(this.inspectedAnalyticUnit === null) { + // Returns HSR (Hastic Signal Representation) for analytic unit with enabled "Show HSR" + // Returns null when there is no analytic units which have "Show HSR" enabled + if(this.hsrAnalyticUnit === null) { return null; } - const hsr = await this._analyticService.getHSR(this.inspectedAnalyticUnit.id, from, to); + const hsr = await this._analyticService.getHSR(this.hsrAnalyticUnit.id, from, to); const datapoints = hsr.values.map(value => value.reverse() as [number, number]); return { target: 'HSR', datapoints }; } @@ -520,14 +521,35 @@ export class AnalyticController { if(hsr === null) { return []; } + if(this.hsrAnalyticUnit.detectorType === DetectorType.ANOMALY) { + const confidence = (this.hsrAnalyticUnit as AnomalyAnalyticUnit).confidence; + // TODO: looks bad + return [ + { + target: 'Confidence interval lower', + datapoints: hsr.datapoints.map(datapoint => + [datapoint[0] - confidence, datapoint[1]] + ), + color: ANALYTIC_UNIT_COLORS[0], + overrides: [{ alias: 'Confidence interval lower', linewidth: 1, fill: 0 }] + }, + { + target: 'Confidence interval upper', + datapoints: hsr.datapoints.map(datapoint => + [datapoint[0] + confidence, datapoint[1]] + ), + color: ANALYTIC_UNIT_COLORS[0], + overrides: [{ alias: 'Confidence interval upper', linewidth: 1, fill: 0 }] + }, + ]; + } return { ...hsr, color: ANALYTIC_UNIT_COLORS[0], - // TODO: render it separately from series - overrides: [{ - alias: 'HSR', - linewidth: 3 - }] + // TODO: render it separately from Metric series + overrides: [ + { alias: 'HSR', linewidth: 3, fill: 0 } + ] }; } @@ -540,6 +562,16 @@ export class AnalyticController { return null; } + get hsrAnalyticUnit(): AnalyticUnit | null { + // TODO: remove inspectedAnalyticUnit duplication + for(let analyticUnit of this.analyticUnits) { + if(analyticUnit.showHSR) { + return analyticUnit; + } + }; + return null; + } + public get conditions() { return _.values(Condition); } @@ -649,12 +681,19 @@ export class AnalyticController { await this.saveAnalyticUnit(analyticUnit); } - public async toggleInspect(id: AnalyticUnitId) { + public toggleInspect(id: AnalyticUnitId) { const analyticUnit = this._analyticUnitsSet.byId(id); if(!analyticUnit.inspect) { - this.analyticUnits.forEach(analyticUnit => analyticUnit.inspect = false); + this.analyticUnits.forEach(unit => unit.inspect = false); + } + } + + public toggleHSR(id: AnalyticUnitId) { + // TODO: remove toggleInspect duplication + const analyticUnit = this._analyticUnitsSet.byId(id); + if(!analyticUnit.showHSR) { + this.analyticUnits.forEach(unit => unit.showHSR = false); } - analyticUnit.inspect = !analyticUnit.inspect; } public onAnalyticUnitDetectorChange(analyticUnitTypes: any) { diff --git a/src/panel/graph_panel/graph_ctrl.ts b/src/panel/graph_panel/graph_ctrl.ts index 246fdde..4c08c41 100644 --- a/src/panel/graph_panel/graph_ctrl.ts +++ b/src/panel/graph_panel/graph_ctrl.ts @@ -384,14 +384,14 @@ class GraphCtrl extends MetricsPanelCtrl { this.dataWarning = null; const hasSomePoint = this.seriesList.some(s => s.datapoints.length > 0); - if (!hasSomePoint) { + if(!hasSomePoint) { this.dataWarning = { title: 'No data points', tip: 'No datapoints returned from data query', }; } else { - for (let series of this.seriesList) { - if (series.isOutsideRange) { + for(let series of this.seriesList) { + if(series.isOutsideRange) { this.dataWarning = { title: 'Data points outside time range', tip: 'Can be caused by timezone mismatch or missing time filter in query', @@ -683,6 +683,11 @@ class GraphCtrl extends MetricsPanelCtrl { this.refresh(); } + onToggleHSR(id: AnalyticUnitId) { + this.analyticsController.toggleHSR(id); + this.refresh(); + } + private async _updatePanelInfo() { let datasource = undefined; if(this.panel.datasource) { diff --git a/src/panel/graph_panel/models/analytic_units/analytic_unit.ts b/src/panel/graph_panel/models/analytic_units/analytic_unit.ts index 6d36f40..a395706 100644 --- a/src/panel/graph_panel/models/analytic_units/analytic_unit.ts +++ b/src/panel/graph_panel/models/analytic_units/analytic_unit.ts @@ -11,7 +11,8 @@ import _ from 'lodash'; // TODO: move types to ./types export enum DetectorType { PATTERN = 'pattern', - THRESHOLD = 'threshold' + THRESHOLD = 'threshold', + ANOMALY = 'anomaly' }; export enum LabelingMode { @@ -54,6 +55,7 @@ export class AnalyticUnit { private _segmentSet = new SegmentArray(); private _detectionSpans: DetectionSpan[]; private _inspect = false; + private _showHSR = false; private _status: string; private _error: string; @@ -114,6 +116,9 @@ export class AnalyticUnit { get inspect(): boolean { return this._inspect; } set inspect(value: boolean) { this._inspect = value; } + get showHSR(): boolean { return this._showHSR; } + set showHSR(value: boolean) { this._showHSR = value; } + get visible(): boolean { return (this._serverObject.visible === undefined) ? true : this._serverObject.visible } diff --git a/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts b/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts new file mode 100644 index 0000000..2d185d6 --- /dev/null +++ b/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts @@ -0,0 +1,33 @@ +import { AnalyticUnit, DetectorType } from './analytic_unit'; + +import _ from 'lodash'; + +const DEFAULTS = { + detectorType: DetectorType.ANOMALY, + type: 'ANOMALY', + alpha: 0.5, + confidence: 1 +}; + +export class AnomalyAnalyticUnit extends AnalyticUnit { + + constructor(_serverObject?: any) { + super(_serverObject); + _.defaults(this._serverObject, DEFAULTS); + } + + toJSON() { + const baseJSON = super.toJSON(); + return { + ...baseJSON, + alpha: this.alpha, + confidence: this.confidence + }; + } + + set alpha(val: number) { this._serverObject.alpha = val; } + get alpha(): number { return this._serverObject.alpha; } + + set confidence(val: number) { this._serverObject.confidence = val; } + get confidence(): number { return this._serverObject.confidence; } +} diff --git a/src/panel/graph_panel/models/analytic_units/utils.ts b/src/panel/graph_panel/models/analytic_units/utils.ts index 512bf5e..02ae5a1 100644 --- a/src/panel/graph_panel/models/analytic_units/utils.ts +++ b/src/panel/graph_panel/models/analytic_units/utils.ts @@ -1,6 +1,7 @@ import { AnalyticUnit, DetectorType } from './analytic_unit'; import { PatternAnalyticUnit } from './pattern_analytic_unit'; import { ThresholdAnalyticUnit } from './threshold_analytic_unit'; +import { AnomalyAnalyticUnit } from './anomaly_analytic_unit'; export function createAnalyticUnit(serverObject: any): AnalyticUnit { const detectorType: DetectorType = serverObject.detectorType; @@ -9,6 +10,8 @@ export function createAnalyticUnit(serverObject: any): AnalyticUnit { return new PatternAnalyticUnit(serverObject); case DetectorType.THRESHOLD: return new ThresholdAnalyticUnit(serverObject); + case DetectorType.ANOMALY: + return new AnomalyAnalyticUnit(serverObject); default: throw new Error(`Can't create analytic unit with type "${detectorType}"`); } diff --git a/src/panel/graph_panel/partials/tab_analytics.html b/src/panel/graph_panel/partials/tab_analytics.html index b7e9da1..97c08f6 100644 --- a/src/panel/graph_panel/partials/tab_analytics.html +++ b/src/panel/graph_panel/partials/tab_analytics.html @@ -70,36 +70,12 @@ /> - - - + - + + + + + + + + + + + + + - - Inspect + + + Detect + saving... - + + - Disable Inspect + + + saving... +