mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
more work on annotations
This commit is contained in:
parent
0e3c0fcc2e
commit
869bebed6e
@ -2,6 +2,7 @@ define([
|
||||
'./dash',
|
||||
'./dashLoader',
|
||||
'./row',
|
||||
'./submenuCtrl',
|
||||
'./pulldown',
|
||||
'./search',
|
||||
'./metricKeys',
|
||||
|
30
src/app/controllers/submenuCtrl.js
Normal file
30
src/app/controllers/submenuCtrl.js
Normal file
@ -0,0 +1,30 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('SubmenuCtrl', function($scope) {
|
||||
var _d = {
|
||||
collapse: false,
|
||||
notice: false,
|
||||
enable: true
|
||||
};
|
||||
|
||||
_.defaults($scope.pulldown,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.panel = $scope.pulldown;
|
||||
$scope.row = $scope.pulldown;
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
});
|
@ -206,13 +206,13 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
function addAnnotations(options) {
|
||||
if(!scope.panel.annotate.enable) {
|
||||
if(!data.annotations || data.annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.events = {
|
||||
levels: 1,
|
||||
data: scope.annotations,
|
||||
data: data.annotations,
|
||||
types: {
|
||||
'annotation': {
|
||||
level: 1,
|
||||
|
@ -1,8 +1,13 @@
|
||||
<div ng-controller='AnnotationsCtrl' ng-init="init()">
|
||||
|
||||
<div class="submenu-toggle">
|
||||
<label class="small" for="showAnnotations">Hide</label>
|
||||
<input type="checkbox" id="showAnnotations"></label>
|
||||
<!-- <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 }">
|
||||
<a ng-click="hide(annotation)" class="small">{{annotation.name}}</a>
|
||||
<i class="icon-ok"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -14,7 +14,7 @@ function (angular, app, _) {
|
||||
var module = angular.module('kibana.panels.annotations', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('AnnotationsCtrl', function($scope) {
|
||||
module.controller('AnnotationsCtrl', function($scope, dashboard, annotationsSrv, $rootScope) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
@ -24,19 +24,26 @@ function (angular, app, _) {
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.panel.annotations = [
|
||||
{
|
||||
type: 'graphite-target',
|
||||
target: 'metric'
|
||||
},
|
||||
{
|
||||
type: 'graphite-target',
|
||||
target: 'metric2'
|
||||
}
|
||||
];
|
||||
$scope.annotationList = annotationsSrv.annotationList;
|
||||
};
|
||||
|
||||
$scope.hideAll = function () {
|
||||
$scope.panel.hideAll = !$scope.panel.hideAll;
|
||||
|
||||
_.each($scope.annotationList, function(annotation) {
|
||||
annotation.enabled = !$scope.panel.hideAll;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.hide = function (annotation) {
|
||||
annotation.enabled = !annotation.enabled;
|
||||
$scope.panel.hideAll = !annotation.enabled;
|
||||
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
var module = angular.module('kibana.panels.graphite', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('graphite', function($scope, $rootScope, filterSrv, graphiteSrv, $timeout) {
|
||||
module.controller('graphite', function($scope, $rootScope, filterSrv, graphiteSrv, $timeout, annotationsSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
modals : [],
|
||||
@ -243,6 +243,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
$scope.range = filterSrv.timeRange();
|
||||
$scope.rangeUnparsed = filterSrv.timeRange(false);
|
||||
|
||||
$scope.interval = '10m';
|
||||
|
||||
if ($scope.range) {
|
||||
@ -279,12 +281,14 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.updateTimeRange();
|
||||
|
||||
var graphiteQuery = {
|
||||
range: filterSrv.timeRange(false),
|
||||
range: $scope.rangeUnparsed,
|
||||
targets: $scope.panel.targets,
|
||||
renderer: $scope.panel.renderer,
|
||||
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
|
||||
maxDataPoints: $scope.panel.span * 50
|
||||
};
|
||||
|
||||
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed);
|
||||
|
||||
return graphiteSrv.query(graphiteQuery)
|
||||
.then($scope.receiveGraphiteData)
|
||||
.then(null, function(err) {
|
||||
@ -327,7 +331,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
data.push(series);
|
||||
});
|
||||
|
||||
$scope.render(data);
|
||||
$scope.annotationsPromise
|
||||
.then(function(annotations) {
|
||||
data.annotations = annotations;
|
||||
$scope.render(data);
|
||||
}, function() {
|
||||
$scope.render(data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.add_target = function() {
|
||||
|
@ -1,17 +1,5 @@
|
||||
<!-- is there a better way to repeat without actually affecting the page? -->
|
||||
<!-- <div class="filter-pulldown" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
|
||||
<div class="top-row-close pointer pull-left" ng-click="toggle_pulldown(pulldown);dismiss();" bs-tooltip="'Toggle '+pulldown.type" data-placement="bottom">
|
||||
<span class="small"><strong>{{pulldown.type}}</strong></span>
|
||||
</div>
|
||||
<div class="top-row-open" ng-hide="pulldown.collapse">
|
||||
<kibana-simple-panel type="filtering" ng-cloak></kibana-simple-panel>
|
||||
<kibana-simple-panel type="annotations" ng-cloak></kibana-simple-panel>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div> -->
|
||||
|
||||
<div class="submenu-controls">
|
||||
<div class="submenu-panel" ng-repeat="pulldown in dashboard.current.pulldowns | filter:{ enable: true }">
|
||||
<div class="submenu-panel" ng-controller="SubmenuCtrl" ng-repeat="pulldown in dashboard.current.pulldowns | filter:{ enable: true }">
|
||||
<div class="submenu-panel-title">
|
||||
<span class="small"><strong>{{pulldown.type}}:</strong></span>
|
||||
</div>
|
||||
|
@ -133,7 +133,7 @@
|
||||
<ng-include src="'app/partials/import.html'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl" ng-show="editor.index == 5+$index">
|
||||
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 5+$index">
|
||||
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
|
||||
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
|
||||
</div>
|
||||
|
@ -6,5 +6,6 @@ define([
|
||||
'./panelMove',
|
||||
'./graphite/graphiteSrv',
|
||||
'./keyboardManager',
|
||||
'./annotationsSrv',
|
||||
],
|
||||
function () {});
|
77
src/app/services/annotationsSrv.js
Normal file
77
src/app/services/annotationsSrv.js
Normal file
@ -0,0 +1,77 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'moment'
|
||||
], function (angular, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.services');
|
||||
|
||||
module.service('annotationsSrv', function(dashboard, graphiteSrv, $q, alertSrv) {
|
||||
|
||||
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',
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
this.getAnnotations = function(rangeUnparsed) {
|
||||
var graphiteAnnotations = _.where(this.annotationList, { type: 'graphite-target', enabled: true });
|
||||
var graphiteTargets = _.map(graphiteAnnotations, function(annotation) {
|
||||
return { target: annotation.target };
|
||||
});
|
||||
|
||||
if (graphiteTargets.length === 0) {
|
||||
return $q.when(null);
|
||||
}
|
||||
|
||||
var graphiteQuery = {
|
||||
range: rangeUnparsed,
|
||||
targets: graphiteTargets,
|
||||
format: 'json',
|
||||
maxDataPoints: 100
|
||||
};
|
||||
|
||||
return graphiteSrv.query(graphiteQuery)
|
||||
.then(function(results) {
|
||||
return _.reduce(results.data, function(list, target) {
|
||||
_.each(target.datapoints, function (values) {
|
||||
if (values[0] === null) {
|
||||
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>"+
|
||||
moment(values[1] * 1000).format('YYYY-MM-DD HH:mm:ss'),
|
||||
score: 1
|
||||
});
|
||||
});
|
||||
|
||||
return list;
|
||||
}, []);
|
||||
})
|
||||
.then(null, function() {
|
||||
alertSrv.set('Annotations','Could not fetch annotations','error');
|
||||
});
|
||||
};
|
||||
|
||||
// Now init
|
||||
this.init();
|
||||
});
|
||||
|
||||
});
|
@ -20,13 +20,13 @@ function (angular, _, $, config, kbn, moment) {
|
||||
from: this.translateTime(options.range.from),
|
||||
until: this.translateTime(options.range.to),
|
||||
targets: options.targets,
|
||||
renderer: options.renderer,
|
||||
format: options.format,
|
||||
maxDataPoints: options.maxDataPoints
|
||||
};
|
||||
|
||||
var params = buildGraphiteParams(graphOptions);
|
||||
|
||||
if (options.renderer === 'png') {
|
||||
if (options.format === 'png') {
|
||||
return $q.when(graphiteRenderUrl + '?' + params.join('&'));
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
var clean_options = [];
|
||||
var graphite_options = ['target', 'targets', 'from', 'until', 'rawData', 'format', 'maxDataPoints'];
|
||||
|
||||
if (options.renderer !== 'png') {
|
||||
if (options.format !== 'png') {
|
||||
options['format'] = 'json';
|
||||
}
|
||||
|
||||
|
2
src/css/bootstrap.dark.min.css
vendored
2
src/css/bootstrap.dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
src/css/bootstrap.light.min.css
vendored
2
src/css/bootstrap.light.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,3 +1,4 @@
|
||||
@import "submenu.less";
|
||||
|
||||
.navbar-static-top {
|
||||
border-bottom: 1px solid black;
|
||||
@ -37,39 +38,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-controls {
|
||||
background: #292929;
|
||||
font-size: inherit;
|
||||
label {
|
||||
margin: 0;
|
||||
padding-right: 4px;
|
||||
display: inline;
|
||||
}
|
||||
input[type=checkbox] {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-panel {
|
||||
padding: 0 10px 0 17px;
|
||||
border-right: 1px solid #202020;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-panel-title {
|
||||
float: left;
|
||||
text-transform: uppercase;
|
||||
padding: 3px 10px 0 0;
|
||||
}
|
||||
|
||||
.submenu-panel-wrapper {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-toggle {
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.row-button {
|
||||
width: 24px;
|
||||
}
|
41
src/css/less/submenu.less
Normal file
41
src/css/less/submenu.less
Normal file
@ -0,0 +1,41 @@
|
||||
.submenu-controls {
|
||||
background: #292929;
|
||||
font-size: inherit;
|
||||
label {
|
||||
margin: 0;
|
||||
padding-right: 4px;
|
||||
display: inline;
|
||||
}
|
||||
input[type=checkbox] {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-panel {
|
||||
padding: 0 10px 0 17px;
|
||||
border-right: 1px solid #202020;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-panel-title {
|
||||
float: left;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 10px 0 0;
|
||||
}
|
||||
|
||||
.submenu-panel-wrapper {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-toggle {
|
||||
padding: 4px 0 0 4px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-toggle:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.annotation-disabled, .annotation-disabled a {
|
||||
color: darken(@textColor, 25%);
|
||||
}
|
225
src/vendor/jquery/jquery.flot.fillbetween.js
vendored
Normal file
225
src/vendor/jquery/jquery.flot.fillbetween.js
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
/* Flot plugin for computing bottoms for filled line and bar charts.
|
||||
|
||||
Copyright (c) 2007-2013 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The case: you've got two series that you want to fill the area between. In Flot
|
||||
terms, you need to use one as the fill bottom of the other. You can specify the
|
||||
bottom of each data point as the third coordinate manually, or you can use this
|
||||
plugin to compute it for you.
|
||||
|
||||
In order to name the other series, you need to give it an id, like this:
|
||||
|
||||
var dataset = [
|
||||
{ data: [ ... ], id: "foo" } , // use default bottom
|
||||
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
|
||||
];
|
||||
|
||||
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
|
||||
|
||||
As a convenience, if the id given is a number that doesn't appear as an id in
|
||||
the series, it is interpreted as the index in the array instead (so fillBetween:
|
||||
0 can also mean the first series).
|
||||
|
||||
Internally, the plugin modifies the datapoints in each series. For line series,
|
||||
extra data points might be inserted through interpolation. Note that at points
|
||||
where the bottom line is not defined (due to a null point or start/end of line),
|
||||
the current line will show a gap too. The algorithm comes from the
|
||||
jquery.flot.stack.js plugin, possibly some code could be shared.
|
||||
|
||||
*/
|
||||
|
||||
(function ( $ ) {
|
||||
|
||||
var options = {
|
||||
series: {
|
||||
fillBetween: null // or number
|
||||
}
|
||||
};
|
||||
|
||||
function init( plot ) {
|
||||
|
||||
function findBottomSeries( s, allseries ) {
|
||||
|
||||
var i;
|
||||
|
||||
for ( i = 0; i < allseries.length; ++i ) {
|
||||
if ( allseries[ i ].id === s.fillBetween ) {
|
||||
return allseries[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( typeof s.fillBetween === "number" ) {
|
||||
if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
|
||||
return null;
|
||||
}
|
||||
return allseries[ s.fillBetween ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function computeFillBottoms( plot, s, datapoints ) {
|
||||
if ( s.fillBetween == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var other = findBottomSeries( s, plot.getData() );
|
||||
|
||||
if ( !other ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ps = datapoints.pointsize,
|
||||
points = datapoints.points,
|
||||
otherps = other.datapoints.pointsize,
|
||||
otherpoints = other.datapoints.points,
|
||||
newpoints = [],
|
||||
px, py, intery, qx, qy, bottom,
|
||||
withlines = s.lines.show,
|
||||
withbottom = ps > 2 && datapoints.format[2].y,
|
||||
withsteps = withlines && s.lines.steps,
|
||||
fromgap = true,
|
||||
i = 0,
|
||||
j = 0,
|
||||
l, m;
|
||||
|
||||
while ( true ) {
|
||||
|
||||
if ( i >= points.length ) {
|
||||
break;
|
||||
}
|
||||
|
||||
l = newpoints.length;
|
||||
|
||||
if ( points[ i ] == null ) {
|
||||
|
||||
// copy gaps
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
|
||||
i += ps;
|
||||
|
||||
} else if ( j >= otherpoints.length ) {
|
||||
|
||||
// for lines, we can't use the rest of the points
|
||||
|
||||
if ( !withlines ) {
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
}
|
||||
|
||||
i += ps;
|
||||
|
||||
} else if ( otherpoints[ j ] == null ) {
|
||||
|
||||
// oops, got a gap
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( null );
|
||||
}
|
||||
|
||||
fromgap = true;
|
||||
j += otherps;
|
||||
|
||||
} else {
|
||||
|
||||
// cases where we actually got two points
|
||||
|
||||
px = points[ i ];
|
||||
py = points[ i + 1 ];
|
||||
qx = otherpoints[ j ];
|
||||
qy = otherpoints[ j + 1 ];
|
||||
bottom = 0;
|
||||
|
||||
if ( px === qx ) {
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
|
||||
//newpoints[ l + 1 ] += qy;
|
||||
bottom = qy;
|
||||
|
||||
i += ps;
|
||||
j += otherps;
|
||||
|
||||
} else if ( px > qx ) {
|
||||
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
|
||||
if ( withlines && i > 0 && points[ i - ps ] != null ) {
|
||||
intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
|
||||
newpoints.push( qx );
|
||||
newpoints.push( intery );
|
||||
for ( m = 2; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
bottom = qy;
|
||||
}
|
||||
|
||||
j += otherps;
|
||||
|
||||
} else { // px < qx
|
||||
|
||||
// if we come from a gap, we just skip this point
|
||||
|
||||
if ( fromgap && withlines ) {
|
||||
i += ps;
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
|
||||
if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
|
||||
bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
|
||||
}
|
||||
|
||||
//newpoints[l + 1] += bottom;
|
||||
|
||||
i += ps;
|
||||
}
|
||||
|
||||
fromgap = false;
|
||||
|
||||
if ( l !== newpoints.length && withbottom ) {
|
||||
newpoints[ l + 2 ] = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// maintain the line steps invariant
|
||||
|
||||
if ( withsteps && l !== newpoints.length && l > 0 &&
|
||||
newpoints[ l ] !== null &&
|
||||
newpoints[ l ] !== newpoints[ l - ps ] &&
|
||||
newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints[ l + ps + m ] = newpoints[ l + m ];
|
||||
}
|
||||
newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
}
|
||||
|
||||
plot.hooks.processDatapoints.push( computeFillBottoms );
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: "fillbetween",
|
||||
version: "1.0"
|
||||
});
|
||||
|
||||
})(jQuery);
|
@ -12,7 +12,7 @@ module.exports = function(config) {
|
||||
// Compile in place when not building
|
||||
src:{
|
||||
options: {
|
||||
paths: ["<%= srcDir %>/vendor/bootstrap/less"],
|
||||
paths: ["<%= srcDir %>/vendor/bootstrap/less", "<%= srcDir %>/css/less"],
|
||||
yuicompress:true
|
||||
},
|
||||
files: {
|
||||
|
Loading…
Reference in New Issue
Block a user