Skip to content

Serialization fixes #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 29 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3182640
Add fields to FoundDashboard
krya-kryak May 6, 2020
bec4589
Convert Target.RawQuery to BoolString
krya-kryak May 12, 2020
326d7a2
Change CommonPanel.GridPos data type
krya-kryak May 12, 2020
3c799ed
change GraphPanel.Pointradius type
krya-kryak May 12, 2020
e6b5322
Update GraphPanel types
krya-kryak May 12, 2020
1b2fe60
Updage ColumnStyle type
krya-kryak May 12, 2020
3830fce
update Panel.Height
krya-kryak May 13, 2020
f0c8a9c
Enable RowPanel deserialization
krya-kryak May 13, 2020
1d97af7
remove debug print
krya-kryak May 13, 2020
3de16fc
Fix FloatOrString serialization
krya-kryak May 13, 2020
b8279dd
Fix RowPanel serialization
krya-kryak May 13, 2020
a89a563
stuff
krya-kryak May 13, 2020
4d48e10
Make some Panel items pointer types
krya-kryak May 14, 2020
8ee2397
Add Panel.Collapsed property
krya-kryak May 14, 2020
d09ec3e
Refactor
krya-kryak May 15, 2020
e11a4d2
Fix custom Panel JSON marshalling
krya-kryak May 18, 2020
c19a064
Update
krya-kryak May 25, 2020
b613a16
Add message to SetDashboard
krya-kryak May 25, 2020
056bfc5
Add fields
krya-kryak May 25, 2020
f72bb85
Remove pointer
krya-kryak May 25, 2020
e6e7b3d
Remove garbled field
krya-kryak May 25, 2020
5f4feea
Allow Description for non-graph panels
krya-kryak May 25, 2020
bf568a7
Update fields
krya-kryak May 25, 2020
e5084f6
Add dashes to series overrides
krya-kryak May 25, 2020
5055129
Add link-related fields to ColumnStyle
krya-kryak Jun 5, 2020
d043c8d
Make some Board fields optional yet present
krya-kryak Jun 5, 2020
55f1602
Add RawQuery to annotations
krya-kryak Jun 5, 2020
53cff95
Fix NewBoard
krya-kryak Jun 5, 2020
59c9dbd
Add AutoMin to TemplateVars
krya-kryak Aug 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 84 additions & 83 deletions board.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ package sdk
*/

