Commit 979f2d79 by Marcus Efraimsson

Merge branch 'master' into docs-5.1

parents abed9c05 3a5d1f45
...@@ -16,8 +16,10 @@ ...@@ -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 @@ ...@@ -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
......
...@@ -61,6 +61,22 @@ a time pattern for the index name or a wildcard. ...@@ -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)
......
...@@ -43,6 +43,22 @@ All requests will be made from the browser to Grafana backend/server which in tu ...@@ -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" >}}
......
...@@ -118,9 +118,14 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { ...@@ -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) { ...@@ -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",
......
...@@ -33,7 +33,7 @@ func ValidateOrgPlaylist(c *m.ReqContext) { ...@@ -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
} }
......
...@@ -333,6 +333,7 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error { ...@@ -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
} }
......
...@@ -79,15 +79,15 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, ...@@ -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)
......
...@@ -12,7 +12,7 @@ import ( ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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() {
......
...@@ -37,6 +37,10 @@ func (tr *TimeRange) GetFromAsSecondsEpoch() int64 { ...@@ -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 { ...@@ -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)
......
...@@ -54,6 +54,9 @@ ...@@ -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 @@ ...@@ -75,4 +78,4 @@
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
\ No newline at end of file
define([ import _ from 'lodash';
'lodash', import $ from 'jquery';
'jquery', import coreModule from '../core_module';
'../core_module',
], /** @ngInject */
function (_, $, coreModule) { export function dropdownTypeahead($compile) {
'use strict'; let inputTemplate =
'<input type="text"' +
coreModule.default.directive('dropdownTypeahead', function($compile) { ' class="gf-form-input input-medium tight-form-input"' +
' spellcheck="false" style="display:none"></input>';
var inputTemplate = '<input type="text"'+
' class="gf-form-input input-medium tight-form-input"' + let buttonTemplate =
' spellcheck="false" style="display:none"></input>'; '<a class="gf-form-label tight-form-func dropdown-toggle"' +
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
var buttonTemplate = '<a class="gf-form-label tight-form-func dropdown-toggle"' + ' data-placement="top"><i class="fa fa-plus"></i></a>';
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
' data-placement="top"><i class="fa fa-plus"></i></a>'; return {
scope: {
return { menuItems: '=dropdownTypeahead',
scope: { dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
menuItems: "=dropdownTypeahead", model: '=ngModel',
dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect", },
model: '=ngModel' link: function($scope, elem, attrs) {
}, let $input = $(inputTemplate);
link: function($scope, elem, attrs) { let $button = $(buttonTemplate);
var $input = $(inputTemplate); $input.appendTo(elem);
var $button = $(buttonTemplate); $button.appendTo(elem);
$input.appendTo(elem);
$button.appendTo(elem); if (attrs.linkText) {
$button.html(attrs.linkText);
if (attrs.linkText) { }
$button.html(attrs.linkText);
}
if (attrs.ngModel) { if (attrs.ngModel) {
$scope.$watch('model', function(newValue) { $scope.$watch('model', function(newValue) {
_.each($scope.menuItems, function(item) { _.each($scope.menuItems, function(item) {
_.each(item.submenu, function(subItem) { _.each(item.submenu, function(subItem) {
if (subItem.value === newValue) { if (subItem.value === newValue) {
$button.html(subItem.text); $button.html(subItem.text);
} }
});
}); });
}); });
} });
}
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { let typeaheadValues = _.reduce(
$scope.menuItems,
function(memo, value, index) {
if (!value.submenu) { if (!value.submenu) {
value.click = 'menuItemSelected(' + index + ')'; value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text); memo.push(value.text);
...@@ -55,110 +55,114 @@ function (_, $, coreModule) { ...@@ -55,110 +55,114 @@ function (_, $, coreModule) {
}); });
} }
return memo; return memo;
}, []); },
[]
$scope.menuItemSelected = function(index, subIndex) { );
var menuItem = $scope.menuItems[index];
var payload = {$item: menuItem}; $scope.menuItemSelected = function(index, subIndex) {
if (menuItem.submenu && subIndex !== void 0) { let menuItem = $scope.menuItems[index];
payload.$subItem = menuItem.submenu[subIndex]; let payload: any = { $item: menuItem };
} if (menuItem.submenu && subIndex !== void 0) {
$scope.dropdownTypeaheadOnSelect(payload); payload.$subItem = menuItem.submenu[subIndex];
}; }
$scope.dropdownTypeaheadOnSelect(payload);
$input.attr('data-provide', 'typeahead'); };
$input.typeahead({
source: typeaheadValues, $input.attr('data-provide', 'typeahead');
minLength: 1, $input.typeahead({
items: 10, source: typeaheadValues,
updater: function (value) { minLength: 1,
var result = {}; items: 10,
_.each($scope.menuItems, function(menuItem) { updater: function(value) {
_.each(menuItem.submenu, function(submenuItem) { let result: any = {};
if (value === (menuItem.text + ' ' + submenuItem.text)) { _.each($scope.menuItems, function(menuItem) {
result.$subItem = submenuItem; _.each(menuItem.submenu, function(submenuItem) {
result.$item = menuItem; if (value === menuItem.text + ' ' + submenuItem.text) {
} result.$subItem = submenuItem;
}); result.$item = menuItem;
}
}); });
});
if (result.$item) { if (result.$item) {
$scope.$apply(function() { $scope.$apply(function() {
$scope.dropdownTypeaheadOnSelect(result); $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); $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);
} }
};
});
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) { if (attrs.ngModel) {
$scope.$watch('model', function(newValue) { $scope.$watch('model', function(newValue) {
_.each($scope.menuItems, function(item) { _.each($scope.menuItems, function(item) {
_.each(item.submenu, function(subItem) { _.each(item.submenu, function(subItem) {
if (subItem.value === newValue) { if (subItem.value === newValue) {
$button.html(subItem.text); $button.html(subItem.text);
} }
});
}); });
}); });
} });
}
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { let typeaheadValues = _.reduce(
$scope.menuItems,
function(memo, value, index) {
if (!value.submenu) { if (!value.submenu) {
value.click = 'menuItemSelected(' + index + ')'; value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text); memo.push(value.text);
...@@ -169,68 +173,72 @@ function (_, $, coreModule) { ...@@ -169,68 +173,72 @@ function (_, $, coreModule) {
}); });
} }
return memo; return memo;
}, []); },
[]
$scope.menuItemSelected = function(index, subIndex) { );
var menuItem = $scope.menuItems[index];
var payload = {$item: menuItem}; $scope.menuItemSelected = function(index, subIndex) {
if (menuItem.submenu && subIndex !== void 0) { let menuItem = $scope.menuItems[index];
payload.$subItem = menuItem.submenu[subIndex]; let payload: any = { $item: menuItem };
} if (menuItem.submenu && subIndex !== void 0) {
$scope.dropdownTypeaheadOnSelect(payload); payload.$subItem = menuItem.submenu[subIndex];
}; }
$scope.dropdownTypeaheadOnSelect(payload);
$input.attr('data-provide', 'typeahead'); };
$input.typeahead({
source: typeaheadValues, $input.attr('data-provide', 'typeahead');
minLength: 1, $input.typeahead({
items: 10, source: typeaheadValues,
updater: function (value) { minLength: 1,
var result = {}; items: 10,
_.each($scope.menuItems, function(menuItem) { updater: function(value) {
_.each(menuItem.submenu, function(submenuItem) { let result: any = {};
if (value === (menuItem.text + ' ' + submenuItem.text)) { _.each($scope.menuItems, function(menuItem) {
result.$subItem = submenuItem; _.each(menuItem.submenu, function(submenuItem) {
result.$item = menuItem; if (value === menuItem.text + ' ' + submenuItem.text) {
} result.$subItem = submenuItem;
}); result.$item = menuItem;
}
}); });
});
if (result.$item) { if (result.$item) {
$scope.$apply(function() { $scope.$apply(function() {
$scope.dropdownTypeaheadOnSelect(result); $scope.dropdownTypeaheadOnSelect(result);
}); });
}
$input.trigger('blur');
return '';
} }
});
$button.click(function() { $input.trigger('blur');
$button.hide(); return '';
$input.show(); },
$input.focus(); });
});
$button.click(function() {
$input.keyup(function() { $button.hide();
elem.toggleClass('open', $input.val() === ''); $input.show();
}); $input.focus();
});
$input.blur(function() {
$input.hide(); $input.keyup(function() {
$input.val(''); elem.toggleClass('open', $input.val() === '');
$button.show(); });
$button.focus();
// clicking the function dropdown menu won't $input.blur(function() {
// work if you remove class at once $input.hide();
setTimeout(function() { $input.val('');
elem.removeClass('open'); $button.show();
}, 200); $button.focus();
}); // clicking the function dropdown menu won't
// work if you remove class at once
$compile(elem.contents())($scope); setTimeout(function() {
} elem.removeClass('open');
}; }, 200);
}); });
});
$compile(elem.contents())($scope);
},
};
}
coreModule.directive('dropdownTypeahead', dropdownTypeahead);
coreModule.directive('dropdownTypeahead2', dropdownTypeahead2);
...@@ -142,7 +142,7 @@ export class ValueSelectDropdownCtrl { ...@@ -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;
......
...@@ -35,7 +35,7 @@ ...@@ -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,
......
...@@ -10,7 +10,7 @@ ...@@ -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 @@ ...@@ -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: 60s if you write metrics to InfluxDB every 60 seconds
- Example: &gt;60s if you write metrics to InfluxDB every 60 seconds
#### Documentation links: #### Documentation links:
......
...@@ -8,7 +8,7 @@ class PostgresConfigCtrl { ...@@ -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';
} }
} }
......
...@@ -28,12 +28,12 @@ An annotation is an event that is overlaid on top of graphs. The query can have ...@@ -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) - $__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 - $__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) - $__timeFrom() -&gt; '2017-04-21T05:01:17Z'
- $__timeTo() -&gt; to_timestamp(1492750877) - $__timeTo() -&gt; '2017-04-21T05:01:17Z'
- $__unixEpochFrom() -&gt; 1492750877 - $__unixEpochFrom() -&gt; 1492750877
- $__unixEpochTo() -&gt; 1492750877 - $__unixEpochTo() -&gt; 1492750877
</pre> </pre>
......
...@@ -48,8 +48,8 @@ Table: ...@@ -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 - $__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 - $__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 ...@@ -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) - $__timeFrom() -&gt; '2017-04-21T05:01:17Z'
- $__timeTo() -&gt; to_timestamp(1492750877) - $__timeTo() -&gt; '2017-04-21T05:01:17Z'
- $__unixEpochFrom() -&gt; 1492750877 - $__unixEpochFrom() -&gt; 1492750877
- $__unixEpochTo() -&gt; 1492750877 - $__unixEpochTo() -&gt; 1492750877
</pre> </pre>
......
...@@ -149,6 +149,15 @@ ...@@ -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;
} }
......
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