Browse Source

UI improvements (#310)

* disable Add Analytic Unit button

* width fixes

* cancel word instead of icon

* Save button

* Cancel creation

* Disable Detect if not saved
master
rozetko 6 years ago committed by GitHub
parent
commit
d965d0b00f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      src/panel/graph_panel/controllers/analytic_controller.ts
  2. 10
      src/panel/graph_panel/graph_ctrl.ts
  3. 4
      src/panel/graph_panel/models/analytic_units/analytic_unit.ts
  4. 162
      src/panel/graph_panel/partials/tab_analytics.html

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

@ -103,6 +103,11 @@ export class AnalyticController {
} }
} }
cancelCreation() {
delete this._newAnalyticUnit;
this._creatingNewAnalyticUnit = false;
}
async saveNew(metric: MetricExpanded, datasource: DatasourceRequest) { async saveNew(metric: MetricExpanded, datasource: DatasourceRequest) {
this._savingNewAnalyticUnit = true; this._savingNewAnalyticUnit = true;
const newAnalyticUnit = createAnalyticUnit(this._newAnalyticUnit.toJSON()); const newAnalyticUnit = createAnalyticUnit(this._newAnalyticUnit.toJSON());
@ -215,7 +220,7 @@ export class AnalyticController {
} else { } else {
analyticUnit.labeledColor = value; analyticUnit.labeledColor = value;
} }
await this.saveAnalyticUnit(analyticUnit); analyticUnit.changed = true;
} }
fetchAnalyticUnitsStatuses() { fetchAnalyticUnitsStatuses() {
@ -474,6 +479,10 @@ export class AnalyticController {
await this._analyticService.setAnalyticUnitAlert(analyticUnit); await this._analyticService.setAnalyticUnitAlert(analyticUnit);
} }
toggleAnalyticUnitChange(analyticUnit: AnalyticUnit, value: boolean): void {
analyticUnit.changed = value;
}
async saveAnalyticUnit(analyticUnit: AnalyticUnit): Promise<void> { async saveAnalyticUnit(analyticUnit: AnalyticUnit): Promise<void> {
if(analyticUnit.id === null || analyticUnit.id === undefined) { if(analyticUnit.id === null || analyticUnit.id === undefined) {
throw new Error('Cannot save analytic unit without id'); throw new Error('Cannot save analytic unit without id');
@ -482,6 +491,7 @@ export class AnalyticController {
analyticUnit.saving = true; analyticUnit.saving = true;
await this._analyticService.updateAnalyticUnit(analyticUnit.toJSON()); await this._analyticService.updateAnalyticUnit(analyticUnit.toJSON());
analyticUnit.saving = false; analyticUnit.saving = false;
analyticUnit.changed = false;
} }
async getAnalyticUnits(): Promise<any[]> { async getAnalyticUnits(): Promise<any[]> {
@ -671,14 +681,14 @@ export class AnalyticController {
return this._tempIdCounted.toString(); return this._tempIdCounted.toString();
} }
public async toggleVisibility(id: AnalyticUnitId, value?: boolean) { public toggleVisibility(id: AnalyticUnitId, value?: boolean) {
const analyticUnit = this._analyticUnitsSet.byId(id); const analyticUnit = this._analyticUnitsSet.byId(id);
if(value !== undefined) { if(value !== undefined) {
analyticUnit.visible = value; analyticUnit.visible = value;
} else { } else {
analyticUnit.visible = !analyticUnit.visible; analyticUnit.visible = !analyticUnit.visible;
} }
await this.saveAnalyticUnit(analyticUnit); analyticUnit.changed = true;
} }
public toggleInspect(id: AnalyticUnitId) { public toggleInspect(id: AnalyticUnitId) {
@ -692,7 +702,7 @@ export class AnalyticController {
if(value !== undefined) { if(value !== undefined) {
analyticUnit.seasonalityPeriod.value = value; analyticUnit.seasonalityPeriod.value = value;
} }
await this.saveAnalyticUnit(analyticUnit); analyticUnit.changed = true;
} }
public onAnalyticUnitDetectorChange(analyticUnitTypes: any) { public onAnalyticUnitDetectorChange(analyticUnitTypes: any) {

10
src/panel/graph_panel/graph_ctrl.ts

@ -562,6 +562,10 @@ class GraphCtrl extends MetricsPanelCtrl {
this.analyticsController.createNew(); this.analyticsController.createNew();
} }
cancelCreation() {
this.analyticsController.cancelCreation();
}
redetectAll() { redetectAll() {
this.analyticsController.redetectAll(); this.analyticsController.redetectAll();
} }
@ -600,7 +604,11 @@ class GraphCtrl extends MetricsPanelCtrl {
await this.analyticsController.toggleAnalyticUnitAlert(analyticUnit); await this.analyticsController.toggleAnalyticUnitAlert(analyticUnit);
} }
async onAnalyticUnitChange(analyticUnit: AnalyticUnit) { onAnalyticUnitChange(analyticUnit: AnalyticUnit) {
this.analyticsController.toggleAnalyticUnitChange(analyticUnit, true);
}
async onAnalyticUnitSave(analyticUnit: AnalyticUnit) {
await this.analyticsController.saveAnalyticUnit(analyticUnit); await this.analyticsController.saveAnalyticUnit(analyticUnit);
this.refresh(); this.refresh();
} }

4
src/panel/graph_panel/models/analytic_units/analytic_unit.ts

@ -60,6 +60,7 @@ export class AnalyticUnit {
private _segmentSet = new SegmentArray<AnalyticSegment>(); private _segmentSet = new SegmentArray<AnalyticSegment>();
private _detectionSpans: DetectionSpan[]; private _detectionSpans: DetectionSpan[];
private _inspect = false; private _inspect = false;
private _changed = false;
private _status: string; private _status: string;
private _error: string; private _error: string;
@ -117,6 +118,9 @@ export class AnalyticUnit {
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; }
get changed(): boolean { return this._changed; }
set changed(value: boolean) { this._changed = value; }
get inspect(): boolean { return this._inspect; } get inspect(): boolean { return this._inspect; }
set inspect(value: boolean) { this._inspect = value; } set inspect(value: boolean) { this._inspect = value; }

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

@ -37,7 +37,7 @@
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-5"> Type </label> <label class="gf-form-label width-5"> Type </label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input width-10" <select class="gf-form-input width-9"
ng-model="analyticUnit.type" ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.analyticUnitTypes[analyticUnit.detectorType]" ng-options="type.value as type.name for type in ctrl.analyticUnitTypes[analyticUnit.detectorType]"
ng-disabled="true" ng-disabled="true"
@ -73,7 +73,7 @@
class="gf-form" class="gf-form"
style="margin-bottom: 0" style="margin-bottom: 0"
label="Inspect" label="Inspect"
label-class="width-7" label-class="width-5"
on-change="ctrl.onToggleInspect(analyticUnit.id)" on-change="ctrl.onToggleInspect(analyticUnit.id)"
checked="analyticUnit.inspect" checked="analyticUnit.inspect"
/> />
@ -127,57 +127,24 @@
</label> </label>
</div> </div>
<div class="gf-form"> <div class="gf-form" ng-if="!analyticUnit.selected">
<label class="gf-form-label"> <a
<a class="btn btn-danger"
ng-if="!analyticUnit.selected" ng-click="ctrl.onRemove(analyticUnit.id)"
ng-click="ctrl.onRemove(analyticUnit.id)" >
class="pointer" <i class="fa fa-trash"></i>
> </a>
<i class="fa fa-trash"></i> </div>
</a>
<div class="gf-form" ng-if="analyticUnit.selected">
<div class="gf-form-label">
<a <a
ng-if="analyticUnit.selected"
ng-click="ctrl.onCancelLabeling(analyticUnit.id)"
class="pointer" class="pointer"
ng-click="ctrl.onCancelLabeling(analyticUnit.id)"
> >
<i class="fa fa-ban"></i> Cancel
</a>
</label>
</div>
<div class="gf-form" ng-if="analyticUnit.selected && !analyticUnit.saving">
<!-- Standard way to add conditions to ng-style: https://stackoverflow.com/a/29470439 -->
<label
class="gf-form-label"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
<a class="pointer"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
<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>
</label>
</div>
<div class="gf-form"
ng-if="
analyticUnit.detectorType === 'threshold' ||
(analyticUnit.detectorType === 'anomaly' && !analyticUnit.hasSeasonality)
"
>
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.runDetectInCurrentRange(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>
</label> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form">
@ -195,16 +162,16 @@
<!-- TODO: move analytic-unit-specific fields rendering to class --> <!-- TODO: move analytic-unit-specific fields rendering to class -->
<div class="gf-form" ng-if="analyticUnit.detectorType === 'threshold'"> <div class="gf-form" ng-if="analyticUnit.detectorType === 'threshold'">
<label class="gf-form-label width-5"> Condition </label> <label class="gf-form-label width-5"> Condition </label>
<select class="gf-form-input width-5" <select class="gf-form-input"
ng-class="{ ng-class="{
'width-5': analyticUnit.condition !== 'NO_DATA', 'width-5': analyticUnit.condition !== 'NO_DATA',
'width-10': analyticUnit.condition === 'NO_DATA' 'width-9': analyticUnit.condition === 'NO_DATA'
}" }"
ng-model="analyticUnit.condition" ng-model="analyticUnit.condition"
ng-options="type for type in ctrl.analyticsController.conditions" ng-options="type for type in ctrl.analyticsController.conditions"
ng-change="ctrl.onAnalyticUnitChange(analyticUnit)" ng-change="ctrl.onAnalyticUnitChange(analyticUnit)"
/> />
<input class="gf-form-input width-5" <input class="gf-form-input width-4"
ng-if="analyticUnit.condition !== 'NO_DATA'" ng-if="analyticUnit.condition !== 'NO_DATA'"
type="number" type="number"
ng-model="analyticUnit.value" ng-model="analyticUnit.value"
@ -212,10 +179,9 @@
/> />
</div> </div>
<div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly' && !analyticUnit.selected"> <div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly'">
<label class="gf-form-label width-5"> Alpha </label> <label class="gf-form-label width-5"> Alpha </label>
<input class="gf-form-input width-10" <input class="gf-form-input width-9"
ng-hide="analyticUnit.selected"
min="0" min="0"
max="1" max="1"
type="number" type="number"
@ -224,7 +190,7 @@
/> />
</div> </div>
<div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly' && !analyticUnit.selected"> <div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly'">
<label class="gf-form-label width-6"> Confidence </label> <label class="gf-form-label width-6"> Confidence </label>
<input class="gf-form-input width-5" <input class="gf-form-input width-5"
min="0" min="0"
@ -234,7 +200,7 @@
/> />
</div> </div>
<div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly' && !analyticUnit.selected"> <div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly'">
<!-- TODO: Remove hack with "margin-bottom: 0" --> <!-- TODO: Remove hack with "margin-bottom: 0" -->
<gf-form-switch <gf-form-switch
class="gf-form" class="gf-form"
@ -248,7 +214,7 @@
<div <div
class="gf-form" class="gf-form"
ng-if="analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality && !analyticUnit.selected" ng-if="analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality"
> >
<label class="gf-form-label width-9"> Seasonality Period </label> <label class="gf-form-label width-9"> Seasonality Period </label>
<input <input
@ -263,8 +229,7 @@
<div class="gf-form" <div class="gf-form"
ng-if=" ng-if="
analyticUnit.detectorType === 'anomaly' && analyticUnit.detectorType === 'anomaly' &&
analyticUnit.hasSeasonality && analyticUnit.hasSeasonality
!analyticUnit.selected
" "
> >
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
@ -277,28 +242,75 @@
</div> </div>
</div> </div>
</div> </div>
<div class="gf-form-inline">
<div class="gf-form width-20"/>
<div class="gf-form">
<button class="btn btn-secondary"
ng-click="ctrl.onAnalyticUnitSave(analyticUnit)"
ng-disabled="!analyticUnit.changed"
>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="!analyticUnit.saving"> Save </b>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</button>
</div>
<!-- TODO: Leave one Detect button instead of 2 -->
<div class="gf-form"
ng-if="
analyticUnit.detectorType === 'pattern' ||
(analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality)
"
>
<button class="btn btn-secondary"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING' || analyticUnit.saving || analyticUnit.changed || !analyticUnit.segments.length"
>
<b> Detect </b>
</button>
</div>
<div class="gf-form"
ng-if="
analyticUnit.detectorType === 'threshold' ||
(analyticUnit.detectorType === 'anomaly' && !analyticUnit.hasSeasonality)
"
>
<button class="btn btn-secondary"
ng-click="ctrl.runDetectInCurrentRange(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING' || analyticUnit.saving || analyticUnit.changed"
>
<b> Detect </b>
</button>
</div>
</div>
</div> </div>
<div class="editor-row" ng-if="ctrl.analyticsController.creatingNew"> <div class="gf-form-inline" ng-if="ctrl.analyticsController.creatingNew">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-5"> Name </label> <label class="gf-form-label width-5"> Name </label>
<input <input
type="text" class="gf-form-input width-15" type="text" class="gf-form-input width-15"
ng-model="ctrl.analyticsController.newAnalyticUnit.name" ng-model="ctrl.analyticsController.newAnalyticUnit.name"
> >
</div>
<div class="gf-form">
<label class="gf-form-label width-8"> Detector Type </label> <label class="gf-form-label width-8"> Detector Type </label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input width-7" <select class="gf-form-input width-8"
ng-model="ctrl.analyticsController.newAnalyticUnit.detectorType" ng-model="ctrl.analyticsController.newAnalyticUnit.detectorType"
ng-options="analyticUnitDetectorType for analyticUnitDetectorType in ctrl.analyticUnitDetectorTypes" ng-options="analyticUnitDetectorType for analyticUnitDetectorType in ctrl.analyticUnitDetectorTypes"
ng-change="ctrl.analyticsController.onAnalyticUnitDetectorChange(ctrl.analyticUnitTypes);" ng-change="ctrl.analyticsController.onAnalyticUnitDetectorChange(ctrl.analyticUnitTypes);"
/> />
</div> </div>
</div>
<div class="gf-form">
<label class="gf-form-label width-5"> Type </label> <label class="gf-form-label width-5"> Type </label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input width-10" <select class="gf-form-input width-9"
ng-model="ctrl.analyticsController.newAnalyticUnit.type" ng-model="ctrl.analyticsController.newAnalyticUnit.type"
ng-options=" ng-options="
type.value as type.name type.value as type.name
@ -306,18 +318,28 @@
" "
/> />
</div> </div>
</div>
<label class="gf-form-label"> <div class="gf-form">
<a class="pointer" tabindex="1" ng-click="ctrl.saveNew()"> <a class="btn btn-danger" ng-click="ctrl.cancelCreation()">
<b ng-if="!ctrl.analyticsController.saving"> create </b> <b> Cancel </b>
<b ng-if="ctrl.analyticsController.saving" ng-disabled="true"> saving... </b> </a>
</a> </div>
</label>
<div class="gf-form">
<a class="btn btn-secondary" ng-click="ctrl.saveNew()">
<b ng-if="!ctrl.analyticsController.saving"> Create </b>
<b ng-if="ctrl.analyticsController.saving" ng-disabled="true"> Saving... </b>
</a>
</div> </div>
</div> </div>
<div class="gf-form-button-row" ng-if="!ctrl.analyticsController.creatingAnalyticUnit"> <div class="gf-form-button-row">
<button class="btn btn-secondary width-12" ng-click="ctrl.createNew()"> <button
class="btn btn-secondary width-12"
ng-click="ctrl.createNew()"
ng-disabled="ctrl.analyticsController.creatingNew"
>
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
Add Analytic Unit Add Analytic Unit
</button> </button>

Loading…
Cancel
Save