Enforce presence of "x", "y", and "y2" axes

Certain aspects of the frontend requires the presence of these three
axes, so part of the contract established is that the backend will
always provide them. Since we centralize creation of
dashboardCellResponses, this is where these axes are added to all cell
responses.

Additionally, because there was previously no coverage over the
dashboard cells endpoints, a test has been added to cover the
DashboardCells method of Service.
pull/10616/head
Tim Raymond 2017-07-27 15:57:04 -04:00
parent 2ff3e27e1f
commit 4391004e7f
5 changed files with 203 additions and 0 deletions

37
mocks/dashboards.go Normal file
View File

@ -0,0 +1,37 @@
package mocks
import (
"context"
"github.com/influxdata/chronograf"
)
var _ chronograf.DashboardsStore = &DashboardsStore{}
type DashboardsStore struct {
AddF func(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error)
AllF func(ctx context.Context) ([]chronograf.Dashboard, error)
DeleteF func(ctx context.Context, target chronograf.Dashboard) error
GetF func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error)
UpdateF func(ctx context.Context, target chronograf.Dashboard) error
}
func (d *DashboardsStore) Add(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) {
return d.AddF(ctx, newDashboard)
}
func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) {
return d.AllF(ctx)
}
func (d *DashboardsStore) Delete(ctx context.Context, target chronograf.Dashboard) error {
return d.DeleteF(ctx, target)
}
func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) {
return d.GetF(ctx, id)
}
func (d *DashboardsStore) Update(ctx context.Context, target chronograf.Dashboard) error {
return d.UpdateF(ctx, target)
}

View File

@ -3,6 +3,7 @@ package mocks
import ( import (
"fmt" "fmt"
"io" "io"
"testing"
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
) )
@ -72,3 +73,11 @@ func (tl *TestLogger) stringifyArg(arg interface{}) []byte {
return []byte("UNKNOWN") return []byte("UNKNOWN")
} }
} }
// Dump dumps out logs into a given testing.T's logs
func (tl *TestLogger) Dump(t *testing.T) {
t.Log("== Dumping Test Logs ==")
for _, msg := range tl.Messages {
t.Logf("lvl: %s, msg: %s", msg.Level, msg.Body)
}
}

View File

@ -33,6 +33,20 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
if len(cell.Queries) == 0 { if len(cell.Queries) == 0 {
cell.Queries = make([]chronograf.DashboardQuery, 0) cell.Queries = make([]chronograf.DashboardQuery, 0)
} }
// ensure x, y, and y2 axes always returned
labels := []string{"x", "y", "y2"}
if cell.Axes == nil {
cell.Axes = make(map[string]chronograf.Axis, len(labels))
}
for _, lbl := range labels {
_, found := cell.Axes[lbl]
if !found {
cell.Axes[lbl] = chronograf.Axis{}
}
}
cells[i] = dashboardCellResponse{ cells[i] = dashboardCellResponse{
DashboardCell: cell, DashboardCell: cell,
Links: dashboardCellLinks{ Links: dashboardCellLinks{

View File

@ -1,9 +1,18 @@
package server_test package server_test
import ( import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing" "testing"
"github.com/bouk/httprouter"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/mocks"
"github.com/influxdata/chronograf/server" "github.com/influxdata/chronograf/server"
) )
@ -58,3 +67,131 @@ func Test_Cells_CorrectAxis(t *testing.T) {
}) })
} }
} }
func Test_Service_DashboardCells(t *testing.T) {
cellsTests := []struct {
name string
reqURL *url.URL
ctxParams map[string]string
mockResponse []chronograf.DashboardCell
expected []chronograf.DashboardCell
expectedCode int
}{
{
"happy path",
&url.URL{
Path: "/chronograf/v1/dashboards/1/cells",
},
map[string]string{
"id": "1",
},
[]chronograf.DashboardCell{},
[]chronograf.DashboardCell{},
http.StatusOK,
},
{
"cell axes should always be \"x\", \"y\", and \"y2\"",
&url.URL{
Path: "/chronograf/v1/dashboards/1/cells",
},
map[string]string{
"id": "1",
},
[]chronograf.DashboardCell{
{
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
X: 0,
Y: 0,
W: 4,
H: 4,
Name: "CPU",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{},
},
},
[]chronograf.DashboardCell{
{
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
X: 0,
Y: 0,
W: 4,
H: 4,
Name: "CPU",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{},
"y": chronograf.Axis{},
"y2": chronograf.Axis{},
},
},
},
http.StatusOK,
},
}
for _, test := range cellsTests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// setup context with params
ctx := context.Background()
params := httprouter.Params{}
for k, v := range test.ctxParams {
params = append(params, httprouter.Param{k, v})
}
ctx = httprouter.WithParams(ctx, params)
// setup response recorder and request
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", test.reqURL.RequestURI(), strings.NewReader("")).WithContext(ctx)
// setup mock DashboardCells store and logger
tlog := &mocks.TestLogger{}
svc := &server.Service{
DashboardsStore: &mocks.DashboardsStore{
GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) {
return chronograf.Dashboard{
ID: chronograf.DashboardID(1),
Cells: test.mockResponse,
Templates: []chronograf.Template{},
Name: "empty dashboard",
}, nil
},
},
Logger: tlog,
}
// invoke DashboardCell handler
svc.DashboardCells(rr, req)
// setup frame to decode response into
respFrame := []struct {
chronograf.DashboardCell
Links json.RawMessage `json:"links"` // ignore links
}{}
// decode response
resp := rr.Result()
if resp.StatusCode != test.expectedCode {
tlog.Dump(t)
t.Fatalf("%q - Status codes do not match. Want %d (%s), Got %d (%s)", test.name, test.expectedCode, http.StatusText(test.expectedCode), resp.StatusCode, http.StatusText(resp.StatusCode))
}
if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil {
t.Fatalf("%q - Error unmarshaling response body: err: %s", test.name, err)
}
// extract actual
actual := []chronograf.DashboardCell{}
for _, rsp := range respFrame {
actual = append(actual, rsp.DashboardCell)
}
// compare actual and expected
if !cmp.Equal(actual, test.expected) {
t.Fatalf("%q - Dashboard Cells do not match: diff: %s", test.name, cmp.Diff(actual, test.expected))
}
})
}
}

View File

@ -273,6 +273,7 @@ func Test_newDashboardResponse(t *testing.T) {
"y": chronograf.Axis{ "y": chronograf.Axis{
Bounds: []string{"2", "95"}, Bounds: []string{"2", "95"},
}, },
"y2": chronograf.Axis{},
}, },
}, },
}, },
@ -284,6 +285,11 @@ func Test_newDashboardResponse(t *testing.T) {
ID: "b", ID: "b",
W: 4, W: 4,
H: 4, H: 4,
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{},
"y": chronograf.Axis{},
"y2": chronograf.Axis{},
},
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",