2019-10-23 17:09:04 +00:00
|
|
|
package pkger
|
|
|
|
|
|
|
|
import (
|
2019-11-08 19:33:41 +00:00
|
|
|
"errors"
|
2019-11-01 18:11:42 +00:00
|
|
|
"fmt"
|
2019-11-22 23:22:10 +00:00
|
|
|
"reflect"
|
2019-11-22 18:41:08 +00:00
|
|
|
"strconv"
|
2019-11-08 19:33:41 +00:00
|
|
|
"strings"
|
2019-10-23 17:09:04 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/influxdata/influxdb"
|
|
|
|
)
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
// Package kinds.
|
2019-10-23 17:09:04 +00:00
|
|
|
const (
|
2019-11-08 19:33:41 +00:00
|
|
|
KindUnknown Kind = ""
|
|
|
|
KindBucket Kind = "bucket"
|
|
|
|
KindDashboard Kind = "dashboard"
|
|
|
|
KindLabel Kind = "label"
|
|
|
|
KindPackage Kind = "package"
|
2019-12-03 02:05:10 +00:00
|
|
|
KindTelegraf Kind = "telegraf"
|
2019-11-08 19:33:41 +00:00
|
|
|
KindVariable Kind = "variable"
|
2019-10-23 17:09:04 +00:00
|
|
|
)
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
var kinds = map[Kind]bool{
|
|
|
|
KindBucket: true,
|
|
|
|
KindDashboard: true,
|
|
|
|
KindLabel: true,
|
|
|
|
KindPackage: true,
|
2019-12-03 02:05:10 +00:00
|
|
|
KindTelegraf: true,
|
2019-11-08 19:33:41 +00:00
|
|
|
KindVariable: true,
|
2019-11-06 22:41:06 +00:00
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
// Kind is a resource kind.
|
|
|
|
type Kind string
|
2019-10-23 17:09:04 +00:00
|
|
|
|
2019-11-21 00:38:12 +00:00
|
|
|
// NewKind returns the kind parsed from the provided string.
|
|
|
|
func NewKind(s string) Kind {
|
2019-11-08 19:33:41 +00:00
|
|
|
return Kind(strings.TrimSpace(strings.ToLower(s)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// String provides the kind in human readable form.
|
|
|
|
func (k Kind) String() string {
|
2019-11-06 22:41:06 +00:00
|
|
|
if kinds[k] {
|
2019-10-23 17:09:04 +00:00
|
|
|
return string(k)
|
2019-11-06 22:41:06 +00:00
|
|
|
}
|
2019-11-08 19:33:41 +00:00
|
|
|
if k == KindUnknown {
|
2019-10-23 17:09:04 +00:00
|
|
|
return "unknown"
|
|
|
|
}
|
2019-11-06 22:41:06 +00:00
|
|
|
return string(k)
|
2019-10-23 17:09:04 +00:00
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
// OK validates the kind is valid.
|
|
|
|
func (k Kind) OK() error {
|
|
|
|
newKind := Kind(strings.ToLower(string(k)))
|
|
|
|
if newKind == KindUnknown {
|
|
|
|
return errors.New("invalid kind")
|
|
|
|
}
|
|
|
|
if !kinds[newKind] {
|
|
|
|
return errors.New("unsupported kind provided")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-09 02:12:48 +00:00
|
|
|
func (k Kind) title() string {
|
|
|
|
return strings.Title(k.String())
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
func (k Kind) is(comp Kind) bool {
|
|
|
|
normed := Kind(strings.TrimSpace(strings.ToLower(string(k))))
|
|
|
|
return normed == comp
|
|
|
|
}
|
|
|
|
|
2019-11-05 01:40:42 +00:00
|
|
|
// SafeID is an equivalent influxdb.ID that encodes safely with
|
|
|
|
// zero values (influxdb.ID == 0).
|
|
|
|
type SafeID influxdb.ID
|
|
|
|
|
|
|
|
// Encode will safely encode the id.
|
|
|
|
func (s SafeID) Encode() ([]byte, error) {
|
|
|
|
id := influxdb.ID(s)
|
|
|
|
b, _ := id.Encode()
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// String prints a encoded string representation of the id.
|
|
|
|
func (s SafeID) String() string {
|
|
|
|
return influxdb.ID(s).String()
|
|
|
|
}
|
|
|
|
|
2019-10-23 17:09:04 +00:00
|
|
|
// Metadata is the pkg metadata. This data describes the user
|
|
|
|
// defined identifiers.
|
|
|
|
type Metadata struct {
|
|
|
|
Description string `yaml:"description" json:"description"`
|
|
|
|
Name string `yaml:"pkgName" json:"pkgName"`
|
|
|
|
Version string `yaml:"pkgVersion" json:"pkgVersion"`
|
|
|
|
}
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
// Diff is the result of a service DryRun call. The diff outlines
|
|
|
|
// what is new and or updated from the current state of the platform.
|
|
|
|
type Diff struct {
|
2019-11-05 01:40:42 +00:00
|
|
|
Buckets []DiffBucket `json:"buckets"`
|
|
|
|
Dashboards []DiffDashboard `json:"dashboards"`
|
|
|
|
Labels []DiffLabel `json:"labels"`
|
|
|
|
LabelMappings []DiffLabelMapping `json:"labelMappings"`
|
2019-11-07 00:45:00 +00:00
|
|
|
Variables []DiffVariable `json:"variables"`
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
2019-11-21 07:12:27 +00:00
|
|
|
// HasConflicts provides a binary t/f if there are any changes within package
|
|
|
|
// after dry run is complete.
|
|
|
|
func (d Diff) HasConflicts() bool {
|
|
|
|
for _, b := range d.Buckets {
|
|
|
|
if b.hasConflict() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, l := range d.Labels {
|
|
|
|
if l.hasConflict() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range d.Variables {
|
|
|
|
if v.hasConflict() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-11-22 23:22:10 +00:00
|
|
|
// DiffBucketValues are the varying values for a bucket.
|
|
|
|
type DiffBucketValues struct {
|
|
|
|
Description string `json:"description"`
|
|
|
|
RetentionRules retentionRules `json:"retentionRules"`
|
|
|
|
}
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
// DiffBucket is a diff of an individual bucket.
|
|
|
|
type DiffBucket struct {
|
2019-11-22 23:22:10 +00:00
|
|
|
ID SafeID
|
|
|
|
Name string
|
|
|
|
New DiffBucketValues `json:"new"`
|
|
|
|
Old *DiffBucketValues `json:"old,omitempty"` // using omitempty here to signal there was no prev state with a nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDiffBucket(b *bucket, i *influxdb.Bucket) DiffBucket {
|
|
|
|
diff := DiffBucket{
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: b.Name(),
|
2019-11-22 23:22:10 +00:00
|
|
|
New: DiffBucketValues{
|
|
|
|
Description: b.Description,
|
|
|
|
RetentionRules: b.RetentionRules,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if i != nil {
|
|
|
|
diff.ID = SafeID(i.ID)
|
|
|
|
diff.Old = &DiffBucketValues{
|
|
|
|
Description: i.Description,
|
|
|
|
}
|
|
|
|
if i.RetentionPeriod > 0 {
|
|
|
|
diff.Old.RetentionRules = retentionRules{newRetentionRule(i.RetentionPeriod)}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return diff
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsNew indicates whether a pkg bucket is going to be new to the platform.
|
|
|
|
func (d DiffBucket) IsNew() bool {
|
2019-11-05 01:40:42 +00:00
|
|
|
return d.ID == SafeID(0)
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
2019-11-21 07:12:27 +00:00
|
|
|
func (d DiffBucket) hasConflict() bool {
|
2019-11-22 23:22:10 +00:00
|
|
|
return !d.IsNew() && d.Old != nil && !reflect.DeepEqual(*d.Old, d.New)
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
// DiffDashboard is a diff of an individual dashboard.
|
|
|
|
type DiffDashboard struct {
|
2019-11-05 01:40:42 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Desc string `json:"description"`
|
|
|
|
Charts []DiffChart `json:"charts"`
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
func newDiffDashboard(d *dashboard) DiffDashboard {
|
|
|
|
diff := DiffDashboard{
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: d.Name(),
|
2019-11-01 18:11:42 +00:00
|
|
|
Desc: d.Description,
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
for _, c := range d.Charts {
|
|
|
|
diff.Charts = append(diff.Charts, DiffChart{
|
|
|
|
Properties: c.properties(),
|
|
|
|
Height: c.Height,
|
|
|
|
Width: c.Width,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return diff
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
// DiffChart is a diff of oa chart. Since all charts are new right now.
|
|
|
|
// the SummaryChart is reused here.
|
|
|
|
type DiffChart SummaryChart
|
|
|
|
|
2019-11-22 23:22:10 +00:00
|
|
|
// DiffLabelValues are the varying values for a label.
|
|
|
|
type DiffLabelValues struct {
|
|
|
|
Color string `json:"color"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
}
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
// DiffLabel is a diff of an individual label.
|
|
|
|
type DiffLabel struct {
|
2019-11-22 23:22:10 +00:00
|
|
|
ID SafeID `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
New DiffLabelValues `json:"new"`
|
|
|
|
Old *DiffLabelValues `json:"old,omitempty"` // using omitempty here to signal there was no prev state with a nil
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsNew indicates whether a pkg label is going to be new to the platform.
|
|
|
|
func (d DiffLabel) IsNew() bool {
|
2019-11-05 01:40:42 +00:00
|
|
|
return d.ID == SafeID(0)
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
2019-11-21 07:12:27 +00:00
|
|
|
func (d DiffLabel) hasConflict() bool {
|
2019-11-22 23:22:10 +00:00
|
|
|
return d.IsNew() || d.Old != nil && *d.Old != d.New
|
2019-11-21 07:12:27 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 23:22:10 +00:00
|
|
|
func newDiffLabel(l *label, i *influxdb.Label) DiffLabel {
|
|
|
|
diff := DiffLabel{
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: l.Name(),
|
2019-11-22 23:22:10 +00:00
|
|
|
New: DiffLabelValues{
|
|
|
|
Color: l.Color,
|
|
|
|
Description: l.Description,
|
|
|
|
},
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
2019-11-22 23:22:10 +00:00
|
|
|
if i != nil {
|
|
|
|
diff.ID = SafeID(i.ID)
|
|
|
|
diff.Old = &DiffLabelValues{
|
|
|
|
Color: i.Properties["color"],
|
|
|
|
Description: i.Properties["description"],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return diff
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DiffLabelMapping is a diff of an individual label mapping. A
|
|
|
|
// single resource may have multiple mappings to multiple labels.
|
|
|
|
// A label can have many mappings to other resources.
|
|
|
|
type DiffLabelMapping struct {
|
2019-11-05 01:40:42 +00:00
|
|
|
IsNew bool `json:"isNew"`
|
2019-10-28 22:23:40 +00:00
|
|
|
|
2019-11-05 01:40:42 +00:00
|
|
|
ResType influxdb.ResourceType `json:"resourceType"`
|
|
|
|
ResID SafeID `json:"resourceID"`
|
|
|
|
ResName string `json:"resourceName"`
|
2019-10-28 22:23:40 +00:00
|
|
|
|
2019-11-05 01:40:42 +00:00
|
|
|
LabelID SafeID `json:"labelID"`
|
|
|
|
LabelName string `json:"labelName"`
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 23:22:10 +00:00
|
|
|
// DiffVariableValues are the varying values for a variable.
|
|
|
|
type DiffVariableValues struct {
|
|
|
|
Description string `json:"description"`
|
|
|
|
Args *influxdb.VariableArguments `json:"args"`
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
// DiffVariable is a diff of an individual variable.
|
|
|
|
type DiffVariable struct {
|
2019-11-22 23:22:10 +00:00
|
|
|
ID SafeID `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
New DiffVariableValues `json:"new"`
|
|
|
|
Old *DiffVariableValues `json:"old,omitempty"` // using omitempty here to signal there was no prev state with a nil
|
2019-11-07 00:45:00 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 23:22:10 +00:00
|
|
|
func newDiffVariable(v *variable, iv *influxdb.Variable) DiffVariable {
|
|
|
|
diff := DiffVariable{
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: v.Name(),
|
2019-11-22 23:22:10 +00:00
|
|
|
New: DiffVariableValues{
|
|
|
|
Description: v.Description,
|
|
|
|
Args: v.influxVarArgs(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if iv != nil {
|
|
|
|
diff.ID = SafeID(iv.ID)
|
|
|
|
diff.Old = &DiffVariableValues{
|
|
|
|
Description: iv.Description,
|
|
|
|
Args: iv.Arguments,
|
|
|
|
}
|
2019-11-07 00:45:00 +00:00
|
|
|
}
|
2019-11-22 23:22:10 +00:00
|
|
|
|
|
|
|
return diff
|
2019-11-07 00:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsNew indicates whether a pkg variable is going to be new to the platform.
|
|
|
|
func (d DiffVariable) IsNew() bool {
|
|
|
|
return d.ID == SafeID(0)
|
|
|
|
}
|
|
|
|
|
2019-11-21 07:12:27 +00:00
|
|
|
func (d DiffVariable) hasConflict() bool {
|
2019-11-22 23:22:10 +00:00
|
|
|
return !d.IsNew() && d.Old != nil && !reflect.DeepEqual(*d.Old, d.New)
|
2019-11-21 07:12:27 +00:00
|
|
|
}
|
|
|
|
|
2019-10-23 17:09:04 +00:00
|
|
|
// Summary is a definition of all the resources that have or
|
|
|
|
// will be created from a pkg.
|
|
|
|
type Summary struct {
|
2019-11-05 01:40:42 +00:00
|
|
|
Buckets []SummaryBucket `json:"buckets"`
|
|
|
|
Dashboards []SummaryDashboard `json:"dashboards"`
|
|
|
|
Labels []SummaryLabel `json:"labels"`
|
|
|
|
LabelMappings []SummaryLabelMapping `json:"labelMappings"`
|
2019-11-06 22:41:06 +00:00
|
|
|
Variables []SummaryVariable `json:"variables"`
|
2019-10-30 17:55:13 +00:00
|
|
|
}
|
2019-10-23 17:09:04 +00:00
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
// SummaryBucket provides a summary of a pkg bucket.
|
|
|
|
type SummaryBucket struct {
|
|
|
|
influxdb.Bucket
|
2019-11-05 22:08:30 +00:00
|
|
|
LabelAssociations []influxdb.Label `json:"labelAssociations"`
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SummaryDashboard provides a summary of a pkg dashboard.
|
|
|
|
type SummaryDashboard struct {
|
2019-11-05 01:40:42 +00:00
|
|
|
ID SafeID `json:"id"`
|
|
|
|
OrgID SafeID `json:"orgID"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Charts []SummaryChart `json:"charts"`
|
2019-11-01 18:11:42 +00:00
|
|
|
|
2019-11-05 22:08:30 +00:00
|
|
|
LabelAssociations []influxdb.Label `json:"labelAssociations"`
|
2019-10-30 17:55:13 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
|
2019-11-05 22:08:30 +00:00
|
|
|
// chartKind identifies what kind of chart is eluded too. Each
|
2019-11-01 18:11:42 +00:00
|
|
|
// chart kind has their own requirements for what constitutes
|
|
|
|
// a chart.
|
2019-11-05 22:08:30 +00:00
|
|
|
type chartKind string
|
2019-11-01 18:11:42 +00:00
|
|
|
|
|
|
|
// available chart kinds
|
|
|
|
const (
|
2019-11-05 22:08:30 +00:00
|
|
|
chartKindUnknown chartKind = ""
|
|
|
|
chartKindGauge chartKind = "gauge"
|
2019-11-12 20:09:13 +00:00
|
|
|
chartKindHeatMap chartKind = "heatmap"
|
2019-11-16 20:14:46 +00:00
|
|
|
chartKindHistogram chartKind = "histogram"
|
2019-11-13 21:30:52 +00:00
|
|
|
chartKindMarkdown chartKind = "markdown"
|
2019-11-12 18:06:53 +00:00
|
|
|
chartKindScatter chartKind = "scatter"
|
2019-11-05 22:08:30 +00:00
|
|
|
chartKindSingleStat chartKind = "single_stat"
|
|
|
|
chartKindSingleStatPlusLine chartKind = "single_stat_plus_line"
|
|
|
|
chartKindXY chartKind = "xy"
|
2019-11-01 18:11:42 +00:00
|
|
|
)
|
|
|
|
|
2019-11-05 22:08:30 +00:00
|
|
|
func (c chartKind) ok() bool {
|
2019-11-01 18:11:42 +00:00
|
|
|
switch c {
|
2019-11-16 20:14:46 +00:00
|
|
|
case chartKindGauge, chartKindHeatMap, chartKindHistogram,
|
|
|
|
chartKindMarkdown, chartKindScatter, chartKindSingleStat,
|
2019-11-12 20:09:13 +00:00
|
|
|
chartKindSingleStatPlusLine, chartKindXY:
|
2019-11-01 18:11:42 +00:00
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-09 02:12:48 +00:00
|
|
|
func (c chartKind) title() string {
|
|
|
|
spacedKind := strings.ReplaceAll(string(c), "_", " ")
|
|
|
|
return strings.ReplaceAll(strings.Title(spacedKind), " ", "_")
|
|
|
|
}
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
// SummaryChart provides a summary of a pkg dashboard's chart.
|
|
|
|
type SummaryChart struct {
|
2019-11-05 01:40:42 +00:00
|
|
|
Properties influxdb.ViewProperties `json:"properties"`
|
2019-11-01 18:11:42 +00:00
|
|
|
|
2019-11-05 01:40:42 +00:00
|
|
|
XPosition int `json:"xPos"`
|
|
|
|
YPosition int `json:"yPos"`
|
|
|
|
Height int `json:"height"`
|
|
|
|
Width int `json:"width"`
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
// SummaryLabel provides a summary of a pkg label.
|
|
|
|
type SummaryLabel struct {
|
|
|
|
influxdb.Label
|
|
|
|
}
|
|
|
|
|
|
|
|
// SummaryLabelMapping provides a summary of a label mapped with a single resource.
|
|
|
|
type SummaryLabelMapping struct {
|
|
|
|
exists bool
|
2019-11-05 01:40:42 +00:00
|
|
|
ResourceName string `json:"resourceName"`
|
|
|
|
LabelName string `json:"labelName"`
|
2019-10-30 17:55:13 +00:00
|
|
|
influxdb.LabelMapping
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
// SummaryVariable provides a summary of a pkg variable.
|
|
|
|
type SummaryVariable struct {
|
|
|
|
influxdb.Variable
|
2019-11-07 00:45:00 +00:00
|
|
|
LabelAssociations []influxdb.Label `json:"labelAssociations"`
|
2019-11-06 22:41:06 +00:00
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldAssociations = "associations"
|
|
|
|
fieldDescription = "description"
|
|
|
|
fieldKind = "kind"
|
2019-11-22 18:41:08 +00:00
|
|
|
fieldLanguage = "language"
|
2019-11-08 19:33:41 +00:00
|
|
|
fieldName = "name"
|
|
|
|
fieldPrefix = "prefix"
|
|
|
|
fieldQuery = "query"
|
|
|
|
fieldSuffix = "suffix"
|
|
|
|
fieldType = "type"
|
|
|
|
fieldValue = "value"
|
|
|
|
fieldValues = "values"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2019-11-22 18:41:08 +00:00
|
|
|
fieldBucketRetentionRules = "retentionRules"
|
2019-11-08 19:33:41 +00:00
|
|
|
)
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
type bucket struct {
|
2019-11-22 18:41:08 +00:00
|
|
|
id influxdb.ID
|
|
|
|
OrgID influxdb.ID
|
|
|
|
Description string
|
2019-12-03 02:05:10 +00:00
|
|
|
name string
|
2019-11-22 18:41:08 +00:00
|
|
|
RetentionRules retentionRules
|
2019-12-03 02:05:10 +00:00
|
|
|
labels sortedLogos
|
2019-10-28 22:23:40 +00:00
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
// existing provides context for a resource that already
|
|
|
|
// exists in the platform. If a resource already exists
|
|
|
|
// then it will be referenced here.
|
2019-10-28 22:23:40 +00:00
|
|
|
existing *influxdb.Bucket
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func (b *bucket) ID() influxdb.ID {
|
|
|
|
if b.existing != nil {
|
|
|
|
return b.existing.ID
|
|
|
|
}
|
|
|
|
return b.id
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
func (b *bucket) Name() string {
|
|
|
|
return b.name
|
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
func (b *bucket) ResourceType() influxdb.ResourceType {
|
|
|
|
return influxdb.BucketsResourceType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *bucket) Exists() bool {
|
|
|
|
return b.existing != nil
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func (b *bucket) summarize() SummaryBucket {
|
2019-11-01 18:11:42 +00:00
|
|
|
return SummaryBucket{
|
2019-10-28 22:23:40 +00:00
|
|
|
Bucket: influxdb.Bucket{
|
2019-10-30 17:55:13 +00:00
|
|
|
ID: b.ID(),
|
2019-10-28 22:23:40 +00:00
|
|
|
OrgID: b.OrgID,
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: b.Name(),
|
2019-10-28 22:23:40 +00:00
|
|
|
Description: b.Description,
|
2019-11-22 18:41:08 +00:00
|
|
|
RetentionPeriod: b.RetentionRules.RP(),
|
2019-10-28 22:23:40 +00:00
|
|
|
},
|
2019-11-01 18:11:42 +00:00
|
|
|
LabelAssociations: toInfluxLabels(b.labels...),
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-22 18:41:08 +00:00
|
|
|
func (b *bucket) valid() []validationErr {
|
|
|
|
return b.RetentionRules.valid()
|
|
|
|
}
|
|
|
|
|
2019-11-03 17:33:36 +00:00
|
|
|
func (b *bucket) shouldApply() bool {
|
|
|
|
return b.existing == nil ||
|
|
|
|
b.Description != b.existing.Description ||
|
2019-12-03 02:05:10 +00:00
|
|
|
b.Name() != b.existing.Name ||
|
2019-11-22 18:41:08 +00:00
|
|
|
b.RetentionRules.RP() != b.existing.RetentionPeriod
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
retentionRuleTypeExpire = "expire"
|
|
|
|
)
|
|
|
|
|
|
|
|
type retentionRule struct {
|
|
|
|
Type string `json:"type" yaml:"type"`
|
|
|
|
Seconds int `json:"everySeconds" yaml:"everySeconds"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRetentionRule(d time.Duration) retentionRule {
|
|
|
|
return retentionRule{
|
|
|
|
Type: retentionRuleTypeExpire,
|
|
|
|
Seconds: int(d.Round(time.Second) / time.Second),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r retentionRule) valid() []validationErr {
|
|
|
|
const hour = 3600
|
|
|
|
var ff []validationErr
|
|
|
|
if r.Seconds < hour {
|
|
|
|
ff = append(ff, validationErr{
|
|
|
|
Field: fieldRetentionRulesEverySeconds,
|
|
|
|
Msg: "seconds must be a minimum of " + strconv.Itoa(hour),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if r.Type != retentionRuleTypeExpire {
|
|
|
|
ff = append(ff, validationErr{
|
|
|
|
Field: fieldType,
|
|
|
|
Msg: `type must be "expire"`,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return ff
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
fieldRetentionRulesEverySeconds = "everySeconds"
|
|
|
|
)
|
|
|
|
|
|
|
|
type retentionRules []retentionRule
|
|
|
|
|
|
|
|
func (r retentionRules) RP() time.Duration {
|
|
|
|
// TODO: this feels very odd to me, will need to follow up with
|
|
|
|
// team to better understand this
|
|
|
|
for _, rule := range r {
|
|
|
|
return time.Duration(rule.Seconds) * time.Second
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r retentionRules) valid() []validationErr {
|
|
|
|
var failures []validationErr
|
|
|
|
for i, rule := range r {
|
|
|
|
if ff := rule.valid(); len(ff) > 0 {
|
|
|
|
failures = append(failures, validationErr{
|
|
|
|
Field: fieldBucketRetentionRules,
|
|
|
|
Index: intPtr(i),
|
|
|
|
Nested: ff,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return failures
|
2019-11-03 17:33:36 +00:00
|
|
|
}
|
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
type assocMapKey struct {
|
2019-10-28 22:23:40 +00:00
|
|
|
resType influxdb.ResourceType
|
|
|
|
name string
|
2019-10-23 17:09:04 +00:00
|
|
|
}
|
2019-10-24 23:59:01 +00:00
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
type assocMapVal struct {
|
2019-10-28 22:23:40 +00:00
|
|
|
exists bool
|
|
|
|
v interface{}
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
func (l assocMapVal) bucket() (*bucket, bool) {
|
2019-10-28 22:23:40 +00:00
|
|
|
if l.v == nil {
|
|
|
|
return nil, false
|
2019-10-24 23:59:01 +00:00
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
b, ok := l.v.(*bucket)
|
|
|
|
return b, ok
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
func (l assocMapVal) dashboard() (*dashboard, bool) {
|
2019-10-30 21:13:42 +00:00
|
|
|
if l.v == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
d, ok := l.v.(*dashboard)
|
|
|
|
return d, ok
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
func (l assocMapVal) variable() (*variable, bool) {
|
|
|
|
if l.v == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
v, ok := l.v.(*variable)
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
type associationMapping struct {
|
|
|
|
mappings map[assocMapKey]assocMapVal
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
func (l *associationMapping) setMapping(v interface {
|
|
|
|
ResourceType() influxdb.ResourceType
|
|
|
|
Name() string
|
|
|
|
}, exists bool) {
|
2019-11-07 00:45:00 +00:00
|
|
|
if l == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if l.mappings == nil {
|
|
|
|
l.mappings = make(map[assocMapKey]assocMapVal)
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
k := assocMapKey{
|
2019-11-07 00:45:00 +00:00
|
|
|
resType: v.ResourceType(),
|
2019-12-03 02:05:10 +00:00
|
|
|
name: v.Name(),
|
2019-11-07 00:45:00 +00:00
|
|
|
}
|
2019-12-03 02:05:10 +00:00
|
|
|
l.mappings[k] = assocMapVal{
|
2019-11-07 00:45:00 +00:00
|
|
|
exists: exists,
|
|
|
|
v: v,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldLabelColor = "color"
|
|
|
|
)
|
|
|
|
|
2019-10-28 22:23:40 +00:00
|
|
|
type label struct {
|
2019-10-30 17:55:13 +00:00
|
|
|
id influxdb.ID
|
2019-10-28 22:23:40 +00:00
|
|
|
OrgID influxdb.ID
|
2019-12-03 02:05:10 +00:00
|
|
|
name string
|
2019-10-28 22:23:40 +00:00
|
|
|
Color string
|
|
|
|
Description string
|
2019-11-07 00:45:00 +00:00
|
|
|
associationMapping
|
2019-10-28 22:23:40 +00:00
|
|
|
|
|
|
|
// exists provides context for a resource that already
|
|
|
|
// exists in the platform. If a resource already exists(exists=true)
|
|
|
|
// then the ID should be populated.
|
|
|
|
existing *influxdb.Label
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
func (l *label) Name() string {
|
|
|
|
return l.name
|
2019-11-03 17:33:36 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func (l *label) ID() influxdb.ID {
|
|
|
|
if l.existing != nil {
|
|
|
|
return l.existing.ID
|
|
|
|
}
|
|
|
|
return l.id
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
func (l *label) shouldApply() bool {
|
|
|
|
return l.existing == nil ||
|
|
|
|
l.Description != l.existing.Properties["description"] ||
|
|
|
|
l.Name() != l.existing.Name ||
|
|
|
|
l.Color != l.existing.Properties["color"]
|
|
|
|
}
|
|
|
|
|
2019-10-30 17:55:13 +00:00
|
|
|
func (l *label) summarize() SummaryLabel {
|
|
|
|
return SummaryLabel{
|
|
|
|
Label: influxdb.Label{
|
|
|
|
ID: l.ID(),
|
|
|
|
OrgID: l.OrgID,
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: l.Name(),
|
2019-10-30 17:55:13 +00:00
|
|
|
Properties: l.properties(),
|
|
|
|
},
|
2019-10-24 23:59:01 +00:00
|
|
|
}
|
2019-10-30 17:55:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *label) mappingSummary() []SummaryLabelMapping {
|
|
|
|
var mappings []SummaryLabelMapping
|
|
|
|
for res, lm := range l.mappings {
|
|
|
|
mappings = append(mappings, SummaryLabelMapping{
|
2019-10-28 22:23:40 +00:00
|
|
|
exists: lm.exists,
|
2019-10-30 17:55:13 +00:00
|
|
|
ResourceName: res.name,
|
2019-12-03 02:05:10 +00:00
|
|
|
LabelName: l.Name(),
|
2019-10-28 22:23:40 +00:00
|
|
|
LabelMapping: influxdb.LabelMapping{
|
2019-10-30 17:55:13 +00:00
|
|
|
LabelID: l.ID(),
|
|
|
|
ResourceID: l.getMappedResourceID(res),
|
|
|
|
ResourceType: res.resType,
|
2019-10-28 22:23:40 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return mappings
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
func (l *label) getMappedResourceID(k assocMapKey) influxdb.ID {
|
2019-10-28 22:23:40 +00:00
|
|
|
switch k.resType {
|
|
|
|
case influxdb.BucketsResourceType:
|
|
|
|
b, ok := l.mappings[k].bucket()
|
|
|
|
if ok {
|
2019-10-30 17:55:13 +00:00
|
|
|
return b.ID()
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
2019-10-30 21:13:42 +00:00
|
|
|
case influxdb.DashboardsResourceType:
|
|
|
|
d, ok := l.mappings[k].dashboard()
|
|
|
|
if ok {
|
|
|
|
return d.ID()
|
|
|
|
}
|
2019-11-07 00:45:00 +00:00
|
|
|
case influxdb.VariablesResourceType:
|
|
|
|
v, ok := l.mappings[k].variable()
|
|
|
|
if ok {
|
|
|
|
return v.ID()
|
|
|
|
}
|
2019-10-28 22:23:40 +00:00
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *label) properties() map[string]string {
|
|
|
|
return map[string]string{
|
|
|
|
"color": l.Color,
|
|
|
|
"description": l.Description,
|
|
|
|
}
|
|
|
|
}
|
2019-10-30 21:13:42 +00:00
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
func toInfluxLabels(labels ...*label) []influxdb.Label {
|
|
|
|
var iLabels []influxdb.Label
|
|
|
|
for _, l := range labels {
|
|
|
|
iLabels = append(iLabels, influxdb.Label{
|
|
|
|
ID: l.ID(),
|
|
|
|
OrgID: l.OrgID,
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: l.Name(),
|
2019-11-01 18:11:42 +00:00
|
|
|
Properties: l.properties(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return iLabels
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
type sortedLogos []*label
|
|
|
|
|
|
|
|
func (s sortedLogos) Len() int {
|
|
|
|
return len(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s sortedLogos) Less(i, j int) bool {
|
|
|
|
return s[i].name < s[j].name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s sortedLogos) Swap(i, j int) {
|
|
|
|
s[i], s[j] = s[j], s[i]
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldArgTypeConstant = "constant"
|
|
|
|
fieldArgTypeMap = "map"
|
|
|
|
fieldArgTypeQuery = "query"
|
|
|
|
)
|
|
|
|
|
2019-11-06 22:41:06 +00:00
|
|
|
type variable struct {
|
|
|
|
id influxdb.ID
|
|
|
|
OrgID influxdb.ID
|
2019-12-03 02:05:10 +00:00
|
|
|
name string
|
2019-11-06 22:41:06 +00:00
|
|
|
Description string
|
|
|
|
Type string
|
|
|
|
Query string
|
|
|
|
Language string
|
|
|
|
ConstValues []string
|
|
|
|
MapValues map[string]string
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
labels sortedLogos
|
2019-11-07 00:45:00 +00:00
|
|
|
|
|
|
|
existing *influxdb.Variable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *variable) ID() influxdb.ID {
|
|
|
|
if v.existing != nil {
|
|
|
|
return v.existing.ID
|
|
|
|
}
|
|
|
|
return v.id
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *variable) Exists() bool {
|
|
|
|
return v.existing != nil
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
func (v *variable) Name() string {
|
|
|
|
return v.name
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:45:00 +00:00
|
|
|
func (v *variable) ResourceType() influxdb.ResourceType {
|
|
|
|
return influxdb.VariablesResourceType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *variable) shouldApply() bool {
|
|
|
|
return v.existing == nil ||
|
|
|
|
v.existing.Description != v.Description ||
|
|
|
|
v.existing.Arguments == nil ||
|
|
|
|
v.existing.Arguments.Type != v.Type
|
2019-11-06 22:41:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *variable) summarize() SummaryVariable {
|
2019-11-07 00:45:00 +00:00
|
|
|
return SummaryVariable{
|
|
|
|
Variable: influxdb.Variable{
|
|
|
|
ID: v.ID(),
|
|
|
|
OrganizationID: v.OrgID,
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: v.Name(),
|
2019-11-07 00:45:00 +00:00
|
|
|
Description: v.Description,
|
|
|
|
Arguments: v.influxVarArgs(),
|
|
|
|
},
|
|
|
|
LabelAssociations: toInfluxLabels(v.labels...),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *variable) influxVarArgs() *influxdb.VariableArguments {
|
2019-11-06 22:41:06 +00:00
|
|
|
args := &influxdb.VariableArguments{
|
|
|
|
Type: v.Type,
|
|
|
|
}
|
|
|
|
switch args.Type {
|
|
|
|
case "query":
|
|
|
|
args.Values = influxdb.VariableQueryValues{
|
|
|
|
Query: v.Query,
|
|
|
|
Language: v.Language,
|
|
|
|
}
|
|
|
|
case "constant":
|
|
|
|
args.Values = influxdb.VariableConstantValues(v.ConstValues)
|
|
|
|
case "map":
|
|
|
|
args.Values = influxdb.VariableMapValues(v.MapValues)
|
|
|
|
}
|
2019-11-07 00:45:00 +00:00
|
|
|
return args
|
2019-11-06 22:41:06 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func (v *variable) valid() []validationErr {
|
|
|
|
var failures []validationErr
|
2019-11-06 22:41:06 +00:00
|
|
|
switch v.Type {
|
|
|
|
case "map":
|
|
|
|
if len(v.MapValues) == 0 {
|
2019-11-22 01:07:12 +00:00
|
|
|
failures = append(failures, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldValues,
|
2019-11-06 22:41:06 +00:00
|
|
|
Msg: "map variable must have at least 1 key/val pair",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
case "constant":
|
|
|
|
if len(v.ConstValues) == 0 {
|
2019-11-22 01:07:12 +00:00
|
|
|
failures = append(failures, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldValues,
|
2019-11-06 22:41:06 +00:00
|
|
|
Msg: "constant variable must have a least 1 value provided",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
case "query":
|
|
|
|
if v.Query == "" {
|
2019-11-22 01:07:12 +00:00
|
|
|
failures = append(failures, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldQuery,
|
2019-11-06 22:41:06 +00:00
|
|
|
Msg: "query variable must provide a query string",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if v.Language != "influxql" && v.Language != "flux" {
|
2019-11-22 01:07:12 +00:00
|
|
|
failures = append(failures, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldLanguage,
|
2019-11-14 00:24:05 +00:00
|
|
|
Msg: fmt.Sprintf(`query variable language must be either "influxql" or "flux"; got %q`, v.Language),
|
2019-11-06 22:41:06 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return failures
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldDashCharts = "charts"
|
|
|
|
)
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
type dashboard struct {
|
|
|
|
id influxdb.ID
|
|
|
|
OrgID influxdb.ID
|
2019-12-03 02:05:10 +00:00
|
|
|
name string
|
2019-10-30 21:13:42 +00:00
|
|
|
Description string
|
2019-11-01 18:11:42 +00:00
|
|
|
Charts []chart
|
2019-10-30 21:13:42 +00:00
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
labels sortedLogos
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *dashboard) ID() influxdb.ID {
|
|
|
|
return d.id
|
|
|
|
}
|
|
|
|
|
2019-12-03 02:05:10 +00:00
|
|
|
func (d *dashboard) Name() string {
|
|
|
|
return d.name
|
|
|
|
}
|
|
|
|
|
2019-10-30 21:13:42 +00:00
|
|
|
func (d *dashboard) ResourceType() influxdb.ResourceType {
|
|
|
|
return influxdb.DashboardsResourceType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *dashboard) Exists() bool {
|
2019-11-01 18:11:42 +00:00
|
|
|
return false
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *dashboard) summarize() SummaryDashboard {
|
|
|
|
iDash := SummaryDashboard{
|
2019-11-05 01:40:42 +00:00
|
|
|
ID: SafeID(d.ID()),
|
|
|
|
OrgID: SafeID(d.OrgID),
|
2019-12-03 02:05:10 +00:00
|
|
|
Name: d.Name(),
|
2019-11-01 18:11:42 +00:00
|
|
|
Description: d.Description,
|
|
|
|
LabelAssociations: toInfluxLabels(d.labels...),
|
2019-10-30 21:13:42 +00:00
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
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,
|
2019-10-30 21:13:42 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return iDash
|
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldChartAxes = "axes"
|
2019-11-16 20:14:46 +00:00
|
|
|
fieldChartBinCount = "binCount"
|
|
|
|
fieldChartBinSize = "binSize"
|
2019-11-08 19:33:41 +00:00
|
|
|
fieldChartColors = "colors"
|
|
|
|
fieldChartDecimalPlaces = "decimalPlaces"
|
2019-11-16 20:14:46 +00:00
|
|
|
fieldChartDomain = "domain"
|
2019-11-08 19:33:41 +00:00
|
|
|
fieldChartGeom = "geom"
|
|
|
|
fieldChartHeight = "height"
|
|
|
|
fieldChartLegend = "legend"
|
|
|
|
fieldChartNote = "note"
|
|
|
|
fieldChartNoteOnEmpty = "noteOnEmpty"
|
2019-11-16 20:14:46 +00:00
|
|
|
fieldChartPosition = "position"
|
2019-11-08 19:33:41 +00:00
|
|
|
fieldChartQueries = "queries"
|
|
|
|
fieldChartShade = "shade"
|
|
|
|
fieldChartWidth = "width"
|
|
|
|
fieldChartXCol = "xCol"
|
|
|
|
fieldChartXPos = "xPos"
|
|
|
|
fieldChartYCol = "yCol"
|
|
|
|
fieldChartYPos = "yPos"
|
|
|
|
)
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
type chart struct {
|
2019-11-05 22:08:30 +00:00
|
|
|
Kind chartKind
|
2019-11-01 18:11:42 +00:00
|
|
|
Name string
|
|
|
|
Prefix string
|
|
|
|
Suffix string
|
|
|
|
Note string
|
|
|
|
NoteOnEmpty bool
|
|
|
|
DecimalPlaces int
|
|
|
|
EnforceDecimals bool
|
|
|
|
Shade bool
|
2019-11-04 19:16:32 +00:00
|
|
|
Legend legend
|
|
|
|
Colors colors
|
|
|
|
Queries queries
|
|
|
|
Axes axes
|
2019-11-05 19:19:25 +00:00
|
|
|
Geom string
|
2019-11-15 01:05:21 +00:00
|
|
|
XCol, YCol string
|
|
|
|
XPos, YPos int
|
|
|
|
Height, Width int
|
|
|
|
BinSize int
|
2019-11-16 20:14:46 +00:00
|
|
|
BinCount int
|
|
|
|
Position string
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c chart) properties() influxdb.ViewProperties {
|
|
|
|
switch c.Kind {
|
2019-11-08 19:33:41 +00:00
|
|
|
case chartKindGauge:
|
|
|
|
return influxdb.GaugeViewProperties{
|
|
|
|
Type: influxdb.ViewPropertyTypeGauge,
|
|
|
|
Queries: c.Queries.influxDashQueries(),
|
|
|
|
Prefix: c.Prefix,
|
|
|
|
Suffix: c.Suffix,
|
|
|
|
ViewColors: c.Colors.influxViewColors(),
|
|
|
|
DecimalPlaces: influxdb.DecimalPlaces{
|
|
|
|
IsEnforced: c.EnforceDecimals,
|
|
|
|
Digits: int32(c.DecimalPlaces),
|
|
|
|
},
|
|
|
|
Note: c.Note,
|
|
|
|
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
|
|
|
}
|
2019-11-13 21:30:52 +00:00
|
|
|
case chartKindHeatMap:
|
|
|
|
return influxdb.HeatmapViewProperties{
|
|
|
|
Type: influxdb.ViewPropertyTypeHeatMap,
|
2019-11-12 18:06:53 +00:00
|
|
|
Queries: c.Queries.influxDashQueries(),
|
|
|
|
ViewColors: c.Colors.strings(),
|
2019-11-13 21:30:52 +00:00
|
|
|
BinSize: int32(c.BinSize),
|
2019-11-12 18:06:53 +00:00
|
|
|
XColumn: c.XCol,
|
|
|
|
YColumn: c.YCol,
|
2019-11-15 01:05:21 +00:00
|
|
|
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,
|
2019-11-12 18:06:53 +00:00
|
|
|
Note: c.Note,
|
|
|
|
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
|
|
|
}
|
2019-11-16 20:14:46 +00:00
|
|
|
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,
|
|
|
|
}
|
2019-11-13 21:30:52 +00:00
|
|
|
case chartKindMarkdown:
|
|
|
|
return influxdb.MarkdownViewProperties{
|
|
|
|
Type: influxdb.ViewPropertyTypeMarkdown,
|
|
|
|
Note: c.Note,
|
|
|
|
}
|
|
|
|
case chartKindScatter:
|
|
|
|
return influxdb.ScatterViewProperties{
|
|
|
|
Type: influxdb.ViewPropertyTypeScatter,
|
2019-11-12 20:09:13 +00:00
|
|
|
Queries: c.Queries.influxDashQueries(),
|
|
|
|
ViewColors: c.Colors.strings(),
|
|
|
|
XColumn: c.XCol,
|
|
|
|
YColumn: c.YCol,
|
2019-11-15 01:05:21 +00:00
|
|
|
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,
|
2019-11-12 20:09:13 +00:00
|
|
|
Note: c.Note,
|
|
|
|
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
|
|
|
}
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindSingleStat:
|
2019-11-01 18:11:42 +00:00
|
|
|
return influxdb.SingleStatViewProperties{
|
2019-11-08 19:33:41 +00:00
|
|
|
Type: influxdb.ViewPropertyTypeSingleStat,
|
2019-11-01 18:11:42 +00:00
|
|
|
Prefix: c.Prefix,
|
|
|
|
Suffix: c.Suffix,
|
|
|
|
DecimalPlaces: influxdb.DecimalPlaces{
|
|
|
|
IsEnforced: c.EnforceDecimals,
|
|
|
|
Digits: int32(c.DecimalPlaces),
|
|
|
|
},
|
|
|
|
Note: c.Note,
|
|
|
|
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
2019-11-04 19:16:32 +00:00
|
|
|
Queries: c.Queries.influxDashQueries(),
|
|
|
|
ViewColors: c.Colors.influxViewColors(),
|
|
|
|
}
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindSingleStatPlusLine:
|
2019-11-04 19:16:32 +00:00
|
|
|
return influxdb.LinePlusSingleStatProperties{
|
2019-11-08 19:33:41 +00:00
|
|
|
Type: influxdb.ViewPropertyTypeSingleStatPlusLine,
|
2019-11-04 19:16:32 +00:00
|
|
|
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(),
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindXY:
|
2019-11-05 19:19:25 +00:00
|
|
|
return influxdb.XYViewProperties{
|
2019-11-08 19:33:41 +00:00
|
|
|
Type: influxdb.ViewPropertyTypeXY,
|
2019-11-05 19:19:25 +00:00
|
|
|
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,
|
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func (c chart) validProperties() []validationErr {
|
2019-11-13 21:30:52 +00:00
|
|
|
if c.Kind == chartKindMarkdown {
|
|
|
|
// at the time of writing, there's nothing to validate for markdown types
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
var fails []validationErr
|
2019-11-01 18:11:42 +00:00
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
validatorFns := []func() []validationErr{
|
2019-11-01 18:11:42 +00:00
|
|
|
c.validBaseProps,
|
2019-11-04 19:16:32 +00:00
|
|
|
c.Queries.valid,
|
|
|
|
c.Colors.valid,
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
for _, validatorFn := range validatorFns {
|
|
|
|
fails = append(fails, validatorFn()...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// chart kind specific validations
|
|
|
|
switch c.Kind {
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindGauge:
|
|
|
|
fails = append(fails, c.Colors.hasTypes(colorTypeMin, colorTypeThreshold, colorTypeMax)...)
|
2019-11-12 20:09:13 +00:00
|
|
|
case chartKindHeatMap:
|
|
|
|
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
2019-11-16 20:14:46 +00:00
|
|
|
case chartKindHistogram:
|
|
|
|
fails = append(fails, c.Axes.hasAxes("x")...)
|
|
|
|
case chartKindScatter:
|
|
|
|
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindSingleStat:
|
2019-11-04 19:16:32 +00:00
|
|
|
fails = append(fails, c.Colors.hasTypes(colorTypeText)...)
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindSingleStatPlusLine:
|
2019-11-08 19:33:41 +00:00
|
|
|
fails = append(fails, c.Colors.hasTypes(colorTypeText)...)
|
2019-11-04 19:16:32 +00:00
|
|
|
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
2019-11-05 22:08:30 +00:00
|
|
|
case chartKindXY:
|
2019-11-05 19:19:25 +00:00
|
|
|
fails = append(fails, validGeometry(c.Geom)...)
|
|
|
|
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fails
|
|
|
|
}
|
|
|
|
|
2019-11-05 19:19:25 +00:00
|
|
|
var geometryTypes = map[string]bool{
|
|
|
|
"line": true,
|
|
|
|
"step": true,
|
|
|
|
"stacked": true,
|
|
|
|
"bar": true,
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func validGeometry(geom string) []validationErr {
|
2019-11-05 19:19:25 +00:00
|
|
|
if !geometryTypes[geom] {
|
2019-11-08 19:33:41 +00:00
|
|
|
msg := "type not found"
|
|
|
|
if geom != "" {
|
|
|
|
msg = "type provided is not supported"
|
|
|
|
}
|
2019-11-22 01:07:12 +00:00
|
|
|
return []validationErr{{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartGeom,
|
2019-11-08 19:33:41 +00:00
|
|
|
Msg: fmt.Sprintf("%s: %q", msg, geom),
|
2019-11-05 19:19:25 +00:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func (c chart) validBaseProps() []validationErr {
|
|
|
|
var fails []validationErr
|
2019-11-01 18:11:42 +00:00
|
|
|
if c.Width <= 0 {
|
2019-11-22 01:07:12 +00:00
|
|
|
fails = append(fails, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartWidth,
|
2019-11-01 18:11:42 +00:00
|
|
|
Msg: "must be greater than 0",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Height <= 0 {
|
2019-11-22 01:07:12 +00:00
|
|
|
fails = append(fails, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartHeight,
|
2019-11-01 18:11:42 +00:00
|
|
|
Msg: "must be greater than 0",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return fails
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2019-11-06 19:22:36 +00:00
|
|
|
colorTypeMin = "min"
|
|
|
|
colorTypeMax = "max"
|
2019-11-05 22:08:30 +00:00
|
|
|
colorTypeScale = "scale"
|
|
|
|
colorTypeText = "text"
|
|
|
|
colorTypeThreshold = "threshold"
|
2019-11-01 18:11:42 +00:00
|
|
|
)
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldColorHex = "hex"
|
|
|
|
)
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
type color struct {
|
2019-11-08 19:33:41 +00:00
|
|
|
id string
|
|
|
|
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"`
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
// - verify templates are desired
|
|
|
|
// - template colors so references can be shared
|
|
|
|
type colors []*color
|
|
|
|
|
|
|
|
func (c colors) influxViewColors() []influxdb.ViewColor {
|
2019-11-08 19:33:41 +00:00
|
|
|
ptrToFloat64 := func(f *float64) float64 {
|
|
|
|
if f == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return *f
|
|
|
|
}
|
|
|
|
|
2019-11-01 18:11:42 +00:00
|
|
|
var iColors []influxdb.ViewColor
|
|
|
|
for _, cc := range c {
|
|
|
|
iColors = append(iColors, influxdb.ViewColor{
|
|
|
|
// need to figure out where to add this, feels best to put it in here for now
|
|
|
|
// until we figure out what to do with sharing colors, or if that is even necessary
|
|
|
|
ID: cc.id,
|
|
|
|
Type: cc.Type,
|
|
|
|
Hex: cc.Hex,
|
|
|
|
Name: cc.Name,
|
2019-11-08 19:33:41 +00:00
|
|
|
Value: ptrToFloat64(cc.Value),
|
2019-11-01 18:11:42 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return iColors
|
|
|
|
}
|
|
|
|
|
2019-11-12 18:06:53 +00:00
|
|
|
func (c colors) strings() []string {
|
|
|
|
clrs := []string{}
|
|
|
|
|
|
|
|
for _, clr := range c {
|
|
|
|
clrs = append(clrs, clr.Hex)
|
|
|
|
}
|
|
|
|
|
|
|
|
return clrs
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
// 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.
|
2019-11-22 01:07:12 +00:00
|
|
|
func (c colors) hasTypes(types ...string) []validationErr {
|
2019-11-04 19:16:32 +00:00
|
|
|
tMap := make(map[string]bool)
|
|
|
|
for _, cc := range c {
|
|
|
|
tMap[cc.Type] = true
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
var failures []validationErr
|
2019-11-04 19:16:32 +00:00
|
|
|
for _, t := range types {
|
|
|
|
if !tMap[t] {
|
2019-11-22 01:07:12 +00:00
|
|
|
failures = append(failures, validationErr{
|
2019-11-04 19:16:32 +00:00
|
|
|
Field: "colors",
|
|
|
|
Msg: fmt.Sprintf("type not found: %q", t),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return failures
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func (c colors) valid() []validationErr {
|
|
|
|
var fails []validationErr
|
2019-11-01 18:11:42 +00:00
|
|
|
for i, cc := range c {
|
2019-11-22 01:07:12 +00:00
|
|
|
cErr := validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartColors,
|
2019-11-14 00:24:05 +00:00
|
|
|
Index: intPtr(i),
|
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
if cc.Hex == "" {
|
2019-11-22 01:07:12 +00:00
|
|
|
cErr.Nested = append(cErr.Nested, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldColorHex,
|
2019-11-01 18:11:42 +00:00
|
|
|
Msg: "a color must have a hex value provided",
|
|
|
|
})
|
|
|
|
}
|
2019-11-14 00:24:05 +00:00
|
|
|
if len(cErr.Nested) > 0 {
|
|
|
|
fails = append(fails, cErr)
|
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fails
|
|
|
|
}
|
|
|
|
|
|
|
|
type query struct {
|
2019-11-08 19:33:41 +00:00
|
|
|
Query string `json:"query" yaml:"query"`
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 thsi buidler configs when issue https://github.com/influxdata/influxdb/issues/15708 is fixed up
|
|
|
|
newQuery.BuilderConfig.Tags = append(newQuery.BuilderConfig.Tags, influxdb.NewBuilderTag("_measurement"))
|
|
|
|
iQueries = append(iQueries, newQuery)
|
|
|
|
}
|
|
|
|
return iQueries
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func (q queries) valid() []validationErr {
|
|
|
|
var fails []validationErr
|
2019-11-01 18:11:42 +00:00
|
|
|
if len(q) == 0 {
|
2019-11-22 01:07:12 +00:00
|
|
|
fails = append(fails, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartQueries,
|
2019-11-01 18:11:42 +00:00
|
|
|
Msg: "at least 1 query must be provided",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, qq := range q {
|
2019-11-22 01:07:12 +00:00
|
|
|
qErr := validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartQueries,
|
2019-11-14 00:24:05 +00:00
|
|
|
Index: intPtr(i),
|
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
if qq.Query == "" {
|
2019-11-22 01:07:12 +00:00
|
|
|
qErr.Nested = append(fails, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldQuery,
|
2019-11-01 18:11:42 +00:00
|
|
|
Msg: "a query must be provided",
|
|
|
|
})
|
|
|
|
}
|
2019-11-14 00:24:05 +00:00
|
|
|
if len(qErr.Nested) > 0 {
|
|
|
|
fails = append(fails, qErr)
|
|
|
|
}
|
2019-11-01 18:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fails
|
|
|
|
}
|
2019-11-04 19:16:32 +00:00
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldAxisBase = "base"
|
|
|
|
fieldAxisLabel = "label"
|
|
|
|
fieldAxisScale = "scale"
|
|
|
|
)
|
|
|
|
|
2019-11-04 19:16:32 +00:00
|
|
|
type axis struct {
|
2019-11-15 01:05:21 +00:00
|
|
|
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"`
|
2019-11-04 19:16:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type axes []axis
|
|
|
|
|
2019-11-15 01:05:21 +00:00
|
|
|
func (a axes) get(name string) axis {
|
|
|
|
for _, ax := range a {
|
|
|
|
if name == ax.Name {
|
|
|
|
return ax
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return axis{}
|
|
|
|
}
|
|
|
|
|
2019-11-04 19:16:32 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
func (a axes) hasAxes(expectedAxes ...string) []validationErr {
|
2019-11-04 19:16:32 +00:00
|
|
|
mAxes := make(map[string]bool)
|
|
|
|
for _, ax := range a {
|
|
|
|
mAxes[ax.Name] = true
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:07:12 +00:00
|
|
|
var failures []validationErr
|
2019-11-04 19:16:32 +00:00
|
|
|
for _, expected := range expectedAxes {
|
|
|
|
if !mAxes[expected] {
|
2019-11-22 01:07:12 +00:00
|
|
|
failures = append(failures, validationErr{
|
2019-11-22 18:41:08 +00:00
|
|
|
Field: fieldChartAxes,
|
2019-11-04 19:16:32 +00:00
|
|
|
Msg: fmt.Sprintf("axis not found: %q", expected),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return failures
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:33:41 +00:00
|
|
|
const (
|
|
|
|
fieldLegendOrientation = "orientation"
|
|
|
|
)
|
|
|
|
|
2019-11-04 19:16:32 +00:00
|
|
|
type legend struct {
|
2019-11-08 19:33:41 +00:00
|
|
|
Orientation string `json:"orientation,omitempty" yaml:"orientation,omitempty"`
|
|
|
|
Type string `json:"type" yaml:"type"`
|
2019-11-04 19:16:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l legend) influxLegend() influxdb.Legend {
|
|
|
|
return influxdb.Legend{
|
|
|
|
Type: l.Type,
|
|
|
|
Orientation: l.Orientation,
|
|
|
|
}
|
|
|
|
}
|
2019-11-08 19:33:41 +00:00
|
|
|
|
|
|
|
func flt64Ptr(f float64) *float64 {
|
|
|
|
if f != 0 {
|
|
|
|
return &f
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-11-14 00:24:05 +00:00
|
|
|
|
|
|
|
func intPtr(i int) *int {
|
|
|
|
return &i
|
|
|
|
}
|