mirror of
https://github.com/grafana/grafana.git
synced 2025-12-21 03:54:29 +08:00
Compare commits
28 Commits
docs/add-t
...
v4.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a91a882b4 | ||
|
|
6ad6131aaf | ||
|
|
1f10928450 | ||
|
|
0cd0aa19d6 | ||
|
|
c2f2a43197 | ||
|
|
7fe1ac5fe7 | ||
|
|
aec448f7c6 | ||
|
|
2ce36c8670 | ||
|
|
fb35d839c1 | ||
|
|
c8f5d39d97 | ||
|
|
34bc19359d | ||
|
|
3dcae78126 | ||
|
|
054c7a154a | ||
|
|
6f3d61f4d2 | ||
|
|
305f8c10e9 | ||
|
|
fff4cfd11e | ||
|
|
d20434f828 | ||
|
|
1ef850fae0 | ||
|
|
2f7a59fb18 | ||
|
|
07be20eeb3 | ||
|
|
689b8d79df | ||
|
|
b70c538633 | ||
|
|
769bc5df21 | ||
|
|
4eb6d82254 | ||
|
|
7935739eb3 | ||
|
|
7403fa0fa7 | ||
|
|
5ebfd1e5ab | ||
|
|
a1a8c0fc07 |
@@ -7,7 +7,12 @@
|
|||||||
- UX changes to nav & side menu
|
- UX changes to nav & side menu
|
||||||
- New dashboard grid layout system
|
- New dashboard grid layout system
|
||||||
|
|
||||||
# 4.6.0 (unreleased)
|
# 4.6.0-beta2 (2017-10-17)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
* **ColorPicker**: Fix for color picker not showing [#9549](https://github.com/grafana/grafana/issues/9549)
|
||||||
|
|
||||||
|
# 4.6.0-beta1 (2017-10-13)
|
||||||
|
|
||||||
## New Features
|
## New Features
|
||||||
* **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin)
|
* **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin)
|
||||||
@@ -40,7 +45,7 @@
|
|||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
* **Go**: Grafana is now built using golang 1.9
|
* **Go**: Grafana is now built using golang 1.9
|
||||||
* **Webpack**: Changed from systemjs to webpack (see readme or building from source guide for new build instructions). Systemjs is still used to load plugins but now plugins can only import a limited set of dependencies. See [PLUGIN_DEV.md](https://github.com/grafana/grafana/blob/master/PLUGIN_DEV.md) for more details on how this can effect some plugins.
|
* **Webpack**: Changed from systemjs to webpack (see readme or building from source guide for new build instructions). Systemjs is still used to load plugins but now plugins can only import a limited set of dependencies. See [PLUGIN_DEV.md](https://github.com/grafana/grafana/blob/master/PLUGIN_DEV.md) for more details on how this can effect some plugins.
|
||||||
|
|
||||||
# 4.5.2 (2017-09-22)
|
# 4.5.2 (2017-09-22)
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Macro example | Description
|
|||||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > to_timestamp(1494410783) AND dateColumn < to_timestamp(1494497183)*
|
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > to_timestamp(1494410783) AND dateColumn < to_timestamp(1494497183)*
|
||||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
|
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
|
||||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
|
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
|
||||||
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from "dateColumn")/extract(epoch from '5m'::interval))::int*
|
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from "dateColumn")/extract(epoch from '5m'::interval))::int*extract(epoch from '5m'::interval)*
|
||||||
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
|
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
|
||||||
*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
|
*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
|
||||||
*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
|
*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
|
||||||
@@ -94,26 +94,26 @@ Example with `metric` column
|
|||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
min(time_date_time) as time,
|
$__timeGroup(time_date_time,'5m') as time,
|
||||||
min(value_double),
|
min(value_double),
|
||||||
'min' as metric
|
'min' as metric
|
||||||
FROM test_data
|
FROM test_data
|
||||||
WHERE $__timeFilter(time_date_time)
|
WHERE $__timeFilter(time_date_time)
|
||||||
GROUP BY metric1, (extract(epoch from time_date_time)/extract(epoch from $__interval::interval))::int
|
GROUP BY time
|
||||||
ORDER BY time asc
|
ORDER BY time
|
||||||
```
|
```
|
||||||
|
|
||||||
Example with multiple columns:
|
Example with multiple columns:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
min(time_date_time) as time,
|
$__timeGroup(time_date_time,'5m') as time,
|
||||||
min(value_double) as min_value,
|
min(value_double) as min_value,
|
||||||
max(value_double) as max_value
|
max(value_double) as max_value
|
||||||
FROM test_data
|
FROM test_data
|
||||||
WHERE $__timeFilter(time_date_time)
|
WHERE $__timeFilter(time_date_time)
|
||||||
GROUP BY metric1, (extract(epoch from time_date_time)/extract(epoch from $__interval::interval))::int
|
GROUP BY time
|
||||||
ORDER BY time asc
|
ORDER BY time
|
||||||
```
|
```
|
||||||
|
|
||||||
## Templating
|
## Templating
|
||||||
|
|||||||
@@ -120,6 +120,37 @@ Content-Type: application/json
|
|||||||
PUT /api/annotations/1141 HTTP/1.1
|
PUT /api/annotations/1141 HTTP/1.1
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"time":1507037197339,
|
||||||
|
"isRegion":true,
|
||||||
|
"timeEnd":1507180805056,
|
||||||
|
"text":"Annotation Description",
|
||||||
|
"tags":["tag3","tag4","tag5"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Delete Annotation By Id
|
||||||
|
|
||||||
|
`DELETE /api/annotation/:id`
|
||||||
|
|
||||||
|
Deletes the annotation that matches the specified id.
|
||||||
|
|
||||||
|
**Example Request**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
DELETE /api/annotation/1 HTTP/1.1
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Delete Annotation By RegionId
|
## Delete Annotation By RegionId
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"company": "Grafana Labs"
|
"company": "Grafana Labs"
|
||||||
},
|
},
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "4.6.0-beta1",
|
"version": "4.6.0-beta3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/grafana/grafana.git"
|
"url": "http://github.com/grafana/grafana.git"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
deb_ver=4.6.0-beta1
|
deb_ver=4.6.0-beta2
|
||||||
rpm_ver=4.6.0-beta1
|
rpm_ver=4.6.0-beta2
|
||||||
|
|
||||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb
|
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -41,9 +40,22 @@ func GetAnnotations(c *middleware.Context) Response {
|
|||||||
return Json(200, items)
|
return Json(200, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateAnnotationError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CreateAnnotationError) Error() string {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response {
|
func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response {
|
||||||
repo := annotations.GetRepository()
|
repo := annotations.GetRepository()
|
||||||
|
|
||||||
|
if cmd.Text == "" {
|
||||||
|
err := &CreateAnnotationError{"text field should not be empty"}
|
||||||
|
return ApiError(500, "Failed to save annotation", err)
|
||||||
|
}
|
||||||
|
|
||||||
item := annotations.Item{
|
item := annotations.Item{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
UserId: c.UserId,
|
UserId: c.UserId,
|
||||||
@@ -55,6 +67,10 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response
|
|||||||
Tags: cmd.Tags,
|
Tags: cmd.Tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.Epoch == 0 {
|
||||||
|
item.Epoch = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
if err := repo.Save(&item); err != nil {
|
if err := repo.Save(&item); err != nil {
|
||||||
return ApiError(500, "Failed to save annotation", err)
|
return ApiError(500, "Failed to save annotation", err)
|
||||||
}
|
}
|
||||||
@@ -82,21 +98,22 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response
|
|||||||
return ApiSuccess("Annotation added")
|
return ApiSuccess("Annotation added")
|
||||||
}
|
}
|
||||||
|
|
||||||
type GraphiteAnnotationError struct {
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *GraphiteAnnotationError) Error() string {
|
|
||||||
return e.message
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatGraphiteAnnotation(what string, data string) string {
|
func formatGraphiteAnnotation(what string, data string) string {
|
||||||
return fmt.Sprintf("%s\n%s", what, data)
|
text := what
|
||||||
|
if data != "" {
|
||||||
|
text = text + "\n" + data
|
||||||
|
}
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotationsCmd) Response {
|
func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotationsCmd) Response {
|
||||||
repo := annotations.GetRepository()
|
repo := annotations.GetRepository()
|
||||||
|
|
||||||
|
if cmd.What == "" {
|
||||||
|
err := &CreateAnnotationError{"what field should not be empty"}
|
||||||
|
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.When == 0 {
|
if cmd.When == 0 {
|
||||||
cmd.When = time.Now().Unix()
|
cmd.When = time.Now().Unix()
|
||||||
}
|
}
|
||||||
@@ -106,18 +123,22 @@ func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotati
|
|||||||
var tagsArray []string
|
var tagsArray []string
|
||||||
switch tags := cmd.Tags.(type) {
|
switch tags := cmd.Tags.(type) {
|
||||||
case string:
|
case string:
|
||||||
tagsArray = strings.Split(tags, " ")
|
if tags != "" {
|
||||||
|
tagsArray = strings.Split(tags, " ")
|
||||||
|
} else {
|
||||||
|
tagsArray = []string{}
|
||||||
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for _, t := range tags {
|
for _, t := range tags {
|
||||||
if tagStr, ok := t.(string); ok {
|
if tagStr, ok := t.(string); ok {
|
||||||
tagsArray = append(tagsArray, tagStr)
|
tagsArray = append(tagsArray, tagStr)
|
||||||
} else {
|
} else {
|
||||||
err := &GraphiteAnnotationError{"tag should be a string"}
|
err := &CreateAnnotationError{"tag should be a string"}
|
||||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err := &GraphiteAnnotationError{"unsupported tags format"}
|
err := &CreateAnnotationError{"unsupported tags format"}
|
||||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +154,7 @@ func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotati
|
|||||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiSuccess("Graphite Annotation added")
|
return ApiSuccess("Graphite annotation added")
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateAnnotation(c *middleware.Context, cmd dtos.UpdateAnnotationsCmd) Response {
|
func UpdateAnnotation(c *middleware.Context, cmd dtos.UpdateAnnotationsCmd) Response {
|
||||||
|
|||||||
@@ -188,9 +188,8 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
|
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
|
||||||
DisableCompression: true,
|
ServeHTTP(ctx.Resp, ctx.Req.Request)
|
||||||
}).ServeHTTP(ctx.Resp, ctx.Req.Request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
|
func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ func Gziper() macaron.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(requestPath, "/metrics") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Invoke(macaronGziper)
|
ctx.Invoke(macaronGziper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
DS_KAIROSDB = "kairosdb"
|
DS_KAIROSDB = "kairosdb"
|
||||||
DS_PROMETHEUS = "prometheus"
|
DS_PROMETHEUS = "prometheus"
|
||||||
DS_POSTGRES = "postgres"
|
DS_POSTGRES = "postgres"
|
||||||
|
DS_MYSQL = "mysql"
|
||||||
DS_ACCESS_DIRECT = "direct"
|
DS_ACCESS_DIRECT = "direct"
|
||||||
DS_ACCESS_PROXY = "proxy"
|
DS_ACCESS_PROXY = "proxy"
|
||||||
)
|
)
|
||||||
@@ -64,6 +65,7 @@ var knownDatasourcePlugins map[string]bool = map[string]bool{
|
|||||||
DS_PROMETHEUS: true,
|
DS_PROMETHEUS: true,
|
||||||
DS_OPENTSDB: true,
|
DS_OPENTSDB: true,
|
||||||
DS_POSTGRES: true,
|
DS_POSTGRES: true,
|
||||||
|
DS_MYSQL: true,
|
||||||
"opennms": true,
|
"opennms": true,
|
||||||
"druid": true,
|
"druid": true,
|
||||||
"dalmatinerdb": true,
|
"dalmatinerdb": true,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func getPluginLogoUrl(pluginType, path, baseUrl string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
|
func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
|
||||||
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1)
|
appSubPath := strings.Replace(strings.Replace(fp.PluginDir, app.PluginDir, "", 1), "\\", "/", 1)
|
||||||
fp.IncludedInAppId = app.Id
|
fp.IncludedInAppId = app.Id
|
||||||
fp.BaseUrl = app.BaseUrl
|
fp.BaseUrl = app.BaseUrl
|
||||||
|
|
||||||
|
|||||||
34
pkg/plugins/frontend_plugin_test.go
Normal file
34
pkg/plugins/frontend_plugin_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFrontendPlugin(t *testing.T) {
|
||||||
|
|
||||||
|
Convey("When setting paths based on App on Windows", t, func() {
|
||||||
|
setting.StaticRootPath = "c:\\grafana\\public"
|
||||||
|
|
||||||
|
fp := &FrontendPluginBase{
|
||||||
|
PluginBase: PluginBase{
|
||||||
|
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata\\datasource",
|
||||||
|
BaseUrl: "fpbase",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app := &AppPlugin{
|
||||||
|
FrontendPluginBase: FrontendPluginBase{
|
||||||
|
PluginBase: PluginBase{
|
||||||
|
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
|
||||||
|
Id: "testdata",
|
||||||
|
BaseUrl: "public/app/plugins/app/testdata",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fp.setPathsBasedOnApp(app)
|
||||||
|
|
||||||
|
So(fp.Module, ShouldEqual, "app/plugins/app/testdata/datasource/module")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ func (r *SqlAnnotationRepo) ensureTagsExist(sess *DBSession, tags []*models.Tag)
|
|||||||
var existingTag models.Tag
|
var existingTag models.Tag
|
||||||
|
|
||||||
// check if it exists
|
// check if it exists
|
||||||
if exists, err := sess.Table("tag").Where("key=? AND value=?", tag.Key, tag.Value).Get(&existingTag); err != nil {
|
if exists, err := sess.Table("tag").Where("`key`=? AND `value`=?", tag.Key, tag.Value).Get(&existingTag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if exists {
|
} else if exists {
|
||||||
tag.Id = existingTag.Id
|
tag.Id = existingTag.Id
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
|||||||
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("%s >= to_timestamp(%d) AND %s <= to_timestamp(%d)", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
|
return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
|
||||||
case "__timeFrom":
|
case "__timeFrom":
|
||||||
return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil
|
return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil
|
||||||
case "__timeTo":
|
case "__timeTo":
|
||||||
@@ -83,7 +83,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
|||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("(extract(epoch from \"%s\")/extract(epoch from %s::interval))::int", args[0], args[1]), nil
|
return fmt.Sprintf("(extract(epoch from \"%s\")/extract(epoch from %s::interval))::int*extract(epoch from %s::interval)", args[0], args[1], args[1]), nil
|
||||||
case "__unixEpochFilter":
|
case "__unixEpochFilter":
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestMacroEngine(t *testing.T) {
|
|||||||
sql, err := engine.Interpolate(timeRange, "WHERE $__timeFilter(time_column)")
|
sql, err := engine.Interpolate(timeRange, "WHERE $__timeFilter(time_column)")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(sql, ShouldEqual, "WHERE time_column >= to_timestamp(18446744066914186738) AND time_column <= to_timestamp(18446744066914187038)")
|
So(sql, ShouldEqual, "WHERE extract(epoch from time_column) BETWEEN 18446744066914186738 AND 18446744066914187038")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("interpolate __timeFrom function", func() {
|
Convey("interpolate __timeFrom function", func() {
|
||||||
@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) {
|
|||||||
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
|
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/extract(epoch from '5m'::interval))::int")
|
So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/extract(epoch from '5m'::interval))::int*extract(epoch from '5m'::interval)")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("interpolate __timeTo function", func() {
|
Convey("interpolate __timeTo function", func() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import coreModule from '../core_module';
|
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
password: string;
|
password: string;
|
||||||
@@ -33,7 +33,5 @@ export class PasswordStrength extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.directive('passwordStrength', function(reactDirective) {
|
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
|
||||||
return reactDirective(PasswordStrength, ['password']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import coreModule from 'app/core/core_module';
|
|
||||||
import { sortedColors } from 'app/core/utils/colors';
|
import { sortedColors } from 'app/core/utils/colors';
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
@@ -23,12 +22,15 @@ export class GfColorPalette extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const colorPaletteItems = this.paletteColors.map((paletteColor) => {
|
const colorPaletteItems = this.paletteColors.map(paletteColor => {
|
||||||
const cssClass = paletteColor.toLowerCase() === this.props.color.toLowerCase() ? 'fa-circle-o' : 'fa-circle';
|
const cssClass = paletteColor.toLowerCase() === this.props.color.toLowerCase() ? 'fa-circle-o' : 'fa-circle';
|
||||||
return (
|
return (
|
||||||
<i key={paletteColor} className={"pointer fa " + cssClass}
|
<i
|
||||||
style={{'color': paletteColor}}
|
key={paletteColor}
|
||||||
onClick={this.onColorSelect(paletteColor)}>
|
className={'pointer fa ' + cssClass}
|
||||||
|
style={{ color: paletteColor }}
|
||||||
|
onClick={this.onColorSelect(paletteColor)}>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -40,6 +42,3 @@ export class GfColorPalette extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.directive('gfColorPalette', function (reactDirective) {
|
|
||||||
return reactDirective(GfColorPalette, ['color', 'onColorSelect']);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Drop from 'tether-drop';
|
import Drop from 'tether-drop';
|
||||||
import coreModule from 'app/core/core_module';
|
|
||||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
|
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
color: string;
|
color: string;
|
||||||
@@ -27,9 +27,7 @@ export class ColorPicker extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openColorPicker() {
|
openColorPicker() {
|
||||||
const dropContent = (
|
const dropContent = <ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />;
|
||||||
<ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />
|
|
||||||
);
|
|
||||||
|
|
||||||
let dropContentElem = document.createElement('div');
|
let dropContentElem = document.createElement('div');
|
||||||
ReactDOM.render(dropContent, dropContentElem);
|
ReactDOM.render(dropContent, dropContentElem);
|
||||||
@@ -38,12 +36,12 @@ export class ColorPicker extends React.Component<IProps, any> {
|
|||||||
target: this.pickerElem[0],
|
target: this.pickerElem[0],
|
||||||
content: dropContentElem,
|
content: dropContentElem,
|
||||||
position: 'top center',
|
position: 'top center',
|
||||||
classes: 'drop-popover drop-popover--form',
|
classes: 'drop-popover',
|
||||||
openOn: 'hover',
|
openOn: 'click',
|
||||||
hoverCloseDelay: 200,
|
hoverCloseDelay: 200,
|
||||||
tetherOptions: {
|
tetherOptions: {
|
||||||
constraints: [{ to: 'scrollParent', attachment: "none both" }]
|
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
drop.on('close', this.closeColorPicker);
|
drop.on('close', this.closeColorPicker);
|
||||||
@@ -68,17 +66,14 @@ export class ColorPicker extends React.Component<IProps, any> {
|
|||||||
return (
|
return (
|
||||||
<div className="sp-replacer sp-light" onClick={this.openColorPicker} ref={this.setPickerElem}>
|
<div className="sp-replacer sp-light" onClick={this.openColorPicker} ref={this.setPickerElem}>
|
||||||
<div className="sp-preview">
|
<div className="sp-preview">
|
||||||
<div className="sp-preview-inner" style={{backgroundColor: this.props.color}}>
|
<div className="sp-preview-inner" style={{ backgroundColor: this.props.color }} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.directive('colorPicker', function (reactDirective) {
|
react2AngularDirective('colorPicker', ColorPicker, [
|
||||||
return reactDirective(ColorPicker, [
|
'color',
|
||||||
'color',
|
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
['onChange', { watchDepth: 'reference', wrapApply: true }]
|
]);
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import coreModule from 'app/core/core_module';
|
|
||||||
import { GfColorPalette } from './ColorPalette';
|
import { GfColorPalette } from './ColorPalette';
|
||||||
import { GfSpectrumPicker } from './SpectrumPicker';
|
import { GfSpectrumPicker } from './SpectrumPicker';
|
||||||
|
|
||||||
@@ -115,7 +114,3 @@ export class ColorPickerPopover extends React.Component<IProps, any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.directive('gfColorPickerPopover', function (reactDirective) {
|
|
||||||
return reactDirective(ColorPickerPopover, ['color', 'onColorSelect']);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import coreModule from 'app/core/core_module';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
import {ColorPickerPopover} from './ColorPickerPopover';
|
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
series: any;
|
series: any;
|
||||||
@@ -50,6 +50,4 @@ export class SeriesColorPicker extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.directive('seriesColorPicker', function(reactDirective) {
|
react2AngularDirective('seriesColorPicker', SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']);
|
||||||
return reactDirective(SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import coreModule from 'app/core/core_module';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import 'vendor/spectrum';
|
import 'vendor/spectrum';
|
||||||
@@ -71,6 +70,3 @@ export class GfSpectrumPicker extends React.Component<IProps, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.directive('gfSpectrumPicker', function (reactDirective) {
|
|
||||||
return reactDirective(GfSpectrumPicker, ['color', 'options', 'onColorSelect']);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
import coreModule from '../../core_module';
|
import coreModule from '../../core_module';
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
export function spectrumPicker() {
|
export function spectrumPicker() {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import {saveAs} from 'file-saver';
|
||||||
declare var window: any;
|
|
||||||
|
|
||||||
const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
|
const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
|
||||||
|
|
||||||
@@ -69,5 +68,5 @@ export function exportTableDataToCsv(table, excel = false) {
|
|||||||
|
|
||||||
export function saveSaveBlob(payload, fname) {
|
export function saveSaveBlob(payload, fname) {
|
||||||
var blob = new Blob([payload], { type: "text/csv;charset=utf-8" });
|
var blob = new Blob([payload], { type: "text/csv;charset=utf-8" });
|
||||||
window.saveAs(blob, fname);
|
saveAs(blob, fname);
|
||||||
}
|
}
|
||||||
|
|||||||
10
public/app/core/utils/react2angular.ts
Normal file
10
public/app/core/utils/react2angular.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import coreModule from 'app/core/core_module';
|
||||||
|
|
||||||
|
export function react2AngularDirective(name: string, component: any, options: any) {
|
||||||
|
|
||||||
|
coreModule.directive(name, ['reactDirective', reactDirective => {
|
||||||
|
return reactDirective(component, options);
|
||||||
|
}]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -383,6 +383,7 @@ export class AlertTabCtrl {
|
|||||||
|
|
||||||
test() {
|
test() {
|
||||||
this.testing = true;
|
this.testing = true;
|
||||||
|
this.testResult = false;
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(),
|
dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(),
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
///<reference path="../../../headers/common.d.ts" />
|
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import coreModule from 'app/core/core_module';
|
import {saveAs} from 'file-saver';
|
||||||
|
|
||||||
|
import coreModule from 'app/core/core_module';
|
||||||
import {DashboardExporter} from './exporter';
|
import {DashboardExporter} from './exporter';
|
||||||
|
|
||||||
export class DashExportCtrl {
|
export class DashExportCtrl {
|
||||||
@@ -22,9 +21,8 @@ export class DashExportCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
var blob = new Blob([angular.toJson(this.dash, true)], { type: "application/json;charset=utf-8" });
|
var blob = new Blob([angular.toJson(this.dash, true)], {type: 'application/json;charset=utf-8'});
|
||||||
var wnd: any = window;
|
saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json');
|
||||||
wnd.saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveJson() {
|
saveJson() {
|
||||||
@@ -44,7 +42,7 @@ export function dashExportDirective() {
|
|||||||
controller: DashExportCtrl,
|
controller: DashExportCtrl,
|
||||||
bindToController: true,
|
bindToController: true,
|
||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
scope: {dismiss: "&"}
|
scope: {dismiss: '&'},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,23 @@ import config from 'app/core/config';
|
|||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
import {coreModule, appEvents, contextSrv} from 'app/core/core';
|
import {coreModule, appEvents, contextSrv} from 'app/core/core';
|
||||||
|
import * as datemath from 'app/core/utils/datemath';
|
||||||
|
import * as fileExport from 'app/core/utils/file_export';
|
||||||
|
import * as flatten from 'app/core/utils/flatten';
|
||||||
|
import * as ticks from 'app/core/utils/ticks';
|
||||||
|
import {impressions} from 'app/features/dashboard/impression_store';
|
||||||
|
import builtInPlugins from './built_in_plugins';
|
||||||
|
import d3 from 'vendor/d3/d3';
|
||||||
|
|
||||||
|
// rxjs
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Subject} from 'rxjs/Subject';
|
import {Subject} from 'rxjs/Subject';
|
||||||
import * as datemath from 'app/core/utils/datemath';
|
|
||||||
import builtInPlugins from './buit_in_plugins';
|
// these imports add functions to Observable
|
||||||
import d3 from 'vendor/d3/d3';
|
import 'rxjs/add/observable/empty';
|
||||||
|
import 'rxjs/add/observable/from';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/combineAll';
|
||||||
|
|
||||||
System.config({
|
System.config({
|
||||||
baseURL: 'public',
|
baseURL: 'public',
|
||||||
@@ -27,6 +39,9 @@ System.config({
|
|||||||
text: 'vendor/plugin-text/text.js',
|
text: 'vendor/plugin-text/text.js',
|
||||||
css: 'vendor/plugin-css/css.js'
|
css: 'vendor/plugin-css/css.js'
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
'*': {esModule: true}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// add cache busting
|
// add cache busting
|
||||||
@@ -53,20 +68,30 @@ exposeToPlugin('rxjs/Subject', Subject);
|
|||||||
exposeToPlugin('rxjs/Observable', Observable);
|
exposeToPlugin('rxjs/Observable', Observable);
|
||||||
exposeToPlugin('d3', d3);
|
exposeToPlugin('d3', d3);
|
||||||
|
|
||||||
|
exposeToPlugin('app/features/dashboard/impression_store', {
|
||||||
|
impressions: impressions,
|
||||||
|
__esModule: true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
exposeToPlugin('app/plugins/sdk', sdk);
|
exposeToPlugin('app/plugins/sdk', sdk);
|
||||||
exposeToPlugin('app/core/utils/datemath', datemath);
|
exposeToPlugin('app/core/utils/datemath', datemath);
|
||||||
|
exposeToPlugin('app/core/utils/file_export', fileExport);
|
||||||
|
exposeToPlugin('app/core/utils/flatten', flatten);
|
||||||
exposeToPlugin('app/core/utils/kbn', kbn);
|
exposeToPlugin('app/core/utils/kbn', kbn);
|
||||||
|
exposeToPlugin('app/core/utils/ticks', ticks);
|
||||||
|
|
||||||
exposeToPlugin('app/core/config', config);
|
exposeToPlugin('app/core/config', config);
|
||||||
exposeToPlugin('app/core/time_series', TimeSeries);
|
exposeToPlugin('app/core/time_series', TimeSeries);
|
||||||
exposeToPlugin('app/core/time_series2', TimeSeries);
|
exposeToPlugin('app/core/time_series2', TimeSeries);
|
||||||
exposeToPlugin('app/core/table_model', TableModel);
|
exposeToPlugin('app/core/table_model', TableModel);
|
||||||
exposeToPlugin('app/core/app_events', appEvents);
|
exposeToPlugin('app/core/app_events', appEvents);
|
||||||
exposeToPlugin('app/core/core_module', coreModule);
|
exposeToPlugin('app/core/core_module', coreModule);
|
||||||
exposeToPlugin('app/core/core_module', coreModule);
|
|
||||||
exposeToPlugin('app/core/core', {
|
exposeToPlugin('app/core/core', {
|
||||||
coreModule: coreModule,
|
coreModule: coreModule,
|
||||||
appEvents: appEvents,
|
appEvents: appEvents,
|
||||||
contextSrv: contextSrv,
|
contextSrv: contextSrv,
|
||||||
|
__esModule: true
|
||||||
});
|
});
|
||||||
|
|
||||||
import 'vendor/flot/jquery.flot';
|
import 'vendor/flot/jquery.flot';
|
||||||
@@ -79,7 +104,11 @@ import 'vendor/flot/jquery.flot.fillbelow';
|
|||||||
import 'vendor/flot/jquery.flot.crosshair';
|
import 'vendor/flot/jquery.flot.crosshair';
|
||||||
import 'vendor/flot/jquery.flot.dashes';
|
import 'vendor/flot/jquery.flot.dashes';
|
||||||
|
|
||||||
for (let flotDep of ['jquery.flot', 'jquery.flot.pie', 'jquery.flot.time']) {
|
const flotDeps = [
|
||||||
|
'jquery.flot', 'jquery.flot.pie', 'jquery.flot.time', 'jquery.flot.fillbelow', 'jquery.flot.crosshair',
|
||||||
|
'jquery.flot.stack', 'jquery.flot.selection', 'jquery.flot.stackpercent', 'jquery.flot.events'
|
||||||
|
];
|
||||||
|
for (let flotDep of flotDeps) {
|
||||||
exposeToPlugin(flotDep, {fakeDep: 1});
|
exposeToPlugin(flotDep, {fakeDep: 1});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
|
item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
|
||||||
item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
|
item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
|
||||||
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
|
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
|
||||||
item.period = self.getPeriod(item, options);
|
item.period = String(self.getPeriod(item, options)); // use string format for period in graph query, and alerting
|
||||||
|
|
||||||
return _.extend({
|
return _.extend({
|
||||||
refId: item.refId,
|
refId: item.refId,
|
||||||
@@ -318,6 +318,8 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
|
|
||||||
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
|
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
|
||||||
return { status: 'success', message: 'Data source is working' };
|
return { status: 'success', message: 'Data source is working' };
|
||||||
|
}, function (err) {
|
||||||
|
return { status: 'error', message: err.message };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -352,6 +354,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
var t = angular.copy(target);
|
var t = angular.copy(target);
|
||||||
var scopedVar = {};
|
var scopedVar = {};
|
||||||
scopedVar[variable.name] = v;
|
scopedVar[variable.name] = v;
|
||||||
|
t.refId = target.refId + '_' + v.value;
|
||||||
t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
|
t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
|
||||||
return t;
|
return t;
|
||||||
}).value();
|
}).value();
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('CloudWatchDatasource', function() {
|
|||||||
InstanceId: 'i-12345678'
|
InstanceId: 'i-12345678'
|
||||||
},
|
},
|
||||||
statistics: ['Average'],
|
statistics: ['Average'],
|
||||||
period: 300
|
period: '300'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -109,7 +109,7 @@ describe('CloudWatchDatasource', function() {
|
|||||||
|
|
||||||
ctx.ds.query(query).then(function() {
|
ctx.ds.query(query).then(function() {
|
||||||
var params = requestParams.queries[0];
|
var params = requestParams.queries[0];
|
||||||
expect(params.period).to.be(600);
|
expect(params.period).to.be('600');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
ctx.$rootScope.$apply();
|
ctx.$rootScope.$apply();
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ export class ElasticDatasource {
|
|||||||
return this.request('POST', url, data).then(function(results) {
|
return this.request('POST', url, data).then(function(results) {
|
||||||
results.data.$$config = results.config;
|
results.data.$$config = results.config;
|
||||||
return results.data;
|
return results.data;
|
||||||
|
}).catch(err => {
|
||||||
|
if (err.data && err.data.error) {
|
||||||
|
throw {message: 'Elasticsearch error: ' + err.data.error.reason, error: err.data.error};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export class PostgresDatasource {
|
|||||||
return '\'' + value + '\'';
|
return '\'' + value + '\'';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
var quotedValues = _.map(value, function(val) {
|
var quotedValues = _.map(value, function(val) {
|
||||||
return '\'' + val + '\'';
|
return '\'' + val + '\'';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class PostgresConfigCtrl {
|
|||||||
|
|
||||||
const defaultQuery = `SELECT
|
const defaultQuery = `SELECT
|
||||||
extract(epoch from time_column) AS time,
|
extract(epoch from time_column) AS time,
|
||||||
title_column as title,
|
text_column as text,
|
||||||
description_column as text
|
tags_column as tags
|
||||||
FROM
|
FROM
|
||||||
metric_table
|
metric_table
|
||||||
WHERE
|
WHERE
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class PromCompleter {
|
|||||||
labelNameCache: any;
|
labelNameCache: any;
|
||||||
labelValueCache: any;
|
labelValueCache: any;
|
||||||
|
|
||||||
identifierRegexps = [/[\[\]a-zA-Z_0-9=]/];
|
identifierRegexps = [/\[/, /[a-zA-Z0-9_:]/];
|
||||||
|
|
||||||
constructor(private datasource: PrometheusDatasource) {
|
constructor(private datasource: PrometheusDatasource) {
|
||||||
this.labelQueryCache = {};
|
this.labelQueryCache = {};
|
||||||
@@ -73,13 +73,15 @@ export class PromCompleter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefix === '[') {
|
if (token.type === 'paren.lparen' && token.value === '[') {
|
||||||
var vectors = [];
|
var vectors = [];
|
||||||
for (let unit of ['s', 'm', 'h']) {
|
for (let unit of ['s', 'm', 'h']) {
|
||||||
for (let value of [1,5,10,30]) {
|
for (let value of [1,5,10,30]) {
|
||||||
vectors.push({caption: value+unit, value: '['+value+unit, meta: 'range vector'});
|
vectors.push({caption: value+unit, value: '['+value+unit, meta: 'range vector'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vectors.push({caption: '$__interval', value: '[$__interval', meta: 'range vector'});
|
||||||
|
vectors.push({caption: '$__interval_ms', value: '[$__interval_ms', meta: 'range vector'});
|
||||||
callback(null, vectors);
|
callback(null, vectors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export class PrometheusDatasource {
|
|||||||
if (interval !== 0 && range / intervalFactor / interval > 11000) {
|
if (interval !== 0 && range / intervalFactor / interval > 11000) {
|
||||||
interval = Math.ceil(range / intervalFactor / 11000);
|
interval = Math.ceil(range / intervalFactor / 11000);
|
||||||
}
|
}
|
||||||
return Math.max(interval * intervalFactor, minInterval);
|
return Math.max(interval * intervalFactor, minInterval, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
performTimeSeriesQuery(query, start, end) {
|
performTimeSeriesQuery(query, start, end) {
|
||||||
|
|||||||
@@ -44,12 +44,18 @@ describe('Prometheus editor completer', function() {
|
|||||||
describe('When inside brackets', () => {
|
describe('When inside brackets', () => {
|
||||||
it('Should return range vectors', () => {
|
it('Should return range vectors', () => {
|
||||||
const session = getSessionStub({
|
const session = getSessionStub({
|
||||||
currentToken: {},
|
currentToken: {type: 'paren.lparen', value: '[', index: 2, start: 9},
|
||||||
tokens: [],
|
tokens: [
|
||||||
line: '',
|
{type: 'identifier', value: 'node_cpu'},
|
||||||
|
{type: 'paren.lparen', value: '['}
|
||||||
|
],
|
||||||
|
line: 'node_cpu[',
|
||||||
});
|
});
|
||||||
completer.getCompletions(editor, session, {row: 0, column: 10}, '[', (s, res) => {
|
|
||||||
expect(res[0]).to.eql({caption: '1s', value: '[1s', meta: 'range vector'});
|
return completer.getCompletions(editor, session, {row: 0, column: 10}, '[', (s, res) => {
|
||||||
|
expect(res[0].caption).to.eql('1s');
|
||||||
|
expect(res[0].value).to.eql('[1s');
|
||||||
|
expect(res[0].meta).to.eql('range vector');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -294,6 +294,20 @@ describe('PrometheusDatasource', function() {
|
|||||||
ctx.ds.query(query);
|
ctx.ds.query(query);
|
||||||
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('step should never go below 1', function() {
|
||||||
|
var query = {
|
||||||
|
// 6 hour range
|
||||||
|
range: { from: moment(1508318768202), to: moment(1508318770118) },
|
||||||
|
targets: [{expr: 'test'}],
|
||||||
|
interval: '100ms'
|
||||||
|
};
|
||||||
|
var urlExpected = 'proxied/api/v1/query_range?query=test&start=1508318769&end=1508318771&step=1';
|
||||||
|
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||||
|
ctx.ds.query(query);
|
||||||
|
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
});
|
||||||
|
|
||||||
it('should be auto interval when greater than min interval', function() {
|
it('should be auto interval when greater than min interval', function() {
|
||||||
var query = {
|
var query = {
|
||||||
// 6 hour range
|
// 6 hour range
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class GettingStartedPanelCtrl extends PanelCtrl {
|
|||||||
check: () => {
|
check: () => {
|
||||||
return $q.when(
|
return $q.when(
|
||||||
datasourceSrv.getMetricSources().filter(item => {
|
datasourceSrv.getMetricSources().filter(item => {
|
||||||
return item.meta.builtIn === false;
|
return item.meta.builtIn !== true;
|
||||||
}).length > 0
|
}).length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
|
|||||||
.tickSize(2);
|
.tickSize(2);
|
||||||
|
|
||||||
let colorRect = legendElem.find(":first-child");
|
let colorRect = legendElem.find(":first-child");
|
||||||
let posY = colorRect.height() + 2;
|
let posY = getSvgElemHeight(legendElem) + 2;
|
||||||
let posX = getSvgElemX(colorRect);
|
let posX = getSvgElemX(colorRect);
|
||||||
|
|
||||||
d3.select(legendElem.get(0)).append("g")
|
d3.select(legendElem.get(0)).append("g")
|
||||||
@@ -256,7 +256,16 @@ function getOpacityScale(options, maxValue, minValue = 0) {
|
|||||||
function getSvgElemX(elem) {
|
function getSvgElemX(elem) {
|
||||||
let svgElem = elem.get(0);
|
let svgElem = elem.get(0);
|
||||||
if (svgElem && svgElem.x && svgElem.x.baseVal) {
|
if (svgElem && svgElem.x && svgElem.x.baseVal) {
|
||||||
return elem.get(0).x.baseVal.value;
|
return svgElem.x.baseVal.value;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSvgElemHeight(elem) {
|
||||||
|
let svgElem = elem.get(0);
|
||||||
|
if (svgElem && svgElem.height && svgElem.height.baseVal) {
|
||||||
|
return svgElem.height.baseVal.value;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,9 +71,8 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
function getYAxisWidth(elem) {
|
function getYAxisWidth(elem) {
|
||||||
let axis_text = elem.selectAll(".axis-y text").nodes();
|
let axis_text = elem.selectAll(".axis-y text").nodes();
|
||||||
let max_text_width = _.max(_.map(axis_text, text => {
|
let max_text_width = _.max(_.map(axis_text, text => {
|
||||||
let el = $(text);
|
// Use SVG getBBox method
|
||||||
// Use JQuery outerWidth() to compute full element width
|
return text.getBBox().width;
|
||||||
return el.outerWidth();
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return max_text_width;
|
return max_text_width;
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ $white: #fff;
|
|||||||
// -------------------------
|
// -------------------------
|
||||||
$blue: #33B5E5;
|
$blue: #33B5E5;
|
||||||
$blue-dark: #005f81;
|
$blue-dark: #005f81;
|
||||||
$green: #609000;
|
$green: #299c46;
|
||||||
$red: #CC3900;
|
$red: #d44a3a;
|
||||||
$yellow: #ECBB13;
|
$yellow: #ECBB13;
|
||||||
$pink: #FF4444;
|
$pink: #FF4444;
|
||||||
$purple: #9933CC;
|
$purple: #9933CC;
|
||||||
@@ -130,20 +130,20 @@ $table-border: $dark-3; // table and cell border
|
|||||||
// Buttons
|
// Buttons
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
$btn-primary-bg: $brand-primary;
|
$btn-primary-bg: #ff6600;
|
||||||
$btn-primary-bg-hl: lighten($brand-primary, 8%);
|
$btn-primary-bg-hl: #bc3e06;
|
||||||
|
|
||||||
$btn-secondary-bg: $blue-dark;
|
$btn-secondary-bg: $blue-dark;
|
||||||
$btn-secondary-bg-hl: lighten($blue-dark, 5%);
|
$btn-secondary-bg-hl: lighten($blue-dark, 5%);
|
||||||
|
|
||||||
$btn-success-bg: lighten($green, 3%);
|
$btn-success-bg: $green;
|
||||||
$btn-success-bg-hl: darken($green, 3%);
|
$btn-success-bg-hl: darken($green, 6%);
|
||||||
|
|
||||||
$btn-warning-bg: $brand-warning;
|
$btn-warning-bg: $brand-warning;
|
||||||
$btn-warning-bg-hl: lighten($brand-warning, 8%);
|
$btn-warning-bg-hl: lighten($brand-warning, 8%);
|
||||||
|
|
||||||
$btn-danger-bg: $red;
|
$btn-danger-bg: $red;
|
||||||
$btn-danger-bg-hl: lighten($red, 5%);
|
$btn-danger-bg-hl: darken($red, 8%);
|
||||||
|
|
||||||
$btn-inverse-bg: $dark-3;
|
$btn-inverse-bg: $dark-3;
|
||||||
$btn-inverse-bg-hl: lighten($dark-3, 4%);
|
$btn-inverse-bg-hl: lighten($dark-3, 4%);
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ $white: #fff;
|
|||||||
// -------------------------
|
// -------------------------
|
||||||
$blue: #2AB2E4;
|
$blue: #2AB2E4;
|
||||||
$blue-dark: #3CAAD6;
|
$blue-dark: #3CAAD6;
|
||||||
$green: #28B62C;
|
$green: #3aa655;
|
||||||
$red: #FF4136;
|
$red: #d44939;
|
||||||
$yellow: #FF851B;
|
$yellow: #FF851B;
|
||||||
$orange: #Ff7941;
|
$orange: #Ff7941;
|
||||||
$pink: #E671B8;
|
$pink: #E671B8;
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ $gf-form-margin: 0.25rem;
|
|||||||
// text areas should be scrollable
|
// text areas should be scrollable
|
||||||
@at-root textarea#{&} {
|
@at-root textarea#{&} {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unstyle the caret on `<select>`s in IE10+.
|
// Unstyle the caret on `<select>`s in IE10+.
|
||||||
|
|||||||
@@ -211,15 +211,6 @@
|
|||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
}
|
}
|
||||||
.close {
|
|
||||||
margin-right: 5px;
|
|
||||||
color: $link-color;
|
|
||||||
opacity: 0.7;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
.editor-row {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.annotation-tags {
|
.annotation-tags {
|
||||||
|
|||||||
Reference in New Issue
Block a user