Perf: Disable core kind registry (#78568)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Todd Treece
2023-11-28 09:09:54 +01:00
committed by GitHub
co-authored by Ryan McKinley
parent b022ddeee8
commit 529271d7a8
12 changed files with 24 additions and 250 deletions
-1
View File
@@ -466,7 +466,6 @@ func (hs *HTTPServer) registerRoutes() {
}) })
dashboardRoute.Post("/calculate-diff", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.CalculateDashboardDiff)) dashboardRoute.Post("/calculate-diff", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.CalculateDashboardDiff))
dashboardRoute.Post("/validate", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.ValidateDashboard))
dashboardRoute.Post("/db", authorize(ac.EvalAny(ac.EvalPermission(dashboards.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionDashboardsWrite))), routing.Wrap(hs.PostDashboard)) dashboardRoute.Post("/db", authorize(ac.EvalAny(ac.EvalPermission(dashboards.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionDashboardsWrite))), routing.Wrap(hs.PostDashboard))
dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard)) dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard))
-73
View File
@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/components/dashdiffs" "github.com/grafana/grafana/pkg/components/dashdiffs"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/kinds/dashboard"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
@@ -792,72 +791,6 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
return response.JSON(http.StatusOK, dashVersionMeta) return response.JSON(http.StatusOK, dashVersionMeta)
} }
// swagger:route POST /dashboards/validate dashboards alpha validateDashboard
//
// Validates a dashboard JSON against the schema.
//
// Produces:
// - application/json
//
// Responses:
// 200: validateDashboardResponse
// 412: validateDashboardResponse
// 422: validateDashboardResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) ValidateDashboard(c *contextmodel.ReqContext) response.Response {
cmd := dashboards.ValidateDashboardCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "Bad request data", err)
}
dk := hs.Kinds.Dashboard()
dashboardBytes := []byte(cmd.Dashboard)
// POST api receives dashboard as a string of json (so line numbers for errors stay consistent),
// but we need to parse the schema version out of it
dashboardJson, err := simplejson.NewJson(dashboardBytes)
if err != nil {
return response.Error(http.StatusBadRequest, "unable to parse dashboard", err)
}
schemaVersion, err := dashboardJson.Get("schemaVersion").Int()
isValid := false
statusCode := http.StatusOK
validationMessage := ""
// Only try to validate if the schemaVersion is at least the handoff version
// (the minimum schemaVersion against which the dashboard schema is known to
// work), or if schemaVersion is absent (which will happen once the Thema
// schema becomes canonical).
if err != nil || schemaVersion >= dashboard.HandoffSchemaVersion {
// Schemas expect the dashboard to live in the spec field
k8sResource := `{"spec": ` + cmd.Dashboard + "}"
_, _, validationErr := dk.JSONValueMux([]byte(k8sResource))
if validationErr == nil {
isValid = true
} else {
validationMessage = validationErr.Error()
statusCode = http.StatusUnprocessableEntity
}
} else {
validationMessage = "invalid schema version"
statusCode = http.StatusPreconditionFailed
}
respData := &ValidateDashboardResponse{
IsValid: isValid,
Message: validationMessage,
}
return response.JSON(statusCode, respData)
}
// swagger:route POST /dashboards/calculate-diff dashboards calculateDashboardDiff // swagger:route POST /dashboards/calculate-diff dashboards calculateDashboardDiff
// //
// Perform diff on two dashboards. // Perform diff on two dashboards.
@@ -1303,9 +1236,3 @@ type DashboardVersionResponse struct {
// in: body // in: body
Body *dashver.DashboardVersionMeta `json:"body"` Body *dashver.DashboardVersionMeta `json:"body"`
} }
// swagger:response validateDashboardResponse
type ValidateDashboardResponse struct {
IsValid bool `json:"isValid"`
Message string `json:"message,omitempty"`
}
-98
View File
@@ -24,7 +24,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/registry/corekind"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/accesscontrol/actest"
@@ -79,7 +78,6 @@ func TestGetHomeDashboard(t *testing.T) {
SQLStore: dbtest.NewFakeDB(), SQLStore: dbtest.NewFakeDB(),
preferenceService: prefService, preferenceService: prefService,
dashboardVersionService: dashboardVersionService, dashboardVersionService: dashboardVersionService,
Kinds: corekind.NewBase(nil),
log: log.New("test-logger"), log: log.New("test-logger"),
} }
@@ -515,60 +513,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}) })
}) })
t.Run("Given a dashboard to validate", func(t *testing.T) {
sqlmock := dbtest.NewFakeDB()
t.Run("When an invalid dashboard json is posted", func(t *testing.T) {
cmd := dashboards.ValidateDashboardCommand{
Dashboard: "{\"hello\": \"world\"}",
}
role := org.RoleAdmin
postValidateScenario(t, "When calling POST on", "/api/dashboards/validate", "/api/dashboards/validate", cmd, role, func(sc *scenarioContext) {
callPostDashboard(sc)
result := sc.ToJSON()
assert.Equal(t, http.StatusUnprocessableEntity, sc.resp.Code)
assert.False(t, result.Get("isValid").MustBool())
assert.NotEmpty(t, result.Get("message").MustString())
}, sqlmock)
})
t.Run("When a dashboard with a too-low schema version is posted", func(t *testing.T) {
cmd := dashboards.ValidateDashboardCommand{
Dashboard: "{\"schemaVersion\": 1}",
}
role := org.RoleAdmin
postValidateScenario(t, "When calling POST on", "/api/dashboards/validate", "/api/dashboards/validate", cmd, role, func(sc *scenarioContext) {
callPostDashboard(sc)
result := sc.ToJSON()
assert.Equal(t, http.StatusPreconditionFailed, sc.resp.Code)
assert.False(t, result.Get("isValid").MustBool())
assert.Equal(t, "invalid schema version", result.Get("message").MustString())
}, sqlmock)
})
t.Run("When a valid dashboard is posted", func(t *testing.T) {
devenvDashboard, readErr := os.ReadFile("../../devenv/dev-dashboards/home.json")
assert.Empty(t, readErr)
cmd := dashboards.ValidateDashboardCommand{
Dashboard: string(devenvDashboard),
}
role := org.RoleAdmin
postValidateScenario(t, "When calling POST on", "/api/dashboards/validate", "/api/dashboards/validate", cmd, role, func(sc *scenarioContext) {
callPostDashboard(sc)
result := sc.ToJSON()
assert.Equal(t, http.StatusOK, sc.resp.Code)
assert.True(t, result.Get("isValid").MustBool())
}, sqlmock)
})
})
t.Run("Given two dashboards being compared", func(t *testing.T) { t.Run("Given two dashboards being compared", func(t *testing.T) {
fakeDashboardVersionService := dashvertest.NewDashboardVersionServiceFake() fakeDashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
fakeDashboardVersionService.ExpectedDashboardVersions = []*dashver.DashboardVersionDTO{ fakeDashboardVersionService.ExpectedDashboardVersions = []*dashver.DashboardVersionDTO{
@@ -742,7 +686,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
AccessControl: accesscontrolmock.New(), AccessControl: accesscontrolmock.New(),
DashboardService: dashboardService, DashboardService: dashboardService,
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
starService: startest.NewStarServiceFake(), starService: startest.NewStarServiceFake(),
} }
hs.callGetDashboard(sc) hs.callGetDashboard(sc)
@@ -781,7 +724,6 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) {
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
DashboardService: dashboardService, DashboardService: dashboardService,
dashboardVersionService: fakeDashboardVersionService, dashboardVersionService: fakeDashboardVersionService,
Kinds: corekind.NewBase(nil),
QuotaService: quotatest.New(false, nil), QuotaService: quotatest.New(false, nil),
userService: userSvc, userService: userSvc,
CacheService: localcache.New(5*time.Minute, 10*time.Minute), CacheService: localcache.New(5*time.Minute, 10*time.Minute),
@@ -922,7 +864,6 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
dashboardProvisioningService: dashboardProvisioningService, dashboardProvisioningService: dashboardProvisioningService,
DashboardService: dashboardService, DashboardService: dashboardService,
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
starService: startest.NewStarServiceFake(), starService: startest.NewStarServiceFake(),
} }
@@ -975,7 +916,6 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
DashboardService: dashboardService, DashboardService: dashboardService,
folderService: folderService, folderService: folderService,
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
accesscontrolService: actest.FakeService{}, accesscontrolService: actest.FakeService{},
log: log.New("test-logger"), log: log.New("test-logger"),
} }
@@ -996,42 +936,6 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
}) })
} }
func postValidateScenario(t *testing.T, desc string, url string, routePattern string, cmd dashboards.ValidateDashboardCommand,
role org.RoleType, fn scenarioFunc, sqlmock db.DB) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
cfg := setting.NewCfg()
hs := HTTPServer{
Cfg: cfg,
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, db.InitTestDB(t)),
QuotaService: quotatest.New(false, nil),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
SQLStore: sqlmock,
Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
}
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.SignedInUser = &user.SignedInUser{
OrgID: testOrgID,
UserID: testUserID,
}
sc.context.OrgRole = role
return hs.ValidateDashboard(c)
})
sc.m.Post(routePattern, sc.defaultHandler)
fn(sc)
})
}
func postDiffScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.CalculateDiffOptions, func postDiffScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.CalculateDiffOptions,
role org.RoleType, fn scenarioFunc, sqlmock db.DB, fakeDashboardVersionService *dashvertest.FakeDashboardVersionService) { role org.RoleType, fn scenarioFunc, sqlmock db.DB, fakeDashboardVersionService *dashvertest.FakeDashboardVersionService) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
@@ -1048,7 +952,6 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
SQLStore: sqlmock, SQLStore: sqlmock,
dashboardVersionService: fakeDashboardVersionService, dashboardVersionService: fakeDashboardVersionService,
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil),
DashboardService: dashSvc, DashboardService: dashSvc,
} }
@@ -1091,7 +994,6 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
SQLStore: sqlStore, SQLStore: sqlStore,
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
dashboardVersionService: fakeDashboardVersionService, dashboardVersionService: fakeDashboardVersionService,
Kinds: corekind.NewBase(nil),
accesscontrolService: actest.FakeService{}, accesscontrolService: actest.FakeService{},
folderService: folderSvc, folderService: folderSvc,
} }
+1 -4
View File
@@ -37,7 +37,6 @@ import (
"github.com/grafana/grafana/pkg/middleware/requestmeta" "github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/pluginscdn" "github.com/grafana/grafana/pkg/plugins/pluginscdn"
"github.com/grafana/grafana/pkg/registry/corekind"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
@@ -187,7 +186,6 @@ type HTTPServer struct {
dashboardVersionService dashver.Service dashboardVersionService dashver.Service
PublicDashboardsApi *publicdashboardsApi.Api PublicDashboardsApi *publicdashboardsApi.Api
starService star.Service starService star.Service
Kinds *corekind.Base
playlistService playlist.Service playlistService playlist.Service
apiKeyService apikey.Service apiKeyService apikey.Service
kvStore kvstore.KVStore kvStore kvstore.KVStore
@@ -240,7 +238,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service, avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service,
folderPermissionsService accesscontrol.FolderPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service, dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
starService star.Service, csrfService csrf.Service, basekinds *corekind.Base, starService star.Service, csrfService csrf.Service,
playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore, playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore,
secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service, secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service,
secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore,
@@ -329,7 +327,6 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dashboardPermissionsService: dashboardPermissionsService, dashboardPermissionsService: dashboardPermissionsService,
dashboardVersionService: dashboardVersionService, dashboardVersionService: dashboardVersionService,
starService: starService, starService: starService,
Kinds: basekinds,
playlistService: playlistService, playlistService: playlistService,
apiKeyService: apiKeyService, apiKeyService: apiKeyService,
kvStore: kvStore, kvStore: kvStore,
+16 -3
View File
@@ -6,6 +6,7 @@ package cuectx
import ( import (
"path/filepath" "path/filepath"
"sync"
"cuelang.org/go/cue" "cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/cuecontext"
@@ -26,14 +27,25 @@ var GoCoreKindParentPath = filepath.Join("pkg", "kinds")
// contains one directory per kind, full of generated TS kind output: types and default consts. // contains one directory per kind, full of generated TS kind output: types and default consts.
var TSCoreKindParentPath = filepath.Join("packages", "grafana-schema", "src", "raw") var TSCoreKindParentPath = filepath.Join("packages", "grafana-schema", "src", "raw")
var ctx = cuecontext.New() var (
var rt = thema.NewRuntime(ctx) ctx *cue.Context
rt *thema.Runtime
once sync.Once
)
func initContext() {
once.Do(func() {
ctx = cuecontext.New()
rt = thema.NewRuntime(ctx)
})
}
// GrafanaCUEContext returns Grafana's singleton instance of [cue.Context]. // GrafanaCUEContext returns Grafana's singleton instance of [cue.Context].
// //
// All code within grafana/grafana that needs a *cue.Context should get it // All code within grafana/grafana that needs a *cue.Context should get it
// from this function, when one was not otherwise provided. // from this function, when one was not otherwise provided.
func GrafanaCUEContext() *cue.Context { func GrafanaCUEContext() *cue.Context {
initContext()
return ctx return ctx
} }
@@ -42,6 +54,7 @@ func GrafanaCUEContext() *cue.Context {
// All code within grafana/grafana that needs a *thema.Runtime should get it // All code within grafana/grafana that needs a *thema.Runtime should get it
// from this function, when one was not otherwise provided. // from this function, when one was not otherwise provided.
func GrafanaThemaRuntime() *thema.Runtime { func GrafanaThemaRuntime() *thema.Runtime {
initContext()
return rt return rt
} }
@@ -56,5 +69,5 @@ func GrafanaThemaRuntime() *thema.Runtime {
// call it repeatedly. Most use cases should probably prefer making // call it repeatedly. Most use cases should probably prefer making
// their own Thema/CUE decoders. // their own Thema/CUE decoders.
func JSONtoCUE(path string, b []byte) (cue.Value, error) { func JSONtoCUE(path string, b []byte) (cue.Value, error) {
return vmux.NewJSONCodec(path).Decode(ctx, b) return vmux.NewJSONCodec(path).Decode(GrafanaCUEContext(), b)
} }
-6
View File
@@ -3,18 +3,12 @@ package corekind
import ( import (
"sync" "sync"
"github.com/google/wire"
"github.com/grafana/kindsys" "github.com/grafana/kindsys"
"github.com/grafana/thema" "github.com/grafana/thema"
"github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/cuectx"
) )
// KindSet contains all of the wire-style providers related to kinds.
var KindSet = wire.NewSet(
NewBase,
)
var ( var (
baseOnce sync.Once baseOnce sync.Once
defaultBase *Base defaultBase *Base
-2
View File
@@ -34,7 +34,6 @@ import (
"github.com/grafana/grafana/pkg/middleware/csrf" "github.com/grafana/grafana/pkg/middleware/csrf"
"github.com/grafana/grafana/pkg/middleware/loggermw" "github.com/grafana/grafana/pkg/middleware/loggermw"
apiregistry "github.com/grafana/grafana/pkg/registry/apis" apiregistry "github.com/grafana/grafana/pkg/registry/apis"
"github.com/grafana/grafana/pkg/registry/corekind"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
@@ -314,7 +313,6 @@ var wireBasicSet = wire.NewSet(
secretsStore.ProvideService, secretsStore.ProvideService,
avatar.ProvideAvatarCacheServer, avatar.ProvideAvatarCacheServer,
statscollector.ProvideService, statscollector.ProvideService,
corekind.KindSet,
cuectx.GrafanaCUEContext, cuectx.GrafanaCUEContext,
cuectx.GrafanaThemaRuntime, cuectx.GrafanaThemaRuntime,
csrf.ProvideCSRFFilter, csrf.ProvideCSRFFilter,
-9
View File
@@ -260,15 +260,6 @@ type SaveDashboardCommand struct {
UpdatedAt time.Time UpdatedAt time.Time
} }
type ValidateDashboardCommand struct {
Dashboard string `json:"dashboard" binding:"Required"`
}
type TrimDashboardCommand struct {
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
Meta *simplejson.Json `json:"meta"`
}
type DashboardProvisioning struct { type DashboardProvisioning struct {
ID int64 `xorm:"pk autoincr 'id'"` ID int64 `xorm:"pk autoincr 'id'"`
DashboardID int64 `xorm:"dashboard_id"` DashboardID int64 `xorm:"dashboard_id"`
-11
View File
@@ -9158,17 +9158,6 @@
"$ref": "#/definitions/UserProfileDTO" "$ref": "#/definitions/UserProfileDTO"
} }
}, },
"validateDashboardResponse": {
"description": "",
"headers": {
"isValid": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
},
"viewPublicDashboardResponse": { "viewPublicDashboardResponse": {
"description": "", "description": "",
"schema": { "schema": {
-11
View File
@@ -22312,17 +22312,6 @@
"$ref": "#/definitions/UserProfileDTO" "$ref": "#/definitions/UserProfileDTO"
} }
}, },
"validateDashboardResponse": {
"description": "(empty)",
"headers": {
"isValid": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
},
"viewPublicDashboardResponse": { "viewPublicDashboardResponse": {
"description": "(empty)", "description": "(empty)",
"schema": { "schema": {
+7 -17
View File
@@ -516,16 +516,13 @@ export class BackendSrv implements BackendService {
return this.get<DashboardDTO>(`/api/dashboards/uid/${uid}`); return this.get<DashboardDTO>(`/api/dashboards/uid/${uid}`);
} }
validateDashboard(dashboard: DashboardModel) { validateDashboard(dashboard: DashboardModel): Promise<ValidateDashboardResponse> {
// We want to send the dashboard as a JSON string (in the JSON body payload) so we can get accurate error line numbers back // support for this function will be implemented in the k8s flavored api-server
const dashboardJson = JSON.stringify(dashboard, replaceJsonNulls, 2); // hidden by experimental feature flag:
// config.featureToggles.showDashboardValidationWarnings
return this.request<ValidateDashboardResponse>({ return Promise.resolve({
method: 'POST', isValid: false,
url: `/api/dashboards/validate`, message: 'dashboard validation is supported',
data: { dashboard: dashboardJson },
showSuccessAlert: false,
showErrorAlert: false,
}); });
} }
@@ -553,10 +550,3 @@ interface ValidateDashboardResponse {
isValid: boolean; isValid: boolean;
message?: string; message?: string;
} }
function replaceJsonNulls<T extends unknown>(key: string, value: T): T | undefined {
if (typeof value === 'number' && !Number.isFinite(value)) {
return undefined;
}
return value;
}
-15
View File
@@ -1911,21 +1911,6 @@
}, },
"description": "(empty)" "description": "(empty)"
}, },
"validateDashboardResponse": {
"description": "(empty)",
"headers": {
"isValid": {
"schema": {
"type": "boolean"
}
},
"message": {
"schema": {
"type": "string"
}
}
}
},
"viewPublicDashboardResponse": { "viewPublicDashboardResponse": {
"content": { "content": {
"application/json": { "application/json": {