import (
"bytes"
"encoding/json"
"strings"

"github.com/gosimple/slug"
Expand All @@ -41,62 +39,77 @@ const (
type (
// Board represents Grafana dashboard.
Board struct {
ID uint `json:"id,omitempty"`
UID string `json:"uid,omitempty"`
Slug string `json:"slug"`
Title string `json:"title"`
OriginalTitle string `json:"originalTitle"`
Tags []string `json:"tags"`
Style string `json:"style"`
Timezone string `json:"timezone"`
Editable bool `json:"editable"`
HideControls bool `json:"hideControls" graf:"hide-controls"`
SharedCrosshair bool `json:"sharedCrosshair" graf:"shared-crosshair"`
Panels []*Panel `json:"panels"`
Rows []*Row `json:"rows"`
Templating Templating `json:"templating"`
Annotations struct {
List []Annotation `json:"list"`
// Default fields as described by schema v17.
// See https://grafana.com/docs/grafana/latest/reference/dashboard/
ID uint `json:"id,omitempty"`
UID string `json:"uid,omitempty"`
Title string `json:"title"`
Tags []string `json:"tags"`
Style string `json:"style"`
Timezone string `json:"timezone"`
Editable bool `json:"editable"`
HideControls *bool `json:"hideControls,omitempty" graf:"hide-controls"`
GraphTooltip *int `json:"graphTooltip,omitempty"`
Panels []*Panel `json:"panels"`
Time Time `json:"time"`
Timepicker Timepicker `json:"timepicker"`
Templating Templating `json:"templating"`
Annotations struct {
List []*Annotation `json:"list"`
} `json:"annotations"`
Refresh *BoolString `json:"refresh,omitempty"`
Refresh *BoolString `json:"refresh"`
SchemaVersion uint `json:"schemaVersion"`
Version uint `json:"version"`
Links []link `json:"links"`
Time Time `json:"time"`
Timepicker Timepicker `json:"timepicker"`
GraphTooltip int `json:"graphTooltip,omitempty"`
Links []*Link `json:"links"`

// Optional fields.
Description *string `json:"description,omitempty"`
Slug *string `json:"slug,omitempty""`
OriginalTitle *string `json:"originalTitle,omitempty"`
SharedCrosshair bool `json:"sharedCrosshair,omitempty" graf:"shared-crosshair"`
Rows []*Row `json:"rows,omitempty"`
}
Time struct {
From string `json:"from"`
To string `json:"to"`
}
Timepicker struct {
Now *bool `json:"now,omitempty"`
// Default fields.
RefreshIntervals []string `json:"refresh_intervals"`
TimeOptions []string `json:"time_options"`
// Optional fields.
Now *bool `json:"now,omitempty"`
}
Templating struct {
List []TemplateVar `json:"list"`
List []*TemplateVar `json:"list"`
}
TemplateVar struct {
Name string `json:"name"`
Type string `json:"type"`
Auto bool `json:"auto,omitempty"`
AutoCount *int `json:"auto_count,omitempty"`
Datasource *string `json:"datasource"`
Refresh BoolInt `json:"refresh"`
Options []Option `json:"options"`
IncludeAll bool `json:"includeAll"`
AllFormat string `json:"allFormat"`
AllValue string `json:"allValue"`
Multi bool `json:"multi"`
MultiFormat string `json:"multiFormat"`
Query string `json:"query"`
Regex string `json:"regex"`
Current Current `json:"current"`
Label string `json:"label"`
Hide uint8 `json:"hide"`
Sort int `json:"sort"`
AllFormat string `json:"allFormat,omitempty"`
AllValue string `json:"allValue,omitempty"`
Auto bool `json:"auto,omitempty"`
AutoCount *int `json:"auto_count,omitempty"`
AutoMin string `json:"auto_min,omitempty"`
Current Current `json:"current"`
Datasource *string `json:"datasource"`
Definition string `json:"definition,omitempty"`
Hide uint8 `json:"hide"`
Index int `json:"index,omitempty"`
IncludeAll bool `json:"includeAll"`
Label string `json:"label"`
Multi bool `json:"multi"`
MultiFormat string `json:"multiFormat,omitempty"`
Name string `json:"name"`
Options []*Option `json:"options"`
Query string `json:"query"`
Refresh BoolInt `json:"refresh"`
Regex string `json:"regex"`
SkipUrlSync bool `json:"skipUrlSync"`
Sort int `json:"sort,omitempty"`
TagValuesQuery string `json:"tagValuesQuery,omitempty"`
Tags []string `json:"tags,omitempty"`
TagsQuery string `json:"tagsQuery,omitempty"`
Type string `json:"type"`
UseTags bool `json:"useTags,omitempty"`
}
// for templateVar
Option struct {
Expand All @@ -106,30 +119,34 @@ type (
}
// for templateVar
Current struct {
Tags []*string `json:"tags,omitempty"`
Text string `json:"text"`
Value interface{} `json:"value"` // TODO select more precise type
Selected bool `json:"selected,omitempty"`
Tags []string `json:"tags,omitempty"`
Text string `json:"text"`
Value interface{} `json:"value"` // TODO select more precise type
}
Annotation struct {
Name string `json:"name"`
BuiltIn int `json:"builtIn,omitempty"`
Datasource *string `json:"datasource"`
ShowLine bool `json:"showLine"`
IconColor string `json:"iconColor"`
LineColor string `json:"lineColor"`
IconSize uint `json:"iconSize"`
Enable bool `json:"enable"`
Query string `json:"query"`
TextField string `json:"textField"`
TagsField string `json:"tagsField"`
Tags []string `json:"tags"`
Type string `json:"type"`
Hide bool `json:"hide,omitempty"`
IconColor string `json:"iconColor"`
IconSize uint `json:"iconSize,omitempty"`
Limit int `json:"limit,omitempty"`
LineColor string `json:"lineColor,omitempty"`
Name string `json:"name"`
Query string `json:"query,omitempty"`
RawQuery string `json:"rawQuery,omitempty"`
ShowIn int `json:"showIn"`
ShowLine bool `json:"showLine,omitempty"`
Tags []string `json:"tags,omitempty"`
TagsField string `json:"tagsField,omitempty"`
TextField string `json:"textField,omitempty"`
Type string `json:"type,omitempty"`
}
)

// link represents link to another dashboard or external weblink
type link struct {
Title string `json:"title"`
Type string `json:"type"`
// Link represents Link to another dashboard or external weblink
type Link struct {
AsDropdown *bool `json:"asDropdown,omitempty"`
DashURI *string `json:"dashUri,omitempty"`
Dashboard *string `json:"dashboard,omitempty"`
Expand All @@ -139,29 +156,12 @@ type link struct {
Params *string `json:"params,omitempty"`
Tags []string `json:"tags,omitempty"`
TargetBlank *bool `json:"targetBlank,omitempty"`
Title string `json:"title,omitempty"`
Tooltip *string `json:"tooltip,omitempty"`
Type string `json:"type"`
URL *string `json:"url,omitempty"`
}

// Height of rows maybe passed as number (ex 200) or
// as string (ex "200px") or empty string
type Height string

func (h *Height) UnmarshalJSON(raw []byte) error {
if raw == nil || bytes.Equal(raw, []byte(`"null"`)) {
return nil
}
if raw[0] != '"' {
tmp := []byte{'"'}
raw = append(tmp, raw...)
raw = append(raw, byte('"'))
}
var tmp string
err := json.Unmarshal(raw, &tmp)
*h = Height(tmp)
return err
}

func NewBoard(title string) *Board {
boardID++
return &Board{
Expand All @@ -170,7 +170,7 @@ func NewBoard(title string) *Board {
Style: "dark",
Timezone: "browser",
Editable: true,
HideControls: false,
HideControls: newFalse(),
Rows: []*Row{},
}
}
Expand Down Expand Up @@ -220,13 +220,14 @@ func (b *Board) AddRow(title string) *Row {
Title: title,
Collapse: false,
Editable: true,
Height: "250px",
Height: NewFloatOrString(FloatOrStringString("250px")),
}
b.Rows = append(b.Rows, row)
return row
}

func (b *Board) UpdateSlug() string {
b.Slug = strings.ToLower(slug.Make(b.Title))
return b.Slug
s := strings.ToLower(slug.Make(b.Title))
b.Slug = &s
return s
}
102 changes: 102 additions & 0 deletions custom-types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
Expand Down Expand Up @@ -191,3 +192,104 @@ func (v *FloatString) MarshalJSON() ([]byte, error) {

return []byte(`"null"`), nil
}

type actualType int

const (
actualNull = iota
actualString
actualFloat
)

type FloatOrStringOpt func(v *FloatOrString)

func NewFloatOrString(opts ...FloatOrStringOpt) FloatOrString {
var f FloatOrString
for _, opt := range opts {
opt(&f)
}
return f
}

func FloatOrStringString(s string) FloatOrStringOpt {
return func(v *FloatOrString) {
v.SetString(s)
}
}

func FloatOrStringFloat(f float64) FloatOrStringOpt {
return func(v *FloatOrString) {
v.SetFloat(f)
}
}

// FloatOrString represents special type for json values that could be strings or floats: "100px" or 100.3.
type FloatOrString struct {
Float float64
String string
actual actualType
}

func (v *FloatOrString) SetString(s string) {
v.actual = actualString
v.String = s
}

func (v *FloatOrString) SetFloat(f float64) {
v.actual = actualFloat
v.Float = f
}

func (v *FloatOrString) Value() interface{} {
switch v.actual {
case actualFloat:
return v.Float
case actualString:
return v.String
}
return nil
}

// UnmarshalJSON implements custom unmarshalling for FloatOrString type.
func (v *FloatOrString) UnmarshalJSON(raw []byte) error {
if raw == nil || bytes.Equal(raw, []byte(`"null"`)) || bytes.Equal(raw, []byte(`""`)) {
v.actual = actualNull
return nil
}

strVal := string(raw)
if rune(raw[0]) == '"' {
strVal = strings.Trim(strVal, `"`)
v.actual = actualString
v.String = strVal
return nil
}

i, err := strconv.ParseFloat(strVal, 64)
if err != nil {
return err
}
v.Float = i
v.actual = actualFloat
return nil
}

// MarshalJSON implements custom marshalling for FloatOrString type.
func (v *FloatOrString) MarshalJSON() ([]byte, error) {
switch v.actual {
case actualFloat:
strVal := strconv.FormatFloat(v.Float, 'g', -1, 64)
return []byte(strVal), nil
case actualString:
var buf bytes.Buffer
buf.WriteRune('"')
buf.WriteString(v.String)
buf.WriteRune('"')
return buf.Bytes(), nil
case actualNull:
return []byte(`"null"`), nil
default:
// This should never happen.
return nil, fmt.Errorf("wrong actual data type for FloatOrString: %v", v.actual)
}
}
16 changes: 8 additions & 8 deletions dashboard-unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@ func TestUnmarshal_DashboardWithMixedYaxes(t *testing.T) {
min3, max3 := p2.Yaxes[0].Min, p2.Yaxes[0].Max
min4, max4 := p2.Yaxes[1].Min, p2.Yaxes[1].Max

if min1.Value != 0 || min1.Valid != true {
t.Errorf("panel #1 has wrong min value: %f, expected: %f", min1.Value, 0.0)
if min1.Value() != "0" {
t.Errorf("panel #1 has wrong min value: %v, expected: \"%s\"", min1.Value(), "0")
}
if max1.Value != 100 || max1.Valid != true {
t.Errorf("panel #1 has wrong max value: %f, expected: %f", max1.Value, 100.0)
if max1.Value() != "100" {
t.Errorf("panel #1 has wrong max value: %v, expected: \"%s\"", max1.Value(), "100")
}

if min2 != nil {
Expand All @@ -165,8 +165,8 @@ func TestUnmarshal_DashboardWithMixedYaxes(t *testing.T) {
t.Errorf("panel #1 has wrong max value: %v, expected: %v", max2, nil)
}

if min3.Value != 0 || min3.Valid != true {
t.Errorf("panel #2 has wrong min value: %f, expected: %f", min3.Value, 0.0)
if min3.Value() != 0.0 {
t.Errorf("panel #2 has wrong min value: %v, expected: %f", min3.Value(), 0.0)
}
if max3 != nil {
t.Errorf("panel #2 has wrong max value: %v, expected: %v", max3, nil)
Expand All @@ -175,7 +175,7 @@ func TestUnmarshal_DashboardWithMixedYaxes(t *testing.T) {
if min4 != nil {
t.Errorf("panel #2 has wrong min value: %v, expected: %v", min4, nil)
}
if max4.Value != 50 || max4.Valid != true {
t.Errorf("panel #1 has wrong max value: %f, expected: %f", max4.Value, 100.0)
if max4.Value() != 50.0 {
t.Errorf("panel #1 has wrong max value: %f, expected: %f", max4.Value(), 50.0)
}
}
Loading