Merge branch 'query-editor-breakout'

Conflicts:
	CHANGELOG.md
This commit is contained in:
Torkel Ödegaard 2015-08-18 08:23:13 +02:00
commit 9a9c9b2b12
53 changed files with 1704 additions and 1386 deletions

View File

@ -1,12 +1,22 @@
# 2.2 (unreleased)
**New Features && Enhancements**
** New Feature: Mix data sources **
A built in data source is now available named `-- Mixed --`, When picked in the metrics tab,
it allows you to add queries of differnet data source types & instances to the same graph/panel!
[Issue #436](https://github.com/grafana/grafana/issues/436)
** Other new Features && Enhancements**
- [Issue #2457](https://github.com/grafana/grafana/issues/2457). Admin: admin page for all grafana organizations (list / edit view)
- [Issue #1186](https://github.com/grafana/grafana/issues/1186). Time Picker: New option `today`, will set time range from midnight to now
**Fixes**
- [Issue #2490](https://github.com/grafana/grafana/issues/2490). Graphite: Dashboard import was broken in 2.1 and 2.1.1, working now
**Breaking Changes**
- Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that
require and update to custom data sources for them to work in 2.2. [Read this doc](https://github.com/grafana/grafana/tree/master/docs/sources/datasources/plugin_api.md) for more on the
data source api change.
# 2.1.x (currently unreleased patch branch)
**Fixes**

View File

@ -0,0 +1,40 @@
----
page_title: Data source Plugin API
page_description: Data Source Plugin Description
page_keywords: grafana, data source, plugin, api, docs
---
# Data source plugin API
All data sources in Grafana are implemented as plugins.
## Breaking change in 2.2
In Grafana 2.2 a breaking change was introduced for how data source query editors
are structured, defined and loaded. This was in order to support mixing multiple data sources
in the same panel.
In Grafana 2.2, the query editor is no longer defined using the partials section in
`plugin.json`, but defined via an angular directive named using convention naming
scheme like `metricQueryEditor<data source type name>`. For example
Graphite defines a directive like this:
```javascript
module.directive('metricQueryEditorGraphite', function() {
return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
});
```
Even though the data source type name is with lowercase `g`, the directive uses capital `G` in `Graphite` because
that is how angular directives needs to be named in order to match an element with name `<metric-query-editor-graphite />`.
You also specify the query controller here instead of in the query.editor.html partial like before.
### query.editor.html
This partial needs to be updated, remove the `np-repeat` this is done in the outer partial now,m the query.editor.html
should only render a single query. Take a look at the Graphite or InfluxDB partials for `query.editor.html` for reference.
You should also add a `tight-form-item` with `{{target.refId}}`, all queries needs to be assigned a letter (`refId`).
These query reference letters are going to be utilized in a later feature.

View File

@ -112,5 +112,13 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
}
func GetDataSourcePlugins(c *middleware.Context) {
c.JSON(200, plugins.DataSources)
dsList := make(map[string]interface{})
for key, value := range plugins.DataSources {
if value.(map[string]interface{})["builtIn"] == nil {
dsList[key] = value
}
}
c.JSON(200, dsList)
}

View File

@ -86,11 +86,17 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
// add grafana backend data source
grafanaDatasourceMeta, _ := plugins.DataSources["grafana"]
datasources["grafana"] = map[string]interface{}{
datasources["-- Grafana --"] = map[string]interface{}{
"type": "grafana",
"meta": grafanaDatasourceMeta,
}
// add mixed backend data source
datasources["-- Mixed --"] = map[string]interface{}{
"type": "mixed",
"meta": plugins.DataSources["mixed"],
}
if defaultDatasource == "" {
defaultDatasource = "grafana"
}

View File

@ -16,8 +16,11 @@ function (angular) {
}
setTimeout(function() {
element.focus();
var pos = element.val().length * 2;
element[0].setSelectionRange(pos, pos);
var domEl = element[0];
if (domEl.setSelectionRange) {
var pos = element.val().length * 2;
domEl.setSelectionRange(pos, pos);
}
}, 200);
},true);
};

View File

@ -15,7 +15,7 @@ function (angular, app, _, $) {
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
'tabindex="1" focus-me="segment.focus" ng-bind-html="segment.html"></a>';
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
return {
scope: {

View File

@ -72,8 +72,7 @@
</div>
</div>
<div ng-include src="currentDatasource.meta.partials.annotations">
</div>
<datasource-editor-view datasource="currentAnnotation.datasource" name="annotations-query-editor"></datasource-editor-view>
<br>
<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>

View File

@ -6,35 +6,108 @@ define([
function (angular, $, config) {
'use strict';
angular
.module('grafana.directives')
.directive('panelLoader', function($compile, $parse) {
return {
restrict: 'E',
link: function(scope, elem, attr) {
var getter = $parse(attr.type), panelType = getter(scope);
var panelPath = config.panels[panelType].path;
var module = angular.module('grafana.directives');
scope.require([panelPath + "/module"], function () {
var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
module.directive('panelLoader', function($compile, $parse) {
return {
restrict: 'E',
link: function(scope, elem, attr) {
var getter = $parse(attr.type), panelType = getter(scope);
var panelPath = config.panels[panelType].path;
scope.require([panelPath + "/module"], function () {
var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
elem.append(panelEl);
$compile(panelEl)(scope);
});
}
};
});
module.directive('grafanaPanel', function() {
return {
restrict: 'E',
templateUrl: 'app/features/panel/partials/panel.html',
transclude: true,
link: function(scope, elem) {
var panelContainer = elem.find('.panel-container');
scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
});
}
};
});
module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) {
var self = this;
this.addDirective = function(options, type, editorScope) {
var panelEl = angular.element(document.createElement(options.name + '-' + type));
options.parentElem.append(panelEl);
$compile(panelEl)(editorScope);
};
this.define = function(options) {
var editorScope;
options.scope.$watch(options.datasourceProperty, function(newVal) {
if (editorScope) {
editorScope.$destroy();
options.parentElem.empty();
}
editorScope = options.scope.$new();
datasourceSrv.get(newVal).then(function(ds) {
self.addDirective(options, ds.meta.type, editorScope);
});
});
};
});
module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) {
return {
restrict: 'E',
link: function(scope, elem) {
var editorScope;
scope.$watch("panel.datasource", function() {
var datasource = scope.target.datasource || scope.panel.datasource;
datasourceSrv.get(datasource).then(function(ds) {
if (editorScope) {
editorScope.$destroy();
elem.empty();
}
editorScope = scope.$new();
editorScope.datasource = ds;
if (!scope.target.refId) {
scope.target.refId = 'A';
}
var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.type));
elem.append(panelEl);
$compile(panelEl)(scope);
$compile(panelEl)(editorScope);
});
}
};
}).directive('grafanaPanel', function() {
return {
restrict: 'E',
templateUrl: 'app/features/panel/partials/panel.html',
transclude: true,
link: function(scope, elem) {
var panelContainer = elem.find('.panel-container');
});
}
};
});
module.directive('datasourceEditorView', function(dynamicDirectiveSrv) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
dynamicDirectiveSrv.define({
datasourceProperty: attrs.datasource,
name: attrs.name,
scope: scope,
parentElem: elem,
});
}
};
});
scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
});
}
};
});
});

View File

@ -43,8 +43,21 @@ function (angular, _, config) {
});
};
$scope.addDataQuery = function() {
$scope.panel.targets.push({target: ''});
$scope.addDataQuery = function(datasource) {
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var target = {};
if (datasource) {
target.datasource = datasource.name;
}
target.refId = _.find(letters, function(refId) {
return _.every($scope.panel.targets, function(other) {
return other.refId !== refId;
});
});
$scope.panel.targets.push(target);
};
$scope.removeDataQuery = function (query) {
@ -53,7 +66,23 @@ function (angular, _, config) {
};
$scope.setDatasource = function(datasource) {
$scope.panel.datasource = datasource;
// switching to mixed
if (datasource.meta.mixed) {
_.each($scope.panel.targets, function(target) {
target.datasource = $scope.panel.datasource;
if (target.datasource === null) {
target.datasource = config.defaultDatasource;
}
});
}
// switching from mixed
else if ($scope.datasource && $scope.datasource.meta.mixed) {
_.each($scope.panel.targets, function(target) {
delete target.datasource;
});
}
$scope.panel.datasource = datasource.value;
$scope.datasource = null;
$scope.get_data();
};

View File

@ -23,7 +23,10 @@ function (angular, _) {
$scope.init = function() {
$scope.editor = { index: 0 };
$scope.datasources = datasourceSrv.getMetricSources();
$scope.datasources = _.filter(datasourceSrv.getMetricSources(), function(ds) {
return !ds.meta.builtIn;
});
$scope.variables = templateSrv.variables;
$scope.reset();

View File

@ -1,24 +1,48 @@
<div ng-include src="datasource.meta.partials.query"></div>
<div class="editor-row">
<div class="tight-form-container">
<query-editor-loader ng-repeat="target in panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
</query-editor-loader>
</div>
<div class="editor-row" style="margin-top: 30px">
<button class="btn btn-inverse pull-right" ng-click="addDataQuery(panel.target)">
<i class="fa fa-plus"></i>&nbsp;
Add query
</button>
<div class="pull-right dropdown" style="margin-right: 10px;">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
<i class="fa fa-database"></i>&nbsp;
{{datasource.name}} <span class="caret"></span>
<div style="margin: 20px 0 0 0">
<button class="btn btn-inverse" ng-click="addDataQuery()" ng-hide="datasource.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Query
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem">
<a ng-click="setDatasource(datasource.value);">{{datasource.name}}</a>
</li>
</ul>
</div>
<div class="dropdown" ng-if="datasource.meta.mixed">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-plus"></i>&nbsp;
Query &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
</div>
<datasource-editor-view datasource="panel.datasource" name="metric-query-options"></datasource-editor-view>
</div>
<div class="editor-row" style="margin-top: 30px">
<div class="pull-right dropdown" style="margin-right: 10px;">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
<i class="fa fa-database"></i>&nbsp;
{{datasource.name}} &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem">
<a ng-click="setDatasource(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
<div class="clearfix"></div>
</div>

View File

@ -3,7 +3,8 @@ define([
'lodash',
'config',
'kbn',
'moment'
'moment',
'./directives'
],
function (angular, _, config, kbn, moment) {
'use strict';

View File

@ -0,0 +1,13 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('annotationsQueryEditorElasticsearch', function() {
return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'};
});
});

View File

@ -2,6 +2,7 @@ define([
'angular',
'lodash',
'kbn',
'./directives',
],
function (angular, _, kbn) {
'use strict';
@ -13,16 +14,6 @@ function (angular, _, kbn) {
function GrafanaDatasource() {
}
GrafanaDatasource.prototype.getDashboard = function(slug, isTemp) {
var url = '/dashboards/' + slug;
if (isTemp) {
url = '/temp/' + slug;
}
return backendSrv.get('/api/dashboards/db/' + slug);
};
GrafanaDatasource.prototype.query = function(options) {
// get from & to in seconds
var from = kbn.parseDate(options.range.from).getTime();
@ -35,36 +26,6 @@ function (angular, _, kbn) {
return $q.when([]);
};
GrafanaDatasource.prototype.starDashboard = function(dashId) {
return backendSrv.post('/api/user/stars/dashboard/' + dashId);
};
GrafanaDatasource.prototype.unstarDashboard = function(dashId) {
return backendSrv.delete('/api/user/stars/dashboard/' + dashId);
};
GrafanaDatasource.prototype.saveDashboard = function(dashboard) {
return backendSrv.post('/api/dashboards/db/', { dashboard: dashboard })
.then(function(data) {
return { title: dashboard.title, url: '/dashboard/db/' + data.slug };
}, function(err) {
err.isHandled = true;
err.data = err.data || {};
throw err.data.message || "Unknown error";
});
};
GrafanaDatasource.prototype.deleteDashboard = function(id) {
return backendSrv.delete('/api/dashboards/db/' + id);
};
GrafanaDatasource.prototype.searchDashboards = function(query) {
return backendSrv.get('/api/search/', query)
.then(function(data) {
return data;
});
};
return GrafanaDatasource;
});

View File

@ -0,0 +1,13 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('metricQueryEditorGrafana', function() {
return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'};
});
});

View File

@ -1,16 +1,56 @@
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li ng-show="parserError" class="tight-form-item">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index-1)">
Move up
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index+1)">
Move down
</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<div class="fluid-row" style="margin-top: 20px">
<div class="span2"></div>
<div class="grafana-info-box span8">
<h5>Test graph</h5>
<p>
This is just a test data source that generates random walk series. If this is your only data source
open the left side menu and navigate to the data sources admin screen and add your data sources (you need to be
logged in to do this). You can change data source using the button to the left of the <strong>Add query</strong> button.
</p>
</div>
<div class="span2"></div>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
<li class="tight-form-item">
Test metric (fake data source)
</li>
</ul>
<div class="clearfix"></div>
</div>

View File

@ -1,15 +1,11 @@
{
"pluginType": "datasource",
"name": "Grafana (for testing)",
"name": "Grafana",
"builtIn": true,
"type": "grafana",
"serviceName": "GrafanaDatasource",
"module": "plugins/datasource/grafana/datasource",
"partials": {
"query": "app/plugins/datasource/grafana/partials/query.editor.html"
},
"metrics": true
}

View File

@ -5,6 +5,7 @@ define([
'config',
'kbn',
'moment',
'./directives',
'./queryCtrl',
'./funcEditor',
'./addGraphiteFunc',
@ -228,21 +229,13 @@ function (angular, _, $, config, kbn, moment) {
return backendSrv.datasourceRequest(options);
};
GraphiteDatasource.prototype._seriesRefLetters = [
'#A', '#B', '#C', '#D',
'#E', '#F', '#G', '#H',
'#I', '#J', '#K', '#L',
'#M', '#N', '#O', '#P',
'#Q', '#R', '#S', '#T',
'#U', '#V', '#W', '#X',
'#Y', '#Z'
];
GraphiteDatasource.prototype._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
GraphiteDatasource.prototype.buildGraphiteParams = function(options, scopedVars) {
var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
var clean_options = [], targets = {};
var target, targetValue, i;
var regex = /(\#[A-Z])/g;
var regex = /\#([A-Z])/g;
var intervalFormatFixRegex = /'(\d+)m'/gi;
if (options.format !== 'png') {
@ -259,13 +252,17 @@ function (angular, _, $, config, kbn, moment) {
continue;
}
if (!target.refId) {
target.refId = this._seriesRefLetters[i];
}
targetValue = templateSrv.replace(target.target, scopedVars);
targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
targets[this._seriesRefLetters[i]] = targetValue;
targets[target.refId] = targetValue;
}
function nestedSeriesRegexReplacer(match) {
return targets[match];
function nestedSeriesRegexReplacer(match, g1) {
return targets[g1];
}
for (i = 0; i < options.targets.length; i++) {
@ -274,9 +271,9 @@ function (angular, _, $, config, kbn, moment) {
continue;
}
targetValue = targets[this._seriesRefLetters[i]];
targetValue = targets[target.refId];
targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
targets[this._seriesRefLetters[i]] = targetValue;
targets[target.refId] = targetValue;
if (!target.hide) {
clean_options.push("target=" + encodeURIComponent(targetValue));

View File

@ -0,0 +1,21 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('metricQueryEditorGraphite', function() {
return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
});
module.directive('metricQueryOptionsGraphite', function() {
return {templateUrl: 'app/plugins/datasource/graphite/partials/query.options.html'};
});
module.directive('annotationsQueryEditorGraphite', function() {
return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'};
});
});

View File

@ -1,221 +1,72 @@
<div class="editor-row">
<div ng-repeat="target in panel.targets"
class="tight-form"
ng-class="{'tight-form-disabled': target.hide}"
ng-controller="GraphiteQueryCtrl"
ng-init="init()">
<ul class="tight-form-list pull-right">
<li ng-show="parserError" class="tight-form-item">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
<a class="pointer" tabindex="1" ng-click="toggleEditorMode()">
<i class="fa fa-pencil"></i>
</a>
</li>
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li ng-show="parserError" class="tight-form-item">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item small" ng-show="target.datasource">
<em>{{target.datasource}}</em>
</li>
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1" ng-click="toggleEditorMode()">
Switch editor mode
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index-1)">
Move up
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index+1)">
Move down
</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="duplicate()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{targetLetters[$index]}}
</li>
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<input type="text" class="tight-form-clear-input span10"
ng-model="target.target"
focus-me="target.textEditor"
spellcheck='false'
ng-model-onblur ng-change="get_data()"
ng-show="target.textEditor" />
<input type="text" class="tight-form-clear-input span10"
ng-model="target.target"
give-focus="target.textEditor"
spellcheck='false'
ng-model-onblur ng-change="get_data()"
ng-show="target.textEditor" />
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
<li ng-repeat="segment in segments" role="menuitem">
<metric-segment segment="segment" get-alt-segments="getAltSegments($index)" on-value-changed="segmentValueChanged(segment, $index)"></metric-segment>
</li>
<li ng-repeat="func in functions">
<span graphite-func-editor class="tight-form-item tight-form-func">
</span>
</li>
<li class="dropdown" graphite-add-func>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
<li ng-repeat="segment in segments" role="menuitem">
<metric-segment segment="segment" get-alt-segments="getAltSegments($index)" on-value-changed="segmentValueChanged(segment, $index)"></metric-segment>
</li>
<li class="tight-form-item">
Cache timeout
<li ng-repeat="func in functions">
<span graphite-func-editor class="tight-form-item tight-form-func">
</span>
</li>
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.cacheTimeout"
bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
placeholder="60">
</li>
<li class="tight-form-item">
Max data points
</li>
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.maxDataPoints"
bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
data-placement="right"
ng-model-onblur ng-change="get_data()"
spellcheck='false'
placeholder="auto">
<li class="dropdown" graphite-add-func>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
shorter legend names
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
series as parameters
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
templating
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
max data points
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
<h5>Shorter legend names</h5>
<ul>
<li>alias() function to specify a custom series name</li>
<li>aliasByNode(2) to alias by a specific part of your metric path</li>
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
</ul>
</div>
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
<h5>Series as parameter</h5>
<ul>
<li>Some graphite functions allow you to have many series arguments</li>
<li>Use #[A-Z] to use a graphite query as parameter to a function</li>
<li>
Examples:
<ul>
<li>asPercent(#A, #B)</li>
<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
<li>divideSeries(#A, #B)</li>
</ul>
</li>
<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Stacking</h5>
<ul>
<li>You find the stacking option under Display Styles tab</li>
<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
<h5>Templating</h5>
<ul>
<li>You can use a template variable in place of metric names</li>
<li>You can use a template variable in place of function parameters</li>
<li>You enable the templating feature in Dashboard settings / Feature toggles </li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
<h5>Max data points</h5>
<ul>
<li>Every graphite request is issued with a maxDataPoints parameter</li>
<li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
<li>If there are more real values, then by default they will be consolidated using averages</li>
<li>This could hide real peaks and max values in your series</li>
<li>You can change how point consolidation is made using the consolidateBy graphite function</li>
<li>Point consolidation will effect series legend values (min,max,total,current)</li>
<li>If you override maxDataPoint and set a high value performance can be severely effected</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,132 @@
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
Cache timeout
</li>
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.cacheTimeout"
bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
placeholder="60"></input>
</li>
<li class="tight-form-item">
Max data points
</li>
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.maxDataPoints"
bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
data-placement="right"
ng-model-onblur ng-change="get_data()"
spellcheck='false'
placeholder="auto"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
shorter legend names
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
series as parameters
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
templating
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
max data points
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
<h5>Shorter legend names</h5>
<ul>
<li>alias() function to specify a custom series name</li>
<li>aliasByNode(2) to alias by a specific part of your metric path</li>
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
</ul>
</div>
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
<h5>Series as parameter</h5>
<ul>
<li>Some graphite functions allow you to have many series arguments</li>
<li>Use #[A-Z] to use a graphite query as parameter to a function</li>
<li>
Examples:
<ul>
<li>asPercent(#A, #B)</li>
<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
<li>divideSeries(#A, #B)</li>
</ul>
</li>
<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Stacking</h5>
<ul>
<li>You find the stacking option under Display Styles tab</li>
<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
<h5>Templating</h5>
<ul>
<li>You can use a template variable in place of metric names</li>
<li>You can use a template variable in place of function parameters</li>
<li>You enable the templating feature in Dashboard settings / Feature toggles </li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
<h5>Max data points</h5>
<ul>
<li>Every graphite request is issued with a maxDataPoints parameter</li>
<li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
<li>If there are more real values, then by default they will be consolidated using averages</li>
<li>This could hide real peaks and max values in your series</li>
<li>You can change how point consolidation is made using the consolidateBy graphite function</li>
<li>Point consolidation will effect series legend values (min,max,total,current)</li>
<li>If you override maxDataPoint and set a high value performance can be severely effected</li>
</ul>
</div>
</div>
</div>

View File

@ -8,9 +8,7 @@
"module": "plugins/datasource/graphite/datasource",
"partials": {
"config": "app/plugins/datasource/graphite/partials/config.html",
"query": "app/plugins/datasource/graphite/partials/query.editor.html",
"annotations": "app/plugins/datasource/graphite/partials/annotations.editor.html"
"config": "app/plugins/datasource/graphite/partials/config.html"
},
"metrics": true,

View File

@ -9,15 +9,14 @@ function (angular, _, config, gfunc, Parser) {
'use strict';
var module = angular.module('grafana.controllers');
var targetLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
module.controller('GraphiteQueryCtrl', function($scope, $sce, templateSrv) {
$scope.init = function() {
$scope.target.target = $scope.target.target || '';
$scope.targetLetters = targetLetters;
parseTarget();
if ($scope.target) {
$scope.target.target = $scope.target.target || '';
parseTarget();
}
};
$scope.toggleEditorMode = function() {
@ -313,22 +312,8 @@ function (angular, _, config, gfunc, Parser) {
return new MetricSegment({value: 'select metric', fake: true});
};
});
$scope.init();
module.directive('focusMe', function($timeout, $parse) {
return {
//scope: true, // optionally create a child scope
link: function(scope, element, attrs) {
var model = $parse(attrs.focusMe);
scope.$watch(model, function(value) {
if(value === true) {
$timeout(function() {
element[0].focus();
});
}
});
}
};
});
});

View File

@ -4,6 +4,7 @@ define([
'kbn',
'./influxSeries',
'./queryBuilder',
'./directives',
'./queryCtrl',
'./funcEditor',
],

View File

@ -0,0 +1,21 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('metricQueryEditorInfluxdb', function() {
return {controller: 'InfluxQueryCtrl', templateUrl: 'app/plugins/datasource/influxdb/partials/query.editor.html'};
});
module.directive('metricQueryOptionsInfluxdb', function() {
return {templateUrl: 'app/plugins/datasource/influxdb/partials/query.options.html'};
});
module.directive('annotationsQueryEditorInfluxdb', function() {
return {templateUrl: 'app/plugins/datasource/influxdb/partials/annotations.editor.html'};
});
});

View File

@ -1,65 +1,56 @@
<div class="editor-row">
<div ng-repeat="target in panel.targets" ng-controller="InfluxQueryCtrl" ng-init="init()" ng-class="{'tight-form-disabled': target.hide}" class="tight-form-container-no-item-borders" style="margin-bottom: 10px">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li ng-show="parserError" class="tight-form-item">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="fa fa-warning"></i>
<div class="tight-form-container-no-item-borders">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li ng-show="parserError" class="tight-form-item">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item small" ng-show="target.datasource">
<em>{{target.datasource}}</em>
</li>
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
</li>
<li class="tight-form-item">
<a class="pointer" tabindex="1" ng-click="toggleQueryMode()">
<i class="fa fa-pencil"></i>
</a>
</li>
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
<ul class="dropdown-menu pull-right" role="menu">
<a tabindex="1" ng-click="toggleEditorMode()">
Switch editor mode
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index-1)">
Move up
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index+1)">
Move down
</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<li role="menuitem">
<a tabindex="1" ng-click="duplicate()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{target.refId}}
</li>
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<input type="text" class="tight-form-clear-input" style="width: 80%" ng-model="target.query" focus-me="target.rawQuery" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.rawQuery"/>
<input type="text" class="tight-form-clear-input" style="width: 80%" ng-model="target.query" give-focus="target.rawQuery" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.rawQuery"/>
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
<li class="tight-form-item query-keyword" style="width: 75px;">
@ -79,10 +70,7 @@
<div class="tight-form" ng-hide="target.rawQuery">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item query-keyword" style="width: 75px;">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
FROM
</li>
<li>
@ -95,11 +83,7 @@
<div class="tight-form" ng-hide="target.rawQuery">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item query-keyword" style="width: 75px;">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
WHERE
</li>
@ -112,11 +96,7 @@
<div class="tight-form">
<ul class="tight-form-list" ng-hide="target.rawQuery">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item query-keyword">
<li class="tight-form-item query-keyword tight-form-align">
GROUP BY
</li>
@ -150,7 +130,9 @@
Alias pattern
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.alias" spellcheck='false' placeholder="alias" ng-model-onblur ng-change="get_data()">
<input type="text" class="input-medium tight-form-input"
ng-model="target.alias" spellcheck='false' placeholder="alias"
ng-model-onblur ng-change="get_data()"></input>
</li>
</ul>
<div class="clearfix"></div>
@ -159,90 +141,3 @@
</div>
</div>
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
Group by time interval
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking &amp; and fill
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
group by time
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul>
<li>$m = replaced with measurement name</li>
<li>$measurement = replaced with measurement name</li>
<li>$col = replaced with column name</li>
<li>$tag_hostname = replaced with the value of the hostname tag</li>
<li>You can also use [[tag_hostname]] pattern replacement syntax</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
<h5>Stacking and fill</h5>
<ul>
<li>When stacking is enabled it important that points align</li>
<li>If there are missing points for one series it can cause gaps or missing bars</li>
<li>You must use fill(0), and select a group by time low limit</li>
<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
<li>The low limit can only be set in the group by time option below your queries</li>
<li>You set a low limit by adding a greater sign before the interval</li>
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,87 @@
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
Group by time interval
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking &amp; and fill
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
group by time
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul>
<li>$m = replaced with measurement name</li>
<li>$measurement = replaced with measurement name</li>
<li>$col = replaced with column name</li>
<li>$tag_hostname = replaced with the value of the hostname tag</li>
<li>You can also use [[tag_hostname]] pattern replacement syntax</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
<h5>Stacking and fill</h5>
<ul>
<li>When stacking is enabled it important that points align</li>
<li>If there are missing points for one series it can cause gaps or missing bars</li>
<li>You must use fill(0), and select a group by time low limit</li>
<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
<li>The low limit can only be set in the group by time option below your queries</li>
<li>You set a low limit by adding a greater sign before the interval</li>
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</div>

View File

@ -8,9 +8,7 @@
"module": "plugins/datasource/influxdb/datasource",
"partials": {
"config": "app/plugins/datasource/influxdb/partials/config.html",
"query": "app/plugins/datasource/influxdb/partials/query.editor.html",
"annotations": "app/plugins/datasource/influxdb/partials/annotations.editor.html"
"config": "app/plugins/datasource/influxdb/partials/config.html"
},
"metrics": true,

View File

@ -11,6 +11,8 @@ function (angular, _, InfluxQueryBuilder) {
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q) {
$scope.init = function() {
if (!$scope.target) { return; }
var target = $scope.target;
target.tags = target.tags || [];
target.groupByTags = target.groupByTags || [];
@ -337,6 +339,8 @@ function (angular, _, InfluxQueryBuilder) {
return new MetricSegment({value: 'select tag value', fake: true});
};
$scope.init();
});
});

View File

@ -4,6 +4,7 @@ define([
'kbn',
'./influxSeries',
'./queryBuilder',
'./directives',
'./queryCtrl',
'./funcEditor',
],

View File

@ -0,0 +1,21 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('metricQueryEditorInfluxdb08', function() {
return {controller: 'InfluxQueryCtrl_08', templateUrl: 'app/plugins/datasource/influxdb_08/partials/query.editor.html'};
});
module.directive('metricQueryOptionsInfluxdb08', function() {
return {templateUrl: 'app/plugins/datasource/influxdb_08/partials/query.options.html'};
});
module.directive('annotationsQueryEditorInfluxdb08', function() {
return {templateUrl: 'app/plugins/datasource/influxdb_08/partials/annotations.editor.html'};
});
});

View File

@ -1,256 +1,165 @@
<div class="editor-row">
<div ng-repeat="target in panel.targets" ng-controller="InfluxQueryCtrl_08" ng-init="init()" ng-class="{'tight-form-disabled': target.hide}" class="tight-form-container">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<!-- Raw Query mode -->
<ul class="tight-form-list" ng-show="target.rawQuery">
<li>
<input type="text"
class="tight-form-input span10"
ng-model="target.query"
placeholder="select ..."
focus-me="target.rawQuery"
spellcheck='false'
data-min-length=0 data-items=100
ng-model-onblur
ng-blur="get_data()">
</li>
</ul>
<!-- Raw Query mode -->
<ul class="tight-form-list" ng-show="target.rawQuery">
<li>
<input type="text"
class="tight-form-input span10"
ng-model="target.query"
placeholder="select ..."
give-focus="target.rawQuery"
spellcheck='false'
data-min-length=0 data-items=100
ng-model-onblur
ng-blur="get_data()">
</li>
</ul>
<!-- Query editor mode -->
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
<li class="tight-form-item">
series
</li>
<li>
<input type="text"
class="tight-form-input span8"
ng-model="target.series"
spellcheck='false'
bs-typeahead="listSeries"
match-all="true"
min-length="3"
placeholder="series name"
data-min-length=0 data-items=100
ng-blur="seriesBlur()">
</li>
<!-- Query editor mode -->
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
<li class="tight-form-item">
series
</li>
<li>
<input type="text"
class="tight-form-input span8"
ng-model="target.series"
spellcheck='false'
bs-typeahead="listSeries"
match-all="true"
min-length="3"
placeholder="series name"
data-min-length=0 data-items=100
ng-blur="seriesBlur()">
</li>
<li class="tight-form-item">
alias
</li>
<li class="tight-form-item">
alias
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
spellcheck='false' placeholder="alias" ng-blur="get_data()">
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
spellcheck='false' placeholder="alias" ng-blur="get_data()">
</li>
</ul>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<!-- Raw Query mode -->
<ul class="tight-form-list" ng-show="target.rawQuery">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item">
alias
</li>
<li>
<input type="text"
class="input-medium tight-form-input"
ng-model="target.alias"
spellcheck='false'
placeholder="alias"
ng-blur="get_data()">
</li>
<li class="tight-form-item">
group by time
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
spellcheck='false' placeholder="{{interval}}" data-placement="right"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
ng-model-onblur ng-change="get_data()" >
</li>
</ul>
<!-- Query editor mode -->
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item">
select
</li>
<li class="dropdown">
<span influxdb-func-editor08 class="tight-form-item tight-form-func">
</span>
</li>
<li class="tight-form-item">
where
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.condition"
bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
</li>
<li class="tight-form-item">
group by time
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
spellcheck='false' placeholder="{{interval}}" data-placement="right"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
ng-model-onblur ng-change="get_data()" >
</li>
<li class="tight-form-item">
and
</li>
<li>
<input type="text" class="input-small tight-form-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
</li>
<li class="dropdown">
<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
<span ng-show="target.fill">
fill ({{target.fill}})
</span>
<span ng-show="!target.fill">
no fill
</span>
</a>
<ul class="dropdown-menu">
<li><a ng-click="target.fill = ''">no fill</a></li>
<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
<li><a ng-click="target.fill = '0'">fill (0)</a></li>
</ul>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="clearfix"></div>
</div>
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
group by time
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<!-- Raw Query mode -->
<ul class="tight-form-list" ng-show="target.rawQuery">
<li class="tight-form-item tight-form-align">
alias
</li>
<li>
<input type="text"
class="input-medium tight-form-input"
ng-model="target.alias"
spellcheck='false'
placeholder="alias"
ng-blur="get_data()">
</li>
<li class="tight-form-item">
group by time
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
spellcheck='false' placeholder="{{interval}}" data-placement="right"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
ng-model-onblur ng-change="get_data()" >
</li>
</ul>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking &amp; and fill
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
group by time
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
<!-- Query editor mode -->
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
<li class="tight-form-item tight-form-align">
select
</li>
<li class="dropdown">
<span influxdb-func-editor08 class="tight-form-item tight-form-func">
</span>
</li>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<li class="tight-form-item">
where
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.condition"
bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
</li>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul>
<li>$s = series name</li>
<li>$g = group by</li>
<li>$[0-9] part of series name for series names seperated by dots.</li>
<li class="tight-form-item">
group by time
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
spellcheck='false' placeholder="{{interval}}" data-placement="right"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
ng-model-onblur ng-change="get_data()" >
</li>
<li class="tight-form-item">
and
</li>
<li>
<input type="text" class="input-small tight-form-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
</li>
<li class="dropdown">
<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
<span ng-show="target.fill">
fill ({{target.fill}})
</span>
<span ng-show="!target.fill">
no fill
</span>
</a>
<ul class="dropdown-menu">
<li><a ng-click="target.fill = ''">no fill</a></li>
<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
<li><a ng-click="target.fill = '0'">fill (0)</a></li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
<h5>Stacking and fill</h5>
<ul>
<li>When stacking is enabled it important that points align</li>
<li>If there are missing points for one series it can cause gaps or missing bars</li>
<li>You must use fill(0), and select a group by time low limit</li>
<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
<li>The low limit can only be set in the group by time option below your queries</li>
<li>You set a low limit by adding a greater sign before the interval</li>
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</li>
</ul>
<div class="clearfix"></div>
</div>

View File

@ -0,0 +1,85 @@
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
group by time
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking &amp; and fill
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
group by time
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul>
<li>$s = series name</li>
<li>$g = group by</li>
<li>$[0-9] part of series name for series names seperated by dots.</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
<h5>Stacking and fill</h5>
<ul>
<li>When stacking is enabled it important that points align</li>
<li>If there are missing points for one series it can cause gaps or missing bars</li>
<li>You must use fill(0), and select a group by time low limit</li>
<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
<li>The low limit can only be set in the group by time option below your queries</li>
<li>You set a low limit by adding a greater sign before the interval</li>
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</div>

View File

@ -8,9 +8,7 @@
"module": "plugins/datasource/influxdb_08/datasource",
"partials": {
"config": "app/plugins/datasource/influxdb_08/partials/config.html",
"query": "app/plugins/datasource/influxdb_08/partials/query.editor.html",
"annotations": "app/plugins/datasource/influxdb_08/partials/annotations.editor.html"
"config": "app/plugins/datasource/influxdb_08/partials/config.html"
},
"metrics": true,

View File

@ -3,6 +3,7 @@ define([
'lodash',
'kbn',
'./queryCtrl',
'./directives',
],
function (angular, _, kbn) {
'use strict';

View File

@ -0,0 +1,17 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('metricQueryEditorKairosdb', function() {
return {controller: 'KairosDBQueryCtrl', templateUrl: 'app/plugins/datasource/kairosdb/partials/query.editor.html'};
});
module.directive('metricQueryOptionsKairosdb', function() {
return {templateUrl: 'app/plugins/datasource/kairosdb/partials/query.options.html'};
});
});

View File

@ -1,384 +1,328 @@
<div class="editor-row">
<div ng-repeat="target in panel.targets"
class="tight-form-container"
ng-class="{'tight-form-disabled': target.hide}"
ng-controller="KairosDBQueryCtrl"
ng-init="init()">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; targetBlur();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
<li class="tight-form-item">
Metric
</li>
<li>
<input type="text" class="input-large tight-form-input"
ng-model="target.metric"
spellcheck="false"
bs-typeahead="suggestMetrics"
placeholder="metric name"
data-min-length=0 data-items=100
ng-blur="targetBlur()">
<a bs-tooltip="target.errors.metric"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.metric">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
Alias
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
spellcheck='false' placeholder="alias" ng-blur="targetBlur()">
</li>
<li class="tight-form-item">
&nbsp;Peak filter
<input class="input-medium" type="checkbox" ng-model="target.exOuter" ng-change="targetBlur()">
</li>
</ul>
<ul class="tight-form-list">
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; targetBlur();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
<li class="tight-form-item">
Metric
</li>
<li>
<input type="text"
class="input-large tight-form-input"
ng-model="target.metric"
spellcheck="false"
bs-typeahead="suggestMetrics"
placeholder="metric name"
data-min-length=0 data-items=100
ng-blur="targetBlur()"
>
<a bs-tooltip="target.errors.metric"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.metric">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
Alias
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
spellcheck='false' placeholder="alias" ng-blur="targetBlur()">
</li>
<li class="tight-form-item">
&nbsp;Peak filter
<input class="input-medium" type="checkbox" ng-model="target.exOuter" ng-change="targetBlur()">
</li>
</ul>
<div class="clearfix"></div>
</div>
<!-- TAGS -->
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item">
Tags
</li>
<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="removeFilterTag(key)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addFilterTagMode">
<a ng-click="addFilterTag()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addFilterTagMode">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagKeys"
ng-change="validateFilterTag()"
data-min-length=0 data-items=100
ng-model="target.currentTagKey"
placeholder="key">
</li>
<li ng-show="addFilterTagMode">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagValues"
ng-change="validateFilterTag()"
data-min-length=0 data-items=100
ng-model="target.currentTagValue"
placeholder="value">
<a bs-tooltip="target.errors.tags"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.tags">
<i class="fa fa-warning"></i>
</a>
<li class="tight-form-item" ng-show="addFilterTagMode">
<a ng-click="addFilterTag()">
<i ng-show="target.errors.tags" class="fa fa-remove"></i>
<i ng-hide="target.errors.tags" class="fa fa-plus-circle"></i>
</a>
</li>
</li>
</ul>
<div class="clearfix"></div>
</div>
<!-- GROUP BY -->
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item">
Group By
</li>
<li class="tight-form-item" ng-show="target.groupByTags">
tags:
</li>
<li ng-repeat="key in target.groupByTags track by $index" class="tight-form-item">
{{key}}
<a ng-click="removeGroupByTag($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-show="target.groupByTags && target.nonTagGroupBys">
and by:
</li>
<li ng-repeat="groupByObject in target.nonTagGroupBys track by $index" class="tight-form-item">
{{_.values(groupByObject)}}
<a ng-click="removeNonTagGroupBy($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addGroupByMode">
<a ng-click="addGroupBy()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addGroupByMode">
<select class="input-small tight-form-input"
ng-change="changeGroupByInput()"
ng-model="target.currentGroupByType"
ng-options="f for f in ['tag','value','time']"></select>
</li>
<li ng-show="isTagGroupBy">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagKeys"
ng-change = "validateGroupBy()"
data-min-length=0 data-items=100
ng-model="target.groupBy.tagKey"
placeholder="key">
<a bs-tooltip="target.errors.groupBy.tagKey"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.tagKey">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="isValueGroupBy">
<input type="text"
class="input-mini tight-form-input"
spellcheck='false'
ng-model="target.groupBy.valueRange"
placeholder="range"
bs-tooltip="'Range on which values are considered in the same group'"
ng-change = "validateGroupBy()" >
<a bs-tooltip="target.errors.groupBy.valueRange"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.valueRange">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="isTimeGroupBy">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.groupBy.timeInterval"
ng-init="target.groupBy.timeInterval='1s'"
placeholder="interval"
bs-tooltip="'Duration of time groups'"
spellcheck='false'
ng-change="validateGroupBy()">
<a bs-tooltip="target.errors.groupBy.timeInterval"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.timeInterval">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="isTimeGroupBy">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.groupBy.groupCount"
placeholder="Count"
bs-tooltip="'Number of time groups to be formed'"
spellcheck='false'
ng-change="validateGroupBy()">
<a bs-tooltip="target.errors.groupBy.groupCount"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.groupCount">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item" ng-show="addGroupByMode">
<a ng-click="addGroupBy()">
<i ng-hide="isGroupByValid" class="fa fa-remove"></i>
<i ng-show="isGroupByValid" class="fa fa-plus-circle"></i>
</a>
</li>
<div class="clearfix"></div>
</div>
<!-- HORIZONTAL AGGREGATION -->
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item">
Aggregators
</li>
<li ng-repeat="aggregatorObject in target.horizontalAggregators track by $index" class="tight-form-item">
{{aggregatorObject.name}}&#40;
<span ng-repeat="aggKey in _.keys(_.omit(aggregatorObject,'name'))" bs-tooltip="aggKey">
{{$last?aggregatorObject[aggKey]:aggregatorObject[aggKey]+","}}
</span>
&#41;
<a ng-click="removeHorizontalAggregator($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addHorizontalAggregatorMode">
<a ng-click="addHorizontalAggregator()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addHorizontalAggregatorMode">
<select class="input-medium tight-form-input"
ng-change="changeHorAggregationInput()"
ng-model="target.currentHorizontalAggregatorName"
ng-options="f for f in ['avg','dev','max','min','rate','sampler','count','sum','least_squares','percentile','scale','div']"></select>
</li>
<!-- Different parameters -->
<li ng-show="hasSamplingRate" class="tight-form-item">
every
</li>
<li ng-show="hasSamplingRate">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.horAggregator.samplingRate"
ng-init="target.horAggregator.samplingRate='1s'"
spellcheck='false'
ng-change="validateHorizontalAggregator()" >
<a bs-tooltip="target.errors.horAggregator.samplingRate"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.horAggregator.samplingRate">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="hasUnit" class="tight-form-item">
every
</li>
<li ng-show="hasUnit">
<select class="input-medium tight-form-input"
ng-model="target.horAggregator.unit"
ng-init="target.horAggregator.unit='millisecond'"
ng-options="f for f in ['millisecond','second','minute','hour','day','week','month','year']"></select>
</li>
<li ng-show="hasFactor" class="tight-form-item">
by
</li>
<li ng-show="hasFactor">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.horAggregator.factor"
ng-init="target.horAggregator.factor='1'"
spellcheck='false'
ng-change="validateHorizontalAggregator()" >
<a bs-tooltip="target.errors.horAggregator.factor"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.horAggregator.factor">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="hasPercentile" class="tight-form-item">
percentile
</li>
<li ng-show="hasPercentile">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.horAggregator.percentile"
ng-init="target.horAggregator.percentile='0.75'"
spellcheck='false'
ng-change="validateHorizontalAggregator()" >
<a bs-tooltip="target.errors.horAggregator.percentile"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.horAggregator.percentile">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item" ng-show="addHorizontalAggregatorMode">
<a ng-click="addHorizontalAggregator()">
<i ng-hide="isAggregatorValid" class="fa fa-remove"></i>
<i ng-show="isAggregatorValid" class="fa fa-plus-circle"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="clearfix"></div>
</div>
<section class="grafana-metric-options" ng-controller="KairosDBQueryCtrl">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<!-- TAGS -->
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align">
Tags
</li>
<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="removeFilterTag(key)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item">
Downsampling with
</li>
<li>
<select class="input-medium tight-form-input" ng-change="panelBlur()" ng-model="panel.downsampling" ng-options="f for f in ['(NONE)','avg', 'sum', 'min', 'max', 'dev']" ></select>
</li>
<li class="tight-form-item" ng-hide="addFilterTagMode">
<a ng-click="addFilterTag()">
<i class="fa fa-plus"></i>
</a>
</li>
<!-- SAMPLING RATE -->
<li ng-hide="panel.downsampling=='(NONE)'" class="tight-form-item">
every
</li>
<li>
<input type="text"
ng-hide="panel.downsampling=='(NONE)'"
class="input-mini tight-form-input"
ng-model="panel.sampling"
placeholder="{{interval}}"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
spellcheck='false'
ng-blur="panelBlur()" >
<a bs-tooltip="target.errors.sampling"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.sampling">
<i class="fa fa-warning"></i>
<li ng-show="addFilterTagMode">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagKeys"
ng-change="validateFilterTag()"
data-min-length=0 data-items=100
ng-model="target.currentTagKey"
placeholder="key">
</li>
<li ng-show="addFilterTagMode">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagValues"
ng-change="validateFilterTag()"
data-min-length=0 data-items=100
ng-model="target.currentTagValue"
placeholder="value">
<a bs-tooltip="target.errors.tags"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.tags">
<i class="fa fa-warning"></i>
</a>
<li class="tight-form-item" ng-show="addFilterTagMode">
<a ng-click="addFilterTag()">
<i ng-show="target.errors.tags" class="fa fa-remove"></i>
<i ng-hide="target.errors.tags" class="fa fa-plus-circle"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>
</li>
</ul>
<div class="clearfix"></div>
</div>
<!-- GROUP BY -->
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align">
Group By
</li>
<li class="tight-form-item" ng-show="target.groupByTags">
tags:
</li>
<li ng-repeat="key in target.groupByTags track by $index" class="tight-form-item">
{{key}}
<a ng-click="removeGroupByTag($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-show="target.groupByTags && target.nonTagGroupBys">
and by:
</li>
<li ng-repeat="groupByObject in target.nonTagGroupBys track by $index" class="tight-form-item">
{{_.values(groupByObject)}}
<a ng-click="removeNonTagGroupBy($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addGroupByMode">
<a ng-click="addGroupBy()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addGroupByMode">
<select class="input-small tight-form-input"
ng-change="changeGroupByInput()"
ng-model="target.currentGroupByType"
ng-options="f for f in ['tag','value','time']"></select>
</li>
<li ng-show="isTagGroupBy">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagKeys"
ng-change = "validateGroupBy()"
data-min-length=0 data-items=100
ng-model="target.groupBy.tagKey"
placeholder="key">
<a bs-tooltip="target.errors.groupBy.tagKey"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.tagKey">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="isValueGroupBy">
<input type="text"
class="input-mini tight-form-input"
spellcheck='false'
ng-model="target.groupBy.valueRange"
placeholder="range"
bs-tooltip="'Range on which values are considered in the same group'"
ng-change = "validateGroupBy()" >
<a bs-tooltip="target.errors.groupBy.valueRange"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.valueRange">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="isTimeGroupBy">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.groupBy.timeInterval"
ng-init="target.groupBy.timeInterval='1s'"
placeholder="interval"
bs-tooltip="'Duration of time groups'"
spellcheck='false'
ng-change="validateGroupBy()">
<a bs-tooltip="target.errors.groupBy.timeInterval"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.timeInterval">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="isTimeGroupBy">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.groupBy.groupCount"
placeholder="Count"
bs-tooltip="'Number of time groups to be formed'"
spellcheck='false'
ng-change="validateGroupBy()">
<a bs-tooltip="target.errors.groupBy.groupCount"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.groupBy.groupCount">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item" ng-show="addGroupByMode">
<a ng-click="addGroupBy()">
<i ng-hide="isGroupByValid" class="fa fa-remove"></i>
<i ng-show="isGroupByValid" class="fa fa-plus-circle"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<!-- HORIZONTAL AGGREGATION -->
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align">
Aggregators
</li>
<li ng-repeat="aggregatorObject in target.horizontalAggregators track by $index" class="tight-form-item">
{{aggregatorObject.name}}&#40;
<span ng-repeat="aggKey in _.keys(_.omit(aggregatorObject,'name'))" bs-tooltip="aggKey">
{{$last?aggregatorObject[aggKey]:aggregatorObject[aggKey]+","}}
</span>
&#41;
<a ng-click="removeHorizontalAggregator($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addHorizontalAggregatorMode">
<a ng-click="addHorizontalAggregator()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addHorizontalAggregatorMode">
<select class="input-medium tight-form-input"
ng-change="changeHorAggregationInput()"
ng-model="target.currentHorizontalAggregatorName"
ng-options="f for f in ['avg','dev','max','min','rate','sampler','count','sum','least_squares','percentile','scale','div']"></select>
</li>
<!-- Different parameters -->
<li ng-show="hasSamplingRate" class="tight-form-item">
every
</li>
<li ng-show="hasSamplingRate">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.horAggregator.samplingRate"
ng-init="target.horAggregator.samplingRate='1s'"
spellcheck='false'
ng-change="validateHorizontalAggregator()" >
<a bs-tooltip="target.errors.horAggregator.samplingRate"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.horAggregator.samplingRate">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="hasUnit" class="tight-form-item">
every
</li>
<li ng-show="hasUnit">
<select class="input-medium tight-form-input"
ng-model="target.horAggregator.unit"
ng-init="target.horAggregator.unit='millisecond'"
ng-options="f for f in ['millisecond','second','minute','hour','day','week','month','year']"></select>
</li>
<li ng-show="hasFactor" class="tight-form-item">
by
</li>
<li ng-show="hasFactor">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.horAggregator.factor"
ng-init="target.horAggregator.factor='1'"
spellcheck='false'
ng-change="validateHorizontalAggregator()" >
<a bs-tooltip="target.errors.horAggregator.factor"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.horAggregator.factor">
<i class="fa fa-warning"></i>
</a>
</li>
<li ng-show="hasPercentile" class="tight-form-item">
percentile
</li>
<li ng-show="hasPercentile">
<input type="text"
class="input-mini tight-form-input"
ng-model="target.horAggregator.percentile"
ng-init="target.horAggregator.percentile='0.75'"
spellcheck='false'
ng-change="validateHorizontalAggregator()" >
<a bs-tooltip="target.errors.horAggregator.percentile"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.horAggregator.percentile">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item" ng-show="addHorizontalAggregatorMode">
<a ng-click="addHorizontalAggregator()">
<i ng-hide="isAggregatorValid" class="fa fa-remove"></i>
<i ng-show="isAggregatorValid" class="fa fa-plus-circle"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>

View File

@ -0,0 +1,37 @@
<section class="grafana-metric-options" ng-controller="KairosDBQueryCtrl">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
Downsampling with
</li>
<li>
<select class="input-medium tight-form-input" ng-change="panelBlur()" ng-model="panel.downsampling" ng-options="f for f in ['(NONE)','avg', 'sum', 'min', 'max', 'dev']" ></select>
</li>
<!-- SAMPLING RATE -->
<li ng-hide="panel.downsampling=='(NONE)'" class="tight-form-item">
every
</li>
<li>
<input type="text"
ng-hide="panel.downsampling=='(NONE)'"
class="input-mini tight-form-input"
ng-model="panel.sampling"
placeholder="{{interval}}"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
spellcheck='false'
ng-blur="panelBlur()" >
<a bs-tooltip="target.errors.sampling"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.sampling">
<i class="fa fa-warning"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</section>

View File

@ -8,8 +8,7 @@
"module": "plugins/datasource/kairosdb/datasource",
"partials": {
"config": "app/plugins/datasource/kairosdb/partials/config.html",
"query": "app/plugins/datasource/kairosdb/partials/query.editor.html"
"config": "app/plugins/datasource/kairosdb/partials/config.html"
},
"metrics": true,

View File

@ -0,0 +1,35 @@
define([
'angular',
'lodash',
],
function (angular, _) {
'use strict';
var module = angular.module('grafana.services');
module.factory('MixedDatasource', function($q, backendSrv, datasourceSrv) {
function MixedDatasource() {
}
MixedDatasource.prototype.query = function(options) {
var sets = _.groupBy(options.targets, 'datasource');
var promises = _.map(sets, function(targets) {
return datasourceSrv.get(targets[0].datasource).then(function(ds) {
var opt = angular.copy(options);
opt.targets = targets;
return ds.query(opt);
});
});
return $q.all(promises).then(function(results) {
return { data: _.flatten(_.pluck(results, 'data')) };
});
};
return MixedDatasource;
});
});

View File

@ -0,0 +1,12 @@
{
"pluginType": "datasource",
"name": "Mixed datasource",
"builtIn": true,
"mixed": true,
"type": "mixed",
"serviceName": "MixedDatasource",
"module": "plugins/datasource/mixed/datasource",
"metrics": true
}

View File

@ -3,6 +3,7 @@ define([
'lodash',
'kbn',
'moment',
'./directives',
'./queryCtrl',
],
function (angular, _, kbn) {

View File

@ -0,0 +1,16 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.directives');
module.directive('metricQueryEditorOpentsdb', function() {
return {
controller: 'OpenTSDBQueryCtrl',
templateUrl: 'app/plugins/datasource/opentsdb/partials/query.editor.html',
};
});
});

View File

@ -1,241 +1,208 @@
<div class="editor-row" style="margin-top: 10px;">
<div ng-repeat="target in panel.targets"
style="margin-bottom: 10px;"
ng-class="{'tight-form-disabled': target.hide}"
ng-controller="OpenTSDBQueryCtrl"
ng-init="init()">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{target.refId}}
</li>
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<ul class="tight-form-list" role="menu">
<li class="tight-form-item" style="width: 86px">
Metric
</li>
<li>
<input type="text"
class="input-large tight-form-input"
ng-model="target.metric"
spellcheck='false'
bs-typeahead="suggestMetrics"
placeholder="metric name"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="targetBlur()"
>
<a bs-tooltip="target.errors.metric"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.metric">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
Aggregator
</li>
<li>
<select ng-model="target.aggregator"
class="tight-form-input input-small"
ng-options="agg for agg in aggregators"
ng-change="targetBlur()">
</select>
<a bs-tooltip="target.errors.aggregator"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.aggregator">
<i class="fa fa-warning"></i>
</a>
</li>
<ul class="tight-form-list" role="menu">
<li class="tight-form-item" style="width: 86px">
Metric
</li>
<li>
<input type="text"
class="input-large tight-form-input"
ng-model="target.metric"
spellcheck='false'
bs-typeahead="suggestMetrics"
placeholder="metric name"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="targetBlur()">
</input>
<a bs-tooltip="target.errors.metric" style="color: rgb(229, 189, 28)" ng-show="target.errors.metric">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
Aggregator
</li>
<li>
<select ng-model="target.aggregator" class="tight-form-input input-small"
ng-options="agg for agg in aggregators"
ng-change="targetBlur()">
</select>
<a bs-tooltip="target.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="target.errors.aggregator">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item">
Alias:
<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip>
</li>
<li>
<li class="tight-form-item">
Alias:
<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip>
</li>
<li>
<input type="text" class="tight-form-input input-large"
ng-model="target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="targetBlur()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align" style="width: 86px">
Down sample
</li>
<li>
<input type="text" class="input-large tight-form-input"
ng-model="target.downsampleInterval"
ng-model-onblur
ng-change="targetBlur()"
placeholder="interval (empty = auto)">
</li>
<li class="tight-form-item">
Aggregator
</li>
<li>
<select ng-model="target.downsampleAggregator" class="tight-form-input input-small"
ng-options="agg for agg in aggregators"
ng-change="targetBlur()">
</select>
</li>
<li class="tight-form-item">
<editor-checkbox text="Disable downsampling" model="target.disableDownsampling" change="targetBlur()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align" style="width: 86px">
Tags
</li>
<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addTagMode">
<a ng-click="addTag()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addTagMode">
<input type="text" class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagKeys"
data-min-length=0 data-items=100
ng-model="target.currentTagKey"
placeholder="key">
<input type="text"
class="tight-form-input input-large"
ng-model="target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="targetBlur()"
/>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagValues"
data-min-length=0 data-items=100
ng-model="target.currentTagValue"
placeholder="value">
</input>
<a ng-click="addTag()">
add tag
</a>
<a bs-tooltip="target.errors.tags"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.tags">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item" style="width: 86px">
Down sample
</li>
<li>
<input type="text"
class="input-large tight-form-input"
ng-model="target.downsampleInterval"
ng-model-onblur
ng-change="targetBlur()"
placeholder="interval (empty = auto)"
>
</li>
<li class="tight-form-item">
Aggregator
</li>
<li>
<select ng-model="target.downsampleAggregator"
class="tight-form-input input-small"
ng-options="agg for agg in aggregators"
ng-change="targetBlur()">
</select>
</li>
<li class="tight-form-item">
<editor-checkbox text="Disable downsampling" model="target.disableDownsampling" change="targetBlur()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item" style="width: 86px">
Tags
</li>
<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item" ng-hide="addTagMode">
<a ng-click="addTag()">
<i class="fa fa-plus"></i>
</a>
</li>
<li ng-show="addTagMode">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagKeys"
data-min-length=0 data-items=100
ng-model="target.currentTagKey"
placeholder="key">
<input type="text"
class="input-small tight-form-input"
spellcheck='false'
bs-typeahead="suggestTagValues"
data-min-length=0 data-items=100
ng-model="target.currentTagValue"
placeholder="value">
<a ng-click="addTag()">
add tag
</a>
<a bs-tooltip="target.errors.tags"
style="color: rgb(229, 189, 28)"
ng-show="target.errors.tags">
<i class="fa fa-warning"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-eye invisible"></i>
</li>
<li class="tight-form-item" style="width: 86px">
<li class="tight-form-item tight-form-align" style="width: 86px">
<editor-checkbox text="Rate" model="target.shouldComputeRate" change="targetBlur()"></editor-checkbox>
</li>
<li class="tight-form-item" ng-hide="!target.shouldComputeRate">
<li class="tight-form-item" ng-hide="!target.shouldComputeRate">
<editor-checkbox text="Counter" model="target.isCounter" change="targetBlur()"></editor-checkbox>
</li>
<li class="tight-form-item" ng-hide="!target.isCounter">
Counter Max:
</li>
Counter Max:
</li>
<li ng-hide="!target.isCounter">
<input type="text"
class="tight-form-input input-small"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterMax"
spellcheck='false'
placeholder="max value"
ng-model-onblur
ng-blur="targetBlur()"
/>
</li>
<li class="tight-form-item" ng-hide="!target.isCounter">
Reset Value:
</li>
<li ng-hide="!target.isCounter">
<input type="text"
class="tight-form-input input-small"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterResetValue"
spellcheck='false'
placeholder="reset value"
ng-model-onblur
ng-blur="targetBlur()"
/>
</li>
<li ng-hide="!target.isCounter">
<input type="text" class="tight-form-input input-small"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterMax"
spellcheck='false'
placeholder="max value"
ng-model-onblur
ng-blur="targetBlur()"></input>
</li>
<li class="tight-form-item" ng-hide="!target.isCounter">
Reset Value:
</li>
<li ng-hide="!target.isCounter">
<input type="text" class="tight-form-input input-small"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterResetValue"
spellcheck='false'
placeholder="reset value"
ng-model-onblur
ng-blur="targetBlur()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@ -8,8 +8,7 @@
"module": "plugins/datasource/opentsdb/datasource",
"partials": {
"config": "app/plugins/datasource/opentsdb/partials/config.html",
"query": "app/plugins/datasource/opentsdb/partials/query.editor.html"
"config": "app/plugins/datasource/opentsdb/partials/config.html"
},
"metrics": true

View File

@ -113,6 +113,7 @@ function (angular, _, kbn) {
return errs;
}
$scope.init();
});
});

