Be more tolerant to invalid JSON when parsing dashboards for indexing (#114040)

* Tags can only be string array.

* Be more lenient when parsing dashboard. Parse what we can, don't error out easily.

* Check element before parsing, log unexpected types.
This commit is contained in:
Peter Štibraný
2025-11-18 14:01:57 +01:00
committed by GitHub
parent e821578ab0
commit ec74a721ea
8 changed files with 586 additions and 100 deletions
+227 -90
View File
@@ -1,16 +1,16 @@
package dashboard
import (
"errors"
"fmt"
"io"
"strconv"
"strings"
jsoniter "github.com/json-iterator/go"
)
func logf(format string, a ...any) {
//fmt.Printf(format, a...)
}
"github.com/grafana/grafana/pkg/infra/log"
)
type templateVariable struct {
current struct {
@@ -48,7 +48,7 @@ func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string
case "No data sources found":
return []DataSourceRef{}
default:
// some variables use `ds.name` rather `ds.uid`
// some variables use `ds.name` rather than `ds.uid`
if ref := d.dsLookup.ByRef(&DataSourceRef{
UID: value,
}); ref != nil {
@@ -114,45 +114,63 @@ func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableL
// ReadDashboard will take a byte stream and return dashboard info
func ReadDashboard(stream io.Reader, lookup DatasourceLookup) (*DashboardSummaryInfo, error) {
return ReadDashboardWithLogContext(stream, lookup, nil)
}
func ReadDashboardWithLogContext(stream io.Reader, lookup DatasourceLookup, logContext map[string]any) (*DashboardSummaryInfo, error) {
iter := jsoniter.Parse(jsoniter.ConfigDefault, stream, 1024)
return readDashboardIter(iter, lookup)
return readDashboardIter("$", iter, lookup, logContext)
}
// nolint:gocyclo
func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*DashboardSummaryInfo, error) {
func readDashboardIter(jsonPath string, iter *jsoniter.Iterator, lookup DatasourceLookup, lc map[string]any) (*DashboardSummaryInfo, error) {
dash := &DashboardSummaryInfo{}
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.ObjectValue) {
return dash, errors.New("expected JSON object at " + jsonPath)
}
datasourceVariablesLookup := newDatasourceVariableLookup(lookup)
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
// Skip null values so we don't need special int handling
if iter.WhatIsNext() == jsoniter.NilValue {
iter.Skip()
continue
}
switch l1Field {
switch field {
// k8s metadata wrappers (skip)
case "metadata", "kind", "apiVersion":
_ = iter.Read()
iter.Skip()
// recursively read the spec as dashboard json
case "spec":
return readDashboardIter(iter, lookup)
return readDashboardIter(jsonPath+".spec", iter, lookup, lc)
case "id":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".id", lc, jsoniter.NumberValue) {
continue
}
dash.ID = iter.ReadInt64()
case "uid":
iter.ReadString()
case "title":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".title", lc, jsoniter.StringValue) {
continue
}
dash.Title = iter.ReadString()
case "description":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".description", lc, jsoniter.StringValue) {
continue
}
dash.Description = iter.ReadString()
case "schemaVersion":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".schemaVersion", lc, jsoniter.NumberValue, jsoniter.StringValue) {
continue
}
switch iter.WhatIsNext() {
case jsoniter.NumberValue:
dash.SchemaVersion = iter.ReadInt64()
@@ -164,10 +182,18 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
default:
iter.Skip()
}
case "timezone":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".timezone", lc, jsoniter.StringValue) {
continue
}
dash.TimeZone = iter.ReadString()
case "editable":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".editable", lc, jsoniter.StringValue, jsoniter.BoolValue) {
continue
}
switch iter.WhatIsNext() {
case jsoniter.BoolValue:
dash.ReadOnly = !iter.ReadBool()
@@ -178,25 +204,42 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
}
case "refresh":
nxt := iter.WhatIsNext()
if nxt == jsoniter.StringValue {
dash.Refresh = iter.ReadString()
} else {
iter.Skip()
if !checkAndSkipUnexpectedElement(iter, jsonPath+".refresh", lc, jsoniter.StringValue) {
continue
}
dash.Refresh = iter.ReadString()
case "tags":
for iter.ReadArray() {
tagsPath := jsonPath + ".tags"
// Only support string array tags. Ignore everything else.
if !checkAndSkipUnexpectedElement(iter, tagsPath, lc, jsoniter.ArrayValue) {
continue
}
for ix := 0; iter.ReadArray(); ix++ {
if !checkAndSkipUnexpectedElement(iter, fmt.Sprintf("%s[%d]", tagsPath, ix), lc, jsoniter.StringValue) {
continue
}
dash.Tags = append(dash.Tags, iter.ReadString())
}
case "links":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".links", lc, jsoniter.ArrayValue) {
continue
}
for iter.ReadArray() {
iter.Skip()
dash.LinkCount++
}
case "time":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".time", lc, jsoniter.ObjectValue) {
continue
}
obj, ok := iter.Read().(map[string]any)
if ok {
if timeFrom, ok := obj["from"].(string); ok {
@@ -206,61 +249,66 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
dash.TimeTo = timeTo
}
}
case "panels":
for iter.ReadArray() {
dash.Panels = append(dash.Panels, readpanelInfo(iter, lookup))
panelsPath := jsonPath + ".panels"
if !checkAndSkipUnexpectedElement(iter, panelsPath, lc, jsoniter.ArrayValue) {
continue
}
case "rows":
for iter.ReadArray() {
v := iter.Read()
logf("[DASHBOARD.ROW???] id=%s // %v\n", dash.ID, v)
}
case "annotations":
switch iter.WhatIsNext() {
case jsoniter.ArrayValue:
// dashboards v2 is an array
for iter.ReadArray() {
v := iter.Read()
logf("[dash.anno] %v\n", v)
for ix := 0; iter.ReadArray(); ix++ {
p, ok := readpanelInfo(iter, lookup, fmt.Sprintf("%s[%d]", panelsPath, ix), lc)
if ok {
dash.Panels = append(dash.Panels, p)
}
case jsoniter.ObjectValue:
// dashboards v0/v1 are an object
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
if sub == "list" {
for iter.ReadArray() {
v := iter.Read()
logf("[dash.anno] %v\n", v)
}
} else {
iter.Skip()
}
}
default:
iter.Skip()
}
case "templating":
templatingPath := jsonPath + ".templating"
if !checkAndSkipUnexpectedElement(iter, templatingPath, lc, jsoniter.ObjectValue) {
continue
}
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
if sub == "list" {
for iter.ReadArray() {
templateVariable := templateVariable{}
templatingListPath := templatingPath + ".list"
if !checkAndSkipUnexpectedElement(iter, templatingListPath, lc, jsoniter.ArrayValue) {
continue
}
for ix := 0; iter.ReadArray(); ix++ {
tv := templateVariable{}
templatingListElementPath := fmt.Sprintf("%s[%d]", templatingListPath, ix)
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath, lc, jsoniter.ObjectValue) {
continue
}
for k := iter.ReadObject(); k != ""; k = iter.ReadObject() {
switch k {
case "name":
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath+".name", lc, jsoniter.StringValue) {
continue
}
name := iter.ReadString()
dash.TemplateVars = append(dash.TemplateVars, name)
templateVariable.name = name
tv.name = name
case "type":
templateVariable.variableType = iter.ReadString()
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath+".type", lc, jsoniter.StringValue) {
continue
}
tv.variableType = iter.ReadString()
case "query":
templateVariable.query = iter.Read()
tv.query = iter.Read()
case "current":
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath+".current", lc, jsoniter.ObjectValue) {
continue
}
for c := iter.ReadObject(); c != ""; c = iter.ReadObject() {
if c == "value" {
templateVariable.current.value = iter.Read()
tv.current.value = iter.Read()
} else {
iter.Skip()
}
@@ -270,8 +318,8 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
}
}
if templateVariable.variableType == "datasource" {
datasourceVariablesLookup.add(templateVariable)
if tv.variableType == "datasource" {
datasourceVariablesLookup.add(tv)
}
}
} else {
@@ -279,17 +327,9 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
}
}
// Ignore these properties
case "timepicker":
fallthrough
case "version":
fallthrough
case "iteration":
iter.Skip()
// Ignore everything else
default:
v := iter.Read()
logf("[DASHBOARD] support key: %s / %v\n", l1Field, v)
iter.Skip()
}
}
@@ -311,6 +351,64 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
return dash, iter.Error
}
var logger = log.New("services.store.kind.dashboard")
// checkAndSkipUnexpectedElement verifies if the next JSON element matches any allowed value types for the specified JSON path.
// If the type matches, it returns true, otherwise it skips the element, logs an error, and returns false.
func checkAndSkipUnexpectedElement(iter *jsoniter.Iterator, jsonPath string, logContext map[string]any, allowedValues ...jsoniter.ValueType) bool {
next := iter.WhatIsNext()
for _, a := range allowedValues {
if next == a {
return true
}
}
// Skip unexpected element.
iter.Skip()
// Prepare log message.
params := []any{
"jsonPath", jsonPath,
"got", valueTypesToString(next),
"expected", valueTypesToString(allowedValues...),
}
// Map iteration is random, so a log message may look different each time if there are multiple entries in the map. That's fine.
for k, v := range logContext {
params = append(params, k, v)
}
logger.Error("Unexpected element in Dashboard JSON", params...)
return false
}
func valueTypesToString(allowedValues ...jsoniter.ValueType) string {
expected := strings.Builder{}
for ix, a := range allowedValues {
if ix > 0 {
expected.WriteString(", ")
}
switch a {
case jsoniter.NilValue:
expected.WriteString("null")
case jsoniter.StringValue:
expected.WriteString("string")
case jsoniter.NumberValue:
expected.WriteString("number")
case jsoniter.BoolValue:
expected.WriteString("bool")
case jsoniter.ArrayValue:
expected.WriteString("array")
case jsoniter.ObjectValue:
expected.WriteString("object")
default:
expected.WriteString(fmt.Sprintf("unknown: %d", a))
}
}
return expected.String()
}
func panelRequiresDatasource(panel PanelSummaryInfo) bool {
return panel.Type != "row"
}
@@ -399,16 +497,20 @@ func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVa
return referencedDs
}
// will always return strings for now
func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummaryInfo {
// nolint:gocyclo
func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup, jsonPath string, lc map[string]any) (PanelSummaryInfo, bool) {
panel := PanelSummaryInfo{}
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.ObjectValue) {
return panel, false
}
targets := newTargetInfo(lookup)
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
if iter.WhatIsNext() == jsoniter.NilValue {
if l1Field == "datasource" {
targets.addDatasource(iter)
if field == "datasource" {
targets.addDatasource(iter, jsonPath+".datasource", lc)
continue
}
@@ -417,23 +519,42 @@ func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummar
continue
}
switch l1Field {
switch field {
case "id":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".id", lc, jsoniter.NumberValue) {
continue
}
panel.ID = iter.ReadInt64()
case "type":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".type", lc, jsoniter.StringValue) {
continue
}
panel.Type = iter.ReadString()
case "title":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".title", lc, jsoniter.StringValue) {
continue
}
panel.Title = iter.ReadString()
case "description":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".description", lc, jsoniter.StringValue) {
continue
}
panel.Description = iter.ReadString()
case "pluginVersion":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".pluginVersion", lc, jsoniter.StringValue) {
continue
}
panel.PluginVersion = iter.ReadString() // since 7x (the saved version for the plugin model)
case "libraryPanel":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".libraryPanel", lc, jsoniter.ObjectValue) {
continue
}
var v map[string]interface{}
iter.ReadVal(&v)
if uid, ok := v["uid"]; ok {
@@ -443,26 +564,42 @@ func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummar
}
case "datasource":
targets.addDatasource(iter)
targets.addDatasource(iter, jsonPath+".datasource", lc)
case "targets":
if !checkAndSkipUnexpectedElement(iter, jsonPath+".targets", lc, jsoniter.ArrayValue, jsoniter.ObjectValue) {
continue
}
switch iter.WhatIsNext() {
case jsoniter.ArrayValue:
for iter.ReadArray() {
targets.addTarget(iter)
for ix := 0; iter.ReadArray(); ix++ {
targets.addTarget(iter, fmt.Sprintf("%s.targets[%d]", jsonPath, ix), lc)
}
case jsoniter.ObjectValue:
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
targets.addTarget(iter)
for fn := iter.ReadObject(); fn != ""; fn = iter.ReadObject() {
targets.addTarget(iter, jsonPath+".targets."+fn, lc)
}
default:
iter.Skip()
}
case "transformations":
for iter.ReadArray() {
if !checkAndSkipUnexpectedElement(iter, jsonPath+".transformations", lc, jsoniter.ArrayValue) {
continue
}
for ix := 0; iter.ReadArray(); ix++ {
if !checkAndSkipUnexpectedElement(iter, fmt.Sprintf("%s.transformations[%d]", jsonPath, ix), lc, jsoniter.ObjectValue) {
continue
}
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
if sub == "id" {
if !checkAndSkipUnexpectedElement(iter, fmt.Sprintf("%s.transformations[%d].id", jsonPath, ix), lc, jsoniter.StringValue) {
continue
}
panel.Transformer = append(panel.Transformer, iter.ReadString())
} else {
iter.Skip()
@@ -472,26 +609,26 @@ func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummar
// Rows have nested panels
case "panels":
for iter.ReadArray() {
panel.Collapsed = append(panel.Collapsed, readpanelInfo(iter, lookup))
if !checkAndSkipUnexpectedElement(iter, jsonPath+".panels", lc, jsoniter.ArrayValue) {
continue
}
case "options":
fallthrough
for ix := 0; iter.ReadArray(); ix++ {
p, ok := readpanelInfo(iter, lookup, fmt.Sprintf("%s.panels[%d]", jsonPath, ix), lc)
if ok {
panel.Collapsed = append(panel.Collapsed, p)
}
}
case "gridPos":
fallthrough
case "fieldConfig":
case "options", "gridPos", "fieldConfig":
iter.Skip()
default:
v := iter.Read()
logf("[PANEL] support key: %s / %v\n", l1Field, v)
iter.Skip()
}
}
panel.Datasource = targets.GetDatasourceInfo()
return panel
return panel, true
}
@@ -55,6 +55,7 @@ func dsLookupForTests() DatasourceLookup {
func TestReadDashboard(t *testing.T) {
inputs := []string{
"absolute-garbage",
"check-string-datasource-id",
"all-panels",
"panel-graph/graph-shared-tooltips",
@@ -73,6 +74,7 @@ func TestReadDashboard(t *testing.T) {
"panel-with-library-panel-field",
"k8s-wrapper",
"k8s-wrapper-editable-string",
"k8s-wrapper-tags-string",
}
devdash := "../../../../../devenv/dev-dashboards/"
+15 -9
View File
@@ -27,7 +27,11 @@ func (s *targetInfo) GetDatasourceInfo() []DataSourceRef {
}
// the node will either be string (name|uid) OR ref
func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) {
func (s *targetInfo) addDatasource(iter *jsoniter.Iterator, jsonPath string, lc map[string]any) {
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.StringValue, jsoniter.NilValue, jsoniter.ObjectValue) {
return
}
switch iter.WhatIsNext() {
case jsoniter.StringValue:
key := iter.ReadString()
@@ -55,8 +59,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) {
}
default:
v := iter.Read()
logf("[Panel.datasource.unknown] %v\n", v)
iter.Skip()
}
}
@@ -66,18 +69,21 @@ func (s *targetInfo) addRef(ref *DataSourceRef) {
}
}
func (s *targetInfo) addTarget(iter *jsoniter.Iterator) {
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
switch l1Field {
func (s *targetInfo) addTarget(iter *jsoniter.Iterator, jsonPath string, lc map[string]any) {
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.ObjectValue) {
return
}
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "datasource":
s.addDatasource(iter)
s.addDatasource(iter, jsonPath+".datasource", lc)
case "refId":
iter.Skip()
default:
v := iter.Read()
logf("[Panel.TARGET] %s=%v\n", l1Field, v)
iter.Skip()
}
}
}
@@ -0,0 +1,58 @@
{
"title": "adfbg6f",
"tags": null,
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
],
"panels": [
{
"id": 1,
"title": "green pie",
"libraryPanel": "a7975b7a-fb53-4ab7-951d-15810953b54f",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 0,
"title": "",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 7,
"title": "",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 8,
"title": "",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
}
],
"schemaVersion": 0,
"linkCount": 4,
"timeFrom": "",
"timeTo": "",
"timezone": ""
}
@@ -0,0 +1,84 @@
{
"metadata": 123,
"spec": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": "true",
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": "not a number",
"links": [
"http://12345",
{
"object": "goes, here"
},
null,
true
],
"liveNow": false,
"panels": [
{
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"libraryPanel": {
"name": "green pie",
"uid": "a7975b7a-fb53-4ab7-951d-15810953b54f"
},
"title": "green pie"
},
{
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": "not a number",
"title": true
},
{
"id": 7,
"type": null
},
{
"id": 8,
"type": {
"a": 123
}
}
],
"refresh": "",
"schemaVersion": null,
"tags": 1234,
"templating": {
"list": []
},
"time": false,
"timepicker": {},
"timezone": 3.141592653589793,
"title": "adfbg6f",
"uid": ["aaa", "bbb", "cc"],
"description": [null, true],
"version": [],
"weekStart": false
}
}
@@ -0,0 +1,74 @@
{
"id": 141,
"title": "pppp",
"tags": null,
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
],
"panels": [
{
"id": 1,
"title": "green pie",
"libraryPanel": "a7975b7a-fb53-4ab7-951d-15810953b54f",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 2,
"title": "green pie",
"libraryPanel": "e1d5f519-dabd-47c6-9ad7-83d181ce1cee",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 7,
"title": "",
"type": "barchart",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 8,
"title": "",
"type": "graph",
"datasource": [
{
"uid": "default.uid",
"type": "default.type"
}
]
},
{
"id": 3,
"title": "collapsed row",
"type": "row",
"collapsed": [
{
"id": 42,
"title": "blue pie",
"libraryPanel": "l3d2s634-fdgf-75u4-3fg3-67j966ii7jur"
}
]
}
],
"schemaVersion": 38,
"linkCount": 0,
"timeFrom": "now-6h",
"timeTo": "now",
"timezone": ""
}
@@ -0,0 +1,122 @@
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v0alpha1",
"metadata": {
"name": "adfbg6f",
"namespace": "default",
"uid": "b396894e-56bf-4a01-837b-64157912ca00",
"creationTimestamp": "2024-10-30T18:30:54Z",
"annotations": {
"grafana.app/createdBy": "user:be2g71ke8yoe8b",
"grafana.app/originHash": "Grafana v9.2.0 (NA)",
"grafana.app/originName": "UI",
"grafana.app/originPath": "/dashboard/new"
}
},
"spec": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 141,
"links": [],
"liveNow": false,
"panels": [
{
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"libraryPanel": {
"name": "green pie",
"uid": "a7975b7a-fb53-4ab7-951d-15810953b54f"
},
"title": "green pie"
},
{
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"libraryPanel": {
"name": "red pie",
"uid": "e1d5f519-dabd-47c6-9ad7-83d181ce1cee"
},
"title": "green pie"
},
{
"id": 7,
"type": "barchart"
},
{
"id": 8,
"type": "graph"
},
{
"collapsed": true,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 9
},
"id": 3,
"panels": [
{
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 42,
"libraryPanel": {
"name": "blue pie",
"uid": "l3d2s634-fdgf-75u4-3fg3-67j966ii7jur"
},
"title": "blue pie"
}
],
"title": "collapsed row",
"type": "row"
}
],
"refresh": "",
"schemaVersion": 38,
"tags": "tag1,tag2, tag3,,",
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "pppp",
"uid": "adfbg6f",
"version": 3,
"weekStart": ""
}
}
+4 -1
View File
@@ -261,7 +261,10 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou
value = rsp.Value
}
summary, err := dashboard.ReadDashboard(bytes.NewReader(value), s.DatasourceLookup)
summary, err := dashboard.ReadDashboardWithLogContext(bytes.NewReader(value), s.DatasourceLookup, map[string]any{
"document": fmt.Sprintf("%s/%s/%s/%s", key.GetNamespace(), key.GetGroup(), key.GetResource(), key.GetName()),
"rv": rv,
})
if err != nil {
return nil, err
}