diff --git a/.editorconfig b/.editorconfig index 5760be58369..831bb8696cc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,13 @@ # http://editorconfig.org root = true +[*.go] +indent_style = tabs +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + [*] indent_style = space indent_size = 2 diff --git a/docs/sources/http_api/data_source.md b/docs/sources/http_api/data_source.md index 57352161d31..f60c9cd7000 100644 --- a/docs/sources/http_api/data_source.md +++ b/docs/sources/http_api/data_source.md @@ -74,6 +74,39 @@ page_keywords: grafana, admin, http, api, documentation, datasource "jsonData":null } +## Get a single data sources by Name + +`GET /api/datasources/name/:name` + +**Example Request**: + + GET /api/datasources/name/test_datasource HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "id":1, + "orgId":1, + "name":"test_datasource", + "type":"graphite", + "access":"proxy", + "url":"http://mydatasource.com", + "password":"", + "user":"", + "database":"", + "basicAuth":false, + "basicAuthUser":"", + "basicAuthPassword":"", + "isDefault":false, + "jsonData":null + } + ## Create data source `POST /api/datasources` diff --git a/docs/sources/plugins/datasources.md b/docs/sources/plugins/datasources.md index 399d99b6292..97ef508b004 100644 --- a/docs/sources/plugins/datasources.md +++ b/docs/sources/plugins/datasources.md @@ -53,7 +53,10 @@ Request object passed to datasource.query function } ``` -Expected response from datasource.query +There are two different kind of results for datasources. +Time series and table. Time series is the most common format and is suppoert by all datasources and panels. Table format is only support by the Influxdb datasource and table panel. But we might se more of this in the future. + +Time series response from datasource.query An array of ```json [ @@ -74,6 +77,42 @@ An array of ] ``` +Table response from datasource.query +An array of +```json +[ + { + "columns": [ + { + "text": "Time", + "type": "time", + "sort": true, + "desc": true, + }, + { + "text": "mean", + }, + { + "text": "sum", + } + ], + "rows": [ + [ + 1457425380000, + null, + null + ], + [ + 1457425370000, + 1002.76215352, + 1002.76215352 + ], + ], + "type": "table" + } +] +``` + ### Annotation Query Request object passed to datasource.annotationsQuery function diff --git a/pkg/api/api.go b/pkg/api/api.go index ed029a5171a..e15c08b770c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -174,6 +174,10 @@ func Register(r *macaron.Macaron) { r.Get("/plugins", GetDataSourcePlugins) }, reqOrgAdmin) + r.Group("/datasources/name/:name", func() { + r.Get("/", wrap(GetDataSourceByName)) + }, reqOrgAdmin) + r.Get("/frontend/settings/", GetFrontendSettings) r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest) r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 54959840d03..8c57438dfb5 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -52,24 +52,9 @@ func GetDataSourceById(c *middleware.Context) Response { } ds := query.Result + dtos := convertModelToDtos(ds) - return Json(200, &dtos.DataSource{ - Id: ds.Id, - OrgId: ds.OrgId, - Name: ds.Name, - Url: ds.Url, - Type: ds.Type, - Access: ds.Access, - Password: ds.Password, - Database: ds.Database, - User: ds.User, - BasicAuth: ds.BasicAuth, - BasicAuthUser: ds.BasicAuthUser, - BasicAuthPassword: ds.BasicAuthPassword, - WithCredentials: ds.WithCredentials, - IsDefault: ds.IsDefault, - JsonData: ds.JsonData, - }) + return Json(200, &dtos) } func DeleteDataSource(c *middleware.Context) { @@ -132,3 +117,40 @@ func GetDataSourcePlugins(c *middleware.Context) { c.JSON(200, dsList) } } + +// Get /api/datasources/name/:name +func GetDataSourceByName(c *middleware.Context) Response { + query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId} + + if err := bus.Dispatch(&query); err != nil { + if err == m.ErrDataSourceNotFound { + return ApiError(404, "Data source not found", nil) + } + return ApiError(500, "Failed to query datasources", err) + } + + ds := query.Result + dtos := convertModelToDtos(ds) + + return Json(200, &dtos) +} + +func convertModelToDtos(ds m.DataSource) dtos.DataSource { + return dtos.DataSource{ + Id: ds.Id, + OrgId: ds.OrgId, + Name: ds.Name, + Url: ds.Url, + Type: ds.Type, + Access: ds.Access, + Password: ds.Password, + Database: ds.Database, + User: ds.User, + BasicAuth: ds.BasicAuth, + BasicAuthUser: ds.BasicAuthUser, + BasicAuthPassword: ds.BasicAuthPassword, + WithCredentials: ds.WithCredentials, + IsDefault: ds.IsDefault, + JsonData: ds.JsonData, + } +} diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 773d5958f4c..c58ed12668f 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -26,8 +26,8 @@ func validateInput(c CommandLine, pluginFolder string) error { return errors.New("missing path flag") } - fileinfo, err := os.Stat(pluginDir) - if err != nil && !fileinfo.IsDir() { + fileInfo, err := os.Stat(pluginDir) + if err != nil && !fileInfo.IsDir() { return errors.New("path is not a directory") } @@ -49,11 +49,12 @@ func installCommand(c CommandLine) error { log.Infof("version: %v\n", version) } - return InstallPlugin(pluginToInstall, pluginFolder, version, c.GlobalString("repo")) + return InstallPlugin(pluginToInstall, version, c) } -func InstallPlugin(pluginName, pluginFolder, version, repoUrl string) error { - plugin, err := s.GetPlugin(pluginName, repoUrl) +func InstallPlugin(pluginName, version string, c CommandLine) error { + plugin, err := s.GetPlugin(pluginName, c.GlobalString("repo")) + pluginFolder := c.GlobalString("path") if err != nil { return err } @@ -85,7 +86,7 @@ func InstallPlugin(pluginName, pluginFolder, version, repoUrl string) error { res, _ := s.ReadPlugin(pluginFolder, pluginName) for _, v := range res.Dependency.Plugins { - InstallPlugin(v.Id, pluginFolder, "", repoUrl) + InstallPlugin(v.Id, version, c) log.Infof("Installed Dependency: %v ✔\n", v.Id) } @@ -106,12 +107,26 @@ func SelectVersion(plugin m.Plugin, version string) (m.Version, error) { return m.Version{}, errors.New("Could not find the version your looking for") } -func RemoveGitBuildFromname(pluginname, filename string) string { +func RemoveGitBuildFromName(pluginName, filename string) string { r := regexp.MustCompile("^[a-zA-Z0-9_.-]*/") - return r.ReplaceAllString(filename, pluginname+"/") + return r.ReplaceAllString(filename, pluginName+"/") } -func downloadFile(pluginName, filepath, url string) (err error) { +var retryCount = 0 + +func downloadFile(pluginName, filePath, url string) (err error) { + defer func() { + if r := recover(); r != nil { + retryCount++ + if retryCount == 1 { + log.Debug("\nFailed downloading. Will retry once.\n") + downloadFile(pluginName, filePath, url) + } else { + panic(r) + } + } + }() + resp, err := http.Get(url) if err != nil { return err @@ -122,24 +137,18 @@ func downloadFile(pluginName, filepath, url string) (err error) { if err != nil { return err } - log.Infof("Got statuscode %s from %s\n", resp.Status, url) - - if resp.StatusCode == 302 || resp.StatusCode == 301 { - str, _ := ioutil.ReadAll(resp.Body) - log.Info("body %s\n\n", string(str)) - } r, err := zip.NewReader(bytes.NewReader(body), resp.ContentLength) if err != nil { return err } for _, zf := range r.File { - newfile := path.Join(filepath, RemoveGitBuildFromname(pluginName, zf.Name)) + newFile := path.Join(filePath, RemoveGitBuildFromName(pluginName, zf.Name)) if zf.FileInfo().IsDir() { - os.Mkdir(newfile, 0777) + os.Mkdir(newFile, 0777) } else { - dst, err := os.Create(newfile) + dst, err := os.Create(newFile) if err != nil { log.Errorf("%v", err) } diff --git a/pkg/cmd/grafana-cli/commands/install_command_test.go b/pkg/cmd/grafana-cli/commands/install_command_test.go index b88677cf614..52b329adf7f 100644 --- a/pkg/cmd/grafana-cli/commands/install_command_test.go +++ b/pkg/cmd/grafana-cli/commands/install_command_test.go @@ -19,7 +19,7 @@ func TestFoldernameReplacement(t *testing.T) { Convey("should be replaced with plugin name", func() { for k, v := range paths { - So(RemoveGitBuildFromname(pluginName, k), ShouldEqual, v) + So(RemoveGitBuildFromName(pluginName, k), ShouldEqual, v) } }) }) @@ -32,7 +32,7 @@ func TestFoldernameReplacement(t *testing.T) { Convey("should be replaced with plugin name", func() { for k, v := range paths { - So(RemoveGitBuildFromname(pluginName, k), ShouldEqual, v) + So(RemoveGitBuildFromName(pluginName, k), ShouldEqual, v) } }) }) diff --git a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go index 0bb89866fcd..d8594182a99 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go @@ -54,7 +54,7 @@ func upgradeAllCommand(c CommandLine) error { log.Infof("Upgrading %v \n", p.Id) s.RemoveInstalledPlugin(pluginDir, p.Id) - InstallPlugin(p.Id, pluginDir, "", c.GlobalString("repo")) + InstallPlugin(p.Id, "", c) } return nil diff --git a/pkg/cmd/grafana-cli/commands/upgrade_command.go b/pkg/cmd/grafana-cli/commands/upgrade_command.go index c3d2cf06aea..e4072e5ced9 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_command.go @@ -24,7 +24,7 @@ func upgradeCommand(c CommandLine) error { if localPlugin.Id == v.Id { if ShouldUpgrade(localPlugin.Info.Version, v) { s.RemoveInstalledPlugin(pluginDir, pluginName) - return InstallPlugin(localPlugin.Id, pluginDir, "", c.GlobalString("repo")) + return InstallPlugin(localPlugin.Id, "", c) } } } diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 46d2f90bc03..51895ff1ebf 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -27,29 +27,71 @@ function (angular, _, kbn) { var queryParams = $location.search(); var promises = []; + // use promises to delay processing variables that + // depend on other variables. + this.variableLock = {}; + _.forEach(this.variables, function(variable) { + self.variableLock[variable.name] = $q.defer(); + }); + for (var i = 0; i < this.variables.length; i++) { var variable = this.variables[i]; - var urlValue = queryParams['var-' + variable.name]; - if (urlValue !== void 0) { - promises.push(this.setVariableFromUrl(variable, urlValue)); - } - else if (variable.refresh) { - promises.push(this.updateOptions(variable)); - } - else if (variable.type === 'interval') { - this.updateAutoInterval(variable); - } + promises.push(this.processVariable(variable, queryParams)); } return $q.all(promises); }; - this.setVariableFromUrl = function(variable, urlValue) { - var option = _.findWhere(variable.options, { text: urlValue }); - option = option || { text: urlValue, value: urlValue }; + this.processVariable = function(variable, queryParams) { + var dependencies = []; + var lock = self.variableLock[variable.name]; - this.updateAutoInterval(variable); - return this.setVariableValue(variable, option); + // determine our dependencies. + if (variable.type === "query") { + _.forEach(this.variables, function(v) { + if (templateSrv.containsVariable(variable.query, v.name)) { + dependencies.push(self.variableLock[v.name].promise); + } + }); + } + + return $q.all(dependencies).then(function() { + var urlValue = queryParams['var-' + variable.name]; + if (urlValue !== void 0) { + return self.setVariableFromUrl(variable, urlValue).then(lock.resolve); + } + else if (variable.refresh) { + return self.updateOptions(variable).then(function() { + if (_.isEmpty(variable.current) && variable.options.length) { + console.log("setting current for %s", variable.name); + self.setVariableValue(variable, variable.options[0]); + } + lock.resolve(); + }); + } + else if (variable.type === 'interval') { + self.updateAutoInterval(variable); + lock.resolve(); + } else { + lock.resolve(); + } + }); + }; + + this.setVariableFromUrl = function(variable, urlValue) { + var promise = $q.when(true); + + if (variable.refresh) { + promise = this.updateOptions(variable); + } + + return promise.then(function() { + var option = _.findWhere(variable.options, { text: urlValue }); + option = option || { text: urlValue, value: urlValue }; + + self.updateAutoInterval(variable); + return self.setVariableValue(variable, option, true); + }); }; this.updateAutoInterval = function(variable) { @@ -64,7 +106,7 @@ function (angular, _, kbn) { templateSrv.setGrafanaVariable('$__auto_interval', interval); }; - this.setVariableValue = function(variable, option) { + this.setVariableValue = function(variable, option, initPhase) { variable.current = angular.copy(option); if (_.isArray(variable.current.value)) { @@ -72,8 +114,14 @@ function (angular, _, kbn) { } self.selectOptionsForCurrentValue(variable); - templateSrv.updateTemplateData(); + + // on first load, variable loading is ordered to ensure + // that parents are updated before children. + if (initPhase) { + return $q.when(); + } + return self.updateOptionsInChildVariables(variable); }; @@ -145,7 +193,7 @@ function (angular, _, kbn) { this.validateVariableSelectionState = function(variable) { if (!variable.current) { if (!variable.options.length) { return; } - return self.setVariableValue(variable, variable.options[0]); + return self.setVariableValue(variable, variable.options[0], true); } if (_.isArray(variable.current.value)) { @@ -153,7 +201,7 @@ function (angular, _, kbn) { } else { var currentOption = _.findWhere(variable.options, { text: variable.current.text }); if (currentOption) { - return self.setVariableValue(variable, currentOption); + return self.setVariableValue(variable, currentOption, true); } else { if (!variable.options.length) { return; } return self.setVariableValue(variable, variable.options[0]); diff --git a/public/app/plugins/datasource/graphite/partials/query.options.html b/public/app/plugins/datasource/graphite/partials/query.options.html index 35c95d03e88..b00028d0e05 100644 --- a/public/app/plugins/datasource/graphite/partials/query.options.html +++ b/public/app/plugins/datasource/graphite/partials/query.options.html @@ -1,74 +1,70 @@ -
-
- -
+ + Cache timeout + + +
+
+ Max data points + + +
-
- -
+ +
-
+
Shorter legend names
diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 88c92ee320a..201d3f3e8c8 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -2,7 +2,7 @@ import './graph'; import './legend'; -import './seriesOverridesCtrl'; +import './series_overrides_ctrl'; import moment from 'moment'; import kbn from 'app/core/utils/kbn'; diff --git a/public/app/plugins/panel/graph/seriesOverridesCtrl.js b/public/app/plugins/panel/graph/series_overrides_ctrl.js similarity index 100% rename from public/app/plugins/panel/graph/seriesOverridesCtrl.js rename to public/app/plugins/panel/graph/series_overrides_ctrl.js diff --git a/public/app/plugins/panel/graph/styleEditor.html b/public/app/plugins/panel/graph/styleEditor.html index 82cf31dd0d1..b730b1d397a 100644 --- a/public/app/plugins/panel/graph/styleEditor.html +++ b/public/app/plugins/panel/graph/styleEditor.html @@ -1,4 +1,4 @@ -
+
Chart Options
diff --git a/public/test/specs/seriesOverridesCtrl-specs.js b/public/test/specs/seriesOverridesCtrl-specs.js index 75030bea077..f83a87d5977 100644 --- a/public/test/specs/seriesOverridesCtrl-specs.js +++ b/public/test/specs/seriesOverridesCtrl-specs.js @@ -1,6 +1,6 @@ define([ './helpers', - 'app/plugins/panel/graph/seriesOverridesCtrl' + 'app/plugins/panel/graph/series_overrides_ctrl' ], function(helpers) { 'use strict'; diff --git a/public/test/specs/templateValuesSrv-specs.js b/public/test/specs/templateValuesSrv-specs.js index 0eaa9d1d99f..d81ce4fd059 100644 --- a/public/test/specs/templateValuesSrv-specs.js +++ b/public/test/specs/templateValuesSrv-specs.js @@ -34,12 +34,13 @@ define([ options: [{text: "test", value: "test"}] }; - beforeEach(function() { + beforeEach(function(done) { var dashboard = { templating: { list: [variable] } }; var urlParams = {}; urlParams["var-apps"] = "new"; ctx.$location.search = sinon.stub().returns(urlParams); - ctx.service.init(dashboard); + ctx.service.init(dashboard).then(function() { done(); }); + ctx.$rootScope.$digest(); }); it('should update current value', function() { @@ -56,12 +57,13 @@ define([ options: [{text: "val1", value: "val1"}, {text: 'val2', value: 'val2'}, {text: 'val3', value: 'val3', selected: true}] }; - beforeEach(function() { + beforeEach(function(done) { var dashboard = { templating: { list: [variable] } }; var urlParams = {}; urlParams["var-apps"] = ["val2", "val1"]; ctx.$location.search = sinon.stub().returns(urlParams); - ctx.service.init(dashboard); + ctx.service.init(dashboard).then(function() { done(); }); + ctx.$rootScope.$digest(); }); it('should update current value', function() {