Browse Source

Merge branch 'master' into docs-5.1

Marcus Efraimsson 7 năm trước cách đây
mục cha
commit
979f2d79fb

+ 5 - 0
CHANGELOG.md

@@ -16,8 +16,10 @@
 * **Table**: Table plugin value mappings [#7119](https://github.com/grafana/grafana/issues/7119), thx [infernix](https://github.com/infernix)
 * **Table**: Table plugin value mappings [#7119](https://github.com/grafana/grafana/issues/7119), thx [infernix](https://github.com/infernix)
 * **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165)
 * **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165)
 * **Scrolling**: Better scrolling experience [#11053](https://github.com/grafana/grafana/issues/11053), [#11252](https://github.com/grafana/grafana/issues/11252), [#10836](https://github.com/grafana/grafana/issues/10836), [#11185](https://github.com/grafana/grafana/issues/11185), [#11168](https://github.com/grafana/grafana/issues/11168)
 * **Scrolling**: Better scrolling experience [#11053](https://github.com/grafana/grafana/issues/11053), [#11252](https://github.com/grafana/grafana/issues/11252), [#10836](https://github.com/grafana/grafana/issues/10836), [#11185](https://github.com/grafana/grafana/issues/11185), [#11168](https://github.com/grafana/grafana/issues/11168)
+* **Docker**: Improved docker image (breaking changes regarding file ownership) [grafana-docker #141](https://github.com/grafana/grafana-docker/issues/141), thx [@Spindel](https://github.com/Spindel), [@ChristianKniep](https://github.com/ChristianKniep), [@brancz](https://github.com/brancz) and [@jangaraj](https://github.com/jangaraj)
 
 
 ### Minor
 ### Minor
+
 * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes)
 * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes)
 * **Cloudwatch**: Support high resolution metrics [#10925](https://github.com/grafana/grafana/pull/10925), thx [@mtanda](https://github.com/mtanda)
 * **Cloudwatch**: Support high resolution metrics [#10925](https://github.com/grafana/grafana/pull/10925), thx [@mtanda](https://github.com/mtanda)
 * **Cloudwatch**: Add dimension filtering to CloudWatch `dimension_values()` [#10029](https://github.com/grafana/grafana/issues/10029), thx [@willyhutw](https://github.com/willyhutw)
 * **Cloudwatch**: Add dimension filtering to CloudWatch `dimension_values()` [#10029](https://github.com/grafana/grafana/issues/10029), thx [@willyhutw](https://github.com/willyhutw)
@@ -45,6 +47,9 @@
 * **Heatmap**: Disable log scale when using time time series buckets [#10792](https://github.com/grafana/grafana/issues/10792)
 * **Heatmap**: Disable log scale when using time time series buckets [#10792](https://github.com/grafana/grafana/issues/10792)
 * **Provisioning**: Remove `id` from json when provisioning dashboards, [#11138](https://github.com/grafana/grafana/issues/11138)
 * **Provisioning**: Remove `id` from json when provisioning dashboards, [#11138](https://github.com/grafana/grafana/issues/11138)
 * **Prometheus**: tooltip for legend format not showing properly [#11516](https://github.com/grafana/grafana/issues/11516), thx [@svenklemm](https://github.com/svenklemm) 
 * **Prometheus**: tooltip for legend format not showing properly [#11516](https://github.com/grafana/grafana/issues/11516), thx [@svenklemm](https://github.com/svenklemm) 
+* **Playlist**: Empty playlists cannot be deleted [#11133](https://github.com/grafana/grafana/issues/11133), thx [@kichristensen](https://github.com/kichristensen) 
+* **Switch Orgs**: Alphabetic order in Switch Organization modal [#11556](https://github.com/grafana/grafana/issues/11556)
+* **Postgres**: improve `$__timeFilter` macro [#11578](https://github.com/grafana/grafana/issues/11578), thx [@svenklemm](https://github.com/svenklemm)
 
 
 ### Tech
 ### Tech
 * Migrated JavaScript files to TypeScript
 * Migrated JavaScript files to TypeScript

+ 16 - 0
docs/sources/features/datasources/elasticsearch.md

@@ -61,6 +61,22 @@ a time pattern for the index name or a wildcard.
 Be sure to specify your Elasticsearch version in the version selection dropdown. This is very important as there are differences how queries are composed. Currently only 2.x and 5.x
 Be sure to specify your Elasticsearch version in the version selection dropdown. This is very important as there are differences how queries are composed. Currently only 2.x and 5.x
 are supported.
 are supported.
 
 
+### Min time interval
+A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example `1m` if your data is written every minute.
+This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formated as a
+number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported:
+
+Identifier | Description
+------------ | -------------
+`y`   | year
+`M`   | month
+`w`   | week
+`d`   | day
+`h`   | hour
+`m`   | minute
+`s`   | second
+`ms`  | millisecond
+
 ## Metric Query editor
 ## Metric Query editor
 
 
 ![Elasticsearch Query Editor](/img/docs/elasticsearch/query_editor.png)
 ![Elasticsearch Query Editor](/img/docs/elasticsearch/query_editor.png)

+ 16 - 0
docs/sources/features/datasources/influxdb.md

@@ -43,6 +43,22 @@ All requests will be made from the browser to Grafana backend/server which in tu
 
 
 All requests will be made from the browser directly to the data source and may be subject to Cross-Origin Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this access mode.
 All requests will be made from the browser directly to the data source and may be subject to Cross-Origin Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this access mode.
 
 
+### Min time interval
+A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example `1m` if your data is written every minute.
+This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formated as a
+number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported:
+
+Identifier | Description
+------------ | -------------
+`y`   | year
+`M`   | month
+`w`   | week
+`d`   | day
+`h`   | hour
+`m`   | minute
+`s`   | second
+`ms`  | millisecond
+
 ## Query Editor
 ## Query Editor
 
 
 {{< docs-imagebox img="/img/docs/v45/influxdb_query_still.png" class="docs-image--no-shadow" animated-gif="/img/docs/v45/influxdb_query.gif" >}}
 {{< docs-imagebox img="/img/docs/v45/influxdb_query_still.png" class="docs-image--no-shadow" animated-gif="/img/docs/v45/influxdb_query.gif" >}}

+ 7 - 1
pkg/api/index.go

@@ -118,9 +118,14 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 	})
 	})
 
 
 	if c.IsSignedIn {
 	if c.IsSignedIn {
+		// Only set login if it's different from the name
+		var login string
+		if c.SignedInUser.Login != c.SignedInUser.NameOrFallback() {
+			login = c.SignedInUser.Login
+		}
 		profileNode := &dtos.NavLink{
 		profileNode := &dtos.NavLink{
 			Text:         c.SignedInUser.NameOrFallback(),
 			Text:         c.SignedInUser.NameOrFallback(),
-			SubTitle:     c.SignedInUser.Login,
+			SubTitle:     login,
 			Id:           "profile",
 			Id:           "profile",
 			Img:          data.User.GravatarUrl,
 			Img:          data.User.GravatarUrl,
 			Url:          setting.AppSubUrl + "/profile",
 			Url:          setting.AppSubUrl + "/profile",
@@ -284,6 +289,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 
 
 	data.NavTree = append(data.NavTree, &dtos.NavLink{
 	data.NavTree = append(data.NavTree, &dtos.NavLink{
 		Text:         "Help",
 		Text:         "Help",
+		SubTitle:     fmt.Sprintf(`Grafana v%s (%s)`, setting.BuildVersion, setting.BuildCommit),
 		Id:           "help",
 		Id:           "help",
 		Url:          "#",
 		Url:          "#",
 		Icon:         "gicon gicon-question",
 		Icon:         "gicon gicon-question",

+ 1 - 1
pkg/api/playlist.go

@@ -33,7 +33,7 @@ func ValidateOrgPlaylist(c *m.ReqContext) {
 		return
 		return
 	}
 	}
 
 
-	if len(items) == 0 {
+	if len(items) == 0 && c.Context.Req.Method != "DELETE" {
 		c.JsonApiErr(404, "Playlist is empty", itemsErr)
 		c.JsonApiErr(404, "Playlist is empty", itemsErr)
 		return
 		return
 	}
 	}

+ 1 - 0
pkg/services/sqlstore/user.go

@@ -333,6 +333,7 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error {
 	sess.Join("INNER", "org", "org_user.org_id=org.id")
 	sess.Join("INNER", "org", "org_user.org_id=org.id")
 	sess.Where("org_user.user_id=?", query.UserId)
 	sess.Where("org_user.user_id=?", query.UserId)
 	sess.Cols("org.name", "org_user.role", "org_user.org_id")
 	sess.Cols("org.name", "org_user.role", "org_user.org_id")
+	sess.OrderBy("org.name")
 	err := sess.Find(&query.Result)
 	err := sess.Find(&query.Result)
 	return err
 	return err
 }
 }

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

@@ -79,15 +79,15 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
 		}
 		}
 		return fmt.Sprintf("extract(epoch from %s) as \"time\"", args[0]), nil
 		return fmt.Sprintf("extract(epoch from %s) as \"time\"", args[0]), nil
 	case "__timeFilter":
 	case "__timeFilter":
-		// don't use to_timestamp in this macro for redshift compatibility #9566
 		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)
 		}
 		}
-		return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil
+
+		return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.TimeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.TimeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
 	case "__timeFrom":
 	case "__timeFrom":
-		return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetFromAsSecondsEpoch()), nil
+		return fmt.Sprintf("'%s'", m.TimeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
 	case "__timeTo":
 	case "__timeTo":
-		return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetToAsSecondsEpoch()), nil
+		return fmt.Sprintf("'%s'", m.TimeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
 	case "__timeGroup":
 	case "__timeGroup":
 		if len(args) < 2 {
 		if len(args) < 2 {
 			return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
 			return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)

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

@@ -12,7 +12,7 @@ import (
 
 
 func TestMacroEngine(t *testing.T) {
 func TestMacroEngine(t *testing.T) {
 	Convey("MacroEngine", t, func() {
 	Convey("MacroEngine", t, func() {
-		engine := &PostgresMacroEngine{}
+		engine := NewPostgresMacroEngine()
 		query := &tsdb.Query{}
 		query := &tsdb.Query{}
 
 
 		Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
 		Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
@@ -38,14 +38,14 @@ func TestMacroEngine(t *testing.T) {
 				sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("WHERE extract(epoch from time_column) BETWEEN %d AND %d", from.Unix(), to.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __timeFrom function", func() {
 			Convey("interpolate __timeFrom function", func() {
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", from.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __timeGroup function", func() {
 			Convey("interpolate __timeGroup function", func() {
@@ -68,7 +68,7 @@ func TestMacroEngine(t *testing.T) {
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", to.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __unixEpochFilter function", func() {
 			Convey("interpolate __unixEpochFilter function", func() {
@@ -102,21 +102,21 @@ func TestMacroEngine(t *testing.T) {
 				sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("WHERE extract(epoch from time_column) BETWEEN %d AND %d", from.Unix(), to.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __timeFrom function", func() {
 			Convey("interpolate __timeFrom function", func() {
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", from.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __timeTo function", func() {
 			Convey("interpolate __timeTo function", func() {
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", to.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __unixEpochFilter function", func() {
 			Convey("interpolate __unixEpochFilter function", func() {
@@ -150,21 +150,21 @@ func TestMacroEngine(t *testing.T) {
 				sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("WHERE extract(epoch from time_column) BETWEEN %d AND %d", from.Unix(), to.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __timeFrom function", func() {
 			Convey("interpolate __timeFrom function", func() {
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", from.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __timeTo function", func() {
 			Convey("interpolate __timeTo function", func() {
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
 				sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", to.Unix()))
+				So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
 			})
 			})
 
 
 			Convey("interpolate __unixEpochFilter function", func() {
 			Convey("interpolate __unixEpochFilter function", func() {

+ 8 - 0
pkg/tsdb/time_range.go

@@ -37,6 +37,10 @@ func (tr *TimeRange) GetFromAsSecondsEpoch() int64 {
 	return tr.GetFromAsMsEpoch() / 1000
 	return tr.GetFromAsMsEpoch() / 1000
 }
 }
 
 
+func (tr *TimeRange) GetFromAsTimeUTC() time.Time {
+	return tr.MustGetFrom().UTC()
+}
+
 func (tr *TimeRange) GetToAsMsEpoch() int64 {
 func (tr *TimeRange) GetToAsMsEpoch() int64 {
 	return tr.MustGetTo().UnixNano() / int64(time.Millisecond)
 	return tr.MustGetTo().UnixNano() / int64(time.Millisecond)
 }
 }
@@ -45,6 +49,10 @@ func (tr *TimeRange) GetToAsSecondsEpoch() int64 {
 	return tr.GetToAsMsEpoch() / 1000
 	return tr.GetToAsMsEpoch() / 1000
 }
 }
 
 
+func (tr *TimeRange) GetToAsTimeUTC() time.Time {
+	return tr.MustGetTo().UTC()
+}
+
 func (tr *TimeRange) MustGetFrom() time.Time {
 func (tr *TimeRange) MustGetFrom() time.Time {
 	if res, err := tr.ParseFrom(); err != nil {
 	if res, err := tr.ParseFrom(); err != nil {
 		return time.Unix(0, 0)
 		return time.Unix(0, 0)

+ 4 - 1
public/app/core/components/sidemenu/sidemenu.html

@@ -54,6 +54,9 @@
       </span>
       </span>
     </a>
     </a>
     <ul class="dropdown-menu dropdown-menu--sidemenu" role="menu">
     <ul class="dropdown-menu dropdown-menu--sidemenu" role="menu">
+      <li ng-if="item.subTitle" class="sidemenu-subtitle">
+        <span class="sidemenu-item-text">{{::item.subTitle}}</span>
+      </li>
       <li ng-if="item.showOrgSwitcher" class="sidemenu-org-switcher">
       <li ng-if="item.showOrgSwitcher" class="sidemenu-org-switcher">
         <a ng-click="ctrl.switchOrg()">
         <a ng-click="ctrl.switchOrg()">
           <div>
           <div>
@@ -75,4 +78,4 @@
       </li>
       </li>
     </ul>
     </ul>
   </div>
   </div>
-</div>
+</div>

+ 0 - 236
public/app/core/directives/dropdown_typeahead.js

@@ -1,236 +0,0 @@
-define([
-  'lodash',
-  'jquery',
-  '../core_module',
-],
-function (_, $, coreModule) {
-  'use strict';
-
-  coreModule.default.directive('dropdownTypeahead', function($compile) {
-
-    var inputTemplate = '<input type="text"'+
-      ' class="gf-form-input input-medium tight-form-input"' +
-      ' spellcheck="false" style="display:none"></input>';
-
-    var buttonTemplate = '<a class="gf-form-label tight-form-func dropdown-toggle"' +
-      ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
-      ' data-placement="top"><i class="fa fa-plus"></i></a>';
-
-    return {
-      scope: {
-        menuItems: "=dropdownTypeahead",
-        dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect",
-        model: '=ngModel'
-      },
-      link: function($scope, elem, attrs) {
-        var $input = $(inputTemplate);
-        var $button = $(buttonTemplate);
-        $input.appendTo(elem);
-        $button.appendTo(elem);
-
-        if (attrs.linkText) {
-          $button.html(attrs.linkText);
-        }
-
-        if (attrs.ngModel) {
-          $scope.$watch('model', function(newValue) {
-            _.each($scope.menuItems, function(item) {
-              _.each(item.submenu, function(subItem) {
-                if (subItem.value === newValue) {
-                  $button.html(subItem.text);
-                }
-              });
-            });
-          });
-        }
-
-        var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
-          if (!value.submenu) {
-            value.click = 'menuItemSelected(' + index + ')';
-            memo.push(value.text);
-          } else {
-            _.each(value.submenu, function(item, subIndex) {
-              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
-              memo.push(value.text + ' ' + item.text);
-            });
-          }
-          return memo;
-        }, []);
-
-        $scope.menuItemSelected = function(index, subIndex) {
-          var menuItem = $scope.menuItems[index];
-          var payload = {$item: menuItem};
-          if (menuItem.submenu && subIndex !== void 0) {
-            payload.$subItem = menuItem.submenu[subIndex];
-          }
-          $scope.dropdownTypeaheadOnSelect(payload);
-        };
-
-        $input.attr('data-provide', 'typeahead');
-        $input.typeahead({
-          source: typeaheadValues,
-          minLength: 1,
-          items: 10,
-          updater: function (value) {
-            var result = {};
-            _.each($scope.menuItems, function(menuItem) {
-              _.each(menuItem.submenu, function(submenuItem) {
-                if (value === (menuItem.text + ' ' + submenuItem.text)) {
-                  result.$subItem = submenuItem;
-                  result.$item = menuItem;
-                }
-              });
-            });
-
-            if (result.$item) {
-              $scope.$apply(function() {
-                $scope.dropdownTypeaheadOnSelect(result);
-              });
-            }
-
-            $input.trigger('blur');
-            return '';
-          }
-        });
-
-        $button.click(function() {
-          $button.hide();
-          $input.show();
-          $input.focus();
-        });
-
-        $input.keyup(function() {
-          elem.toggleClass('open', $input.val() === '');
-        });
-
-        $input.blur(function() {
-          $input.hide();
-          $input.val('');
-          $button.show();
-          $button.focus();
-          // clicking the function dropdown menu won't
-          // work if you remove class at once
-          setTimeout(function() {
-            elem.removeClass('open');
-          }, 200);
-        });
-
-        $compile(elem.contents())($scope);
-      }
-    };
-  });
-
-  coreModule.default.directive('dropdownTypeahead2', function($compile) {
-
-    var inputTemplate = '<input type="text"'+
-      ' class="gf-form-input"' +
-      ' spellcheck="false" style="display:none"></input>';
-
-    var buttonTemplate = '<a class="gf-form-input dropdown-toggle"' +
-      ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
-      ' data-placement="top"><i class="fa fa-plus"></i></a>';
-
-    return {
-      scope: {
-        menuItems: "=dropdownTypeahead2",
-        dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect",
-        model: '=ngModel'
-      },
-      link: function($scope, elem, attrs) {
-        var $input = $(inputTemplate);
-        var $button = $(buttonTemplate);
-        $input.appendTo(elem);
-        $button.appendTo(elem);
-
-        if (attrs.linkText) {
-          $button.html(attrs.linkText);
-        }
-
-        if (attrs.ngModel) {
-          $scope.$watch('model', function(newValue) {
-            _.each($scope.menuItems, function(item) {
-              _.each(item.submenu, function(subItem) {
-                if (subItem.value === newValue) {
-                  $button.html(subItem.text);
-                }
-              });
-            });
-          });
-        }
-
-        var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
-          if (!value.submenu) {
-            value.click = 'menuItemSelected(' + index + ')';
-            memo.push(value.text);
-          } else {
-            _.each(value.submenu, function(item, subIndex) {
-              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
-              memo.push(value.text + ' ' + item.text);
-            });
-          }
-          return memo;
-        }, []);
-
-        $scope.menuItemSelected = function(index, subIndex) {
-          var menuItem = $scope.menuItems[index];
-          var payload = {$item: menuItem};
-          if (menuItem.submenu && subIndex !== void 0) {
-            payload.$subItem = menuItem.submenu[subIndex];
-          }
-          $scope.dropdownTypeaheadOnSelect(payload);
-        };
-
-        $input.attr('data-provide', 'typeahead');
-        $input.typeahead({
-          source: typeaheadValues,
-          minLength: 1,
-          items: 10,
-          updater: function (value) {
-            var result = {};
-            _.each($scope.menuItems, function(menuItem) {
-              _.each(menuItem.submenu, function(submenuItem) {
-                if (value === (menuItem.text + ' ' + submenuItem.text)) {
-                  result.$subItem = submenuItem;
-                  result.$item = menuItem;
-                }
-              });
-            });
-
-            if (result.$item) {
-              $scope.$apply(function() {
-                $scope.dropdownTypeaheadOnSelect(result);
-              });
-            }
-
-            $input.trigger('blur');
-            return '';
-          }
-        });
-
-        $button.click(function() {
-          $button.hide();
-          $input.show();
-          $input.focus();
-        });
-
-        $input.keyup(function() {
-          elem.toggleClass('open', $input.val() === '');
-        });
-
-        $input.blur(function() {
-          $input.hide();
-          $input.val('');
-          $button.show();
-          $button.focus();
-          // clicking the function dropdown menu won't
-          // work if you remove class at once
-          setTimeout(function() {
-            elem.removeClass('open');
-          }, 200);
-        });
-
-        $compile(elem.contents())($scope);
-      }
-    };
-  });
-});

+ 244 - 0
public/app/core/directives/dropdown_typeahead.ts

@@ -0,0 +1,244 @@
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from '../core_module';
+
+/** @ngInject */
+export function dropdownTypeahead($compile) {
+  let inputTemplate =
+    '<input type="text"' +
+    ' class="gf-form-input input-medium tight-form-input"' +
+    ' spellcheck="false" style="display:none"></input>';
+
+  let buttonTemplate =
+    '<a class="gf-form-label tight-form-func dropdown-toggle"' +
+    ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
+    ' data-placement="top"><i class="fa fa-plus"></i></a>';
+
+  return {
+    scope: {
+      menuItems: '=dropdownTypeahead',
+      dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
+      model: '=ngModel',
+    },
+    link: function($scope, elem, attrs) {
+      let $input = $(inputTemplate);
+      let $button = $(buttonTemplate);
+      $input.appendTo(elem);
+      $button.appendTo(elem);
+
+      if (attrs.linkText) {
+        $button.html(attrs.linkText);
+      }
+
+      if (attrs.ngModel) {
+        $scope.$watch('model', function(newValue) {
+          _.each($scope.menuItems, function(item) {
+            _.each(item.submenu, function(subItem) {
+              if (subItem.value === newValue) {
+                $button.html(subItem.text);
+              }
+            });
+          });
+        });
+      }
+
+      let typeaheadValues = _.reduce(
+        $scope.menuItems,
+        function(memo, value, index) {
+          if (!value.submenu) {
+            value.click = 'menuItemSelected(' + index + ')';
+            memo.push(value.text);
+          } else {
+            _.each(value.submenu, function(item, subIndex) {
+              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
+              memo.push(value.text + ' ' + item.text);
+            });
+          }
+          return memo;
+        },
+        []
+      );
+
+      $scope.menuItemSelected = function(index, subIndex) {
+        let menuItem = $scope.menuItems[index];
+        let payload: any = { $item: menuItem };
+        if (menuItem.submenu && subIndex !== void 0) {
+          payload.$subItem = menuItem.submenu[subIndex];
+        }
+        $scope.dropdownTypeaheadOnSelect(payload);
+      };
+
+      $input.attr('data-provide', 'typeahead');
+      $input.typeahead({
+        source: typeaheadValues,
+        minLength: 1,
+        items: 10,
+        updater: function(value) {
+          let result: any = {};
+          _.each($scope.menuItems, function(menuItem) {
+            _.each(menuItem.submenu, function(submenuItem) {
+              if (value === menuItem.text + ' ' + submenuItem.text) {
+                result.$subItem = submenuItem;
+                result.$item = menuItem;
+              }
+            });
+          });
+
+          if (result.$item) {
+            $scope.$apply(function() {
+              $scope.dropdownTypeaheadOnSelect(result);
+            });
+          }
+
+          $input.trigger('blur');
+          return '';
+        },
+      });
+
+      $button.click(function() {
+        $button.hide();
+        $input.show();
+        $input.focus();
+      });
+
+      $input.keyup(function() {
+        elem.toggleClass('open', $input.val() === '');
+      });
+
+      $input.blur(function() {
+        $input.hide();
+        $input.val('');
+        $button.show();
+        $button.focus();
+        // clicking the function dropdown menu won't
+        // work if you remove class at once
+        setTimeout(function() {
+          elem.removeClass('open');
+        }, 200);
+      });
+
+      $compile(elem.contents())($scope);
+    },
+  };
+}
+
+/** @ngInject */
+export function dropdownTypeahead2($compile) {
+  let inputTemplate =
+    '<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
+
+  let buttonTemplate =
+    '<a class="gf-form-input dropdown-toggle"' +
+    ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
+    ' data-placement="top"><i class="fa fa-plus"></i></a>';
+
+  return {
+    scope: {
+      menuItems: '=dropdownTypeahead2',
+      dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
+      model: '=ngModel',
+    },
+    link: function($scope, elem, attrs) {
+      let $input = $(inputTemplate);
+      let $button = $(buttonTemplate);
+      $input.appendTo(elem);
+      $button.appendTo(elem);
+
+      if (attrs.linkText) {
+        $button.html(attrs.linkText);
+      }
+
+      if (attrs.ngModel) {
+        $scope.$watch('model', function(newValue) {
+          _.each($scope.menuItems, function(item) {
+            _.each(item.submenu, function(subItem) {
+              if (subItem.value === newValue) {
+                $button.html(subItem.text);
+              }
+            });
+          });
+        });
+      }
+
+      let typeaheadValues = _.reduce(
+        $scope.menuItems,
+        function(memo, value, index) {
+          if (!value.submenu) {
+            value.click = 'menuItemSelected(' + index + ')';
+            memo.push(value.text);
+          } else {
+            _.each(value.submenu, function(item, subIndex) {
+              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
+              memo.push(value.text + ' ' + item.text);
+            });
+          }
+          return memo;
+        },
+        []
+      );
+
+      $scope.menuItemSelected = function(index, subIndex) {
+        let menuItem = $scope.menuItems[index];
+        let payload: any = { $item: menuItem };
+        if (menuItem.submenu && subIndex !== void 0) {
+          payload.$subItem = menuItem.submenu[subIndex];
+        }
+        $scope.dropdownTypeaheadOnSelect(payload);
+      };
+
+      $input.attr('data-provide', 'typeahead');
+      $input.typeahead({
+        source: typeaheadValues,
+        minLength: 1,
+        items: 10,
+        updater: function(value) {
+          let result: any = {};
+          _.each($scope.menuItems, function(menuItem) {
+            _.each(menuItem.submenu, function(submenuItem) {
+              if (value === menuItem.text + ' ' + submenuItem.text) {
+                result.$subItem = submenuItem;
+                result.$item = menuItem;
+              }
+            });
+          });
+
+          if (result.$item) {
+            $scope.$apply(function() {
+              $scope.dropdownTypeaheadOnSelect(result);
+            });
+          }
+
+          $input.trigger('blur');
+          return '';
+        },
+      });
+
+      $button.click(function() {
+        $button.hide();
+        $input.show();
+        $input.focus();
+      });
+
+      $input.keyup(function() {
+        elem.toggleClass('open', $input.val() === '');
+      });
+
+      $input.blur(function() {
+        $input.hide();
+        $input.val('');
+        $button.show();
+        $button.focus();
+        // clicking the function dropdown menu won't
+        // work if you remove class at once
+        setTimeout(function() {
+          elem.removeClass('open');
+        }, 200);
+      });
+
+      $compile(elem.contents())($scope);
+    },
+  };
+}
+
+coreModule.directive('dropdownTypeahead', dropdownTypeahead);
+coreModule.directive('dropdownTypeahead2', dropdownTypeahead2);

+ 1 - 1
public/app/core/directives/value_select_dropdown.ts

@@ -142,7 +142,7 @@ export class ValueSelectDropdownCtrl {
     commitChange = commitChange || false;
     commitChange = commitChange || false;
     excludeOthers = excludeOthers || false;
     excludeOthers = excludeOthers || false;
 
 
-    let setAllExceptCurrentTo = function(newValue) {
+    let setAllExceptCurrentTo = newValue => {
       _.each(this.options, other => {
       _.each(this.options, other => {
         if (option !== other) {
         if (option !== other) {
           other.selected = newValue;
           other.selected = newValue;

+ 1 - 1
public/app/plugins/datasource/elasticsearch/partials/config.html

@@ -35,7 +35,7 @@
     </div>
     </div>
 	<div class="gf-form-inline">
 	<div class="gf-form-inline">
 		<div class="gf-form">
 		<div class="gf-form">
-			<span class="gf-form-label width-9">Min interval</span>
+			<span class="gf-form-label width-9">Min time interval</span>
 			<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="10s"></input>
 			<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="10s"></input>
 			<info-popover mode="right-absolute">
 			<info-popover mode="right-absolute">
 				A lower limit for the auto group by time interval. Recommended to be set to write frequency,
 				A lower limit for the auto group by time interval. Recommended to be set to write frequency,

+ 2 - 3
public/app/plugins/datasource/influxdb/query_help.md

@@ -10,7 +10,7 @@
 - When stacking is enabled it is important that points align
 - When stacking is enabled it is important that points align
 - If there are missing points for one series it can cause gaps or missing bars
 - If there are missing points for one series it can cause gaps or missing bars
 - You must use fill(0), and select a group by time low limit
 - You must use fill(0), and select a group by time low limit
-- Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds
+- Use the group by time option below your queries and specify for example 10s if your metrics are written every 10 seconds
 - This will insert zeros for series that are missing measurements and will make stacking work properly
 - This will insert zeros for series that are missing measurements and will make stacking work properly
 
 
 #### Group by time
 #### Group by time
@@ -18,8 +18,7 @@
 - Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph
 - Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph
 - If you use fill(0) or fill(null) set a low limit for the auto group by time interval
 - If you use fill(0) or fill(null) set a low limit for the auto group by time interval
 - The low limit can only be set in the group by time option below your queries
 - The low limit can only be set in the group by time option below your queries
-- You set a low limit by adding a greater sign before the interval
-- Example: &gt;60s if you write metrics to InfluxDB every 60 seconds
+- Example: 60s if you write metrics to InfluxDB every 60 seconds
 
 
 #### Documentation links:
 #### Documentation links:
 
 

+ 1 - 1
public/app/plugins/datasource/postgres/module.ts

@@ -8,7 +8,7 @@ class PostgresConfigCtrl {
 
 
   /** @ngInject **/
   /** @ngInject **/
   constructor($scope) {
   constructor($scope) {
-    this.current.jsonData.sslmode = this.current.jsonData.sslmode || 'require';
+    this.current.jsonData.sslmode = this.current.jsonData.sslmode || 'verify-full';
   }
   }
 }
 }
 
 

+ 4 - 4
public/app/plugins/datasource/postgres/partials/annotations.editor.html

@@ -28,12 +28,12 @@ An annotation is an event that is overlaid on top of graphs. The query can have
 Macros:
 Macros:
 - $__time(column) -&gt; column as "time"
 - $__time(column) -&gt; column as "time"
 - $__timeEpoch -&gt; extract(epoch from column) as "time"
 - $__timeEpoch -&gt; extract(epoch from column) as "time"
-- $__timeFilter(column) -&gt;  column &ge; to_timestamp(1492750877) AND column &le; to_timestamp(1492750877)
-- $__unixEpochFilter(column) -&gt;  column &gt; 1492750877 AND column &lt; 1492750877
+- $__timeFilter(column) -&gt; column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z'
+- $__unixEpochFilter(column) -&gt;  column &gt;= 1492750877 AND column &lt;= 1492750877
 
 
 Or build your own conditionals using these macros which just return the values:
 Or build your own conditionals using these macros which just return the values:
-- $__timeFrom() -&gt;  to_timestamp(1492750877)
-- $__timeTo() -&gt;  to_timestamp(1492750877)
+- $__timeFrom() -&gt;  '2017-04-21T05:01:17Z'
+- $__timeTo() -&gt;  '2017-04-21T05:01:17Z'
 - $__unixEpochFrom() -&gt;  1492750877
 - $__unixEpochFrom() -&gt;  1492750877
 - $__unixEpochTo() -&gt;  1492750877
 - $__unixEpochTo() -&gt;  1492750877
 		</pre>
 		</pre>

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

@@ -48,8 +48,8 @@ Table:
 Macros:
 Macros:
 - $__time(column) -&gt; column as "time"
 - $__time(column) -&gt; column as "time"
 - $__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
-- $__unixEpochFilter(column) -&gt;  column &gt; 1492750877 AND column &lt; 1492750877
+- $__timeFilter(column) -&gt; column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z'
+- $__unixEpochFilter(column) -&gt;  column &gt;= 1492750877 AND column &lt;= 1492750877
 - $__timeGroup(column,'5m') -&gt; (extract(epoch from column)/300)::bigint*300 AS time
 - $__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:
@@ -61,8 +61,8 @@ GROUP BY time
 ORDER BY time
 ORDER BY time
 
 
 Or build your own conditionals using these macros which just return the values:
 Or build your own conditionals using these macros which just return the values:
-- $__timeFrom() -&gt;  to_timestamp(1492750877)
-- $__timeTo() -&gt;  to_timestamp(1492750877)
+- $__timeFrom() -&gt;  '2017-04-21T05:01:17Z'
+- $__timeTo() -&gt;  '2017-04-21T05:01:17Z'
 - $__unixEpochFrom() -&gt;  1492750877
 - $__unixEpochFrom() -&gt;  1492750877
 - $__unixEpochTo() -&gt;  1492750877
 - $__unixEpochTo() -&gt;  1492750877
 		</pre>
 		</pre>

+ 9 - 0
public/sass/components/_sidemenu.scss

@@ -149,6 +149,15 @@
   color: #ebedf2;
   color: #ebedf2;
 }
 }
 
 
+.sidemenu-subtitle {
+  padding: 0.5rem 1rem 0.5rem;
+  font-size: $font-size-sm;
+  color: $text-color-weak;
+  border-bottom: 1px solid $dropdownDividerBottom;
+  margin-bottom: 0.25rem;
+  white-space: nowrap;
+}
+
 li.sidemenu-org-switcher {
 li.sidemenu-org-switcher {
   border-bottom: 1px solid $dropdownDividerBottom;
   border-bottom: 1px solid $dropdownDividerBottom;
 }
 }