chore(pkger): refactor parser dashboard state out into stateful type

references: #17434
pull/17775/head
Johnny Steenbergen 2020-04-16 12:41:02 -07:00 committed by Johnny Steenbergen
parent f084a93fa2
commit 21694416a3
13 changed files with 944 additions and 847 deletions

View File

@ -7579,8 +7579,8 @@ components:
items:
type: object
properties:
remove:
type: boolean
stateStatus:
type: string
id:
type: string
pkgName:

View File

@ -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"

View File

@ -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,

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
})
})

View File

@ -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,

View File

@ -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

View File

@ -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) {

View File

@ -42,5 +42,15 @@
}
]
}
},
{
"apiVersion": "influxdata.com/v2alpha1",
"kind": "Dashboard",
"metadata": {
"name": "dash_2"
},
"spec": {
"description": "desc"
}
}
]

View File

@ -27,3 +27,10 @@ spec:
type: text
hex: "#8F8AF4"
value: 3
---
apiVersion: influxdata.com/v2alpha1
kind: Dashboard
metadata:
name: dash_2
spec:
description: desc

View File

@ -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"
}
]
}

View File

@ -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