Sfoglia il codice sorgente

Merge branch 'master' into backend_plugins

* master:
  changelog: adds note about closing #10131
  Explicitly specify default region in CloudWatch datasource (#9440)
  wait for all sub routines to finish
  changelog: adds ntoe about closing #10111
  postgres: change $__timeGroup macro to include "AS time" column alias (#10119)
  fixes broken test
  Solves problem with Github authentication restriction by organization membership when the organization's access policy is set to "Access restricted". "Access restricted" policy should not stop user to authenticate.
bergquist 8 anni fa
parent
commit
a4748d82ab

+ 5 - 0
CHANGELOG.md

@@ -33,9 +33,14 @@ From `/etc/grafana/datasources` to `/etc/grafana/provisioning/datasources` when
 * **Dashboard**: Make it possible to start dashboards from search and dashboard list panel [#1871](https://github.com/grafana/grafana/issues/1871)
 * **Dashboard**: Make it possible to start dashboards from search and dashboard list panel [#1871](https://github.com/grafana/grafana/issues/1871)
 * **Annotations**: Posting annotations now return the id of the annotation [#9798](https://github.com/grafana/grafana/issues/9798)
 * **Annotations**: Posting annotations now return the id of the annotation [#9798](https://github.com/grafana/grafana/issues/9798)
 * **Systemd**: Use systemd notification ready flag [#10024](https://github.com/grafana/grafana/issues/10024), thx [@jgrassler](https://github.com/jgrassler)
 * **Systemd**: Use systemd notification ready flag [#10024](https://github.com/grafana/grafana/issues/10024), thx [@jgrassler](https://github.com/jgrassler)
+* **Github**: Use organizations_url provided from github to verify user belongs in org. [#10111](https://github.com/grafana/grafana/issues/10111), thx 
+[@adiletmaratov](https://github.com/adiletmaratov)
+* **Backend**: Fixed bug where Grafana exited before all sub routines where finished [#10131](https://github.com/grafana/grafana/issues/10131)
+
 ## Tech
 ## Tech
 * **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
 * **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
 
 
+
 ## Fixes
 ## Fixes
 * **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
 * **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
 * **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu)
 * **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu)

+ 4 - 1
docs/sources/features/datasources/cloudwatch.md

@@ -78,11 +78,14 @@ CloudWatch Datasource Plugin provides the following queries you can specify in t
 edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names`
 edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names`
 and `dimension keys/values`.
 and `dimension keys/values`.
 
 
+In place of `region` you can specify `default` to use the default region configured in the datasource for the query,
+e.g. `metrics(AWS/DynamoDB, default)` or `dimension_values(default, ..., ..., ...)`.
+
 Name | Description
 Name | Description
 ------- | --------
 ------- | --------
 *regions()* | Returns a list of regions AWS provides their service.
 *regions()* | Returns a list of regions AWS provides their service.
 *namespaces()* | Returns a list of namespaces CloudWatch support.
 *namespaces()* | Returns a list of namespaces CloudWatch support.
-*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region for custom metrics)
+*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region or use "default" for custom metrics)
 *dimension_keys(namespace)* | Returns a list of dimension keys in the namespace.
 *dimension_keys(namespace)* | Returns a list of dimension keys in the namespace.
 *dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
 *dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
 *ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
 *ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.

+ 3 - 3
docs/sources/features/datasources/postgres.md

@@ -48,7 +48,7 @@ Macro example | Description
 *$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *extract(epoch from dateColumn) BETWEEN 1494410783 AND 1494497183*
 *$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *extract(epoch from dateColumn) BETWEEN 1494410783 AND 1494497183*
 *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
 *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
 *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
 *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
-*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from "dateColumn")/300)::bigint*300*
+*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300 AS time*
 *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
 *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
 *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
 *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
 *$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
 *$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
@@ -94,7 +94,7 @@ Example with `metric` column
 
 
 ```sql
 ```sql
 SELECT
 SELECT
-  $__timeGroup(time_date_time,'5m') as time,
+  $__timeGroup(time_date_time,'5m'),
   min(value_double),
   min(value_double),
   'min' as metric
   'min' as metric
 FROM test_data
 FROM test_data
@@ -107,7 +107,7 @@ Example with multiple columns:
 
 
 ```sql
 ```sql
 SELECT
 SELECT
-  $__timeGroup(time_date_time,'5m') as time,
+  $__timeGroup(time_date_time,'5m'),
   min(value_double) as min_value,
   min(value_double) as min_value,
   max(value_double) as max_value
   max(value_double) as max_value
 FROM test_data
 FROM test_data

+ 1 - 1
pkg/api/http_server.go

@@ -95,7 +95,7 @@ func (hs *HttpServer) Start(ctx context.Context) error {
 
 
 func (hs *HttpServer) Shutdown(ctx context.Context) error {
 func (hs *HttpServer) Shutdown(ctx context.Context) error {
 	err := hs.httpSrv.Shutdown(ctx)
 	err := hs.httpSrv.Shutdown(ctx)
-	hs.log.Info("stopped http server")
+	hs.log.Info("Stopped HTTP server")
 	return err
 	return err
 }
 }
 
 

+ 24 - 9
pkg/cmd/grafana-server/main.go

@@ -14,8 +14,8 @@ import (
 	"net/http"
 	"net/http"
 	_ "net/http/pprof"
 	_ "net/http/pprof"
 
 
+	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/metrics"
-	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 
 
 	_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
 	_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
@@ -40,9 +40,6 @@ var homePath = flag.String("homepath", "", "path to grafana install/home path, d
 var pidFile = flag.String("pidfile", "", "path to pid file")
 var pidFile = flag.String("pidfile", "", "path to pid file")
 var exitChan = make(chan int)
 var exitChan = make(chan int)
 
 
-func init() {
-}
-
 func main() {
 func main() {
 	v := flag.Bool("v", false, "prints current version and exits")
 	v := flag.Bool("v", false, "prints current version and exits")
 	profile := flag.Bool("profile", false, "Turn on pprof profiling")
 	profile := flag.Bool("profile", false, "Turn on pprof profiling")
@@ -82,12 +79,28 @@ func main() {
 	setting.BuildStamp = buildstampInt64
 	setting.BuildStamp = buildstampInt64
 
 
 	metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
 	metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
-
+	shutdownCompleted := make(chan int)
 	server := NewGrafanaServer()
 	server := NewGrafanaServer()
-	server.Start()
+
+	go listenToSystemSignals(server, shutdownCompleted)
+
+	go func() {
+		code := 0
+		if err := server.Start(); err != nil {
+			log.Error2("Startup failed", "error", err)
+			code = 1
+		}
+
+		exitChan <- code
+	}()
+
+	code := <-shutdownCompleted
+	log.Info2("Grafana shutdown completed.", "code", code)
+	log.Close()
+	os.Exit(code)
 }
 }
 
 
-func listenToSystemSignals(server models.GrafanaServer) {
+func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
 	signalChan := make(chan os.Signal, 1)
 	signalChan := make(chan os.Signal, 1)
 	ignoreChan := make(chan os.Signal, 1)
 	ignoreChan := make(chan os.Signal, 1)
 	code := 0
 	code := 0
@@ -97,10 +110,12 @@ func listenToSystemSignals(server models.GrafanaServer) {
 
 
 	select {
 	select {
 	case sig := <-signalChan:
 	case sig := <-signalChan:
-		// Stops trace if profiling has been enabled
-		trace.Stop()
+		trace.Stop() // Stops trace if profiling has been enabled
 		server.Shutdown(0, fmt.Sprintf("system signal: %s", sig))
 		server.Shutdown(0, fmt.Sprintf("system signal: %s", sig))
+		shutdownCompleted <- 0
 	case code = <-exitChan:
 	case code = <-exitChan:
+		trace.Stop() // Stops trace if profiling has been enabled
 		server.Shutdown(code, "startup error")
 		server.Shutdown(code, "startup error")
+		shutdownCompleted <- code
 	}
 	}
 }
 }

+ 16 - 26
pkg/cmd/grafana-server/server.go

@@ -11,7 +11,6 @@ import (
 	"strconv"
 	"strconv"
 	"time"
 	"time"
 
 
-	"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
 	"github.com/grafana/grafana/pkg/services/provisioning"
 	"github.com/grafana/grafana/pkg/services/provisioning"
 
 
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
@@ -20,7 +19,6 @@ import (
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/login"
 	"github.com/grafana/grafana/pkg/login"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/metrics"
-	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/services/alerting"
 	"github.com/grafana/grafana/pkg/services/alerting"
 	"github.com/grafana/grafana/pkg/services/cleanup"
 	"github.com/grafana/grafana/pkg/services/cleanup"
@@ -33,7 +31,7 @@ import (
 	"github.com/grafana/grafana/pkg/tracing"
 	"github.com/grafana/grafana/pkg/tracing"
 )
 )
 
 
-func NewGrafanaServer() models.GrafanaServer {
+func NewGrafanaServer() *GrafanaServerImpl {
 	rootCtx, shutdownFn := context.WithCancel(context.Background())
 	rootCtx, shutdownFn := context.WithCancel(context.Background())
 	childRoutines, childCtx := errgroup.WithContext(rootCtx)
 	childRoutines, childCtx := errgroup.WithContext(rootCtx)
 
 
@@ -54,9 +52,7 @@ type GrafanaServerImpl struct {
 	httpServer *api.HttpServer
 	httpServer *api.HttpServer
 }
 }
 
 
-func (g *GrafanaServerImpl) Start() {
-	go listenToSystemSignals(g)
-
+func (g *GrafanaServerImpl) Start() error {
 	g.initLogging()
 	g.initLogging()
 	g.writePIDFile()
 	g.writePIDFile()
 
 
@@ -75,16 +71,12 @@ func (g *GrafanaServerImpl) Start() {
 	defer pluginCloser()
 	defer pluginCloser()
 
 
 	if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil {
 	if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil {
-		logger.Error("Failed to provision Grafana from config", "error", err)
-		g.Shutdown(1, "Startup failed")
-		return
+		return fmt.Errorf("Failed to provision Grafana from config. error: %v", err)
 	}
 	}
 
 
 	tracingCloser, err := tracing.Init(setting.Cfg)
 	tracingCloser, err := tracing.Init(setting.Cfg)
 	if err != nil {
 	if err != nil {
-		g.log.Error("Tracing settings is not valid", "error", err)
-		g.Shutdown(1, "Startup failed")
-		return
+		return fmt.Errorf("Tracing settings is not valid. error: %v", err)
 	}
 	}
 	defer tracingCloser.Close()
 	defer tracingCloser.Close()
 
 
@@ -99,13 +91,12 @@ func (g *GrafanaServerImpl) Start() {
 	g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
 	g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
 
 
 	if err = notifications.Init(); err != nil {
 	if err = notifications.Init(); err != nil {
-		g.log.Error("Notification service failed to initialize", "error", err)
-		g.Shutdown(1, "Startup failed")
-		return
+		return fmt.Errorf("Notification service failed to initialize. error: %v", err)
 	}
 	}
 
 
-	SendSystemdNotification("READY=1")
-	g.startHttpServer()
+	sendSystemdNotification("READY=1")
+
+	return g.startHttpServer()
 }
 }
 
 
 func initSql() {
 func initSql() {
@@ -129,16 +120,16 @@ func (g *GrafanaServerImpl) initLogging() {
 	setting.LogConfigurationInfo()
 	setting.LogConfigurationInfo()
 }
 }
 
 
-func (g *GrafanaServerImpl) startHttpServer() {
+func (g *GrafanaServerImpl) startHttpServer() error {
 	g.httpServer = api.NewHttpServer()
 	g.httpServer = api.NewHttpServer()
 
 
 	err := g.httpServer.Start(g.context)
 	err := g.httpServer.Start(g.context)
 
 
 	if err != nil {
 	if err != nil {
-		g.log.Error("Fail to start server", "error", err)
-		g.Shutdown(1, "Startup failed")
-		return
+		return fmt.Errorf("Fail to start server. error: %v", err)
 	}
 	}
+
+	return nil
 }
 }
 
 
 func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
 func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
@@ -151,10 +142,9 @@ func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
 
 
 	g.shutdownFn()
 	g.shutdownFn()
 	err = g.childRoutines.Wait()
 	err = g.childRoutines.Wait()
-
-	g.log.Info("Shutdown completed", "reason", err)
-	log.Close()
-	os.Exit(code)
+	if err != nil && err != context.Canceled {
+		g.log.Error("Server shutdown completed with an error", "error", err)
+	}
 }
 }
 
 
 func (g *GrafanaServerImpl) writePIDFile() {
 func (g *GrafanaServerImpl) writePIDFile() {
@@ -179,7 +169,7 @@ func (g *GrafanaServerImpl) writePIDFile() {
 	g.log.Info("Writing PID file", "path", *pidFile, "pid", pid)
 	g.log.Info("Writing PID file", "path", *pidFile, "pid", pid)
 }
 }
 
 
-func SendSystemdNotification(state string) error {
+func sendSystemdNotification(state string) error {
 	notifySocket := os.Getenv("NOTIFY_SOCKET")
 	notifySocket := os.Getenv("NOTIFY_SOCKET")
 
 
 	if notifySocket == "" {
 	if notifySocket == "" {

+ 0 - 6
pkg/models/server.go

@@ -1,6 +0,0 @@
-package models
-
-type GrafanaServer interface {
-	Start()
-	Shutdown(code int, reason string)
-}

+ 1 - 1
pkg/services/provisioning/dashboards/config_reader_test.go

@@ -8,7 +8,7 @@ import (
 
 
 var (
 var (
 	simpleDashboardConfig string = "./test-configs/dashboards-from-disk"
 	simpleDashboardConfig string = "./test-configs/dashboards-from-disk"
-	brokenConfigs         string = "./test-configs/borken-configs"
+	brokenConfigs         string = "./test-configs/broken-configs"
 )
 )
 
 
 func TestDashboardsAsConfig(t *testing.T) {
 func TestDashboardsAsConfig(t *testing.T) {

+ 10 - 8
pkg/social/github_oauth.go

@@ -58,12 +58,12 @@ func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
 	return false
 	return false
 }
 }
 
 
-func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
+func (s *SocialGithub) IsOrganizationMember(client *http.Client, organizationsUrl string) bool {
 	if len(s.allowedOrganizations) == 0 {
 	if len(s.allowedOrganizations) == 0 {
 		return true
 		return true
 	}
 	}
 
 
-	organizations, err := s.FetchOrganizations(client)
+	organizations, err := s.FetchOrganizations(client, organizationsUrl)
 	if err != nil {
 	if err != nil {
 		return false
 		return false
 	}
 	}
@@ -167,12 +167,12 @@ func (s *SocialGithub) HasMoreRecords(headers http.Header) (string, bool) {
 
 
 }
 }
 
 
-func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
+func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl string) ([]string, error) {
 	type Record struct {
 	type Record struct {
 		Login string `json:"login"`
 		Login string `json:"login"`
 	}
 	}
 
 
-	response, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs"))
+	response, err := HttpGet(client, organizationsUrl)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Error getting organizations: %s", err)
 		return nil, fmt.Errorf("Error getting organizations: %s", err)
 	}
 	}
@@ -193,10 +193,12 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error)
 }
 }
 
 
 func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
 func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
+
 	var data struct {
 	var data struct {
-		Id    int    `json:"id"`
-		Login string `json:"login"`
-		Email string `json:"email"`
+		Id               int    `json:"id"`
+		Login            string `json:"login"`
+		Email            string `json:"email"`
+		OrganizationsUrl string `json:"organizations_url"`
 	}
 	}
 
 
 	response, err := HttpGet(client, s.apiUrl)
 	response, err := HttpGet(client, s.apiUrl)
@@ -219,7 +221,7 @@ func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
 		return nil, ErrMissingTeamMembership
 		return nil, ErrMissingTeamMembership
 	}
 	}
 
 
-	if !s.IsOrganizationMember(client) {
+	if !s.IsOrganizationMember(client, data.OrganizationsUrl) {
 		return nil, ErrMissingOrganizationMembership
 		return nil, ErrMissingOrganizationMembership
 	}
 	}
 
 

+ 5 - 0
pkg/tsdb/cloudwatch/credentials.go

@@ -141,6 +141,11 @@ func ec2RoleProvider(sess *session.Session) credentials.Provider {
 }
 }
 
 
 func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
 func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
+	defaultRegion := e.DataSource.JsonData.Get("defaultRegion").MustString()
+	if region == "default" {
+		region = defaultRegion
+	}
+
 	authType := e.DataSource.JsonData.Get("authType").MustString()
 	authType := e.DataSource.JsonData.Get("authType").MustString()
 	assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
 	assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
 	accessKey := ""
 	accessKey := ""

+ 1 - 1
pkg/tsdb/postgres/macros.go

@@ -89,7 +89,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
 		if err != nil {
 		if err != nil {
 			return "", fmt.Errorf("error parsing interval %v", args[1])
 			return "", fmt.Errorf("error parsing interval %v", args[1])
 		}
 		}
-		return fmt.Sprintf("(extract(epoch from \"%s\")/%v)::bigint*%v", args[0], interval.Seconds(), interval.Seconds()), nil
+		return fmt.Sprintf("(extract(epoch from %s)/%v)::bigint*%v AS time", args[0], interval.Seconds(), interval.Seconds()), nil
 	case "__unixEpochFilter":
 	case "__unixEpochFilter":
 		if len(args) == 0 {
 		if len(args) == 0 {
 			return "", fmt.Errorf("missing time column argument for macro %v", name)
 			return "", fmt.Errorf("missing time column argument for macro %v", name)

+ 1 - 1
pkg/tsdb/postgres/macros_test.go

@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) {
 			sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
 			sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
-			So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/300)::bigint*300")
+			So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time")
 		})
 		})
 
 
 		Convey("interpolate __timeTo function", func() {
 		Convey("interpolate __timeTo function", func() {

+ 14 - 7
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -39,7 +39,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
           !!item.metricName &&
           !!item.metricName &&
           !_.isEmpty(item.statistics);
           !_.isEmpty(item.statistics);
       }).map(function (item) {
       }).map(function (item) {
-        item.region = templateSrv.replace(item.region, options.scopedVars);
+        item.region = templateSrv.replace(self.getActualRegion(item.region), options.scopedVars);
         item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
         item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
         item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
         item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
         item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
         item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
@@ -165,21 +165,21 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
 
 
     this.getMetrics = function (namespace, region) {
     this.getMetrics = function (namespace, region) {
       return this.doMetricQueryRequest('metrics', {
       return this.doMetricQueryRequest('metrics', {
-        region: templateSrv.replace(region),
+        region: templateSrv.replace(this.getActualRegion(region)),
         namespace: templateSrv.replace(namespace)
         namespace: templateSrv.replace(namespace)
       });
       });
     };
     };
 
 
     this.getDimensionKeys = function(namespace, region) {
     this.getDimensionKeys = function(namespace, region) {
       return this.doMetricQueryRequest('dimension_keys', {
       return this.doMetricQueryRequest('dimension_keys', {
-        region: templateSrv.replace(region),
+        region: templateSrv.replace(this.getActualRegion(region)),
         namespace: templateSrv.replace(namespace)
         namespace: templateSrv.replace(namespace)
       });
       });
     };
     };
 
 
     this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
     this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
       return this.doMetricQueryRequest('dimension_values', {
       return this.doMetricQueryRequest('dimension_values', {
-        region: templateSrv.replace(region),
+        region: templateSrv.replace(this.getActualRegion(region)),
         namespace: templateSrv.replace(namespace),
         namespace: templateSrv.replace(namespace),
         metricName: templateSrv.replace(metricName),
         metricName: templateSrv.replace(metricName),
         dimensionKey: templateSrv.replace(dimensionKey),
         dimensionKey: templateSrv.replace(dimensionKey),
@@ -189,14 +189,14 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
 
 
     this.getEbsVolumeIds = function(region, instanceId) {
     this.getEbsVolumeIds = function(region, instanceId) {
       return this.doMetricQueryRequest('ebs_volume_ids', {
       return this.doMetricQueryRequest('ebs_volume_ids', {
-        region: templateSrv.replace(region),
+        region: templateSrv.replace(this.getActualRegion(region)),
         instanceId: templateSrv.replace(instanceId)
         instanceId: templateSrv.replace(instanceId)
       });
       });
     };
     };
 
 
     this.getEc2InstanceAttribute = function(region, attributeName, filters) {
     this.getEc2InstanceAttribute = function(region, attributeName, filters) {
       return this.doMetricQueryRequest('ec2_instance_attribute', {
       return this.doMetricQueryRequest('ec2_instance_attribute', {
-        region: templateSrv.replace(region),
+        region: templateSrv.replace(this.getActualRegion(region)),
         attributeName: templateSrv.replace(attributeName),
         attributeName: templateSrv.replace(attributeName),
         filters: filters
         filters: filters
       });
       });
@@ -267,7 +267,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
       period = parseInt(period, 10);
       period = parseInt(period, 10);
       var parameters = {
       var parameters = {
         prefixMatching: annotation.prefixMatching,
         prefixMatching: annotation.prefixMatching,
-        region: templateSrv.replace(annotation.region),
+        region: templateSrv.replace(this.getActualRegion(annotation.region)),
         namespace: templateSrv.replace(annotation.namespace),
         namespace: templateSrv.replace(annotation.namespace),
         metricName: templateSrv.replace(annotation.metricName),
         metricName: templateSrv.replace(annotation.metricName),
         dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
         dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
@@ -341,6 +341,13 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
       return this.defaultRegion;
       return this.defaultRegion;
     };
     };
 
 
+    this.getActualRegion = function(region) {
+      if (region === 'default' || _.isEmpty(region)) {
+        return this.getDefaultRegion();
+      }
+      return region;
+    };
+
     this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
     this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
       /* if the all checkbox is marked we should add all values to the targets */
       /* if the all checkbox is marked we should add all values to the targets */
       var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
       var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});

+ 6 - 2
public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts

@@ -28,7 +28,7 @@ export class CloudWatchQueryParameterCtrl {
       target.statistics = target.statistics || ['Average'];
       target.statistics = target.statistics || ['Average'];
       target.dimensions = target.dimensions || {};
       target.dimensions = target.dimensions || {};
       target.period = target.period || '';
       target.period = target.period || '';
-      target.region = target.region || '';
+      target.region = target.region || 'default';
 
 
       $scope.regionSegment =  uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region');
       $scope.regionSegment =  uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region');
       $scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace');
       $scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace');
@@ -51,7 +51,7 @@ export class CloudWatchQueryParameterCtrl {
       $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'});
       $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'});
 
 
       if (_.isEmpty($scope.target.region)) {
       if (_.isEmpty($scope.target.region)) {
-        $scope.target.region = $scope.datasource.getDefaultRegion();
+        $scope.target.region = 'default';
       }
       }
 
 
       if (!$scope.onChange) {
       if (!$scope.onChange) {
@@ -148,6 +148,10 @@ export class CloudWatchQueryParameterCtrl {
 
 
     $scope.getRegions = function() {
     $scope.getRegions = function() {
       return $scope.datasource.metricFindQuery('regions()')
       return $scope.datasource.metricFindQuery('regions()')
+      .then(function(results) {
+        results.unshift({ text: 'default'});
+        return results;
+      })
       .then($scope.transformToSegments(true));
       .then($scope.transformToSegments(true));
     };
     };
 
 

+ 69 - 0
public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts

@@ -165,6 +165,55 @@ describe('CloudWatchDatasource', function() {
     });
     });
   });
   });
 
 
+  describe('When query region is "default"', function () {
+    it('should return the datasource region if empty or "default"', function() {
+      var defaultRegion = instanceSettings.jsonData.defaultRegion;
+
+      expect(ctx.ds.getActualRegion()).to.be(defaultRegion);
+      expect(ctx.ds.getActualRegion('')).to.be(defaultRegion);
+      expect(ctx.ds.getActualRegion("default")).to.be(defaultRegion);
+    });
+
+    it('should return the specified region if specified', function() {
+      expect(ctx.ds.getActualRegion('some-fake-region-1')).to.be('some-fake-region-1');
+    });
+
+    var requestParams;
+    beforeEach(function() {
+      ctx.ds.performTimeSeriesQuery = function(request) {
+        requestParams = request;
+        return ctx.$q.when({data: {}});
+      };
+    });
+
+    it('should query for the datasource region if empty or "default"', function(done) {
+      var query = {
+        range: { from: 'now-1h', to: 'now' },
+        rangeRaw: { from: 1483228800, to: 1483232400 },
+        targets: [
+          {
+            region: 'default',
+            namespace: 'AWS/EC2',
+            metricName: 'CPUUtilization',
+            dimensions: {
+              InstanceId: 'i-12345678'
+            },
+            statistics: ['Average'],
+            period: 300
+          }
+        ]
+      };
+
+      ctx.ds.query(query).then(function(result) {
+        expect(requestParams.queries[0].region).to.be(instanceSettings.jsonData.defaultRegion);
+        done();
+      });
+      ctx.$rootScope.$apply();
+    });
+
+
+  });
+
   describe('When performing CloudWatch query for extended statistics', function() {
   describe('When performing CloudWatch query for extended statistics', function() {
     var requestParams;
     var requestParams;
 
 
@@ -348,6 +397,26 @@ describe('CloudWatchDatasource', function() {
     });
     });
   });
   });
 
 
+  describeMetricFindQuery('dimension_values(default,AWS/EC2,CPUUtilization,InstanceId)', scenario => {
+    scenario.setup(() => {
+      scenario.requestResponse = {
+        results: {
+          metricFindQuery: {
+            tables: [
+              { rows: [['i-12345678', 'i-12345678']] }
+            ]
+          }
+        }
+      };
+    });
+
+    it('should call __ListMetrics and return result', () => {
+      expect(scenario.result[0].text).to.contain('i-12345678');
+      expect(scenario.request.queries[0].type).to.be('metricFindQuery');
+      expect(scenario.request.queries[0].subtype).to.be('dimension_values');
+    });
+  });
+
   it('should caclculate the correct period', function () {
   it('should caclculate the correct period', function () {
     var hourSec = 60 * 60;
     var hourSec = 60 * 60;
     var daySec = hourSec * 24;
     var daySec = hourSec * 24;

+ 2 - 2
public/app/plugins/datasource/postgres/partials/query.editor.html

@@ -50,11 +50,11 @@ Macros:
 - $__timeEpoch -&gt; extract(epoch from column) as "time"
 - $__timeEpoch -&gt; extract(epoch from column) as "time"
 - $__timeFilter(column) -&gt;  extract(epoch from column) BETWEEN 1492750877 AND 1492750877
 - $__timeFilter(column) -&gt;  extract(epoch from column) BETWEEN 1492750877 AND 1492750877
 - $__unixEpochFilter(column) -&gt;  column &gt; 1492750877 AND column &lt; 1492750877
 - $__unixEpochFilter(column) -&gt;  column &gt; 1492750877 AND column &lt; 1492750877
-- $__timeGroup(column,'5m') -&gt; (extract(epoch from "dateColumn")/300)::bigint*300
+- $__timeGroup(column,'5m') -&gt; (extract(epoch from column)/300)::bigint*300 AS time
 
 
 Example of group by and order by with $__timeGroup:
 Example of group by and order by with $__timeGroup:
 SELECT
 SELECT
-  $__timeGroup(date_time_col, '1h') AS time,
+  $__timeGroup(date_time_col, '1h'),
   sum(value) as value
   sum(value) as value
 FROM yourtable
 FROM yourtable
 GROUP BY time
 GROUP BY time