mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into docs-5.1
This commit is contained in:
@@ -20,18 +20,20 @@ jobs:
|
||||
gometalinter:
|
||||
docker:
|
||||
- image: circleci/golang:1.10
|
||||
environment:
|
||||
# we need CGO because of go-sqlite3
|
||||
CGO_ENABLED: 1
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run: 'go get -u gopkg.in/alecthomas/gometalinter.v2'
|
||||
- run: 'go get -u github.com/gordonklaus/ineffassign'
|
||||
- run: 'go get -u github.com/opennota/check/cmd/structcheck'
|
||||
- run: 'go get -u github.com/mdempsky/unconvert'
|
||||
- run: 'go get -u github.com/opennota/check/cmd/varcheck'
|
||||
- run:
|
||||
name: install gometalinter tool
|
||||
command: 'go get -u github.com/alecthomas/gometalinter'
|
||||
- run:
|
||||
name: install linters
|
||||
command: 'gometalinter --install'
|
||||
- run:
|
||||
name: run some linters
|
||||
command: 'gometalinter --vendor --deadline 6m --disable-all --enable=structcheck --enable=unconvert --enable=varcheck ./pkg/...'
|
||||
name: run linters
|
||||
command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...'
|
||||
|
||||
test-frontend:
|
||||
docker:
|
||||
@@ -139,6 +141,10 @@ workflows:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- gometalinter:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build:
|
||||
filters:
|
||||
tags:
|
||||
|
||||
@@ -56,10 +56,10 @@ To simplify syntax and to allow for dynamic parts, like date range filters, the
|
||||
Macro example | Description
|
||||
------------ | -------------
|
||||
*$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *dateColumn as time*
|
||||
*$__timeEpoch(dateColumn)* | Will be replaced by an expression to rename the column to `time` and converting the value to unix timestamp. For example, *extract(epoch from dateColumn) as time*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-05-10T10:06:23Z' AND '2017-05-10T10:06:23Z'*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-05-10T10:06:23Z'*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-05-10T10:06:23Z'*
|
||||
*$__timeSec(dateColumn)* | Will be replaced by an expression to rename the column to `time` and converting the value to unix timestamp. For example, *extract(epoch from dateColumn) as time*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:06:17Z'*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'*
|
||||
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300 AS time*
|
||||
*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example).
|
||||
*$__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*
|
||||
|
||||
@@ -36,6 +36,8 @@ Query Parameters:
|
||||
- `alertId`: number. Optional. Find annotations for a specified alert.
|
||||
- `dashboardId`: number. Optional. Find annotations that are scoped to a specific dashboard
|
||||
- `panelId`: number. Optional. Find annotations that are scoped to a specific panel
|
||||
- `userId`: number. Optional. Find annotations created by a specific user
|
||||
- `type`: string. Optional. `alert`|`annotation` Return alerts or user created annotations
|
||||
- `tags`: string. Optional. Use this to filter global annotations. Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. `tags=tag1&tags=tag2`.
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "5.0.0",
|
||||
"testing": "5.0.0"
|
||||
"stable": "5.0.4",
|
||||
"testing": "5.0.4"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"company": "Grafana Labs"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "5.1.0-pre1",
|
||||
"version": "5.2.0-pre1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
|
||||
@@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -15,9 +14,10 @@ import (
|
||||
func GetAnnotations(c *m.ReqContext) Response {
|
||||
|
||||
query := &annotations.ItemQuery{
|
||||
From: c.QueryInt64("from") / 1000,
|
||||
To: c.QueryInt64("to") / 1000,
|
||||
From: c.QueryInt64("from"),
|
||||
To: c.QueryInt64("to"),
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.QueryInt64("userId"),
|
||||
AlertId: c.QueryInt64("alertId"),
|
||||
DashboardId: c.QueryInt64("dashboardId"),
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
@@ -37,7 +37,7 @@ func GetAnnotations(c *m.ReqContext) Response {
|
||||
if item.Email != "" {
|
||||
item.AvatarUrl = dtos.GetGravatarUrl(item.Email)
|
||||
}
|
||||
item.Time = item.Time * 1000
|
||||
item.Time = item.Time
|
||||
}
|
||||
|
||||
return JSON(200, items)
|
||||
@@ -68,16 +68,12 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
|
||||
UserId: c.UserId,
|
||||
DashboardId: cmd.DashboardId,
|
||||
PanelId: cmd.PanelId,
|
||||
Epoch: cmd.Time / 1000,
|
||||
Epoch: cmd.Time,
|
||||
Text: cmd.Text,
|
||||
Data: cmd.Data,
|
||||
Tags: cmd.Tags,
|
||||
}
|
||||
|
||||
if item.Epoch == 0 {
|
||||
item.Epoch = time.Now().Unix()
|
||||
}
|
||||
|
||||
if err := repo.Save(&item); err != nil {
|
||||
return Error(500, "Failed to save annotation", err)
|
||||
}
|
||||
@@ -97,7 +93,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
|
||||
}
|
||||
|
||||
item.Id = 0
|
||||
item.Epoch = cmd.TimeEnd / 1000
|
||||
item.Epoch = cmd.TimeEnd
|
||||
|
||||
if err := repo.Save(&item); err != nil {
|
||||
return Error(500, "Failed save annotation for region end time", err)
|
||||
@@ -132,9 +128,6 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
|
||||
return Error(500, "Failed to save Graphite annotation", err)
|
||||
}
|
||||
|
||||
if cmd.When == 0 {
|
||||
cmd.When = time.Now().Unix()
|
||||
}
|
||||
text := formatGraphiteAnnotation(cmd.What, cmd.Data)
|
||||
|
||||
// Support tags in prior to Graphite 0.10.0 format (string of tags separated by space)
|
||||
@@ -163,7 +156,7 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
|
||||
item := annotations.Item{
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
Epoch: cmd.When,
|
||||
Epoch: cmd.When * 1000,
|
||||
Text: text,
|
||||
Tags: tagsArray,
|
||||
}
|
||||
@@ -191,7 +184,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
Id: annotationID,
|
||||
Epoch: cmd.Time / 1000,
|
||||
Epoch: cmd.Time,
|
||||
Text: cmd.Text,
|
||||
Tags: cmd.Tags,
|
||||
}
|
||||
@@ -203,7 +196,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
|
||||
if cmd.IsRegion {
|
||||
itemRight := item
|
||||
itemRight.RegionId = item.Id
|
||||
itemRight.Epoch = cmd.TimeEnd / 1000
|
||||
itemRight.Epoch = cmd.TimeEnd
|
||||
|
||||
// We don't know id of region right event, so set it to 0 and find then using query like
|
||||
// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...
|
||||
|
||||
@@ -100,9 +100,9 @@ func main() {
|
||||
}
|
||||
|
||||
func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
|
||||
var code int
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
ignoreChan := make(chan os.Signal, 1)
|
||||
code := 0
|
||||
|
||||
signal.Notify(ignoreChan, syscall.SIGHUP)
|
||||
signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
|
||||
@@ -141,5 +141,9 @@ func getDiff(baseData, newData *simplejson.Json) (interface{}, diff.Diff, error)
|
||||
|
||||
left := make(map[string]interface{})
|
||||
err = json.Unmarshal(leftBytes, &left)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return left, jsonDiff, nil
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ func TestFirst(t *testing.T) {
|
||||
}`
|
||||
|
||||
j, err := NewObjectFromBytes([]byte(testJSON))
|
||||
assert.True(err == nil, "failed to create new object from bytes")
|
||||
|
||||
a, err := j.GetObject("address")
|
||||
assert.True(a != nil && err == nil, "failed to create json from string")
|
||||
@@ -108,6 +109,7 @@ func TestFirst(t *testing.T) {
|
||||
//log.Println("address: ", address)
|
||||
|
||||
s, err = address.GetString("street")
|
||||
assert.True(s == "Street 42" && err == nil, "street mismatching")
|
||||
|
||||
addressAsString, err := j.GetString("address")
|
||||
assert.True(addressAsString == "" && err != nil, "address should not be an string")
|
||||
@@ -148,6 +150,7 @@ func TestFirst(t *testing.T) {
|
||||
//assert.True(element.IsObject() == true, "first fail")
|
||||
|
||||
element, err := elementValue.Object()
|
||||
assert.True(err == nil, "create element fail")
|
||||
|
||||
s, err = element.GetString("street")
|
||||
assert.True(s == "Street 42" && err == nil, "second fail")
|
||||
@@ -232,6 +235,7 @@ func TestSecond(t *testing.T) {
|
||||
assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch")
|
||||
|
||||
actions, err := dataItem.GetObjectArray("actions")
|
||||
assert.True(err == nil, "get object from array failed")
|
||||
|
||||
for index, action := range actions {
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ func TestUploadToAzureBlob(t *testing.T) {
|
||||
err := setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
uploader, _ := NewImageUploader()
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
|
||||
Convey("with bucket url https://foo.bar.baz.s3-us-east-2.amazonaws.com", func() {
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
So(err, ShouldBeNil)
|
||||
s3sec.NewKey("bucket_url", "https://foo.bar.baz.s3-us-east-2.amazonaws.com")
|
||||
s3sec.NewKey("access_key", "access_key")
|
||||
s3sec.NewKey("secret_key", "secret_key")
|
||||
@@ -37,6 +38,7 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
|
||||
Convey("with bucket url https://s3.amazonaws.com/mybucket", func() {
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
So(err, ShouldBeNil)
|
||||
s3sec.NewKey("bucket_url", "https://s3.amazonaws.com/my.bucket.com")
|
||||
s3sec.NewKey("access_key", "access_key")
|
||||
s3sec.NewKey("secret_key", "secret_key")
|
||||
@@ -55,15 +57,15 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
|
||||
Convey("with bucket url https://s3-us-west-2.amazonaws.com/mybucket", func() {
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
So(err, ShouldBeNil)
|
||||
s3sec.NewKey("bucket_url", "https://s3-us-west-2.amazonaws.com/my.bucket.com")
|
||||
s3sec.NewKey("access_key", "access_key")
|
||||
s3sec.NewKey("secret_key", "secret_key")
|
||||
|
||||
uploader, err := NewImageUploader()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
original, ok := uploader.(*S3Uploader)
|
||||
|
||||
original, ok := uploader.(*S3Uploader)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(original.region, ShouldEqual, "us-west-2")
|
||||
So(original.bucket, ShouldEqual, "my.bucket.com")
|
||||
@@ -82,6 +84,7 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
setting.ImageUploadProvider = "webdav"
|
||||
|
||||
webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
|
||||
So(err, ShouldBeNil)
|
||||
webdavSec.NewKey("url", "webdavUrl")
|
||||
webdavSec.NewKey("username", "username")
|
||||
webdavSec.NewKey("password", "password")
|
||||
@@ -107,14 +110,14 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
setting.ImageUploadProvider = "gcs"
|
||||
|
||||
gcpSec, err := setting.Cfg.GetSection("external_image_storage.gcs")
|
||||
So(err, ShouldBeNil)
|
||||
gcpSec.NewKey("key_file", "/etc/secrets/project-79a52befa3f6.json")
|
||||
gcpSec.NewKey("bucket", "project-grafana-east")
|
||||
|
||||
uploader, err := NewImageUploader()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
original, ok := uploader.(*GCSUploader)
|
||||
|
||||
original, ok := uploader.(*GCSUploader)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(original.keyFile, ShouldEqual, "/etc/secrets/project-79a52befa3f6.json")
|
||||
So(original.bucket, ShouldEqual, "project-grafana-east")
|
||||
@@ -128,15 +131,15 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
|
||||
Convey("with container name", func() {
|
||||
azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob")
|
||||
So(err, ShouldBeNil)
|
||||
azureBlobSec.NewKey("account_name", "account_name")
|
||||
azureBlobSec.NewKey("account_key", "account_key")
|
||||
azureBlobSec.NewKey("container_name", "container_name")
|
||||
|
||||
uploader, err := NewImageUploader()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
original, ok := uploader.(*AzureBlobUploader)
|
||||
|
||||
original, ok := uploader.(*AzureBlobUploader)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(original.account_name, ShouldEqual, "account_name")
|
||||
So(original.account_key, ShouldEqual, "account_key")
|
||||
|
||||
@@ -41,14 +41,20 @@ func (u *WebdavUploader) Upload(ctx context.Context, pa string) (string, error)
|
||||
url.Path = path.Join(url.Path, filename)
|
||||
|
||||
imgData, err := ioutil.ReadFile(pa)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if u.username != "" {
|
||||
req.SetBasicAuth(u.username, u.password)
|
||||
}
|
||||
|
||||
res, err := netClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ func TestLogFile(t *testing.T) {
|
||||
|
||||
Convey("Logging should add lines", func() {
|
||||
err := fileLogWrite.WriteLine("test1\n")
|
||||
So(err, ShouldBeNil)
|
||||
err = fileLogWrite.WriteLine("test2\n")
|
||||
So(err, ShouldBeNil)
|
||||
err = fileLogWrite.WriteLine("test3\n")
|
||||
So(err, ShouldBeNil)
|
||||
So(fileLogWrite.maxlines_curlines, ShouldEqual, 3)
|
||||
|
||||
@@ -302,9 +302,11 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
|
||||
// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
|
||||
var groupSearchResult *ldap.SearchResult
|
||||
for _, groupSearchBase := range a.server.GroupSearchBaseDNs {
|
||||
filter_replace := getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult)
|
||||
var filter_replace string
|
||||
if a.server.GroupSearchFilterUserAttribute == "" {
|
||||
filter_replace = getLdapAttr(a.server.Attr.Username, searchResult)
|
||||
} else {
|
||||
filter_replace = getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult)
|
||||
}
|
||||
filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)
|
||||
|
||||
@@ -346,6 +348,9 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
|
||||
}
|
||||
|
||||
func getLdapAttrN(name string, result *ldap.SearchResult, n int) string {
|
||||
if name == "DN" {
|
||||
return result.Entries[0].DN
|
||||
}
|
||||
for _, attr := range result.Entries[n].Attributes {
|
||||
if attr.Name == name {
|
||||
if len(attr.Values) > 0 {
|
||||
|
||||
@@ -69,6 +69,7 @@ type DashboardAclInfoDTO struct {
|
||||
Slug string `json:"slug"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
Url string `json:"url"`
|
||||
Inherited bool `json:"inherited"`
|
||||
}
|
||||
|
||||
func (dto *DashboardAclInfoDTO) hasSameRoleAs(other *DashboardAclInfoDTO) bool {
|
||||
|
||||
@@ -3,13 +3,14 @@ package notifiers
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -133,6 +134,9 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval
|
||||
}
|
||||
|
||||
ruleUrl, err := evalContext.GetRuleUrl()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metrics := generateMetricsMessage(evalContext)
|
||||
message := generateImageCaption(evalContext, ruleUrl, metrics)
|
||||
|
||||
@@ -83,27 +83,6 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := make([]map[string]interface{}, 0)
|
||||
fieldLimitCount := 4
|
||||
for index, evt := range evalContext.EvalMatches {
|
||||
fields = append(fields, map[string]interface{}{
|
||||
"title": evt.Metric,
|
||||
"value": evt.Value,
|
||||
"short": true,
|
||||
})
|
||||
if index > fieldLimitCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if evalContext.Error != nil {
|
||||
fields = append(fields, map[string]interface{}{
|
||||
"title": "Error message",
|
||||
"value": evalContext.Error.Error(),
|
||||
"short": false,
|
||||
})
|
||||
}
|
||||
|
||||
messageType := evalContext.Rule.State
|
||||
if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog)
|
||||
messageType = AlertStateCritical
|
||||
|
||||
@@ -77,7 +77,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
|
||||
Text: "",
|
||||
NewState: string(evalContext.Rule.State),
|
||||
PrevState: string(evalContext.PrevAlertState),
|
||||
Epoch: time.Now().Unix(),
|
||||
Epoch: time.Now().UnixNano() / int64(time.Millisecond),
|
||||
Data: annotationData,
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ type ItemQuery struct {
|
||||
OrgId int64 `json:"orgId"`
|
||||
From int64 `json:"from"`
|
||||
To int64 `json:"to"`
|
||||
UserId int64 `json:"userId"`
|
||||
AlertId int64 `json:"alertId"`
|
||||
DashboardId int64 `json:"dashboardId"`
|
||||
PanelId int64 `json:"panelId"`
|
||||
@@ -63,6 +64,8 @@ type Item struct {
|
||||
PrevState string `json:"prevState"`
|
||||
NewState string `json:"newState"`
|
||||
Epoch int64 `json:"epoch"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
Tags []string `json:"tags"`
|
||||
Data *simplejson.Json `json:"data"`
|
||||
|
||||
@@ -80,6 +83,8 @@ type ItemDTO struct {
|
||||
UserId int64 `json:"userId"`
|
||||
NewState string `json:"newState"`
|
||||
PrevState string `json:"prevState"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
Time int64 `json:"time"`
|
||||
Text string `json:"text"`
|
||||
RegionId int64 `json:"regionId"`
|
||||
|
||||
@@ -154,12 +154,7 @@ func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.Permiss
|
||||
// validate overridden permissions to be higher
|
||||
for _, a := range acl {
|
||||
for _, existingPerm := range existingPermissions {
|
||||
// handle default permissions
|
||||
if existingPerm.DashboardId == -1 {
|
||||
existingPerm.DashboardId = g.dashId
|
||||
}
|
||||
|
||||
if a.DashboardId == existingPerm.DashboardId {
|
||||
if !existingPerm.Inherited {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -187,13 +182,6 @@ func (g *dashboardGuardianImpl) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, a := range query.Result {
|
||||
// handle default permissions
|
||||
if a.DashboardId == -1 {
|
||||
a.DashboardId = g.dashId
|
||||
}
|
||||
}
|
||||
|
||||
g.acl = query.Result
|
||||
return g.acl, nil
|
||||
}
|
||||
|
||||
@@ -217,13 +217,13 @@ func (sc *scenarioContext) parentFolderPermissionScenario(pt permissionType, per
|
||||
|
||||
switch pt {
|
||||
case USER:
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, UserId: userID, Permission: permission}}
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, UserId: userID, Permission: permission, Inherited: true}}
|
||||
case TEAM:
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, TeamId: teamID, Permission: permission}}
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, TeamId: teamID, Permission: permission, Inherited: true}}
|
||||
case EDITOR:
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &editorRole, Permission: permission}}
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &editorRole, Permission: permission, Inherited: true}}
|
||||
case VIEWER:
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &viewerRole, Permission: permission}}
|
||||
folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &viewerRole, Permission: permission, Inherited: true}}
|
||||
}
|
||||
|
||||
permissionScenario(fmt.Sprintf("and parent folder has %s with permission to %s", pt.String(), permission.String()), childDashboardID, sc, folderPermissionList, func(sc *scenarioContext) {
|
||||
@@ -649,6 +649,9 @@ func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShou
|
||||
}
|
||||
|
||||
_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
|
||||
if err != nil {
|
||||
sc.reportFailure(tc, nil, err)
|
||||
}
|
||||
sc.updatePermissions = permissionList
|
||||
ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
|
||||
|
||||
|
||||
@@ -235,7 +235,6 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, service dashboards.DashboardPr
|
||||
func resolveSymlink(fileinfo os.FileInfo, path string) (os.FileInfo, error) {
|
||||
checkFilepath, err := filepath.EvalSymlinks(path)
|
||||
if path != checkFilepath {
|
||||
path = checkFilepath
|
||||
fi, err := os.Lstat(checkFilepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
@@ -17,6 +18,12 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
tags := models.ParseTagPairs(item.Tags)
|
||||
item.Tags = models.JoinTagPairs(tags)
|
||||
item.Created = time.Now().UnixNano() / int64(time.Millisecond)
|
||||
item.Updated = item.Created
|
||||
if item.Epoch == 0 {
|
||||
item.Epoch = item.Created
|
||||
}
|
||||
|
||||
if _, err := sess.Table("annotation").Insert(item); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,6 +86,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
|
||||
return errors.New("Annotation not found")
|
||||
}
|
||||
|
||||
existing.Updated = time.Now().UnixNano() / int64(time.Millisecond)
|
||||
existing.Epoch = item.Epoch
|
||||
existing.Text = item.Text
|
||||
if item.RegionId != 0 {
|
||||
@@ -102,7 +110,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
|
||||
|
||||
existing.Tags = item.Tags
|
||||
|
||||
_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "tags").Update(existing)
|
||||
_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "updated", "tags").Update(existing)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -124,6 +132,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
|
||||
annotation.text,
|
||||
annotation.tags,
|
||||
annotation.data,
|
||||
annotation.created,
|
||||
annotation.updated,
|
||||
usr.email,
|
||||
usr.login,
|
||||
alert.name as alert_name
|
||||
@@ -161,6 +171,11 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
|
||||
params = append(params, query.PanelId)
|
||||
}
|
||||
|
||||
if query.UserId != 0 {
|
||||
sql.WriteString(` AND annotation.user_id = ?`)
|
||||
params = append(params, query.UserId)
|
||||
}
|
||||
|
||||
if query.From > 0 && query.To > 0 {
|
||||
sql.WriteString(` AND annotation.epoch BETWEEN ? AND ?`)
|
||||
params = append(params, query.From, query.To)
|
||||
@@ -168,6 +183,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
|
||||
|
||||
if query.Type == "alert" {
|
||||
sql.WriteString(` AND annotation.alert_id > 0`)
|
||||
} else if query.Type == "annotation" {
|
||||
sql.WriteString(` AND annotation.alert_id = 0`)
|
||||
}
|
||||
|
||||
if len(query.Tags) > 0 {
|
||||
|
||||
@@ -79,6 +79,12 @@ func TestAnnotations(t *testing.T) {
|
||||
Convey("Can read tags", func() {
|
||||
So(items[0].Tags, ShouldResemble, []string{"outage", "error", "type:outage", "server:server-1"})
|
||||
})
|
||||
|
||||
Convey("Has created and updated values", func() {
|
||||
So(items[0].Created, ShouldBeGreaterThan, 0)
|
||||
So(items[0].Updated, ShouldBeGreaterThan, 0)
|
||||
So(items[0].Updated, ShouldEqual, items[0].Created)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Can query for annotation by id", func() {
|
||||
@@ -231,6 +237,10 @@ func TestAnnotations(t *testing.T) {
|
||||
So(items[0].Tags, ShouldResemble, []string{"newtag1", "newtag2"})
|
||||
So(items[0].Text, ShouldEqual, "something new")
|
||||
})
|
||||
|
||||
Convey("Updated time has increased", func() {
|
||||
So(items[0].Updated, ShouldBeGreaterThan, items[0].Created)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Can delete annotation", func() {
|
||||
@@ -246,6 +256,7 @@ func TestAnnotations(t *testing.T) {
|
||||
annotationId := items[0].Id
|
||||
|
||||
err = repo.Delete(&annotations.DeleteParams{Id: annotationId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
items, err = repo.Find(query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -77,7 +77,7 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
|
||||
parentVersion := dash.Version
|
||||
affectedRows := int64(0)
|
||||
var affectedRows int64
|
||||
var err error
|
||||
|
||||
if dash.Id == 0 {
|
||||
|
||||
@@ -67,7 +67,8 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
'' as title,
|
||||
'' as slug,
|
||||
'' as uid,` +
|
||||
falseStr + ` AS is_folder
|
||||
falseStr + ` AS is_folder,` +
|
||||
falseStr + ` AS inherited
|
||||
FROM dashboard_acl as da
|
||||
WHERE da.dashboard_id = -1`
|
||||
query.Result = make([]*m.DashboardAclInfoDTO, 0)
|
||||
@@ -94,7 +95,8 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
d.title,
|
||||
d.slug,
|
||||
d.uid,
|
||||
d.is_folder
|
||||
d.is_folder,
|
||||
CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
|
||||
FROM dashboard as d
|
||||
LEFT JOIN dashboard folder on folder.id = d.folder_id
|
||||
LEFT JOIN dashboard_acl AS da ON
|
||||
|
||||
@@ -26,6 +26,22 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder with default permissions", func() {
|
||||
Convey("When reading folder acl should include default acl", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
defaultPermissionsId := -1
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[0].Inherited, ShouldBeFalse)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
So(query.Result[1].Inherited, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("When reading dashboard acl should include acl for parent folder", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
|
||||
|
||||
@@ -36,8 +52,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
defaultPermissionsId := -1
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[0].Inherited, ShouldBeTrue)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
So(query.Result[1].Inherited, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -94,7 +112,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(query.Result[0].Inherited, ShouldBeTrue)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, childDash.Id)
|
||||
So(query.Result[1].Inherited, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -118,9 +138,12 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[0].Inherited, ShouldBeTrue)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
So(query.Result[1].Inherited, ShouldBeTrue)
|
||||
So(query.Result[2].DashboardId, ShouldEqual, childDash.Id)
|
||||
So(query.Result[2].Inherited, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -131,6 +154,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
@@ -209,8 +233,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
defaultPermissionsId := -1
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[0].Inherited, ShouldBeFalse)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
So(query.Result[1].Inherited, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -90,4 +90,29 @@ func addAnnotationMig(mg *Migrator) {
|
||||
Sqlite(updateTextFieldSql).
|
||||
Postgres(updateTextFieldSql).
|
||||
Mysql(updateTextFieldSql))
|
||||
|
||||
//
|
||||
// Add a 'created' & 'updated' column
|
||||
//
|
||||
mg.AddMigration("Add created time to annotation table", NewAddColumnMigration(table, &Column{
|
||||
Name: "created", Type: DB_BigInt, Nullable: true, Default: "0",
|
||||
}))
|
||||
mg.AddMigration("Add updated time to annotation table", NewAddColumnMigration(table, &Column{
|
||||
Name: "updated", Type: DB_BigInt, Nullable: true, Default: "0",
|
||||
}))
|
||||
mg.AddMigration("Add index for created in annotation table", NewAddIndexMigration(table, &Index{
|
||||
Cols: []string{"org_id", "created"}, Type: IndexType,
|
||||
}))
|
||||
mg.AddMigration("Add index for updated in annotation table", NewAddIndexMigration(table, &Index{
|
||||
Cols: []string{"org_id", "updated"}, Type: IndexType,
|
||||
}))
|
||||
|
||||
//
|
||||
// Convert epoch saved as seconds to miliseconds
|
||||
//
|
||||
updateEpochSql := "UPDATE annotation SET epoch = (epoch*1000) where epoch < 9999999999"
|
||||
mg.AddMigration("Convert existing annotations from seconds to milliseconds", new(RawSqlMigration).
|
||||
Sqlite(updateEpochSql).
|
||||
Postgres(updateEpochSql).
|
||||
Mysql(updateEpochSql))
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestMigrations(t *testing.T) {
|
||||
|
||||
sqlutil.CleanDB(x)
|
||||
|
||||
has, err := x.SQL(sql).Get(&r)
|
||||
_, err = x.SQL(sql).Get(&r)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
mg := NewMigrator(x)
|
||||
@@ -36,7 +36,7 @@ func TestMigrations(t *testing.T) {
|
||||
err = mg.Start()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
has, err = x.SQL(sql).Get(&r)
|
||||
has, err := x.SQL(sql).Get(&r)
|
||||
So(err, ShouldBeNil)
|
||||
So(has, ShouldBeTrue)
|
||||
expectedMigrations := mg.MigrationsCount() - 2 //we currently skip to migrations. We should rewrite skipped migrations to write in the log as well. until then we have to keep this
|
||||
|
||||
@@ -22,6 +22,9 @@ func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
|
||||
}
|
||||
|
||||
_, err := x.Insert(&playlist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
playlistItems := make([]m.PlaylistItem, 0)
|
||||
for _, item := range cmd.Items {
|
||||
|
||||
@@ -48,6 +48,9 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
|
||||
var pluginSetting m.PluginSetting
|
||||
|
||||
exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.UseBool("enabled")
|
||||
sess.UseBool("pinned")
|
||||
if !exists {
|
||||
|
||||
@@ -72,6 +72,9 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error {
|
||||
|
||||
var prefs m.Preferences
|
||||
exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
prefs = m.Preferences{
|
||||
|
||||
@@ -74,6 +74,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
Convey("Should be able to return all teams a user is member of", func() {
|
||||
groupId := group2.Result.Id
|
||||
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
|
||||
err = GetTeamsByUser(query)
|
||||
@@ -103,7 +104,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
|
||||
So(err, ShouldBeNil)
|
||||
err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
||||
@@ -168,11 +168,9 @@ func GetUserByLogin(query *m.GetUserByLoginQuery) error {
|
||||
return m.ErrUserNotFound
|
||||
}
|
||||
|
||||
user := new(m.User)
|
||||
|
||||
// Try and find the user by login first.
|
||||
// It's not sufficient to assume that a LoginOrEmail with an "@" is an email.
|
||||
user = &m.User{Login: query.LoginOrEmail}
|
||||
user := &m.User{Login: query.LoginOrEmail}
|
||||
has, err := x.Get(user)
|
||||
|
||||
if err != nil {
|
||||
@@ -202,9 +200,7 @@ func GetUserByEmail(query *m.GetUserByEmailQuery) error {
|
||||
return m.ErrUserNotFound
|
||||
}
|
||||
|
||||
user := new(m.User)
|
||||
|
||||
user = &m.User{Email: query.Email}
|
||||
user := &m.User{Email: query.Email}
|
||||
has, err := x.Get(user)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestUserAuth(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
_, err = x.Exec("DELETE FROM org WHERE 1=1")
|
||||
So(err, ShouldBeNil)
|
||||
_, err = x.Exec("DELETE FROM user WHERE 1=1")
|
||||
_, err = x.Exec("DELETE FROM " + dialect.Quote("user") + " WHERE 1=1")
|
||||
So(err, ShouldBeNil)
|
||||
_, err = x.Exec("DELETE FROM user_auth WHERE 1=1")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -117,7 +117,7 @@ func TestUserAuth(t *testing.T) {
|
||||
So(query.Result.Login, ShouldEqual, "loginuser1")
|
||||
|
||||
// remove user
|
||||
_, err = x.Exec("DELETE FROM user WHERE id=?", query.Result.Id)
|
||||
_, err = x.Exec("DELETE FROM "+dialect.Quote("user")+" WHERE id=?", query.Result.Id)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// get via user_auth for deleted user
|
||||
|
||||
@@ -271,7 +271,7 @@ func parseQuery(model *simplejson.Json) (*CloudWatchQuery, error) {
|
||||
}
|
||||
}
|
||||
|
||||
period := 300
|
||||
var period int
|
||||
if regexp.MustCompile(`^\d+$`).Match([]byte(p)) {
|
||||
period, err = strconv.Atoi(p)
|
||||
if err != nil {
|
||||
|
||||
@@ -40,6 +40,9 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data
|
||||
}
|
||||
|
||||
parsedInterval, err := tsdb.GetIntervalFrom(dsInfo, model, time.Millisecond*1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Query{
|
||||
Measurement: measurement,
|
||||
|
||||
@@ -62,9 +62,8 @@ func (query *Query) renderTags() []string {
|
||||
}
|
||||
}
|
||||
|
||||
textValue := ""
|
||||
|
||||
// quote value unless regex or number
|
||||
var textValue string
|
||||
if tag.Operator == "=~" || tag.Operator == "!~" {
|
||||
textValue = tag.Value
|
||||
} else if tag.Operator == "<" || tag.Operator == ">" {
|
||||
@@ -107,7 +106,7 @@ func (query *Query) renderSelectors(queryContext *tsdb.TsdbQuery) string {
|
||||
}
|
||||
|
||||
func (query *Query) renderMeasurement() string {
|
||||
policy := ""
|
||||
var policy string
|
||||
if query.Policy == "" || query.Policy == "default" {
|
||||
policy = ""
|
||||
} else {
|
||||
|
||||
@@ -83,6 +83,10 @@ func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsd
|
||||
u.Path = path.Join(u.Path, "api/query")
|
||||
|
||||
postData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
plog.Info("Failed marshalling data", "error", err)
|
||||
return nil, fmt.Errorf("Failed to create request. error: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(postData)))
|
||||
if err != nil {
|
||||
|
||||
@@ -41,7 +41,7 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde
|
||||
permissionChanged(itemIndex, permissionOption.value, permissionOption.label);
|
||||
};
|
||||
|
||||
const inheritedFromRoot = item.dashboardId === -1 && folderInfo && folderInfo.id === 0;
|
||||
const inheritedFromRoot = item.dashboardId === -1 && !item.inherited;
|
||||
|
||||
return (
|
||||
<tr className={setClassNameHelper(item.inherited)}>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { PanelModel } from '../panel_model';
|
||||
import { PanelContainer } from './PanelContainer';
|
||||
import templateSrv from 'app/features/templating/template_srv';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export interface DashboardRowProps {
|
||||
panel: PanelModel;
|
||||
@@ -95,7 +94,7 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
|
||||
{title}
|
||||
<span className="dashboard-row__panel_count">({hiddenPanels} hidden panels)</span>
|
||||
</a>
|
||||
{config.bootData.user.orgRole !== 'Viewer' && (
|
||||
{this.dashboard.meta.canEdit === true && (
|
||||
<div className="dashboard-row__actions">
|
||||
<a className="pointer" onClick={this.openSettings}>
|
||||
<i className="fa fa-cog" />
|
||||
|
||||
@@ -2,17 +2,15 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { DashboardRow } from '../dashgrid/DashboardRow';
|
||||
import { PanelModel } from '../panel_model';
|
||||
import config from '../../../core/config';
|
||||
|
||||
describe('DashboardRow', () => {
|
||||
let wrapper, panel, getPanelContainer, dashboardMock;
|
||||
|
||||
beforeEach(() => {
|
||||
dashboardMock = { toggleRow: jest.fn() };
|
||||
|
||||
config.bootData = {
|
||||
user: {
|
||||
orgRole: 'Admin',
|
||||
dashboardMock = {
|
||||
toggleRow: jest.fn(),
|
||||
meta: {
|
||||
canEdit: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -41,8 +39,8 @@ describe('DashboardRow', () => {
|
||||
expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should have zero actions as viewer', () => {
|
||||
config.bootData.user.orgRole = 'Viewer';
|
||||
it('should have zero actions when cannot edit', () => {
|
||||
dashboardMock.meta.canEdit = false;
|
||||
panel = new PanelModel({ collapsed: false });
|
||||
wrapper = shallow(<DashboardRow panel={panel} getPanelContainer={getPanelContainer} />);
|
||||
expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(0);
|
||||
|
||||
@@ -453,7 +453,13 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
|
||||
};
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this.metricFindQuery('*').then(function() {
|
||||
let query = {
|
||||
panelId: 3,
|
||||
rangeRaw: { from: 'now-1h', to: 'now' },
|
||||
targets: [{ target: 'constantLine(100)' }],
|
||||
maxDataPoints: 300,
|
||||
};
|
||||
return this.query(query).then(function() {
|
||||
return { status: 'success', message: 'Data source is working' };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('PermissionsStore', () => {
|
||||
permissionName: 'View',
|
||||
teamId: 1,
|
||||
team: 'MyTestTeam',
|
||||
inherited: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
|
||||
@@ -224,8 +224,6 @@ const prepareServerResponse = (response, dashboardId: number, isFolder: boolean,
|
||||
};
|
||||
|
||||
const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boolean) => {
|
||||
item.inherited = !isFolder && !isInRoot && dashboardId !== item.dashboardId;
|
||||
|
||||
item.sortRank = 0;
|
||||
if (item.userId > 0) {
|
||||
item.name = item.userLogin;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%_signature gpg
|
||||
%_gpg_path /home/ubuntu/.gnupg
|
||||
%_gpg_path /root/.gnupg
|
||||
%_gpg_name Grafana
|
||||
%_gpgbin /usr/bin/gpg
|
||||
|
||||
Reference in New Issue
Block a user