Browse Source

Analytics units details view #282 (#329)

- make `Analytics` tab UI identical to `Metrics` tab
- split analytic unit view for Grafana 5.x and 6.x
- support analytic unit collapsing
master
rozetko 6 years ago committed by GitHub
parent
commit
9e77b33bab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      src/panel/graph_panel/controllers/analytic_controller.ts
  2. 49
      src/panel/graph_panel/graph_ctrl.ts
  3. 7
      src/panel/graph_panel/models/analytic_units/analytic_unit.ts
  4. 148
      src/panel/graph_panel/partials/analytic_unit.html
  5. 158
      src/panel/graph_panel/partials/analytic_units_5.x.html
  6. 155
      src/panel/graph_panel/partials/analytic_units_6.x.html
  7. 43
      src/panel/graph_panel/partials/new_analytic_unit.html
  8. 11
      src/panel/graph_panel/partials/new_analytic_unit_5.x.html
  9. 29
      src/panel/graph_panel/partials/new_analytic_unit_6.x.html
  10. 322
      src/panel/graph_panel/partials/tab_analytics.html

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

@ -706,6 +706,15 @@ export class AnalyticController {
this.analyticUnits this.analyticUnits
.filter(analyticUnit => analyticUnit.id !== id) .filter(analyticUnit => analyticUnit.id !== id)
.forEach(unit => unit.inspect = false); .forEach(unit => unit.inspect = false);
const analyticUnit = this._analyticUnitsSet.byId(id);
analyticUnit.inspect = !analyticUnit.inspect;
}
public toggleCollapsed(id: AnalyticUnitId) {
const analyticUnit = this._analyticUnitsSet.byId(id);
analyticUnit.collapsed = !analyticUnit.collapsed;
analyticUnit.changed = true;
} }
public async updateSeasonality(id: AnalyticUnitId, value?: number) { public async updateSeasonality(id: AnalyticUnitId, value?: number) {

49
src/panel/graph_panel/graph_ctrl.ts

@ -292,13 +292,12 @@ class GraphCtrl extends MetricsPanelCtrl {
this.rebindKeys(); // 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'; this.addEditorTab('Analytics', `${this.partialsPath}/tab_analytics.html`, 2);
this.addEditorTab('Analytics', `${partialPath}/tab_analytics.html`, 2); this.addEditorTab('Webhooks', `${this.partialsPath}/tab_webhooks.html`, 3);
this.addEditorTab('Webhooks', `${partialPath}/tab_webhooks.html`, 3);
this.addEditorTab('Axes', axesEditorComponent, 4); this.addEditorTab('Axes', axesEditorComponent, 4);
this.addEditorTab('Legend', `${partialPath}/tab_legend.html`, 5); this.addEditorTab('Legend', `${this.partialsPath}/tab_legend.html`, 5);
this.addEditorTab('Display', `${partialPath}/tab_display.html`, 6); this.addEditorTab('Display', `${this.partialsPath}/tab_display.html`, 6);
this.addEditorTab('Hastic info', `${partialPath}/tab_info.html`, 7); this.addEditorTab('Hastic info', `${this.partialsPath}/tab_info.html`, 7);
this.subTabIndex = 0; this.subTabIndex = 0;
} }
@ -555,7 +554,32 @@ class GraphCtrl extends MetricsPanelCtrl {
} }
get panelPath() { get panelPath() {
return this.pluginPath + '/panel/graph_panel'; return `${this.pluginPath}/panel/graph_panel`;
}
get partialsPath() {
return `${this.panelPath}/partials`;
}
get grafanaVersion() {
if(_.has(window, 'grafanaBootData.settings.buildInfo.version')) {
return window.grafanaBootData.settings.buildInfo.version;
}
return null;
}
getTemplatePath(filename: string) {
const grafanaVersion = this.grafanaVersion;
if(grafanaVersion === null) {
throw new Error('Unknown Grafana version');
}
if(grafanaVersion[0] === '5') {
return `${this.partialsPath}/${filename}_5.x.html`;
}
if(grafanaVersion[0] === '6') {
return `${this.partialsPath}/${filename}_6.x.html`;
}
throw new Error(`Unsupported Grafana version: ${grafanaVersion}`);
} }
createNew() { createNew() {
@ -681,6 +705,11 @@ class GraphCtrl extends MetricsPanelCtrl {
this.refresh(); this.refresh();
} }
onToggleCollapsed(id: AnalyticUnitId) {
this.analyticsController.toggleCollapsed(id);
this.refresh();
}
onSeasonalityChange(id: AnalyticUnitId, value?: number) { onSeasonalityChange(id: AnalyticUnitId, value?: number) {
this.analyticsController.updateSeasonality(id, value); this.analyticsController.updateSeasonality(id, value);
this.refresh(); this.refresh();
@ -694,9 +723,9 @@ class GraphCtrl extends MetricsPanelCtrl {
const hasticDatasource = this.hasticDatasource; const hasticDatasource = this.hasticDatasource;
let grafanaVersion = 'unknown'; let grafanaVersion = this.grafanaVersion;
if(_.has(window, 'grafanaBootData.settings.buildInfo.version')) { if(grafanaVersion === null) {
grafanaVersion = window.grafanaBootData.settings.buildInfo.version; grafanaVersion = 'unknown';
} }
this._panelInfo = { this._panelInfo = {
grafanaVersion, grafanaVersion,

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

@ -47,7 +47,8 @@ const DEFAULTS = {
labeledColor: ANALYTIC_UNIT_COLORS[0], labeledColor: ANALYTIC_UNIT_COLORS[0],
deletedColor: DEFAULT_DELETED_SEGMENT_COLOR, deletedColor: DEFAULT_DELETED_SEGMENT_COLOR,
alert: false, alert: false,
visible: true visible: true,
collapsed: false
}; };
const LABELING_MODES = []; const LABELING_MODES = [];
@ -85,6 +86,7 @@ export class AnalyticUnit {
deletedColor: this.deletedColor, deletedColor: this.deletedColor,
alert: this.alert, alert: this.alert,
visible: this.visible, visible: this.visible,
collapsed: this.collapsed
}; };
} }
@ -106,6 +108,9 @@ export class AnalyticUnit {
set deletedColor(value: string) { this._serverObject.deletedColor = value; } set deletedColor(value: string) { this._serverObject.deletedColor = value; }
get deletedColor(): string { return this._serverObject.deletedColor; } get deletedColor(): string { return this._serverObject.deletedColor; }
get collapsed(): boolean { return this._serverObject.collapsed; }
set collapsed(value: boolean) { this._serverObject.collapsed = value; }
set alert(value: boolean) { this._serverObject.alert = value; } set alert(value: boolean) { this._serverObject.alert = value; }
get alert(): boolean { return this._serverObject.alert; } get alert(): boolean { return this._serverObject.alert; }

148
src/panel/graph_panel/partials/analytic_unit.html

@ -0,0 +1,148 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8">
Name
</label>
<input
type="text" class="gf-form-input width-16"
ng-model="analyticUnit.name"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-9"
ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.analyticUnitTypes[analyticUnit.detectorType]"
ng-disabled="true"
/>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Positive Color </label>
<label class="gf-form-label">
<color-picker
color="analyticUnit.labeledColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, false)"
/>
</label>
</div>
<div class="gf-form"
ng-if="analyticUnit.detectorType === 'pattern' || analyticUnit.detectorType === 'anomaly'"
>
<label class="gf-form-label query-keyword"> Negative Color </label>
<label class="gf-form-label">
<color-picker
color="analyticUnit.deletedColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, true)"
/>
</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="analyticUnit.detectorType === 'threshold'">
<!-- TODO: move analytic-unit-specific fields rendering to class -->
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Condition </label>
<select class="gf-form-input"
ng-class="{
'width-5': analyticUnit.condition !== 'NO_DATA',
'width-9': analyticUnit.condition === 'NO_DATA'
}"
ng-model="analyticUnit.condition"
ng-options="type for type in ctrl.analyticsController.conditions"
ng-change="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
<input class="gf-form-input width-4"
ng-if="analyticUnit.condition !== 'NO_DATA'"
type="number"
ng-model="analyticUnit.value"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="analyticUnit.detectorType === 'anomaly'">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Alpha </label>
<input class="gf-form-input width-9"
min="0"
max="1"
type="number"
ng-model="analyticUnit.alpha"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-6"> Confidence </label>
<input class="gf-form-input width-5"
min="0"
type="number"
ng-model="analyticUnit.confidence"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="analyticUnit.detectorType === 'anomaly'">
<div class="gf-form">
<gf-form-switch
class="gf-form"
label="Seasonality"
label-class="query-keyword width-8"
on-change="ctrl.onAnalyticUnitChange(analyticUnit)"
checked="analyticUnit.hasSeasonality"
/>
</div>
<div class="gf-form" ng-if="analyticUnit.hasSeasonality">
<label class="gf-form-label query-keyword width-9"> Seasonality Period </label>
<input
type="number" class="gf-form-input width-5"
ng-init="seasonalityValue = analyticUnit.seasonalityPeriod.value"
ng-model="seasonalityValue"
ng-blur="ctrl.onSeasonalityChange(analyticUnit.id, seasonalityValue)"
min="0"
>
</div>
<div class="gf-form" ng-if="analyticUnit.hasSeasonality">
<div class="gf-form-select-wrapper">
<!-- TODO: move periods from ng-options -->
<select class="gf-form-input width-8"
ng-model="analyticUnit.seasonalityPeriod.unit"
ng-change="ctrl.onSeasonalityChange(analyticUnit.id)"
ng-options="type for type in ['seconds', 'minutes', 'hours', 'days', 'years']"
/>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>

158
src/panel/graph_panel/partials/analytic_units_5.x.html

@ -0,0 +1,158 @@
<div class="gf-form-query" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits">
<div class="gf-form gf-form-query-letter-cell">
<label class="gf-form-label width-25">
<a class="pointer" ng-click="ctrl.onToggleCollapsed(analyticUnit.id)">
<span class="gf-form-query-letter-cell-carret">
<i class="fa fa-caret-down" ng-hide="analyticUnit.collapsed"></i>
<i class="fa fa-caret-right" ng-show="analyticUnit.collapsed"></i>
</span>
<span class="gf-form-query-letter-cell-letter">{{analyticUnit.name}}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="analyticUnit.id">({{analyticUnit.id}})</em>
</a>
&nbsp;
<i ng-if="analyticUnit.status === 'READY'"
class="fa fa-check-circle"
bs-tooltip="'Ready'"
></i>
<i ng-if="analyticUnit.status === 'SUCCESS'"
class="fa fa-check"
bs-tooltip="'Learning succeeded'"
></i>
<i ng-if="analyticUnit.status === 'LEARNING'"
class="fa fa-leanpub"
bs-tooltip="'Learning'"
></i>
<i ng-if="analyticUnit.status === 'DETECTION'"
class="fa fa-search"
bs-tooltip="'Detection'"
></i>
<i ng-if="analyticUnit.status === 'PENDING'"
class="fa fa-list-ul"
bs-tooltip="'Pending'"
></i>
<i ng-if="analyticUnit.status === 'FAILED'"
class="fa fa-exclamation-circle"
bs-tooltip="'Error: ' + analyticUnit.error"
></i>
</label>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="analyticUnit.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.onToggleCollapsed(analyticUnit.id)">
</label>
</div>
</div>
<div class="gf-form-query-content" ng-if="!analyticUnit.collapsed">
<ng-include src="ctrl.partialsPath + '/analytic_unit.html'"></ng-include>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a
ng-if="analyticUnit.visible"
bs-tooltip="'Hide. It`s visible now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
role="menuitem"
>
<i class="fa fa-eye"></i>
</a>
<a
ng-if="!analyticUnit.visible"
bs-tooltip="'Show. It`s hidden now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
role="menuitem"
>
<i class="fa fa-eye-slash"></i>
</a>
</label>
<label class="gf-form-label" bs-tooltip="'Inspect Mode'">
<a class="pointer" ng-click="ctrl.onToggleInspect(analyticUnit.id)">
<i class="fa fa-crosshairs" ng-if="analyticUnit.inspect"></i>
<i class="fa fa-circle-thin" ng-if="!analyticUnit.inspect"></i>
</a>
</label>
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.onRemove(analyticUnit.id)" role="menuitem">
<i class="fa fa-trash"></i>
</a>
</label>
<label class="gf-form-label"
ng-style="!analyticUnit.changed && { 'cursor': 'not-allowed' }"
>
<a class="pointer"
ng-click="ctrl.onAnalyticUnitSave(analyticUnit)"
ng-disabled="!analyticUnit.changed"
ng-style="!analyticUnit.changed && { 'color': 'lightgray' }"
role="menuitem"
>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<i class="fa fa-save"ng-if="!analyticUnit.saving"></i>
</a>
</label>
<div class="gf-form-label" ng-if="
(analyticUnit.detectorType === 'pattern' ||
(analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality)) &&
!analyticUnit.selected
">
<a
class="pointer"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
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>
Label
</a>
</div>
<select class="gf-form-input width-11"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in analyticUnit.labelingModes"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
<label class="gf-form-label" ng-if="analyticUnit.selected && !analyticUnit.saving">
<a class="pointer" ng-click="ctrl.onCancelLabeling(analyticUnit.id)">
<i class="fa fa-ban"></i>
</a>
</label>
<!-- TODO: Leave one Detect button instead of 2 -->
<label class="gf-form-label"
ng-if="
analyticUnit.detectorType === 'pattern' ||
(analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality)
"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING' || analyticUnit.saving || analyticUnit.changed || !analyticUnit.selected"
>
<a class="pointer">
Detect
</a>
</label>
<label class="gf-form-label"
ng-if="
analyticUnit.detectorType === 'threshold' ||
(analyticUnit.detectorType === 'anomaly' && !analyticUnit.hasSeasonality)
"
ng-click="ctrl.runDetectInCurrentRange(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING' || analyticUnit.saving || analyticUnit.changed"
>
<a class="pointer">
Detect
</a>
</label>
</div>
</div>

155
src/panel/graph_panel/partials/analytic_units_6.x.html

@ -0,0 +1,155 @@
<div class="query-editor-row" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits">
<div class="query-editor-row__header">
<div class="query-editor-row__ref-id width-25" ng-click="ctrl.onToggleCollapsed(analyticUnit.id)">
<i class="fa fa-fw fa-caret-down" ng-hide="analyticUnit.collapsed"></i>
<i class="fa fa-fw fa-caret-right" ng-show="analyticUnit.collapsed"></i>
<span>{{analyticUnit.name}}</span>
<em class="query-editor-row__context-info">({{analyticUnit.id}})</em>
</div>
&nbsp;
<i ng-if="analyticUnit.status === 'READY'"
class="fa fa-fw fa-check-circle"
bs-tooltip="'Ready'"
></i>
<i ng-if="analyticUnit.status === 'SUCCESS'"
class="fa fa-fw fa-check"
bs-tooltip="'Learning succeeded'"
></i>
<i ng-if="analyticUnit.status === 'LEARNING'"
class="fa fa-fw fa-leanpub"
bs-tooltip="'Learning'"
></i>
<i ng-if="analyticUnit.status === 'DETECTION'"
class="fa fa-fw fa-search"
bs-tooltip="'Detection'"
></i>
<i ng-if="analyticUnit.status === 'PENDING'"
class="fa fa-fw fa-list-ul"
bs-tooltip="'Pending'"
></i>
<i ng-if="analyticUnit.status === 'FAILED'"
class="fa fa-fw fa-exclamation-circle"
bs-tooltip="'Error: ' + analyticUnit.error"
></i>
<div class="query-editor-row__collapsed-text">
</div>
<div class="query-editor-row__actions">
<button
class="query-editor-row__action"
ng-click=ctrl.onToggleVisibility(analyticUnit.id)
>
<a
ng-if="analyticUnit.visible"
bs-tooltip="'Hide. It`s visible now.'"
class="pointer"
>
<i class="fa fa-fw fa-eye"></i>
</a>
<a
ng-if="!analyticUnit.visible"
bs-tooltip="'Show. It`s hidden now.'"
class="pointer"
>
<i class="fa fa-fw fa-eye-slash"></i>
</a>
</button>
<button class="query-editor-row__action"
ng-click="ctrl.onToggleInspect(analyticUnit.id)"
bs-tooltip="'Inspect Mode'"
>
<a class="pointer">
<i class="fa fa-fw fa-crosshairs" ng-if="analyticUnit.inspect"></i>
<i class="fa fa-fw fa-circle-thin" ng-if="!analyticUnit.inspect"></i>
</a>
</button>
<button class="query-editor-row__action" ng-click="ctrl.onRemove(analyticUnit.id)">
<a class="pointer">
<i class="fa fa-fw fa-trash"></i>
</a>
</button>
<button class="query-editor-row__action"
ng-click="ctrl.onAnalyticUnitSave(analyticUnit)"
ng-disabled="!analyticUnit.changed"
>
<a class="pointer"
ng-style="!analyticUnit.changed && {
'color': 'lightgray',
'cursor': 'not-allowed'
}"
>
<i class="fa fa-fw fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<i class="fa fa-fw fa-save" ng-if="!analyticUnit.saving"></i>
</a>
</button>
<button class="query-editor-row__action" ng-if="
(analyticUnit.detectorType === 'pattern' ||
(analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality)) &&
!analyticUnit.selected
">
<a
class="pointer"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
<i class="fa fa-fw fa-bar-chart" ng-if="!analyticUnit.saving"></i>
<i class="fa fa-fw fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
Label
</a>
</button>
<select class="gf-form-input width-11"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in analyticUnit.labelingModes"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
<button class="query-editor-row__action" ng-if="analyticUnit.selected && !analyticUnit.saving">
<a class="pointer" ng-click="ctrl.onCancelLabeling(analyticUnit.id)">
<i class="fa fa-fw fa-ban"></i>
</a>
</button>
<!-- TODO: Leave one Detect button instead of 2 -->
<button class="query-editor-row__action"
ng-if="
analyticUnit.detectorType === 'pattern' ||
(analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality)
"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING' || analyticUnit.saving || analyticUnit.changed || !analyticUnit.selected"
>
<a class="pointer">
Detect
</a>
</button>
<button class="query-editor-row__action"
ng-if="
analyticUnit.detectorType === 'threshold' ||
(analyticUnit.detectorType === 'anomaly' && !analyticUnit.hasSeasonality)
"
ng-click="ctrl.runDetectInCurrentRange(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING' || analyticUnit.saving || analyticUnit.changed"
>
<a class="pointer">
Detect
</a>
</button>
</div>
</div>
<div
class="query-editor-row__body gf-form-query"
ng-class="analyticUnit.collapsed && 'query-editor-row__body--collapsed'"
>
<ng-include src="ctrl.partialsPath + '/analytic_unit.html'"></ng-include>
</div>
</div>

43
src/panel/graph_panel/partials/new_analytic_unit.html

@ -0,0 +1,43 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Name </label>
<input
type="text" class="gf-form-input width-15"
ng-model="ctrl.analyticsController.newAnalyticUnit.name"
>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Detector Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-8"
ng-model="ctrl.analyticsController.newAnalyticUnit.detectorType"
ng-options="analyticUnitDetectorType for analyticUnitDetectorType in ctrl.analyticUnitDetectorTypes"
ng-change="ctrl.analyticsController.onAnalyticUnitDetectorChange(ctrl.analyticUnitTypes);"
/>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-8"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-9"
ng-model="ctrl.analyticsController.newAnalyticUnit.type"
ng-options="
type.value as type.name
for type in ctrl.analyticUnitTypes[ctrl.analyticsController.newAnalyticUnit.detectorType]
"
/>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>

11
src/panel/graph_panel/partials/new_analytic_unit_5.x.html

@ -0,0 +1,11 @@
<div class="gf-form-query">
<div class="gf-form gf-form-query-letter-cell">
<label class="gf-form-label width-25">
<span class="gf-form-query-letter-cell-letter">New analytic unit</span>
</label>
</div>
<div class="gf-form-query-content">
<ng-include src="ctrl.partialsPath + '/new_analytic_unit.html'"></ng-include>
</div>
</div>

29
src/panel/graph_panel/partials/new_analytic_unit_6.x.html

@ -0,0 +1,29 @@
<div class="query-editor-row">
<div class="query-editor-row__header">
<div class="query-editor-row__ref-id width-25" style="cursor: default;">
<span>New analytic unit</span>
</div>
<div class="query-editor-row__collapsed-text">
</div>
<div class="query-editor-row__actions">
<button class="query-editor-row__action">
<a class="pointer" ng-click="ctrl.cancelCreation()">
Cancel
</a>
</button>
<button class="query-editor-row__action">
<a class="pointer" ng-click="ctrl.saveNew()">
<b ng-if="!ctrl.analyticsController.saving"> Create </b>
<b ng-if="ctrl.analyticsController.saving" ng-disabled="true"> Saving... </b>
</a>
</button>
</div>
</div>
<div class="query-editor-row__body gf-form-query">
<ng-include src="ctrl.partialsPath + '/new_analytic_unit.html'"></ng-include>
</div>
</div>

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

@ -9,7 +9,7 @@
</div> </div>
</div> </div>
<div class="gf-form"> <div>
<div class="gf-form-button-row" ng-if="ctrl.analyticsController.serverStatus === false"> <div class="gf-form-button-row" ng-if="ctrl.analyticsController.serverStatus === false">
<h5>Hastic server at "{{ctrl.hasticDatasource.url}}" is not available</h5> <h5>Hastic server at "{{ctrl.hasticDatasource.url}}" is not available</h5>
<button class="btn btn-inverse" ng-click="ctrl.onHasticDatasourceChange()"> <button class="btn btn-inverse" ng-click="ctrl.onHasticDatasourceChange()">
@ -19,322 +19,14 @@
</div> </div>
<div ng-if="ctrl.analyticsController.serverStatus === true && !ctrl.analyticsController.loading"> <div ng-if="ctrl.analyticsController.serverStatus === true && !ctrl.analyticsController.loading">
<h5> Analytic Units </h5> <ng-include src="ctrl.getTemplatePath('analytic_units')">
<div ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits"> </ng-include>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-5">
<i class="fa fa-info" bs-tooltip="'Analytic unit id: ' + analyticUnit.id"></i>
&nbsp; Name
</label>
<input
type="text" class="gf-form-input width-15"
ng-model="analyticUnit.name"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
>
</div>
<div class="gf-form">
<label class="gf-form-label width-5"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-9"
ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.analyticUnitTypes[analyticUnit.detectorType]"
ng-disabled="true"
/>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-8"> Positive Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.labeledColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, false)"
/>
</span>
</div>
<div class="gf-form"
ng-if="analyticUnit.detectorType === 'pattern' || analyticUnit.detectorType === 'anomaly'"
>
<label class="gf-form-label width-8"> Negative Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.deletedColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, true)"
/>
</span>
</div>
<div class="gf-form" ng-if="analyticUnit.visible">
<!-- TODO: Remove hack with "margin-bottom: 0" -->
<gf-form-switch
class="gf-form"
style="margin-bottom: 0"
label="Inspect"
label-class="width-5"
on-change="ctrl.onToggleInspect(analyticUnit.id)"
checked="analyticUnit.inspect"
/>
</div>
<div
class="gf-form"
ng-if="
analyticUnit.detectorType === 'pattern' ||
(analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality)
"
>
<label
class="gf-form-label pointer"
ng-if="!analyticUnit.selected"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
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>
Label
</label>
<select class="gf-form-input width-12"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in analyticUnit.labelingModes"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
</div>
<div class="gf-form" ng-if="!analyticUnit.selected">
<label class="gf-form-label">
<a
ng-if="analyticUnit.visible"
bs-tooltip="'Hide. It`s visible now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-eye"></i>
</a>
<a
ng-if="!analyticUnit.visible"
bs-tooltip="'Show. It`s hidden now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-eye-slash"></i>
</a>
</label>
</div>
<div class="gf-form" ng-if="!analyticUnit.selected">
<a
class="btn btn-danger"
ng-click="ctrl.onRemove(analyticUnit.id)"
>
<i class="fa fa-trash"></i>
</a>
</div>
<div class="gf-form" ng-if="analyticUnit.selected"> <ng-include
<div class="gf-form-label"> ng-if="ctrl.analyticsController.creatingNew"
<a src="ctrl.getTemplatePath('new_analytic_unit')"
class="pointer"
ng-click="ctrl.onCancelLabeling(analyticUnit.id)"
> >
Cancel </ng-include>
</a>
</div>
</div>
<div class="gf-form">
<label>
<i ng-if="analyticUnit.status === 'READY'" class="grafana-tip fa fa-check-circle ng-scope" bs-tooltip="'Ready'"></i>
<i ng-if="analyticUnit.status === 'SUCCESS'" class="grafana-tip fa fa-check ng-scope" bs-tooltip="'Learning succeeded'"></i>
<i ng-if="analyticUnit.status === 'LEARNING'" class="grafana-tip fa fa-leanpub ng-scope" bs-tooltip="'Learning'"></i>
<i ng-if="analyticUnit.status === 'DETECTION'" class="grafana-tip fa fa-search ng-scope" bs-tooltip="'Detection'"></i>
<i ng-if="analyticUnit.status === 'PENDING'" class="grafana-tip fa fa-list-ul ng-scope" bs-tooltip="'Pending'"></i>
<i ng-if="analyticUnit.status === 'FAILED'" class="grafana-tip fa fa-exclamation-circle ng-scope" bs-tooltip="'Error: ' + analyticUnit.error"></i>
</label>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form width-20"/>
<!-- TODO: move analytic-unit-specific fields rendering to class -->
<div class="gf-form" ng-if="analyticUnit.detectorType === 'threshold'">
<label class="gf-form-label width-5"> Condition </label>
<select class="gf-form-input"
ng-class="{
'width-5': analyticUnit.condition !== 'NO_DATA',
'width-9': analyticUnit.condition === 'NO_DATA'
}"
ng-model="analyticUnit.condition"
ng-options="type for type in ctrl.analyticsController.conditions"
ng-change="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
<input class="gf-form-input width-4"
ng-if="analyticUnit.condition !== 'NO_DATA'"
type="number"
ng-model="analyticUnit.value"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
</div>
<div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly'">
<label class="gf-form-label width-5"> Alpha </label>
<input class="gf-form-input width-9"
min="0"
max="1"
type="number"
ng-model="analyticUnit.alpha"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
</div>
<div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly'">
<label class="gf-form-label width-6"> Confidence </label>
<input class="gf-form-input width-5"
min="0"
type="number"
ng-model="analyticUnit.confidence"
ng-blur="ctrl.onAnalyticUnitChange(analyticUnit)"
/>
</div>
<div class="gf-form" ng-if="analyticUnit.detectorType === 'anomaly'">
<!-- TODO: Remove hack with "margin-bottom: 0" -->
<gf-form-switch
class="gf-form"
style="margin-bottom: 0"
label="Seasonality"
label-class="width-7"
on-change="ctrl.onAnalyticUnitChange(analyticUnit)"
checked="analyticUnit.hasSeasonality"
/>
</div>
<div
class="gf-form"
ng-if="analyticUnit.detectorType === 'anomaly' && analyticUnit.hasSeasonality"
>
<label class="gf-form-label width-9"> Seasonality Period </label>
<input
type="number" class="gf-form-input width-5"
ng-init="seasonalityValue = analyticUnit.seasonalityPeriod.value"
ng-model="seasonalityValue"
ng-blur="ctrl.onSeasonalityChange(analyticUnit.id, seasonalityValue)"
min="0"
>
</div>
<div class="gf-form"
ng-if="
analyticUnit.detectorType === 'anomaly' &&
analyticUnit.hasSeasonality
"
>
<div class="gf-form-select-wrapper">
<!-- TODO: move periods from ng-options -->
<select class="gf-form-input width-8"
ng-model="analyticUnit.seasonalityPeriod.unit"
ng-change="ctrl.onSeasonalityChange(analyticUnit.id)"
ng-options="type for type in ['seconds', 'minutes', 'hours', 'days', 'years']"
/>
</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.selected"
>
<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 class="gf-form-inline" ng-if="ctrl.analyticsController.creatingNew">
<div class="gf-form">
<label class="gf-form-label width-5"> Name </label>
<input
type="text" class="gf-form-input width-15"
ng-model="ctrl.analyticsController.newAnalyticUnit.name"
>
</div>
<div class="gf-form">
<label class="gf-form-label width-8"> Detector Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-8"
ng-model="ctrl.analyticsController.newAnalyticUnit.detectorType"
ng-options="analyticUnitDetectorType for analyticUnitDetectorType in ctrl.analyticUnitDetectorTypes"
ng-change="ctrl.analyticsController.onAnalyticUnitDetectorChange(ctrl.analyticUnitTypes);"
/>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-5"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-9"
ng-model="ctrl.analyticsController.newAnalyticUnit.type"
ng-options="
type.value as type.name
for type in ctrl.analyticUnitTypes[ctrl.analyticsController.newAnalyticUnit.detectorType]
"
/>
</div>
</div>
<div class="gf-form">
<a class="btn btn-danger" ng-click="ctrl.cancelCreation()">
<b> Cancel </b>
</a>
</div>
<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 class="gf-form-button-row"> <div class="gf-form-button-row">
<button <button

Loading…
Cancel
Save