diff --git a/pkger/models.go b/pkger/models.go index c7647935f9..ff9ee392bd 100644 --- a/pkger/models.go +++ b/pkger/models.go @@ -171,11 +171,12 @@ const ( ChartKindSingleStat ChartKind = "single_stat" ChartKindSingleStatPlusLine ChartKind = "single_stat_plus_line" ChartKindXY ChartKind = "xy" + ChartKindGauge ChartKind = "gauge" ) func (c ChartKind) ok() bool { switch c { - case ChartKindSingleStat, ChartKindSingleStatPlusLine, ChartKindXY: + case ChartKindSingleStat, ChartKindSingleStatPlusLine, ChartKindXY, ChartKindGauge: return true default: return false @@ -519,6 +520,20 @@ func (c chart) properties() influxdb.ViewProperties { Axes: c.Axes.influxAxes(), Geom: c.Geom, } + case ChartKindGauge: + return influxdb.GaugeViewProperties{ + Type: "gauge", + Queries: c.Queries.influxDashQueries(), + Prefix: c.Prefix, + Suffix: c.Suffix, + ViewColors: c.Colors.influxViewColors(), + DecimalPlaces: influxdb.DecimalPlaces{ + IsEnforced: c.EnforceDecimals, + Digits: int32(c.DecimalPlaces), + }, + Note: c.Note, + ShowNoteWhenEmpty: c.NoteOnEmpty, + } default: return nil } @@ -547,6 +562,8 @@ func (c chart) validProperties() []failure { fails = append(fails, c.Colors.hasTypes(colorTypeScale)...) fails = append(fails, validGeometry(c.Geom)...) fails = append(fails, c.Axes.hasAxes("x", "y")...) + case ChartKindGauge: + fails = append(fails, c.Colors.hasTypes(colorTypeMin, colorTypeThreshold, colorTypeMax)...) } return fails @@ -589,8 +606,11 @@ func (c chart) validBaseProps() []failure { } const ( - colorTypeText = "text" - colorTypeScale = "scale" + colorTypeText = "text" + colorTypeScale = "scale" + colorTypeMin = "min" + colorTypeThreshold = "threshold" + colorTypeMax = "max" ) type color struct { diff --git a/pkger/parser_test.go b/pkger/parser_test.go index c455ab66a4..bf5b57939e 100644 --- a/pkger/parser_test.go +++ b/pkger/parser_test.go @@ -1413,6 +1413,272 @@ spec: }) }) + t.Run("pkg with single dashboard gauge chart", func(t *testing.T) { + t.Run("gauge chart", func(t *testing.T) { + testfileRunner(t, "testdata/dashboard_gauge", func(t *testing.T, pkg *Pkg) { + sum := pkg.Summary() + require.Len(t, sum.Dashboards, 1) + + actual := sum.Dashboards[0] + assert.Equal(t, "dash_1", actual.Name) + assert.Equal(t, "desc1", actual.Description) + + require.Len(t, actual.Charts, 1) + actualChart := actual.Charts[0] + assert.Equal(t, ChartKindGauge, actualChart.Kind) + assert.Equal(t, 3, actualChart.Height) + assert.Equal(t, 6, actualChart.Width) + assert.Equal(t, 1, actualChart.XPosition) + assert.Equal(t, 2, actualChart.YPosition) + + props, ok := actualChart.Properties.(influxdb.GaugeViewProperties) + require.True(t, ok) + assert.Equal(t, "gauge", props.GetType()) + assert.Equal(t, "gauge note", props.Note) + assert.True(t, props.ShowNoteWhenEmpty) + + require.Len(t, props.Queries, 1) + q := props.Queries[0] + queryText := `from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter")` + assert.Equal(t, queryText, q.Text) + assert.Equal(t, "advanced", q.EditMode) + + require.Len(t, props.ViewColors, 3) + c := props.ViewColors[0] + assert.NotZero(t, c.ID) + assert.Equal(t, "laser", c.Name) + assert.Equal(t, "min", c.Type) + assert.Equal(t, "#8F8AF4", c.Hex) + assert.Equal(t, 0.0, c.Value) + }) + + t.Run("handles invalid config", func(t *testing.T) { + tests := []struct { + name string + jsonStr string + numErrs int + errFields []string + }{ + { + name: "color a gauge type", + numErrs: 1, + errFields: []string{"charts[0].colors"}, + jsonStr: ` +{ + "apiVersion": "0.1.0", + "kind": "Package", + "meta": { + "pkgName": "pkg_name", + "pkgVersion": "1", + "description": "pack description" + }, + "spec": { + "resources": [ + { + "kind": "Dashboard", + "name": "dash_1", + "description": "desc1", + "charts": [ + { + "kind": "gauge", + "name": "gauge", + "prefix": "prefix", + "suffix": "suffix", + "note": "gauge note", + "noteOnEmpty": true, + "xPos": 1, + "yPos": 2, + "width": 6, + "height": 3, + "decimalPlaces": 1, + "xColumn": "_time", + "yColumn": "_value", + "queries": [ + { + "query": "from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"boltdb_writes_total\") |> filter(fn: (r) => r._field == \"counter\")" + } + ], + "colors": [ + { + "name": "laser", + "type": "min", + "hex": "#8F8AF4", + "value": 0 + }, + { + "name": "comet", + "type": "max", + "hex": "#F4CF31", + "value": 5000 + } + ] + } + ] + } + ] + } +} +`, + }, + { + name: "color mixing a hex value", + numErrs: 1, + errFields: []string{"charts[0].colors[0].hex"}, + jsonStr: ` +{ + "apiVersion": "0.1.0", + "kind": "Package", + "meta": { + "pkgName": "pkg_name", + "pkgVersion": "1", + "description": "pack description" + }, + "spec": { + "resources": [ + { + "kind": "Dashboard", + "name": "dash_1", + "description": "desc1", + "charts": [ + { + "kind": "gauge", + "name": "gauge", + "prefix": "prefix", + "suffix": "suffix", + "note": "gauge note", + "noteOnEmpty": true, + "xPos": 1, + "yPos": 2, + "width": 6, + "height": 3, + "decimalPlaces": 1, + "xColumn": "_time", + "yColumn": "_value", + "queries": [ + { + "query": "from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"boltdb_writes_total\") |> filter(fn: (r) => r._field == \"counter\")" + } + ], + "colors": [ + { + "name": "laser", + "type": "min", + "value": 0 + }, + { + "name": "pool", + "type": "threshold", + "hex": "#F4CF31", + "value": 700 + }, + { + "name": "comet", + "type": "max", + "hex": "#F4CF31", + "value": 5000 + } + ] + } + ] + } + ] + } +} +`, + }, + { + name: "missing a query value", + numErrs: 1, + errFields: []string{"charts[0].queries[0].query"}, + jsonStr: ` +{ + "apiVersion": "0.1.0", + "kind": "Package", + "meta": { + "pkgName": "pkg_name", + "pkgVersion": "1", + "description": "pack description" + }, + "spec": { + "resources": [ + { + "kind": "Dashboard", + "name": "dash_1", + "description": "desc1", + "charts": [ + { + "kind": "gauge", + "name": "gauge", + "prefix": "prefix", + "suffix": "suffix", + "note": "gauge note", + "noteOnEmpty": true, + "xPos": 1, + "yPos": 2, + "width": 6, + "height": 3, + "decimalPlaces": 1, + "xColumn": "_time", + "yColumn": "_value", + "queries": [ + { + "query": null + } + ], + "colors": [ + { + "name": "laser", + "type": "min", + "hex": "#FFF000", + "value": 0 + }, + { + "name": "pool", + "type": "threshold", + "hex": "#F4CF31", + "value": 700 + }, + { + "name": "comet", + "type": "max", + "hex": "#F4CF31", + "value": 5000 + } + ] + } + ] + } + ] + } +} +`, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + _, err := Parse(EncodingJSON, FromString(tt.jsonStr)) + require.Error(t, err) + + pErr, ok := IsParseErr(err) + require.True(t, ok, err) + + require.Len(t, pErr.Resources, 1) + + resErr := pErr.Resources[0] + assert.Equal(t, "dashboard", resErr.Kind) + + require.Len(t, resErr.ValidationFails, tt.numErrs) + for i, vFail := range resErr.ValidationFails { + assert.Equal(t, tt.errFields[i], vFail.Field) + } + } + t.Run(tt.name, fn) + } + }) + }) + }) + t.Run("pkg with dashboard and labels associated", func(t *testing.T) { testfileRunner(t, "testdata/dashboard_associates_label", func(t *testing.T, pkg *Pkg) { sum := pkg.Summary() diff --git a/pkger/testdata/dashboard_gauge.json b/pkger/testdata/dashboard_gauge.json new file mode 100644 index 0000000000..cb79c817a4 --- /dev/null +++ b/pkger/testdata/dashboard_gauge.json @@ -0,0 +1,60 @@ +{ + "apiVersion": "0.1.0", + "kind": "Package", + "meta": { + "pkgName": "pkg_name", + "pkgVersion": "1", + "description": "pack description" + }, + "spec": { + "resources": [ + { + "kind": "Dashboard", + "name": "dash_1", + "description": "desc1", + "charts": [ + { + "kind": "gauge", + "name": "gauge", + "prefix": "prefix", + "suffix": "suffix", + "note": "gauge note", + "noteOnEmpty": true, + "xPos": 1, + "yPos": 2, + "width": 6, + "height": 3, + "decimalPlaces": 1, + "xColumn": "_time", + "yColumn": "_value", + "queries": [ + { + "query": "from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"boltdb_writes_total\") |> filter(fn: (r) => r._field == \"counter\")" + } + ], + "colors": [ + { + "name": "laser", + "type": "min", + "hex": "#8F8AF4", + "value": 0 + }, + { + "name": "pool", + "type": "threshold", + "hex": "#F4CF31", + "value": 700 + }, + { + "name": "comet", + "type": "max", + "hex": "#F4CF31", + "value": 5000 + } + ] + } + ] + } + ] + } +} diff --git a/pkger/testdata/dashboard_gauge.yml b/pkger/testdata/dashboard_gauge.yml new file mode 100644 index 0000000000..e7745fa6a1 --- /dev/null +++ b/pkger/testdata/dashboard_gauge.yml @@ -0,0 +1,36 @@ +apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Dashboard + name: dash_1 + description: desc1 + charts: + - kind: gauge + name: gauge + note: gauge note + noteOnEmpty: true + xPos: 1 + yPos: 2 + width: 6 + height: 3 + queries: + - query: > + from(bucket: v.bucket) |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "boltdb_writes_total") |> filter(fn: (r) => r._field == "counter") + colors: + - name: laser + type: min + hex: "#8F8AF4" + value: 0 + - name: laser + type: threshold + hex: "#8F8AF4" + value: 700 + - name: laser + type: max + hex: "#8F8AF4" + value: 5000