Browse Source

Unlabeled segments #187 && Choose deleted segment color #182 (#188)

master
rozetko 5 years ago committed by GitHub
parent
commit
5ac21a011e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/panel/graph_panel/colors.ts
  2. 109
      src/panel/graph_panel/controllers/analytic_controller.ts
  3. 38
      src/panel/graph_panel/graph_ctrl.ts
  4. 54
      src/panel/graph_panel/graph_renderer.ts
  5. 2
      src/panel/graph_panel/graph_tooltip.ts
  6. 28
      src/panel/graph_panel/models/analytic_unit.ts
  7. 2
      src/panel/graph_panel/models/segment_array.ts
  8. 29
      src/panel/graph_panel/partials/tab_analytics.html
  9. 16
      src/panel/graph_panel/services/analytic_service.ts
  10. 12
      tests/analytic_controller.jest.ts

12
src/panel/graph_panel/colors.ts

@ -76,6 +76,16 @@ export const ANALYTIC_UNIT_COLORS = [
'#f8c171', '#f8c171',
]; ];
export const DEFAULT_DELETED_SEGMENT_COLOR = '#00f0ff';
export const REGION_UNLABEL_COLOR_LIGHT = '#d1d1d1';
export const REGION_UNLABEL_COLOR_DARK = 'white';
export const LABELED_SEGMENT_BORDER_COLOR = 'black';
export const DELETED_SEGMENT_BORDER_COLOR = 'black';
export const SEGMENT_FILL_ALPHA = 0.5;
export const SEGMENT_STROKE_ALPHA = 0.8;
export const LABELING_MODE_ALPHA = 0.7;
export function hexToHsl(color) { export function hexToHsl(color) {
return tinycolor(color).toHsl(); return tinycolor(color).toHsl();
} }
@ -85,4 +95,4 @@ export function hslToHex(color) {
} }
export default colors; export default colors;

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

