More work on annotations (#44, #8) It is working for annotations from graphite metrics, basic annotation editor and everything is working. Needs some final touches and support for more datasources

This commit is contained in:
Torkel Ödegaard
2014-02-18 20:41:56 +01:00
parent bda5ae5b90
commit 700d60058f
14 changed files with 222 additions and 113 deletions

View File

@@ -10,8 +10,6 @@ function (angular, app, _) {
module.controller('SubmenuCtrl', function($scope) {
var _d = {
collapse: false,
notice: false,
enable: true
};

View File

@@ -2,7 +2,7 @@ define([
'angular',
'underscore'
],
function (angular,_) {
function (angular, _) {
'use strict';
angular
@@ -10,30 +10,29 @@ function (angular,_) {
.directive('configModal', function($modal,$q) {
return {
restrict: 'A',
link: function(scope, elem) {
link: function(scope, elem, attrs) {
var
model = attrs.kbnModel,
partial = attrs.configModal;
// create a new modal. Can't reuse one modal unforunately as the directive will not
// re-render on show.
elem.bind('click',function(){
if (scope.openConfigureModal) {
scope.openConfigureModal();
scope.$apply();
return;
}
// Create a temp scope so we can discard changes to it if needed
var tmpScope = scope.$new();
tmpScope.panel = angular.copy(scope.panel);
tmpScope[model] = angular.copy(scope[model]);
tmpScope.editSave = function(panel) {
// Correctly set the top level properties of the panel object
_.each(panel,function(v,k) {
scope.panel[k] = panel[k];
scope[model][k] = panel[k];
});
};
var panelModal = $modal({
template: './app/partials/paneleditor.html',
template: partial,
persist: true,
show: false,
scope: tmpScope,

View File

@@ -44,7 +44,8 @@ function (angular, $) {
'</span>' +
'</span>'+
'<span ng-if="!panelMeta.menuItems" config-modal class="panel-text panel-title pointer" ng-show="panel.title">' +
'<span ng-if="!panelMeta.menuItems" config-modal="./app/partials/paneleditor.html" ' +
' kbn-model="panel" class="panel-text panel-title pointer" >' +
'{{panel.title}}' +
'</span>'+

View File

@@ -0,0 +1,60 @@
<div bindonce class="modal-body">
<div class="pull-right editor-title">Annotations</div>
<div class="editor-row">
<table class="table table-striped annotation-editor-table">
<thead>
<th width="1%">Type</th>
<th width="90%">Name</th>
<th width="1%"></th>
<th width="1%"></th>
<th width="1%"></th>
<th width="1%"></th>
</thead>
<tr ng-repeat="annotation in panel.annotations">
<td>{{annotation.type}}</td>
<td>{{annotation.name}}</td>
<td><i ng-click="_.move(panel.annotations,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(panel.annotations,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td><i ng-click="panel.annotations = _.without(panel.annotations, annotation)" class="pointer icon-remove"></i></td>
<td><a ng-click="edit(annotation)"><i class="icon-pencil" /></a></td>
</tr>
</table>
</div>
<div class="editor-row">
<h4 ng-show="currentIsNew">Add Annotation</h4>
<h4 ng-show="!currentIsNew">Edit Annotation</h4>
<div class="editor-option">
<label class="small">Name</label>
<input type="text" class="input-medium" ng-model='currentAnnnotation.name' placeholder="name"></input>
</div>
<div class="editor-option">
<label class="small">Type</label>
<select ng-model="currentAnnnotation.type" ng-options="f for f in ['graphite metric']"></select>
</div>
</div>
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite metric'">
<div class="editor-option">
<label class="small">Graphite target expression</label>
<input type="text" class="span10" ng-model='currentAnnnotation.target' placeholder=""></input>
</div>
</div>
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite events'">
<div class="editor-option">
<label class="small">Graphite event tags</label>
<input type="text" ng-model='currentAnnnotation.tags' placeholder=""></input>
</div>
</div>
</div>
<div class="modal-footer">
<!-- close_edit() is provided here to allow for a scope to perform action on dismiss -->
<button ng-show="currentIsNew" type="button" class="btn btn-success" ng-click="add()">Add annotation</button>
<button ng-show="!currentIsNew" type="button" class="btn btn-success" ng-click="update()">Update</button>
<button type="button" class="btn btn-danger" ng-click="editSave(panel);close_edit();dismiss()">Close</button>
</div>

View File

@@ -1,13 +1,12 @@
<div ng-controller='AnnotationsCtrl' ng-init="init()">
<!-- <div class="submenu-toggle" ng-class="{'annotation-disabled': panel.hideAll }">
<a ng-click="hideAll();" class="small">Hide All</a>
<i class="icon-ok"></i>
</div>
-->
<div class="submenu-toggle" ng-repeat="annotation in annotationList" ng-class="{'annotation-disabled': !annotation.enabled }">
<div class="submenu-toggle" ng-repeat="annotation in panel.annotations" ng-class="{'annotation-disabled': !annotation.enable }">
<i class="annotation-color-icon icon-minus"></i>
<a ng-click="hide(annotation)" class="small">{{annotation.name}}</a>
<i class="icon-ok"></i>
</div>
<div class="submenu-control-edit">
<i class="icon-cog pointer" bs-modal="'app/panels/annotations/editor.html'" bs-tooltip="'Edit annotations'" ></i>
</div>
</div>

View File

@@ -14,7 +14,7 @@ function (angular, app, _) {
var module = angular.module('kibana.panels.annotations', []);
app.useModule(module);
module.controller('AnnotationsCtrl', function($scope, dashboard, annotationsSrv, $rootScope) {
module.controller('AnnotationsCtrl', function($scope, dashboard, $rootScope) {
$scope.panelMeta = {
status : "Stable",
@@ -23,26 +23,46 @@ function (angular, app, _) {
// Set and populate defaults
var _d = {
annotations: []
};
var annotationDefaults = {
name: '',
type: 'graphite metric'
};
_.defaults($scope.panel,_d);
$scope.init = function() {
$scope.annotationList = annotationsSrv.annotationList;
$scope.currentAnnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.hideAll = function () {
$scope.panel.hideAll = !$scope.panel.hideAll;
$scope.getAnnotationInfo = function(annotation) {
return annotation.target;
};
_.each($scope.annotationList, function(annotation) {
annotation.enabled = !$scope.panel.hideAll;
});
$scope.edit = function(annotation) {
$scope.currentAnnnotation = annotation;
$scope.currentIsNew = false;
};
$scope.getInfo = function(annotation) {
return annotation.target;
};
$scope.update = function() {
$scope.currentAnnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.add = function() {
$scope.panel.annotations.push($scope.currentAnnnotation);
$scope.currentAnnnotation = angular.copy(annotationDefaults);
};
$scope.hide = function (annotation) {
annotation.enabled = !annotation.enabled;
$scope.panel.hideAll = !annotation.enabled;
annotation.enable = !annotation.enable;
$rootScope.$broadcast('refresh');
};

View File

@@ -43,7 +43,7 @@
</div>
<div ng-if="editor.index == 1">
<div class="row-fluid">
<div class="editor-row">
<div class="span8">
<h4>Rows</h4>
<table class="table table-striped">
@@ -69,9 +69,6 @@
<input type="text" class="input-mini" ng-model='row.height'></input>
</div>
</div>
<div class="row-fluid">
</div>
</div>
<div ng-if="editor.index == 2" ng-controller="dashLoader">
@@ -124,7 +121,7 @@
<div ng-if="editor.index == 2">
<div class="editor-row">
<div class="section">
<h5>Pulldowns</h5>
<h5>Feature toggles</h5>
<div class="editor-option" ng-repeat="pulldown in dashboard.current.pulldowns">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
</div>

View File

@@ -7,27 +7,35 @@ define([
var module = angular.module('kibana.services');
module.service('annotationsSrv', function(dashboard, datasourceSrv, $q, alertSrv) {
module.service('annotationsSrv', function(dashboard, datasourceSrv, $q, alertSrv, $rootScope) {
var promiseCached;
var annotationPanel;
this.init = function() {
this.annotationList = [
/* {
type: 'graphite-target',
enabled: false,
target: 'metrics_data.mysite.dolph.counters.payment.cart_klarna_payment_completed.count',
name: 'deploys',
},
{
type: 'graphite-target',
enabled: true,
target: 'metrics_data.mysite.dolph.counters.payment.cart_paypal_payment_completed.count',
name: 'restarts',
}*/
];
$rootScope.$on('refresh', this.clearCache);
$rootScope.$on('dashboard-loaded', this.dashboardLoaded);
this.dashboardLoaded();
};
this.dashboardLoaded = function () {
annotationPanel = _.findWhere(dashboard.current.pulldowns, { type: 'annotations' });
};
this.clearCache = function() {
promiseCached = null;
};
this.getAnnotations = function(rangeUnparsed) {
var graphiteAnnotations = _.where(this.annotationList, { type: 'graphite-target', enabled: true });
if (!annotationPanel.enable) {
return $q.when(null);
}
if (promiseCached) {
return promiseCached;
}
var graphiteAnnotations = _.where(annotationPanel.annotations, { type: 'graphite metric', enable: true });
var graphiteTargets = _.map(graphiteAnnotations, function(annotation) {
return { target: annotation.target };
});
@@ -43,7 +51,7 @@ define([
maxDataPoints: 100
};
return datasourceSrv.default.query(graphiteQuery)
promiseCached = datasourceSrv.default.query(graphiteQuery)
.then(function(results) {
return _.reduce(results.data, function(list, target) {
_.each(target.datapoints, function (values) {
@@ -51,12 +59,13 @@ define([
return;
}
list.push({
min: values[1] * 1000,
max: values[1] * 1000,
eventType: "annotation",
title: null,
description: "<small><i class='icon-tag icon-flip-vertical'></i>test</small><br>"+
description: "<small>" + target.target + "</small><br>"+
moment(values[1] * 1000).format('YYYY-MM-DD HH:mm:ss'),
score: 1
});
@@ -68,6 +77,8 @@ define([
.then(null, function() {
alertSrv.set('Annotations','Could not fetch annotations','error');
});
return promiseCached;
};
// Now init

View File

@@ -28,7 +28,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
failover: false,
panel_hints: true,
rows: [],
pulldowns: [ { type: 'filtering' }, /*{ type: 'annotations' }*/ ],
pulldowns: [ { type: 'filtering' }, { type: 'annotations' } ],
nav: [ { type: 'timepicker' } ],
services: {},
loader: {
@@ -131,13 +131,13 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
});
}
/*var annotations = _.findWhere(dashboard.pulldowns, {type: 'annotations'});
var annotations = _.findWhere(dashboard.pulldowns, {type: 'annotations'});
if (!annotations) {
dashboard.pulldowns.push({
type: 'annotations',
enable: false
});
}*/
}
return dashboard;
};
@@ -177,6 +177,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
// Take out any that we're not allowed to add from the gui.
self.availablePanels = _.difference(self.availablePanels,config.hidden_panels);
$rootScope.$emit('dashboard-loaded');
return true;
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -142,7 +142,7 @@
padding: 0 4px;
i {
position: relative;
top: 3px;
top: 2px;
}
}

View File

@@ -565,57 +565,8 @@ div.flot-text {
color: @white;
}
// Filter submenu
.filtering-container {
float: left;
}
.filtering-container label {
float: left;
}
.filtering-container input[type=checkbox] {
margin: 0;
}
.filter-panel-filter {
display:inline-block;
vertical-align: top;
padding: 4px 10px 3px 10px;
border-right: 1px solid @submenuBorder;
}
.filter-panel-filter:first-child {
padding-left: 0;
}
.filter-panel-filter ul {
margin-bottom: 0px;
}
.filter-deselected {
opacity: 0.5;
}
.filter-action {
float:right;
padding-right: 2px;
margin-bottom: 0px !important;
margin-left: 0px;
margin-top: 4px;
}
.add-filter-action {
padding: 3px 10px 0px 5px;
position: relative;
top: 4px;
}
.filter-mandate {
text-decoration: underline;
cursor: pointer;
}
.filter-apply {
float:right;
.annotation-editor-table {
td {
white-space: nowrap;
}
}

View File

@@ -12,11 +12,15 @@
}
.submenu-panel {
padding: 0 10px 0 17px;
padding: 0 4px 0 8px;
border-right: 1px solid @submenuBorder;
float: left;
}
.submenu-panel:first-child {
padding-left: 17px;
}
.submenu-panel-title {
float: left;
text-transform: uppercase;
@@ -30,12 +34,79 @@
.submenu-toggle {
padding: 4px 0 3px 8px;
float: left;
.annotation-color-icon {
position: relative;
top: 2px;
}
}
.submenu-toggle:first-child {
padding-left: 0;
}
.submenu-control-edit {
padding: 4px 4px 3px 8px;
float: right;
border-left: 1px solid @submenuBorder;
margin-left: 8px;
}
.annotation-disabled, .annotation-disabled a {
color: darken(@textColor, 25%);
}
// Filter submenu
.filtering-container {
float: left;
}
.filtering-container label {
float: left;
}
.filtering-container input[type=checkbox] {
margin: 0;
}
.filter-panel-filter {
display:inline-block;
vertical-align: top;
padding: 4px 10px 3px 10px;
border-right: 1px solid @submenuBorder;
}
.filter-panel-filter:first-child {
padding-left: 0;
}
.filter-panel-filter ul {
margin-bottom: 0px;
}
.filter-deselected {
opacity: 0.5;
}
.filtering-container .filter-action {
float:right;
padding-right: 2px;
margin-bottom: 0px !important;
margin-left: 0px;
margin-top: 4px;
}
.add-filter-action {
padding: 3px 5px 0px 5px;
position: relative;
top: 4px;
}
.filter-mandate {
text-decoration: underline;
cursor: pointer;
}
.filter-apply {
float:right;
}