feat(pkger): add export functonality to pkger for existing buckets/labels/dashboards
no associations included at this time. Also fixes http response to be just the pkg without the envelope. Having that envelope makes the API icky to work with from any shell script or just saving it to file. This feels more organic to just drop that envelope.pull/15824/head
parent
f2cda2ae10
commit
a64b976561
37
dashboard.go
37
dashboard.go
|
@ -299,6 +299,21 @@ type ViewContents struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Values for all supported view property types.
|
||||
const (
|
||||
ViewPropertyTypeCheck = "check"
|
||||
ViewPropertyTypeGauge = "gauge"
|
||||
ViewPropertyTypeHeatMap = "heatmap"
|
||||
ViewPropertyTypeHistogram = "histogram"
|
||||
ViewPropertyTypeLogViewer = "log-viewer"
|
||||
ViewPropertyTypeMarkdown = "markdown"
|
||||
ViewPropertyTypeScatter = "scatter"
|
||||
ViewPropertyTypeSingleStat = "single-stat"
|
||||
ViewPropertyTypeSingleStatPlusLine = "line-plus-single-stat"
|
||||
ViewPropertyTypeTable = "table"
|
||||
ViewPropertyTypeXY = "xy"
|
||||
)
|
||||
|
||||
// ViewProperties is used to mark other structures as conforming to a View.
|
||||
type ViewProperties interface {
|
||||
viewProperties()
|
||||
|
@ -340,67 +355,67 @@ func UnmarshalViewPropertiesJSON(b []byte) (ViewProperties, error) {
|
|||
switch t.Shape {
|
||||
case "chronograf-v2":
|
||||
switch t.Type {
|
||||
case "check":
|
||||
case ViewPropertyTypeCheck:
|
||||
var cv CheckViewProperties
|
||||
if err := json.Unmarshal(v.B, &cv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = cv
|
||||
case "xy":
|
||||
case ViewPropertyTypeXY:
|
||||
var xyv XYViewProperties
|
||||
if err := json.Unmarshal(v.B, &xyv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = xyv
|
||||
case "single-stat":
|
||||
case ViewPropertyTypeSingleStat:
|
||||
var ssv SingleStatViewProperties
|
||||
if err := json.Unmarshal(v.B, &ssv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = ssv
|
||||
case "gauge":
|
||||
case ViewPropertyTypeGauge:
|
||||
var gv GaugeViewProperties
|
||||
if err := json.Unmarshal(v.B, &gv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = gv
|
||||
case "table":
|
||||
case ViewPropertyTypeTable:
|
||||
var tv TableViewProperties
|
||||
if err := json.Unmarshal(v.B, &tv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = tv
|
||||
case "markdown":
|
||||
case ViewPropertyTypeMarkdown:
|
||||
var mv MarkdownViewProperties
|
||||
if err := json.Unmarshal(v.B, &mv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = mv
|
||||
case "log-viewer": // happens in log viewer stays in log viewer.
|
||||
case ViewPropertyTypeLogViewer: // happens in log viewer stays in log viewer.
|
||||
var lv LogViewProperties
|
||||
if err := json.Unmarshal(v.B, &lv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = lv
|
||||
case "line-plus-single-stat":
|
||||
case ViewPropertyTypeSingleStatPlusLine:
|
||||
var lv LinePlusSingleStatProperties
|
||||
if err := json.Unmarshal(v.B, &lv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = lv
|
||||
case "histogram":
|
||||
case ViewPropertyTypeHistogram:
|
||||
var hv HistogramViewProperties
|
||||
if err := json.Unmarshal(v.B, &hv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = hv
|
||||
case "heatmap":
|
||||
case ViewPropertyTypeHeatMap:
|
||||
var hv HeatmapViewProperties
|
||||
if err := json.Unmarshal(v.B, &hv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = hv
|
||||
case "scatter":
|
||||
case ViewPropertyTypeScatter:
|
||||
var sv ScatterViewProperties
|
||||
if err := json.Unmarshal(v.B, &sv); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -54,11 +54,13 @@ type ReqCreatePkg struct {
|
|||
PkgName string `json:"pkgName"`
|
||||
PkgDescription string `json:"pkgDescription"`
|
||||
PkgVersion string `json:"pkgVersion"`
|
||||
|
||||
Resources []pkger.ResourceToClone `json:"resources"`
|
||||
}
|
||||
|
||||
// RespCreatePkg is a response body for the create pkg endpoint.
|
||||
type RespCreatePkg struct {
|
||||
Package *pkger.Pkg `json:"package"`
|
||||
*pkger.Pkg
|
||||
}
|
||||
|
||||
func (s *HandlerPkg) createPkg(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -75,6 +77,7 @@ func (s *HandlerPkg) createPkg(w http.ResponseWriter, r *http.Request) {
|
|||
Name: reqBody.PkgName,
|
||||
Version: reqBody.PkgVersion,
|
||||
}),
|
||||
pkger.WithResourceClones(reqBody.Resources...),
|
||||
)
|
||||
if err != nil {
|
||||
s.HandleHTTPError(r.Context(), err, w)
|
||||
|
@ -82,7 +85,7 @@ func (s *HandlerPkg) createPkg(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
s.encResp(r.Context(), w, http.StatusOK, RespCreatePkg{
|
||||
Package: newPkg,
|
||||
Pkg: newPkg,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/go-chi/chi"
|
||||
"github.com/influxdata/influxdb"
|
||||
fluxTTP "github.com/influxdata/influxdb/http"
|
||||
"github.com/influxdata/influxdb/mock"
|
||||
"github.com/influxdata/influxdb/pkger"
|
||||
"github.com/jsteenb2/testttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -21,13 +22,27 @@ import (
|
|||
func TestPkgerHTTPServer(t *testing.T) {
|
||||
t.Run("create pkg", func(t *testing.T) {
|
||||
t.Run("should successfully return with valid req body", func(t *testing.T) {
|
||||
pkgHandler := fluxTTP.NewHandlerPkg(fluxTTP.ErrorHandler(0), new(pkger.Service))
|
||||
fakeLabelSVC := mock.NewLabelService()
|
||||
fakeLabelSVC.FindLabelByIDFn = func(ctx context.Context, id influxdb.ID) (*influxdb.Label, error) {
|
||||
return &influxdb.Label{
|
||||
ID: id,
|
||||
}, nil
|
||||
}
|
||||
svc := pkger.NewService(pkger.WithLabelSVC(fakeLabelSVC))
|
||||
pkgHandler := fluxTTP.NewHandlerPkg(fluxTTP.ErrorHandler(0), svc)
|
||||
svr := newMountedHandler(pkgHandler)
|
||||
|
||||
body := newReqBody(t, fluxTTP.ReqCreatePkg{
|
||||
PkgName: "name1",
|
||||
PkgDescription: "desc1",
|
||||
PkgVersion: "v1",
|
||||
Resources: []pkger.ResourceToClone{
|
||||
{
|
||||
Kind: pkger.KindLabel,
|
||||
ID: 1,
|
||||
Name: "new name",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
testttp.Post("/api/v2/packages", body).
|
||||
|
@ -38,7 +53,8 @@ func TestPkgerHTTPServer(t *testing.T) {
|
|||
var resp fluxTTP.RespCreatePkg
|
||||
decodeBody(t, buf, &resp)
|
||||
|
||||
pkg := resp.Package
|
||||
pkg := resp.Pkg
|
||||
require.NoError(t, pkg.Validate())
|
||||
assert.Equal(t, pkger.APIVersion, pkg.APIVersion)
|
||||
assert.Equal(t, "package", pkg.Kind)
|
||||
|
||||
|
@ -47,7 +63,8 @@ func TestPkgerHTTPServer(t *testing.T) {
|
|||
assert.Equal(t, "desc1", meta.Description)
|
||||
assert.Equal(t, "v1", meta.Version)
|
||||
|
||||
assert.NotNil(t, pkg.Spec.Resources)
|
||||
assert.Len(t, pkg.Spec.Resources, 1)
|
||||
assert.Len(t, pkg.Summary().Labels, 1)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -7104,6 +7104,21 @@ components:
|
|||
type: string
|
||||
pkgVersion:
|
||||
type: string
|
||||
resources:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- bucket
|
||||
- dashboard
|
||||
- label
|
||||
- variable
|
||||
name:
|
||||
type: string
|
||||
required: [id, kind]
|
||||
Pkg:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
package pkger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
// ResourceToClone is a resource that will be cloned.
|
||||
type ResourceToClone struct {
|
||||
Kind Kind `json:"kind"`
|
||||
ID influxdb.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// OK validates a resource clone is viable.
|
||||
func (r ResourceToClone) OK() error {
|
||||
if err := r.Kind.OK(); err != nil {
|
||||
return err
|
||||
}
|
||||
if r.ID == influxdb.ID(0) {
|
||||
return errors.New("must provide an ID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bucketToResource(bkt influxdb.Bucket, name string) Resource {
|
||||
if name == "" {
|
||||
name = bkt.Name
|
||||
}
|
||||
return Resource{
|
||||
fieldKind: KindBucket.String(),
|
||||
fieldName: name,
|
||||
fieldDescription: bkt.Description,
|
||||
fieldBucketRetentionPeriod: bkt.RetentionPeriod.String(),
|
||||
}
|
||||
}
|
||||
|
||||
type cellView struct {
|
||||
c influxdb.Cell
|
||||
v influxdb.View
|
||||
}
|
||||
|
||||
func convertCellView(cv cellView) chart {
|
||||
ch := chart{
|
||||
Name: cv.v.Name,
|
||||
Height: int(cv.c.H),
|
||||
Width: int(cv.c.W),
|
||||
XPos: int(cv.c.X),
|
||||
YPos: int(cv.c.Y),
|
||||
}
|
||||
|
||||
setCommon := func(k chartKind, iColors []influxdb.ViewColor, dec influxdb.DecimalPlaces, iQueries []influxdb.DashboardQuery) {
|
||||
ch.Kind = k
|
||||
ch.Colors = convertColors(iColors)
|
||||
ch.DecimalPlaces = int(dec.Digits)
|
||||
ch.EnforceDecimals = dec.IsEnforced
|
||||
ch.Queries = convertQueries(iQueries)
|
||||
}
|
||||
|
||||
setNoteFixes := func(note string, noteOnEmpty bool, prefix, suffix string) {
|
||||
ch.Note = note
|
||||
ch.NoteOnEmpty = noteOnEmpty
|
||||
ch.Prefix = prefix
|
||||
ch.Suffix = suffix
|
||||
}
|
||||
|
||||
setLegend := func(l influxdb.Legend) {
|
||||
ch.Legend.Orientation = l.Orientation
|
||||
ch.Legend.Type = l.Type
|
||||
}
|
||||
|
||||
props := cv.v.Properties
|
||||
switch p := props.(type) {
|
||||
case influxdb.GaugeViewProperties:
|
||||
setCommon(chartKindGauge, p.ViewColors, p.DecimalPlaces, p.Queries)
|
||||
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, p.Prefix, p.Suffix)
|
||||
case influxdb.LinePlusSingleStatProperties:
|
||||
setCommon(chartKindSingleStatPlusLine, p.ViewColors, p.DecimalPlaces, p.Queries)
|
||||
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, p.Prefix, p.Suffix)
|
||||
setLegend(p.Legend)
|
||||
ch.Axes = convertAxes(p.Axes)
|
||||
ch.Shade = p.ShadeBelow
|
||||
ch.XCol = p.XColumn
|
||||
ch.YCol = p.YColumn
|
||||
case influxdb.SingleStatViewProperties:
|
||||
setCommon(chartKindSingleStat, p.ViewColors, p.DecimalPlaces, p.Queries)
|
||||
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, p.Prefix, p.Suffix)
|
||||
case influxdb.XYViewProperties:
|
||||
setCommon(chartKindXY, p.ViewColors, influxdb.DecimalPlaces{}, p.Queries)
|
||||
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, "", "")
|
||||
setLegend(p.Legend)
|
||||
ch.Axes = convertAxes(p.Axes)
|
||||
ch.Geom = p.Geom
|
||||
ch.Shade = p.ShadeBelow
|
||||
ch.XCol = p.XColumn
|
||||
ch.YCol = p.YColumn
|
||||
}
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func convertChartToResource(ch chart) Resource {
|
||||
r := Resource{
|
||||
fieldKind: string(ch.Kind),
|
||||
fieldName: ch.Name,
|
||||
fieldChartQueries: ch.Queries,
|
||||
fieldChartHeight: ch.Height,
|
||||
fieldChartWidth: ch.Width,
|
||||
}
|
||||
if len(ch.Colors) > 0 {
|
||||
r[fieldChartColors] = ch.Colors
|
||||
}
|
||||
if len(ch.Axes) > 0 {
|
||||
r[fieldChartAxes] = ch.Axes
|
||||
}
|
||||
if ch.EnforceDecimals {
|
||||
r[fieldChartDecimalPlaces] = ch.DecimalPlaces
|
||||
}
|
||||
|
||||
if ch.Legend.Type != "" {
|
||||
r[fieldChartLegend] = ch.Legend
|
||||
}
|
||||
|
||||
ignoreFalseBools := map[string]bool{
|
||||
fieldChartNoteOnEmpty: ch.NoteOnEmpty,
|
||||
fieldChartShade: ch.Shade,
|
||||
}
|
||||
for k, v := range ignoreFalseBools {
|
||||
if v {
|
||||
r[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
ignoreEmptyStrPairs := map[string]string{
|
||||
fieldChartNote: ch.Note,
|
||||
fieldPrefix: ch.Prefix,
|
||||
fieldSuffix: ch.Suffix,
|
||||
fieldChartGeom: ch.Geom,
|
||||
fieldChartXCol: ch.XCol,
|
||||
fieldChartYCol: ch.YCol,
|
||||
}
|
||||
for k, v := range ignoreEmptyStrPairs {
|
||||
if v != "" {
|
||||
r[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
ignoreEmptyIntPairs := map[string]int{
|
||||
fieldChartXPos: ch.XPos,
|
||||
fieldChartYPos: ch.YPos,
|
||||
}
|
||||
for k, v := range ignoreEmptyIntPairs {
|
||||
if v != 0 {
|
||||
r[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func convertAxes(iAxes map[string]influxdb.Axis) axes {
|
||||
out := make(axes, 0, len(iAxes))
|
||||
for name, a := range iAxes {
|
||||
out = append(out, axis{
|
||||
Base: a.Base,
|
||||
Label: a.Label,
|
||||
Name: name,
|
||||
Prefix: a.Prefix,
|
||||
Scale: a.Scale,
|
||||
Suffix: a.Suffix,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func convertColors(iColors []influxdb.ViewColor) colors {
|
||||
out := make(colors, 0, len(iColors))
|
||||
for _, ic := range iColors {
|
||||
out = append(out, &color{
|
||||
Name: ic.Name,
|
||||
Type: ic.Type,
|
||||
Hex: ic.Hex,
|
||||
Value: flt64Ptr(ic.Value),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func convertQueries(iQueries []influxdb.DashboardQuery) queries {
|
||||
out := make(queries, 0, len(iQueries))
|
||||
for _, iq := range iQueries {
|
||||
out = append(out, query{Query: iq.Text})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func dashboardToResource(dash influxdb.Dashboard, cellViews []cellView, name string) Resource {
|
||||
if name == "" {
|
||||
name = dash.Name
|
||||
}
|
||||
|
||||
sort.Slice(cellViews, func(i, j int) bool {
|
||||
ic, jc := cellViews[i].c, cellViews[j].c
|
||||
if ic.X == jc.X {
|
||||
return ic.Y < jc.Y
|
||||
}
|
||||
return ic.X < jc.X
|
||||
})
|
||||
|
||||
charts := make([]Resource, 0, len(cellViews))
|
||||
for _, cv := range cellViews {
|
||||
if cv.c.ID == influxdb.ID(0) {
|
||||
continue
|
||||
}
|
||||
ch := convertCellView(cv)
|
||||
if !ch.Kind.ok() {
|
||||
continue
|
||||
}
|
||||
charts = append(charts, convertChartToResource(ch))
|
||||
}
|
||||
|
||||
return Resource{
|
||||
fieldKind: KindDashboard.String(),
|
||||
fieldName: name,
|
||||
fieldDescription: dash.Description,
|
||||
fieldDashCharts: charts,
|
||||
}
|
||||
}
|
||||
|
||||
func labelToResource(l influxdb.Label, name string) Resource {
|
||||
if name == "" {
|
||||
name = l.Name
|
||||
}
|
||||
return Resource{
|
||||
fieldKind: KindLabel.String(),
|
||||
fieldName: name,
|
||||
fieldLabelColor: l.Properties["color"],
|
||||
fieldDescription: l.Properties["description"],
|
||||
}
|
||||
}
|
||||
|
||||
func variableToResource(v influxdb.Variable, name string) Resource {
|
||||
if name == "" {
|
||||
name = v.Name
|
||||
}
|
||||
|
||||
r := Resource{
|
||||
fieldKind: KindVariable.String(),
|
||||
fieldName: name,
|
||||
fieldDescription: v.Description,
|
||||
}
|
||||
args := v.Arguments
|
||||
if args == nil {
|
||||
return r
|
||||
}
|
||||
r[fieldType] = args.Type
|
||||
|
||||
switch args.Type {
|
||||
case fieldArgTypeConstant:
|
||||
vals, ok := args.Values.(influxdb.VariableConstantValues)
|
||||
if ok {
|
||||
r[fieldValues] = []string(vals)
|
||||
}
|
||||
case fieldArgTypeMap:
|
||||
vals, ok := args.Values.(influxdb.VariableMapValues)
|
||||
if ok {
|
||||
r[fieldValues] = map[string]string(vals)
|
||||
}
|
||||
case fieldArgTypeQuery:
|
||||
vals, ok := args.Values.(influxdb.VariableQueryValues)
|
||||
if ok {
|
||||
r[fieldVarLanguage] = vals.Language
|
||||
r[fieldQuery] = vals.Query
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
221
pkger/models.go
221
pkger/models.go
|
@ -1,41 +1,67 @@
|
|||
package pkger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
// Package kinds.
|
||||
const (
|
||||
kindUnknown kind = ""
|
||||
kindBucket kind = "bucket"
|
||||
kindDashboard kind = "dashboard"
|
||||
kindLabel kind = "label"
|
||||
kindPackage kind = "package"
|
||||
kindVariable kind = "variable"
|
||||
KindUnknown Kind = ""
|
||||
KindBucket Kind = "bucket"
|
||||
KindDashboard Kind = "dashboard"
|
||||
KindLabel Kind = "label"
|
||||
KindPackage Kind = "package"
|
||||
KindVariable Kind = "variable"
|
||||
)
|
||||
|
||||
var kinds = map[kind]bool{
|
||||
kindBucket: true,
|
||||
kindDashboard: true,
|
||||
kindLabel: true,
|
||||
kindPackage: true,
|
||||
kindVariable: true,
|
||||
var kinds = map[Kind]bool{
|
||||
KindBucket: true,
|
||||
KindDashboard: true,
|
||||
KindLabel: true,
|
||||
KindPackage: true,
|
||||
KindVariable: true,
|
||||
}
|
||||
|
||||
type kind string
|
||||
// Kind is a resource kind.
|
||||
type Kind string
|
||||
|
||||
func (k kind) String() string {
|
||||
func newKind(s string) Kind {
|
||||
return Kind(strings.TrimSpace(strings.ToLower(s)))
|
||||
}
|
||||
|
||||
// String provides the kind in human readable form.
|
||||
func (k Kind) String() string {
|
||||
if kinds[k] {
|
||||
return string(k)
|
||||
}
|
||||
if k == kindUnknown {
|
||||
if k == KindUnknown {
|
||||
return "unknown"
|
||||
}
|
||||
return string(k)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (k Kind) is(comp Kind) bool {
|
||||
normed := Kind(strings.TrimSpace(strings.ToLower(string(k))))
|
||||
return normed == comp
|
||||
}
|
||||
|
||||
// SafeID is an equivalent influxdb.ID that encodes safely with
|
||||
// zero values (influxdb.ID == 0).
|
||||
type SafeID influxdb.ID
|
||||
|
@ -271,6 +297,23 @@ type SummaryVariable struct {
|
|||
LabelAssociations []influxdb.Label `json:"labelAssociations"`
|
||||
}
|
||||
|
||||
const (
|
||||
fieldAssociations = "associations"
|
||||
fieldDescription = "description"
|
||||
fieldKind = "kind"
|
||||
fieldName = "name"
|
||||
fieldPrefix = "prefix"
|
||||
fieldQuery = "query"
|
||||
fieldSuffix = "suffix"
|
||||
fieldType = "type"
|
||||
fieldValue = "value"
|
||||
fieldValues = "values"
|
||||
)
|
||||
|
||||
const (
|
||||
fieldBucketRetentionPeriod = "retention_period"
|
||||
)
|
||||
|
||||
type bucket struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
|
@ -401,6 +444,10 @@ func (l *associationMapping) setVariableMapping(v *variable, exists bool) {
|
|||
l.setMapping(key, val)
|
||||
}
|
||||
|
||||
const (
|
||||
fieldLabelColor = "color"
|
||||
)
|
||||
|
||||
type label struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
|
@ -499,6 +546,13 @@ func toInfluxLabels(labels ...*label) []influxdb.Label {
|
|||
return iLabels
|
||||
}
|
||||
|
||||
const (
|
||||
fieldArgTypeConstant = "constant"
|
||||
fieldArgTypeMap = "map"
|
||||
fieldArgTypeQuery = "query"
|
||||
fieldVarLanguage = "language"
|
||||
)
|
||||
|
||||
type variable struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
|
@ -603,6 +657,10 @@ func (v *variable) valid() []failure {
|
|||
return failures
|
||||
}
|
||||
|
||||
const (
|
||||
fieldDashCharts = "charts"
|
||||
)
|
||||
|
||||
type dashboard struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
|
@ -645,6 +703,24 @@ func (d *dashboard) summarize() SummaryDashboard {
|
|||
return iDash
|
||||
}
|
||||
|
||||
const (
|
||||
fieldChartAxes = "axes"
|
||||
fieldChartColors = "colors"
|
||||
fieldChartDecimalPlaces = "decimalPlaces"
|
||||
fieldChartGeom = "geom"
|
||||
fieldChartHeight = "height"
|
||||
fieldChartLegend = "legend"
|
||||
fieldChartNote = "note"
|
||||
fieldChartNoteOnEmpty = "noteOnEmpty"
|
||||
fieldChartQueries = "queries"
|
||||
fieldChartShade = "shade"
|
||||
fieldChartWidth = "width"
|
||||
fieldChartXCol = "xCol"
|
||||
fieldChartXPos = "xPos"
|
||||
fieldChartYCol = "yCol"
|
||||
fieldChartYPos = "yPos"
|
||||
)
|
||||
|
||||
type chart struct {
|
||||
Kind chartKind
|
||||
Name string
|
||||
|
@ -668,9 +744,23 @@ type chart struct {
|
|||
|
||||
func (c chart) properties() influxdb.ViewProperties {
|
||||
switch c.Kind {
|
||||
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,
|
||||
}
|
||||
case chartKindSingleStat:
|
||||
return influxdb.SingleStatViewProperties{
|
||||
Type: "single-stat",
|
||||
Type: influxdb.ViewPropertyTypeSingleStat,
|
||||
Prefix: c.Prefix,
|
||||
Suffix: c.Suffix,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
|
@ -684,7 +774,7 @@ func (c chart) properties() influxdb.ViewProperties {
|
|||
}
|
||||
case chartKindSingleStatPlusLine:
|
||||
return influxdb.LinePlusSingleStatProperties{
|
||||
Type: "line-plus-single-stat",
|
||||
Type: influxdb.ViewPropertyTypeSingleStatPlusLine,
|
||||
Prefix: c.Prefix,
|
||||
Suffix: c.Suffix,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
|
@ -703,7 +793,7 @@ func (c chart) properties() influxdb.ViewProperties {
|
|||
}
|
||||
case chartKindXY:
|
||||
return influxdb.XYViewProperties{
|
||||
Type: "xy",
|
||||
Type: influxdb.ViewPropertyTypeXY,
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
XColumn: c.XCol,
|
||||
|
@ -715,20 +805,6 @@ func (c chart) properties() influxdb.ViewProperties {
|
|||
Axes: c.Axes.influxAxes(),
|
||||
Geom: c.Geom,
|
||||
}
|
||||
case chartKindGauge:
|
||||
return influxdb.GaugeViewProperties{
|
||||
Type: "gauge",
|
||||
Queries: c.Queries.influxDashQueries(),
|
||||
Prefix: c.Prefix,
|
||||
Suffix: c.Suffix,
|
||||
ViewColors: c.Colors.influxViewColors(),
|
||||
DecimalPlaces: influxdb.DecimalPlaces{
|
||||
IsEnforced: c.EnforceDecimals,
|
||||
Digits: int32(c.DecimalPlaces),
|
||||
},
|
||||
Note: c.Note,
|
||||
ShowNoteWhenEmpty: c.NoteOnEmpty,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -753,10 +829,9 @@ func (c chart) validProperties() []failure {
|
|||
case chartKindSingleStat:
|
||||
fails = append(fails, c.Colors.hasTypes(colorTypeText)...)
|
||||
case chartKindSingleStatPlusLine:
|
||||
fails = append(fails, c.Colors.hasTypes(colorTypeText, colorTypeScale)...)
|
||||
fails = append(fails, c.Colors.hasTypes(colorTypeText)...)
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
case chartKindXY:
|
||||
fails = append(fails, c.Colors.hasTypes(colorTypeScale)...)
|
||||
fails = append(fails, validGeometry(c.Geom)...)
|
||||
fails = append(fails, c.Axes.hasAxes("x", "y")...)
|
||||
}
|
||||
|
@ -773,9 +848,13 @@ var geometryTypes = map[string]bool{
|
|||
|
||||
func validGeometry(geom string) []failure {
|
||||
if !geometryTypes[geom] {
|
||||
msg := "type not found"
|
||||
if geom != "" {
|
||||
msg = "type provided is not supported"
|
||||
}
|
||||
return []failure{{
|
||||
Field: "geom",
|
||||
Msg: fmt.Sprintf("type not found: %q", geom),
|
||||
Msg: fmt.Sprintf("%s: %q", msg, geom),
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -808,12 +887,19 @@ const (
|
|||
colorTypeThreshold = "threshold"
|
||||
)
|
||||
|
||||
const (
|
||||
fieldColorHex = "hex"
|
||||
)
|
||||
|
||||
type color struct {
|
||||
id string
|
||||
Name string
|
||||
Type string
|
||||
Hex string
|
||||
Value float64
|
||||
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"`
|
||||
}
|
||||
|
||||
// TODO:
|
||||
|
@ -822,6 +908,13 @@ type color struct {
|
|||
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{
|
||||
|
@ -831,12 +924,15 @@ func (c colors) influxViewColors() []influxdb.ViewColor {
|
|||
Type: cc.Type,
|
||||
Hex: cc.Hex,
|
||||
Name: cc.Name,
|
||||
Value: cc.Value,
|
||||
Value: ptrToFloat64(cc.Value),
|
||||
})
|
||||
}
|
||||
return iColors
|
||||
}
|
||||
|
||||
// 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) []failure {
|
||||
tMap := make(map[string]bool)
|
||||
for _, cc := range c {
|
||||
|
@ -858,13 +954,6 @@ func (c colors) hasTypes(types ...string) []failure {
|
|||
|
||||
func (c colors) valid() []failure {
|
||||
var fails []failure
|
||||
if len(c) == 0 {
|
||||
fails = append(fails, failure{
|
||||
Field: "colors",
|
||||
Msg: "at least 1 color must be provided",
|
||||
})
|
||||
}
|
||||
|
||||
for i, cc := range c {
|
||||
if cc.Hex == "" {
|
||||
fails = append(fails, failure{
|
||||
|
@ -878,7 +967,7 @@ func (c colors) valid() []failure {
|
|||
}
|
||||
|
||||
type query struct {
|
||||
Query string
|
||||
Query string `json:"query" yaml:"query"`
|
||||
}
|
||||
|
||||
type queries []query
|
||||
|
@ -918,13 +1007,19 @@ func (q queries) valid() []failure {
|
|||
return fails
|
||||
}
|
||||
|
||||
const (
|
||||
fieldAxisBase = "base"
|
||||
fieldAxisLabel = "label"
|
||||
fieldAxisScale = "scale"
|
||||
)
|
||||
|
||||
type axis struct {
|
||||
Base string
|
||||
Label string
|
||||
Name string
|
||||
Prefix string
|
||||
Scale string
|
||||
Suffix string
|
||||
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"`
|
||||
}
|
||||
|
||||
type axes []axis
|
||||
|
@ -963,9 +1058,14 @@ func (a axes) hasAxes(expectedAxes ...string) []failure {
|
|||
return failures
|
||||
}
|
||||
|
||||
const (
|
||||
fieldLegendLanguage = "language"
|
||||
fieldLegendOrientation = "orientation"
|
||||
)
|
||||
|
||||
type legend struct {
|
||||
Orientation string
|
||||
Type string
|
||||
Orientation string `json:"orientation,omitempty" yaml:"orientation,omitempty"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
}
|
||||
|
||||
func (l legend) influxLegend() influxdb.Legend {
|
||||
|
@ -974,3 +1074,10 @@ func (l legend) influxLegend() influxdb.Legend {
|
|||
Orientation: l.Orientation,
|
||||
}
|
||||
}
|
||||
|
||||
func flt64Ptr(f float64) *float64 {
|
||||
if f != 0 {
|
||||
return &f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
210
pkger/parser.go
210
pkger/parser.go
|
@ -277,15 +277,15 @@ func (p *Pkg) labelMappings() []SummaryLabelMapping {
|
|||
|
||||
func (p *Pkg) validMetadata() error {
|
||||
var failures []*failure
|
||||
if p.APIVersion != "0.1.0" {
|
||||
if p.APIVersion != APIVersion {
|
||||
failures = append(failures, &failure{
|
||||
Field: "apiVersion",
|
||||
Msg: "must be version 0.1.0",
|
||||
Msg: "must be version " + APIVersion,
|
||||
})
|
||||
}
|
||||
|
||||
mKind := kind(strings.TrimSpace(strings.ToLower(p.Kind)))
|
||||
if mKind != kindPackage {
|
||||
mKind := Kind(strings.TrimSpace(strings.ToLower(p.Kind)))
|
||||
if mKind != KindPackage {
|
||||
failures = append(failures, &failure{
|
||||
Field: "kind",
|
||||
Msg: `must be of kind "Package"`,
|
||||
|
@ -294,14 +294,14 @@ func (p *Pkg) validMetadata() error {
|
|||
|
||||
if p.Metadata.Version == "" {
|
||||
failures = append(failures, &failure{
|
||||
Field: "pkgVersion",
|
||||
Field: "meta.pkgVersion",
|
||||
Msg: "version is required",
|
||||
})
|
||||
}
|
||||
|
||||
if p.Metadata.Name == "" {
|
||||
failures = append(failures, &failure{
|
||||
Field: "pkgName",
|
||||
Field: "meta.pkgName",
|
||||
Msg: "must be at least 1 char",
|
||||
})
|
||||
}
|
||||
|
@ -311,7 +311,7 @@ func (p *Pkg) validMetadata() error {
|
|||
}
|
||||
|
||||
res := errResource{
|
||||
Kind: kindPackage.String(),
|
||||
Kind: KindPackage.String(),
|
||||
Idx: -1,
|
||||
}
|
||||
for _, f := range failures {
|
||||
|
@ -366,7 +366,7 @@ func (p *Pkg) graphResources() error {
|
|||
|
||||
func (p *Pkg) graphBuckets() error {
|
||||
p.mBuckets = make(map[string]*bucket)
|
||||
return p.eachResource(kindBucket, func(r Resource) []failure {
|
||||
return p.eachResource(KindBucket, func(r Resource) []failure {
|
||||
if r.Name() == "" {
|
||||
return []failure{{
|
||||
Field: "name",
|
||||
|
@ -383,8 +383,8 @@ func (p *Pkg) graphBuckets() error {
|
|||
|
||||
bkt := &bucket{
|
||||
Name: r.Name(),
|
||||
Description: r.stringShort("description"),
|
||||
RetentionPeriod: r.duration("retention_period"),
|
||||
Description: r.stringShort(fieldDescription),
|
||||
RetentionPeriod: r.duration(fieldBucketRetentionPeriod),
|
||||
}
|
||||
|
||||
failures := p.parseNestedLabels(r, func(l *label) error {
|
||||
|
@ -407,7 +407,7 @@ func (p *Pkg) graphBuckets() error {
|
|||
|
||||
func (p *Pkg) graphLabels() error {
|
||||
p.mLabels = make(map[string]*label)
|
||||
return p.eachResource(kindLabel, func(r Resource) []failure {
|
||||
return p.eachResource(KindLabel, func(r Resource) []failure {
|
||||
if r.Name() == "" {
|
||||
return []failure{{
|
||||
Field: "name",
|
||||
|
@ -423,8 +423,8 @@ func (p *Pkg) graphLabels() error {
|
|||
}
|
||||
p.mLabels[r.Name()] = &label{
|
||||
Name: r.Name(),
|
||||
Color: r.stringShort("color"),
|
||||
Description: r.stringShort("description"),
|
||||
Color: r.stringShort(fieldLabelColor),
|
||||
Description: r.stringShort(fieldDescription),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -433,7 +433,7 @@ func (p *Pkg) graphLabels() error {
|
|||
|
||||
func (p *Pkg) graphDashboards() error {
|
||||
p.mDashboards = make(map[string]*dashboard)
|
||||
return p.eachResource(kindDashboard, func(r Resource) []failure {
|
||||
return p.eachResource(KindDashboard, func(r Resource) []failure {
|
||||
if r.Name() == "" {
|
||||
return []failure{{
|
||||
Field: "name",
|
||||
|
@ -450,7 +450,7 @@ func (p *Pkg) graphDashboards() error {
|
|||
|
||||
dash := &dashboard{
|
||||
Name: r.Name(),
|
||||
Description: r.stringShort("description"),
|
||||
Description: r.stringShort(fieldDescription),
|
||||
}
|
||||
|
||||
failures := p.parseNestedLabels(r, func(l *label) error {
|
||||
|
@ -462,7 +462,7 @@ func (p *Pkg) graphDashboards() error {
|
|||
return dash.labels[i].Name < dash.labels[j].Name
|
||||
})
|
||||
|
||||
for i, cr := range r.slcResource("charts") {
|
||||
for i, cr := range r.slcResource(fieldDashCharts) {
|
||||
ch, fails := parseChart(cr)
|
||||
if fails != nil {
|
||||
for _, f := range fails {
|
||||
|
@ -488,7 +488,7 @@ func (p *Pkg) graphDashboards() error {
|
|||
|
||||
func (p *Pkg) graphVariables() error {
|
||||
p.mVariables = make(map[string]*variable)
|
||||
return p.eachResource(kindVariable, func(r Resource) []failure {
|
||||
return p.eachResource(KindVariable, func(r Resource) []failure {
|
||||
if r.Name() == "" {
|
||||
return []failure{{
|
||||
Field: "name",
|
||||
|
@ -505,12 +505,12 @@ func (p *Pkg) graphVariables() error {
|
|||
|
||||
newVar := &variable{
|
||||
Name: r.Name(),
|
||||
Description: r.stringShort("description"),
|
||||
Type: strings.ToLower(r.stringShort("type")),
|
||||
Query: strings.TrimSpace(r.stringShort("query")),
|
||||
Language: strings.ToLower(strings.TrimSpace(r.stringShort("language"))),
|
||||
ConstValues: r.slcStr("values"),
|
||||
MapValues: r.mapStrStr("values"),
|
||||
Description: r.stringShort(fieldDescription),
|
||||
Type: strings.ToLower(r.stringShort(fieldType)),
|
||||
Query: strings.TrimSpace(r.stringShort(fieldQuery)),
|
||||
Language: strings.ToLower(strings.TrimSpace(r.stringShort(fieldLegendLanguage))),
|
||||
ConstValues: r.slcStr(fieldValues),
|
||||
MapValues: r.mapStrStr(fieldValues),
|
||||
}
|
||||
|
||||
failures := p.parseNestedLabels(r, func(l *label) error {
|
||||
|
@ -536,7 +536,7 @@ func (p *Pkg) graphVariables() error {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Pkg) eachResource(resourceKind kind, fn func(r Resource) []failure) error {
|
||||
func (p *Pkg) eachResource(resourceKind Kind, fn func(r Resource) []failure) error {
|
||||
var parseErr ParseErr
|
||||
for i, r := range p.Spec.Resources {
|
||||
k, err := r.kind()
|
||||
|
@ -556,7 +556,7 @@ func (p *Pkg) eachResource(resourceKind kind, fn func(r Resource) []failure) err
|
|||
})
|
||||
continue
|
||||
}
|
||||
if k != resourceKind {
|
||||
if !k.is(resourceKind) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -593,7 +593,7 @@ func (p *Pkg) parseNestedLabels(r Resource, fn func(lb *label) error) []failure
|
|||
nestedLabels := make(map[string]*label)
|
||||
|
||||
var failures []failure
|
||||
for i, nr := range r.nestedAssociations() {
|
||||
for i, nr := range r.slcResource(fieldAssociations) {
|
||||
fail := p.parseNestedLabel(i, nr, func(l *label) error {
|
||||
if _, ok := nestedLabels[l.Name]; ok {
|
||||
return fmt.Errorf("duplicate nested label: %q", l.Name)
|
||||
|
@ -620,7 +620,7 @@ func (p *Pkg) parseNestedLabel(idx int, nr Resource, fn func(lb *label) error) *
|
|||
assIndex: idx,
|
||||
}
|
||||
}
|
||||
if k != kindLabel {
|
||||
if !k.is(KindLabel) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -657,56 +657,73 @@ func parseChart(r Resource) (chart, []failure) {
|
|||
c := chart{
|
||||
Kind: ck,
|
||||
Name: r.Name(),
|
||||
Prefix: r.stringShort("prefix"),
|
||||
Suffix: r.stringShort("suffix"),
|
||||
Note: r.stringShort("note"),
|
||||
NoteOnEmpty: r.boolShort("noteOnEmpty"),
|
||||
Shade: r.boolShort("shade"),
|
||||
XCol: r.stringShort("xCol"),
|
||||
YCol: r.stringShort("yCol"),
|
||||
XPos: r.intShort("xPos"),
|
||||
YPos: r.intShort("yPos"),
|
||||
Height: r.intShort("height"),
|
||||
Width: r.intShort("width"),
|
||||
Geom: r.stringShort("geom"),
|
||||
Prefix: r.stringShort(fieldPrefix),
|
||||
Suffix: r.stringShort(fieldSuffix),
|
||||
Note: r.stringShort(fieldChartNote),
|
||||
NoteOnEmpty: r.boolShort(fieldChartNoteOnEmpty),
|
||||
Shade: r.boolShort(fieldChartShade),
|
||||
XCol: r.stringShort(fieldChartXCol),
|
||||
YCol: r.stringShort(fieldChartYCol),
|
||||
XPos: r.intShort(fieldChartXPos),
|
||||
YPos: r.intShort(fieldChartYPos),
|
||||
Height: r.intShort(fieldChartHeight),
|
||||
Width: r.intShort(fieldChartWidth),
|
||||
Geom: r.stringShort(fieldChartGeom),
|
||||
}
|
||||
|
||||
if leg, ok := ifaceToResource(r["legend"]); ok {
|
||||
c.Legend.Type = leg.stringShort("type")
|
||||
c.Legend.Orientation = leg.stringShort("orientation")
|
||||
if presLeg, ok := r[fieldChartLegend].(legend); ok {
|
||||
c.Legend = presLeg
|
||||
} else {
|
||||
if leg, ok := ifaceToResource(r[fieldChartLegend]); ok {
|
||||
c.Legend.Type = leg.stringShort(fieldType)
|
||||
c.Legend.Orientation = leg.stringShort(fieldLegendOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
if dp, ok := r.int("decimalPlaces"); ok {
|
||||
if dp, ok := r.int(fieldChartDecimalPlaces); ok {
|
||||
c.EnforceDecimals = true
|
||||
c.DecimalPlaces = dp
|
||||
}
|
||||
|
||||
var failures []failure
|
||||
for _, rq := range r.slcResource("queries") {
|
||||
c.Queries = append(c.Queries, query{
|
||||
Query: strings.TrimSpace(rq.stringShort("query")),
|
||||
})
|
||||
if presentQueries, ok := r[fieldChartQueries].(queries); ok {
|
||||
c.Queries = presentQueries
|
||||
} else {
|
||||
for _, rq := range r.slcResource(fieldChartQueries) {
|
||||
c.Queries = append(c.Queries, query{
|
||||
Query: strings.TrimSpace(rq.stringShort(fieldQuery)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, rc := range r.slcResource("colors") {
|
||||
c.Colors = append(c.Colors, &color{
|
||||
id: influxdb.ID(int(time.Now().UnixNano())).String(),
|
||||
Name: rc.Name(),
|
||||
Type: rc.stringShort("type"),
|
||||
Hex: rc.stringShort("hex"),
|
||||
Value: rc.float64Short("value"),
|
||||
})
|
||||
if presentColors, ok := r[fieldChartColors].(colors); ok {
|
||||
c.Colors = presentColors
|
||||
} else {
|
||||
for _, rc := range r.slcResource(fieldChartColors) {
|
||||
c.Colors = append(c.Colors, &color{
|
||||
// TODO: think we can just axe the stub here
|
||||
id: influxdb.ID(int(time.Now().UnixNano())).String(),
|
||||
Name: rc.Name(),
|
||||
Type: rc.stringShort(fieldType),
|
||||
Hex: rc.stringShort(fieldColorHex),
|
||||
Value: flt64Ptr(rc.float64Short(fieldValue)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, ra := range r.slcResource("axes") {
|
||||
c.Axes = append(c.Axes, axis{
|
||||
Base: ra.stringShort("base"),
|
||||
Label: ra.stringShort("label"),
|
||||
Name: ra.Name(),
|
||||
Prefix: ra.stringShort("prefix"),
|
||||
Scale: ra.stringShort("scale"),
|
||||
Suffix: ra.stringShort("suffix"),
|
||||
})
|
||||
if presAxes, ok := r[fieldChartAxes].(axes); ok {
|
||||
c.Axes = presAxes
|
||||
} else {
|
||||
for _, ra := range r.slcResource(fieldChartAxes) {
|
||||
c.Axes = append(c.Axes, axis{
|
||||
Base: ra.stringShort(fieldAxisBase),
|
||||
Label: ra.stringShort(fieldAxisLabel),
|
||||
Name: ra.Name(),
|
||||
Prefix: ra.stringShort(fieldPrefix),
|
||||
Scale: ra.stringShort(fieldAxisScale),
|
||||
Suffix: ra.stringShort(fieldSuffix),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if fails := c.validProperties(); len(fails) > 0 {
|
||||
|
@ -724,25 +741,23 @@ func parseChart(r Resource) (chart, []failure) {
|
|||
// available kinds that are supported.
|
||||
type Resource map[string]interface{}
|
||||
|
||||
// Name returns the name of the resource.
|
||||
func (r Resource) Name() string {
|
||||
return strings.TrimSpace(r.stringShort("name"))
|
||||
return strings.TrimSpace(r.stringShort(fieldName))
|
||||
}
|
||||
|
||||
func (r Resource) kind() (kind, error) {
|
||||
resKind, ok := r.string("kind")
|
||||
func (r Resource) kind() (Kind, error) {
|
||||
resKind, ok := r.string(fieldKind)
|
||||
if !ok {
|
||||
return kindUnknown, errors.New("no kind provided")
|
||||
return KindUnknown, errors.New("no kind provided")
|
||||
}
|
||||
|
||||
newKind := kind(strings.TrimSpace(strings.ToLower(resKind)))
|
||||
if newKind == kindUnknown {
|
||||
return kindUnknown, errors.New("invalid kind")
|
||||
}
|
||||
if !kinds[newKind] {
|
||||
return newKind, errors.New("unsupported kind provided")
|
||||
k := newKind(resKind)
|
||||
if err := k.OK(); err != nil {
|
||||
return k, err
|
||||
}
|
||||
|
||||
return newKind, nil
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (r Resource) chartKind() (chartKind, error) {
|
||||
|
@ -754,29 +769,6 @@ func (r Resource) chartKind() (chartKind, error) {
|
|||
return chartKind, nil
|
||||
}
|
||||
|
||||
func (r Resource) nestedAssociations() []Resource {
|
||||
v, ok := r["associations"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
ifaces, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var resources []Resource
|
||||
for _, iface := range ifaces {
|
||||
newRes, ok := ifaceToResource(iface)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
resources = append(resources, newRes)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (r Resource) bool(key string) (bool, bool) {
|
||||
b, ok := r[key].(bool)
|
||||
return b, ok
|
||||
|
@ -843,6 +835,10 @@ func (r Resource) slcResource(key string) []Resource {
|
|||
return nil
|
||||
}
|
||||
|
||||
if resources, ok := v.([]Resource); ok {
|
||||
return resources
|
||||
}
|
||||
|
||||
iFaceSlc, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
|
@ -866,6 +862,10 @@ func (r Resource) slcStr(key string) []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
if strSlc, ok := v.([]string); ok {
|
||||
return strSlc
|
||||
}
|
||||
|
||||
iFaceSlc, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
|
@ -884,7 +884,16 @@ func (r Resource) slcStr(key string) []string {
|
|||
}
|
||||
|
||||
func (r Resource) mapStrStr(key string) map[string]string {
|
||||
res, ok := ifaceToResource(r[key])
|
||||
v, ok := r[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m, ok := v.(map[string]string); ok {
|
||||
return m
|
||||
}
|
||||
|
||||
res, ok := ifaceToResource(v)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
@ -905,8 +914,7 @@ func ifaceToResource(i interface{}) (Resource, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
res, ok := i.(Resource)
|
||||
if ok {
|
||||
if res, ok := i.(Resource); ok {
|
||||
return res, true
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ spec:
|
|||
name: buck_1
|
||||
retention_period: 1h
|
||||
`,
|
||||
valFields: []string{"pkgName"},
|
||||
valFields: []string{"meta.pkgName"},
|
||||
},
|
||||
{
|
||||
name: "missing pkgVersion",
|
||||
|
@ -88,7 +88,7 @@ spec:
|
|||
name: buck_1
|
||||
retention_period: 1h
|
||||
`,
|
||||
valFields: []string{"pkgVersion"},
|
||||
valFields: []string{"meta.pkgVersion"},
|
||||
},
|
||||
{
|
||||
name: "missing multiple",
|
||||
|
@ -98,12 +98,12 @@ spec:
|
|||
name: buck_1
|
||||
retention_period: 1h
|
||||
`,
|
||||
valFields: []string{"apiVersion", "kind", "pkgVersion", "pkgName"},
|
||||
valFields: []string{"apiVersion", "kind", "meta.pkgVersion", "meta.pkgName"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindPackage, tt)
|
||||
testPkgErrors(t, KindPackage, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -203,7 +203,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindBucket, tt)
|
||||
testPkgErrors(t, KindBucket, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -281,7 +281,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindLabel, tt)
|
||||
testPkgErrors(t, KindLabel, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -435,7 +435,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindBucket, tt)
|
||||
testPkgErrors(t, KindBucket, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -512,33 +512,6 @@ spec:
|
|||
colors:
|
||||
- name: laser
|
||||
type: text
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no colors provided",
|
||||
validationErrs: 2,
|
||||
valFields: []string{"charts[0].colors", "charts[0].colors"},
|
||||
pkgStr: `apiVersion: 0.1.0
|
||||
kind: Package
|
||||
meta:
|
||||
pkgName: pkg_name
|
||||
pkgVersion: 1
|
||||
description: pack description
|
||||
spec:
|
||||
resources:
|
||||
- kind: Dashboard
|
||||
name: dash_1
|
||||
description: desc1
|
||||
charts:
|
||||
- kind: Single_Stat
|
||||
name: single stat
|
||||
suffix: days
|
||||
width: 6
|
||||
height: 3
|
||||
shade: true
|
||||
queries:
|
||||
- query: >
|
||||
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "system") |> filter(fn: (r) => r._field == "uptime") |> last() |> map(fn: (r) => ({r with _value: r._value / 86400})) |> yield(name: "last")
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -662,7 +635,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindDashboard, tt)
|
||||
testPkgErrors(t, KindDashboard, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -766,40 +739,6 @@ spec:
|
|||
label: y_label
|
||||
base: 10
|
||||
scale: linear
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no colors provided",
|
||||
validationErrs: 3,
|
||||
valFields: []string{"charts[0].colors", "charts[0].colors", "charts[0].colors"},
|
||||
pkgStr: `apiVersion: 0.1.0
|
||||
kind: Package
|
||||
meta:
|
||||
pkgName: pkg_name
|
||||
pkgVersion: 1
|
||||
description: pack description
|
||||
spec:
|
||||
resources:
|
||||
- kind: Dashboard
|
||||
name: dash_1
|
||||
description: desc1
|
||||
charts:
|
||||
- kind: Single_Stat_Plus_Line
|
||||
name: single stat plus line
|
||||
width: 6
|
||||
height: 3
|
||||
queries:
|
||||
- query: >
|
||||
from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "system") |> filter(fn: (r) => r._field == "uptime") |> last() |> map(fn: (r) => ({r with _value: r._value / 86400})) |> yield(name: "last")
|
||||
axes:
|
||||
- name : "x"
|
||||
label: x_label
|
||||
base: 10
|
||||
scale: linear
|
||||
- name: "y"
|
||||
label: y_label
|
||||
base: 10
|
||||
scale: linear
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -1068,7 +1007,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindDashboard, tt)
|
||||
testPkgErrors(t, KindDashboard, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1268,7 +1207,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindDashboard, tt)
|
||||
testPkgErrors(t, KindDashboard, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1512,7 +1451,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindDashboard, tt)
|
||||
testPkgErrors(t, KindDashboard, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1634,7 +1573,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindDashboard, tt)
|
||||
testPkgErrors(t, KindDashboard, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1829,7 +1768,7 @@ spec:
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testPkgErrors(t, kindVariable, tt)
|
||||
testPkgErrors(t, KindVariable, tt)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1889,7 +1828,7 @@ type testPkgResourceError struct {
|
|||
|
||||
// defaults to yaml encoding if encoding not provided
|
||||
// defaults num resources to 1 if resource errs not provided.
|
||||
func testPkgErrors(t *testing.T, k kind, tt testPkgResourceError) {
|
||||
func testPkgErrors(t *testing.T, k Kind, tt testPkgResourceError) {
|
||||
t.Helper()
|
||||
encoding := EncodingYAML
|
||||
if tt.encoding != EncodingUnknown {
|
||||
|
|
114
pkger/service.go
114
pkger/service.go
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -99,38 +100,119 @@ func NewService(opts ...ServiceSetterFn) *Service {
|
|||
}
|
||||
|
||||
// CreatePkgSetFn is a functional input for setting the pkg fields.
|
||||
type CreatePkgSetFn func(ctx context.Context, pkg *Pkg) error
|
||||
type CreatePkgSetFn func(opt *createOpt) error
|
||||
|
||||
type createOpt struct {
|
||||
metadata Metadata
|
||||
resources []ResourceToClone
|
||||
}
|
||||
|
||||
// WithMetadata sets the metadata on the pkg in a CreatePkg call.
|
||||
func WithMetadata(meta Metadata) CreatePkgSetFn {
|
||||
return func(ctx context.Context, pkg *Pkg) error {
|
||||
pkg.Metadata = meta
|
||||
return func(opt *createOpt) error {
|
||||
opt.metadata = meta
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithResourceClones allows the create method to clone existing resources.
|
||||
func WithResourceClones(resources ...ResourceToClone) CreatePkgSetFn {
|
||||
return func(opt *createOpt) error {
|
||||
for _, r := range resources {
|
||||
if err := r.OK(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
opt.resources = append(opt.resources, resources...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePkg will produce a pkg from the parameters provided.
|
||||
func (s *Service) CreatePkg(ctx context.Context, setters ...CreatePkgSetFn) (*Pkg, error) {
|
||||
pkg := &Pkg{
|
||||
APIVersion: APIVersion,
|
||||
Kind: kindPackage.String(),
|
||||
Spec: struct {
|
||||
Resources []Resource `yaml:"resources" json:"resources"`
|
||||
}{
|
||||
Resources: []Resource{},
|
||||
},
|
||||
}
|
||||
|
||||
opt := new(createOpt)
|
||||
for _, setter := range setters {
|
||||
err := setter(ctx, pkg)
|
||||
if err != nil {
|
||||
if err := setter(opt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pkg := &Pkg{
|
||||
APIVersion: APIVersion,
|
||||
Kind: KindPackage.String(),
|
||||
Metadata: opt.metadata,
|
||||
Spec: struct {
|
||||
Resources []Resource `yaml:"resources" json:"resources"`
|
||||
}{
|
||||
Resources: make([]Resource, 0, len(opt.resources)),
|
||||
},
|
||||
}
|
||||
if pkg.Metadata.Name == "" {
|
||||
// sudo randomness, this is not an attempt at making charts unique
|
||||
// that is a problem for the consumer.
|
||||
pkg.Metadata.Name = fmt.Sprintf("new_%7d", rand.Int())
|
||||
}
|
||||
if pkg.Metadata.Version == "" {
|
||||
pkg.Metadata.Version = "v1"
|
||||
}
|
||||
|
||||
for _, r := range opt.resources {
|
||||
newResource, err := s.resourceCloneToResource(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.Spec.Resources = append(pkg.Spec.Resources, newResource)
|
||||
}
|
||||
|
||||
if err := pkg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
func (s *Service) resourceCloneToResource(ctx context.Context, r ResourceToClone) (Resource, error) {
|
||||
switch {
|
||||
case r.Kind.is(KindBucket):
|
||||
bkt, err := s.bucketSVC.FindBucketByID(ctx, r.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bucketToResource(*bkt, r.Name), nil
|
||||
case r.Kind.is(KindDashboard):
|
||||
dash, err := s.dashSVC.FindDashboardByID(ctx, r.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cellViews []cellView
|
||||
for _, cell := range dash.Cells {
|
||||
v, err := s.dashSVC.GetDashboardCellView(ctx, r.ID, cell.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cellViews = append(cellViews, cellView{
|
||||
c: *cell,
|
||||
v: *v,
|
||||
})
|
||||
}
|
||||
return dashboardToResource(*dash, cellViews, r.Name), nil
|
||||
case r.Kind.is(KindLabel):
|
||||
l, err := s.labelSVC.FindLabelByID(ctx, r.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return labelToResource(*l, r.Name), nil
|
||||
case r.Kind.is(KindVariable):
|
||||
v, err := s.varSVC.FindVariableByID(ctx, r.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return variableToResource(*v, r.Name), nil
|
||||
default:
|
||||
return nil, errors.New("unsupported kind provided: " + string(r.Kind))
|
||||
}
|
||||
}
|
||||
|
||||
// DryRun provides a dry run of the pkg application. The pkg will be marked verified
|
||||
// for later calls to Apply. This func will be run on an Apply if it has not been run
|
||||
// already.
|
||||
|
@ -657,6 +739,8 @@ func convertChartsToCells(ch []chart) ([]*influxdb.Cell, map[*influxdb.Cell]int)
|
|||
for i, c := range ch {
|
||||
icell := &influxdb.Cell{
|
||||
CellProperty: influxdb.CellProperty{
|
||||
X: int32(c.XPos),
|
||||
Y: int32(c.YPos),
|
||||
H: int32(c.Height),
|
||||
W: int32(c.Width),
|
||||
},
|
||||
|
|
|
@ -659,19 +659,453 @@ func TestService(t *testing.T) {
|
|||
|
||||
t.Run("CreatePkg", func(t *testing.T) {
|
||||
t.Run("with metadata sets the new pkgs metadata", func(t *testing.T) {
|
||||
svc := new(Service)
|
||||
bktSVC := mock.NewBucketService()
|
||||
bktSVC.FindBucketByIDFn = func(_ context.Context, id influxdb.ID) (*influxdb.Bucket, error) {
|
||||
return &influxdb.Bucket{ID: 1, Name: "name"}, nil
|
||||
}
|
||||
svc := NewService(WithBucketSVC(bktSVC))
|
||||
|
||||
expectedMeta := Metadata{
|
||||
Description: "desc",
|
||||
Name: "name",
|
||||
Version: "v1",
|
||||
}
|
||||
pkg, err := svc.CreatePkg(context.TODO(), WithMetadata(expectedMeta))
|
||||
pkg, err := svc.CreatePkg(context.TODO(),
|
||||
WithMetadata(expectedMeta),
|
||||
WithResourceClones(ResourceToClone{ // sets stub resource to pass validation
|
||||
Kind: KindBucket,
|
||||
ID: 1,
|
||||
Name: "name",
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, APIVersion, pkg.APIVersion)
|
||||
assert.Equal(t, expectedMeta, pkg.Metadata)
|
||||
assert.NotNil(t, pkg.Spec.Resources)
|
||||
})
|
||||
|
||||
t.Run("with existing resources", func(t *testing.T) {
|
||||
t.Run("bucket", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
newName string
|
||||
}{
|
||||
{
|
||||
name: "without new name",
|
||||
},
|
||||
{
|
||||
name: "with new name",
|
||||
newName: "new name",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
fn := func(t *testing.T) {
|
||||
expected := &influxdb.Bucket{
|
||||
ID: 3,
|
||||
Name: "bucket name",
|
||||
Description: "desc",
|
||||
RetentionPeriod: time.Hour,
|
||||
}
|
||||
|
||||
bktSVC := mock.NewBucketService()
|
||||
bktSVC.FindBucketByIDFn = func(_ context.Context, id influxdb.ID) (*influxdb.Bucket, error) {
|
||||
if id != expected.ID {
|
||||
return nil, errors.New("uh ohhh, wrong id here: " + id.String())
|
||||
}
|
||||
return expected, nil
|
||||
}
|
||||
|
||||
svc := NewService(WithBucketSVC(bktSVC))
|
||||
|
||||
resToClone := ResourceToClone{
|
||||
Kind: KindBucket,
|
||||
ID: expected.ID,
|
||||
Name: tt.newName,
|
||||
}
|
||||
pkg, err := svc.CreatePkg(context.TODO(), WithResourceClones(resToClone))
|
||||
require.NoError(t, err)
|
||||
|
||||
bkts := pkg.Summary().Buckets
|
||||
require.Len(t, bkts, 1)
|
||||
|
||||
actual := bkts[0]
|
||||
expectedName := expected.Name
|
||||
if tt.newName != "" {
|
||||
expectedName = tt.newName
|
||||
}
|
||||
assert.Equal(t, expectedName, actual.Name)
|
||||
assert.Equal(t, expected.Description, actual.Description)
|
||||
assert.Equal(t, expected.RetentionPeriod, actual.RetentionPeriod)
|
||||
}
|
||||
t.Run(tt.name, fn)
|
||||
}
|
||||
})
|
||||
|
||||
newQuery := func() influxdb.DashboardQuery {
|
||||
q := influxdb.DashboardQuery{
|
||||
Text: "from(v.bucket) |> count()",
|
||||
EditMode: "advanced",
|
||||
}
|
||||
// TODO: remove this when issue that forced the builder tag to be here to render in UI.
|
||||
q.BuilderConfig.Tags = append(q.BuilderConfig.Tags, influxdb.NewBuilderTag("_measurement"))
|
||||
return q
|
||||
}
|
||||
|
||||
newAxes := func() map[string]influxdb.Axis {
|
||||
return map[string]influxdb.Axis{
|
||||
"x": {
|
||||
Bounds: []string{},
|
||||
Label: "labx",
|
||||
Prefix: "pre",
|
||||
Suffix: "suf",
|
||||
Base: "base",
|
||||
Scale: "linear",
|
||||
},
|
||||
"y": {
|
||||
Bounds: []string{},
|
||||
Label: "laby",
|
||||
Prefix: "pre",
|
||||
Suffix: "suf",
|
||||
Base: "base",
|
||||
Scale: "linear",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
newColors := func(types ...string) []influxdb.ViewColor {
|
||||
var out []influxdb.ViewColor
|
||||
for _, t := range types {
|
||||
out = append(out, influxdb.ViewColor{
|
||||
Type: t,
|
||||
Hex: time.Now().Format(time.RFC3339),
|
||||
Name: time.Now().Format(time.RFC3339),
|
||||
Value: float64(time.Now().Unix()),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
t.Run("dashboard", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
newName string
|
||||
expectedView influxdb.View
|
||||
}{
|
||||
{
|
||||
name: "without new name single stat",
|
||||
expectedView: influxdb.View{
|
||||
ViewContents: influxdb.ViewContents{
|
||||
Name: "view name",
|
||||
},
|
||||
Properties: influxdb.SingleStatViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStat,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
|
||||
Note: "a note",
|
||||
Queries: []influxdb.DashboardQuery{newQuery()},
|
||||
Prefix: "pre",
|
||||
ShowNoteWhenEmpty: true,
|
||||
Suffix: "suf",
|
||||
ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with new name single stat",
|
||||
newName: "new name",
|
||||
expectedView: influxdb.View{
|
||||
ViewContents: influxdb.ViewContents{
|
||||
Name: "view name",
|
||||
},
|
||||
Properties: influxdb.SingleStatViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStat,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
|
||||
Note: "a note",
|
||||
Queries: []influxdb.DashboardQuery{newQuery()},
|
||||
Prefix: "pre",
|
||||
ShowNoteWhenEmpty: true,
|
||||
Suffix: "suf",
|
||||
ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "guage",
|
||||
newName: "new name",
|
||||
expectedView: influxdb.View{
|
||||
ViewContents: influxdb.ViewContents{
|
||||
Name: "view name",
|
||||
},
|
||||
Properties: influxdb.GaugeViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeGauge,
|
||||
DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
|
||||
Note: "a note",
|
||||
Prefix: "pre",
|
||||
Suffix: "suf",
|
||||
Queries: []influxdb.DashboardQuery{newQuery()},
|
||||
ShowNoteWhenEmpty: true,
|
||||
ViewColors: newColors("min", "max", "threshold"),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "single stat plus line",
|
||||
newName: "new name",
|
||||
expectedView: influxdb.View{
|
||||
ViewContents: influxdb.ViewContents{
|
||||
Name: "view name",
|
||||
},
|
||||
Properties: influxdb.LinePlusSingleStatProperties{
|
||||
Type: influxdb.ViewPropertyTypeSingleStatPlusLine,
|
||||
Axes: newAxes(),
|
||||
DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
|
||||
Legend: influxdb.Legend{Type: "type", Orientation: "horizontal"},
|
||||
Note: "a note",
|
||||
Prefix: "pre",
|
||||
Suffix: "suf",
|
||||
Queries: []influxdb.DashboardQuery{newQuery()},
|
||||
ShadeBelow: true,
|
||||
ShowNoteWhenEmpty: true,
|
||||
ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}},
|
||||
XColumn: "x",
|
||||
YColumn: "y",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "xy",
|
||||
newName: "new name",
|
||||
expectedView: influxdb.View{
|
||||
ViewContents: influxdb.ViewContents{
|
||||
Name: "view name",
|
||||
},
|
||||
Properties: influxdb.XYViewProperties{
|
||||
Type: influxdb.ViewPropertyTypeXY,
|
||||
Axes: newAxes(),
|
||||
Geom: "step",
|
||||
Legend: influxdb.Legend{Type: "type", Orientation: "horizontal"},
|
||||
Note: "a note",
|
||||
Queries: []influxdb.DashboardQuery{newQuery()},
|
||||
ShadeBelow: true,
|
||||
ShowNoteWhenEmpty: true,
|
||||
ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}},
|
||||
XColumn: "x",
|
||||
YColumn: "y",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
fn := func(t *testing.T) {
|
||||
expectedCell := &influxdb.Cell{
|
||||
ID: 5,
|
||||
CellProperty: influxdb.CellProperty{X: 1, Y: 2, W: 3, H: 4},
|
||||
}
|
||||
expected := &influxdb.Dashboard{
|
||||
ID: 3,
|
||||
Name: "bucket name",
|
||||
Description: "desc",
|
||||
Cells: []*influxdb.Cell{expectedCell},
|
||||
}
|
||||
|
||||
dashSVC := mock.NewDashboardService()
|
||||
dashSVC.FindDashboardByIDF = func(_ context.Context, id influxdb.ID) (*influxdb.Dashboard, error) {
|
||||
if id != expected.ID {
|
||||
return nil, errors.New("uh ohhh, wrong id here: " + id.String())
|
||||
}
|
||||
return expected, nil
|
||||
}
|
||||
dashSVC.GetDashboardCellViewF = func(_ context.Context, id influxdb.ID, cID influxdb.ID) (*influxdb.View, error) {
|
||||
if id == expected.ID && cID == expectedCell.ID {
|
||||
return &tt.expectedView, nil
|
||||
}
|
||||
return nil, errors.New("wrongo ids")
|
||||
}
|
||||
|
||||
svc := NewService(WithDashboardSVC(dashSVC))
|
||||
|
||||
resToClone := ResourceToClone{
|
||||
Kind: KindDashboard,
|
||||
ID: expected.ID,
|
||||
Name: tt.newName,
|
||||
}
|
||||
pkg, err := svc.CreatePkg(context.TODO(), WithResourceClones(resToClone))
|
||||
require.NoError(t, err)
|
||||
|
||||
dashs := pkg.Summary().Dashboards
|
||||
require.Len(t, dashs, 1)
|
||||
|
||||
actual := dashs[0]
|
||||
expectedName := expected.Name
|
||||
if tt.newName != "" {
|
||||
expectedName = tt.newName
|
||||
}
|
||||
assert.Equal(t, expectedName, actual.Name)
|
||||
assert.Equal(t, expected.Description, actual.Description)
|
||||
|
||||
require.Len(t, actual.Charts, 1)
|
||||
ch := actual.Charts[0]
|
||||
assert.Equal(t, int(expectedCell.X), ch.XPosition)
|
||||
assert.Equal(t, int(expectedCell.Y), ch.YPosition)
|
||||
assert.Equal(t, int(expectedCell.H), ch.Height)
|
||||
assert.Equal(t, int(expectedCell.W), ch.Width)
|
||||
assert.Equal(t, tt.expectedView.Properties, ch.Properties)
|
||||
}
|
||||
t.Run(tt.name, fn)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("label", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
newName string
|
||||
}{
|
||||
{
|
||||
name: "without new name",
|
||||
},
|
||||
{
|
||||
name: "with new name",
|
||||
newName: "new name",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
fn := func(t *testing.T) {
|
||||
expectedLabel := &influxdb.Label{
|
||||
ID: 3,
|
||||
Name: "bucket name",
|
||||
Properties: map[string]string{
|
||||
"description": "desc",
|
||||
"color": "red",
|
||||
},
|
||||
}
|
||||
|
||||
labelSVC := mock.NewLabelService()
|
||||
labelSVC.FindLabelByIDFn = func(_ context.Context, id influxdb.ID) (*influxdb.Label, error) {
|
||||
if id != expectedLabel.ID {
|
||||
return nil, errors.New("uh ohhh, wrong id here: " + id.String())
|
||||
}
|
||||
return expectedLabel, nil
|
||||
}
|
||||
|
||||
svc := NewService(WithLabelSVC(labelSVC))
|
||||
|
||||
resToClone := ResourceToClone{
|
||||
Kind: KindLabel,
|
||||
ID: expectedLabel.ID,
|
||||
Name: tt.newName,
|
||||
}
|
||||
pkg, err := svc.CreatePkg(context.TODO(), WithResourceClones(resToClone))
|
||||
require.NoError(t, err)
|
||||
|
||||
newLabels := pkg.Summary().Labels
|
||||
require.Len(t, newLabels, 1)
|
||||
|
||||
actual := newLabels[0]
|
||||
expectedName := expectedLabel.Name
|
||||
if tt.newName != "" {
|
||||
expectedName = tt.newName
|
||||
}
|
||||
assert.Equal(t, expectedName, actual.Name)
|
||||
assert.Equal(t, expectedLabel.Properties, actual.Properties)
|
||||
}
|
||||
t.Run(tt.name, fn)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("variable", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
newName string
|
||||
expectedVar influxdb.Variable
|
||||
}{
|
||||
{
|
||||
name: "without new name",
|
||||
expectedVar: influxdb.Variable{
|
||||
ID: 1,
|
||||
Name: "old name",
|
||||
Description: "desc",
|
||||
Arguments: &influxdb.VariableArguments{
|
||||
Type: "constant",
|
||||
Values: influxdb.VariableConstantValues{"val"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with new name",
|
||||
newName: "new name",
|
||||
expectedVar: influxdb.Variable{
|
||||
ID: 1,
|
||||
Name: "old name",
|
||||
Arguments: &influxdb.VariableArguments{
|
||||
Type: "constant",
|
||||
Values: influxdb.VariableConstantValues{"val"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with map arg",
|
||||
expectedVar: influxdb.Variable{
|
||||
ID: 1,
|
||||
Name: "old name",
|
||||
Arguments: &influxdb.VariableArguments{
|
||||
Type: "map",
|
||||
Values: influxdb.VariableMapValues{"k": "v"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with query arg",
|
||||
expectedVar: influxdb.Variable{
|
||||
ID: 1,
|
||||
Name: "old name",
|
||||
Arguments: &influxdb.VariableArguments{
|
||||
Type: "query",
|
||||
Values: influxdb.VariableQueryValues{
|
||||
Query: "query",
|
||||
Language: "flux",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
fn := func(t *testing.T) {
|
||||
varSVC := mock.NewVariableService()
|
||||
varSVC.FindVariableByIDF = func(_ context.Context, id influxdb.ID) (*influxdb.Variable, error) {
|
||||
if id != tt.expectedVar.ID {
|
||||
return nil, errors.New("uh ohhh, wrong id here: " + id.String())
|
||||
}
|
||||
return &tt.expectedVar, nil
|
||||
}
|
||||
|
||||
svc := NewService(WithVariableSVC(varSVC))
|
||||
|
||||
resToClone := ResourceToClone{
|
||||
Kind: KindVariable,
|
||||
ID: tt.expectedVar.ID,
|
||||
Name: tt.newName,
|
||||
}
|
||||
pkg, err := svc.CreatePkg(context.TODO(), WithResourceClones(resToClone))
|
||||
require.NoError(t, err)
|
||||
|
||||
newVars := pkg.Summary().Variables
|
||||
require.Len(t, newVars, 1)
|
||||
|
||||
actual := newVars[0]
|
||||
expectedName := tt.expectedVar.Name
|
||||
if tt.newName != "" {
|
||||
expectedName = tt.newName
|
||||
}
|
||||
assert.Equal(t, expectedName, actual.Name)
|
||||
assert.Equal(t, tt.expectedVar.Description, actual.Description)
|
||||
assert.Equal(t, tt.expectedVar.Arguments, actual.Arguments)
|
||||
}
|
||||
t.Run(tt.name, fn)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue