Browse Source

Threshold is not a part of analytic unit #274 (#276)

master
rozetko 5 years ago committed by GitHub
parent
commit
aec2450e63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 89
      src/panel/graph_panel/controllers/analytic_controller.ts
  2. 11
      src/panel/graph_panel/graph_ctrl.ts
  3. 2
      src/panel/graph_panel/graph_renderer.ts
  4. 2
      src/panel/graph_panel/graph_tooltip.ts
  5. 100
      src/panel/graph_panel/models/analytic_units/analytic_unit.ts
  6. 47
      src/panel/graph_panel/models/analytic_units/analytic_units_set.ts
  7. 23
      src/panel/graph_panel/models/analytic_units/pattern_analytic_unit.ts
  8. 43
      src/panel/graph_panel/models/analytic_units/threshold_analytic_unit.ts
  9. 15
      src/panel/graph_panel/models/analytic_units/utils.ts
  10. 2
      src/panel/graph_panel/models/detection.ts
  11. 16
      src/panel/graph_panel/models/threshold.ts
  12. 21
      src/panel/graph_panel/partials/tab_analytics.html
  13. 27
      src/panel/graph_panel/services/analytic_service.ts
  14. 2
      tests/setup_tests.ts

89
src/panel/graph_panel/controllers/analytic_controller.ts