@ -4,7 +4,8 @@ import { AnalyticService } from '../services/analytic_service'
import { import {
AnalyticUnitId, AnalyticUnit, AnalyticUnitId, AnalyticUnit,
AnalyticUnitsSet, AnalyticSegment, AnalyticSegmentsSearcher, AnalyticSegmentPair AnalyticUnitsSet, AnalyticSegment, AnalyticSegmentsSearcher, AnalyticSegmentPair,
LabelingMode
} from '../models/analytic_unit'; } from '../models/analytic_unit';
import { MetricExpanded } from '../models/metric'; import { MetricExpanded } from '../models/metric';
import { DatasourceRequest } from '../models/datasource'; import { DatasourceRequest } from '../models/datasource';
@ -14,30 +15,27 @@ import { SegmentArray } from '../models/segment_array';
import { ServerInfo } from '../models/info'; import { ServerInfo } from '../models/info';
import { Threshold, Condition } from '../models/threshold'; import { Threshold, Condition } from '../models/threshold';
import { ANALYTIC_UNIT_COLORS } from '../colors'; import {
ANALYTIC_UNIT_COLORS,
LABELED_SEGMENT_BORDER_COLOR,
DELETED_SEGMENT_BORDER_COLOR,
SEGMENT_FILL_ALPHA,
SEGMENT_STROKE_ALPHA,
LABELING_MODE_ALPHA
} from '../colors';
import { Emitter } from 'grafana/app/core/utils/emitter'; import { Emitter } from 'grafana/app/core/utils/emitter';
import _ from 'lodash'; import _ from 'lodash';
import * as tinycolor from 'tinycolor2'; import * as tinycolor from 'tinycolor2';
export const REGION_FILL_ALPHA = 0.5;
export const REGION_STROKE_ALPHA = 0.8;
const LABELING_MODE_ALPHA = 0.7;
export const REGION_DELETE_COLOR_LIGHT = '#d1d1d1';
export const REGION_DELETE_COLOR_DARK = 'white';
const LABELED_SEGMENT_BORDER_COLOR = 'black';
const DELETED_SEGMENT_FILL_COLOR = '#00f0ff';
const DELETED_SEGMENT_BORDER_COLOR = 'black';
export class AnalyticController { export class AnalyticController {
private _analyticUnitsSet: AnalyticUnitsSet; private _analyticUnitsSet: AnalyticUnitsSet;
private _selectedAnalyticUnitId: AnalyticUnitId = null; private _selectedAnalyticUnitId: AnalyticUnitId = null;
private _labelingDataAddedSegments: SegmentsSet<AnalyticSegment>; private _labelingDataAddedSegments: SegmentsSet<AnalyticSegment>;
private _labelingDataDeletedSegments: SegmentsSet<AnalyticSegment>; private _labelingDataRemovedSegments: SegmentsSet<AnalyticSegment>;
private _newAnalyticUnit: AnalyticUnit = null; private _newAnalyticUnit: AnalyticUnit = null;
private _creatingNewAnalyticType: boolean = false; private _creatingNewAnalyticType: boolean = false;
private _savingNewAnalyticUnit: boolean = false; private _savingNewAnalyticUnit: boolean = false;
@ -54,7 +52,7 @@ export class AnalyticController {
_panelObject.analyticUnits = _panelObject.anomalyTypes || []; _panelObject.analyticUnits = _panelObject.anomalyTypes || [];
} }
this._labelingDataAddedSegments = new SegmentArray<AnalyticSegment>(); this._labelingDataAddedSegments = new SegmentArray<AnalyticSegment>();
this._labelingDataDeletedSegments = new SegmentArray<AnalyticSegment>(); this._labelingDataRemovedSegments = new SegmentArray<AnalyticSegment>();
this._analyticUnitsSet = new AnalyticUnitsSet(this._panelObject.analyticUnits); this._analyticUnitsSet = new AnalyticUnitsSet(this._panelObject.analyticUnits);
this._thresholds = []; this._thresholds = [];
this.updateThresholds(); this.updateThresholds();
@ -87,11 +85,11 @@ export class AnalyticController {
this._creatingNewAnalyticType = true; this._creatingNewAnalyticType = true;
this._savingNewAnalyticUnit = false; this._savingNewAnalyticUnit = false;
if (this.analyticUnits.length === 0) { if (this.analyticUnits.length === 0) {
this._newAnalyticUnit.color = ANALYTIC_UNIT_COLORS[0]; this._newAnalyticUnit.labeledColor = ANALYTIC_UNIT_COLORS[0];
} else { } else {
let colorIndex = ANALYTIC_UNIT_COLORS.indexOf(_.last(this.analyticUnits).color) + 1; let colorIndex = ANALYTIC_UNIT_COLORS.indexOf(_.last(this.analyticUnits).labeledColor) + 1;
colorIndex %= ANALYTIC_UNIT_COLORS.length; colorIndex %= ANALYTIC_UNIT_COLORS.length;
this._newAnalyticUnit.color = ANALYTIC_UNIT_COLORS[colorIndex]; this._newAnalyticUnit.labeledColor = ANALYTIC_UNIT_COLORS[colorIndex];
} }
} }
@ -161,29 +159,29 @@ export class AnalyticController {
this._labelingDataAddedSegments.getSegments().forEach(s => { this._labelingDataAddedSegments.getSegments().forEach(s => {
this.labelingUnit.segments.remove(s.id); this.labelingUnit.segments.remove(s.id);
}); });
this._labelingDataDeletedSegments.getSegments().forEach(s => { this._labelingDataRemovedSegments.getSegments().forEach(s => {
s.deleted = false; this.labelingUnit.segments.addSegment(s);
}); });
this.dropLabeling(); this.dropLabeling();
} }
dropLabeling() { dropLabeling() {
this._labelingDataAddedSegments.clear(); this._labelingDataAddedSegments.clear();
this._labelingDataDeletedSegments.clear(); this._labelingDataRemovedSegments.clear();
this.labelingUnit.selected = false; this.labelingUnit.selected = false;
this._selectedAnalyticUnitId = null; this._selectedAnalyticUnitId = null;
this._tempIdCounted = -1; this._tempIdCounted = -1;
} }
get labelingMode(): boolean { get inLabelingMode(): boolean {
return this._selectedAnalyticUnitId !== null; return this._selectedAnalyticUnitId !== null;
} }
get labelingDeleteMode(): boolean { get labelingMode(): LabelingMode {
if(!this.labelingMode) { if(!this.inLabelingMode) {
return false; return LabelingMode.NOT_IN_LABELING_MODE;
} }
return this.labelingUnit.deleteMode; return this.labelingUnit.labelingMode;
} }
addLabelSegment(segment: Segment, deleted = false) { addLabelSegment(segment: Segment, deleted = false) {
@ -195,11 +193,15 @@ export class AnalyticController {
return this._analyticUnitsSet.items; return this._analyticUnitsSet.items;
} }
onAnalyticUnitColorChange(id: AnalyticUnitId, value: string) { onAnalyticUnitColorChange(id: AnalyticUnitId, value: string, deleted: boolean) {
if(id === undefined) { if(id === undefined) {
throw new Error('id is undefined'); throw new Error('id is undefined');
} }
this._analyticUnitsSet.byId(id).color = value; if(deleted) {
this._analyticUnitsSet.byId(id).deletedColor = value;
} else {
this._analyticUnitsSet.byId(id).labeledColor = value;
}
} }
fetchAnalyticUnitsStatuses() { fetchAnalyticUnitsStatuses() {
@ -228,7 +230,7 @@ export class AnalyticController {
var allSegmentsSet = new SegmentArray(allSegmentsList); var allSegmentsSet = new SegmentArray(allSegmentsList);
if(analyticUnit.selected) { if(analyticUnit.selected) {
this._labelingDataAddedSegments.getSegments().forEach(s => allSegmentsSet.addSegment(s)); this._labelingDataAddedSegments.getSegments().forEach(s => allSegmentsSet.addSegment(s));
this._labelingDataDeletedSegments.getSegments().forEach(s => allSegmentsSet.remove(s.id)); this._labelingDataRemovedSegments.getSegments().forEach(s => allSegmentsSet.remove(s.id));
} }
analyticUnit.segments = allSegmentsSet; analyticUnit.segments = allSegmentsSet;
} }
@ -241,16 +243,20 @@ export class AnalyticController {
if( if(
this._labelingDataAddedSegments.length === 0 && this._labelingDataAddedSegments.length === 0 &&
this._labelingDataDeletedSegments.length === 0 this._labelingDataRemovedSegments.length === 0
) { ) {
return []; return [];
} }
await this._analyticService.updateMetric(unit.id, this._currentMetric, this._currentDatasource); await this._analyticService.updateMetric(unit.id, this._currentMetric, this._currentDatasource);
return this._analyticService.updateSegments( const newIds = await this._analyticService.updateSegments(
unit.id, this._labelingDataAddedSegments, this._labelingDataDeletedSegments unit.id, this._labelingDataAddedSegments, this._labelingDataRemovedSegments
); );
if(unit.labelingMode !== LabelingMode.UNLABELING) {
await this._analyticService.runDetect(unit.id);
}
return newIds;
} }
// TODO: move to renderer // TODO: move to renderer
@ -265,18 +271,18 @@ export class AnalyticController {
continue; continue;
} }
let borderColor = addAlphaToRGB(analyticUnit.color, REGION_STROKE_ALPHA); let defaultBorderColor = addAlphaToRGB(analyticUnit.labeledColor, SEGMENT_STROKE_ALPHA);
let fillColor = addAlphaToRGB(analyticUnit.color, REGION_FILL_ALPHA); let defaultFillColor = addAlphaToRGB(analyticUnit.labeledColor, SEGMENT_FILL_ALPHA);
let labeledSegmentBorderColor = tinycolor(LABELED_SEGMENT_BORDER_COLOR).toRgbString(); let labeledSegmentBorderColor = tinycolor(LABELED_SEGMENT_BORDER_COLOR).toRgbString();
labeledSegmentBorderColor = addAlphaToRGB(labeledSegmentBorderColor, REGION_STROKE_ALPHA); labeledSegmentBorderColor = addAlphaToRGB(labeledSegmentBorderColor, SEGMENT_STROKE_ALPHA);
let deletedSegmentFillColor = tinycolor(DELETED_SEGMENT_FILL_COLOR).toRgbString(); let deletedSegmentFillColor = tinycolor(analyticUnit.deletedColor).toRgbString();
deletedSegmentFillColor = addAlphaToRGB(deletedSegmentFillColor, REGION_FILL_ALPHA); deletedSegmentFillColor = addAlphaToRGB(deletedSegmentFillColor, SEGMENT_FILL_ALPHA);
let deletedSegmentBorderColor = tinycolor(DELETED_SEGMENT_BORDER_COLOR).toRgbString(); let deletedSegmentBorderColor = tinycolor(DELETED_SEGMENT_BORDER_COLOR).toRgbString();
deletedSegmentBorderColor = addAlphaToRGB(deletedSegmentBorderColor, REGION_STROKE_ALPHA); deletedSegmentBorderColor = addAlphaToRGB(deletedSegmentBorderColor, SEGMENT_STROKE_ALPHA);
if(isEditMode && this.labelingMode && analyticUnit.selected) { if(isEditMode && this.inLabelingMode && analyticUnit.selected) {
borderColor = addAlphaToRGB(borderColor, LABELING_MODE_ALPHA); defaultBorderColor = addAlphaToRGB(defaultBorderColor, LABELING_MODE_ALPHA);
fillColor = addAlphaToRGB(fillColor, LABELING_MODE_ALPHA); defaultFillColor = addAlphaToRGB(defaultFillColor, LABELING_MODE_ALPHA);
labeledSegmentBorderColor = addAlphaToRGB(labeledSegmentBorderColor, LABELING_MODE_ALPHA); labeledSegmentBorderColor = addAlphaToRGB(labeledSegmentBorderColor, LABELING_MODE_ALPHA);
deletedSegmentFillColor = addAlphaToRGB(deletedSegmentFillColor, LABELING_MODE_ALPHA); deletedSegmentFillColor = addAlphaToRGB(deletedSegmentFillColor, LABELING_MODE_ALPHA);
deletedSegmentBorderColor = addAlphaToRGB(deletedSegmentBorderColor, LABELING_MODE_ALPHA); deletedSegmentBorderColor = addAlphaToRGB(deletedSegmentBorderColor, LABELING_MODE_ALPHA);
@ -286,8 +292,8 @@ export class AnalyticController {
const rangeDist = +options.xaxis.max - +options.xaxis.min; const rangeDist = +options.xaxis.max - +options.xaxis.min;
segments.forEach(s => { segments.forEach(s => {
let segmentBorderColor = borderColor; let segmentBorderColor = defaultBorderColor;
let segmentFillColor = fillColor; let segmentFillColor = defaultFillColor;
if(s.deleted) { if(s.deleted) {
segmentBorderColor = deletedSegmentBorderColor; segmentBorderColor = deletedSegmentBorderColor;
@ -298,7 +304,7 @@ export class AnalyticController {
} }
} }
var expanded = s.expandDist(rangeDist, 0.01); const expanded = s.expandDist(rangeDist, 0.01);
options.grid.markings.push({ options.grid.markings.push({
xaxis: { from: expanded.from, to: expanded.to }, xaxis: { from: expanded.from, to: expanded.to },
color: segmentFillColor color: segmentFillColor
@ -317,21 +323,24 @@ export class AnalyticController {
} }
deleteLabelingAnalyticUnitSegmentsInRange(from: number, to: number) { deleteLabelingAnalyticUnitSegmentsInRange(from: number, to: number) {
let allRemovedSegs = this.labelingUnit.removeSegmentsInRange(from, to); const allRemovedSegs = this.labelingUnit.removeSegmentsInRange(from, to);
allRemovedSegs.forEach(s => { allRemovedSegs.forEach(s => {
if(!this._labelingDataAddedSegments.has(s.id)) { if(!this._labelingDataAddedSegments.has(s.id)) {
this._labelingDataDeletedSegments.addSegment(s); this._labelingDataRemovedSegments.addSegment(s);
this.labelingUnit.addLabeledSegment(s, true);
} }
}); });
this._labelingDataAddedSegments.removeInRange(from, to); this._labelingDataAddedSegments.removeInRange(from, to);
} }
toggleDeleteMode() { toggleLabelingMode(mode: LabelingMode) {
if(!this.labelingMode) { if(!this.inLabelingMode) {
throw new Error('Cant enter delete mode is labeling mode disabled'); throw new Error(`Can't enter ${mode} mode when labeling mode is disabled`);
}
if(this.labelingUnit.labelingMode === mode) {
this.labelingUnit.labelingMode = LabelingMode.LABELING;
} else {
this.labelingUnit.labelingMode = mode;
} }
this.labelingUnit.deleteMode = !this.labelingUnit.deleteMode;
} }
async removeAnalyticUnit(id: AnalyticUnitId, silent: boolean = false) { async removeAnalyticUnit(id: AnalyticUnitId, silent: boolean = false) {

38
src/panel/graph_panel/graph_ctrl.ts

@ -7,7 +7,7 @@ import { GraphLegend } from './graph_legend';
import { DataProcessor } from './data_processor'; import { DataProcessor } from './data_processor';
import { MetricExpanded } from './models/metric'; import { MetricExpanded } from './models/metric';
import { DatasourceRequest } from './models/datasource'; import { DatasourceRequest } from './models/datasource';
import { AnalyticUnitId, AnalyticUnit } from './models/analytic_unit'; import { AnalyticUnitId, AnalyticUnit, LabelingMode } from './models/analytic_unit';
import { AnalyticService } from './services/analytic_service'; import { AnalyticService } from './services/analytic_service';
import { AnalyticController } from './controllers/analytic_controller'; import { AnalyticController } from './controllers/analytic_controller';
import { PanelInfo } from './models/info'; import { PanelInfo } from './models/info';
@ -35,7 +35,6 @@ class GraphCtrl extends MetricsPanelCtrl {
private _datasourceRequest: DatasourceRequest; private _datasourceRequest: DatasourceRequest;
private _datasources: any; private _datasources: any;
private _panelPath: any;
private _renderError: boolean = false; private _renderError: boolean = false;
// annotationsPromise: any; // annotationsPromise: any;
@ -157,19 +156,28 @@ class GraphCtrl extends MetricsPanelCtrl {
this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
} }
rebindDKey() { rebindKeys() {
const dKeyCode = 68;
const uKeyCode = 85;
$(document).off('keydown.hasticDKey'); $(document).off('keydown.hasticDKey');
$(document).on('keydown.hasticDKey', (e) => { $(document).on('keydown.hasticDKey', (e) => {
// 68 is 'd' key kode if(e.keyCode === dKeyCode) {
if(e.keyCode === 68) {
this.onDKey(); this.onDKey();
} }
}); });
$(document).off('keydown.hasticUKey');
$(document).on('keydown.hasticUKey', (e) => {
if(e.keyCode === uKeyCode) {
this.onUKey();
}
});
} }
editPanel() { editPanel() {
super.editPanel(); super.editPanel();
this.rebindDKey(); this.rebindKeys();
} }
async getBackendURL(): Promise<string> { async getBackendURL(): Promise<string> {
@ -300,7 +308,7 @@ class GraphCtrl extends MetricsPanelCtrl {
onInitEditMode() { onInitEditMode() {
this.rebindDKey(); // a small hask: bind if we open page in edit mode this.rebindKeys(); // a small hask: bind if we open page in edit mode
const partialPath = this.panelPath + '/partials'; const partialPath = this.panelPath + '/partials';
this.addEditorTab('Analytics', `${partialPath}/tab_analytics.html`, 2); this.addEditorTab('Analytics', `${partialPath}/tab_analytics.html`, 2);
@ -581,11 +589,11 @@ class GraphCtrl extends MetricsPanelCtrl {
this.analyticsController.fetchAnalyticUnitName(analyticUnit); this.analyticsController.fetchAnalyticUnitName(analyticUnit);
} }
onColorChange(id: AnalyticUnitId, value: string) { onColorChange(id: AnalyticUnitId, deleted: boolean, value: string) {
if(id === undefined) { if(id === undefined) {
throw new Error('id is undefined'); throw new Error('id is undefined');
} }
this.analyticsController.onAnalyticUnitColorChange(id, value); this.analyticsController.onAnalyticUnitColorChange(id, value, deleted);
this.render(); this.render();
} }
@ -624,10 +632,18 @@ class GraphCtrl extends MetricsPanelCtrl {
} }
onDKey() { onDKey() {
if(!this.analyticsController.labelingMode) { if(!this.analyticsController.inLabelingMode) {
return;
}
this.analyticsController.toggleLabelingMode(LabelingMode.DELETING);
this.refresh();
}
onUKey() {
if(!this.analyticsController.inLabelingMode) {
return; return;
} }
this.analyticsController.toggleDeleteMode(); this.analyticsController.toggleLabelingMode(LabelingMode.UNLABELING);
this.refresh(); this.refresh();
} }

54
src/panel/graph_panel/graph_renderer.ts

@ -1,15 +1,17 @@
import { Segment } from './models/segment'; import { Segment } from './models/segment';
import { LabelingMode } from './models/analytic_unit';
import { GraphTooltip } from './graph_tooltip'; import { GraphTooltip } from './graph_tooltip';
import { convertValuesToHistogram, getSeriesValues } from './histogram'; import { convertValuesToHistogram, getSeriesValues } from './histogram';
import { import {
AnalyticController, AnalyticController
REGION_FILL_ALPHA,
REGION_STROKE_ALPHA,
REGION_DELETE_COLOR_LIGHT,
REGION_DELETE_COLOR_DARK
} from './controllers/analytic_controller'; } from './controllers/analytic_controller';
import {
REGION_UNLABEL_COLOR_LIGHT,
REGION_UNLABEL_COLOR_DARK
} from './colors';
import { GraphCtrl } from './graph_ctrl'; import { GraphCtrl } from './graph_ctrl';
import 'jquery'; import 'jquery';
@ -140,19 +142,26 @@ export class GraphRenderer {
if(this._isHasticEvent(selectionEvent)) { if(this._isHasticEvent(selectionEvent)) {
this.plot.clearSelection(); this.plot.clearSelection();
var id = this._analyticController.getNewTempSegmentId(); const id = this._analyticController.getNewTempSegmentId();
var segment = new Segment( const segment = new Segment(
id, id,
Math.round(selectionEvent.xaxis.from), Math.round(selectionEvent.xaxis.from),
Math.round(selectionEvent.xaxis.to) Math.round(selectionEvent.xaxis.to)
); );
if(this._analyticController.labelingDeleteMode) { if(this._analyticController.labelingMode === LabelingMode.DELETING) {
this._analyticController.deleteLabelingAnalyticUnitSegmentsInRange( this._analyticController.deleteLabelingAnalyticUnitSegmentsInRange(
segment.from, segment.to segment.from, segment.to
); );
} else { this._analyticController.addLabelSegment(segment, true);
this._analyticController.addLabelSegment(segment); }
if(this._analyticController.labelingMode === LabelingMode.LABELING) {
this._analyticController.addLabelSegment(segment, false);
} }
if(this._analyticController.labelingMode === LabelingMode.UNLABELING) {
this._analyticController.deleteLabelingAnalyticUnitSegmentsInRange(
segment.from, segment.to
);
}
this.renderPanel(); this.renderPanel();
return; return;
@ -335,21 +344,22 @@ export class GraphRenderer {
} }
private _chooseSelectionColor(e) { private _chooseSelectionColor(e) {
var color = COLOR_SELECTION; let color = COLOR_SELECTION;
var fillAlpha = 0.4;
var strokeAlpha = 0.4;
if(this._isHasticEvent(e)) { if(this._isHasticEvent(e)) {
if(this._analyticController.labelingDeleteMode) { if(this._analyticController.labelingMode === LabelingMode.DELETING) {
color = this._analyticController.labelingUnit.deletedColor;
}
if(this._analyticController.labelingMode === LabelingMode.LABELING) {
color = this._analyticController.labelingUnit.labeledColor;
}
if(this._analyticController.labelingMode === LabelingMode.UNLABELING) {
color = this.contextSrv.user.lightTheme ? color = this.contextSrv.user.lightTheme ?
REGION_DELETE_COLOR_LIGHT : REGION_UNLABEL_COLOR_LIGHT :
REGION_DELETE_COLOR_DARK; REGION_UNLABEL_COLOR_DARK;
} else {
color = this._analyticController.labelingUnit.color;
} }
fillAlpha = REGION_FILL_ALPHA;
strokeAlpha = REGION_STROKE_ALPHA;
} }
this.plot.getOptions().selection.color = color this.plot.getOptions().selection.color = color;
} }
private _buildFlotPairs(data) { private _buildFlotPairs(data) {
@ -806,7 +816,7 @@ export class GraphRenderer {
private _isHasticEvent(obj: any) { private _isHasticEvent(obj: any) {
return (obj.ctrlKey || obj.metaKey) && return (obj.ctrlKey || obj.metaKey) &&
this.contextSrv.isEditor && this.contextSrv.isEditor &&
this._analyticController.labelingMode; this._analyticController.inLabelingMode;
} }
} }

2
src/panel/graph_panel/graph_tooltip.ts

@ -211,7 +211,7 @@ export class GraphTooltip {
result += ` result += `
<div class="graph-tooltip-list-item"> <div class="graph-tooltip-list-item">
<div class="graph-tooltip-series-name"> <div class="graph-tooltip-series-name">
<i class="fa fa-exclamation" style="color:${s.analyticUnit.color}"></i> <i class="fa fa-exclamation" style="color:${s.analyticUnit.labeledColor}"></i>
${s.analyticUnit.name}: ${s.analyticUnit.name}:
</div> </div>
<div class="graph-tooltip-value"> <div class="graph-tooltip-value">

28
src/panel/graph_panel/models/analytic_unit.ts

@ -2,11 +2,18 @@ import { SegmentsSet } from './segment_set';
import { SegmentArray } from './segment_array'; import { SegmentArray } from './segment_array';
import { Segment, SegmentId } from './segment'; import { Segment, SegmentId } from './segment';
import { ANALYTIC_UNIT_COLORS } from '../colors'; import { ANALYTIC_UNIT_COLORS, DEFAULT_DELETED_SEGMENT_COLOR } from '../colors';
import _ from 'lodash'; import _ from 'lodash';
export enum LabelingMode {
LABELING = 'LABELING',
UNLABELING = 'UNLABELING',
DELETING = 'DELETING',
NOT_IN_LABELING_MODE = 'NOT_IN_LABELING_MODE'
};
export type AnalyticSegmentPair = { analyticUnit: AnalyticUnit, segment: AnalyticSegment }; export type AnalyticSegmentPair = { analyticUnit: AnalyticUnit, segment: AnalyticSegment };
export type AnalyticSegmentsSearcher = (point: number, rangeDist: number) => AnalyticSegmentPair[]; export type AnalyticSegmentsSearcher = (point: number, rangeDist: number) => AnalyticSegmentPair[];
@ -23,8 +30,8 @@ export class AnalyticSegment extends Segment {
export class AnalyticUnit { export class AnalyticUnit {
private _labelingMode: LabelingMode = LabelingMode.LABELING;
private _selected: boolean = false; private _selected: boolean = false;
private _deleteMode: boolean = false;
private _saving: boolean = false; private _saving: boolean = false;
private _segmentSet = new SegmentArray<AnalyticSegment>(); private _segmentSet = new SegmentArray<AnalyticSegment>();
private _status: string; private _status: string;
@ -36,7 +43,8 @@ export class AnalyticUnit {
} }
_.defaults(this._panelObject, { _.defaults(this._panelObject, {
name: 'AnalyticUnitName', name: 'AnalyticUnitName',
color: ANALYTIC_UNIT_COLORS[0], labeledColor: ANALYTIC_UNIT_COLORS[0],
deletedColor: DEFAULT_DELETED_SEGMENT_COLOR,
detectorType: 'pattern', detectorType: 'pattern',
type: 'GENERAL', type: 'GENERAL',
alert: false alert: false
@ -58,8 +66,11 @@ export class AnalyticUnit {
set confidence(value: number) { this._panelObject.confidence = value; } set confidence(value: number) { this._panelObject.confidence = value; }
get confidence(): number { return this._panelObject.confidence; } get confidence(): number { return this._panelObject.confidence; }
set color(value: string) { this._panelObject.color = value; } set labeledColor(value: string) { this._panelObject.labeledColor = value; }
get color(): string { return this._panelObject.color; } get labeledColor(): string { return this._panelObject.labeledColor; }
set deletedColor(value: string) { this._panelObject.deletedColor = value; }
get deletedColor(): string { return this._panelObject.deletedColor; }
set alert(value: boolean) { this._panelObject.alert = value; } set alert(value: boolean) { this._panelObject.alert = value; }
get alert(): boolean { return this._panelObject.alert; } get alert(): boolean { return this._panelObject.alert; }
@ -67,8 +78,8 @@ export class AnalyticUnit {
get selected(): boolean { return this._selected; } get selected(): boolean { return this._selected; }
set selected(value: boolean) { this._selected = value; } set selected(value: boolean) { this._selected = value; }
get deleteMode(): boolean { return this._deleteMode; } get labelingMode(): LabelingMode { return this._labelingMode; }
set deleteMode(value: boolean) { this._deleteMode = value; } set labelingMode(value: LabelingMode) { this._labelingMode = value; }
get saving(): boolean { return this._saving; } get saving(): boolean { return this._saving; }
set saving(value: boolean) { this._saving = value; } set saving(value: boolean) { this._saving = value; }
@ -88,9 +99,6 @@ export class AnalyticUnit {
removeSegmentsInRange(from: number, to: number): AnalyticSegment[] { removeSegmentsInRange(from: number, to: number): AnalyticSegment[] {
let deletedSegments = this._segmentSet.removeInRange(from, to); let deletedSegments = this._segmentSet.removeInRange(from, to);
deletedSegments.forEach(s => {
s.deleted = true;
});
return deletedSegments; return deletedSegments;
} }

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

@ -47,7 +47,7 @@ export class SegmentArray<T extends Segment> implements SegmentsSet<T> {
findSegments(point: number, rangeDist: number): T[] { findSegments(point: number, rangeDist: number): T[] {
return this._segments.filter(s => { return this._segments.filter(s => {
var expanded = s.expandDist(rangeDist, 0.01); const expanded = s.expandDist(rangeDist, 0.01);
return (expanded.from <= point) && (point <= expanded.to); return (expanded.from <= point) && (point <= expanded.to);
}); });
} }

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

@ -11,7 +11,7 @@
<div class="editor-row"> <div class="editor-row">
<div class="gf-form" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits"> <div class="gf-form" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits">
<label class="gf-form-label width-6"> <label class="gf-form-label width-5">
<i class="fa fa-info" bs-tooltip="'Analytic unit id: ' + analyticUnit.id"></i> <i class="fa fa-info" bs-tooltip="'Analytic unit id: ' + analyticUnit.id"></i>
&nbsp; Name &nbsp; Name
</label> </label>
@ -21,7 +21,7 @@
ng-change="ctrl.onAnalyticUnitNameChange(analyticUnit)" ng-change="ctrl.onAnalyticUnitNameChange(analyticUnit)"
> >
<label class="gf-form-label width-8"> Type </label> <label class="gf-form-label width-4"> Type </label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input width-12" <select class="gf-form-input width-12"
ng-model="analyticUnit.type" ng-model="analyticUnit.type"
@ -39,11 +39,19 @@
/> />
--> -->
<label class="gf-form-label width-6"> Color </label> <label class="gf-form-label width-8"> Labeled Color </label>
<span class="gf-form-label"> <span class="gf-form-label">
<color-picker <color-picker
color="analyticUnit.color" color="analyticUnit.labeledColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id)" onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, false)"
/>
</span>
<label class="gf-form-label width-8"> Deleted Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.deletedColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, true)"
/> />
</span> </span>
@ -58,8 +66,9 @@
> >
<i class="fa fa-bar-chart" ng-if="!analyticUnit.saving"></i> <i class="fa fa-bar-chart" ng-if="!analyticUnit.saving"></i>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i> <i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="analyticUnit.selected && !analyticUnit.deleteMode && !analyticUnit.saving"> labeling </b> <b ng-if="analyticUnit.selected && analyticUnit.labelingMode === 'LABELING' && !analyticUnit.saving"> labeling </b>
<b ng-if="analyticUnit.selected && analyticUnit.deleteMode && !analyticUnit.saving"> deleting </b> <b ng-if="analyticUnit.selected && analyticUnit.labelingMode === 'DELETING' && !analyticUnit.saving"> deleting </b>
<b ng-if="analyticUnit.selected && analyticUnit.labelingMode === 'UNLABELING' && !analyticUnit.saving"> unlabeling </b>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b> <b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a> </a>
</label> </label>
@ -79,9 +88,9 @@
" "
/> />
<!-- TODO set .saving flag to thresholds, when learning is in progress --> <!-- TODO set .saving flag to thresholds, when learning is in progress -->
<button <button
class="btn btn-inverse" class="btn btn-inverse"
ng-if="analyticUnit.detectorType === 'threshold'" ng-if="analyticUnit.detectorType === 'threshold'"
ng-click="ctrl.analyticsController.sendThresholdParamsToServer(analyticUnit.id)" ng-click="ctrl.analyticsController.sendThresholdParamsToServer(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'PENDING' || analyticUnit.status === 'LEARNING'" ng-disabled="analyticUnit.status === 'PENDING' || analyticUnit.status === 'LEARNING'"
> >

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

@ -66,20 +66,22 @@ export class AnalyticService {
} }
async updateSegments( async updateSegments(
id: AnalyticUnitId, addedSegments: SegmentsSet<Segment>, removedSegments: SegmentsSet<Segment> id: AnalyticUnitId, addedSegments: SegmentsSet<AnalyticSegment>, removedSegments: SegmentsSet<AnalyticSegment>
): Promise<SegmentId[]> { ): Promise<SegmentId[]> {
const getJSONs = (segs: SegmentsSet<Segment>) => segs.getSegments().map(segment => ({ const getJSONs = (segs: SegmentsSet<AnalyticSegment>) => segs.getSegments().map(segment => ({
from: segment.from, from: segment.from,
to: segment.to to: segment.to,
labeled: segment.labeled,
deleted: segment.deleted
})); }));
var payload = { const payload = {
id, id,
addedSegments: getJSONs(addedSegments), addedSegments: getJSONs(addedSegments),
removedSegments: removedSegments.getSegments().map(s => s.id) removedSegments: removedSegments.getSegments().map(s => s.id)
}; };
var data = await this.patch('/segments', payload); const data = await this.patch('/segments', payload);
if(data.addedIds === undefined) { if(data.addedIds === undefined) {
throw new Error('Server didn`t send addedIds'); throw new Error('Server didn`t send addedIds');
} }
@ -158,6 +160,10 @@ export class AnalyticService {
return this.patch('/analyticUnits', updateObj); return this.patch('/analyticUnits', updateObj);
} }
async runDetect(id: AnalyticUnitId) {
return this.post('/analyticUnits/detect', { id });
}
private async _analyticRequest(method: string, url: string, data?: any) { private async _analyticRequest(method: string, url: string, data?: any) {
try { try {
method = method.toUpperCase(); method = method.toUpperCase();

12
tests/analytic_controller.jest.ts

@ -9,7 +9,7 @@ describe('AnalyticController', function () {
it('should create analytic units with colors from palette', async function () { it('should create analytic units with colors from palette', async function () {
for (let color of ANALYTIC_UNIT_COLORS) { for (let color of ANALYTIC_UNIT_COLORS) {
analyticController.createNew(); analyticController.createNew();
expect(analyticController.newAnalyticUnit.color).toBe(color); expect(analyticController.newAnalyticUnit.labeledColor).toBe(color);
await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, ''); await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, '');
} }
}); });
@ -29,19 +29,19 @@ describe('AnalyticController', function () {
let auArray = analyticController.analyticUnits; let auArray = analyticController.analyticUnits;
analyticController.createNew(); analyticController.createNew();
await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, ''); await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, '');
expect(auArray[auArray.length - 2].panelObject.color).not.toBe(auArray[auArray.length - 1].panelObject.color); expect(auArray[auArray.length - 2].panelObject.labeledColor).not.toBe(auArray[auArray.length - 1].panelObject.labeledColor);
}); });
it('should set different color to newly created Analytic Unit, afer LAST AU was deleted', async function () { it('should set different color to newly created Analytic Unit, after LAST AU was deleted', async function () {
let auArray = analyticController.analyticUnits; let auArray = analyticController.analyticUnits;
auArray.splice(-1, 1); auArray.splice(-1, 1);
analyticController.createNew(); analyticController.createNew();
await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, ''); await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, '');
expect(auArray[auArray.length - 2].panelObject.color).not.toBe(auArray[auArray.length - 1].panelObject.color); expect(auArray[auArray.length - 2].panelObject.labeledColor).not.toBe(auArray[auArray.length - 1].panelObject.labeledColor);
}); });
it('should change color on choosing from palette', function () { it('should change color on choosing from palette', function () {
analyticController.onAnalyticUnitColorChange('1', 'red'); analyticController.onAnalyticUnitColorChange('1', 'red', false);
expect(analyticController.analyticUnits[0].color).toBe('red'); expect(analyticController.analyticUnits[0].labeledColor).toBe('red');
}); });
}); });

Loading…
Cancel
Save