diff --git a/go.mod b/go.mod index 2e91c84..ba69689 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,6 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/dskit v0.0.0-20240905221822-931a021fb06b // indirect github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56 // indirect - github.com/grafana/grafana-foundation-sdk/go v0.0.0-20241101005901-83e3491f2a70 // indirect github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 // indirect github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect diff --git a/lint/lint.go b/lint/lint.go index 2e81a17..a3b780b 100644 --- a/lint/lint.go +++ b/lint/lint.go @@ -4,8 +4,6 @@ import ( "encoding/json" "fmt" "strings" - - "github.com/grafana/grafana-foundation-sdk/go/dashboard" ) type Severity int @@ -208,20 +206,37 @@ type Panel struct { Options json.RawMessage `json:"options,omitempty"` } -// Stat panel options is a deliberately incomplete representation of the stat panel options from grafana. -// The properties which are extracted from JSON are only those used for linting purposes. -type StatOptions struct { - ReduceOptions ReduceOptions `json:"reduceOptions,omitempty"` +type FieldConfig struct { + Defaults Defaults `json:"defaults,omitempty"` + Overrides []Override `json:"overrides,omitempty"` +} + +type Override struct { + OverrideProperties []OverrideProperty `json:"properties"` +} + +type OverrideProperty struct { + Id string `json:"id"` + Value any `json:"value"` } // oversimplified Reduce options type ReduceOptions struct { - Fields string `json:"fields,omitempty"` + Fields string `json:"fields,omitempty"` + Calcs []string `json:"[]calcs,omitempty"` + Values bool `json:"values,omitempty"` + Limit int `json:"limit,omitempty"` } -type FieldConfig struct { - Defaults dashboard.FieldConfig - Overrides []dashboard.DashboardFieldConfigSourceOverrides +// Stat panel options is a deliberately incomplete representation of the stat panel options from grafana. +// The properties which are extracted from JSON are only those used for linting purposes. +type StatOptions struct { + ReduceOptions ReduceOptions `json:"reduceOptions,omitempty"` +} + +type Defaults struct { + Unit string `json:"unit,omitempty"` + Mappings json.RawMessage `json:"mappings,omitempty"` } // GetPanels returns the all panels nested inside the panel (inc the current panel) @@ -269,7 +284,6 @@ type Dashboard struct { // Kubernetes shaped dashboards will include an APIVersion and Kind APIVersion string `json:"apiVersion,omitempty"` - // When reading a kubernetes encoded dashboard, the Dashboard will be Spec json.RawMessage `json:"spec,omitempty"` } @@ -314,7 +328,6 @@ func NewDashboard(buf []byte) (Dashboard, error) { if err := json.Unmarshal(buf, &dash); err != nil { return dash, err } - // Support kubernetes flavored dashboards if dash.Spec != nil { apiVersion := dash.APIVersion diff --git a/lint/rule_panel_units.go b/lint/rule_panel_units.go index e056bed..32c739a 100644 --- a/lint/rule_panel_units.go +++ b/lint/rule_panel_units.go @@ -3,8 +3,6 @@ package lint import ( "encoding/json" "fmt" - - "github.com/grafana/grafana-foundation-sdk/go/dashboard" ) func NewPanelUnitsRule() *PanelRuleFunc { @@ -85,8 +83,12 @@ func NewPanelUnitsRule() *PanelRuleFunc { } } - //ignore if has value mappings: - if len(getValueMappings(p)) > 0 { + //ignore this rule if has value mappings: + valueMappings, err := getValueMappings(p) + if err != nil { + r.AddError(d, p, err.Error()) + } + if valueMappings != nil { return r } @@ -109,43 +111,46 @@ func getConfiguredUnit(p Panel) string { configuredUnit := "" // First check if an override with unit exists - if no override then check if standard unit is present and valid if p.FieldConfig != nil && len(p.FieldConfig.Overrides) > 0 { - for _, p := range p.FieldConfig.Overrides { - for _, o := range p.Properties { - if o.Id == "unit" && o.Value != nil { - configuredUnit = o.Value.(string) + for _, override := range p.FieldConfig.Overrides { + if len(override.OverrideProperties) > 0 { + for _, o := range override.OverrideProperties { + if o.Id == "unit" { + configuredUnit = o.Value.(string) + } } } } } - if configuredUnit == "" && p.FieldConfig != nil && p.FieldConfig.Defaults.Unit != nil { - configuredUnit = *p.FieldConfig.Defaults.Unit + if configuredUnit == "" && p.FieldConfig != nil && p.FieldConfig.Defaults.Unit != "" { + configuredUnit = p.FieldConfig.Defaults.Unit } return configuredUnit } -func getValueMappings(p Panel) []dashboard.ValueMapping { - valueMappings := make([]dashboard.ValueMapping, 0) - // First check if an override with value mapping exists - if no override then check if standard value mapping is present and valid +func getValueMappings(p Panel) (any, error) { + var valueMappings any + // First check if an override with unit exists - if no override then check if standard unit is present and valid if p.FieldConfig != nil && len(p.FieldConfig.Overrides) > 0 { - for _, p := range p.FieldConfig.Overrides { - for _, o := range p.Properties { - if o.Id == "mappings" { - vm, ok := o.Value.([]dashboard.ValueMapping) - if ok { - valueMappings = vm + for _, override := range p.FieldConfig.Overrides { + if len(override.OverrideProperties) > 0 { + for _, o := range override.OverrideProperties { + if o.Id == "mappings" && o.Value != nil { + return o.Value, nil } } } } } - if len(valueMappings) == 0 && p.FieldConfig != nil && p.FieldConfig.Defaults.Mappings != nil { - valueMappings = p.FieldConfig.Defaults.Mappings + if p.FieldConfig != nil && p.FieldConfig.Defaults.Mappings != nil { + err := json.Unmarshal(p.FieldConfig.Defaults.Mappings, &valueMappings) + if err != nil { + return valueMappings, err + } } - return valueMappings + return valueMappings, nil } // Numeric fields are set as empty string "". Any other value means nonnumeric on grafana stat panel. func hasReduceOptionsNonNumericFields(reduceOpts *ReduceOptions) bool { - return reduceOpts.Fields != "" } diff --git a/lint/rule_panel_units_test.go b/lint/rule_panel_units_test.go index 21f1d84..d0ebca2 100644 --- a/lint/rule_panel_units_test.go +++ b/lint/rule_panel_units_test.go @@ -2,28 +2,35 @@ package lint import ( "testing" - - "github.com/grafana/grafana-foundation-sdk/go/dashboard" ) -func ptr[T any](t T) *T { return &t } func TestPanelUnits(t *testing.T) { linter := NewPanelUnitsRule() - - testValueMap := &dashboard.ValueMap{ - Type: "value", - Options: map[string]dashboard.ValueMappingResult{ - "1": { - Text: ptr("Ok"), - Color: ptr("green"), - }, - "2": { - Text: ptr("Down"), - Color: ptr("red"), + var overrides = make([]Override, 0) + overrides = append(overrides, Override{ + OverrideProperties: []OverrideProperty{ + { + Id: "mappings", + Value: []byte(`[ + { + "type": "value", + "options": { + "1": { + "text": "OK", + "color": "green", + "index": 0 + }, + "2": { + "text": "Problem", + "color": "red", + "index": 1 + } + } + } + ]`), }, }, - } - + }) for _, tc := range []struct { name string result Result @@ -40,8 +47,8 @@ func TestPanelUnits(t *testing.T) { Datasource: "foo", Title: "bar", FieldConfig: &FieldConfig{ - Defaults: dashboard.FieldConfig{ - Unit: ptr("MyInvalidUnit"), + Defaults: Defaults{ + Unit: "MyInvalidUnit", }, }, }, @@ -79,8 +86,8 @@ func TestPanelUnits(t *testing.T) { Datasource: "foo", Title: "bar", FieldConfig: &FieldConfig{ - Defaults: dashboard.FieldConfig{ - Unit: ptr("short"), + Defaults: Defaults{ + Unit: "short", }, }, }, @@ -93,8 +100,8 @@ func TestPanelUnits(t *testing.T) { Datasource: "foo", Title: "bar", FieldConfig: &FieldConfig{ - Defaults: dashboard.FieldConfig{ - Unit: ptr("none"), + Defaults: Defaults{ + Unit: "none", }, }, }, @@ -144,12 +151,26 @@ func TestPanelUnits(t *testing.T) { Datasource: "foo", Title: "bar", FieldConfig: &FieldConfig{ - Defaults: dashboard.FieldConfig{ - Mappings: []dashboard.ValueMapOrRangeMapOrRegexMapOrSpecialValueMap{ - dashboard.ValueMapOrRangeMapOrRegexMapOrSpecialValueMap{ - ValueMap: testValueMap, - }, - }, + Defaults: Defaults{ + Mappings: []byte(` + [ + { + "options": { + "0": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "1": { + "color": "green", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ]`, + ), }, }, }, @@ -162,24 +183,7 @@ func TestPanelUnits(t *testing.T) { Datasource: "foo", Title: "bar", FieldConfig: &FieldConfig{ - Overrides: []dashboard.DashboardFieldConfigSourceOverrides{ - dashboard.DashboardFieldConfigSourceOverrides{ - Matcher: dashboard.MatcherConfig{ - Id: "byRegexp", - Options: "/.*/", - }, - Properties: []dashboard.DynamicConfigValue{ - dashboard.DynamicConfigValue{ - Id: "mappings", - Value: []dashboard.ValueMapOrRangeMapOrRegexMapOrSpecialValueMap{ - dashboard.ValueMapOrRangeMapOrRegexMapOrSpecialValueMap{ - ValueMap: testValueMap, - }, - }, - }, - }, - }, - }, + Overrides: overrides, }, }, },