feat(server): add v2 dashboard API

pull/3947/head
Michael Desa 2018-07-17 14:44:51 -04:00
parent 9191d18394
commit b61f424319
6 changed files with 1045 additions and 7 deletions

241
server/dashboardsv2.go Normal file
View File

@ -0,0 +1,241 @@
package server
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/bouk/httprouter"
platform "github.com/influxdata/chronograf/v2"
)
type dashboardV2Links struct {
Self string `json:"self"`
}
type dashboardV2Response struct {
platform.Dashboard
Links dashboardV2Links `json:"links"`
}
func newDashboardV2Response(c *platform.Dashboard) dashboardV2Response {
// Make nil slice values into empty array for front end.
if c.Cells == nil {
c.Cells = []platform.DashboardCell{}
}
if c.Templates == nil {
c.Templates = []platform.Template{}
}
return dashboardV2Response{
Links: dashboardV2Links{
Self: fmt.Sprintf("/chronograf/v2/dashboards/%s", c.ID),
},
Dashboard: *c,
}
}
// DashboardsV2 returns all dashboards within the store.
func (s *Service) DashboardsV2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// TODO: support filtering via query params
dashboards, _, err := s.Store.DashboardsV2(ctx).FindDashboards(ctx, platform.DashboardFilter{})
if err != nil {
Error(w, http.StatusInternalServerError, "Error loading dashboards", s.Logger)
return
}
s.encodeGetDashboardsResponse(w, dashboards)
}
type getDashboardsV2Links struct {
Self string `json:"self"`
}
type getDashboardsV2Response struct {
Links getDashboardsV2Links `json:"links"`
Dashboards []dashboardV2Response `json:"dashboards"`
}
func (s *Service) encodeGetDashboardsResponse(w http.ResponseWriter, dashboards []*platform.Dashboard) {
res := getDashboardsV2Response{
Links: getDashboardsV2Links{
Self: "/chronograf/v2/dashboards",
},
Dashboards: make([]dashboardV2Response, 0, len(dashboards)),
}
for _, dashboard := range dashboards {
res.Dashboards = append(res.Dashboards, newDashboardV2Response(dashboard))
}
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// NewDashboardV2 creates a new dashboard.
func (s *Service) NewDashboardV2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePostDashboardRequest(ctx, r)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
if err := s.Store.DashboardsV2(ctx).CreateDashboard(ctx, req.Dashboard); err != nil {
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error loading dashboards: %v", err), s.Logger)
return
}
s.encodePostDashboardResponse(w, req.Dashboard)
}
type postDashboardRequest struct {
Dashboard *platform.Dashboard
}
func decodePostDashboardRequest(ctx context.Context, r *http.Request) (*postDashboardRequest, error) {
c := &platform.Dashboard{}
if err := json.NewDecoder(r.Body).Decode(c); err != nil {
return nil, err
}
return &postDashboardRequest{
Dashboard: c,
}, nil
}
func (s *Service) encodePostDashboardResponse(w http.ResponseWriter, dashboard *platform.Dashboard) {
encodeJSON(w, http.StatusCreated, newDashboardV2Response(dashboard), s.Logger)
}
// DashboardIDV2 retrieves a dashboard by ID.
func (s *Service) DashboardIDV2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodeGetDashboardRequest(ctx, r)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
dashboard, err := s.Store.DashboardsV2(ctx).FindDashboardByID(ctx, req.DashboardID)
if err == platform.ErrDashboardNotFound {
Error(w, http.StatusNotFound, err.Error(), s.Logger)
return
}
if err != nil {
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error loading dashboard: %v", err), s.Logger)
return
}
s.encodeGetDashboardResponse(w, dashboard)
}
type getDashboardRequest struct {
DashboardID platform.ID
}
func decodeGetDashboardRequest(ctx context.Context, r *http.Request) (*getDashboardRequest, error) {
param := httprouter.GetParamFromContext(ctx, "id")
return &getDashboardRequest{
DashboardID: platform.ID(param),
}, nil
}
func (s *Service) encodeGetDashboardResponse(w http.ResponseWriter, dashboard *platform.Dashboard) {
encodeJSON(w, http.StatusOK, newDashboardV2Response(dashboard), s.Logger)
}
// RemoveDashboardV2 removes a dashboard by ID.
func (s *Service) RemoveDashboardV2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodeDeleteDashboardRequest(ctx, r)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
err = s.Store.DashboardsV2(ctx).DeleteDashboard(ctx, req.DashboardID)
if err == platform.ErrDashboardNotFound {
Error(w, http.StatusNotFound, err.Error(), s.Logger)
return
}
if err != nil {
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error deleting dashboard: %v", err), s.Logger)
return
}
w.WriteHeader(http.StatusNoContent)
}
type deleteDashboardRequest struct {
DashboardID platform.ID
}
func decodeDeleteDashboardRequest(ctx context.Context, r *http.Request) (*deleteDashboardRequest, error) {
param := httprouter.GetParamFromContext(ctx, "id")
return &deleteDashboardRequest{
DashboardID: platform.ID(param),
}, nil
}
// UpdateDashboardV2 updates a dashboard.
func (s *Service) UpdateDashboardV2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePatchDashboardRequest(ctx, r)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
dashboard, err := s.Store.DashboardsV2(ctx).UpdateDashboard(ctx, req.DashboardID, req.Upd)
if err == platform.ErrDashboardNotFound {
Error(w, http.StatusNotFound, err.Error(), s.Logger)
return
}
if err != nil {
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error updating dashboard: %v", err), s.Logger)
return
}
s.encodePatchDashboardResponse(w, dashboard)
}
type patchDashboardRequest struct {
DashboardID platform.ID
Upd platform.DashboardUpdate
}
func decodePatchDashboardRequest(ctx context.Context, r *http.Request) (*patchDashboardRequest, error) {
req := &patchDashboardRequest{}
upd := platform.DashboardUpdate{}
if err := json.NewDecoder(r.Body).Decode(&upd); err != nil {
return nil, err
}
req.Upd = upd
param := httprouter.GetParamFromContext(ctx, "id")
req.DashboardID = platform.ID(param)
if err := req.Valid(); err != nil {
return nil, err
}
return req, nil
}
// Valid validates that the dashboard ID is non zero valued and update has expected values set.
func (r *patchDashboardRequest) Valid() error {
if r.DashboardID == "" {
return fmt.Errorf("missing dashboard ID")
}
return r.Upd.Valid()
}
func (s *Service) encodePatchDashboardResponse(w http.ResponseWriter, dashboard *platform.Dashboard) {
encodeJSON(w, http.StatusOK, newDashboardV2Response(dashboard), s.Logger)
}

780
server/dashboardsv2_test.go Normal file
View File

@ -0,0 +1,780 @@
package server
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf/log"
"github.com/influxdata/chronograf/mocks"
"github.com/influxdata/chronograf/v2"
)
func TestService_DashboardsV2(t *testing.T) {
type fields struct {
DashboardService platform.DashboardService
}
type args struct {
queryParams map[string][]string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "get all dashboards",
fields: fields{
&mocks.DashboardService{
FindDashboardsF: func(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) {
return []*platform.Dashboard{
{
ID: platform.ID("0"),
Name: "hello",
Cells: []platform.DashboardCell{
{
X: 1,
Y: 2,
W: 3,
H: 4,
Ref: "/chronograf/v2/cells/12",
},
},
},
{
ID: platform.ID("2"),
Name: "example",
},
}, 2, nil
},
},
},
args: args{},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json",
body: `
{
"links": {
"self": "/chronograf/v2/dashboards"
},
"dashboards": [
{
"id": "0",
"name": "hello",
"cells": [
{
"x": 1,
"y": 2,
"w": 3,
"h": 4,
"ref": "/chronograf/v2/cells/12"
}
],
"templates": [],
"links": {
"self": "/chronograf/v2/dashboards/0"
}
},
{
"id": "2",
"name": "example",
"cells": [],
"templates": [],
"links": {
"self": "/chronograf/v2/dashboards/2"
}
}
]
}
`,
},
},
{
name: "get all dashboards when there are none",
fields: fields{
&mocks.DashboardService{
FindDashboardsF: func(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) {
return []*platform.Dashboard{}, 0, nil
},
},
},
args: args{},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json",
body: `
{
"links": {
"self": "/chronograf/v2/dashboards"
},
"dashboards": []
}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
DashboardService: tt.fields.DashboardService,
},
Logger: log.New(log.DebugLevel),
}
r := httptest.NewRequest("GET", "http://any.url", nil)
qp := r.URL.Query()
for k, vs := range tt.args.queryParams {
for _, v := range vs {
qp.Add(k, v)
}
}
r.URL.RawQuery = qp.Encode()
w := httptest.NewRecorder()
s.DashboardsV2(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. DashboardsV2() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. DashboardsV2() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. DashboardsV2() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func TestService_DashboardIDV2(t *testing.T) {
type fields struct {
DashboardService platform.DashboardService
}
type args struct {
id string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "get a dashboard by id",
fields: fields{
&mocks.DashboardService{
FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*platform.Dashboard, error) {
if id == "2" {
return &platform.Dashboard{
ID: platform.ID("2"),
Name: "hello",
Cells: []platform.DashboardCell{
{
X: 1,
Y: 2,
W: 3,
H: 4,
Ref: "/chronograf/v2/cells/12",
},
},
}, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "2",
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json",
body: `
{
"id": "2",
"name": "hello",
"cells": [
{
"x": 1,
"y": 2,
"w": 3,
"h": 4,
"ref": "/chronograf/v2/cells/12"
}
],
"templates": [],
"links": {
"self": "/chronograf/v2/dashboards/2"
}
}
`,
},
},
{
name: "not found",
fields: fields{
&mocks.DashboardService{
FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*platform.Dashboard, error) {
return nil, platform.ErrDashboardNotFound
},
},
},
args: args{
id: "2",
},
wants: wants{
statusCode: http.StatusNotFound,
contentType: "application/json",
body: `{"code":404,"message":"dashboard not found"}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
DashboardService: tt.fields.DashboardService,
},
Logger: log.New(log.DebugLevel),
}
r := httptest.NewRequest("GET", "http://any.url", nil)
r = r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
s.DashboardIDV2(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. DashboardIDV2() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. DashboardIDV2() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. DashboardIDV2() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func TestService_NewDashboardV2(t *testing.T) {
type fields struct {
DashboardService platform.DashboardService
}
type args struct {
dashboard *platform.Dashboard
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "create a new dashboard",
fields: fields{
&mocks.DashboardService{
CreateDashboardF: func(ctx context.Context, c *platform.Dashboard) error {
c.ID = "2"
return nil
},
},
},
args: args{
dashboard: &platform.Dashboard{
Name: "hello",
Cells: []platform.DashboardCell{
{
X: 1,
Y: 2,
W: 3,
H: 4,
Ref: "/chronograf/v2/cells/12",
},
},
},
},
wants: wants{
statusCode: http.StatusCreated,
contentType: "application/json",
body: `
{
"id": "2",
"name": "hello",
"cells": [
{
"x": 1,
"y": 2,
"w": 3,
"h": 4,
"ref": "/chronograf/v2/cells/12"
}
],
"templates": [],
"links": {
"self": "/chronograf/v2/dashboards/2"
}
}
`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
DashboardService: tt.fields.DashboardService,
},
Logger: log.New(log.DebugLevel),
}
b, err := json.Marshal(tt.args.dashboard)
if err != nil {
t.Fatalf("failed to unmarshal dashboard: %v", err)
}
r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
w := httptest.NewRecorder()
s.NewDashboardV2(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. DashboardIDV2() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. DashboardIDV2() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. DashboardIDV2() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func TestService_RemoveDashboardV2(t *testing.T) {
type fields struct {
DashboardService platform.DashboardService
}
type args struct {
id string
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "remove a dashboard by id",
fields: fields{
&mocks.DashboardService{
DeleteDashboardF: func(ctx context.Context, id platform.ID) error {
if id == "2" {
return nil
}
return fmt.Errorf("wrong id")
},
},
},
args: args{
id: "2",
},
wants: wants{
statusCode: http.StatusNoContent,
},
},
{
name: "dashboard not found",
fields: fields{
&mocks.DashboardService{
DeleteDashboardF: func(ctx context.Context, id platform.ID) error {
return platform.ErrDashboardNotFound
},
},
},
args: args{
id: "2",
},
wants: wants{
statusCode: http.StatusNotFound,
contentType: "application/json",
body: `{"code":404,"message":"dashboard not found"}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
DashboardService: tt.fields.DashboardService,
},
Logger: log.New(log.DebugLevel),
}
r := httptest.NewRequest("GET", "http://any.url", nil)
r = r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
s.RemoveDashboardV2(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. RemoveDashboardV2() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. RemoveDashboardV2() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. RemoveDashboardV2() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}
func TestService_UpdateDashboardV2(t *testing.T) {
type fields struct {
DashboardService platform.DashboardService
}
type args struct {
id string
name string
cells []platform.DashboardCell
templates []platform.Template
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "update a dashboard name",
fields: fields{
&mocks.DashboardService{
UpdateDashboardF: func(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
if id == "2" {
d := &platform.Dashboard{
ID: platform.ID("2"),
Name: "hello",
Cells: []platform.DashboardCell{
{
X: 1,
Y: 2,
W: 3,
H: 4,
Ref: "/chronograf/v2/cells/12",
},
},
}
if upd.Name != nil {
d.Name = *upd.Name
}
if upd.Cells != nil {
d.Cells = upd.Cells
}
if upd.Templates != nil {
d.Templates = upd.Templates
}
return d, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "2",
name: "example",
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json",
body: `
{
"id": "2",
"name": "example",
"cells": [
{
"x": 1,
"y": 2,
"w": 3,
"h": 4,
"ref": "/chronograf/v2/cells/12"
}
],
"templates": [],
"links": {
"self": "/chronograf/v2/dashboards/2"
}
}
`,
},
},
{
name: "update a dashboard cells",
fields: fields{
&mocks.DashboardService{
UpdateDashboardF: func(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
if id == "2" {
d := &platform.Dashboard{
ID: platform.ID("2"),
Name: "hello",
Cells: []platform.DashboardCell{
{
X: 1,
Y: 2,
W: 3,
H: 4,
Ref: "/chronograf/v2/cells/12",
},
},
}
if upd.Name != nil {
d.Name = *upd.Name
}
if upd.Cells != nil {
d.Cells = upd.Cells
}
if upd.Templates != nil {
d.Templates = upd.Templates
}
return d, nil
}
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "2",
cells: []platform.DashboardCell{
{
X: 1,
Y: 2,
W: 3,
H: 4,
Ref: "/chronograf/v2/cells/12",
},
{
X: 2,
Y: 3,
W: 4,
H: 5,
Ref: "/chronograf/v2/cells/1",
},
},
},
wants: wants{
statusCode: http.StatusOK,
contentType: "application/json",
body: `
{
"id": "2",
"name": "hello",
"cells": [
{
"x": 1,
"y": 2,
"w": 3,
"h": 4,
"ref": "/chronograf/v2/cells/12"
},
{
"x": 2,
"y": 3,
"w": 4,
"h": 5,
"ref": "/chronograf/v2/cells/1"
}
],
"templates": [],
"links": {
"self": "/chronograf/v2/dashboards/2"
}
}
`,
},
},
{
name: "update a dashboard with empty request body",
fields: fields{
&mocks.DashboardService{
UpdateDashboardF: func(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
return nil, fmt.Errorf("not found")
},
},
},
args: args{
id: "2",
},
wants: wants{
statusCode: http.StatusBadRequest,
contentType: "application/json",
body: `{"code":400,"message":"must update at least one attribute"}`,
},
},
{
name: "dashboard not found",
fields: fields{
&mocks.DashboardService{
UpdateDashboardF: func(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
return nil, platform.ErrDashboardNotFound
},
},
},
args: args{
id: "2",
name: "hello",
},
wants: wants{
statusCode: http.StatusNotFound,
contentType: "application/json",
body: `{"code":404,"message":"dashboard not found"}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Service{
Store: &mocks.Store{
DashboardService: tt.fields.DashboardService,
},
Logger: log.New(log.DebugLevel),
}
upd := platform.DashboardUpdate{}
if tt.args.name != "" {
upd.Name = &tt.args.name
}
if tt.args.cells != nil {
upd.Cells = tt.args.cells
}
if tt.args.templates != nil {
upd.Templates = tt.args.templates
}
b, err := json.Marshal(upd)
if err != nil {
t.Fatalf("failed to unmarshal dashboard update: %v", err)
}
r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
r = r.WithContext(httprouter.WithParams(
context.Background(),
httprouter.Params{
{
Key: "id",
Value: tt.args.id,
},
}))
w := httptest.NewRecorder()
s.UpdateDashboardV2(w, r)
res := w.Result()
content := res.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != tt.wants.statusCode {
t.Errorf("%q. UpdateDashboardV2() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. UpdateDashboardV2() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
t.Errorf("%q. UpdateDashboardV2() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
}
})
}
}

View File

@ -331,6 +331,14 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
router.DELETE("/chronograf/v2/cells/:id", EnsureEditor(service.RemoveCellV2))
router.PATCH("/chronograf/v2/cells/:id", EnsureEditor(service.UpdateCellV2))
// V2 Dashboards
router.GET("/chronograf/v2/dashboards", EnsureViewer(service.DashboardsV2))
router.POST("/chronograf/v2/dashboards", EnsureEditor(service.NewDashboardV2))
router.GET("/chronograf/v2/dashboards/:id", EnsureViewer(service.DashboardIDV2))
router.DELETE("/chronograf/v2/dashboards/:id", EnsureEditor(service.RemoveDashboardV2))
router.PATCH("/chronograf/v2/dashboards/:id", EnsureEditor(service.UpdateDashboardV2))
allRoutes := &AllRoutes{
Logger: opts.Logger,
StatusFeed: opts.StatusFeedURL,

View File

@ -42,6 +42,7 @@ type getRoutesResponse struct {
Dashboards string `json:"dashboards"` // Location of the dashboards endpoint
Config getConfigLinksResponse `json:"config"` // Location of the config endpoint and its various sections
Cells string `json:"cells"` // Location of the v2 cells
DashboardsV2 string `json:"dashboardsv2"` // Location of the v2 dashboards
Auth []AuthRoute `json:"auth"` // Location of all auth routes.
Logout *string `json:"logout,omitempty"` // Location of the logout route for all auth routes
ExternalLinks getExternalLinksResponse `json:"external"` // All external links for the client to use
@ -88,6 +89,7 @@ func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Environment: "/chronograf/v1/env",
Mappings: "/chronograf/v1/mappings",
Dashboards: "/chronograf/v1/dashboards",
DashboardsV2: "/chronograf/v2/dashboards",
Cells: "/chronograf/v2/cells",
Config: getConfigLinksResponse{
Self: "/chronograf/v1/config",

View File

@ -29,7 +29,7 @@ func TestAllRoutes(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutes not able to unmarshal JSON response")
}
want := `{"orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
want := `{"dashboardsv2":"/chronograf/v2/dashboards","orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
eq, err := jsonEqual(want, string(body))
@ -72,7 +72,7 @@ func TestAllRoutesWithAuth(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutesWithAuth not able to unmarshal JSON response")
}
want := `{"orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
want := `{"dashboardsv2":"/chronograf/v2/dashboards","orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
eq, err := jsonEqual(want, string(body))
if err != nil {
@ -109,7 +109,7 @@ func TestAllRoutesWithExternalLinks(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutesWithExternalLinks not able to unmarshal JSON response")
}
want := `{"orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
want := `{"dashboardsv2":"/chronograf/v2/dashboards","orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
eq, err := jsonEqual(want, string(body))
if err != nil {

View File

@ -7,7 +7,7 @@ import (
"github.com/influxdata/chronograf/noop"
"github.com/influxdata/chronograf/organizations"
"github.com/influxdata/chronograf/roles"
chronografv2 "github.com/influxdata/chronograf/v2"
platform "github.com/influxdata/chronograf/v2"
)
// hasOrganizationContext retrieves organization specified on context
@ -93,7 +93,8 @@ type DataStore interface {
Dashboards(ctx context.Context) chronograf.DashboardsStore
Config(ctx context.Context) chronograf.ConfigStore
OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore
Cells(ctx context.Context) chronografv2.CellService
Cells(ctx context.Context) platform.CellService
DashboardsV2(ctx context.Context) platform.DashboardService
}
// ensure that Store implements a DataStore
@ -110,7 +111,8 @@ type Store struct {
OrganizationsStore chronograf.OrganizationsStore
ConfigStore chronograf.ConfigStore
OrganizationConfigStore chronograf.OrganizationConfigStore
CellService chronografv2.CellService
CellService platform.CellService
DashboardService platform.DashboardService
}
// Sources returns a noop.SourcesStore if the context has no organization specified
@ -222,6 +224,11 @@ func (s *Store) Mappings(ctx context.Context) chronograf.MappingsStore {
}
// Cells returns the underlying CellService.
func (s *Store) Cells(ctx context.Context) chronografv2.CellService {
func (s *Store) Cells(ctx context.Context) platform.CellService {
return s.CellService
}
// DashboardsV2 returns the underlying DashboardsService.
func (s *Store) DashboardsV2(ctx context.Context) platform.DashboardService {
return s.DashboardService
}