Commit e2f6633d by Torkel Ödegaard

Began work on data source test / validation, #1997 & #2043

parent fc43ce65
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
- [Issue #1928](https://github.com/grafana/grafana/issues/1928). HTTP API: GET /api/dashboards/db/:slug response changed property `model` to `dashboard` to match the POST request nameing - [Issue #1928](https://github.com/grafana/grafana/issues/1928). HTTP API: GET /api/dashboards/db/:slug response changed property `model` to `dashboard` to match the POST request nameing
- Backend render URL changed from `/render/dashboard/solo` `render/dashboard-solo/` (in order to have consistent dashboard url `/dashboard/:type/:slug`) - Backend render URL changed from `/render/dashboard/solo` `render/dashboard-solo/` (in order to have consistent dashboard url `/dashboard/:type/:slug`)
- Search HTTP API response has changed (simplified), tags list moved to seperate HTTP resource URI - Search HTTP API response has changed (simplified), tags list moved to seperate HTTP resource URI
- Datasource HTTP api breaking change, ADD datasource is now POST /api/datasources/, update is now PUT /api/datasources/:id
# 2.0.3 (unreleased - 2.0.x branch) # 2.0.3 (unreleased - 2.0.x branch)
......
...@@ -107,10 +107,9 @@ func Register(r *macaron.Macaron) { ...@@ -107,10 +107,9 @@ func Register(r *macaron.Macaron) {
// Data sources // Data sources
r.Group("/datasources", func() { r.Group("/datasources", func() {
r.Combo("/"). r.Get("/", GetDataSources)
Get(GetDataSources). r.Post("/", bind(m.AddDataSourceCommand{}), AddDataSource)
Put(bind(m.AddDataSourceCommand{}), AddDataSource). r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
Post(bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
r.Delete("/:id", DeleteDataSource) r.Delete("/:id", DeleteDataSource)
r.Get("/:id", GetDataSourceById) r.Get("/:id", GetDataSourceById)
r.Get("/plugins", GetDataSourcePlugins) r.Get("/plugins", GetDataSourcePlugins)
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util"
) )
func GetDataSources(c *middleware.Context) { func GetDataSources(c *middleware.Context) {
...@@ -94,11 +95,12 @@ func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) { ...@@ -94,11 +95,12 @@ func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) {
return return
} }
c.JsonOK("Datasource added") c.JSON(200, util.DynMap{"message": "Datasource added", "id": cmd.Result.Id})
} }
func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) { func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
cmd.OrgId = c.OrgId cmd.OrgId = c.OrgId
cmd.Id = c.ParamsInt64(":id")
err := bus.Dispatch(&cmd) err := bus.Dispatch(&cmd)
if err != nil { if err != nil {
......
...@@ -69,7 +69,6 @@ type AddDataSourceCommand struct { ...@@ -69,7 +69,6 @@ type AddDataSourceCommand struct {
// Also acts as api DTO // Also acts as api DTO
type UpdateDataSourceCommand struct { type UpdateDataSourceCommand struct {
Id int64 `json:"id" binding:"Required"`
Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"` Access DsAccess `json:"access" binding:"Required"`
...@@ -84,6 +83,7 @@ type UpdateDataSourceCommand struct { ...@@ -84,6 +83,7 @@ type UpdateDataSourceCommand struct {
JsonData map[string]interface{} `json:"jsonData"` JsonData map[string]interface{} `json:"jsonData"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Id int64 `json:"-"`
} }
type DeleteDataSourceCommand struct { type DeleteDataSourceCommand struct {
......
...@@ -25,7 +25,6 @@ function (angular, config) { ...@@ -25,7 +25,6 @@ function (angular, config) {
$scope.loadDatasourceTypes().then(function() { $scope.loadDatasourceTypes().then(function() {
if ($routeParams.id) { if ($routeParams.id) {
$scope.isNew = false;
$scope.getDatasourceById($routeParams.id); $scope.getDatasourceById($routeParams.id);
} else { } else {
$scope.current = angular.copy(defaults); $scope.current = angular.copy(defaults);
...@@ -48,6 +47,7 @@ function (angular, config) { ...@@ -48,6 +47,7 @@ function (angular, config) {
$scope.getDatasourceById = function(id) { $scope.getDatasourceById = function(id) {
backendSrv.get('/api/datasources/' + id).then(function(ds) { backendSrv.get('/api/datasources/' + id).then(function(ds) {
$scope.isNew = false;
$scope.current = ds; $scope.current = ds;
$scope.typeChanged(); $scope.typeChanged();
}); });
...@@ -65,26 +65,46 @@ function (angular, config) { ...@@ -65,26 +65,46 @@ function (angular, config) {
}); });
}; };
$scope.update = function() { $scope.testDatasource = function() {
if (!$scope.editForm.$valid) { $scope.testing = { done: false };
return;
}
backendSrv.post('/api/datasources', $scope.current).then(function() { datasourceSrv.get($scope.current.name).then(function(datasource) {
$scope.updateFrontendSettings(); if (!datasource.testDatasource) {
$location.path("datasources"); $scope.testing.message = 'Data source does not support test connection feature.';
$scope.testing.status = 'warning';
$scope.testing.title = 'Unknown';
return;
}
return datasource.testDatasource().then(function(result) {
$scope.testing.message = result.message;
$scope.testing.status = result.status;
$scope.testing.title = result.title;
});
}).finally(function() {
$scope.testing.done = true;
}); });
}; };
$scope.add = function() { $scope.saveChanges = function(test) {
if (!$scope.editForm.$valid) { if (!$scope.editForm.$valid) {
return; return;
} }
backendSrv.put('/api/datasources', $scope.current).then(function() { if ($scope.current.id) {
$scope.updateFrontendSettings(); return backendSrv.put('/api/datasources/' + $scope.current.id, $scope.current).then(function() {
$location.path("datasources"); $scope.updateFrontendSettings();
}); if (test) {
$scope.testDatasource();
} else {
$location.path('datasources');
}
});
} else {
return backendSrv.post('/api/datasources', $scope.current).then(function(result) {
$scope.updateFrontendSettings();
$location.path('datasources/edit/' + result.id);
});
}
}; };
$scope.init(); $scope.init();
......
...@@ -43,11 +43,22 @@ ...@@ -43,11 +43,22 @@
</div> </div>
<div ng-include="datasourceMeta.partials.config" ng-if="datasourceMeta.partials.config"></div> <div ng-include="datasourceMeta.partials.config" ng-if="datasourceMeta.partials.config"></div>
<br>
<br> <div ng-if="testing" style="margin-top: 25px">
<div class="pull-right"> <h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<button type="submit" class="btn btn-success" ng-show="isNew" ng-click="add()">Add</button> <h5 ng-show="testing.done">Test results</h5>
<button type="submit" class="btn btn-success" ng-show="!isNew" ng-click="update()">Update</button> <div class="alert-{{testing.status}} alert">
<div class="alert-title">{{testing.title}}</div>
<div ng-bind='testing.message'></div>
</div>
</div>
<div class="pull-right" style="margin-top: 35px">
<button type="submit" class="btn btn-success" ng-show="isNew" ng-click="saveChanges()">Add</button>
<button type="submit" class="btn btn-success" ng-show="!isNew" ng-click="saveChanges()">Save</button>
<button type="submit" class="btn btn-inverse" ng-show="!isNew" ng-click="saveChanges(true)">
Test Connection
</button>
<a class="btn btn-inverse" ng-show="!isNew" href="datasources">Cancel</a> <a class="btn btn-inverse" ng-show="!isNew" href="datasources">Cancel</a>
</div> </div>
<br> <br>
......
...@@ -196,6 +196,22 @@ function (angular, _, $, config, kbn, moment) { ...@@ -196,6 +196,22 @@ function (angular, _, $, config, kbn, moment) {
}); });
}; };
GraphiteDatasource.prototype.testDatasource = function() {
return this.metricFindQuery('*').then(function () {
return { status: "success", message: "Data source is working", title: "Success" };
}, function(err) {
var message, title;
if (err.statusText) {
message = err.statusText;
title = "HTTP Error";
} else {
message = err;
title = "Unknown error";
}
return { status: "error", message: message, title: title };
});
};
GraphiteDatasource.prototype.listDashboards = function(query) { GraphiteDatasource.prototype.listDashboards = function(query) {
return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} }) return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} })
.then(function(results) { .then(function(results) {
......
...@@ -315,38 +315,37 @@ div.flot-text { ...@@ -315,38 +315,37 @@ div.flot-text {
position: fixed; position: fixed;
right: 20px; right: 20px;
top: 56px; top: 56px;
}
.alert { .alert {
color: @white; color: @white;
padding-bottom: 13px; padding-bottom: 13px;
position: relative; position: relative;
} }
.alert-close { .alert-close {
position: absolute; position: absolute;
top: -4px; top: -4px;
right: -2px; right: -2px;
width: 19px; width: 19px;
height: 19px; height: 19px;
padding: 0; padding: 0;
background: @grayLighter; background: @grayLighter;
border-radius: 50%; border-radius: 50%;
border: none; border: none;
font-size: 1.1rem; font-size: 1.1rem;
color: @grayDarker; color: @grayDarker;
} }
.alert-title { .alert-title {
font-weight: bold; font-weight: bold;
padding-bottom: 2px; padding-bottom: 2px;
}
} }
.alert-warning { .alert-warning {
background-color: @warningBackground; background-color: @warningBackground;
border-color: @warningBorder; border-color: @warningBorder;
color: @warningText;
} }
/* =================================================== /* ===================================================
......
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