View File

@ -20,13 +20,24 @@ function (angular, _, config) {
if (value.meta && value.meta.metrics) {
self.metricSources.push({
value: key === config.defaultDatasource ? null : key,
name: key
name: key,
meta: value.meta,
});
}
if (value.meta && value.meta.annotations) {
self.annotationSources.push(value);
}
});
this.metricSources.sort(function(a, b) {
if (a.meta.builtIn || a.name > b.name) {
return 1;
}
if (a.name < b.name) {
return -1;
}
return 0;
});
};
this.get = function(name) {

View File

@ -358,7 +358,6 @@ div.subnav {
// BUTTONS
// -----------------------------------------------------
.btn {
padding: 5px 12px;
background-image: none;
@ -389,13 +388,6 @@ div.subnav {
}
.btn-group {
& > .btn:first-child,
& > .btn:last-child,
& > .dropdown-toggle {
.border-radius(0);
}
& > .btn + .dropdown-toggle {
.box-shadow(none);
}

View File

@ -22,12 +22,14 @@
.tight-form-container-no-item-borders {
border: 1px solid @grafanaTargetBorder;
border-bottom: none;
.tight-form, .tight-form-item, [type=text].tight-form-input, [type=text].tight-form-clear-input {
border: none;
}
}
.spaced-form {
.tight-form {
margin: 7px 0;
@ -42,12 +44,11 @@
}
.tight-form-container {
border-bottom: 1px solid @grafanaTargetBorder;
.tight-form:last-child {
border-bottom: none;
}
&:last-child {
border-bottom: 1px solid @grafanaTargetBorder;
}
}
.tight-form-btn {
@ -63,7 +64,7 @@
}
.grafana-metric-options {
margin-top: 35px;
margin-top: 25px;
}
.tight-form-item {
@ -209,3 +210,7 @@ select.tight-form-input {
}
}
.tight-form-align {
padding-left: 66px;
}

View File

@ -13,9 +13,7 @@ define([
beforeEach(ctx.createControllerPhase('GraphiteQueryCtrl'));
beforeEach(function() {
ctx.scope.target = {
target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'
};
ctx.scope.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));

View File

@ -0,0 +1,58 @@
define([
'helpers',
'features/panel/panelSrv',
], function() {
'use strict';
describe('PanelSrv', function() {
var _panelSrv;
var _panelScope;
var _datasourceSrvStub;
beforeEach(module('grafana.services'));
beforeEach(module(function($provide) {
_datasourceSrvStub = {
getMetricSources: sinon.spy(),
};
$provide.value('datasourceSrv', _datasourceSrvStub);
}));
beforeEach(inject(function(panelSrv, $rootScope) {
_panelSrv = panelSrv;
_panelScope = $rootScope.$new();
_panelScope.panel = {
targets: [],
};
_panelScope.dashboardViewState = {
registerPanel: sinon.spy(),
};
}));
describe('init', function() {
beforeEach(function() {
_panelSrv.init(_panelScope);
});
describe('addDataQuery', function() {
it('should add target', function() {
_panelScope.addDataQuery();
expect(_panelScope.panel.targets.length).to.be(1);
});
it('should set refId', function() {
_panelScope.addDataQuery();
expect(_panelScope.panel.targets[0].refId).to.be('A');
});
it('should set refId to first available letter', function() {
_panelScope.panel.targets = [{refId: 'A'}];
_panelScope.addDataQuery();
expect(_panelScope.panel.targets[1].refId).to.be('B');
});
});
});
});
});

View File

@ -139,6 +139,7 @@ require([
'specs/seriesOverridesCtrl-specs',
'specs/shareModalCtrl-specs',
'specs/timeSrv-specs',
'specs/panelSrv-specs',
'specs/templateSrv-specs',
'specs/templateValuesSrv-specs',
'specs/kbn-format-specs',

View File

@ -32,6 +32,7 @@
// Components: Buttons & Alerts
@import "buttons.less";
@import "button-groups.less";
@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less
// Components: Nav