chore(pkger): refactor parser dashboard state out into stateful type
references: #17434pull/17775/head
parent
f084a93fa2
commit
21694416a3
|
|
@ -7579,8 +7579,8 @@ components:
|
|||
items:
|
||||
type: object
|
||||
properties:
|
||||
remove:
|
||||
type: boolean
|
||||
stateStatus:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
pkgName:
|
||||
|
|
|
|||
759
pkger/models.go
759
pkger/models.go
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
|
|
@ -253,58 +252,6 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func newDiffDashboard(d *dashboard) DiffDashboard {
|
||||
diff := DiffDashboard{
|
||||
DiffIdentifier: DiffIdentifier{
|
||||
ID: SafeID(d.ID()),
|
||||
Remove: d.shouldRemove,
|
||||
PkgName: d.PkgName(),
|
||||
},
|
||||
New: DiffDashboardValues{
|
||||
Name: d.Name(),
|
||||
Desc: d.Description,
|
||||
Charts: make([]DiffChart, 0, len(d.Charts)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range d.Charts {
|
||||
diff.New.Charts = append(diff.New.Charts, DiffChart{
|
||||
Properties: c.properties(),
|
||||
Height: c.Height,
|
||||
Width: c.Width,
|
||||
})
|
||||
}
|
||||
|
||||
if !d.Exists() {
|
||||
return diff
|
||||
}
|
||||
|
||||
oldDiff := DiffDashboardValues{
|
||||
Name: d.existing.Name,
|
||||
Desc: d.existing.Description,
|
||||
Charts: make([]DiffChart, 0, len(d.existing.Cells)),
|
||||
}
|
||||
|
||||
for _, c := range d.existing.Cells {
|
||||
var props influxdb.ViewProperties
|
||||
if c.View != nil {
|
||||
props = c.View.Properties
|
||||
}
|
||||
|
||||
oldDiff.Charts = append(oldDiff.Charts, DiffChart{
|
||||
Properties: props,
|
||||
XPosition: int(c.X),
|
||||
YPosition: int(c.Y),
|
||||
Height: int(c.H),
|
||||
Width: int(c.W),
|
||||
})
|
||||
}
|
||||
|
||||
diff.Old = &oldDiff
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
// DiffChart is a diff of oa chart. Since all charts are new right now.
|
||||
// the SummaryChart is reused here.
|
||||
type DiffChart SummaryChart
|
||||
|
|
@ -606,41 +553,6 @@ type SummaryDashboard struct {
|
|||
LabelAssociations []SummaryLabel `json:"labelAssociations"`
|
||||
}
|
||||
|
||||
// chartKind identifies what kind of chart is eluded too. Each
|
||||
// chart kind has their own requirements for what constitutes
|
||||
// a chart.
|
||||
type chartKind string
|
||||
|
||||
// available chart kinds
|
||||
const (
|
||||
chartKindUnknown chartKind = ""
|
||||
chartKindGauge chartKind = "gauge"
|
||||
chartKindHeatMap chartKind = "heatmap"
|
||||
chartKindHistogram chartKind = "histogram"
|
||||
chartKindMarkdown chartKind = "markdown"
|
||||
chartKindScatter chartKind = "scatter"
|
||||
chartKindSingleStat chartKind = "single_stat"
|
||||
chartKindSingleStatPlusLine chartKind = "single_stat_plus_line"
|
||||
chartKindTable chartKind = "table"
|
||||
chartKindXY chartKind = "xy"
|
||||
)
|
||||
|
||||
func (c chartKind) ok() bool {
|
||||
switch c {
|
||||
case chartKindGauge, chartKindHeatMap, chartKindHistogram,
|
||||
chartKindMarkdown, chartKindScatter, chartKindSingleStat,
|
||||
chartKindSingleStatPlusLine, chartKindTable, chartKindXY:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c chartKind) title() string {
|
||||
spacedKind := strings.ReplaceAll(string(c), "_", " ")
|
||||
return strings.ReplaceAll(strings.Title(spacedKind), " ", "_")
|
||||
}
|
||||
|
||||
// SummaryChart provides a summary of a pkg dashboard's chart.
|
||||
type SummaryChart struct {
|
||||
Properties influxdb.ViewProperties `json:"-"`
|
||||
|
|
@ -1070,677 +982,6 @@ func (r mapperNotificationRules) Len() int {
|
|||
return len(r)
|
||||
}
|
||||
|
||||
const (
|
||||
fieldDashCharts = "charts"
|
||||
)
|
||||
|
||||
const dashboardNameMinLength = 2
|
||||
|
||||
type dashboard struct {
|
||||
identity
|
||||
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
Description string
|
||||
Charts []chart
|
||||
|
||||
labels sortedLabels
|
||||
|
||||
existing *influxdb.Dashboard
|
||||
}
|
||||
|
||||
func (d *dashboard) ID() influxdb.ID {
|
||||
if d.existing != nil {
|
||||
return d.existing.ID
|
||||
}
|
||||
return d.id
|
||||
}
|
||||
|
||||
func (d *dashboard) Labels() []*label {
|
||||
return d.labels
|
||||
}
|
||||
|
||||
func (d *dashboard) ResourceType() influxdb.ResourceType {
|
||||
return KindDashboard.ResourceType()
|
||||
}
|
||||
|
||||
func (d *dashboard) Exists() bool {
|
||||
return d.existing != nil
|
||||
}
|
||||
|
||||
func (d *dashboard) summarize() SummaryDashboard {
|
||||
iDash := SummaryDashboard{
|
||||
ID: SafeID(d.ID()),
|
||||
OrgID: SafeID(d.OrgID),
|
||||
PkgName: d.PkgName(),
|
||||
Name: d.Name(),
|
||||
Description: d.Description,
|
||||
LabelAssociations: toSummaryLabels(d.labels...),
|
||||
}
|
||||
for _, c := range d.Charts {
|
||||
iDash.Charts = append(iDash.Charts, SummaryChart{
|
||||
Properties: c.properties(),
|
||||
Height: c.Height,
|
||||
Width: c.Width,
|
||||
XPosition: c.XPos,
|
||||
YPosition: c.YPos,
|
||||
})
|
||||
}
|
||||
return iDash
|
||||
}
|
||||
|
||||
func (d *dashboard) valid() []validationErr {
|
||||
var vErrs []validationErr
|
||||
if err, ok := isValidName(d.Name(), dashboardNameMinLength); !ok {
|
||||
vErrs = append(vErrs, err)
|
||||
}
|
||||
if len(vErrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return []validationErr{
|
||||
objectValidationErr(fieldSpec, vErrs...),
|
||||
}
|
||||
}
|
||||
|
||||
type mapperDashboards []*dashboard
|
||||
|
||||
func (m mapperDashboards) Association(i int) labelAssociater {
|
||||
return m[i]
|
||||
}
|
||||
|
||||
func (m mapperDashboards) Len() int {
|
||||
return len(m)
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartAxes = "axes"
|
||||
fieldChartBinCount = "binCount"
|
||||
fieldChartBinSize = "binSize"
|
||||
fieldChartColors = "colors"
|
||||
fieldChartDecimalPlaces = "decimalPlaces"
|
||||
fieldChartDomain = "domain"
|
||||
fieldChartGeom = "geom"
|
||||
fieldChartHeight = "height"
|
||||
fieldChartLegend = "legend"
|
||||
fieldChartNote = "note"
|
||||
fieldChartNoteOnEmpty = "noteOnEmpty"
|
||||
fieldChartPosition = "position"
|
||||
fieldChartQueries = "queries"
|
||||
fieldChartShade = "shade"
|
||||
fieldChartFieldOptions = "fieldOptions"
|
||||
fieldChartTableOptions = "tableOptions"
|
||||
fieldChartTickPrefix = "tickPrefix"
|
||||
fieldChartTickSuffix = "tickSuffix"
|
||||
fieldChartTimeFormat = "timeFormat"
|
||||
fieldChartWidth = "width"
|
||||
fieldChartXCol = "xCol"
|
||||
fieldChartXPos = "xPos"
|
||||
fieldChartYCol = "yCol"
|
||||
fieldChartYPos = "yPos"
|
||||
)
|
||||
|
||||
type chart struct {
|
||||
Kind chartKind
|
||||
Name string
|
||||
Prefix string
|
||||
TickPrefix string
|
||||
Suffix string
|
||||
TickSuffix string
|
||||
Note string
|
||||
NoteOnEmpty bool
|
||||
DecimalPlaces int
|
||||
EnforceDecimals bool
|
||||
Shade bool
|
||||
Legend legend
|
||||
Colors colors
|
||||
Queries queries
|
||||
Axes axes
|
||||
Geom string
|
||||
XCol, YCol string
|
||||
XPos, YPos int
|
||||
Height, Width int
|
||||
BinSize int
|
||||
BinCount int
|
||||
Position string
|
||||
FieldOptions []fieldOption
|
||||
TableOptions tableOptions
|
||||
TimeFormat string
|
||||
}
|
||||
|
||||
func (c chart) properties() influxdb.ViewProperties {
|
||||
switch c.Kind {
|
||||
case chartKindGauge:
|
||||
return influxdb.GaugeViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeGauge,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
Prefix: c.Prefix,
|
||||
TickPrefix: c.TickPrefix,
|
||||
Suffix: c.Suffix,
|
||||
TickSuffix: c.TickSuffix,
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
}
|
||||
case chartKindHeatMap:
|
||||
return influxdb.HeatmapViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeHeatMap,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.strings(),
|
||||
BinSize: int32(c.BinSize),
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
XDomain: c.Axes.get("x").Domain,
|
||||
YDomain: c.Axes.get("y").Domain,
|
||||
XPrefix: c.Axes.get("x").Prefix,
|
||||
YPrefix: c.Axes.get("y").Prefix,
|
||||
XSuffix: c.Axes.get("x").Suffix,
|
||||
YSuffix: c.Axes.get("y").Suffix,
|
||||
XAxisLabel: c.Axes.get("x").Label,
|
||||
YAxisLabel: c.Axes.get("y").Label,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
case chartKindHistogram:
|
||||
return influxdb.HistogramViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeHistogram,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
FillColumns: []string{},
|
||||
XColumn: c.XCol,
|
||||
XDomain: c.Axes.get("x").Domain,
|
||||
XAxisLabel: c.Axes.get("x").Label,
|
||||
Position: c.Position,
|
||||
BinCount: c.BinCount,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
}
|
||||
case chartKindMarkdown:
|
||||
return influxdb.MarkdownViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeMarkdown,
|
||||
Note: c.Note,
|
||||
}
|
||||
case chartKindScatter:
|
||||
return influxdb.ScatterViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeScatter,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.strings(),
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
XDomain: c.Axes.get("x").Domain,
|
||||
YDomain: c.Axes.get("y").Domain,
|
||||
XPrefix: c.Axes.get("x").Prefix,
|
||||
YPrefix: c.Axes.get("y").Prefix,
|
||||
XSuffix: c.Axes.get("x").Suffix,
|
||||
YSuffix: c.Axes.get("y").Suffix,
|
||||
XAxisLabel: c.Axes.get("x").Label,
|
||||
YAxisLabel: c.Axes.get("y").Label,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
case chartKindSingleStat:
|
||||
return influxdb.SingleStatViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStat,
|
||||
Prefix: c.Prefix,
|
||||
TickPrefix: c.TickPrefix,
|
||||
Suffix: c.Suffix,
|
||||
TickSuffix: c.TickSuffix,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
}
|
||||
case chartKindSingleStatPlusLine:
|
||||
return influxdb.LinePlusSingleStatProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStatPlusLine,
|
||||
Prefix: c.Prefix,
|
||||
Suffix: c.Suffix,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
ShadeBelow: c.Shade,
|
||||
Legend: c.Legend.influxLegend(),
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
Axes: c.Axes.influxAxes(),
|
||||
Position: c.Position,
|
||||
}
|
||||
case chartKindTable:
|
||||
fieldOptions := make([]influxdb.RenamableField, 0, len(c.FieldOptions))
|
||||
for _, fieldOpt := range c.FieldOptions {
|
||||
fieldOptions = append(fieldOptions, influxdb.RenamableField{
|
||||
InternalName: fieldOpt.FieldName,
|
||||
DisplayName: fieldOpt.DisplayName,
|
||||
Visible: fieldOpt.Visible,
|
||||
})
|
||||
}
|
||||
|
||||
return influxdb.TableViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeTable,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
TableOptions: influxdb.TableOptions{
|
||||
VerticalTimeAxis: c.TableOptions.VerticalTimeAxis,
|
||||
SortBy: influxdb.RenamableField{
|
||||
InternalName: c.TableOptions.SortByField,
|
||||
},
|
||||
Wrapping: c.TableOptions.Wrapping,
|
||||
FixFirstColumn: c.TableOptions.FixFirstColumn,
|
||||
},
|
||||
FieldOptions: fieldOptions,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
case chartKindXY:
|
||||
return influxdb.XYViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeXY,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
ShadeBelow: c.Shade,
|
||||
Legend: c.Legend.influxLegend(),
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
Axes: c.Axes.influxAxes(),
|
||||
Geom: c.Geom,
|
||||
Position: c.Position,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c chart) validProperties() []validationErr {
|
||||
if c.Kind == chartKindMarkdown {
|
||||
// at the time of writing, there's nothing to validate for markdown types
|
||||
return nil
|
||||
}
|
||||
|
||||
var fails []validationErr
|
||||
|
||||
validatorFns := []func() []validationErr{
|
||||
c.validBaseProps,
|
||||
c.Queries.valid,
|
||||
c.Colors.valid,
|
||||
}
|
||||
for _, validatorFn := range validatorFns {
|
||||
fails = append(fails, validatorFn()...)
|
||||
}
|
||||
|
||||
// chart kind specific validations
|
||||
switch c.Kind {
|
||||
case chartKindGauge:
|
||||
fails = append(fails, c.Colors.hasTypes(colorTypeMin, colorTypeMax)...)
|
||||
case chartKindHeatMap:
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
case chartKindHistogram:
|
||||
fails = append(fails, c.Axes.hasAxes("x")...)
|
||||
case chartKindScatter:
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
case chartKindSingleStat:
|
||||
case chartKindSingleStatPlusLine:
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
fails = append(fails, validPosition(c.Position)...)
|
||||
case chartKindTable:
|
||||
fails = append(fails, validTableOptions(c.TableOptions)...)
|
||||
case chartKindXY:
|
||||
fails = append(fails, validGeometry(c.Geom)...)
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
fails = append(fails, validPosition(c.Position)...)
|
||||
}
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
func validPosition(pos string) []validationErr {
|
||||
pos = strings.ToLower(pos)
|
||||
if pos != "" && pos != "overlaid" && pos != "stacked" {
|
||||
return []validationErr{{
|
||||
Field: fieldChartPosition,
|
||||
Msg: fmt.Sprintf("invalid position supplied %q; valid positions is one of [overlaid, stacked]", pos),
|
||||
}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var geometryTypes = map[string]bool{
|
||||
"line": true,
|
||||
"step": true,
|
||||
"stacked": true,
|
||||
"monotoneX": true,
|
||||
"bar": true,
|
||||
}
|
||||
|
||||
func validGeometry(geom string) []validationErr {
|
||||
if !geometryTypes[geom] {
|
||||
msg := "type not found"
|
||||
if geom != "" {
|
||||
msg = "type provided is not supported"
|
||||
}
|
||||
return []validationErr{{
|
||||
Field: fieldChartGeom,
|
||||
Msg: fmt.Sprintf("%s: %q", msg, geom),
|
||||
}}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c chart) validBaseProps() []validationErr {
|
||||
var fails []validationErr
|
||||
if c.Width <= 0 {
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartWidth,
|
||||
Msg: "must be greater than 0",
|
||||
})
|
||||
}
|
||||
|
||||
if c.Height <= 0 {
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartHeight,
|
||||
Msg: "must be greater than 0",
|
||||
})
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartFieldOptionDisplayName = "displayName"
|
||||
fieldChartFieldOptionFieldName = "fieldName"
|
||||
fieldChartFieldOptionVisible = "visible"
|
||||
)
|
||||
|
||||
type fieldOption struct {
|
||||
FieldName string
|
||||
DisplayName string
|
||||
Visible bool
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartTableOptionVerticalTimeAxis = "verticalTimeAxis"
|
||||
fieldChartTableOptionSortBy = "sortBy"
|
||||
fieldChartTableOptionWrapping = "wrapping"
|
||||
fieldChartTableOptionFixFirstColumn = "fixFirstColumn"
|
||||
)
|
||||
|
||||
type tableOptions struct {
|
||||
VerticalTimeAxis bool
|
||||
SortByField string
|
||||
Wrapping string
|
||||
FixFirstColumn bool
|
||||
}
|
||||
|
||||
func validTableOptions(opts tableOptions) []validationErr {
|
||||
var fails []validationErr
|
||||
|
||||
switch opts.Wrapping {
|
||||
case "", "single-line", "truncate", "wrap":
|
||||
default:
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartTableOptionWrapping,
|
||||
Msg: `chart table option should 1 in ["single-line", "truncate", "wrap"]`,
|
||||
})
|
||||
}
|
||||
|
||||
if len(fails) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []validationErr{
|
||||
{
|
||||
Field: fieldChartTableOptions,
|
||||
Nested: fails,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
colorTypeBackground = "background"
|
||||
colorTypeMin = "min"
|
||||
colorTypeMax = "max"
|
||||
colorTypeScale = "scale"
|
||||
colorTypeText = "text"
|
||||
colorTypeThreshold = "threshold"
|
||||
)
|
||||
|
||||
const (
|
||||
fieldColorHex = "hex"
|
||||
)
|
||||
|
||||
type color struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Hex string `json:"hex,omitempty" yaml:"hex,omitempty"`
|
||||
// using reference for Value here so we can set to nil and
|
||||
// it will be ignored during encoding, keeps our exported pkgs
|
||||
// clear of unneeded entries.
|
||||
Value *float64 `json:"value,omitempty" yaml:"value,omitempty"`
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - verify templates are desired
|
||||
// - template colors so references can be shared
|
||||
type colors []*color
|
||||
|
||||
func (c colors) influxViewColors() []influxdb.ViewColor {
|
||||
ptrToFloat64 := func(f *float64) float64 {
|
||||
if f == nil {
|
||||
return 0
|
||||
}
|
||||
return *f
|
||||
}
|
||||
|
||||
var iColors []influxdb.ViewColor
|
||||
for _, cc := range c {
|
||||
iColors = append(iColors, influxdb.ViewColor{
|
||||
Type: cc.Type,
|
||||
Hex: cc.Hex,
|
||||
Name: cc.Name,
|
||||
Value: ptrToFloat64(cc.Value),
|
||||
})
|
||||
}
|
||||
return iColors
|
||||
}
|
||||
|
||||
func (c colors) strings() []string {
|
||||
clrs := []string{}
|
||||
|
||||
for _, clr := range c {
|
||||
clrs = append(clrs, clr.Hex)
|
||||
}
|
||||
|
||||
return clrs
|
||||
}
|
||||
|
||||
// TODO: looks like much of these are actually getting defaults in
|
||||
// the UI. looking at sytem charts, seeign lots of failures for missing
|
||||
// color types or no colors at all.
|
||||
func (c colors) hasTypes(types ...string) []validationErr {
|
||||
tMap := make(map[string]bool)
|
||||
for _, cc := range c {
|
||||
tMap[cc.Type] = true
|
||||
}
|
||||
|
||||
var failures []validationErr
|
||||
for _, t := range types {
|
||||
if !tMap[t] {
|
||||
failures = append(failures, validationErr{
|
||||
Field: "colors",
|
||||
Msg: fmt.Sprintf("type not found: %q", t),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
func (c colors) valid() []validationErr {
|
||||
var fails []validationErr
|
||||
for i, cc := range c {
|
||||
cErr := validationErr{
|
||||
Field: fieldChartColors,
|
||||
Index: intPtr(i),
|
||||
}
|
||||
if cc.Hex == "" {
|
||||
cErr.Nested = append(cErr.Nested, validationErr{
|
||||
Field: fieldColorHex,
|
||||
Msg: "a color must have a hex value provided",
|
||||
})
|
||||
}
|
||||
if len(cErr.Nested) > 0 {
|
||||
fails = append(fails, cErr)
|
||||
}
|
||||
}
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
type query struct {
|
||||
Query string `json:"query" yaml:"query"`
|
||||
}
|
||||
|
||||
type queries []query
|
||||
|
||||
func (q queries) influxDashQueries() []influxdb.DashboardQuery {
|
||||
var iQueries []influxdb.DashboardQuery
|
||||
for _, qq := range q {
|
||||
newQuery := influxdb.DashboardQuery{
|
||||
Text: qq.Query,
|
||||
EditMode: "advanced",
|
||||
}
|
||||
// TODO: axe this builder configs when issue https://github.com/influxdata/influxdb/issues/15708 is fixed up
|
||||
newQuery.BuilderConfig.Tags = append(newQuery.BuilderConfig.Tags, influxdb.NewBuilderTag("_measurement", "filter", ""))
|
||||
iQueries = append(iQueries, newQuery)
|
||||
}
|
||||
return iQueries
|
||||
}
|
||||
|
||||
func (q queries) valid() []validationErr {
|
||||
var fails []validationErr
|
||||
if len(q) == 0 {
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartQueries,
|
||||
Msg: "at least 1 query must be provided",
|
||||
})
|
||||
}
|
||||
|
||||
for i, qq := range q {
|
||||
qErr := validationErr{
|
||||
Field: fieldChartQueries,
|
||||
Index: intPtr(i),
|
||||
}
|
||||
if qq.Query == "" {
|
||||
qErr.Nested = append(fails, validationErr{
|
||||
Field: fieldQuery,
|
||||
Msg: "a query must be provided",
|
||||
})
|
||||
}
|
||||
if len(qErr.Nested) > 0 {
|
||||
fails = append(fails, qErr)
|
||||
}
|
||||
}
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
const (
|
||||
fieldAxisBase = "base"
|
||||
fieldAxisLabel = "label"
|
||||
fieldAxisScale = "scale"
|
||||
)
|
||||
|
||||
type axis struct {
|
||||
Base string `json:"base,omitempty" yaml:"base,omitempty"`
|
||||
Label string `json:"label,omitempty" yaml:"label,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Scale string `json:"scale,omitempty" yaml:"scale,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
Domain []float64 `json:"domain,omitempty" yaml:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type axes []axis
|
||||
|
||||
func (a axes) get(name string) axis {
|
||||
for _, ax := range a {
|
||||
if name == ax.Name {
|
||||
return ax
|
||||
}
|
||||
}
|
||||
return axis{}
|
||||
}
|
||||
|
||||
func (a axes) influxAxes() map[string]influxdb.Axis {
|
||||
m := make(map[string]influxdb.Axis)
|
||||
for _, ax := range a {
|
||||
m[ax.Name] = influxdb.Axis{
|
||||
Bounds: []string{},
|
||||
Label: ax.Label,
|
||||
Prefix: ax.Prefix,
|
||||
Suffix: ax.Suffix,
|
||||
Base: ax.Base,
|
||||
Scale: ax.Scale,
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (a axes) hasAxes(expectedAxes ...string) []validationErr {
|
||||
mAxes := make(map[string]bool)
|
||||
for _, ax := range a {
|
||||
mAxes[ax.Name] = true
|
||||
}
|
||||
|
||||
var failures []validationErr
|
||||
for _, expected := range expectedAxes {
|
||||
if !mAxes[expected] {
|
||||
failures = append(failures, validationErr{
|
||||
Field: fieldChartAxes,
|
||||
Msg: fmt.Sprintf("axis not found: %q", expected),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
const (
|
||||
fieldLegendOrientation = "orientation"
|
||||
)
|
||||
|
||||
type legend struct {
|
||||
Orientation string `json:"orientation,omitempty" yaml:"orientation,omitempty"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
}
|
||||
|
||||
func (l legend) influxLegend() influxdb.Legend {
|
||||
return influxdb.Legend{
|
||||
Type: l.Type,
|
||||
Orientation: l.Orientation,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
fieldReferencesEnv = "envRef"
|
||||
fieldReferencesSecret = "secretRef"
|
||||
|
|
|
|||
|
|
@ -476,11 +476,6 @@ func TestPkg(t *testing.T) {
|
|||
kind Kind
|
||||
validName string
|
||||
}{
|
||||
{
|
||||
pkgFile: "testdata/dashboard.yml",
|
||||
kind: KindDashboard,
|
||||
validName: "dash_1",
|
||||
},
|
||||
{
|
||||
pkgFile: "testdata/label.yml",
|
||||
kind: KindLabel,
|
||||
|
|
|
|||
|
|
@ -409,11 +409,6 @@ func (p *Pkg) addObjectForRemoval(k Kind, pkgName string, id influxdb.ID) {
|
|||
}
|
||||
|
||||
switch k {
|
||||
case KindDashboard:
|
||||
p.mDashboards[pkgName] = &dashboard{
|
||||
identity: newIdentity,
|
||||
id: id,
|
||||
}
|
||||
case KindLabel:
|
||||
p.mLabels[pkgName] = &label{
|
||||
identity: newIdentity,
|
||||
|
|
@ -429,11 +424,6 @@ func (p *Pkg) addObjectForRemoval(k Kind, pkgName string, id influxdb.ID) {
|
|||
|
||||
func (p *Pkg) getObjectIDSetter(k Kind, pkgName string) (func(influxdb.ID), bool) {
|
||||
switch k {
|
||||
case KindDashboard:
|
||||
d, ok := p.mDashboards[pkgName]
|
||||
return func(id influxdb.ID) {
|
||||
d.id = id
|
||||
}, ok
|
||||
case KindLabel:
|
||||
l, ok := p.mLabels[pkgName]
|
||||
return func(id influxdb.ID) {
|
||||
|
|
@ -558,7 +548,7 @@ func (p *Pkg) dashboards() []*dashboard {
|
|||
for _, d := range p.mDashboards {
|
||||
dashes = append(dashes, d)
|
||||
}
|
||||
sort.Slice(dashes, func(i, j int) bool { return dashes[i].Name() < dashes[j].Name() })
|
||||
sort.Slice(dashes, func(i, j int) bool { return dashes[i].PkgName() < dashes[j].PkgName() })
|
||||
return dashes
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,685 @@ func (c *check) valid() []validationErr {
|
|||
return nil
|
||||
}
|
||||
|
||||
// chartKind identifies what kind of chart is eluded too. Each
|
||||
// chart kind has their own requirements for what constitutes
|
||||
// a chart.
|
||||
type chartKind string
|
||||
|
||||
// available chart kinds
|
||||
const (
|
||||
chartKindUnknown chartKind = ""
|
||||
chartKindGauge chartKind = "gauge"
|
||||
chartKindHeatMap chartKind = "heatmap"
|
||||
chartKindHistogram chartKind = "histogram"
|
||||
chartKindMarkdown chartKind = "markdown"
|
||||
chartKindScatter chartKind = "scatter"
|
||||
chartKindSingleStat chartKind = "single_stat"
|
||||
chartKindSingleStatPlusLine chartKind = "single_stat_plus_line"
|
||||
chartKindTable chartKind = "table"
|
||||
chartKindXY chartKind = "xy"
|
||||
)
|
||||
|
||||
func (c chartKind) ok() bool {
|
||||
switch c {
|
||||
case chartKindGauge, chartKindHeatMap, chartKindHistogram,
|
||||
chartKindMarkdown, chartKindScatter, chartKindSingleStat,
|
||||
chartKindSingleStatPlusLine, chartKindTable, chartKindXY:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c chartKind) title() string {
|
||||
spacedKind := strings.ReplaceAll(string(c), "_", " ")
|
||||
return strings.ReplaceAll(strings.Title(spacedKind), " ", "_")
|
||||
}
|
||||
|
||||
const (
|
||||
fieldDashCharts = "charts"
|
||||
)
|
||||
|
||||
const dashboardNameMinLength = 2
|
||||
|
||||
type dashboard struct {
|
||||
identity
|
||||
|
||||
Description string
|
||||
Charts []chart
|
||||
|
||||
labels sortedLabels
|
||||
}
|
||||
|
||||
func (d *dashboard) Labels() []*label {
|
||||
return d.labels
|
||||
}
|
||||
|
||||
func (d *dashboard) ResourceType() influxdb.ResourceType {
|
||||
return KindDashboard.ResourceType()
|
||||
}
|
||||
|
||||
func (d *dashboard) summarize() SummaryDashboard {
|
||||
iDash := SummaryDashboard{
|
||||
PkgName: d.PkgName(),
|
||||
Name: d.Name(),
|
||||
Description: d.Description,
|
||||
LabelAssociations: toSummaryLabels(d.labels...),
|
||||
}
|
||||
for _, c := range d.Charts {
|
||||
iDash.Charts = append(iDash.Charts, SummaryChart{
|
||||
Properties: c.properties(),
|
||||
Height: c.Height,
|
||||
Width: c.Width,
|
||||
XPosition: c.XPos,
|
||||
YPosition: c.YPos,
|
||||
})
|
||||
}
|
||||
return iDash
|
||||
}
|
||||
|
||||
func (d *dashboard) valid() []validationErr {
|
||||
var vErrs []validationErr
|
||||
if err, ok := isValidName(d.Name(), dashboardNameMinLength); !ok {
|
||||
vErrs = append(vErrs, err)
|
||||
}
|
||||
if len(vErrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return []validationErr{
|
||||
objectValidationErr(fieldSpec, vErrs...),
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartAxes = "axes"
|
||||
fieldChartBinCount = "binCount"
|
||||
fieldChartBinSize = "binSize"
|
||||
fieldChartColors = "colors"
|
||||
fieldChartDecimalPlaces = "decimalPlaces"
|
||||
fieldChartDomain = "domain"
|
||||
fieldChartGeom = "geom"
|
||||
fieldChartHeight = "height"
|
||||
fieldChartLegend = "legend"
|
||||
fieldChartNote = "note"
|
||||
fieldChartNoteOnEmpty = "noteOnEmpty"
|
||||
fieldChartPosition = "position"
|
||||
fieldChartQueries = "queries"
|
||||
fieldChartShade = "shade"
|
||||
fieldChartFieldOptions = "fieldOptions"
|
||||
fieldChartTableOptions = "tableOptions"
|
||||
fieldChartTickPrefix = "tickPrefix"
|
||||
fieldChartTickSuffix = "tickSuffix"
|
||||
fieldChartTimeFormat = "timeFormat"
|
||||
fieldChartWidth = "width"
|
||||
fieldChartXCol = "xCol"
|
||||
fieldChartXPos = "xPos"
|
||||
fieldChartYCol = "yCol"
|
||||
fieldChartYPos = "yPos"
|
||||
)
|
||||
|
||||
type chart struct {
|
||||
Kind chartKind
|
||||
Name string
|
||||
Prefix string
|
||||
TickPrefix string
|
||||
Suffix string
|
||||
TickSuffix string
|
||||
Note string
|
||||
NoteOnEmpty bool
|
||||
DecimalPlaces int
|
||||
EnforceDecimals bool
|
||||
Shade bool
|
||||
Legend legend
|
||||
Colors colors
|
||||
Queries queries
|
||||
Axes axes
|
||||
Geom string
|
||||
XCol, YCol string
|
||||
XPos, YPos int
|
||||
Height, Width int
|
||||
BinSize int
|
||||
BinCount int
|
||||
Position string
|
||||
FieldOptions []fieldOption
|
||||
TableOptions tableOptions
|
||||
TimeFormat string
|
||||
}
|
||||
|
||||
func (c chart) properties() influxdb.ViewProperties {
|
||||
switch c.Kind {
|
||||
case chartKindGauge:
|
||||
return influxdb.GaugeViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeGauge,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
Prefix: c.Prefix,
|
||||
TickPrefix: c.TickPrefix,
|
||||
Suffix: c.Suffix,
|
||||
TickSuffix: c.TickSuffix,
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
}
|
||||
case chartKindHeatMap:
|
||||
return influxdb.HeatmapViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeHeatMap,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.strings(),
|
||||
BinSize: int32(c.BinSize),
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
XDomain: c.Axes.get("x").Domain,
|
||||
YDomain: c.Axes.get("y").Domain,
|
||||
XPrefix: c.Axes.get("x").Prefix,
|
||||
YPrefix: c.Axes.get("y").Prefix,
|
||||
XSuffix: c.Axes.get("x").Suffix,
|
||||
YSuffix: c.Axes.get("y").Suffix,
|
||||
XAxisLabel: c.Axes.get("x").Label,
|
||||
YAxisLabel: c.Axes.get("y").Label,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
case chartKindHistogram:
|
||||
return influxdb.HistogramViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeHistogram,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
FillColumns: []string{},
|
||||
XColumn: c.XCol,
|
||||
XDomain: c.Axes.get("x").Domain,
|
||||
XAxisLabel: c.Axes.get("x").Label,
|
||||
Position: c.Position,
|
||||
BinCount: c.BinCount,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
}
|
||||
case chartKindMarkdown:
|
||||
return influxdb.MarkdownViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeMarkdown,
|
||||
Note: c.Note,
|
||||
}
|
||||
case chartKindScatter:
|
||||
return influxdb.ScatterViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeScatter,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.strings(),
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
XDomain: c.Axes.get("x").Domain,
|
||||
YDomain: c.Axes.get("y").Domain,
|
||||
XPrefix: c.Axes.get("x").Prefix,
|
||||
YPrefix: c.Axes.get("y").Prefix,
|
||||
XSuffix: c.Axes.get("x").Suffix,
|
||||
YSuffix: c.Axes.get("y").Suffix,
|
||||
XAxisLabel: c.Axes.get("x").Label,
|
||||
YAxisLabel: c.Axes.get("y").Label,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
case chartKindSingleStat:
|
||||
return influxdb.SingleStatViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStat,
|
||||
Prefix: c.Prefix,
|
||||
TickPrefix: c.TickPrefix,
|
||||
Suffix: c.Suffix,
|
||||
TickSuffix: c.TickSuffix,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
}
|
||||
case chartKindSingleStatPlusLine:
|
||||
return influxdb.LinePlusSingleStatProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStatPlusLine,
|
||||
Prefix: c.Prefix,
|
||||
Suffix: c.Suffix,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
ShadeBelow: c.Shade,
|
||||
Legend: c.Legend.influxLegend(),
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
Axes: c.Axes.influxAxes(),
|
||||
Position: c.Position,
|
||||
}
|
||||
case chartKindTable:
|
||||
fieldOptions := make([]influxdb.RenamableField, 0, len(c.FieldOptions))
|
||||
for _, fieldOpt := range c.FieldOptions {
|
||||
fieldOptions = append(fieldOptions, influxdb.RenamableField{
|
||||
InternalName: fieldOpt.FieldName,
|
||||
DisplayName: fieldOpt.DisplayName,
|
||||
Visible: fieldOpt.Visible,
|
||||
})
|
||||
}
|
||||
|
||||
return influxdb.TableViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeTable,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
TableOptions: influxdb.TableOptions{
|
||||
VerticalTimeAxis: c.TableOptions.VerticalTimeAxis,
|
||||
SortBy: influxdb.RenamableField{
|
||||
InternalName: c.TableOptions.SortByField,
|
||||
},
|
||||
Wrapping: c.TableOptions.Wrapping,
|
||||
FixFirstColumn: c.TableOptions.FixFirstColumn,
|
||||
},
|
||||
FieldOptions: fieldOptions,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
case chartKindXY:
|
||||
return influxdb.XYViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeXY,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
XColumn: c.XCol,
|
||||
YColumn: c.YCol,
|
||||
ShadeBelow: c.Shade,
|
||||
Legend: c.Legend.influxLegend(),
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
Axes: c.Axes.influxAxes(),
|
||||
Geom: c.Geom,
|
||||
Position: c.Position,
|
||||
TimeFormat: c.TimeFormat,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c chart) validProperties() []validationErr {
|
||||
if c.Kind == chartKindMarkdown {
|
||||
// at the time of writing, there's nothing to validate for markdown types
|
||||
return nil
|
||||
}
|
||||
|
||||
var fails []validationErr
|
||||
|
||||
validatorFns := []func() []validationErr{
|
||||
c.validBaseProps,
|
||||
c.Queries.valid,
|
||||
c.Colors.valid,
|
||||
}
|
||||
for _, validatorFn := range validatorFns {
|
||||
fails = append(fails, validatorFn()...)
|
||||
}
|
||||
|
||||
// chart kind specific validations
|
||||
switch c.Kind {
|
||||
case chartKindGauge:
|
||||
fails = append(fails, c.Colors.hasTypes(colorTypeMin, colorTypeMax)...)
|
||||
case chartKindHeatMap:
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
case chartKindHistogram:
|
||||
fails = append(fails, c.Axes.hasAxes("x")...)
|
||||
case chartKindScatter:
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
case chartKindSingleStat:
|
||||
case chartKindSingleStatPlusLine:
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
fails = append(fails, validPosition(c.Position)...)
|
||||
case chartKindTable:
|
||||
fails = append(fails, validTableOptions(c.TableOptions)...)
|
||||
case chartKindXY:
|
||||
fails = append(fails, validGeometry(c.Geom)...)
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
fails = append(fails, validPosition(c.Position)...)
|
||||
}
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
func validPosition(pos string) []validationErr {
|
||||
pos = strings.ToLower(pos)
|
||||
if pos != "" && pos != "overlaid" && pos != "stacked" {
|
||||
return []validationErr{{
|
||||
Field: fieldChartPosition,
|
||||
Msg: fmt.Sprintf("invalid position supplied %q; valid positions is one of [overlaid, stacked]", pos),
|
||||
}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var geometryTypes = map[string]bool{
|
||||
"line": true,
|
||||
"step": true,
|
||||
"stacked": true,
|
||||
"monotoneX": true,
|
||||
"bar": true,
|
||||
}
|
||||
|
||||
func validGeometry(geom string) []validationErr {
|
||||
if !geometryTypes[geom] {
|
||||
msg := "type not found"
|
||||
if geom != "" {
|
||||
msg = "type provided is not supported"
|
||||
}
|
||||
return []validationErr{{
|
||||
Field: fieldChartGeom,
|
||||
Msg: fmt.Sprintf("%s: %q", msg, geom),
|
||||
}}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c chart) validBaseProps() []validationErr {
|
||||
var fails []validationErr
|
||||
if c.Width <= 0 {
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartWidth,
|
||||
Msg: "must be greater than 0",
|
||||
})
|
||||
}
|
||||
|
||||
if c.Height <= 0 {
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartHeight,
|
||||
Msg: "must be greater than 0",
|
||||
})
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartFieldOptionDisplayName = "displayName"
|
||||
fieldChartFieldOptionFieldName = "fieldName"
|
||||
fieldChartFieldOptionVisible = "visible"
|
||||
)
|
||||
|
||||
type fieldOption struct {
|
||||
FieldName string
|
||||
DisplayName string
|
||||
Visible bool
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartTableOptionVerticalTimeAxis = "verticalTimeAxis"
|
||||
fieldChartTableOptionSortBy = "sortBy"
|
||||
fieldChartTableOptionWrapping = "wrapping"
|
||||
fieldChartTableOptionFixFirstColumn = "fixFirstColumn"
|
||||
)
|
||||
|
||||
type tableOptions struct {
|
||||
VerticalTimeAxis bool
|
||||
SortByField string
|
||||
Wrapping string
|
||||
FixFirstColumn bool
|
||||
}
|
||||
|
||||
func validTableOptions(opts tableOptions) []validationErr {
|
||||
var fails []validationErr
|
||||
|
||||
switch opts.Wrapping {
|
||||
case "", "single-line", "truncate", "wrap":
|
||||
default:
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartTableOptionWrapping,
|
||||
Msg: `chart table option should 1 in ["single-line", "truncate", "wrap"]`,
|
||||
})
|
||||
}
|
||||
|
||||
if len(fails) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []validationErr{
|
||||
{
|
||||
Field: fieldChartTableOptions,
|
||||
Nested: fails,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
colorTypeBackground = "background"
|
||||
colorTypeMin = "min"
|
||||
colorTypeMax = "max"
|
||||
colorTypeScale = "scale"
|
||||
colorTypeText = "text"
|
||||
colorTypeThreshold = "threshold"
|
||||
)
|
||||
|
||||
const (
|
||||
fieldColorHex = "hex"
|
||||
)
|
||||
|
||||
type color struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Hex string `json:"hex,omitempty" yaml:"hex,omitempty"`
|
||||
// using reference for Value here so we can set to nil and
|
||||
// it will be ignored during encoding, keeps our exported pkgs
|
||||
// clear of unneeded entries.
|
||||
Value *float64 `json:"value,omitempty" yaml:"value,omitempty"`
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - verify templates are desired
|
||||
// - template colors so references can be shared
|
||||
type colors []*color
|
||||
|
||||
func (c colors) influxViewColors() []influxdb.ViewColor {
|
||||
ptrToFloat64 := func(f *float64) float64 {
|
||||
if f == nil {
|
||||
return 0
|
||||
}
|
||||
return *f
|
||||
}
|
||||
|
||||
var iColors []influxdb.ViewColor
|
||||
for _, cc := range c {
|
||||
iColors = append(iColors, influxdb.ViewColor{
|
||||
Type: cc.Type,
|
||||
Hex: cc.Hex,
|
||||
Name: cc.Name,
|
||||
Value: ptrToFloat64(cc.Value),
|
||||
})
|
||||
}
|
||||
return iColors
|
||||
}
|
||||
|
||||
func (c colors) strings() []string {
|
||||
clrs := []string{}
|
||||
|
||||
for _, clr := range c {
|
||||
clrs = append(clrs, clr.Hex)
|
||||
}
|
||||
|
||||
return clrs
|
||||
}
|
||||
|
||||
// TODO: looks like much of these are actually getting defaults in
|
||||
// the UI. looking at sytem charts, seeign lots of failures for missing
|
||||
// color types or no colors at all.
|
||||
func (c colors) hasTypes(types ...string) []validationErr {
|
||||
tMap := make(map[string]bool)
|
||||
for _, cc := range c {
|
||||
tMap[cc.Type] = true
|
||||
}
|
||||
|
||||
var failures []validationErr
|
||||
for _, t := range types {
|
||||
if !tMap[t] {
|
||||
failures = append(failures, validationErr{
|
||||
Field: "colors",
|
||||
Msg: fmt.Sprintf("type not found: %q", t),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
func (c colors) valid() []validationErr {
|
||||
var fails []validationErr
|
||||
for i, cc := range c {
|
||||
cErr := validationErr{
|
||||
Field: fieldChartColors,
|
||||
Index: intPtr(i),
|
||||
}
|
||||
if cc.Hex == "" {
|
||||
cErr.Nested = append(cErr.Nested, validationErr{
|
||||
Field: fieldColorHex,
|
||||
Msg: "a color must have a hex value provided",
|
||||
})
|
||||
}
|
||||
if len(cErr.Nested) > 0 {
|
||||
fails = append(fails, cErr)
|
||||
}
|
||||
}
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
type query struct {
|
||||
Query string `json:"query" yaml:"query"`
|
||||
}
|
||||
|
||||
type queries []query
|
||||
|
||||
func (q queries) influxDashQueries() []influxdb.DashboardQuery {
|
||||
var iQueries []influxdb.DashboardQuery
|
||||
for _, qq := range q {
|
||||
newQuery := influxdb.DashboardQuery{
|
||||
Text: qq.Query,
|
||||
EditMode: "advanced",
|
||||
}
|
||||
// TODO: axe this builder configs when issue https://github.com/influxdata/influxdb/issues/15708 is fixed up
|
||||
newQuery.BuilderConfig.Tags = append(newQuery.BuilderConfig.Tags, influxdb.NewBuilderTag("_measurement", "filter", ""))
|
||||
iQueries = append(iQueries, newQuery)
|
||||
}
|
||||
return iQueries
|
||||
}
|
||||
|
||||
func (q queries) valid() []validationErr {
|
||||
var fails []validationErr
|
||||
if len(q) == 0 {
|
||||
fails = append(fails, validationErr{
|
||||
Field: fieldChartQueries,
|
||||
Msg: "at least 1 query must be provided",
|
||||
})
|
||||
}
|
||||
|
||||
for i, qq := range q {
|
||||
qErr := validationErr{
|
||||
Field: fieldChartQueries,
|
||||
Index: intPtr(i),
|
||||
}
|
||||
if qq.Query == "" {
|
||||
qErr.Nested = append(fails, validationErr{
|
||||
Field: fieldQuery,
|
||||
Msg: "a query must be provided",
|
||||
})
|
||||
}
|
||||
if len(qErr.Nested) > 0 {
|
||||
fails = append(fails, qErr)
|
||||
}
|
||||
}
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
const (
|
||||
fieldAxisBase = "base"
|
||||
fieldAxisLabel = "label"
|
||||
fieldAxisScale = "scale"
|
||||
)
|
||||
|
||||
type axis struct {
|
||||
Base string `json:"base,omitempty" yaml:"base,omitempty"`
|
||||
Label string `json:"label,omitempty" yaml:"label,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Scale string `json:"scale,omitempty" yaml:"scale,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
Domain []float64 `json:"domain,omitempty" yaml:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type axes []axis
|
||||
|
||||
func (a axes) get(name string) axis {
|
||||
for _, ax := range a {
|
||||
if name == ax.Name {
|
||||
return ax
|
||||
}
|
||||
}
|
||||
return axis{}
|
||||
}
|
||||
|
||||
func (a axes) influxAxes() map[string]influxdb.Axis {
|
||||
m := make(map[string]influxdb.Axis)
|
||||
for _, ax := range a {
|
||||
m[ax.Name] = influxdb.Axis{
|
||||
Bounds: []string{},
|
||||
Label: ax.Label,
|
||||
Prefix: ax.Prefix,
|
||||
Suffix: ax.Suffix,
|
||||
Base: ax.Base,
|
||||
Scale: ax.Scale,
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (a axes) hasAxes(expectedAxes ...string) []validationErr {
|
||||
mAxes := make(map[string]bool)
|
||||
for _, ax := range a {
|
||||
mAxes[ax.Name] = true
|
||||
}
|
||||
|
||||
var failures []validationErr
|
||||
for _, expected := range expectedAxes {
|
||||
if !mAxes[expected] {
|
||||
failures = append(failures, validationErr{
|
||||
Field: fieldChartAxes,
|
||||
Msg: fmt.Sprintf("axis not found: %q", expected),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
const (
|
||||
fieldLegendOrientation = "orientation"
|
||||
)
|
||||
|
||||
type legend struct {
|
||||
Orientation string `json:"orientation,omitempty" yaml:"orientation,omitempty"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
}
|
||||
|
||||
func (l legend) influxLegend() influxdb.Legend {
|
||||
return influxdb.Legend{
|
||||
Type: l.Type,
|
||||
Orientation: l.Orientation,
|
||||
}
|
||||
}
|
||||
|
||||
type assocMapKey struct {
|
||||
resType influxdb.ResourceType
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -1662,9 +1662,10 @@ spec:
|
|||
t.Run("happy path", func(t *testing.T) {
|
||||
testfileRunner(t, "testdata/dashboard", func(t *testing.T, pkg *Pkg) {
|
||||
sum := pkg.Summary()
|
||||
require.Len(t, sum.Dashboards, 1)
|
||||
require.Len(t, sum.Dashboards, 2)
|
||||
|
||||
actual := sum.Dashboards[0]
|
||||
assert.Equal(t, "dash_1", actual.PkgName)
|
||||
assert.Equal(t, "display name", actual.Name)
|
||||
assert.Equal(t, "desc1", actual.Description)
|
||||
|
||||
|
|
@ -1699,6 +1700,11 @@ spec:
|
|||
assert.Equal(t, "text", c.Type)
|
||||
assert.Equal(t, "#8F8AF4", c.Hex)
|
||||
assert.Equal(t, 3.0, c.Value)
|
||||
|
||||
actual2 := sum.Dashboards[1]
|
||||
assert.Equal(t, "dash_2", actual2.PkgName)
|
||||
assert.Equal(t, "dash_2", actual2.Name)
|
||||
assert.Equal(t, "desc", actual2.Description)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -2600,18 +2606,32 @@ spec:
|
|||
actual := sum.Dashboards[0]
|
||||
assert.Equal(t, "dash_1", actual.Name)
|
||||
|
||||
require.Len(t, actual.LabelAssociations, 1)
|
||||
actualLabel := actual.LabelAssociations[0]
|
||||
assert.Equal(t, "label_1", actualLabel.Name)
|
||||
require.Len(t, actual.LabelAssociations, 2)
|
||||
assert.Equal(t, "label_1", actual.LabelAssociations[0].Name)
|
||||
assert.Equal(t, "label_2", actual.LabelAssociations[1].Name)
|
||||
|
||||
assert.Contains(t, sum.LabelMappings, SummaryLabelMapping{
|
||||
Status: StateStatusNew,
|
||||
ResourceType: influxdb.DashboardsResourceType,
|
||||
ResourcePkgName: "dash_1",
|
||||
ResourceName: "dash_1",
|
||||
LabelPkgName: "label_1",
|
||||
LabelName: "label_1",
|
||||
})
|
||||
expectedMappings := []SummaryLabelMapping{
|
||||
{
|
||||
Status: StateStatusNew,
|
||||
ResourceType: influxdb.DashboardsResourceType,
|
||||
ResourcePkgName: "dash_1",
|
||||
ResourceName: "dash_1",
|
||||
LabelPkgName: "label_1",
|
||||
LabelName: "label_1",
|
||||
},
|
||||
{
|
||||
Status: StateStatusNew,
|
||||
ResourceType: influxdb.DashboardsResourceType,
|
||||
ResourcePkgName: "dash_1",
|
||||
ResourceName: "dash_1",
|
||||
LabelPkgName: "label_2",
|
||||
LabelName: "label_2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, expectedMapping := range expectedMappings {
|
||||
assert.Contains(t, sum.LabelMappings, expectedMapping)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -686,8 +686,6 @@ func (s *Service) dryRun(ctx context.Context, orgID influxdb.ID, pkg *Pkg, opts
|
|||
}
|
||||
|
||||
var diff Diff
|
||||
diff.Dashboards = s.dryRunDashboards(pkg)
|
||||
|
||||
diffRules, err := s.dryRunNotificationRules(ctx, orgID, pkg)
|
||||
if err != nil {
|
||||
return Summary{}, Diff{}, nil, err
|
||||
|
|
@ -710,6 +708,7 @@ func (s *Service) dryRun(ctx context.Context, orgID influxdb.ID, pkg *Pkg, opts
|
|||
|
||||
diff.Buckets = stateDiff.Buckets
|
||||
diff.Checks = stateDiff.Checks
|
||||
diff.Dashboards = stateDiff.Dashboards
|
||||
diff.NotificationEndpoints = stateDiff.NotificationEndpoints
|
||||
diff.Labels = stateDiff.Labels
|
||||
diff.Tasks = stateDiff.Tasks
|
||||
|
|
@ -757,16 +756,6 @@ func (s *Service) dryRunChecks(ctx context.Context, orgID influxdb.ID, checks ma
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) dryRunDashboards(pkg *Pkg) []DiffDashboard {
|
||||
dashs := pkg.dashboards()
|
||||
|
||||
diffs := make([]DiffDashboard, 0, len(dashs))
|
||||
for _, d := range dashs {
|
||||
diffs = append(diffs, newDiffDashboard(d))
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
func (s *Service) dryRunLabels(ctx context.Context, orgID influxdb.ID, labels map[string]*stateLabel) {
|
||||
for _, pkgLabel := range labels {
|
||||
pkgLabel.orgID = orgID
|
||||
|
|
@ -941,7 +930,6 @@ type (
|
|||
|
||||
func (s *Service) dryRunLabelMappings(ctx context.Context, pkg *Pkg, state *stateCoordinator) ([]DiffLabelMapping, error) {
|
||||
mappers := []labelMappers{
|
||||
mapperDashboards(pkg.dashboards()),
|
||||
mapperNotificationRules(pkg.notificationRules()),
|
||||
}
|
||||
|
||||
|
|
@ -1068,6 +1056,17 @@ func (s *Service) dryRunLabelMappingsV2(ctx context.Context, state *stateCoordin
|
|||
mappings = append(mappings, mm...)
|
||||
}
|
||||
|
||||
for _, d := range state.mDashboards {
|
||||
if IsRemoval(d.stateStatus) {
|
||||
continue
|
||||
}
|
||||
mm, err := s.dryRunResourceLabelMappingV2(ctx, state, stateLabelsByResName, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mappings = append(mappings, mm...)
|
||||
}
|
||||
|
||||
for _, e := range state.mEndpoints {
|
||||
if IsRemoval(e.stateStatus) {
|
||||
continue
|
||||
|
|
@ -1323,7 +1322,7 @@ func (s *Service) apply(ctx context.Context, coordinator *rollbackCoordinator, o
|
|||
s.applyVariables(ctx, state.variables()),
|
||||
s.applyBuckets(ctx, state.buckets()),
|
||||
s.applyChecks(ctx, state.checks()),
|
||||
s.applyDashboards(pkg.dashboards()),
|
||||
s.applyDashboards(state.dashboards()),
|
||||
s.applyNotificationEndpoints(ctx, userID, state.endpoints()),
|
||||
s.applyTasks(state.tasks()),
|
||||
s.applyTelegrafs(state.telegrafConfigs()),
|
||||
|
|
@ -1583,23 +1582,23 @@ func (s *Service) applyCheck(ctx context.Context, c *stateCheck, userID influxdb
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) applyDashboards(dashboards []*dashboard) applier {
|
||||
func (s *Service) applyDashboards(dashboards []*stateDashboard) applier {
|
||||
const resource = "dashboard"
|
||||
|
||||
mutex := new(doMutex)
|
||||
rollbackDashboards := make([]*dashboard, 0, len(dashboards))
|
||||
rollbackDashboards := make([]*stateDashboard, 0, len(dashboards))
|
||||
|
||||
createFn := func(ctx context.Context, i int, orgID, userID influxdb.ID) *applyErrBody {
|
||||
var d dashboard
|
||||
var d *stateDashboard
|
||||
mutex.Do(func() {
|
||||
dashboards[i].OrgID = orgID
|
||||
d = *dashboards[i]
|
||||
dashboards[i].orgID = orgID
|
||||
d = dashboards[i]
|
||||
})
|
||||
|
||||
influxBucket, err := s.applyDashboard(ctx, d)
|
||||
if err != nil {
|
||||
return &applyErrBody{
|
||||
name: d.Name(),
|
||||
name: d.parserDash.Name(),
|
||||
msg: err.Error(),
|
||||
}
|
||||
}
|
||||
|
|
@ -1627,12 +1626,12 @@ func (s *Service) applyDashboards(dashboards []*dashboard) applier {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) applyDashboard(ctx context.Context, d dashboard) (influxdb.Dashboard, error) {
|
||||
cells := convertChartsToCells(d.Charts)
|
||||
func (s *Service) applyDashboard(ctx context.Context, d *stateDashboard) (influxdb.Dashboard, error) {
|
||||
cells := convertChartsToCells(d.parserDash.Charts)
|
||||
influxDashboard := influxdb.Dashboard{
|
||||
OrganizationID: d.OrgID,
|
||||
Description: d.Description,
|
||||
Name: d.Name(),
|
||||
OrganizationID: d.orgID,
|
||||
Description: d.parserDash.Description,
|
||||
Name: d.parserDash.Name(),
|
||||
Cells: cells,
|
||||
}
|
||||
err := s.dashSVC.CreateDashboard(ctx, &influxDashboard)
|
||||
|
|
@ -2615,6 +2614,7 @@ func newSummaryFromStatePkg(pkg *Pkg, state *stateCoordinator) Summary {
|
|||
pkgSum := pkg.Summary()
|
||||
pkgSum.Buckets = stateSum.Buckets
|
||||
pkgSum.Checks = stateSum.Checks
|
||||
pkgSum.Dashboards = stateSum.Dashboards
|
||||
pkgSum.NotificationEndpoints = stateSum.NotificationEndpoints
|
||||
pkgSum.Labels = stateSum.Labels
|
||||
pkgSum.Tasks = stateSum.Tasks
|
||||
|
|
@ -2626,6 +2626,7 @@ func newSummaryFromStatePkg(pkg *Pkg, state *stateCoordinator) Summary {
|
|||
resourcesToSkip := map[influxdb.ResourceType]bool{
|
||||
influxdb.BucketsResourceType: true,
|
||||
influxdb.ChecksResourceType: true,
|
||||
influxdb.DashboardsResourceType: true,
|
||||
influxdb.NotificationEndpointResourceType: true,
|
||||
influxdb.TasksResourceType: true,
|
||||
influxdb.TelegrafsResourceType: true,
|
||||
|
|
|
|||
|
|
@ -8,26 +8,28 @@ import (
|
|||
)
|
||||
|
||||
type stateCoordinator struct {
|
||||
mBuckets map[string]*stateBucket
|
||||
mChecks map[string]*stateCheck
|
||||
mEndpoints map[string]*stateEndpoint
|
||||
mLabels map[string]*stateLabel
|
||||
mTasks map[string]*stateTask
|
||||
mTelegrafs map[string]*stateTelegraf
|
||||
mVariables map[string]*stateVariable
|
||||
mBuckets map[string]*stateBucket
|
||||
mChecks map[string]*stateCheck
|
||||
mDashboards map[string]*stateDashboard
|
||||
mEndpoints map[string]*stateEndpoint
|
||||
mLabels map[string]*stateLabel
|
||||
mTasks map[string]*stateTask
|
||||
mTelegrafs map[string]*stateTelegraf
|
||||
mVariables map[string]*stateVariable
|
||||
|
||||
labelMappings []stateLabelMapping
|
||||
}
|
||||
|
||||
func newStateCoordinator(pkg *Pkg) *stateCoordinator {
|
||||
state := stateCoordinator{
|
||||
mBuckets: make(map[string]*stateBucket),
|
||||
mChecks: make(map[string]*stateCheck),
|
||||
mEndpoints: make(map[string]*stateEndpoint),
|
||||
mLabels: make(map[string]*stateLabel),
|
||||
mTasks: make(map[string]*stateTask),
|
||||
mTelegrafs: make(map[string]*stateTelegraf),
|
||||
mVariables: make(map[string]*stateVariable),
|
||||
mBuckets: make(map[string]*stateBucket),
|
||||
mChecks: make(map[string]*stateCheck),
|
||||
mDashboards: make(map[string]*stateDashboard),
|
||||
mEndpoints: make(map[string]*stateEndpoint),
|
||||
mLabels: make(map[string]*stateLabel),
|
||||
mTasks: make(map[string]*stateTask),
|
||||
mTelegrafs: make(map[string]*stateTelegraf),
|
||||
mVariables: make(map[string]*stateVariable),
|
||||
}
|
||||
|
||||
for _, pkgBkt := range pkg.buckets() {
|
||||
|
|
@ -42,6 +44,12 @@ func newStateCoordinator(pkg *Pkg) *stateCoordinator {
|
|||
stateStatus: StateStatusNew,
|
||||
}
|
||||
}
|
||||
for _, pkgDash := range pkg.dashboards() {
|
||||
state.mDashboards[pkgDash.PkgName()] = &stateDashboard{
|
||||
parserDash: pkgDash,
|
||||
stateStatus: StateStatusNew,
|
||||
}
|
||||
}
|
||||
for _, pkgEndpoint := range pkg.notificationEndpoints() {
|
||||
state.mEndpoints[pkgEndpoint.PkgName()] = &stateEndpoint{
|
||||
parserEndpoint: pkgEndpoint,
|
||||
|
|
@ -92,6 +100,14 @@ func (s *stateCoordinator) checks() []*stateCheck {
|
|||
return out
|
||||
}
|
||||
|
||||
func (s *stateCoordinator) dashboards() []*stateDashboard {
|
||||
out := make([]*stateDashboard, 0, len(s.mDashboards))
|
||||
for _, d := range s.mDashboards {
|
||||
out = append(out, d)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *stateCoordinator) endpoints() []*stateEndpoint {
|
||||
out := make([]*stateEndpoint, 0, len(s.mEndpoints))
|
||||
for _, e := range s.mEndpoints {
|
||||
|
|
@ -148,6 +164,13 @@ func (s *stateCoordinator) diff() Diff {
|
|||
return diff.Checks[i].PkgName < diff.Checks[j].PkgName
|
||||
})
|
||||
|
||||
for _, d := range s.mDashboards {
|
||||
diff.Dashboards = append(diff.Dashboards, d.diffDashboard())
|
||||
}
|
||||
sort.Slice(diff.Dashboards, func(i, j int) bool {
|
||||
return diff.Dashboards[i].PkgName < diff.Dashboards[j].PkgName
|
||||
})
|
||||
|
||||
for _, e := range s.mEndpoints {
|
||||
diff.NotificationEndpoints = append(diff.NotificationEndpoints, e.diffEndpoint())
|
||||
}
|
||||
|
|
@ -228,12 +251,25 @@ func (s *stateCoordinator) summary() Summary {
|
|||
return sum.Checks[i].PkgName < sum.Checks[j].PkgName
|
||||
})
|
||||
|
||||
for _, d := range s.mDashboards {
|
||||
if IsRemoval(d.stateStatus) {
|
||||
continue
|
||||
}
|
||||
sum.Dashboards = append(sum.Dashboards, d.summarize())
|
||||
}
|
||||
sort.Slice(sum.Dashboards, func(i, j int) bool {
|
||||
return sum.Dashboards[i].PkgName < sum.Dashboards[j].PkgName
|
||||
})
|
||||
|
||||
for _, e := range s.mEndpoints {
|
||||
if IsRemoval(e.stateStatus) {
|
||||
continue
|
||||
}
|
||||
sum.NotificationEndpoints = append(sum.NotificationEndpoints, e.summarize())
|
||||
}
|
||||
sort.Slice(sum.NotificationEndpoints, func(i, j int) bool {
|
||||
return sum.NotificationEndpoints[i].PkgName < sum.NotificationEndpoints[j].PkgName
|
||||
})
|
||||
|
||||
for _, v := range s.mLabels {
|
||||
if IsRemoval(v.stateStatus) {
|
||||
|
|
@ -569,6 +605,99 @@ func (c *stateCheck) summarize() SummaryCheck {
|
|||
return sum
|
||||
}
|
||||
|
||||
type stateDashboard struct {
|
||||
id, orgID influxdb.ID
|
||||
stateStatus StateStatus
|
||||
|
||||
parserDash *dashboard
|
||||
existing *influxdb.Dashboard
|
||||
}
|
||||
|
||||
func (d *stateDashboard) ID() influxdb.ID {
|
||||
if IsExisting(d.stateStatus) && d.existing != nil {
|
||||
return d.existing.ID
|
||||
}
|
||||
return d.id
|
||||
}
|
||||
|
||||
func (d *stateDashboard) labels() []*label {
|
||||
return d.parserDash.labels
|
||||
}
|
||||
|
||||
func (d *stateDashboard) resourceType() influxdb.ResourceType {
|
||||
return KindDashboard.ResourceType()
|
||||
}
|
||||
|
||||
func (d *stateDashboard) stateIdentity() stateIdentity {
|
||||
return stateIdentity{
|
||||
id: d.ID(),
|
||||
name: d.parserDash.Name(),
|
||||
pkgName: d.parserDash.PkgName(),
|
||||
resourceType: d.resourceType(),
|
||||
stateStatus: d.stateStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *stateDashboard) diffDashboard() DiffDashboard {
|
||||
diff := DiffDashboard{
|
||||
DiffIdentifier: DiffIdentifier{
|
||||
ID: SafeID(d.ID()),
|
||||
Remove: IsRemoval(d.stateStatus),
|
||||
StateStatus: d.stateStatus,
|
||||
PkgName: d.parserDash.PkgName(),
|
||||
},
|
||||
New: DiffDashboardValues{
|
||||
Name: d.parserDash.Name(),
|
||||
Desc: d.parserDash.Description,
|
||||
Charts: make([]DiffChart, 0, len(d.parserDash.Charts)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range d.parserDash.Charts {
|
||||
diff.New.Charts = append(diff.New.Charts, DiffChart{
|
||||
Properties: c.properties(),
|
||||
Height: c.Height,
|
||||
Width: c.Width,
|
||||
})
|
||||
}
|
||||
|
||||
if d.existing == nil {
|
||||
return diff
|
||||
}
|
||||
|
||||
oldDiff := DiffDashboardValues{
|
||||
Name: d.existing.Name,
|
||||
Desc: d.existing.Description,
|
||||
Charts: make([]DiffChart, 0, len(d.existing.Cells)),
|
||||
}
|
||||
|
||||
for _, c := range d.existing.Cells {
|
||||
var props influxdb.ViewProperties
|
||||
if c.View != nil {
|
||||
props = c.View.Properties
|
||||
}
|
||||
|
||||
oldDiff.Charts = append(oldDiff.Charts, DiffChart{
|
||||
Properties: props,
|
||||
XPosition: int(c.X),
|
||||
YPosition: int(c.Y),
|
||||
Height: int(c.H),
|
||||
Width: int(c.W),
|
||||
})
|
||||
}
|
||||
|
||||
diff.Old = &oldDiff
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
func (d *stateDashboard) summarize() SummaryDashboard {
|
||||
sum := d.parserDash.summarize()
|
||||
sum.ID = SafeID(d.ID())
|
||||
sum.OrgID = SafeID(d.orgID)
|
||||
return sum
|
||||
}
|
||||
|
||||
type stateLabel struct {
|
||||
id, orgID influxdb.ID
|
||||
stateStatus StateStatus
|
||||
|
|
|
|||
|
|
@ -829,12 +829,19 @@ func TestService(t *testing.T) {
|
|||
sum, _, err := svc.Apply(context.TODO(), orgID, 0, pkg)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, sum.Dashboards, 1)
|
||||
require.Len(t, sum.Dashboards, 2)
|
||||
dash1 := sum.Dashboards[0]
|
||||
assert.Equal(t, SafeID(1), dash1.ID)
|
||||
assert.Equal(t, SafeID(orgID), dash1.OrgID)
|
||||
assert.NotZero(t, dash1.ID)
|
||||
assert.NotZero(t, dash1.OrgID)
|
||||
assert.Equal(t, "dash_1", dash1.PkgName)
|
||||
assert.Equal(t, "display name", dash1.Name)
|
||||
require.Len(t, dash1.Charts, 1)
|
||||
|
||||
dash2 := sum.Dashboards[1]
|
||||
assert.NotZero(t, dash2.ID)
|
||||
assert.Equal(t, "dash_2", dash2.PkgName)
|
||||
assert.Equal(t, "dash_2", dash2.Name)
|
||||
require.Empty(t, dash2.Charts)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -855,8 +862,6 @@ func TestService(t *testing.T) {
|
|||
return nil
|
||||
}
|
||||
|
||||
pkg.mDashboards["dash_1_copy"] = pkg.mDashboards["dash_1"]
|
||||
|
||||
svc := newTestService(WithDashboardSVC(fakeDashSVC))
|
||||
|
||||
orgID := influxdb.ID(9000)
|
||||
|
|
@ -1071,19 +1076,22 @@ func TestService(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("maps dashboards with labels", func(t *testing.T) {
|
||||
testLabelMappingFn(
|
||||
t,
|
||||
"testdata/dashboard_associates_label.yml",
|
||||
1,
|
||||
func() []ServiceSetterFn {
|
||||
fakeDashSVC := mock.NewDashboardService()
|
||||
fakeDashSVC.CreateDashboardF = func(_ context.Context, d *influxdb.Dashboard) error {
|
||||
d.ID = influxdb.ID(rand.Int())
|
||||
return nil
|
||||
}
|
||||
return []ServiceSetterFn{WithDashboardSVC(fakeDashSVC)}
|
||||
},
|
||||
)
|
||||
opts := func() []ServiceSetterFn {
|
||||
fakeDashSVC := mock.NewDashboardService()
|
||||
fakeDashSVC.CreateDashboardF = func(_ context.Context, d *influxdb.Dashboard) error {
|
||||
d.ID = influxdb.ID(rand.Int())
|
||||
return nil
|
||||
}
|
||||
return []ServiceSetterFn{WithDashboardSVC(fakeDashSVC)}
|
||||
}
|
||||
|
||||
t.Run("applies successfully", func(t *testing.T) {
|
||||
testLabelMappingV2ApplyFn(t, "testdata/dashboard_associates_label.yml", 2, opts)
|
||||
})
|
||||
|
||||
t.Run("deletes new label mappings on error", func(t *testing.T) {
|
||||
testLabelMappingV2RollbackFn(t, "testdata/dashboard_associates_label.yml", 1, opts)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("maps notification endpoints with labels", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -42,5 +42,15 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "influxdata.com/v2alpha1",
|
||||
"kind": "Dashboard",
|
||||
"metadata": {
|
||||
"name": "dash_2"
|
||||
},
|
||||
"spec": {
|
||||
"description": "desc"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,3 +27,10 @@ spec:
|
|||
type: text
|
||||
hex: "#8F8AF4"
|
||||
value: 3
|
||||
---
|
||||
apiVersion: influxdata.com/v2alpha1
|
||||
kind: Dashboard
|
||||
metadata:
|
||||
name: dash_2
|
||||
spec:
|
||||
description: desc
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@
|
|||
"name": "label_1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "influxdata.com/v2alpha1",
|
||||
"kind": "Label",
|
||||
"metadata": {
|
||||
"name": "label_2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "influxdata.com/v2alpha1",
|
||||
"kind": "Dashboard",
|
||||
|
|
@ -17,6 +24,10 @@
|
|||
{
|
||||
"kind": "Label",
|
||||
"name": "label_1"
|
||||
},
|
||||
{
|
||||
"kind": "Label",
|
||||
"name": "label_2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
---
|
||||
apiVersion: influxdata.com/v2alpha1
|
||||
kind: Label
|
||||
metadata:
|
||||
name: label_1
|
||||
---
|
||||
apiVersion: influxdata.com/v2alpha1
|
||||
kind: Label
|
||||
metadata:
|
||||
name: label_2
|
||||
---
|
||||
apiVersion: influxdata.com/v2alpha1
|
||||
kind: Dashboard
|
||||
metadata:
|
||||
name: dash_1
|
||||
|
|
@ -12,3 +16,5 @@ spec:
|
|||
associations:
|
||||
- kind: Label
|
||||
name: label_1
|
||||
- kind: Label
|
||||
name: label_2
|
||||
|
|
|
|||
Loading…
Reference in New Issue