fix: special handling of unmarshalling for invalid json dashboards (#108519)
* implement special dashboard fallback logic when dashboard data has invalid json behind feature flag --------- Co-authored-by: Will Assis <william@williamassis.com>
This commit is contained in:
co-authored by
Will Assis
parent
4a34a2313c
commit
98e37f2ca9
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
@@ -62,6 +63,8 @@ type dashboardSqlAccess struct {
|
||||
namespacer request.NamespaceMapper
|
||||
provisioning provisioning.ProvisioningService
|
||||
|
||||
invalidDashboardParseFallbackEnabled bool
|
||||
|
||||
// Use for writing (not reading)
|
||||
dashStore dashboards.Store
|
||||
dashboardSearchClient legacysearcher.DashboardSearchClient
|
||||
@@ -82,17 +85,19 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider,
|
||||
libraryPanelSvc librarypanels.Service,
|
||||
sorter sort.Service,
|
||||
accessControl accesscontrol.AccessControl,
|
||||
features featuremgmt.FeatureToggles,
|
||||
) DashboardAccess {
|
||||
dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
|
||||
return &dashboardSqlAccess{
|
||||
sql: sql,
|
||||
namespacer: namespacer,
|
||||
dashStore: dashStore,
|
||||
provisioning: provisioning,
|
||||
dashboardSearchClient: *dashboardSearchClient,
|
||||
libraryPanelSvc: libraryPanelSvc,
|
||||
accessControl: accessControl,
|
||||
log: log.New("dashboard.legacysql"),
|
||||
sql: sql,
|
||||
namespacer: namespacer,
|
||||
dashStore: dashStore,
|
||||
provisioning: provisioning,
|
||||
dashboardSearchClient: *dashboardSearchClient,
|
||||
libraryPanelSvc: libraryPanelSvc,
|
||||
accessControl: accessControl,
|
||||
log: log.New("dashboard.legacysql"),
|
||||
invalidDashboardParseFallbackEnabled: features.IsEnabled(context.Background(), featuremgmt.FlagScanRowInvalidDashboardParseFallbackEnabled),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +181,7 @@ func (r *rowsWrapper) Next() bool {
|
||||
r.row, err = r.a.scanRow(r.rows, r.history)
|
||||
if err != nil {
|
||||
r.a.log.Error("error scanning dashboard", "error", err)
|
||||
if len(r.rejected) > 0 || r.row == nil {
|
||||
if len(r.rejected) > 100 || r.row == nil {
|
||||
r.err = fmt.Errorf("too many rejected rows (%d) %w", len(r.rejected), err)
|
||||
return false
|
||||
}
|
||||
@@ -228,6 +233,51 @@ func (r *rowsWrapper) Value() []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func generateFallbackDashboard(data []byte, title, uid string) ([]byte, error) {
|
||||
generatedDashboard := map[string]interface{}{
|
||||
"editable": true,
|
||||
"id": 1,
|
||||
"panels": []map[string]interface{}{
|
||||
{
|
||||
"description": "The JSON is invalid. You can import it again after fixing it.",
|
||||
"gridPos": map[string]interface{}{"h": 8, "w": 24, "x": 0, "y": 0},
|
||||
"id": 1,
|
||||
"options": map[string]interface{}{
|
||||
"code": map[string]interface{}{"language": "plaintext", "showLineNumbers": false, "showMiniMap": false},
|
||||
"content": string(data),
|
||||
"mode": "code",
|
||||
},
|
||||
"title": "Invalid dashboard",
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
"schemaVersion": 41,
|
||||
"title": title,
|
||||
"uid": uid,
|
||||
"version": 3,
|
||||
}
|
||||
return json.Marshal(generatedDashboard)
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) parseDashboard(dash *dashboardV1.Dashboard, data []byte, id int64, title string) error {
|
||||
if err := dash.Spec.UnmarshalJSON(data); err != nil {
|
||||
a.log.Warn("error unmarshalling dashboard spec. Generating fallback dashboard data", "error", err, "uid", dash.UID, "name", dash.Name)
|
||||
dash.Spec = *dashboardV0.NewDashboardSpec()
|
||||
|
||||
dashboardData, err := generateFallbackDashboard(data, title, string(dash.UID))
|
||||
if err != nil {
|
||||
a.log.Warn("error generating fallback dashboard data", "error", err, "uid", dash.UID, "name", dash.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = dash.Spec.UnmarshalJSON(dashboardData); err != nil {
|
||||
a.log.Warn("error unmarshalling fallback dashboard data", "error", err, "uid", dash.UID, "name", dash.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRow, error) {
|
||||
dash := &dashboardV1.Dashboard{
|
||||
TypeMeta: dashboardV1.DashboardResourceInfo.TypeMeta(),
|
||||
@@ -238,6 +288,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
var dashboard_id int64
|
||||
var orgId int64
|
||||
var folder_uid sql.NullString
|
||||
var title string
|
||||
var updated time.Time
|
||||
var updatedBy sql.NullString
|
||||
var updatedByID sql.NullInt64
|
||||
@@ -257,7 +308,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
var data []byte // the dashboard JSON
|
||||
var version int64
|
||||
|
||||
err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &folder_uid,
|
||||
err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &title, &folder_uid,
|
||||
&deleted, &plugin_id,
|
||||
&origin_name, &origin_path, &origin_hash, &origin_ts,
|
||||
&created, &createdBy, &createdByID,
|
||||
@@ -286,6 +337,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
dash.SetCreationTimestamp(metav1.NewTime(created))
|
||||
meta, err := utils.MetaAccessor(dash)
|
||||
if err != nil {
|
||||
a.log.Debug("failed to get meta accessor for dashboard", "error", err, "uid", dash.UID, "name", dash.Name, "version", version)
|
||||
return nil, err
|
||||
}
|
||||
meta.SetUpdatedTimestamp(&updated)
|
||||
@@ -331,9 +383,14 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
err = dash.Spec.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return row, fmt.Errorf("JSON unmarshal error for: %s // %w", dash.Name, err)
|
||||
if a.invalidDashboardParseFallbackEnabled {
|
||||
if err := a.parseDashboard(dash, data, dashboard_id, title); err != nil {
|
||||
return row, err
|
||||
}
|
||||
} else {
|
||||
if err := dash.Spec.UnmarshalJSON(data); err != nil {
|
||||
return row, fmt.Errorf("JSON unmarshal error for: %s // %w", dash.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ignore any saved values for id/version/uid
|
||||
|
||||
Reference in New Issue
Block a user