feat(server): add cells api
parent
3add4592b8
commit
4ba1dba579
|
@ -0,0 +1,220 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
chronograf "github.com/influxdata/chronograf/v2"
|
||||
)
|
||||
|
||||
type cellV2Links struct {
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
type cellV2Response struct {
|
||||
chronograf.Cell
|
||||
Links cellV2Links `json:"links"`
|
||||
}
|
||||
|
||||
func (r cellV2Response) MarshalJSON() ([]byte, error) {
|
||||
vis, err := chronograf.MarshalVisualizationJSON(r.Visualization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(struct {
|
||||
chronograf.CellContents
|
||||
Links cellV2Links `json:"links"`
|
||||
Visualization json.RawMessage `json:"visualization"`
|
||||
}{
|
||||
CellContents: r.CellContents,
|
||||
Links: r.Links,
|
||||
Visualization: vis,
|
||||
})
|
||||
}
|
||||
|
||||
func newCellV2Response(c *chronograf.Cell) cellV2Response {
|
||||
return cellV2Response{
|
||||
Links: cellV2Links{
|
||||
Self: fmt.Sprintf("/chronograf/v1/cells/%s", c.ID),
|
||||
},
|
||||
Cell: *c,
|
||||
}
|
||||
}
|
||||
|
||||
// CellsV2 returns all cells within the store.
|
||||
func (s *Service) CellsV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
// TODO: support filtering via query params
|
||||
cells, _, err := s.Store.Cells(ctx).FindCells(ctx, chronograf.CellFilter{})
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "Error loading cells", s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
s.encodeGetCellsResponse(w, cells)
|
||||
}
|
||||
|
||||
type getCellsLinks struct {
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
type getCellsResponse struct {
|
||||
Links getCellsLinks `json:"links"`
|
||||
Cells []cellV2Response `json:"cells"`
|
||||
}
|
||||
|
||||
func (s *Service) encodeGetCellsResponse(w http.ResponseWriter, cells []*chronograf.Cell) {
|
||||
res := getCellsResponse{
|
||||
Links: getCellsLinks{
|
||||
Self: "/chronograf/v2/cells",
|
||||
},
|
||||
Cells: make([]cellV2Response, 0, len(cells)),
|
||||
}
|
||||
|
||||
for _, cell := range cells {
|
||||
res.Cells = append(res.Cells, newCellV2Response(cell))
|
||||
}
|
||||
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
||||
|
||||
// NewCellV2 creates a new cell.
|
||||
func (s *Service) NewCellV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePostCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
if err := s.Store.Cells(ctx).CreateCell(ctx, req.Cell); err != nil {
|
||||
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error loading cells: %v", err), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
s.encodePostCellResponse(w, req.Cell)
|
||||
}
|
||||
|
||||
type postCellRequest struct {
|
||||
Cell *chronograf.Cell
|
||||
}
|
||||
|
||||
func decodePostCellRequest(ctx context.Context, r *http.Request) (*postCellRequest, error) {
|
||||
c := &chronograf.Cell{}
|
||||
if err := json.NewDecoder(r.Body).Decode(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &postCellRequest{
|
||||
Cell: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) encodePostCellResponse(w http.ResponseWriter, cell *chronograf.Cell) {
|
||||
encodeJSON(w, http.StatusCreated, newCellV2Response(cell), s.Logger)
|
||||
}
|
||||
|
||||
// CellIDV2 retrieves a cell by ID.
|
||||
func (s *Service) CellIDV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeGetCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
cell, err := s.Store.Cells(ctx).FindCellByID(ctx, req.CellID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error loading cell: %v", err), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
s.encodeGetCellResponse(w, cell)
|
||||
}
|
||||
|
||||
type getCellRequest struct {
|
||||
CellID chronograf.ID
|
||||
}
|
||||
|
||||
func decodeGetCellRequest(ctx context.Context, r *http.Request) (*getCellRequest, error) {
|
||||
param := httprouter.GetParamFromContext(ctx, "id")
|
||||
return &getCellRequest{
|
||||
CellID: chronograf.ID(param),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) encodeGetCellResponse(w http.ResponseWriter, cell *chronograf.Cell) {
|
||||
encodeJSON(w, http.StatusOK, newCellV2Response(cell), s.Logger)
|
||||
}
|
||||
|
||||
// RemoveCellV2 removes a cell by ID.
|
||||
func (s *Service) RemoveCellV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeDeleteCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
if err := s.Store.Cells(ctx).DeleteCell(ctx, req.CellID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error deleting cell: %v", err), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
type deleteCellRequest struct {
|
||||
CellID chronograf.ID
|
||||
}
|
||||
|
||||
func decodeDeleteCellRequest(ctx context.Context, r *http.Request) (*deleteCellRequest, error) {
|
||||
param := httprouter.GetParamFromContext(ctx, "id")
|
||||
return &deleteCellRequest{
|
||||
CellID: chronograf.ID(param),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateCellV2 updates a cell.
|
||||
func (s *Service) UpdateCellV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePatchCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
cell, err := s.Store.Cells(ctx).UpdateCell(ctx, req.CellID, req.Upd)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, fmt.Sprintf("Error updating cell: %v", err), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
s.encodePatchCellResponse(w, cell)
|
||||
}
|
||||
|
||||
type patchCellRequest struct {
|
||||
CellID chronograf.ID
|
||||
Upd chronograf.CellUpdate
|
||||
}
|
||||
|
||||
func decodePatchCellRequest(ctx context.Context, r *http.Request) (*patchCellRequest, error) {
|
||||
upd := chronograf.CellUpdate{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&upd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
param := httprouter.GetParamFromContext(ctx, "id")
|
||||
|
||||
return &patchCellRequest{
|
||||
CellID: chronograf.ID(param),
|
||||
Upd: upd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) encodePatchCellResponse(w http.ResponseWriter, cell *chronograf.Cell) {
|
||||
encodeJSON(w, http.StatusOK, newCellV2Response(cell), s.Logger)
|
||||
}
|
|
@ -323,6 +323,14 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
|
||||
router.GET("/chronograf/v1/env", EnsureViewer(service.Environment))
|
||||
|
||||
/// V2 Cells
|
||||
router.GET("/chronograf/v2/cells", EnsureViewer(service.CellsV2))
|
||||
router.POST("/chronograf/v2/cells", EnsureEditor(service.NewCellV2))
|
||||
|
||||
router.GET("/chronograf/v2/cells/:id", EnsureViewer(service.CellIDV2))
|
||||
router.DELETE("/chronograf/v2/cells/:id", EnsureEditor(service.RemoveCellV2))
|
||||
router.PATCH("/chronograf/v2/cells/:id", EnsureEditor(service.UpdateCellV2))
|
||||
|
||||
allRoutes := &AllRoutes{
|
||||
Logger: opts.Logger,
|
||||
StatusFeed: opts.StatusFeedURL,
|
||||
|
|
|
@ -495,6 +495,7 @@ func openService(ctx context.Context, buildInfo chronograf.BuildInfo, boltPath s
|
|||
ConfigStore: db.ConfigStore,
|
||||
MappingsStore: db.MappingsStore,
|
||||
OrganizationConfigStore: db.OrganizationConfigStore,
|
||||
CellService: db,
|
||||
},
|
||||
Logger: logger,
|
||||
UseAuth: useAuth,
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/influxdata/chronograf/noop"
|
||||
"github.com/influxdata/chronograf/organizations"
|
||||
"github.com/influxdata/chronograf/roles"
|
||||
chronografv2 "github.com/influxdata/chronograf/v2"
|
||||
)
|
||||
|
||||
// hasOrganizationContext retrieves organization specified on context
|
||||
|
@ -92,6 +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
|
||||
}
|
||||
|
||||
// ensure that Store implements a DataStore
|
||||
|
@ -108,6 +111,8 @@ type Store struct {
|
|||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
OrganizationConfigStore chronograf.OrganizationConfigStore
|
||||
|
||||
CellService chronografv2.CellService
|
||||
}
|
||||
|
||||
// Sources returns a noop.SourcesStore if the context has no organization specified
|
||||
|
@ -217,3 +222,8 @@ func (s *Store) Mappings(ctx context.Context) chronograf.MappingsStore {
|
|||
}
|
||||
return &noop.MappingsStore{}
|
||||
}
|
||||
|
||||
// Cells returns the underlying CellService.
|
||||
func (s *Store) Cells(ctx context.Context) chronografv2.CellService {
|
||||
return s.CellService
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue