mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
new endpoint to post/get trimmed dashboard json (#33465)
* new endpoint to post/get trimed dashboard json * add isdisabled check in dashboard.go
This commit is contained in:
parent
076e2ce06a
commit
748778fff0
@ -80,6 +80,7 @@ Family: scuemata.#Family & {
|
|||||||
// synthetic Family to represent them in Go, for ease of generating
|
// synthetic Family to represent them in Go, for ease of generating
|
||||||
// e.g. JSON Schema.
|
// e.g. JSON Schema.
|
||||||
#Panel: {
|
#Panel: {
|
||||||
|
...
|
||||||
// The panel plugin type id.
|
// The panel plugin type id.
|
||||||
type: !=""
|
type: !=""
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ Family: scuemata.#Family & {
|
|||||||
// _pv: { maj: int, min: int }
|
// _pv: { maj: int, min: int }
|
||||||
// The major and minor versions of the panel plugin for this schema.
|
// The major and minor versions of the panel plugin for this schema.
|
||||||
// TODO 2-tuple list instead of struct?
|
// TODO 2-tuple list instead of struct?
|
||||||
panelSchema: { maj: number, min: number }
|
panelSchema?: { maj: number, min: number }
|
||||||
|
|
||||||
// Panel title.
|
// Panel title.
|
||||||
title?: string
|
title?: string
|
||||||
@ -128,7 +129,7 @@ Family: scuemata.#Family & {
|
|||||||
// with types derived from plugins in the Instance variant.
|
// with types derived from plugins in the Instance variant.
|
||||||
// When working directly from CUE, importers can extend this
|
// When working directly from CUE, importers can extend this
|
||||||
// type directly to achieve the same effect.
|
// type directly to achieve the same effect.
|
||||||
targets?: [...{}]
|
targets?: [...{...}]
|
||||||
|
|
||||||
// The values depend on panel type
|
// The values depend on panel type
|
||||||
options: {...}
|
options: {...}
|
||||||
|
3
go.mod
3
go.mod
@ -13,7 +13,7 @@ replace k8s.io/client-go => k8s.io/client-go v0.18.8
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/storage v1.14.0
|
cloud.google.com/go/storage v1.14.0
|
||||||
cuelang.org/go v0.3.0-beta.6
|
cuelang.org/go v0.3.2
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
||||||
github.com/aws/aws-sdk-go v1.38.17
|
github.com/aws/aws-sdk-go v1.38.17
|
||||||
@ -95,6 +95,7 @@ require (
|
|||||||
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
|
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
||||||
|
golang.org/x/tools v0.1.0 // indirect
|
||||||
gonum.org/v1/gonum v0.9.1
|
gonum.org/v1/gonum v0.9.1
|
||||||
google.golang.org/api v0.45.0
|
google.golang.org/api v0.45.0
|
||||||
google.golang.org/grpc v1.37.0
|
google.golang.org/grpc v1.37.0
|
||||||
|
4
go.sum
4
go.sum
@ -53,6 +53,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeS
|
|||||||
contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys=
|
contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys=
|
||||||
cuelang.org/go v0.3.0-beta.6 h1:od1S/Hbl2S45TLSONl95X3O4TXN1za6CUSD13bTxCVk=
|
cuelang.org/go v0.3.0-beta.6 h1:od1S/Hbl2S45TLSONl95X3O4TXN1za6CUSD13bTxCVk=
|
||||||
cuelang.org/go v0.3.0-beta.6/go.mod h1:Ikvs157igkGV5gFUdYSFa+lWp/CDteVhubPTXyvPRtA=
|
cuelang.org/go v0.3.0-beta.6/go.mod h1:Ikvs157igkGV5gFUdYSFa+lWp/CDteVhubPTXyvPRtA=
|
||||||
|
cuelang.org/go v0.3.2 h1:/Am5yFDwqnaEi+g942OPM1M4/qtfVSm49wtkQbeh5Z4=
|
||||||
|
cuelang.org/go v0.3.2/go.mod h1:jvMO35Q4D2D3m2ujAmKESICaYkjMbu5+D+2zIGuWTpQ=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||||
@ -1333,6 +1335,7 @@ github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
|
|||||||
github.com/pierrec/lz4/v4 v4.0.2-0.20200813132121-22f5d580d5c4/go.mod h1:vvUajMAuienWCEdMnA5Zb5mp0VIa9M8VvKcVEOkoAh8=
|
github.com/pierrec/lz4/v4 v4.0.2-0.20200813132121-22f5d580d5c4/go.mod h1:vvUajMAuienWCEdMnA5Zb5mp0VIa9M8VvKcVEOkoAh8=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@ -1450,6 +1453,7 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369 h1:wdCVGtPadWC/ZuuLC7Hv58VQ5UF7V98ewE71n5mJfrM=
|
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369 h1:wdCVGtPadWC/ZuuLC7Hv58VQ5UF7V98ewE71n5mJfrM=
|
||||||
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
|
@ -324,6 +324,7 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
dashboardRoute.Delete("/db/:slug", routing.Wrap(hs.DeleteDashboardBySlug))
|
dashboardRoute.Delete("/db/:slug", routing.Wrap(hs.DeleteDashboardBySlug))
|
||||||
|
|
||||||
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), routing.Wrap(CalculateDashboardDiff))
|
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), routing.Wrap(CalculateDashboardDiff))
|
||||||
|
dashboardRoute.Post("/trim", bind(models.TrimDashboardCommand{}), routing.Wrap(hs.TrimDashboard))
|
||||||
|
|
||||||
dashboardRoute.Post("/db", bind(models.SaveDashboardCommand{}), routing.Wrap(hs.PostDashboard))
|
dashboardRoute.Post("/db", bind(models.SaveDashboardCommand{}), routing.Wrap(hs.PostDashboard))
|
||||||
dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard))
|
dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard))
|
||||||
|
@ -46,9 +46,32 @@ func dashboardGuardianResponse(err error) response.Response {
|
|||||||
return response.Error(403, "Access denied to this dashboard", nil)
|
return response.Error(403, "Access denied to this dashboard", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hs *HTTPServer) TrimDashboard(c *models.ReqContext, cmd models.TrimDashboardCommand) response.Response {
|
||||||
|
var err error
|
||||||
|
dash := cmd.Dashboard
|
||||||
|
meta := cmd.Meta
|
||||||
|
|
||||||
|
trimedResult := *dash
|
||||||
|
if !hs.LoadSchemaService.IsDisabled() {
|
||||||
|
trimedResult, err = hs.LoadSchemaService.DashboardTrimDefaults(*dash)
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(500, "Error while trim default value from dashboard json", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := dtos.TrimDashboardFullWithMeta{
|
||||||
|
Dashboard: &trimedResult,
|
||||||
|
Meta: meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.TimeRequest(metrics.MApiDashboardGet)
|
||||||
|
return response.JSON(200, dto)
|
||||||
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||||
slug := c.Params(":slug")
|
slug := c.Params(":slug")
|
||||||
uid := c.Params(":uid")
|
uid := c.Params(":uid")
|
||||||
|
trimDefaults := c.QueryBoolWithDefault("trimdefaults", false)
|
||||||
dash, rsp := getDashboardHelper(c.OrgId, slug, 0, uid)
|
dash, rsp := getDashboardHelper(c.OrgId, slug, 0, uid)
|
||||||
if rsp != nil {
|
if rsp != nil {
|
||||||
return rsp
|
return rsp
|
||||||
@ -154,6 +177,14 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
|||||||
return response.Error(500, "Error while loading library panels", err)
|
return response.Error(500, "Error while loading library panels", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var trimedJson simplejson.Json
|
||||||
|
if trimDefaults && !hs.LoadSchemaService.IsDisabled() {
|
||||||
|
trimedJson, err = hs.LoadSchemaService.DashboardTrimDefaults(*dash.Data)
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(500, "Error while trim default value from dashboard json", err)
|
||||||
|
}
|
||||||
|
dash.Data = &trimedJson
|
||||||
|
}
|
||||||
|
|
||||||
dto := dtos.DashboardFullWithMeta{
|
dto := dtos.DashboardFullWithMeta{
|
||||||
Dashboard: dash.Data,
|
Dashboard: dash.Data,
|
||||||
@ -254,11 +285,17 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboardCommand) response.Response {
|
func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboardCommand) response.Response {
|
||||||
|
var err error
|
||||||
cmd.OrgId = c.OrgId
|
cmd.OrgId = c.OrgId
|
||||||
cmd.UserId = c.UserId
|
cmd.UserId = c.UserId
|
||||||
|
trimDefaults := c.QueryBoolWithDefault("trimdefaults", false)
|
||||||
|
if trimDefaults && !hs.LoadSchemaService.IsDisabled() {
|
||||||
|
cmd.Dashboard, err = hs.LoadSchemaService.DashboardApplyDefaults(cmd.Dashboard)
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(500, "Error while applying default value to the dashboard json", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
dash := cmd.GetDashboardModel()
|
dash := cmd.GetDashboardModel()
|
||||||
|
|
||||||
newDashboard := dash.Id == 0 && dash.Uid == ""
|
newDashboard := dash.Id == 0 && dash.Uid == ""
|
||||||
if newDashboard {
|
if newDashboard {
|
||||||
limitReached, err := hs.QuotaService.QuotaReached(c, "dashboard")
|
limitReached, err := hs.QuotaService.QuotaReached(c, "dashboard")
|
||||||
|
@ -37,6 +37,11 @@ type DashboardFullWithMeta struct {
|
|||||||
Dashboard *simplejson.Json `json:"dashboard"`
|
Dashboard *simplejson.Json `json:"dashboard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrimDashboardFullWithMeta struct {
|
||||||
|
Meta *simplejson.Json `json:"meta"`
|
||||||
|
Dashboard *simplejson.Json `json:"dashboard"`
|
||||||
|
}
|
||||||
|
|
||||||
type DashboardRedirect struct {
|
type DashboardRedirect struct {
|
||||||
RedirectUri string `json:"redirectUri"`
|
RedirectUri string `json:"redirectUri"`
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
"github.com/grafana/grafana/pkg/services/rendering"
|
||||||
|
"github.com/grafana/grafana/pkg/services/schemaloader"
|
||||||
"github.com/grafana/grafana/pkg/services/search"
|
"github.com/grafana/grafana/pkg/services/search"
|
||||||
"github.com/grafana/grafana/pkg/services/shorturls"
|
"github.com/grafana/grafana/pkg/services/shorturls"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@ -98,6 +99,7 @@ type HTTPServer struct {
|
|||||||
DataService *tsdb.Service `inject:""`
|
DataService *tsdb.Service `inject:""`
|
||||||
PluginDashboardService *plugindashboards.Service `inject:""`
|
PluginDashboardService *plugindashboards.Service `inject:""`
|
||||||
AlertEngine *alerting.AlertEngine `inject:""`
|
AlertEngine *alerting.AlertEngine `inject:""`
|
||||||
|
LoadSchemaService *schemaloader.SchemaLoaderService `inject:""`
|
||||||
Listener net.Listener
|
Listener net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,6 +346,12 @@ type SaveDashboardCommand struct {
|
|||||||
Result *Dashboard
|
Result *Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrimDashboardCommand struct {
|
||||||
|
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
|
||||||
|
Meta *simplejson.Json `json:"meta"`
|
||||||
|
Result *Dashboard
|
||||||
|
}
|
||||||
|
|
||||||
type DashboardProvisioning struct {
|
type DashboardProvisioning struct {
|
||||||
Id int64
|
Id int64
|
||||||
DashboardId int64
|
DashboardId int64
|
||||||
|
116
pkg/schema/load/applydefault_test.go
Normal file
116
pkg/schema/load/applydefault_test.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package load
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"cuelang.org/go/cue"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/grafana/grafana/pkg/schema"
|
||||||
|
"golang.org/x/tools/txtar"
|
||||||
|
)
|
||||||
|
|
||||||
|
var CasesDir = filepath.Join("testdata", "artifacts", "dashboards", "trimdefault")
|
||||||
|
|
||||||
|
type Case struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
CUE string
|
||||||
|
Full string
|
||||||
|
Trimed string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
cases, err := loadCases(CasesDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.Name+" apply default value", func(t *testing.T) {
|
||||||
|
var r cue.Runtime
|
||||||
|
scmInstance, err := r.Compile(c.Name+".cue", c.CUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
inputResource := schema.Resource{Value: c.Trimed}
|
||||||
|
scm := genericVersionedSchema{actual: scmInstance.Value()}
|
||||||
|
out, err := scm.ApplyDefaults(inputResource)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := []byte(out.Value.(string))
|
||||||
|
|
||||||
|
if s := cmp.Diff(string(b), c.Full); s != "" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Skip()
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.Name+" trim default value", func(t *testing.T) {
|
||||||
|
var r cue.Runtime
|
||||||
|
scmInstance, err := r.Compile(c.Name+".cue", c.CUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
inputResource := schema.Resource{Value: c.Full}
|
||||||
|
scm := genericVersionedSchema{actual: scmInstance.Value()}
|
||||||
|
out, err := scm.TrimDefaults(inputResource)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := []byte(out.Value.(string))
|
||||||
|
if s := cmp.Diff(string(b), c.Trimed); s != "" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCases(dir string) ([]Case, error) {
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cases []Case
|
||||||
|
|
||||||
|
for _, fi := range files {
|
||||||
|
file := filepath.Join(dir, fi.Name())
|
||||||
|
a, err := txtar.ParseFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.Files) != 3 {
|
||||||
|
return nil, fmt.Errorf("Malformed test case '%s': Must contain exactly three files (CUE, Full and Trimed), but has %d", file, len(a.Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
fullBuffer := new(bytes.Buffer)
|
||||||
|
fullJson := a.Files[1].Data
|
||||||
|
if err := json.Compact(fullBuffer, fullJson); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trimBuffer := new(bytes.Buffer)
|
||||||
|
trimedJson := a.Files[2].Data
|
||||||
|
if err := json.Compact(trimBuffer, trimedJson); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cases = append(cases, Case{
|
||||||
|
Name: fi.Name(),
|
||||||
|
CUE: string(a.Files[0].Data),
|
||||||
|
Full: fullBuffer.String(),
|
||||||
|
Trimed: trimBuffer.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return cases, nil
|
||||||
|
}
|
@ -84,7 +84,8 @@ func DistDashboardFamily(p BaseLoadPaths) (schema.VersionedCueSchema, error) {
|
|||||||
#Panel: obj
|
#Panel: obj
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
filled := dummy.Value().Fill(dj, "obj")
|
|
||||||
|
filled := dummy.Value().FillPath(cue.MakePath(cue.Str("obj")), dj)
|
||||||
ddj := filled.LookupPath(cue.MakePath(cue.Str("dummy")))
|
ddj := filled.LookupPath(cue.MakePath(cue.Str("dummy")))
|
||||||
|
|
||||||
var first, prev *compositeDashboardSchema
|
var first, prev *compositeDashboardSchema
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package load
|
package load
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"cuelang.org/go/cue"
|
"cuelang.org/go/cue"
|
||||||
"cuelang.org/go/cue/load"
|
"cuelang.org/go/cue/load"
|
||||||
|
cuejson "cuelang.org/go/pkg/encoding/json"
|
||||||
"github.com/grafana/grafana/pkg/schema"
|
"github.com/grafana/grafana/pkg/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,15 +110,119 @@ func (gvs *genericVersionedSchema) Validate(r schema.Resource) error {
|
|||||||
// ApplyDefaults returns a new, concrete copy of the Resource with all paths
|
// ApplyDefaults returns a new, concrete copy of the Resource with all paths
|
||||||
// that are 1) missing in the Resource AND 2) specified by the schema,
|
// that are 1) missing in the Resource AND 2) specified by the schema,
|
||||||
// filled with default values specified by the schema.
|
// filled with default values specified by the schema.
|
||||||
func (gvs *genericVersionedSchema) ApplyDefaults(_ schema.Resource) (schema.Resource, error) {
|
func (gvs *genericVersionedSchema) ApplyDefaults(r schema.Resource) (schema.Resource, error) {
|
||||||
panic("not implemented") // TODO: Implement
|
rv, err := rt.Compile("resource", r.Value)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
rvUnified := rv.Value().Unify(gvs.CUE())
|
||||||
|
re, err := convertCUEValueToString(rvUnified)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
return schema.Resource{Value: re}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCUEValueToString(inputCUE cue.Value) (string, error) {
|
||||||
|
re, err := cuejson.Marshal(inputCUE)
|
||||||
|
if err != nil {
|
||||||
|
return re, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []byte(re)
|
||||||
|
result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1)
|
||||||
|
result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1)
|
||||||
|
result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1)
|
||||||
|
return string(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimDefaults returns a new, concrete copy of the Resource where all paths
|
// TrimDefaults returns a new, concrete copy of the Resource where all paths
|
||||||
// in the where the values at those paths are the same as the default value
|
// in the where the values at those paths are the same as the default value
|
||||||
// given in the schema.
|
// given in the schema.
|
||||||
func (gvs *genericVersionedSchema) TrimDefaults(_ schema.Resource) (schema.Resource, error) {
|
func (gvs *genericVersionedSchema) TrimDefaults(r schema.Resource) (schema.Resource, error) {
|
||||||
panic("not implemented") // TODO: Implement
|
rvInstance, err := rt.Compile("resource", r.Value)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
rv, _, err := removeDefaultHelper(gvs.CUE(), rvInstance.Value())
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
re, err := convertCUEValueToString(rv)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
return schema.Resource{Value: re}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool, error) {
|
||||||
|
// Since for now, panel definition is open validation,
|
||||||
|
// we need to loop on the input CUE for trimming
|
||||||
|
rvInstance, err := rt.Compile("resource", []byte{})
|
||||||
|
if err != nil {
|
||||||
|
return input, false, err
|
||||||
|
}
|
||||||
|
rv := rvInstance.Value()
|
||||||
|
|
||||||
|
switch inputdef.IncompleteKind() {
|
||||||
|
case cue.StructKind:
|
||||||
|
// Get all fields including optional fields
|
||||||
|
iter, err := inputdef.Fields(cue.Optional(true))
|
||||||
|
if err != nil {
|
||||||
|
return rv, false, err
|
||||||
|
}
|
||||||
|
for iter.Next() {
|
||||||
|
lable, _ := iter.Value().Label()
|
||||||
|
|
||||||
|
lv := input.LookupPath(cue.MakePath(cue.Str(lable)))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lv.Exists() {
|
||||||
|
re, isEqual, err := removeDefaultHelper(iter.Value(), lv)
|
||||||
|
if err == nil && !isEqual {
|
||||||
|
rv = rv.FillPath(cue.MakePath(cue.Str(lable)), re)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv, false, nil
|
||||||
|
case cue.ListKind:
|
||||||
|
val, _ := inputdef.Default()
|
||||||
|
err1 := input.Subsume(val)
|
||||||
|
err2 := val.Subsume(input)
|
||||||
|
if val.IsConcrete() && err1 == nil && err2 == nil {
|
||||||
|
return rv, true, nil
|
||||||
|
}
|
||||||
|
ele := inputdef.LookupPath(cue.MakePath(cue.AnyIndex))
|
||||||
|
fmt.Println("xxxxxxxxxxxxxxxxxxxxx ", ele.IncompleteKind())
|
||||||
|
if ele.IncompleteKind() == cue.BottomKind {
|
||||||
|
return rv, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := input.List()
|
||||||
|
if err != nil {
|
||||||
|
return rv, true, nil
|
||||||
|
}
|
||||||
|
index := 0
|
||||||
|
for iter.Next() {
|
||||||
|
re, isEqual, err := removeDefaultHelper(ele, iter.Value())
|
||||||
|
if err == nil && !isEqual {
|
||||||
|
rv = rv.FillPath(cue.MakePath(cue.Index(index)), re)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rv = rv.FillPath(cue.MakePath(cue.Str(lable)), rv)
|
||||||
|
return rv, false, nil
|
||||||
|
default:
|
||||||
|
val, _ := inputdef.Default()
|
||||||
|
err1 := input.Subsume(val)
|
||||||
|
err2 := val.Subsume(input)
|
||||||
|
if val.IsConcrete() && err1 == nil && err2 == nil {
|
||||||
|
return input, true, nil
|
||||||
|
}
|
||||||
|
return input, false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CUE returns the cue.Value representing the actual schema.
|
// CUE returns the cue.Value representing the actual schema.
|
||||||
@ -164,7 +272,7 @@ var terminalMigrationFunc = func(x interface{}) (cue.Value, schema.VersionedCueS
|
|||||||
// earlier schema) with a later schema.
|
// earlier schema) with a later schema.
|
||||||
func implicitMigration(v cue.Value, next schema.VersionedCueSchema) migrationFunc {
|
func implicitMigration(v cue.Value, next schema.VersionedCueSchema) migrationFunc {
|
||||||
return func(x interface{}) (cue.Value, schema.VersionedCueSchema, error) {
|
return func(x interface{}) (cue.Value, schema.VersionedCueSchema, error) {
|
||||||
w := v.Fill(x)
|
w := v.FillPath(cue.Path{}, x)
|
||||||
// TODO is it possible that migration would be successful, but there
|
// TODO is it possible that migration would be successful, but there
|
||||||
// still exists some error here? Need to better understand internal CUE
|
// still exists some error here? Need to better understand internal CUE
|
||||||
// erroring rules? seems like incomplete cue.Value may always an Err()?
|
// erroring rules? seems like incomplete cue.Value may always an Err()?
|
||||||
|
@ -51,7 +51,7 @@ func TestDashboardValidity(t *testing.T) {
|
|||||||
// TODO FIXME remove this once we actually have dashboard schema filled in
|
// TODO FIXME remove this once we actually have dashboard schema filled in
|
||||||
// enough that the tests pass, lol
|
// enough that the tests pass, lol
|
||||||
t.Skip()
|
t.Skip()
|
||||||
validdir := os.DirFS(filepath.Join("testdata", "artifacts", "dashboards"))
|
validdir := os.DirFS(filepath.Join("testdata", "artifacts", "dashboards", "basic"))
|
||||||
|
|
||||||
dash, err := BaseDashboardFamily(p)
|
dash, err := BaseDashboardFamily(p)
|
||||||
require.NoError(t, err, "error while loading base dashboard scuemata")
|
require.NoError(t, err, "error while loading base dashboard scuemata")
|
||||||
|
@ -30,7 +30,7 @@ func disjunctPanelScuemata(scuemap map[string]schema.VersionedCueSchema) (cue.Va
|
|||||||
cv := mapPanelModel(id, sch)
|
cv := mapPanelModel(id, sch)
|
||||||
|
|
||||||
mjv, miv := sch.Version()
|
mjv, miv := sch.Version()
|
||||||
parts = parts.Fill(cv, "allPanels", fmt.Sprintf("%s@%v.%v", id, mjv, miv))
|
parts = parts.FillPath(cue.MakePath(cue.Str("allPanels"), cue.Str(fmt.Sprintf("%s@%v.%v", id, mjv, miv))), cv)
|
||||||
sch = sch.Successor()
|
sch = sch.Successor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ func mapPanelModel(id string, vcs schema.VersionedCueSchema) cue.Value {
|
|||||||
`, id, maj, min))
|
`, id, maj, min))
|
||||||
|
|
||||||
// TODO validate, especially with #PanelModel
|
// TODO validate, especially with #PanelModel
|
||||||
return inter.Value().Fill(vcs.CUE(), "in", "model").LookupPath(cue.MakePath(cue.Str(("result"))))
|
return inter.Value().FillPath(cue.MakePath(cue.Str("in"), cue.Str("model")), vcs.CUE()).LookupPath(cue.MakePath(cue.Str(("result"))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPanelModels(p BaseLoadPaths) (map[string]schema.VersionedCueSchema, error) {
|
func readPanelModels(p BaseLoadPaths) (map[string]schema.VersionedCueSchema, error) {
|
||||||
|
35
pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test1
vendored
Normal file
35
pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test1
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
Verifies common usecases for trimdefault/applydefault functions:
|
||||||
|
* Basic cases
|
||||||
|
* Real dashboard
|
||||||
|
|
||||||
|
-- CUE --
|
||||||
|
{
|
||||||
|
id?: number
|
||||||
|
uid: string
|
||||||
|
gnetId?: string
|
||||||
|
style: *"light" | "dark"
|
||||||
|
timezone?: *"browser" | "utc"
|
||||||
|
editable: bool | *true
|
||||||
|
schemaVersion: number | *25
|
||||||
|
version?: number
|
||||||
|
graphTooltip: >=0 & <=2 | *0
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Full --
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"uid": "emal8gQMz",
|
||||||
|
"style": "light",
|
||||||
|
"editable": true,
|
||||||
|
"schemaVersion": 27,
|
||||||
|
"version": 2,
|
||||||
|
"graphTooltip": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Trimed --
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"uid": "emal8gQMz",
|
||||||
|
"schemaVersion": 27,
|
||||||
|
"version": 2
|
||||||
|
}
|
41
pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test2
vendored
Normal file
41
pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test2
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Verifies common usecases for trimdefault/applydefault functions:
|
||||||
|
* Basic cases
|
||||||
|
* Real dashboard
|
||||||
|
|
||||||
|
-- CUE --
|
||||||
|
{
|
||||||
|
timepicker?: {
|
||||||
|
collapse: bool | *false
|
||||||
|
enable: bool | *true
|
||||||
|
hidden: bool | *false
|
||||||
|
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Full --
|
||||||
|
{
|
||||||
|
"timepicker": {
|
||||||
|
"collapse": true,
|
||||||
|
"enable":true,
|
||||||
|
"hidden":false,
|
||||||
|
"refresh_intervals":[
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Trimed --
|
||||||
|
{
|
||||||
|
"timepicker": {
|
||||||
|
"collapse": true
|
||||||
|
}
|
||||||
|
}
|
46
pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test3
vendored
Normal file
46
pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test3
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
Verifies common usecases for trimdefault/applydefault functions:
|
||||||
|
* Nested struct
|
||||||
|
* Simple array
|
||||||
|
|
||||||
|
-- CUE --
|
||||||
|
{
|
||||||
|
annotations?: list: [...{
|
||||||
|
builtIn: number | *0
|
||||||
|
datasource: string
|
||||||
|
enable?: bool | *true
|
||||||
|
hide?: bool | *false
|
||||||
|
iconColor?: string
|
||||||
|
name?: string
|
||||||
|
type: string | *"dashboard"
|
||||||
|
rawQuery?: string
|
||||||
|
showIn: number | *0
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Full --
|
||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard",
|
||||||
|
"showIn": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Trimed --
|
||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"name": "Annotations & Alerts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
149
pkg/services/schemaloader/schemaloader.go
Normal file
149
pkg/services/schemaloader/schemaloader.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package schemaloader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana"
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/schema"
|
||||||
|
"github.com/grafana/grafana/pkg/schema/load"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Register(®istry.Descriptor{
|
||||||
|
Name: ServiceName,
|
||||||
|
Instance: &SchemaLoaderService{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceName = "SchemaLoader"
|
||||||
|
|
||||||
|
var baseLoadPath load.BaseLoadPaths = load.BaseLoadPaths{
|
||||||
|
BaseCueFS: grafana.CoreSchema,
|
||||||
|
DistPluginCueFS: grafana.PluginSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderUser struct {
|
||||||
|
OrgID int64
|
||||||
|
UserID int64
|
||||||
|
OrgRole string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchemaLoaderService struct {
|
||||||
|
log log.Logger
|
||||||
|
DashFamily schema.VersionedCueSchema
|
||||||
|
Cfg *setting.Cfg `inject:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *SchemaLoaderService) Init() error {
|
||||||
|
rs.log = log.New("schemaloader")
|
||||||
|
var err error
|
||||||
|
rs.DashFamily, err = load.BaseDashboardFamily(baseLoadPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load dashboard cue schema from path %q: %w", baseLoadPath, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (rs *SchemaLoaderService) IsDisabled() bool {
|
||||||
|
if rs.Cfg == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !rs.Cfg.IsTrimDefaultsEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *SchemaLoaderService) DashboardApplyDefaults(input *simplejson.Json) (*simplejson.Json, error) {
|
||||||
|
val, _ := input.Map()
|
||||||
|
val = removeNils(val)
|
||||||
|
data, _ := json.Marshal(val)
|
||||||
|
dsSchema := schema.Find(rs.DashFamily, schema.Latest())
|
||||||
|
result, err := dsSchema.ApplyDefaults(schema.Resource{Value: data})
|
||||||
|
if err != nil {
|
||||||
|
return input, err
|
||||||
|
}
|
||||||
|
output, err := simplejson.NewJson([]byte(result.Value.(string)))
|
||||||
|
if err != nil {
|
||||||
|
return input, err
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *SchemaLoaderService) DashboardTrimDefaults(input simplejson.Json) (simplejson.Json, error) {
|
||||||
|
val, _ := input.Map()
|
||||||
|
val = removeNils(val)
|
||||||
|
data, _ := json.Marshal(val)
|
||||||
|
|
||||||
|
dsSchema, err := schema.SearchAndValidate(rs.DashFamily, data)
|
||||||
|
if err != nil {
|
||||||
|
return input, err
|
||||||
|
}
|
||||||
|
// spew.Dump(dsSchema)
|
||||||
|
result, err := dsSchema.TrimDefaults(schema.Resource{Value: data})
|
||||||
|
if err != nil {
|
||||||
|
return input, err
|
||||||
|
}
|
||||||
|
output, err := simplejson.NewJson([]byte(result.Value.(string)))
|
||||||
|
if err != nil {
|
||||||
|
return input, err
|
||||||
|
}
|
||||||
|
return *output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeNils(initialMap map[string]interface{}) map[string]interface{} {
|
||||||
|
withoutNils := map[string]interface{}{}
|
||||||
|
for key, value := range initialMap {
|
||||||
|
_, ok := value.(map[string]interface{})
|
||||||
|
if ok {
|
||||||
|
value = removeNils(value.(map[string]interface{}))
|
||||||
|
withoutNils[key] = value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, ok = value.([]interface{})
|
||||||
|
if ok {
|
||||||
|
value = removeNilArray(value.([]interface{}))
|
||||||
|
withoutNils[key] = value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if value != nil {
|
||||||
|
if val, ok := value.(string); ok {
|
||||||
|
if val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withoutNils[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return withoutNils
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeNilArray(initialArray []interface{}) []interface{} {
|
||||||
|
withoutNils := []interface{}{}
|
||||||
|
for _, value := range initialArray {
|
||||||
|
_, ok := value.(map[string]interface{})
|
||||||
|
if ok {
|
||||||
|
value = removeNils(value.(map[string]interface{}))
|
||||||
|
withoutNils = append(withoutNils, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, ok = value.([]interface{})
|
||||||
|
if ok {
|
||||||
|
value = removeNilArray(value.([]interface{}))
|
||||||
|
withoutNils = append(withoutNils, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if value != nil {
|
||||||
|
if val, ok := value.(string); ok {
|
||||||
|
if val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withoutNils = append(withoutNils, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return withoutNils
|
||||||
|
}
|
@ -384,6 +384,11 @@ func (cfg Cfg) IsNgAlertEnabled() bool {
|
|||||||
return cfg.FeatureToggles["ngalert"]
|
return cfg.FeatureToggles["ngalert"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsTrimDefaultsEnabled returns whether the standalone trim dashboard default feature is enabled.
|
||||||
|
func (cfg Cfg) IsTrimDefaultsEnabled() bool {
|
||||||
|
return cfg.FeatureToggles["trimDefaults"]
|
||||||
|
}
|
||||||
|
|
||||||
// IsDatabaseMetricsEnabled returns whether the database instrumentation feature is enabled.
|
// IsDatabaseMetricsEnabled returns whether the database instrumentation feature is enabled.
|
||||||
func (cfg Cfg) IsDatabaseMetricsEnabled() bool {
|
func (cfg Cfg) IsDatabaseMetricsEnabled() bool {
|
||||||
return cfg.FeatureToggles["database_metrics"]
|
return cfg.FeatureToggles["database_metrics"]
|
||||||
|
Loading…
Reference in New Issue
Block a user