Commit 979f2d79 by Marcus Efraimsson

Merge branch 'master' into docs-5.1

parents abed9c05 3a5d1f45
......@@ -16,8 +16,10 @@
* **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)
* **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
* **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**: 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)
* **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)
* **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
* Migrated JavaScript files to TypeScript
......
......@@ -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
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
![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
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
{{< 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) {
})
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{
Text: c.SignedInUser.NameOrFallback(),
SubTitle: c.SignedInUser.Login,
SubTitle: login,
Id: "profile",
Img: data.User.GravatarUrl,
Url: setting.AppSubUrl + "/profile",
......@@ -284,6 +289,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Help",
SubTitle: fmt.Sprintf(`Grafana v%s (%s)`, setting.BuildVersion, setting.BuildCommit),
Id: "help",
Url: "#",
Icon: "gicon gicon-question",
......
......@@ -33,7 +33,7 @@ func ValidateOrgPlaylist(c *m.ReqContext) {
return
}
if len(items) == 0 {
if len(items) == 0 && c.Context.Req.Method != "DELETE" {
c.JsonApiErr(404, "Playlist is empty", itemsErr)
return
}
......
......@@ -333,6 +333,7 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error {
sess.Join("INNER", "org", "org_user.org_id=org.id")
sess.Where("org_user.user_id=?", query.UserId)
sess.Cols("org.name", "org_user.role", "org_user.org_id")
sess.OrderBy("org.name")
err := sess.Find(&query.Result)
return err
}
......
......@@ -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
case "__timeFilter":
// don't use to_timestamp in this macro for redshift compatibility #9566
if len(args) == 0 {
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":
return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetFromAsSecondsEpoch()), nil
return fmt.Sprintf("'%s'", m.TimeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
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":
if len(args) < 2 {
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
......
......@@ -12,7 +12,7 @@ import (
func TestMacroEngine(t *testing.T) {
Convey("MacroEngine", t, func() {
engine := &PostgresMacroEngine{}
engine := NewPostgresMacroEngine()
query := &tsdb.Query{}
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)")
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() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
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() {
......@@ -68,7 +68,7 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
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() {
......@@ -102,21 +102,21 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
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() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
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() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
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() {
......@@ -150,21 +150,21 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
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() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
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() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
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() {
......
......@@ -37,6 +37,10 @@ func (tr *TimeRange) GetFromAsSecondsEpoch() int64 {
return tr.GetFromAsMsEpoch() / 1000
}
func (tr *TimeRange) GetFromAsTimeUTC() time.Time {
return tr.MustGetFrom().UTC()
}
func (tr *TimeRange) GetToAsMsEpoch() int64 {
return tr.MustGetTo().UnixNano() / int64(time.Millisecond)
}
......@@ -45,6 +49,10 @@ func (tr *TimeRange) GetToAsSecondsEpoch() int64 {
return tr.GetToAsMsEpoch() / 1000
}
func (tr *TimeRange) GetToAsTimeUTC() time.Time {
return tr.MustGetTo().UTC()
}
func (tr *TimeRange) MustGetFrom() time.Time {
if res, err := tr.ParseFrom(); err != nil {
return time.Unix(0, 0)
......
......@@ -54,6 +54,9 @@
</span>
</a>
<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">
<a ng-click="ctrl.switchOrg()">
<div>
......
define([
'lodash',
'jquery',
'../core_module',
],
function (_, $, coreModule) {
'use strict';
coreModule.default.directive('dropdownTypeahead', function($compile) {
var inputTemplate = '<input type="text"'+
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>';
var buttonTemplate = '<a class="gf-form-label tight-form-func dropdown-toggle"' +
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'
menuItems: '=dropdownTypeahead',
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
model: '=ngModel',
},
link: function($scope, elem, attrs) {
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
let $input = $(inputTemplate);
let $button = $(buttonTemplate);
$input.appendTo(elem);
$button.appendTo(elem);
......@@ -44,7 +42,9 @@ function (_, $, coreModule) {
});
}
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
let typeaheadValues = _.reduce(
$scope.menuItems,
function(memo, value, index) {
if (!value.submenu) {
value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text);
......@@ -55,11 +55,13 @@ function (_, $, coreModule) {
});
}
return memo;
}, []);
},
[]
);
$scope.menuItemSelected = function(index, subIndex) {
var menuItem = $scope.menuItems[index];
var payload = {$item: menuItem};
let menuItem = $scope.menuItems[index];
let payload: any = { $item: menuItem };
if (menuItem.submenu && subIndex !== void 0) {
payload.$subItem = menuItem.submenu[subIndex];
}
......@@ -71,11 +73,11 @@ function (_, $, coreModule) {
source: typeaheadValues,
minLength: 1,
items: 10,
updater: function (value) {
var result = {};
updater: function(value) {
let result: any = {};
_.each($scope.menuItems, function(menuItem) {
_.each(menuItem.submenu, function(submenuItem) {
if (value === (menuItem.text + ' ' + submenuItem.text)) {
if (value === menuItem.text + ' ' + submenuItem.text) {
result.$subItem = submenuItem;
result.$item = menuItem;
}
......@@ -90,7 +92,7 @@ function (_, $, coreModule) {
$input.trigger('blur');
return '';
}
},
});
$button.click(function() {
......@@ -116,29 +118,29 @@ function (_, $, coreModule) {
});
$compile(elem.contents())($scope);
}
},
};
});
}
coreModule.default.directive('dropdownTypeahead2', function($compile) {
/** @ngInject */
export function dropdownTypeahead2($compile) {
let inputTemplate =
'<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
var inputTemplate = '<input type="text"'+
' class="gf-form-input"' +
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="gf-form-input dropdown-toggle"' +
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'
menuItems: '=dropdownTypeahead2',
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
model: '=ngModel',
},
link: function($scope, elem, attrs) {
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
let $input = $(inputTemplate);
let $button = $(buttonTemplate);
$input.appendTo(elem);
$button.appendTo(elem);
......@@ -158,7 +160,9 @@ function (_, $, coreModule) {
});
}
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
let typeaheadValues = _.reduce(
$scope.menuItems,
function(memo, value, index) {
if (!value.submenu) {
value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text);
......@@ -169,11 +173,13 @@ function (_, $, coreModule) {
});
}
return memo;
}, []);
},
[]
);
$scope.menuItemSelected = function(index, subIndex) {
var menuItem = $scope.menuItems[index];
var payload = {$item: menuItem};
let menuItem = $scope.menuItems[index];
let payload: any = { $item: menuItem };
if (menuItem.submenu && subIndex !== void 0) {
payload.$subItem = menuItem.submenu[subIndex];
}
......@@ -185,11 +191,11 @@ function (_, $, coreModule) {
source: typeaheadValues,
minLength: 1,
items: 10,
updater: function (value) {
var result = {};
updater: function(value) {
let result: any = {};
_.each($scope.menuItems, function(menuItem) {
_.each(menuItem.submenu, function(submenuItem) {
if (value === (menuItem.text + ' ' + submenuItem.text)) {
if (value === menuItem.text + ' ' + submenuItem.text) {
result.$subItem = submenuItem;
result.$item = menuItem;
}
......@@ -204,7 +210,7 @@ function (_, $, coreModule) {
$input.trigger('blur');
return '';
}
},
});
$button.click(function() {
......@@ -230,7 +236,9 @@ function (_, $, coreModule) {
});
$compile(elem.contents())($scope);
}
},
};
});
});
}
coreModule.directive('dropdownTypeahead', dropdownTypeahead);
coreModule.directive('dropdownTypeahead2', dropdownTypeahead2);
......@@ -142,7 +142,7 @@ export class ValueSelectDropdownCtrl {
commitChange = commitChange || false;
excludeOthers = excludeOthers || false;
let setAllExceptCurrentTo = function(newValue) {
let setAllExceptCurrentTo = newValue => {
_.each(this.options, other => {
if (option !== other) {
other.selected = newValue;
......
......@@ -35,7 +35,7 @@
</div>
<div class="gf-form-inline">
<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>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
......
......@@ -10,7 +10,7 @@
- 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
- 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
#### 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
- 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
- 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:
......
......@@ -8,7 +8,7 @@ class PostgresConfigCtrl {
/** @ngInject **/
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
Macros:
- $__time(column) -&gt; 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:
- $__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
- $__unixEpochTo() -&gt; 1492750877
</pre>
......
......@@ -48,8 +48,8 @@ Table:
Macros:
- $__time(column) -&gt; 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
Example of group by and order by with $__timeGroup:
......@@ -61,8 +61,8 @@ GROUP BY time
ORDER BY time
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
- $__unixEpochTo() -&gt; 1492750877
</pre>
......
......@@ -149,6 +149,15 @@
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 {
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