Add colors to all cells
parent
ede2c2a7d0
commit
1d11677c5f
|
@ -31,6 +31,8 @@ const (
|
||||||
ErrAuthentication = Error("user not authenticated")
|
ErrAuthentication = Error("user not authenticated")
|
||||||
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
||||||
ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'")
|
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
|
// Error is a domain error encountered while processing chronograf requests
|
||||||
|
@ -687,6 +689,15 @@ type Axis struct {
|
||||||
Scale string `json:"scale"` // Scale is the axis formatting scale. Supported: "log", "linear"
|
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
|
// DashboardCell holds visual and query information for a cell
|
||||||
type DashboardCell struct {
|
type DashboardCell struct {
|
||||||
ID string `json:"i"`
|
ID string `json:"i"`
|
||||||
|
@ -698,6 +709,7 @@ type DashboardCell struct {
|
||||||
Queries []DashboardQuery `json:"queries"`
|
Queries []DashboardQuery `json:"queries"`
|
||||||
Axes map[string]Axis `json:"axes"`
|
Axes map[string]Axis `json:"axes"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
CellColors []CellColor `json:"colors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DashboardsStore is the storage and retrieval of dashboards
|
// 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))
|
newCell.Queries = make([]chronograf.DashboardQuery, len(cell.Queries))
|
||||||
copy(newCell.Queries, 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
|
// ensure x, y, and y2 axes always returned
|
||||||
labels := []string{"x", "y", "y2"}
|
labels := []string{"x", "y", "y2"}
|
||||||
newCell.Axes = make(map[string]chronograf.Axis, len(labels))
|
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
|
// have the correct axes specified
|
||||||
func ValidDashboardCellRequest(c *chronograf.DashboardCell) error {
|
func ValidDashboardCellRequest(c *chronograf.DashboardCell) error {
|
||||||
CorrectWidthHeight(c)
|
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
|
// HasCorrectAxes verifies that only permitted axes exist within a DashboardCell
|
||||||
|
@ -93,6 +100,19 @@ func HasCorrectAxes(c *chronograf.DashboardCell) error {
|
||||||
return nil
|
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
|
// oneOf reports whether a provided string is a member of a variadic list of
|
||||||
// valid options
|
// valid options
|
||||||
func oneOf(prop string, validOpts ...string) bool {
|
func oneOf(prop string, validOpts ...string) bool {
|
||||||
|
|
|
@ -25,8 +25,8 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"correct axes",
|
name: "correct axes",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Bounds: []string{"0", "100"},
|
Bounds: []string{"0", "100"},
|
||||||
|
@ -39,11 +39,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"invalid axes present",
|
name: "invalid axes present",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"axis of evil": chronograf.Axis{
|
"axis of evil": chronograf.Axis{
|
||||||
Bounds: []string{"666", "666"},
|
Bounds: []string{"666", "666"},
|
||||||
|
@ -53,11 +52,11 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"linear scale value",
|
name: "linear scale value",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Scale: "linear",
|
Scale: "linear",
|
||||||
|
@ -65,11 +64,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"log scale value",
|
name: "log scale value",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Scale: "log",
|
Scale: "log",
|
||||||
|
@ -77,11 +75,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"invalid scale value",
|
name: "invalid scale value",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Scale: "potatoes",
|
Scale: "potatoes",
|
||||||
|
@ -89,11 +86,11 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"base 10 axis",
|
name: "base 10 axis",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Base: "10",
|
Base: "10",
|
||||||
|
@ -101,11 +98,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"base 2 axis",
|
name: "base 2 axis",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Base: "2",
|
Base: "2",
|
||||||
|
@ -113,11 +109,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"invalid base",
|
name: "invalid base",
|
||||||
&chronograf.DashboardCell{
|
cell: &chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Base: "all your base are belong to us",
|
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
|
expectedCode int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"happy path",
|
name: "happy path",
|
||||||
&url.URL{
|
reqURL: &url.URL{
|
||||||
Path: "/chronograf/v1/dashboards/1/cells",
|
Path: "/chronograf/v1/dashboards/1/cells",
|
||||||
},
|
},
|
||||||
map[string]string{
|
ctxParams: map[string]string{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
},
|
},
|
||||||
[]chronograf.DashboardCell{},
|
mockResponse: []chronograf.DashboardCell{},
|
||||||
[]chronograf.DashboardCell{},
|
expected: []chronograf.DashboardCell{},
|
||||||
http.StatusOK,
|
expectedCode: http.StatusOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell axes should always be \"x\", \"y\", and \"y2\"",
|
name: "cell axes should always be \"x\", \"y\", and \"y2\"",
|
||||||
&url.URL{
|
reqURL: &url.URL{
|
||||||
Path: "/chronograf/v1/dashboards/1/cells",
|
Path: "/chronograf/v1/dashboards/1/cells",
|
||||||
},
|
},
|
||||||
map[string]string{
|
ctxParams: map[string]string{
|
||||||
"id": "1",
|
"id": "1",
|
||||||
},
|
},
|
||||||
[]chronograf.DashboardCell{
|
mockResponse: []chronograf.DashboardCell{
|
||||||
{
|
{
|
||||||
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||||
X: 0,
|
X: 0,
|
||||||
|
@ -182,7 +177,7 @@ func Test_Service_DashboardCells(t *testing.T) {
|
||||||
Axes: map[string]chronograf.Axis{},
|
Axes: map[string]chronograf.Axis{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]chronograf.DashboardCell{
|
expected: []chronograf.DashboardCell{
|
||||||
{
|
{
|
||||||
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||||
X: 0,
|
X: 0,
|
||||||
|
@ -192,6 +187,7 @@ func Test_Service_DashboardCells(t *testing.T) {
|
||||||
Name: "CPU",
|
Name: "CPU",
|
||||||
Type: "bar",
|
Type: "bar",
|
||||||
Queries: []chronograf.DashboardQuery{},
|
Queries: []chronograf.DashboardQuery{},
|
||||||
|
CellColors: []chronograf.CellColor{},
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Bounds: []string{},
|
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{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Bounds: []string{"0", "100"},
|
Bounds: []string{"0", "100"},
|
||||||
|
@ -303,6 +304,7 @@ func Test_newDashboardResponse(t *testing.T) {
|
||||||
Bounds: []string{},
|
Bounds: []string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
CellColors: []chronograf.CellColor{},
|
||||||
Queries: []chronograf.DashboardQuery{
|
Queries: []chronograf.DashboardQuery{
|
||||||
{
|
{
|
||||||
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
|
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
|
||||||
|
|
|
@ -3955,6 +3955,14 @@
|
||||||
],
|
],
|
||||||
"default": "line"
|
"default": "line"
|
||||||
},
|
},
|
||||||
|
"colors": {
|
||||||
|
"description":
|
||||||
|
"Colors define encoding data into a visualization",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/DashboardColor"
|
||||||
|
}
|
||||||
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"Axis": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "A description of a particular axis for a visualization",
|
"description": "A description of a particular axis for a visualization",
|
||||||
|
|
Loading…
Reference in New Issue