Commit 3ed63d09 by Torkel Ödegaard

Merge branch 'master' into cloudwatch

parents 822a689b 4b429960
......@@ -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 #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 #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**
- Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that
......
......@@ -49,7 +49,6 @@ pages:
- ['reference/graph.md', 'Reference', 'Graph Panel']
- ['reference/singlestat.md', 'Reference', 'Singlestat Panel']
- ['reference/dashlist.md', 'Reference', 'Dashboard List Panel']
- ['reference/text.md', 'Reference', 'Text Panel']
- ['reference/sharing.md', 'Reference', 'Sharing']
- ['reference/annotations.md', 'Reference', 'Annotations']
- ['reference/timerange.md', 'Reference', 'Time Range Controls']
......
......@@ -51,7 +51,7 @@ the tag key and select `--remove tag filter--`.
### 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
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
To group by a tag click the plus icon after the `GROUP BY ($interval)` text. Pick a tag from the dropdown that appears.
......
......@@ -154,10 +154,11 @@ func CompleteInvite(c *middleware.Context, completeInvite dtos.CompleteInviteFor
}
cmd := m.CreateUserCommand{
Email: completeInvite.Email,
Name: completeInvite.Name,
Login: completeInvite.Username,
Password: completeInvite.Password,
Email: completeInvite.Email,
Name: completeInvite.Name,
Login: completeInvite.Username,
Password: completeInvite.Password,
SkipOrgSetup: true,
}
if err := bus.Dispatch(&cmd); err != nil {
......
......@@ -65,6 +65,7 @@ func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
OrgName: form.OrgName,
}
// verify email
if setting.VerifyEmailEnabled {
if ok, rsp := verifyUserSignUpEmail(form.Email, form.Code); !ok {
return rsp
......@@ -72,11 +73,13 @@ func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
createUserCmd.EmailVerified = true
}
// check if user exists
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := bus.Dispatch(&existing); err == nil {
return ApiError(401, "User with same email address already exists", nil)
}
// dispatch create command
if err := bus.Dispatch(&createUserCmd); err != nil {
return ApiError(500, "Failed to create user", err)
}
......
......@@ -52,6 +52,7 @@ type CreateUserCommand struct {
Password string
EmailVerified bool
IsAdmin bool
SkipOrgSetup bool
Result User
}
......
......@@ -153,7 +153,7 @@ func signUpCompletedHandler(evt *events.SignUpCompleted) error {
return sendEmailCommandHandler(&m.SendEmailCommand{
To: []string{evt.Email},
Template: tmplSignUpStarted,
Template: tmplWelcomeOnSignUp,
Data: map[string]interface{}{
"Name": evt.Name,
},
......
......@@ -30,6 +30,10 @@ func init() {
}
func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *session) (int64, error) {
if cmd.SkipOrgSetup {
return -1, nil
}
var org m.Org
if setting.AutoAssignOrg {
......@@ -103,23 +107,6 @@ func CreateUser(cmd *m.CreateUserCommand) error {
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{
Timestamp: user.Created,
Id: user.Id,
......@@ -129,6 +116,26 @@ func CreateUser(cmd *m.CreateUserCommand) error {
})
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
})
}
......
<topnav icon="fa fa-th-large" title="Dashboards" subnav="true">
<ul class="nav">
<li class="active"><a href="import">Import</a></li>
<li class="active"><a href="import/dashboard">Import</a></li>
</ul>
</topnav>
......
......@@ -200,7 +200,7 @@
Multi format
</li>
<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>
</ul>
<div class="clearfix"></div>
......
......@@ -102,7 +102,7 @@ function (_) {
if (i > 0) {
query += ', ';
}
query += field.func + '(' + field.name + ')';
query += field.func + '("' + field.name + '")';
}
var measurement = target.measurement;
......
......@@ -119,8 +119,7 @@ function (angular, _, InfluxQueryBuilder) {
$scope.getMeasurements = function () {
var query = $scope.queryBuilder.buildExploreQuery('MEASUREMENTS');
return $scope.datasource.metricFindQuery(query)
.then($scope.transformToSegments)
.then($scope.addTemplateVariableSegments)
.then($scope.transformToSegments(true))
.then(null, $scope.handleQueryError);
};
......@@ -129,42 +128,46 @@ function (angular, _, InfluxQueryBuilder) {
return [];
};
$scope.transformToSegments = function(results) {
return _.map(results, function(segment) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
});
};
$scope.transformToSegments = function(addTemplateVars) {
return function(results) {
var segments = _.map(results, function(segment) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
});
$scope.addTemplateVariableSegments = function(segments) {
_.each(templateSrv.variables, function(variable) {
segments.unshift(new MetricSegment({ type: 'template', value: '/$' + variable.name + '/', expandable: true }));
});
return segments;
if (addTemplateVars) {
_.each(templateSrv.variables, function(variable) {
segments.unshift(new MetricSegment({ type: 'template', value: '/$' + variable.name + '/', expandable: true }));
});
}
return segments;
};
};
$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') {
query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
addTemplateVars = false;
} else if (segment.type === 'value') {
query = $scope.queryBuilder.buildExploreQuery('TAG_VALUES', $scope.tagSegments[index-2].value);
} else if (segment.type === 'condition') {
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([]);
addTemplateVars = true;
}
return $scope.datasource.metricFindQuery(query)
.then($scope.transformToSegments)
.then($scope.addTemplateVariableSegments)
.then($scope.transformToSegments(addTemplateVars))
.then(function(results) {
if (segment.type === 'key') {
results.splice(0, 0, angular.copy($scope.removeTagFilterSegment));
......@@ -177,8 +180,8 @@ function (angular, _, InfluxQueryBuilder) {
$scope.getFieldSegments = function() {
var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS');
return $scope.datasource.metricFindQuery(fieldsQuery)
.then($scope.transformToSegments)
.then(null, $scope.handleQueryError);
.then($scope.transformToSegments(false))
.then(null, $scope.handleQueryError);
};
$scope.addField = function() {
......@@ -197,8 +200,7 @@ function (angular, _, InfluxQueryBuilder) {
var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
return $scope.datasource.metricFindQuery(query)
.then($scope.transformToSegments)
.then($scope.addTemplateVariableSegments)
.then($scope.transformToSegments(false))
.then(function(results) {
if (segment.type !== 'plus-button') {
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' });
};
MetricSegment.newOperators = function(ops) {
return _.map(ops, function(op) {
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
});
};
MetricSegment.newPlusButton = function() {
return new MetricSegment({fake: true, html: '<i class="fa fa-plus "></i>', type: 'plus-button' });
};
......
......@@ -10,7 +10,7 @@ function (angular, _, kbn) {
var module = angular.module('grafana.services');
module.factory('KairosDBDatasource', function($q, $http, templateSrv) {
module.factory('KairosDBDatasource', function($q, backendSrv, templateSrv) {
function KairosDBDatasource(datasource) {
this.type = datasource.type;
......@@ -51,10 +51,6 @@ function (angular, _, kbn) {
return this.performTimeSeriesQuery(queries, start, end).then(handleKairosDBQueryResponseAlias, handleQueryError);
};
///////////////////////////////////////////////////////////////////////
/// Query methods
///////////////////////////////////////////////////////////////////////
KairosDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) {
var reqBody = {
metrics: queries,
......@@ -70,7 +66,7 @@ function (angular, _, kbn) {
data: reqBody
};
return $http(options);
return backendSrv.datasourceRequest(options);
};
/**
......@@ -83,7 +79,7 @@ function (angular, _, kbn) {
method: 'GET'
};
return $http(options).then(function(response) {
return backendSrv.datasourceRequest(options).then(function(response) {
if (!response.data) {
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) {
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) {
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) {
return [];
}
......
......@@ -13,7 +13,7 @@ define([
var query = builder.build();
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();
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)');
});
it('should switch regex operator with tag value is regex', function() {
var builder = new InfluxQueryBuilder({measurement: 'cpu', tags: [{key: 'app', value: '/e.*/'}]});
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();
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();
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)');
});
});
......@@ -75,7 +75,7 @@ define([
var query = builder.build();
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)');
});
});
......@@ -89,7 +89,7 @@ define([
});
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"');
});
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment