Add colors to all cells
parent
ede2c2a7d0
commit
1d11677c5f
|
@ -31,6 +31,8 @@ const (
|
|||
ErrAuthentication = Error("user not authenticated")
|
||||
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
||||
ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'")
|
||||
ErrInvalidColorType = Error("Invalid color type. Valid color types are 'min', 'max', 'threshold'")
|
||||
ErrInvalidColor = Error("Invalid color. Accepted color format is #RRGGBB")
|
||||
)
|
||||
|
||||
// Error is a domain error encountered while processing chronograf requests
|
||||
|
@ -687,17 +689,27 @@ type Axis struct {
|
|||
Scale string `json:"scale"` // Scale is the axis formatting scale. Supported: "log", "linear"
|
||||
}
|
||||
|
||||
// CellColor represents the encoding of data into visualizations
|
||||
type CellColor struct {
|
||||
ID string `json:"id"` // ID is the unique id of the cell color
|
||||
Type string `json:"type"` // Type is how the color is used. Accepted (min,max,threshold)
|
||||
Hex string `json:"hex"` // Hex is the hex number of the color
|
||||
Name string `json:"name"` // Name is the user-facing name of the hex color
|
||||
Value string `json:"value"` // Value is the data value mapped to this color
|
||||
}
|
||||
|
||||
// DashboardCell holds visual and query information for a cell
|
||||
type DashboardCell struct {
|
||||
ID string `json:"i"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
W int32 `json:"w"`
|
||||
H int32 `json:"h"`
|
||||
Name string `json:"name"`
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
Type string `json:"type"`
|
||||
ID string `json:"i"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
W int32 `json:"w"`
|
||||
H int32 `json:"h"`
|
||||
Name string `json:"name"`
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
Type string `json:"type"`
|
||||
CellColors []CellColor `json:"colors"`
|
||||
}
|
||||
|
||||
// DashboardsStore is the storage and retrieval of dashboards
|
||||
|
|
|
@ -35,6 +35,9 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
|
|||
newCell.Queries = make([]chronograf.DashboardQuery, len(cell.Queries))
|
||||
copy(newCell.Queries, cell.Queries)
|
||||
|
||||
newCell.CellColors = make([]chronograf.CellColor, len(cell.CellColors))
|
||||
copy(newCell.CellColors, cell.CellColors)
|
||||
|
||||
// ensure x, y, and y2 axes always returned
|
||||
labels := []string{"x", "y", "y2"}
|
||||
newCell.Axes = make(map[string]chronograf.Axis, len(labels))
|
||||
|
@ -71,7 +74,11 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
|
|||
// have the correct axes specified
|
||||
func ValidDashboardCellRequest(c *chronograf.DashboardCell) error {
|
||||
CorrectWidthHeight(c)
|
||||
return HasCorrectAxes(c)
|
||||
err := HasCorrectAxes(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return HasCorrectColors(c)
|
||||
}
|
||||
|
||||
// HasCorrectAxes verifies that only permitted axes exist within a DashboardCell
|
||||
|
@ -93,6 +100,19 @@ func HasCorrectAxes(c *chronograf.DashboardCell) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// HasCorrectColors verifies that the format of each color is correct
|
||||
func HasCorrectColors(c *chronograf.DashboardCell) error {
|
||||
for _, color := range c.CellColors {
|
||||
if !oneOf(color.Type, "max", "min", "threshold") {
|
||||
return chronograf.ErrInvalidColorType
|
||||
}
|
||||
if len(color.Hex) != 7 {
|
||||
return chronograf.ErrInvalidColor
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// oneOf reports whether a provided string is a member of a variadic list of
|
||||
// valid options
|
||||
func oneOf(prop string, validOpts ...string) bool {
|
||||
|
|
|
@ -25,8 +25,8 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
"correct axes",
|
||||
&chronograf.DashboardCell{
|
||||
name: "correct axes",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{"0", "100"},
|
||||
|
@ -39,11 +39,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid axes present",
|
||||
&chronograf.DashboardCell{
|
||||
name: "invalid axes present",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"axis of evil": chronograf.Axis{
|
||||
Bounds: []string{"666", "666"},
|
||||
|
@ -53,11 +52,11 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
"linear scale value",
|
||||
&chronograf.DashboardCell{
|
||||
name: "linear scale value",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Scale: "linear",
|
||||
|
@ -65,11 +64,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"log scale value",
|
||||
&chronograf.DashboardCell{
|
||||
name: "log scale value",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Scale: "log",
|
||||
|
@ -77,11 +75,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid scale value",
|
||||
&chronograf.DashboardCell{
|
||||
name: "invalid scale value",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Scale: "potatoes",
|
||||
|
@ -89,11 +86,11 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
"base 10 axis",
|
||||
&chronograf.DashboardCell{
|
||||
name: "base 10 axis",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Base: "10",
|
||||
|
@ -101,11 +98,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"base 2 axis",
|
||||
&chronograf.DashboardCell{
|
||||
name: "base 2 axis",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Base: "2",
|
||||
|
@ -113,11 +109,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid base",
|
||||
&chronograf.DashboardCell{
|
||||
name: "invalid base",
|
||||
cell: &chronograf.DashboardCell{
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Base: "all your base are belong to us",
|
||||
|
@ -125,7 +120,7 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -150,26 +145,26 @@ func Test_Service_DashboardCells(t *testing.T) {
|
|||
expectedCode int
|
||||
}{
|
||||
{
|
||||
"happy path",
|
||||
&url.URL{
|
||||
name: "happy path",
|
||||
reqURL: &url.URL{
|
||||
Path: "/chronograf/v1/dashboards/1/cells",
|
||||
},
|
||||
map[string]string{
|
||||
ctxParams: map[string]string{
|
||||
"id": "1",
|
||||
},
|
||||
[]chronograf.DashboardCell{},
|
||||
[]chronograf.DashboardCell{},
|
||||
http.StatusOK,
|
||||
mockResponse: []chronograf.DashboardCell{},
|
||||
expected: []chronograf.DashboardCell{},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
"cell axes should always be \"x\", \"y\", and \"y2\"",
|
||||
&url.URL{
|
||||
name: "cell axes should always be \"x\", \"y\", and \"y2\"",
|
||||
reqURL: &url.URL{
|
||||
Path: "/chronograf/v1/dashboards/1/cells",
|
||||
},
|
||||
map[string]string{
|
||||
ctxParams: map[string]string{
|
||||
"id": "1",
|
||||
},
|
||||
[]chronograf.DashboardCell{
|
||||
mockResponse: []chronograf.DashboardCell{
|
||||
{
|
||||
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||
X: 0,
|
||||
|
@ -182,16 +177,17 @@ func Test_Service_DashboardCells(t *testing.T) {
|
|||
Axes: map[string]chronograf.Axis{},
|
||||
},
|
||||
},
|
||||
[]chronograf.DashboardCell{
|
||||
expected: []chronograf.DashboardCell{
|
||||
{
|
||||
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: 4,
|
||||
H: 4,
|
||||
Name: "CPU",
|
||||
Type: "bar",
|
||||
Queries: []chronograf.DashboardQuery{},
|
||||
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: 4,
|
||||
H: 4,
|
||||
Name: "CPU",
|
||||
Type: "bar",
|
||||
Queries: []chronograf.DashboardQuery{},
|
||||
CellColors: []chronograf.CellColor{},
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
|
@ -205,7 +201,7 @@ func Test_Service_DashboardCells(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -275,3 +271,76 @@ func Test_Service_DashboardCells(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasCorrectColors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c *chronograf.DashboardCell
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "min type is valid",
|
||||
c: &chronograf.DashboardCell{
|
||||
CellColors: []chronograf.CellColor{
|
||||
{
|
||||
Type: "min",
|
||||
Hex: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max type is valid",
|
||||
c: &chronograf.DashboardCell{
|
||||
CellColors: []chronograf.CellColor{
|
||||
{
|
||||
Type: "max",
|
||||
Hex: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "threshold type is valid",
|
||||
c: &chronograf.DashboardCell{
|
||||
CellColors: []chronograf.CellColor{
|
||||
{
|
||||
Type: "threshold",
|
||||
Hex: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid color type",
|
||||
c: &chronograf.DashboardCell{
|
||||
CellColors: []chronograf.CellColor{
|
||||
{
|
||||
Type: "unknown",
|
||||
Hex: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid color hex",
|
||||
c: &chronograf.DashboardCell{
|
||||
CellColors: []chronograf.CellColor{
|
||||
{
|
||||
Type: "min",
|
||||
Hex: "bad",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := server.HasCorrectColors(tt.c); (err != nil) != tt.wantErr {
|
||||
t.Errorf("HasCorrectColors() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,6 +270,7 @@ func Test_newDashboardResponse(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
CellColors: []chronograf.CellColor{},
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{"0", "100"},
|
||||
|
@ -303,6 +304,7 @@ func Test_newDashboardResponse(t *testing.T) {
|
|||
Bounds: []string{},
|
||||
},
|
||||
},
|
||||
CellColors: []chronograf.CellColor{},
|
||||
Queries: []chronograf.DashboardQuery{
|
||||
{
|
||||
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
|
||||
|
|
|
@ -3955,6 +3955,14 @@
|
|||
],
|
||||
"default": "line"
|
||||
},
|
||||
"colors": {
|
||||
"description":
|
||||
"Colors define encoding data into a visualization",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/DashboardColor"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -4025,6 +4033,36 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"DashboardColor": {
|
||||
"type": "object",
|
||||
"description":
|
||||
"Color defines an encoding of a data value into color space",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the unique id of the cell color",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type is how the color is used.",
|
||||
"type": "string",
|
||||
"enum": ["min", "max", "threshold"]
|
||||
},
|
||||
"hex": {
|
||||
"description": "Hex is the hex number of the color",
|
||||
"type": "string",
|
||||
"maxLength": 9,
|
||||
"minLength": 7
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the user-facing name of the hex color",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"description": "Value is the data value mapped to this color",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Axis": {
|
||||
"type": "object",
|
||||
"description": "A description of a particular axis for a visualization",
|
||||
|
|
Loading…
Reference in New Issue