Browse Source

UI for anomaly detector configuration #270 && Optional HSR #273 (#278)

* Introduce anomaly analytic unit

* todo

* Render anomaly analytic unit config

* Optional HSR #273

* minor fix for #273

* fix codestyle

* minor ui fix

* Improve ui

* render confidence interval

* Update src/panel/graph_panel/controllers/analytic_controller.ts

* use from and to in runDetect
master
rozetko 5 years ago committed by Alexey Velikiy
parent
commit
b0cb7d6907
  1. 67
      src/panel/graph_panel/controllers/analytic_controller.ts
  2. 11
      src/panel/graph_panel/graph_ctrl.ts
  3. 7
      src/panel/graph_panel/models/analytic_units/analytic_unit.ts
  4. 33
      src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts
  5. 3
      src/panel/graph_panel/models/analytic_units/utils.ts
  6. 118
      src/panel/graph_panel/partials/tab_analytics.html

67
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<HSRTimeSeries | null> {
// 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) {

11
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) {

7
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<AnalyticSegment>();
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
}

33
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; }
}

3
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}"`);
}

118
src/panel/graph_panel/partials/tab_analytics.html

@ -70,36 +70,12 @@
/>
</span>
<label
class="gf-form-label"
ng-if="analyticUnit.detectorType === 'pattern'"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
>
<a class="pointer" tabindex="1"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
<i class="fa fa-bar-chart" ng-if="!analyticUnit.saving"></i>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
</label>
<select class="gf-form-input width-12"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in [
{ name:'Label Positive', value: 'LABELING' },
{ name:'Label Negative', value: 'DELETING' },
{ name:'Unlabel', value: 'UNLABELING' }
]"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
<select class="gf-form-input width-7"
<!-- TODO: move analytic-unit-specific fields rendering to class -->
<select class="gf-form-input width-9"
ng-if="analyticUnit.detectorType === 'threshold'"
ng-model="analyticUnit.condition"
ng-options="type for type in ctrl.analyticsController.conditions"
ng-change="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
<input class="gf-form-input width-5"
ng-if="
@ -108,16 +84,45 @@
"
type="number"
ng-model="analyticUnit.value"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
<button
class="btn btn-inverse"
ng-if="analyticUnit.detectorType === 'threshold'"
ng-click="ctrl.runDetectInCurrentRange(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'PENDING' || analyticUnit.status === 'LEARNING'"
>
Apply
</button>
<label class="gf-form-label width-6" ng-if="analyticUnit.detectorType === 'anomaly'"> Alpha </label>
<input class="gf-form-input width-5"
ng-if="analyticUnit.detectorType === 'anomaly'"
min="0"
max="1"
type="number"
ng-model="analyticUnit.alpha"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
<label class="gf-form-label width-6" ng-if="analyticUnit.detectorType === 'anomaly'"> Confidence </label>
<input class="gf-form-input width-5"
ng-if="analyticUnit.detectorType === 'anomaly'"
min="0"
type="number"
ng-model="analyticUnit.confidence"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
<label class="gf-form-label" ng-if="analyticUnit.status === 'READY' && analyticUnit.visible">
Inspect
</label>
<gf-form-switch ng-if="analyticUnit.status === 'READY' && analyticUnit.visible"
on-change="ctrl.onToggleInspect(analyticUnit.id)"
checked="analyticUnit.inspect"
/>
<label class="gf-form-label" ng-if="analyticUnit.visible">
HSR
</label>
<gf-form-switch ng-if="analyticUnit.visible"
on-change="ctrl.onToggleHSR(analyticUnit.id)"
checked="analyticUnit.showHSR"
/>
<label class="gf-form-label" ng-hide="analyticUnit.selected">
<a
@ -139,23 +144,42 @@
</a>
</label>
<label class="gf-form-label" ng-if="analyticUnit.status === 'READY' && analyticUnit.visible">
<a
class="pointer"
ng-click="ctrl.onToggleInspect(analyticUnit.id)"
ng-if="!analyticUnit.inspect"
>
Inspect
<label class="gf-form-label"
ng-if="analyticUnit.detectorType === 'threshold' || analyticUnit.detectorType === 'anomaly'"
>
<a class="pointer" ng-click="ctrl.runDetect(analyticUnit.id)">
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="!analyticUnit.saving"> Detect </b>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
<a
class="pointer"
ng-click="ctrl.onToggleInspect(analyticUnit.id)"
ng-if="analyticUnit.inspect"
</label>
<label
class="gf-form-label"
ng-if="analyticUnit.detectorType === 'pattern'"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
>
<a class="pointer" tabindex="1"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
Disable Inspect
<i class="fa fa-bar-chart" ng-if="!analyticUnit.saving"></i>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
</label>
<select class="gf-form-input width-12"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in [
{ name:'Label Positive', value: 'LABELING' },
{ name:'Label Negative', value: 'DELETING' },
{ name:'Unlabel', value: 'UNLABELING' }
]"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
<label class="gf-form-label">
<a
ng-if="!analyticUnit.selected"

Loading…
Cancel
Save