Merge branch 'master' into react-panels-step1

This commit is contained in:
Torkel Ödegaard
2018-10-14 10:44:38 +02:00
18 changed files with 2123 additions and 30 deletions

1
.gitignore vendored
View File

@@ -69,7 +69,6 @@ debug.test
/vendor/**/*.yml /vendor/**/*.yml
/vendor/**/*_test.go /vendor/**/*_test.go
/vendor/**/.editorconfig /vendor/**/.editorconfig
/vendor/**/appengine*
*.orig *.orig
/devenv/bulk-dashboards/*.json /devenv/bulk-dashboards/*.json

View File

@@ -8,6 +8,7 @@
* **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu)
* **Elasticsearch**: Fix no limit size in terms aggregation for alerting queries [#13172](https://github.com/grafana/grafana/issues/13172), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino) * **Elasticsearch**: Fix no limit size in terms aggregation for alerting queries [#13172](https://github.com/grafana/grafana/issues/13172), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino)
* **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg)
### Breaking changes ### Breaking changes
@@ -16,6 +17,7 @@
# 5.3.1 (unreleased) # 5.3.1 (unreleased)
* **Render**: Fix PhantomJS render of graph panel when legend displayed as table to the right [#13616](https://github.com/grafana/grafana/issues/13616) * **Render**: Fix PhantomJS render of graph panel when legend displayed as table to the right [#13616](https://github.com/grafana/grafana/issues/13616)
* **Stackdriver**: Filter option disappears after removing initial filter [#13607](https://github.com/grafana/grafana/issues/13607)
# 5.3.0 (2018-10-10) # 5.3.0 (2018-10-10)
@@ -68,7 +70,7 @@
* **Profile**: List teams that the user is member of in current/active organization [#12476](https://github.com/grafana/grafana/issues/12476) * **Profile**: List teams that the user is member of in current/active organization [#12476](https://github.com/grafana/grafana/issues/12476)
* **Configuration**: Allow auto-assigning users to specific organization (other than Main. Org) [#1823](https://github.com/grafana/grafana/issues/1823) [#12801](https://github.com/grafana/grafana/issues/12801), thx [@gzzo](https://github.com/gzzo) and [@ofosos](https://github.com/ofosos) * **Configuration**: Allow auto-assigning users to specific organization (other than Main. Org) [#1823](https://github.com/grafana/grafana/issues/1823) [#12801](https://github.com/grafana/grafana/issues/12801), thx [@gzzo](https://github.com/gzzo) and [@ofosos](https://github.com/ofosos)
* **Dataproxy**: Pass configured/auth headers to a Datasource [#10971](https://github.com/grafana/grafana/issues/10971), thx [@mrsiano](https://github.com/mrsiano) * **Dataproxy**: Pass configured/auth headers to a Datasource [#10971](https://github.com/grafana/grafana/issues/10971), thx [@mrsiano](https://github.com/mrsiano)
* ****: **: CloudWatch GetMetricData support [#11487](https://github.com/grafana/grafana/issues/11487), thx [@mtanda](https://github.com/mtanda) * **CloudWatch**: GetMetricData support [#11487](https://github.com/grafana/grafana/issues/11487), thx [@mtanda](https://github.com/mtanda)
* **Postgres**: TimescaleDB support, e.g. use `time_bucket` for grouping by time when option enabled [#12680](https://github.com/grafana/grafana/pull/12680), thx [svenklemm](https://github.com/svenklemm) * **Postgres**: TimescaleDB support, e.g. use `time_bucket` for grouping by time when option enabled [#12680](https://github.com/grafana/grafana/pull/12680), thx [svenklemm](https://github.com/svenklemm)
* **Cleanup**: Make temp file time to live configurable [#11607](https://github.com/grafana/grafana/issues/11607), thx [@xapon](https://github.com/xapon) * **Cleanup**: Make temp file time to live configurable [#11607](https://github.com/grafana/grafana/issues/11607), thx [@xapon](https://github.com/xapon)

View File

@@ -10,7 +10,7 @@ weight = 1
# Developer Guide # Developer Guide
You can extend Grafana by writing your own plugins and then share then with other users in [our plugin repository](https://grafana.com/plugins). You can extend Grafana by writing your own plugins and then share them with other users in [our plugin repository](https://grafana.com/plugins).
## Short version ## Short version
@@ -33,7 +33,7 @@ There are two blog posts about authoring a plugin that might also be of interest
## What languages? ## What languages?
Since everything turns into javascript it's up to you to choose which language you want. That said it's probably a good idea to choose es6 or typescript since Since everything turns into javascript it's up to you to choose which language you want. That said it's probably a good idea to choose es6 or typescript since
we use es6 classes in Grafana. So it's easier to get inspiration from the Grafana repo is you choose one of those languages. we use es6 classes in Grafana. So it's easier to get inspiration from the Grafana repo if you choose one of those languages.
## Buildscript ## Buildscript
@@ -60,7 +60,6 @@ and [apps]({{< relref "apps.md" >}}) plugins in the documentation.
The Grafana SDK is quite small so far and can be found here: The Grafana SDK is quite small so far and can be found here:
- [SDK file in Grafana](https://github.com/grafana/grafana/blob/master/public/app/plugins/sdk.ts) - [SDK file in Grafana](https://github.com/grafana/grafana/blob/master/public/app/plugins/sdk.ts)
- [SDK Readme](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md)
The SDK contains three different plugin classes: PanelCtrl, MetricsPanelCtrl and QueryCtrl. For plugins of the panel type, the module.js file should export one of these. There are some extra classes for [data sources]({{< relref "datasources.md" >}}). The SDK contains three different plugin classes: PanelCtrl, MetricsPanelCtrl and QueryCtrl. For plugins of the panel type, the module.js file should export one of these. There are some extra classes for [data sources]({{< relref "datasources.md" >}}).

View File

@@ -80,7 +80,7 @@
"style-loader": "^0.21.0", "style-loader": "^0.21.0",
"systemjs": "0.20.19", "systemjs": "0.20.19",
"systemjs-plugin-css": "^0.1.36", "systemjs-plugin-css": "^0.1.36",
"ts-jest": "^23.1.4", "ts-jest": "^23.10.4",
"ts-loader": "^5.1.0", "ts-loader": "^5.1.0",
"tslib": "^1.9.3", "tslib": "^1.9.3",
"tslint": "^5.8.0", "tslint": "^5.8.0",

View File

@@ -28,6 +28,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@@ -52,6 +53,7 @@ type HTTPServer struct {
Bus bus.Bus `inject:""` Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""` RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
} }
func (hs *HTTPServer) Init() error { func (hs *HTTPServer) Init() error {

View File

@@ -316,19 +316,6 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
} }
if c.IsGrafanaAdmin { if c.IsGrafanaAdmin {
children := []*dtos.NavLink{
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
}
if setting.IsEnterprise {
children = append(children, &dtos.NavLink{Text: "Licensing", Id: "licensing", Url: setting.AppSubUrl + "/admin/licensing", Icon: "fa fa-fw fa-unlock-alt"})
}
children = append(children, &dtos.NavLink{Text: "Style Guide", Id: "styleguide", Url: setting.AppSubUrl + "/styleguide", Icon: "fa fa-fw fa-eyedropper"})
cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{ cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{
Text: "Server Admin", Text: "Server Admin",
HideFromTabs: true, HideFromTabs: true,
@@ -336,7 +323,13 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
Id: "admin", Id: "admin",
Icon: "gicon gicon-shield", Icon: "gicon gicon-shield",
Url: setting.AppSubUrl + "/admin/users", Url: setting.AppSubUrl + "/admin/users",
Children: children, Children: []*dtos.NavLink{
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
{Text: "Style Guide", Id: "styleguide", Url: setting.AppSubUrl + "/styleguide", Icon: "fa fa-fw fa-eyedropper"},
},
}) })
} }
@@ -357,6 +350,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
}, },
}) })
hs.HooksService.RunIndexDataHooks(&data)
return &data, nil return &data, nil
} }

View File

@@ -127,7 +127,13 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval
var err error var err error
imageFile, err = os.Open(evalContext.ImageOnDiskPath) imageFile, err = os.Open(evalContext.ImageOnDiskPath)
defer imageFile.Close() defer func() {
err := imageFile.Close()
if err != nil {
log.Error2("Could not close Telegram inline image.", "err", err)
}
}()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -0,0 +1,30 @@
package hooks
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/registry"
)
type IndexDataHook func(indexData *dtos.IndexViewData)
type HooksService struct {
indexDataHooks []IndexDataHook
}
func init() {
registry.RegisterService(&HooksService{})
}
func (srv *HooksService) Init() error {
return nil
}
func (srv *HooksService) AddIndexDataHook(hook IndexDataHook) {
srv.indexDataHooks = append(srv.indexDataHooks, hook)
}
func (srv *HooksService) RunIndexDataHooks(indexData *dtos.IndexViewData) {
for _, hook := range srv.indexDataHooks {
hook(indexData)
}
}

View File

@@ -16,7 +16,6 @@ func TestUserAuth(t *testing.T) {
Convey("Given 5 users", t, func() { Convey("Given 5 users", t, func() {
var err error var err error
var cmd *m.CreateUserCommand var cmd *m.CreateUserCommand
users := []m.User{}
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
cmd = &m.CreateUserCommand{ cmd = &m.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
@@ -25,7 +24,6 @@ func TestUserAuth(t *testing.T) {
} }
err = CreateUser(context.Background(), cmd) err = CreateUser(context.Background(), cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
users = append(users, cmd.Result)
} }
Reset(func() { Reset(func() {

View File

@@ -692,7 +692,7 @@ func TestMSSQL(t *testing.T) {
}, },
} }
resp, err := endpoint.Query(nil, nil, query) resp, err := endpoint.Query(context.Background(), nil, query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
queryResult := resp.Results["A"] queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil) So(queryResult.Error, ShouldBeNil)

View File

@@ -769,7 +769,7 @@ func TestMySQL(t *testing.T) {
}, },
} }
resp, err := endpoint.Query(nil, nil, query) resp, err := endpoint.Query(context.Background(), nil, query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
queryResult := resp.Results["A"] queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil) So(queryResult.Error, ShouldBeNil)

View File

@@ -701,7 +701,7 @@ func TestPostgres(t *testing.T) {
}, },
} }
resp, err := endpoint.Query(nil, nil, query) resp, err := endpoint.Query(context.Background(), nil, query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
queryResult := resp.Results["A"] queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil) So(queryResult.Error, ShouldBeNil)

View File

@@ -399,6 +399,77 @@ describe('duration', () => {
}); });
}); });
describe('clock', () => {
it('null', () => {
const str = kbn.toClock(null, 0);
expect(str).toBe('');
});
it('size less than 1 second', () => {
const str = kbn.toClock(999, 0);
expect(str).toBe('999ms');
});
describe('size less than 1 minute', () => {
it('default', () => {
const str = kbn.toClock(59999);
expect(str).toBe('59s:999ms');
});
it('decimals equals 0', () => {
const str = kbn.toClock(59999, 0);
expect(str).toBe('59s');
});
});
describe('size less than 1 hour', () => {
it('default', () => {
const str = kbn.toClock(3599999);
expect(str).toBe('59m:59s:999ms');
});
it('decimals equals 0', () => {
const str = kbn.toClock(3599999, 0);
expect(str).toBe('59m');
});
it('decimals equals 1', () => {
const str = kbn.toClock(3599999, 1);
expect(str).toBe('59m:59s');
});
});
describe('size greater than or equal 1 hour', () => {
it('default', () => {
const str = kbn.toClock(7199999);
expect(str).toBe('01h:59m:59s:999ms');
});
it('decimals equals 0', () => {
const str = kbn.toClock(7199999, 0);
expect(str).toBe('01h');
});
it('decimals equals 1', () => {
const str = kbn.toClock(7199999, 1);
expect(str).toBe('01h:59m');
});
it('decimals equals 2', () => {
const str = kbn.toClock(7199999, 2);
expect(str).toBe('01h:59m:59s');
});
});
describe('size greater than or equal 1 day', () => {
it('default', () => {
const str = kbn.toClock(89999999);
expect(str).toBe('24h:59m:59s:999ms');
});
it('decimals equals 0', () => {
const str = kbn.toClock(89999999, 0);
expect(str).toBe('24h');
});
it('decimals equals 1', () => {
const str = kbn.toClock(89999999, 1);
expect(str).toBe('24h:59m');
});
it('decimals equals 2', () => {
const str = kbn.toClock(89999999, 2);
expect(str).toBe('24h:59m:59s');
});
});
});
describe('volume', () => { describe('volume', () => {
it('1000m3', () => { it('1000m3', () => {
const str = kbn.valueFormats['m3'](1000, 1, null); const str = kbn.valueFormats['m3'](1000, 1, null);

View File

@@ -808,6 +808,51 @@ kbn.toDuration = (size, decimals, timeScale) => {
return strings.join(', '); return strings.join(', ');
}; };
kbn.toClock = (size, decimals) => {
if (size === null) {
return '';
}
// < 1 second
if (size < 1000) {
return moment.utc(size).format('SSS\\m\\s');
}
// < 1 minute
if (size < 60000) {
let format = 'ss\\s:SSS\\m\\s';
if (decimals === 0) {
format = 'ss\\s';
}
return moment.utc(size).format(format);
}
// < 1 hour
if (size < 3600000) {
let format = 'mm\\m:ss\\s:SSS\\m\\s';
if (decimals === 0) {
format = 'mm\\m';
} else if (decimals === 1) {
format = 'mm\\m:ss\\s';
}
return moment.utc(size).format(format);
}
let format = 'mm\\m:ss\\s:SSS\\m\\s';
const hours = `${('0' + Math.floor(moment.duration(size, 'milliseconds').asHours())).slice(-2)}h`;
if (decimals === 0) {
format = '';
} else if (decimals === 1) {
format = 'mm\\m';
} else if (decimals === 2) {
format = 'mm\\m:ss\\s';
}
return format ? `${hours}:${moment.utc(size).format(format)}` : hours;
};
kbn.valueFormats.dtdurationms = (size, decimals) => { kbn.valueFormats.dtdurationms = (size, decimals) => {
return kbn.toDuration(size, decimals, 'millisecond'); return kbn.toDuration(size, decimals, 'millisecond');
}; };
@@ -824,6 +869,14 @@ kbn.valueFormats.timeticks = (size, decimals, scaledDecimals) => {
return kbn.valueFormats.s(size / 100, decimals, scaledDecimals); return kbn.valueFormats.s(size / 100, decimals, scaledDecimals);
}; };
kbn.valueFormats.clockms = (size, decimals) => {
return kbn.toClock(size, decimals);
};
kbn.valueFormats.clocks = (size, decimals) => {
return kbn.toClock(size * 1000, decimals);
};
kbn.valueFormats.dateTimeAsIso = (epoch, isUtc) => { kbn.valueFormats.dateTimeAsIso = (epoch, isUtc) => {
const time = isUtc ? moment.utc(epoch) : moment(epoch); const time = isUtc ? moment.utc(epoch) : moment(epoch);
@@ -901,6 +954,8 @@ kbn.getUnitFormats = () => {
{ text: 'duration (s)', value: 'dtdurations' }, { text: 'duration (s)', value: 'dtdurations' },
{ text: 'duration (hh:mm:ss)', value: 'dthms' }, { text: 'duration (hh:mm:ss)', value: 'dthms' },
{ text: 'Timeticks (s/100)', value: 'timeticks' }, { text: 'Timeticks (s/100)', value: 'timeticks' },
{ text: 'clock (ms)', value: 'clockms' },
{ text: 'clock (s)', value: 'clocks' },
], ],
}, },
{ {

View File

@@ -12,7 +12,7 @@ export default class PrometheusMetricFindQuery {
} }
process() { process() {
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)\s*$/; const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
const metricNamesRegex = /^metrics\((.+)\)\s*$/; const metricNamesRegex = /^metrics\((.+)\)\s*$/;
const queryResultRegex = /^query_result\((.+)\)\s*$/; const queryResultRegex = /^query_result\((.+)\)\s*$/;

View File

@@ -87,7 +87,7 @@ export class FilterSegments {
} }
// remove condition if it is first segment // remove condition if it is first segment
if (index === 0 && this.filterSegments[0].type === 'condition') { if (index === 0 && this.filterSegments.length > 0 && this.filterSegments[0].type === 'condition') {
this.filterSegments.splice(0, 1); this.filterSegments.splice(0, 1);
} }
} }

View File

@@ -714,7 +714,9 @@ class GraphElement {
if (min && max && ticks) { if (min && max && ticks) {
const range = max - min; const range = max - min;
const secPerTick = range / ticks / 1000; const secPerTick = range / ticks / 1000;
const oneDay = 86400000; // Need have 10 milisecond margin on the day range
// As sometimes last 24 hour dashboard evaluates to more than 86400000
const oneDay = 86400010;
const oneYear = 31536000000; const oneYear = 31536000000;
if (secPerTick <= 45) { if (secPerTick <= 45) {

1937
yarn.lock

File diff suppressed because it is too large Load Diff