diff --git a/src/panel/graph_panel/controllers/analytic_controller.ts b/src/panel/graph_panel/controllers/analytic_controller.ts index 5310a20..b18c94c 100644 --- a/src/panel/graph_panel/controllers/analytic_controller.ts +++ b/src/panel/graph_panel/controllers/analytic_controller.ts @@ -49,16 +49,16 @@ export class AnalyticController { private _thresholds: Threshold[]; constructor( + private _grafanaUrl: string, + private _panelId: string, private _panelObject: any, private _emitter: Emitter, private _analyticService?: AnalyticService, ) { - if(_panelObject.analyticUnits === undefined) { - _panelObject.analyticUnits = _panelObject.anomalyTypes || []; - } this._labelingDataAddedSegments = new SegmentArray(); this._labelingDataRemovedSegments = new SegmentArray(); - this._analyticUnitsSet = new AnalyticUnitsSet(this._panelObject.analyticUnits); + this._analyticUnitsSet = new AnalyticUnitsSet([]); + this.fetchAnalyticUnits(); this._thresholds = []; this.updateThresholds(); } @@ -99,10 +99,10 @@ export class AnalyticController { } } - async saveNew(metric: MetricExpanded, datasource: DatasourceRequest, panelUrl: string) { + async saveNew(metric: MetricExpanded, datasource: DatasourceRequest) { this._savingNewAnalyticUnit = true; this._newAnalyticUnit.id = await this._analyticService.postNewItem( - this._newAnalyticUnit, metric, datasource, panelUrl + this._newAnalyticUnit, metric, datasource, this._grafanaUrl, this._panelId ); if(this._newAnalyticUnit.detectorType === 'threshold') { await this.saveThreshold(this._newAnalyticUnit.id); @@ -205,15 +205,17 @@ export class AnalyticController { return this._analyticUnitsSet.items; } - onAnalyticUnitColorChange(id: AnalyticUnitId, value: string, deleted: boolean) { + async onAnalyticUnitColorChange(id: AnalyticUnitId, value: string, deleted: boolean) { if(id === undefined) { throw new Error('id is undefined'); } + const analyticUnit = this._analyticUnitsSet.byId(id); if(deleted) { - this._analyticUnitsSet.byId(id).deletedColor = value; + analyticUnit.deletedColor = value; } else { - this._analyticUnitsSet.byId(id).labeledColor = value; + analyticUnit.labeledColor = value; } + await this.saveAnalyticUnit(analyticUnit); } fetchAnalyticUnitsStatuses() { @@ -366,23 +368,39 @@ export class AnalyticController { if(id === this._selectedAnalyticUnitId) { this.dropLabeling(); } - this._analyticUnitsSet.removeItem(id); if(!silent) { await this._analyticService.removeAnalyticUnit(id); } + this._analyticUnitsSet.removeItem(id); } async toggleAnalyticUnitAlert(analyticUnit: AnalyticUnit): Promise { analyticUnit.alert = analyticUnit.alert ? true : false; + // TODO: saveAnalyticUnit instead of specific method await this._analyticService.setAnalyticUnitAlert(analyticUnit); } - async fetchAnalyticUnitName(analyticUnit: AnalyticUnit): Promise { - let updateObj = { - id: analyticUnit.id, - name: analyticUnit.name + async saveAnalyticUnit(analyticUnit: AnalyticUnit): Promise { + if(analyticUnit.id === null || analyticUnit.id === undefined) { + throw new Error('Cannot save analytic unit without id'); + } + + analyticUnit.saving = true; + await this._analyticService.updateAnalyticUnit(analyticUnit.serverObject); + analyticUnit.saving = false; + } + + async getAnalyticUnits(): Promise { + if(this._analyticService === undefined) { + return []; } - await this._analyticService.updateAnalyticUnit(analyticUnit.id, updateObj); + + return this._analyticService.getAnalyticUnits(this._panelId); + } + + async fetchAnalyticUnits(): Promise { + const units = await this.getAnalyticUnits(); + this._analyticUnitsSet = new AnalyticUnitsSet(units); } async updateThresholds(): Promise { @@ -470,13 +488,14 @@ export class AnalyticController { return this._tempIdCounted.toString(); } - public toggleVisibility(id: AnalyticUnitId, value?: boolean) { - var analyticUnit = this._analyticUnitsSet.byId(id); + public async toggleVisibility(id: AnalyticUnitId, value?: boolean) { + const analyticUnit = this._analyticUnitsSet.byId(id); if(value !== undefined) { analyticUnit.visible = value; } else { analyticUnit.visible = !analyticUnit.visible; } + await this.saveAnalyticUnit(analyticUnit); } public onAnalyticUnitDetectorChange(analyticUnitTypes: any) { diff --git a/src/panel/graph_panel/graph_ctrl.ts b/src/panel/graph_panel/graph_ctrl.ts index c2cb56d..e10a2c4 100644 --- a/src/panel/graph_panel/graph_ctrl.ts +++ b/src/panel/graph_panel/graph_ctrl.ts @@ -55,6 +55,9 @@ class GraphCtrl extends MetricsPanelCtrl { private $graphElem: any; private $legendElem: any; + private _grafanaUrl: string; + private _panelId: string; + panelDefaults = { // datasource name, null = default datasource datasource: null, @@ -157,6 +160,17 @@ class GraphCtrl extends MetricsPanelCtrl { // because of https://github.com/hastic/hastic-grafana-app/issues/162 this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); + + + const grafanaUrlRegex = /^(.+)\/d/; + const parsedUrl = window.location.href.match(grafanaUrlRegex); + if(parsedUrl !== null) { + this._grafanaUrl = parsedUrl[1]; + } else { + throw new Error('Cannot parse grafana url'); + } + + this._panelId = `${this.dashboard.uid}/${this.panel.id}`; } rebindKeys() { @@ -305,10 +319,10 @@ class GraphCtrl extends MetricsPanelCtrl { delete this.analyticService; } else { this.analyticService = new AnalyticService(hasticDatasource.url, this.$http); - this.runDatasourceConnectivityCheck(); + this.runDatasourceConnectivityCheck(); } - this.analyticsController = new AnalyticController(this.panel, this.events, this.analyticService); + this.analyticsController = new AnalyticController(this._grafanaUrl, this._panelId, this.panel, this.events, this.analyticService); this.analyticsController.fetchAnalyticUnitsStatuses(); this._updatePanelInfo(); @@ -559,15 +573,11 @@ class GraphCtrl extends MetricsPanelCtrl { async saveNew() { try { - const panelId = this.panel.id; - const panelUrl = window.location.origin + window.location.pathname + `?panelId=${panelId}`; - const datasource = await this._getDatasourceRequest(); await this.analyticsController.saveNew( new MetricExpanded(this.panel.datasource, this.panel.targets), - datasource, - panelUrl + datasource ); } catch(e) { appEvents.emit( @@ -582,20 +592,21 @@ class GraphCtrl extends MetricsPanelCtrl { this.render(this.seriesList); } - onAnalyticUnitAlertChange(analyticUnit: AnalyticUnit) { - this.analyticsController.toggleAnalyticUnitAlert(analyticUnit); + async onAnalyticUnitAlertChange(analyticUnit: AnalyticUnit) { + await this.analyticsController.toggleAnalyticUnitAlert(analyticUnit); } - onAnalyticUnitNameChange(analyticUnit: AnalyticUnit) { - this.analyticsController.fetchAnalyticUnitName(analyticUnit); + async onAnalyticUnitNameChange(analyticUnit: AnalyticUnit) { + await this.analyticsController.saveAnalyticUnit(analyticUnit); + this.refresh(); } - onColorChange(id: AnalyticUnitId, deleted: boolean, value: string) { + async onColorChange(id: AnalyticUnitId, deleted: boolean, value: string) { if(id === undefined) { throw new Error('id is undefined'); } - this.analyticsController.onAnalyticUnitColorChange(id, value, deleted); - this.render(); + await this.analyticsController.onAnalyticUnitColorChange(id, value, deleted); + this.refresh(); } async onRemove(id: AnalyticUnitId) { @@ -603,7 +614,7 @@ class GraphCtrl extends MetricsPanelCtrl { throw new Error('id is undefined'); } await this.analyticsController.removeAnalyticUnit(id); - this.render(); + this.refresh(); } onCancelLabeling(id: AnalyticUnitId) { @@ -650,7 +661,7 @@ class GraphCtrl extends MetricsPanelCtrl { onToggleVisibility(id: AnalyticUnitId) { this.analyticsController.toggleVisibility(id); - this.render(); + this.refresh(); } private async _updatePanelInfo() { @@ -658,7 +669,7 @@ class GraphCtrl extends MetricsPanelCtrl { if(this.panel.datasource) { datasource = await this._getDatasourceByName(this.panel.datasource); } - + const hasticDatasource = this.getHasticDatasource(); let grafanaVersion = 'unknown'; diff --git a/src/panel/graph_panel/models/analytic_unit.ts b/src/panel/graph_panel/models/analytic_unit.ts index 85e70c8..1dfb366 100644 --- a/src/panel/graph_panel/models/analytic_unit.ts +++ b/src/panel/graph_panel/models/analytic_unit.ts @@ -7,6 +7,11 @@ import { ANALYTIC_UNIT_COLORS, DEFAULT_DELETED_SEGMENT_COLOR } from '../colors'; import _ from 'lodash'; +export enum DetectorType { + PATTERN = 'pattern', + THRESHOLD = 'threshold' +}; + export enum LabelingMode { LABELING = 'LABELING', UNLABELING = 'UNLABELING', @@ -22,7 +27,7 @@ export type AnalyticUnitId = string; export class AnalyticSegment extends Segment { constructor(public labeled: boolean, id: SegmentId, from: number, to: number, public deleted = false) { super(id, from, to); - if(!_.isBoolean(labeled)) { + if(!_.isBoolean(this.labeled)) { throw new Error('labeled value is not boolean'); } } @@ -37,43 +42,44 @@ export class AnalyticUnit { private _status: string; private _error: string; - constructor(private _panelObject?: any) { - if(_panelObject === undefined) { - this._panelObject = {}; - } - _.defaults(this._panelObject, { + constructor(private _serverObject?: any) { + const defaults = { name: 'AnalyticUnitName', labeledColor: ANALYTIC_UNIT_COLORS[0], deletedColor: DEFAULT_DELETED_SEGMENT_COLOR, - detectorType: 'pattern', + detectorType: DetectorType.PATTERN, type: 'GENERAL', - alert: false - }); - } + alert: false, + id: null, + visible: true + } - get id(): AnalyticUnitId { return this._panelObject.id; } - set id(value: AnalyticUnitId) { this._panelObject.id = value; } + if(_serverObject === undefined) { + this._serverObject = defaults; + } + _.defaults(this._serverObject, defaults); + } - set name(value: string) { this._panelObject.name = value; } - get name(): string { return this._panelObject.name; } + get id(): AnalyticUnitId { return this._serverObject.id; } + set id(value: AnalyticUnitId) { this._serverObject.id = value; } - set detectorType(value: string) { this._panelObject.detectorType = value; } - get detectorType(): string { return this._panelObject.detectorType; } + set name(value: string) { this._serverObject.name = value; } + get name(): string { return this._serverObject.name; } - set type(value: string) { this._panelObject.type = value; } - get type(): string { return this._panelObject.type; } + set detectorType(value: DetectorType) { this._serverObject.detectorType = value; } + get detectorType(): DetectorType { return this._serverObject.detectorType; } - set confidence(value: number) { this._panelObject.confidence = value; } - get confidence(): number { return this._panelObject.confidence; } + set type(value: string) { this._serverObject.type = value; } + get type(): string { return this._serverObject.type; } - set labeledColor(value: string) { this._panelObject.labeledColor = value; } - get labeledColor(): string { return this._panelObject.labeledColor; } + set labeledColor(value: string) { this._serverObject.labeledColor = value; } + get labeledColor(): string { return this._serverObject.labeledColor; } - set deletedColor(value: string) { this._panelObject.deletedColor = value; } - get deletedColor(): string { return this._panelObject.deletedColor; } + set deletedColor(value: string) { this._serverObject.deletedColor = value; } + get deletedColor(): string { return this._serverObject.deletedColor; } - set alert(value: boolean) { this._panelObject.alert = value; } - get alert(): boolean { return this._panelObject.alert; } + set alert(value: boolean) { this._serverObject.alert = value; } + get alert(): boolean { return this._serverObject.alert; } get selected(): boolean { return this._selected; } set selected(value: boolean) { this._selected = value; } @@ -85,10 +91,10 @@ export class AnalyticUnit { set saving(value: boolean) { this._saving = value; } get visible(): boolean { - return (this._panelObject.visible === undefined) ? true : this._panelObject.visible + return (this._serverObject.visible === undefined) ? true : this._serverObject.visible } set visible(value: boolean) { - this._panelObject.visible = value; + this._serverObject.visible = value; } addLabeledSegment(segment: Segment, deleted: boolean): AnalyticSegment { @@ -134,7 +140,7 @@ export class AnalyticUnit { return true; } - get panelObject() { return this._panelObject; } + get serverObject() { return this._serverObject; } } @@ -143,26 +149,26 @@ export class AnalyticUnitsSet { private _mapIdIndex: Map; private _items: AnalyticUnit[]; - constructor(private _panelObject: any[]) { - if(_panelObject === undefined) { - throw new Error('panel object can`t be undefined'); + constructor(private _serverObject: any[]) { + if(_serverObject === undefined) { + throw new Error('server object can`t be undefined'); } this._mapIdIndex = new Map(); - this._items = _panelObject.map(p => new AnalyticUnit(p)); + this._items = _serverObject.map(p => new AnalyticUnit(p)); this._rebuildIndex(); } get items() { return this._items; } addItem(item: AnalyticUnit) { - this._panelObject.push(item.panelObject); + 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._panelObject.splice(index, 1); + this._serverObject.splice(index, 1); this._items.splice(index, 1); this._rebuildIndex(); } diff --git a/src/panel/graph_panel/partials/tab_analytics.html b/src/panel/graph_panel/partials/tab_analytics.html index 26533e2..3037700 100644 --- a/src/panel/graph_panel/partials/tab_analytics.html +++ b/src/panel/graph_panel/partials/tab_analytics.html @@ -30,7 +30,7 @@ diff --git a/src/panel/graph_panel/services/analytic_service.ts b/src/panel/graph_panel/services/analytic_service.ts index 9dcc73f..4eaf684 100644 --- a/src/panel/graph_panel/services/analytic_service.ts +++ b/src/panel/graph_panel/services/analytic_service.ts @@ -9,6 +9,7 @@ import { Threshold } from '../models/threshold'; import { appEvents } from 'grafana/app/core/core'; + export class AnalyticService { private _isUp: boolean = false; @@ -25,6 +26,14 @@ export class AnalyticService { return this.get('/analyticUnits/types'); } + async getAnalyticUnits(panelId: string) { + const resp = await this.get('/analyticUnits/units', { panelId }); + if(resp === undefined) { + return []; + } + return resp.analyticUnits; + } + async getThresholds(ids: AnalyticUnitId[]) { const resp = await this.get('/threshold', { ids: ids.join(',') }); if(resp === undefined) { @@ -41,10 +50,12 @@ export class AnalyticService { newItem: AnalyticUnit, metric: MetricExpanded, datasource: DatasourceRequest, - panelUrl: string + grafanaUrl: string, + panelId: string ): Promise { const response = await this.post('/analyticUnits', { - panelUrl, + grafanaUrl, + panelId, type: newItem.type, name: newItem.name, metric: metric.toJSON(), @@ -174,8 +185,7 @@ export class AnalyticService { }); } - async updateAnalyticUnit(id: AnalyticUnitId, updateObj: any) { - updateObj.id = id; + async updateAnalyticUnit(updateObj: any) { return this.patch('/analyticUnits', updateObj); } diff --git a/tests/analytic_controller.jest.ts b/tests/analytic_controller.jest.ts index c7618ac..31ccda6 100644 --- a/tests/analytic_controller.jest.ts +++ b/tests/analytic_controller.jest.ts @@ -10,7 +10,7 @@ describe('AnalyticController', function () { for (let color of ANALYTIC_UNIT_COLORS) { analyticController.createNew(); expect(analyticController.newAnalyticUnit.labeledColor).toBe(color); - await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, ''); + await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest); } }); @@ -28,16 +28,16 @@ describe('AnalyticController', function () { it('should set different color to newly created Analytic Unit, afer NOT last AU was deleted', async function() { let auArray = analyticController.analyticUnits; analyticController.createNew(); - await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, ''); - expect(auArray[auArray.length - 2].panelObject.labeledColor).not.toBe(auArray[auArray.length - 1].panelObject.labeledColor); + await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest); + expect(auArray[auArray.length - 2].serverObject.labeledColor).not.toBe(auArray[auArray.length - 1].serverObject.labeledColor); }); it('should set different color to newly created Analytic Unit, after LAST AU was deleted', async function () { let auArray = analyticController.analyticUnits; auArray.splice(-1, 1); analyticController.createNew(); - await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest, ''); - expect(auArray[auArray.length - 2].panelObject.labeledColor).not.toBe(auArray[auArray.length - 1].panelObject.labeledColor); + await analyticController.saveNew({} as MetricExpanded, {} as DatasourceRequest); + expect(auArray[auArray.length - 2].serverObject.labeledColor).not.toBe(auArray[auArray.length - 1].serverObject.labeledColor); }); it('should change color on choosing from palette', function () { diff --git a/tests/setup_tests.ts b/tests/setup_tests.ts index ba85c9b..88e9764 100644 --- a/tests/setup_tests.ts +++ b/tests/setup_tests.ts @@ -25,7 +25,7 @@ analyticService.postNewItem = async function ( return Promise.resolve(id.toString()); } -export const analyticController = new AnalyticController({}, new Emitter(), analyticService); +export const analyticController = new AnalyticController('http://localhost:3000', '13Qdb1jmz', {}, new Emitter(), analyticService); jest.mock('../src/panel/graph_panel/partials/help_section.html', () => '');