Merge branch 'master' into cloudwatch

This commit is contained in:
Torkel Ödegaard
2015-09-01 17:25:36 +02:00
14 changed files with 90 additions and 74 deletions

View File

@ -24,6 +24,7 @@ it allows you to add queries of differnet data source types & instances to the s
- [Issue #2563](https://github.com/grafana/grafana/issues/2563). Annotations: Fixed issue when html sanitizer failes for title to annotation body, now fallbacks to html escaping title and text - [Issue #2563](https://github.com/grafana/grafana/issues/2563). Annotations: Fixed issue when html sanitizer failes for title to annotation body, now fallbacks to html escaping title and text
- [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url) - [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url)
- [Issue #2620](https://github.com/grafana/grafana/issues/2620). Graph: multi series tooltip did no highlight correct point when stacking was enabled and series were of different resolution - [Issue #2620](https://github.com/grafana/grafana/issues/2620). Graph: multi series tooltip did no highlight correct point when stacking was enabled and series were of different resolution
- [Issue #2636](https://github.com/grafana/grafana/issues/2636). InfluxDB: Do no show template vars in dropdown for tag keys and group by keys
**Breaking Changes** **Breaking Changes**
- Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that - Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that

View File

@ -49,7 +49,6 @@ pages:
- ['reference/graph.md', 'Reference', 'Graph Panel'] - ['reference/graph.md', 'Reference', 'Graph Panel']
- ['reference/singlestat.md', 'Reference', 'Singlestat Panel'] - ['reference/singlestat.md', 'Reference', 'Singlestat Panel']
- ['reference/dashlist.md', 'Reference', 'Dashboard List Panel'] - ['reference/dashlist.md', 'Reference', 'Dashboard List Panel']
- ['reference/text.md', 'Reference', 'Text Panel']
- ['reference/sharing.md', 'Reference', 'Sharing'] - ['reference/sharing.md', 'Reference', 'Sharing']
- ['reference/annotations.md', 'Reference', 'Annotations'] - ['reference/annotations.md', 'Reference', 'Annotations']
- ['reference/timerange.md', 'Reference', 'Time Range Controls'] - ['reference/timerange.md', 'Reference', 'Time Range Controls']

View File

@ -51,7 +51,7 @@ the tag key and select `--remove tag filter--`.
### Regex matching ### Regex matching
You can type in regex patterns for metric names or tag filter values, be sure to wrap the regex pattern in forward slashes (`/`). Grafana You can type in regex patterns for metric names or tag filter values, be sure to wrap the regex pattern in forward slashes (`/`). Grafana
will automaticallay adjust the filter tag condition to use the InfluxDB regex match condition operator (`=~`). will automatically adjust the filter tag condition to use the InfluxDB regex match condition operator (`=~`).
### Editor group by ### Editor group by
To group by a tag click the plus icon after the `GROUP BY ($interval)` text. Pick a tag from the dropdown that appears. To group by a tag click the plus icon after the `GROUP BY ($interval)` text. Pick a tag from the dropdown that appears.

View File

@ -154,10 +154,11 @@ func CompleteInvite(c *middleware.Context, completeInvite dtos.CompleteInviteFor
} }
cmd := m.CreateUserCommand{ cmd := m.CreateUserCommand{
Email: completeInvite.Email, Email: completeInvite.Email,
Name: completeInvite.Name, Name: completeInvite.Name,
Login: completeInvite.Username, Login: completeInvite.Username,
Password: completeInvite.Password, Password: completeInvite.Password,
SkipOrgSetup: true,
} }
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {

View File

@ -65,6 +65,7 @@ func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
OrgName: form.OrgName, OrgName: form.OrgName,
} }
// verify email
if setting.VerifyEmailEnabled { if setting.VerifyEmailEnabled {
if ok, rsp := verifyUserSignUpEmail(form.Email, form.Code); !ok { if ok, rsp := verifyUserSignUpEmail(form.Email, form.Code); !ok {
return rsp return rsp
@ -72,11 +73,13 @@ func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
createUserCmd.EmailVerified = true createUserCmd.EmailVerified = true
} }
// check if user exists
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email} existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := bus.Dispatch(&existing); err == nil { if err := bus.Dispatch(&existing); err == nil {
return ApiError(401, "User with same email address already exists", nil) return ApiError(401, "User with same email address already exists", nil)
} }
// dispatch create command
if err := bus.Dispatch(&createUserCmd); err != nil { if err := bus.Dispatch(&createUserCmd); err != nil {
return ApiError(500, "Failed to create user", err) return ApiError(500, "Failed to create user", err)
} }

View File

@ -52,6 +52,7 @@ type CreateUserCommand struct {
Password string Password string
EmailVerified bool EmailVerified bool
IsAdmin bool IsAdmin bool
SkipOrgSetup bool
Result User Result User
} }

View File

@ -153,7 +153,7 @@ func signUpCompletedHandler(evt *events.SignUpCompleted) error {
return sendEmailCommandHandler(&m.SendEmailCommand{ return sendEmailCommandHandler(&m.SendEmailCommand{
To: []string{evt.Email}, To: []string{evt.Email},
Template: tmplSignUpStarted, Template: tmplWelcomeOnSignUp,
Data: map[string]interface{}{ Data: map[string]interface{}{
"Name": evt.Name, "Name": evt.Name,
}, },

View File

@ -30,6 +30,10 @@ func init() {
} }
func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *session) (int64, error) { func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *session) (int64, error) {
if cmd.SkipOrgSetup {
return -1, nil
}
var org m.Org var org m.Org
if setting.AutoAssignOrg { if setting.AutoAssignOrg {
@ -103,23 +107,6 @@ func CreateUser(cmd *m.CreateUserCommand) error {
return err return err
} }
// create org user link
orgUser := m.OrgUser{
OrgId: orgId,
UserId: user.Id,
Role: m.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
if setting.AutoAssignOrg && !user.IsAdmin {
orgUser.Role = m.RoleType(setting.AutoAssignOrgRole)
}
if _, err = sess.Insert(&orgUser); err != nil {
return err
}
sess.publishAfterCommit(&events.UserCreated{ sess.publishAfterCommit(&events.UserCreated{
Timestamp: user.Created, Timestamp: user.Created,
Id: user.Id, Id: user.Id,
@ -129,6 +116,26 @@ func CreateUser(cmd *m.CreateUserCommand) error {
}) })
cmd.Result = user cmd.Result = user
// create org user link
if !cmd.SkipOrgSetup {
orgUser := m.OrgUser{
OrgId: orgId,
UserId: user.Id,
Role: m.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
if setting.AutoAssignOrg && !user.IsAdmin {
orgUser.Role = m.RoleType(setting.AutoAssignOrgRole)
}
if _, err = sess.Insert(&orgUser); err != nil {
return err
}
}
return nil return nil
}) })
} }

View File

@ -1,6 +1,6 @@
<topnav icon="fa fa-th-large" title="Dashboards" subnav="true"> <topnav icon="fa fa-th-large" title="Dashboards" subnav="true">
<ul class="nav"> <ul class="nav">
<li class="active"><a href="import">Import</a></li> <li class="active"><a href="import/dashboard">Import</a></li>
</ul> </ul>
</topnav> </topnav>

View File

@ -200,7 +200,7 @@
Multi format Multi format
</li> </li>
<li ng-show="current.multi"> <li ng-show="current.multi">
<select class="input-small tight-form-input last" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values']"></select> <select class="input-medium tight-form-input last" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values']"></select>
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>

View File

@ -102,7 +102,7 @@ function (_) {
if (i > 0) { if (i > 0) {
query += ', '; query += ', ';
} }
query += field.func + '(' + field.name + ')'; query += field.func + '("' + field.name + '")';
} }
var measurement = target.measurement; var measurement = target.measurement;

View File

@ -119,8 +119,7 @@ function (angular, _, InfluxQueryBuilder) {
$scope.getMeasurements = function () { $scope.getMeasurements = function () {
var query = $scope.queryBuilder.buildExploreQuery('MEASUREMENTS'); var query = $scope.queryBuilder.buildExploreQuery('MEASUREMENTS');
return $scope.datasource.metricFindQuery(query) return $scope.datasource.metricFindQuery(query)
.then($scope.transformToSegments) .then($scope.transformToSegments(true))
.then($scope.addTemplateVariableSegments)
.then(null, $scope.handleQueryError); .then(null, $scope.handleQueryError);
}; };
@ -129,42 +128,46 @@ function (angular, _, InfluxQueryBuilder) {
return []; return [];
}; };
$scope.transformToSegments = function(results) { $scope.transformToSegments = function(addTemplateVars) {
return _.map(results, function(segment) { return function(results) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable }); var segments = _.map(results, function(segment) {
}); return new MetricSegment({ value: segment.text, expandable: segment.expandable });
}; });
$scope.addTemplateVariableSegments = function(segments) { if (addTemplateVars) {
_.each(templateSrv.variables, function(variable) { _.each(templateSrv.variables, function(variable) {
segments.unshift(new MetricSegment({ type: 'template', value: '/$' + variable.name + '/', expandable: true })); segments.unshift(new MetricSegment({ type: 'template', value: '/$' + variable.name + '/', expandable: true }));
}); });
return segments; }
return segments;
};
}; };
$scope.getTagsOrValues = function(segment, index) { $scope.getTagsOrValues = function(segment, index) {
var query; if (segment.type === 'condition') {
return $q.when([new MetricSegment('AND'), new MetricSegment('OR')]);
}
if (segment.type === 'operator') {
var nextValue = $scope.tagSegments[index+1].value;
if (/^\/.*\/$/.test(nextValue)) {
return $q.when(MetricSegment.newOperators(['=~', '!~']));
} else {
return $q.when(MetricSegment.newOperators(['=', '<>', '<', '>']));
}
}
var query, addTemplateVars;
if (segment.type === 'key' || segment.type === 'plus-button') { if (segment.type === 'key' || segment.type === 'plus-button') {
query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
addTemplateVars = false;
} else if (segment.type === 'value') { } else if (segment.type === 'value') {
query = $scope.queryBuilder.buildExploreQuery('TAG_VALUES', $scope.tagSegments[index-2].value); query = $scope.queryBuilder.buildExploreQuery('TAG_VALUES', $scope.tagSegments[index-2].value);
} else if (segment.type === 'condition') { addTemplateVars = true;
return $q.when([new MetricSegment('AND'), new MetricSegment('OR')]);
} else if (segment.type === 'operator' && /^(?!\/.*\/$)/.test($scope.tagSegments[index+1].value)) {
return $q.when([MetricSegment.newOperator('='), MetricSegment.newOperator('<>'),
MetricSegment.newOperator('<'), MetricSegment.newOperator('>')]);
} else if (segment.type === 'operator' && /^\/.*\/$/.test($scope.tagSegments[index+1].value)) {
return $q.when([MetricSegment.newOperator('=~'), MetricSegment.newOperator('!~')]);
}
else {
return $q.when([]);
} }
return $scope.datasource.metricFindQuery(query) return $scope.datasource.metricFindQuery(query)
.then($scope.transformToSegments) .then($scope.transformToSegments(addTemplateVars))
.then($scope.addTemplateVariableSegments)
.then(function(results) { .then(function(results) {
if (segment.type === 'key') { if (segment.type === 'key') {
results.splice(0, 0, angular.copy($scope.removeTagFilterSegment)); results.splice(0, 0, angular.copy($scope.removeTagFilterSegment));
@ -177,8 +180,8 @@ function (angular, _, InfluxQueryBuilder) {
$scope.getFieldSegments = function() { $scope.getFieldSegments = function() {
var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS'); var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS');
return $scope.datasource.metricFindQuery(fieldsQuery) return $scope.datasource.metricFindQuery(fieldsQuery)
.then($scope.transformToSegments) .then($scope.transformToSegments(false))
.then(null, $scope.handleQueryError); .then(null, $scope.handleQueryError);
}; };
$scope.addField = function() { $scope.addField = function() {
@ -197,8 +200,7 @@ function (angular, _, InfluxQueryBuilder) {
var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
return $scope.datasource.metricFindQuery(query) return $scope.datasource.metricFindQuery(query)
.then($scope.transformToSegments) .then($scope.transformToSegments(false))
.then($scope.addTemplateVariableSegments)
.then(function(results) { .then(function(results) {
if (segment.type !== 'plus-button') { if (segment.type !== 'plus-button') {
results.splice(0, 0, angular.copy($scope.removeGroupBySegment)); results.splice(0, 0, angular.copy($scope.removeGroupBySegment));
@ -322,6 +324,12 @@ function (angular, _, InfluxQueryBuilder) {
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' }); return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
}; };
MetricSegment.newOperators = function(ops) {
return _.map(ops, function(op) {
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
});
};
MetricSegment.newPlusButton = function() { MetricSegment.newPlusButton = function() {
return new MetricSegment({fake: true, html: '<i class="fa fa-plus "></i>', type: 'plus-button' }); return new MetricSegment({fake: true, html: '<i class="fa fa-plus "></i>', type: 'plus-button' });
}; };

View File

@ -10,7 +10,7 @@ function (angular, _, kbn) {
var module = angular.module('grafana.services'); var module = angular.module('grafana.services');
module.factory('KairosDBDatasource', function($q, $http, templateSrv) { module.factory('KairosDBDatasource', function($q, backendSrv, templateSrv) {
function KairosDBDatasource(datasource) { function KairosDBDatasource(datasource) {
this.type = datasource.type; this.type = datasource.type;
@ -51,10 +51,6 @@ function (angular, _, kbn) {
return this.performTimeSeriesQuery(queries, start, end).then(handleKairosDBQueryResponseAlias, handleQueryError); return this.performTimeSeriesQuery(queries, start, end).then(handleKairosDBQueryResponseAlias, handleQueryError);
}; };
///////////////////////////////////////////////////////////////////////
/// Query methods
///////////////////////////////////////////////////////////////////////
KairosDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) { KairosDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) {
var reqBody = { var reqBody = {
metrics: queries, metrics: queries,
@ -70,7 +66,7 @@ function (angular, _, kbn) {
data: reqBody data: reqBody
}; };
return $http(options); return backendSrv.datasourceRequest(options);
}; };
/** /**
@ -83,7 +79,7 @@ function (angular, _, kbn) {
method: 'GET' method: 'GET'
}; };
return $http(options).then(function(response) { return backendSrv.datasourceRequest(options).then(function(response) {
if (!response.data) { if (!response.data) {
return $q.when([]); return $q.when([]);
} }
@ -110,7 +106,7 @@ function (angular, _, kbn) {
} }
}; };
return $http(options).then(function(result) { return backendSrv.datasourceRequest(options).then(function(result) {
if (!result.data) { if (!result.data) {
return $q.when([]); return $q.when([]);
} }
@ -139,7 +135,7 @@ function (angular, _, kbn) {
} }
}; };
return $http(options).then(function(result) { return backendSrv.datasourceRequest(options).then(function(result) {
if (!result.data) { if (!result.data) {
return $q.when([]); return $q.when([]);
} }
@ -158,7 +154,7 @@ function (angular, _, kbn) {
} }
}; };
return $http(options).then(function(response) { return backendSrv.datasourceRequest(options).then(function(response) {
if (!response.data) { if (!response.data) {
return []; return [];
} }

View File

@ -13,7 +13,7 @@ define([
var query = builder.build(); var query = builder.build();
it('should generate correct query', function() { it('should generate correct query', function() {
expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); expect(query).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval)');
}); });
}); });
@ -27,14 +27,14 @@ define([
var query = builder.build(); var query = builder.build();
it('should generate correct query', function() { it('should generate correct query', function() {
expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE "hostname" = \'server1\' AND $timeFilter' expect(query).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND $timeFilter'
+ ' GROUP BY time($interval)'); + ' GROUP BY time($interval)');
}); });
it('should switch regex operator with tag value is regex', function() { it('should switch regex operator with tag value is regex', function() {
var builder = new InfluxQueryBuilder({measurement: 'cpu', tags: [{key: 'app', value: '/e.*/'}]}); var builder = new InfluxQueryBuilder({measurement: 'cpu', tags: [{key: 'app', value: '/e.*/'}]});
var query = builder.build(); var query = builder.build();
expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)'); expect(query).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)');
}); });
}); });
@ -48,7 +48,7 @@ define([
var query = builder.build(); var query = builder.build();
it('should generate correct query', function() { it('should generate correct query', function() {
expect(query).to.be('SELECT sum(tx_in), mean(tx_out) FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); expect(query).to.be('SELECT sum("tx_in"), mean("tx_out") FROM "cpu" WHERE $timeFilter GROUP BY time($interval)');
}); });
}); });
@ -61,7 +61,7 @@ define([
var query = builder.build(); var query = builder.build();
it('should generate correct query', function() { it('should generate correct query', function() {
expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' + expect(query).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' +
'$timeFilter GROUP BY time($interval)'); '$timeFilter GROUP BY time($interval)');
}); });
}); });
@ -75,7 +75,7 @@ define([
var query = builder.build(); var query = builder.build();
it('should generate correct query', function() { it('should generate correct query', function() {
expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' + expect(query).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' +
'$timeFilter GROUP BY time($interval)'); '$timeFilter GROUP BY time($interval)');
}); });
}); });
@ -89,7 +89,7 @@ define([
}); });
var query = builder.build(); var query = builder.build();
expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE $timeFilter ' + expect(query).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter ' +
'GROUP BY time($interval), "host"'); 'GROUP BY time($interval), "host"');
}); });
}); });