@ -1,21 +1,23 @@
// Corresponds to https://github.com/hastic/hastic-server/blob/master/server/src/models/analytic_unit.ts
// Corresponds to https://github.com/hastic/hastic-server/blob/master/server/src/models/analytic_units/analytic_unit.ts
import { AnalyticService } from '../services/analytic_service'
import { AnalyticService } from '../services/analytic_service';
import {
AnalyticUnitId, AnalyticUnit,
AnalyticUnitsSet, AnalyticSegment, AnalyticSegmentsSearcher, AnalyticSegmentPair,
AnalyticSegment, AnalyticSegmentsSearcher, AnalyticSegmentPair,
LabelingMode
} from '../models/analytic_unit';
} from '../models/analytic_units/analytic_unit';
import { AnalyticUnitsSet } from '../models/analytic_units/analytic_units_set';
import { MetricExpanded } from '../models/metric';
import { DatasourceRequest } from '../models/datasource';
import { Segment, SegmentId } from '../models/segment';
import { SegmentsSet } from '../models/segment_set';
import { SegmentArray } from '../models/segment_array';
import { HasticServerInfo, HasticServerInfoUnknown } from '../models/hastic_server_info';
import { Threshold, Condition } from '../models/threshold';
import { Condition } from '../models/analytic_units/threshold_analytic_unit';
import { DetectionStatus, DETECTION_STATUS_TEXT, DetectionSpan } from '../models/detection';
import text from '../partials/help_section.html';
import { createAnalyticUnit } from '../models/analytic_units/utils';
import helpSectionText from '../partials/help_section.html';
import {
ANALYTIC_UNIT_COLORS,
@ -42,7 +44,7 @@ export class AnalyticController {
private _labelingDataAddedSegments: SegmentsSet<AnalyticSegment>;
private _labelingDataRemovedSegments: SegmentsSet<AnalyticSegment>;
private _newAnalyticUnit: AnalyticUnit = null;
private _creatingNewAnalyticType: boolean = false;
private _creatingNewAnalyticUnit: boolean = false;
private _savingNewAnalyticUnit: boolean = false;
private _tempIdCounted: number = -1;
private _graphLocked: boolean = false;
@ -51,7 +53,6 @@ export class AnalyticController {
private _serverInfo: HasticServerInfo;
private _currentMetric: MetricExpanded;
private _currentDatasource: DatasourceRequest;
private _thresholds: Threshold[];
private _loading = true;
constructor(
@ -65,11 +66,9 @@ export class AnalyticController {
this._labelingDataRemovedSegments = new SegmentArray<AnalyticSegment>();
this._analyticUnitsSet = new AnalyticUnitsSet([]);
this.fetchAnalyticUnits();
this._thresholds = [];
this.updateThresholds();
}
get helpSectionText() { return text; }
get helpSectionText() { return helpSectionText; }
get loading() {
return this._loading;
@ -90,15 +89,9 @@ export class AnalyticController {
return result;
}
async sendThresholdParamsToServer(id: AnalyticUnitId) {
await this.saveThreshold(id);
await this._analyticService.runDetect(id);
await this._runStatusWaiter(this._analyticUnitsSet.byId(id));
}
createNew() {
this._newAnalyticUnit = new AnalyticUnit();
this._creatingNewAnalyticType = true;
this._creatingNewAnalyticUnit = true;
this._savingNewAnalyticUnit = false;
if(this.analyticUnits.length === 0) {
this._newAnalyticUnit.labeledColor = ANALYTIC_UNIT_COLORS[0];
@ -111,18 +104,17 @@ export class AnalyticController {
async saveNew(metric: MetricExpanded, datasource: DatasourceRequest) {
this._savingNewAnalyticUnit = true;
this._newAnalyticUnit.id = await this._analyticService.postNewAnalyticUnit(
this._newAnalyticUnit, metric, datasource, this._grafanaUrl, this._panelId
const newAnalyticUnit = createAnalyticUnit(this._newAnalyticUnit.serverObject);
newAnalyticUnit.id = await this._analyticService.postNewAnalyticUnit(
newAnalyticUnit, metric, datasource, this._grafanaUrl, this._panelId
);
if(this._newAnalyticUnit.detectorType === 'threshold') {
await this.saveThreshold(this._newAnalyticUnit.id);
}
this._analyticUnitsSet.addItem(this._newAnalyticUnit);
this._creatingNewAnalyticType = false;
this._analyticUnitsSet.addItem(newAnalyticUnit);
this._creatingNewAnalyticUnit = false;
this._savingNewAnalyticUnit = false;
delete this._newAnalyticUnit;
}
get creatingNew() { return this._creatingNewAnalyticType; }
get creatingNew() { return this._creatingNewAnalyticUnit; }
get saving() { return this._savingNewAnalyticUnit; }
get newAnalyticUnit(): AnalyticUnit { return this._newAnalyticUnit; }
@ -326,6 +318,15 @@ export class AnalyticController {
_.each(this.analyticUnits, analyticUnit => this._runStatusWaiter(analyticUnit));
}
async runDetect(analyticUnitId: AnalyticUnitId) {
const analyticUnit = this._analyticUnitsSet.byId(analyticUnitId);
analyticUnit.segments.clear();
analyticUnit.status = null;
await this.saveAnalyticUnit(analyticUnit);
await this._analyticService.runDetect(analyticUnitId);
this._runStatusWaiter(analyticUnit);
}
// TODO: move to renderer
updateFlotEvents(isEditMode: boolean, plot: any): void {
// We get a reference to flot options so we can change it and it'll be rendered
@ -536,39 +537,6 @@ export class AnalyticController {
return null;
}
async updateThresholds(): Promise<void> {
if(this._analyticService === undefined) {
return;
}
const ids = _.map(this._panelObject.analyticUnits, (analyticUnit: any) => analyticUnit.id);
const thresholds = await this._analyticService.getThresholds(ids);
this._thresholds = thresholds;
}
getThreshold(id: AnalyticUnitId): Threshold {
let threshold = _.find(this._thresholds, { id });
if(threshold === undefined) {
threshold = {
id,
value: 0,
condition: Condition.ABOVE
};
this._thresholds.push(threshold);
}
return threshold;
}
async saveThreshold(id: AnalyticUnitId) {
const threshold = this.getThreshold(id);
if(threshold.value === undefined) {
throw new Error('Cannot save threshold with undefined value');
}
if(threshold.condition === undefined) {
throw new Error('Cannot save threshold with undefined condition');
}
return this._analyticService.updateThreshold(threshold);
}
public get conditions() {
return _.values(Condition);
}
@ -687,7 +655,8 @@ export class AnalyticController {
}
public onAnalyticUnitDetectorChange(analyticUnitTypes: any) {
this.newAnalyticUnit.type = analyticUnitTypes[this.newAnalyticUnit.detectorType][0].value;
// TODO: looks bad
this._newAnalyticUnit.type = analyticUnitTypes[this._newAnalyticUnit.detectorType][0].value;
}
public async updateServerInfo() {

11
src/panel/graph_panel/graph_ctrl.ts

@ -7,7 +7,7 @@ import { GraphLegend } from './graph_legend';
import { DataProcessor } from './data_processor';
import { MetricExpanded } from './models/metric';
import { DatasourceRequest } from './models/datasource';
import { AnalyticUnitId, AnalyticUnit, LabelingMode } from './models/analytic_unit';
import { AnalyticUnitId, AnalyticUnit, LabelingMode } from './models/analytic_units/analytic_unit';
import { AnalyticService } from './services/analytic_service';
import { AnalyticController } from './controllers/analytic_controller';
import { HasticPanelInfo } from './models/hastic_panel_info';
@ -144,8 +144,7 @@ class GraphCtrl extends MetricsPanelCtrl {
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
thresholds: []
seriesOverrides: []
};
/** @ngInject */
@ -581,6 +580,10 @@ class GraphCtrl extends MetricsPanelCtrl {
this.analyticsController.redetectAll();
}
async runDetect(analyticUnitId: AnalyticUnitId) {
this.analyticsController.runDetect(analyticUnitId);
}
async saveNew() {
try {
const datasource = await this._getDatasourceRequest();
@ -606,7 +609,7 @@ class GraphCtrl extends MetricsPanelCtrl {
await this.analyticsController.toggleAnalyticUnitAlert(analyticUnit);
}
async onAnalyticUnitNameChange(analyticUnit: AnalyticUnit) {
async onAnalyticUnitChange(analyticUnit: AnalyticUnit) {
await this.analyticsController.saveAnalyticUnit(analyticUnit);
this.refresh();
}

2
src/panel/graph_panel/graph_renderer.ts

@ -1,5 +1,5 @@
import { Segment } from './models/segment';
import { LabelingMode } from './models/analytic_unit';
import { LabelingMode } from './models/analytic_units/analytic_unit';
import { GraphTooltip } from './graph_tooltip';
import { convertValuesToHistogram, getSeriesValues } from './histogram';

2
src/panel/graph_panel/graph_tooltip.ts

@ -1,4 +1,4 @@
import { AnalyticSegmentsSearcher } from './models/analytic_unit';
import { AnalyticSegmentsSearcher } from './models/analytic_units/analytic_unit';
export class GraphTooltip {

100
src/panel/graph_panel/models/analytic_unit.ts → src/panel/graph_panel/models/analytic_units/analytic_unit.ts

@ -1,13 +1,14 @@
import { SegmentsSet } from './segment_set';
import { SegmentArray } from './segment_array';
import { Segment, SegmentId } from './segment';
import { DetectionSpan } from './detection';
import { SegmentsSet } from '../segment_set';
import { SegmentArray } from '../segment_array';
import { Segment, SegmentId } from '../segment';
import { DetectionSpan } from '../detection';
import { ANALYTIC_UNIT_COLORS, DEFAULT_DELETED_SEGMENT_COLOR } from '../colors';
import { ANALYTIC_UNIT_COLORS, DEFAULT_DELETED_SEGMENT_COLOR } from '../../colors';
import _ from 'lodash';
// TODO: move types to ./types
export enum DetectorType {
PATTERN = 'pattern',
THRESHOLD = 'threshold'
@ -34,6 +35,17 @@ export class AnalyticSegment extends Segment {
}
}
const DEFAULTS = {
id: null,
name: 'AnalyticUnitName',
type: 'GENERAL',
detectorType: DetectorType.PATTERN,
labeledColor: ANALYTIC_UNIT_COLORS[0],
deletedColor: DEFAULT_DELETED_SEGMENT_COLOR,
alert: false,
visible: true
};
export class AnalyticUnit {
private _labelingMode: LabelingMode = LabelingMode.LABELING;
@ -45,22 +57,28 @@ export class AnalyticUnit {
private _status: string;
private _error: string;
constructor(private _serverObject?: any) {
const defaults = {
name: 'AnalyticUnitName',
labeledColor: ANALYTIC_UNIT_COLORS[0],
deletedColor: DEFAULT_DELETED_SEGMENT_COLOR,
detectorType: DetectorType.PATTERN,
type: 'GENERAL',
alert: false,
id: null,
visible: true
};
// TODO: serverObject -> fields
constructor(protected _serverObject?: any) {
if(_serverObject === undefined) {
this._serverObject = defaults;
this._serverObject = _.clone(DEFAULTS);
}
_.defaults(this._serverObject, defaults);
_.defaults(this._serverObject, DEFAULTS);
}
toJSON() {
return {
id: this.id,
name: this.name,
// TODO: enum type
// TODO: type -> subType
type: this.type,
// TODO: detectorType -> type
detectorType: this.detectorType,
labeledColor: this.labeledColor,
deletedColor: this.deletedColor,
alert: this.alert,
visible: this.visible,
};
}
get id(): AnalyticUnitId { return this._serverObject.id; }
@ -154,47 +172,3 @@ export class AnalyticUnit {
get serverObject() { return this._serverObject; }
}
export class AnalyticUnitsSet {
private _mapIdIndex: Map<AnalyticUnitId, number>;
private _items: AnalyticUnit[];
constructor(private _serverObject: any[]) {
if(_serverObject === undefined) {
throw new Error('server object can`t be undefined');
}
this._mapIdIndex = new Map<AnalyticUnitId, number>();
this._items = _serverObject.map(p => new AnalyticUnit(p));
this._rebuildIndex();
}
get items() { return this._items; }
addItem(item: AnalyticUnit) {
this._serverObject.push(item.serverObject);
this._mapIdIndex[item.id] = this._items.length;
this._items.push(item);
}
removeItem(id: AnalyticUnitId) {
var index = this._mapIdIndex[id];
this._serverObject.splice(index, 1);
this._items.splice(index, 1);
this._rebuildIndex();
}
_rebuildIndex() {
this._items.forEach((a, i) => {
this._mapIdIndex[a.id] = i;
});
}
byId(id: AnalyticUnitId): AnalyticUnit {
return this._items[this._mapIdIndex[id]];
}
byIndex(index: number): AnalyticUnit {
return this._items[index];
}
}

47
src/panel/graph_panel/models/analytic_units/analytic_units_set.ts

@ -0,0 +1,47 @@
import { AnalyticUnit, AnalyticUnitId } from './analytic_unit';
import { createAnalyticUnit } from './utils';
export class AnalyticUnitsSet {
private _mapIdIndex: Map<AnalyticUnitId, number>;
private _items: AnalyticUnit[];
constructor(private _serverObject: any[]) {
if (_serverObject === undefined) {
throw new Error('server object can`t be undefined');
}
this._mapIdIndex = new Map<AnalyticUnitId, number>();
this._items = _serverObject.map(p => createAnalyticUnit(p));
this._rebuildIndex();
}
get items() { return this._items; }
addItem(item: AnalyticUnit) {
this._serverObject.push(item.serverObject);
this._mapIdIndex[item.id] = this._items.length;
this._items.push(item);
}
removeItem(id: AnalyticUnitId) {
var index = this._mapIdIndex[id];
this._serverObject.splice(index, 1);
this._items.splice(index, 1);
this._rebuildIndex();
}
_rebuildIndex() {
this._items.forEach((a, i) => {
this._mapIdIndex[a.id] = i;
});
}
byId(id: AnalyticUnitId): AnalyticUnit {
return this._items[this._mapIdIndex[id]];
}
byIndex(index: number): AnalyticUnit {
return this._items[index];
}
}

23
src/panel/graph_panel/models/analytic_units/pattern_analytic_unit.ts

@ -0,0 +1,23 @@
import { AnalyticUnit, DetectorType } from './analytic_unit';
import _ from 'lodash';
const DEFAULTS = {
detectorType: DetectorType.PATTERN,
type: 'GENERAL'
};
export class PatternAnalyticUnit extends AnalyticUnit {
constructor(_serverObject?: any) {
super(_serverObject);
_.defaults(this._serverObject, DEFAULTS);
}
toJSON() {
const baseJSON = super.toJSON();
return {
...baseJSON
};
}
}

43
src/panel/graph_panel/models/analytic_units/threshold_analytic_unit.ts

@ -0,0 +1,43 @@
import { AnalyticUnit, DetectorType } from './analytic_unit';
import _ from 'lodash';
export enum Condition {
ABOVE = '>',
ABOVE_OR_EQUAL = '>=',
EQUAL = '=',
LESS_OR_EQUAL = '<=',
LESS = '<',
NO_DATA = 'NO_DATA'
};
const DEFAULTS = {
detectorType: DetectorType.THRESHOLD,
type: 'THRESHOLD',
value: 0,
condition: Condition.ABOVE_OR_EQUAL
};
export class ThresholdAnalyticUnit extends AnalyticUnit {
constructor(_serverObject?: any) {
super(_serverObject);
_.defaults(this._serverObject, DEFAULTS);
}
toJSON() {
const baseJSON = super.toJSON();
return {
...baseJSON,
value: this.value,
condition: this.condition
};
}
set value(val: number) { this._serverObject.value = val; }
get value(): number { return this._serverObject.value; }
set condition(val: Condition) { this._serverObject.condition = val; }
get condition(): Condition { return this._serverObject.condition; }
}

15
src/panel/graph_panel/models/analytic_units/utils.ts

@ -0,0 +1,15 @@
import { AnalyticUnit, DetectorType } from './analytic_unit';
import { PatternAnalyticUnit } from './pattern_analytic_unit';
import { ThresholdAnalyticUnit } from './threshold_analytic_unit';
export function createAnalyticUnit(serverObject: any): AnalyticUnit {
const detectorType: DetectorType = serverObject.detectorType;
switch(detectorType) {
case DetectorType.PATTERN:
return new PatternAnalyticUnit(serverObject);
case DetectorType.THRESHOLD:
return new ThresholdAnalyticUnit(serverObject);
default:
throw new Error(`Can't create analytic unit with type "${detectorType}"`);
}
}

2
src/panel/graph_panel/models/detection.ts

@ -1,4 +1,4 @@
import { AnalyticUnitId } from './analytic_unit';
import { AnalyticUnitId } from './analytic_units/analytic_unit';
export enum DetectionStatus {
READY = 'READY',

16
src/panel/graph_panel/models/threshold.ts

@ -1,16 +0,0 @@
import { AnalyticUnitId } from './analytic_unit';
export enum Condition {
ABOVE = '>',
ABOVE_OR_EQUAL = '>=',
EQUAL = '=',
LESS_OR_EQUAL = '<=',
LESS = '<',
NO_DATA = 'NO_DATA'
};
export type Threshold = {
id: AnalyticUnitId,
value: number,
condition: Condition
};

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

@ -30,7 +30,7 @@
<input
type="text" class="gf-form-input max-width-15"
ng-model="analyticUnit.name"
ng-blur="ctrl.onAnalyticUnitNameChange(analyticUnit)"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
>
<label class="gf-form-label width-4"> Type </label>
@ -97,24 +97,23 @@
/>
<select class="gf-form-input width-7"
ng-model="ctrl.analyticsController.getThreshold(analyticUnit.id).condition"
ng-options="type for type in ctrl.analyticsController.conditions"
ng-if="analyticUnit.detectorType === 'threshold'"
ng-model="analyticUnit.condition"
ng-options="type for type in ctrl.analyticsController.conditions"
/>
<input
class="gf-form-input width-5"
type="number"
ng-model="ctrl.analyticsController.getThreshold(analyticUnit.id).value"
<input class="gf-form-input width-5"
ng-if="
analyticUnit.detectorType === 'threshold' &&
ctrl.analyticsController.getThreshold(analyticUnit.id).condition != 'NO_DATA'
analyticUnit.condition !== 'NO_DATA'
"
type="number"
ng-model="analyticUnit.value"
/>
<!-- TODO set .saving flag to thresholds, when learning is in progress -->
<button
class="btn btn-inverse"
ng-if="analyticUnit.detectorType === 'threshold'"
ng-click="ctrl.analyticsController.sendThresholdParamsToServer(analyticUnit.id)"
ng-click="ctrl.runDetect(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'PENDING' || analyticUnit.status === 'LEARNING'"
>
Apply
@ -139,7 +138,7 @@
<i class="fa fa-eye-slash"></i>
</a>
</label>
<label class="gf-form-label" ng-if="analyticUnit.status === 'READY' && analyticUnit.visible">
<a
class="pointer"

27
src/panel/graph_panel/services/analytic_service.ts

@ -2,9 +2,8 @@ import { SegmentId } from '../models/segment';
import { MetricExpanded } from '../models/metric';
import { DatasourceRequest } from '../models/datasource';
import { SegmentsSet } from '../models/segment_set';
import { AnalyticUnitId, AnalyticUnit, AnalyticSegment } from '../models/analytic_unit';
import { AnalyticUnitId, AnalyticUnit, AnalyticSegment } from '../models/analytic_units/analytic_unit';
import { HasticServerInfo, HasticServerInfoUnknown } from '../models/hastic_server_info';
import { Threshold } from '../models/threshold';
import { DetectionSpan } from '../models/detection';
import { isHasticServerResponse, isSupportedServerVersion, SUPPORTED_SERVER_VERSION } from '../../../utlis';
@ -42,18 +41,6 @@ export class AnalyticService {
return resp.analyticUnits;
}
async getThresholds(ids: AnalyticUnitId[]) {
const resp = await this.get('/threshold', { ids: ids.join(',') });
if(resp === undefined) {
return [];
}
return resp.thresholds.filter(t => t !== null);
}
async updateThreshold(threshold: Threshold): Promise<void> {
return this.patch('/threshold', threshold);
}
async postNewAnalyticUnit(
analyticUnit: AnalyticUnit,
metric: MetricExpanded,
@ -61,19 +48,13 @@ export class AnalyticService {
grafanaUrl: string,
panelId: string
): Promise<AnalyticUnitId> {
const analyticUnitJson = analyticUnit.toJSON();
const response = await this.post('/analyticUnits', {
grafanaUrl,
panelId,
// TODO: serialize analytic unit
name: analyticUnit.name,
type: analyticUnit.type,
alert: analyticUnit.alert,
labeledColor: analyticUnit.labeledColor,
deletedColor: analyticUnit.deletedColor,
detectorType: analyticUnit.detectorType,
visible: analyticUnit.visible,
metric: metric.toJSON(),
datasource
datasource,
...analyticUnitJson
});
return response.id as AnalyticUnitId;

2
tests/setup_tests.ts

@ -1,5 +1,5 @@
import { AnalyticController } from '../src/panel/graph_panel/controllers/analytic_controller';
import { AnalyticUnit, AnalyticUnitId } from '../src/panel/graph_panel/models/analytic_unit';
import { AnalyticUnit, AnalyticUnitId } from '../src/panel/graph_panel/models/analytic_units/analytic_unit';
import { AnalyticService } from '../src/panel/graph_panel/services/analytic_service';
import { MetricExpanded } from '../src/panel/graph_panel/models/metric';
import { DatasourceRequest } from '../src/panel/graph_panel/models/datasource';

Loading…
Cancel
Save