commit
c3c6381c9d
|
@ -69,6 +69,11 @@ func (c *Client) initialize(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Always create Dashboards bucket.
|
||||
if err := c.initializeDashboards(ctx, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Always create User bucket.
|
||||
if err := c.initializeUsers(ctx, tx); err != nil {
|
||||
return err
|
||||
|
|
|
@ -0,0 +1,367 @@
|
|||
package bolt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/influxdata/platform"
|
||||
)
|
||||
|
||||
var (
|
||||
dashboardBucket = []byte("dashboardsv1")
|
||||
)
|
||||
|
||||
func (c *Client) initializeDashboards(ctx context.Context, tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucketIfNotExists([]byte(dashboardBucket)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) setOrganizationOnDashboard(ctx context.Context, tx *bolt.Tx, d *platform.Dashboard) error {
|
||||
o, err := c.findOrganizationByID(ctx, tx, d.OrganizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Organization = o.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindDashboardByID retrieves a dashboard by id.
|
||||
func (c *Client) FindDashboardByID(ctx context.Context, id platform.ID) (*platform.Dashboard, error) {
|
||||
var d *platform.Dashboard
|
||||
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
dash, err := c.findDashboardByID(ctx, tx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d = dash
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (c *Client) findDashboardByID(ctx context.Context, tx *bolt.Tx, id platform.ID) (*platform.Dashboard, error) {
|
||||
var d platform.Dashboard
|
||||
|
||||
v := tx.Bucket(dashboardBucket).Get(id)
|
||||
|
||||
if len(v) == 0 {
|
||||
// TODO: Make standard error
|
||||
return nil, fmt.Errorf("dashboard not found")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(v, &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.setOrganizationOnDashboard(ctx, tx, &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// FindDashboard retrieves a dashboard using an arbitrary dashboard filter.
|
||||
// Filters using ID, or OrganizationID and dashboard Name should be efficient.
|
||||
// Other filters will do a linear scan across dashboards until it finds a match.
|
||||
func (c *Client) FindDashboard(ctx context.Context, filter platform.DashboardFilter) (*platform.Dashboard, error) {
|
||||
if filter.ID != nil {
|
||||
return c.FindDashboardByID(ctx, *filter.ID)
|
||||
}
|
||||
|
||||
var d *platform.Dashboard
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
if filter.Organization != nil {
|
||||
o, err := c.findOrganizationByName(ctx, tx, *filter.Organization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter.OrganizationID = &o.ID
|
||||
}
|
||||
|
||||
filterFn := filterDashboardsFn(filter)
|
||||
return c.forEachDashboard(ctx, tx, func(dash *platform.Dashboard) bool {
|
||||
if filterFn(dash) {
|
||||
d = dash
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d == nil {
|
||||
return nil, fmt.Errorf("dashboard not found")
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func filterDashboardsFn(filter platform.DashboardFilter) func(d *platform.Dashboard) bool {
|
||||
if filter.ID != nil {
|
||||
return func(d *platform.Dashboard) bool {
|
||||
return bytes.Equal(d.ID, *filter.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if filter.OrganizationID != nil {
|
||||
return func(d *platform.Dashboard) bool {
|
||||
return bytes.Equal(d.OrganizationID, *filter.OrganizationID)
|
||||
}
|
||||
}
|
||||
|
||||
return func(d *platform.Dashboard) bool { return true }
|
||||
}
|
||||
|
||||
// FindDashboardsByOrganizationID retrieves all dashboards that belong to a particular organization ID.
|
||||
func (c *Client) FindDashboardsByOrganizationID(ctx context.Context, orgID platform.ID) ([]*platform.Dashboard, int, error) {
|
||||
return c.FindDashboards(ctx, platform.DashboardFilter{OrganizationID: &orgID})
|
||||
}
|
||||
|
||||
// FindDashboardsByOrganizationName retrieves all dashboards that belong to a particular organization.
|
||||
func (c *Client) FindDashboardsByOrganizationName(ctx context.Context, org string) ([]*platform.Dashboard, int, error) {
|
||||
return c.FindDashboards(ctx, platform.DashboardFilter{Organization: &org})
|
||||
}
|
||||
|
||||
// FindDashboards retrives all dashboards that match an arbitrary dashboard filter.
|
||||
// Filters using ID, or OrganizationID and dashboard Name should be efficient.
|
||||
// Other filters will do a linear scan across all dashboards searching for a match.
|
||||
func (c *Client) FindDashboards(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) {
|
||||
if filter.ID != nil {
|
||||
d, err := c.FindDashboardByID(ctx, *filter.ID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return []*platform.Dashboard{d}, 1, nil
|
||||
}
|
||||
|
||||
ds := []*platform.Dashboard{}
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
dashs, err := c.findDashboards(ctx, tx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ds = dashs
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return ds, len(ds), nil
|
||||
}
|
||||
|
||||
func (c *Client) findDashboards(ctx context.Context, tx *bolt.Tx, filter platform.DashboardFilter) ([]*platform.Dashboard, error) {
|
||||
ds := []*platform.Dashboard{}
|
||||
if filter.Organization != nil {
|
||||
o, err := c.findOrganizationByName(ctx, tx, *filter.Organization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter.OrganizationID = &o.ID
|
||||
}
|
||||
|
||||
filterFn := filterDashboardsFn(filter)
|
||||
err := c.forEachDashboard(ctx, tx, func(d *platform.Dashboard) bool {
|
||||
if filterFn(d) {
|
||||
ds = append(ds, d)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
// CreateDashboard creates a platform dashboard and sets d.ID.
|
||||
func (c *Client) CreateDashboard(ctx context.Context, d *platform.Dashboard) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
if len(d.OrganizationID) == 0 {
|
||||
o, err := c.findOrganizationByName(ctx, tx, d.Organization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.OrganizationID = o.ID
|
||||
}
|
||||
|
||||
d.ID = c.IDGenerator.ID()
|
||||
|
||||
for i, cell := range d.Cells {
|
||||
cell.ID = c.IDGenerator.ID()
|
||||
d.Cells[i] = cell
|
||||
}
|
||||
|
||||
return c.putDashboard(ctx, tx, d)
|
||||
})
|
||||
}
|
||||
|
||||
// PutDashboard will put a dashboard without setting an ID.
|
||||
func (c *Client) PutDashboard(ctx context.Context, d *platform.Dashboard) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.putDashboard(ctx, tx, d)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) putDashboard(ctx context.Context, tx *bolt.Tx, d *platform.Dashboard) error {
|
||||
d.Organization = ""
|
||||
v, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Bucket(dashboardBucket).Put(d.ID, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.setOrganizationOnDashboard(ctx, tx, d)
|
||||
}
|
||||
|
||||
// forEachDashboard will iterate through all dashboards while fn returns true.
|
||||
func (c *Client) forEachDashboard(ctx context.Context, tx *bolt.Tx, fn func(*platform.Dashboard) bool) error {
|
||||
cur := tx.Bucket(dashboardBucket).Cursor()
|
||||
for k, v := cur.First(); k != nil; k, v = cur.Next() {
|
||||
d := &platform.Dashboard{}
|
||||
if err := json.Unmarshal(v, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.setOrganizationOnDashboard(ctx, tx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if !fn(d) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDashboard updates a dashboard according the parameters set on upd.
|
||||
func (c *Client) UpdateDashboard(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
|
||||
var d *platform.Dashboard
|
||||
err := c.db.Update(func(tx *bolt.Tx) error {
|
||||
dash, err := c.updateDashboard(ctx, tx, id, upd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d = dash
|
||||
return nil
|
||||
})
|
||||
|
||||
return d, err
|
||||
}
|
||||
|
||||
func (c *Client) updateDashboard(ctx context.Context, tx *bolt.Tx, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
|
||||
d, err := c.findDashboardByID(ctx, tx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if upd.Name != nil {
|
||||
d.Name = *upd.Name
|
||||
}
|
||||
|
||||
if err := c.putDashboard(ctx, tx, d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.setOrganizationOnDashboard(ctx, tx, d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// DeleteDashboard deletes a dashboard and prunes it from the index.
|
||||
func (c *Client) DeleteDashboard(ctx context.Context, id platform.ID) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.deleteDashboard(ctx, tx, id)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) deleteDashboard(ctx context.Context, tx *bolt.Tx, id platform.ID) error {
|
||||
_, err := c.findDashboardByID(ctx, tx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Bucket(dashboardBucket).Delete(id)
|
||||
}
|
||||
|
||||
// AddDashboardCell adds a cell to a dashboard.
|
||||
func (c *Client) AddDashboardCell(ctx context.Context, dashboardID platform.ID, cell *platform.DashboardCell) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
d, err := c.findDashboardByID(ctx, tx, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cell.ID = c.IDGenerator.ID()
|
||||
d.Cells = append(d.Cells, *cell)
|
||||
return c.putDashboard(ctx, tx, d)
|
||||
})
|
||||
}
|
||||
|
||||
// ReplaceDashboardCell updates a cell in a dashboard.
|
||||
func (c *Client) ReplaceDashboardCell(ctx context.Context, dashboardID platform.ID, dc *platform.DashboardCell) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
d, err := c.findDashboardByID(ctx, tx, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := -1
|
||||
for i, cell := range d.Cells {
|
||||
if bytes.Equal(dc.ID, cell.ID) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("cell not found")
|
||||
}
|
||||
|
||||
d.Cells[idx] = *dc
|
||||
|
||||
return c.putDashboard(ctx, tx, d)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveDashboardCell removes a cell from a dashboard.
|
||||
func (c *Client) RemoveDashboardCell(ctx context.Context, dashboardID platform.ID, cellID platform.ID) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
d, err := c.findDashboardByID(ctx, tx, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := -1
|
||||
for i, cell := range d.Cells {
|
||||
if bytes.Equal(cellID, cell.ID) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("cell not found")
|
||||
}
|
||||
|
||||
// Remove cell
|
||||
d.Cells = append(d.Cells[:idx], d.Cells[idx+1:]...)
|
||||
|
||||
return c.putDashboard(ctx, tx, d)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package bolt_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/platform"
|
||||
platformtesting "github.com/influxdata/platform/testing"
|
||||
)
|
||||
|
||||
func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (platform.DashboardService, func()) {
|
||||
c, closeFn, err := NewTestClient()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new bolt client: %v", err)
|
||||
}
|
||||
c.IDGenerator = f.IDGenerator
|
||||
ctx := context.TODO()
|
||||
for _, o := range f.Organizations {
|
||||
if err := c.PutOrganization(ctx, o); err != nil {
|
||||
t.Fatalf("failed to populate organizations")
|
||||
}
|
||||
}
|
||||
for _, b := range f.Dashboards {
|
||||
if err := c.PutDashboard(ctx, b); err != nil {
|
||||
t.Fatalf("failed to populate dashboards")
|
||||
}
|
||||
}
|
||||
return c, func() {
|
||||
defer closeFn()
|
||||
for _, o := range f.Organizations {
|
||||
if err := c.DeleteOrganization(ctx, o.ID); err != nil {
|
||||
t.Logf("failed to remove organization: %v", err)
|
||||
}
|
||||
}
|
||||
for _, b := range f.Dashboards {
|
||||
if err := c.DeleteDashboard(ctx, b.ID); err != nil {
|
||||
t.Logf("failed to remove dashboard: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardService_CreateDashboard(t *testing.T) {
|
||||
platformtesting.CreateDashboard(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_FindDashboardByID(t *testing.T) {
|
||||
platformtesting.FindDashboardByID(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_FindDashboards(t *testing.T) {
|
||||
platformtesting.FindDashboards(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_FindDashboardsByOrganizationID(t *testing.T) {
|
||||
platformtesting.FindDashboardsByOrganizationID(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_FindDashboardsByOrganizationName(t *testing.T) {
|
||||
platformtesting.FindDashboardsByOrganizationName(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_DeleteDashboard(t *testing.T) {
|
||||
platformtesting.DeleteDashboard(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_UpdateDashboard(t *testing.T) {
|
||||
platformtesting.UpdateDashboard(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_AddDashboardCell(t *testing.T) {
|
||||
platformtesting.AddDashboardCell(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_ReplaceDashboardCell(t *testing.T) {
|
||||
platformtesting.ReplaceDashboardCell(initDashboardService, t)
|
||||
}
|
||||
|
||||
func TestDashboardService_RemoveDashboardCell(t *testing.T) {
|
||||
platformtesting.RemoveDashboardCell(initDashboardService, t)
|
||||
}
|
|
@ -91,6 +91,11 @@ func platformF(cmd *cobra.Command, args []string) {
|
|||
userSvc = c
|
||||
}
|
||||
|
||||
var dashboardSvc platform.DashboardService
|
||||
{
|
||||
dashboardSvc = c
|
||||
}
|
||||
|
||||
errc := make(chan error)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
|
@ -111,6 +116,9 @@ func platformF(cmd *cobra.Command, args []string) {
|
|||
userHandler := http.NewUserHandler()
|
||||
userHandler.UserService = userSvc
|
||||
|
||||
dashboardHandler := http.NewDashboardHandler()
|
||||
dashboardHandler.DashboardService = dashboardSvc
|
||||
|
||||
authHandler := http.NewAuthorizationHandler()
|
||||
authHandler.AuthorizationService = authSvc
|
||||
authHandler.Logger = logger.With(zap.String("handler", "auth"))
|
||||
|
@ -120,6 +128,7 @@ func platformF(cmd *cobra.Command, args []string) {
|
|||
OrgHandler: orgHandler,
|
||||
UserHandler: userHandler,
|
||||
AuthorizationHandler: authHandler,
|
||||
DashboardHandler: dashboardHandler,
|
||||
}
|
||||
h := http.NewHandler("platform")
|
||||
h.Handler = platformHandler
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DashboardService represents a service for managing dashboard data.
|
||||
type DashboardService interface {
|
||||
// FindDashboardByID returns a single dashboard by ID.
|
||||
FindDashboardByID(ctx context.Context, id ID) (*Dashboard, error)
|
||||
|
||||
// FindDashboardsByOrganizationID returns a list of dashboards by organization.
|
||||
FindDashboardsByOrganizationID(ctx context.Context, orgID ID) ([]*Dashboard, int, error)
|
||||
|
||||
// FindDashboardsByOrganizationName returns a list of dashboards by organization.
|
||||
FindDashboardsByOrganizationName(ctx context.Context, org string) ([]*Dashboard, int, error)
|
||||
|
||||
// FindDashboards returns a list of dashboards that match filter and the total count of matching dashboards.
|
||||
// Additional options provide pagination & sorting.
|
||||
FindDashboards(ctx context.Context, filter DashboardFilter) ([]*Dashboard, int, error)
|
||||
|
||||
// CreateDashboard creates a new dashboard and sets b.ID with the new identifier.
|
||||
CreateDashboard(ctx context.Context, b *Dashboard) error
|
||||
|
||||
// UpdateDashboard updates a single dashboard with changeset.
|
||||
// Returns the new dashboard state after update.
|
||||
UpdateDashboard(ctx context.Context, id ID, upd DashboardUpdate) (*Dashboard, error)
|
||||
|
||||
// DeleteDashboard removes a dashboard by ID.
|
||||
DeleteDashboard(ctx context.Context, id ID) error
|
||||
|
||||
// AddDashboardCell adds a new cell to a dashboard.
|
||||
AddDashboardCell(ctx context.Context, dashboardID ID, cell *DashboardCell) error
|
||||
|
||||
// ReplaceDashboardCell replaces a single dashboard cell. It expects ID to be set on the provided cell.
|
||||
ReplaceDashboardCell(ctx context.Context, dashboardID ID, cell *DashboardCell) error
|
||||
|
||||
// RemoveDashboardCell removes a cell from a dashboard.
|
||||
RemoveDashboardCell(ctx context.Context, dashboardID, cellID ID) error
|
||||
}
|
||||
|
||||
// DashboardFilter represents a set of filter that restrict the returned results.
|
||||
type DashboardFilter struct {
|
||||
ID *ID
|
||||
OrganizationID *ID
|
||||
Organization *string
|
||||
}
|
||||
|
||||
// DashboardUpdate represents updates to a dashboard.
|
||||
// Only fields which are set are updated.
|
||||
type DashboardUpdate struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Dashboard represents all visual and query data for a dashboard
|
||||
type Dashboard struct {
|
||||
// TODO: add meta information fields like created_at, updated_at, created_by, etc
|
||||
ID ID `json:"id"`
|
||||
OrganizationID ID `json:"organizationID"`
|
||||
Organization string `json:"organization"`
|
||||
Name string `json:"name"`
|
||||
Cells []DashboardCell `json:"cells"`
|
||||
}
|
||||
|
||||
// DashboardCell holds positional and visual information for a cell.
|
||||
type DashboardCell struct {
|
||||
DashboardCellContents
|
||||
Visualization Visualization
|
||||
}
|
||||
|
||||
type DashboardCellContents struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
W int32 `json:"w"`
|
||||
H int32 `json:"h"`
|
||||
}
|
||||
|
||||
type Visualization interface {
|
||||
Visualization()
|
||||
}
|
||||
|
||||
func (c DashboardCell) MarshalJSON() ([]byte, error) {
|
||||
vis, err := MarshalVisualizationJSON(c.Visualization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(struct {
|
||||
DashboardCellContents
|
||||
Visualization json.RawMessage `json:"visualization"`
|
||||
}{
|
||||
DashboardCellContents: c.DashboardCellContents,
|
||||
Visualization: vis,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *DashboardCell) UnmarshalJSON(b []byte) error {
|
||||
if err := json.Unmarshal(b, &c.DashboardCellContents); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := UnmarshalVisualizationJSON(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Visualization = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: fill in with real visualization requirements
|
||||
type CommonVisualization struct {
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
func (CommonVisualization) Visualization() {}
|
||||
|
||||
func UnmarshalVisualizationJSON(b []byte) (Visualization, error) {
|
||||
var v struct {
|
||||
B json.RawMessage `json:"visualization"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(v.B, &t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vis Visualization
|
||||
switch t.Type {
|
||||
case "common":
|
||||
var qv CommonVisualization
|
||||
if err := json.Unmarshal(v.B, &qv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = qv
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type %v", t.Type)
|
||||
}
|
||||
|
||||
return vis, nil
|
||||
}
|
||||
|
||||
func MarshalVisualizationJSON(v Visualization) ([]byte, error) {
|
||||
var s interface{}
|
||||
switch vis := v.(type) {
|
||||
case CommonVisualization:
|
||||
s = struct {
|
||||
Type string `json:"type"`
|
||||
CommonVisualization
|
||||
}{
|
||||
Type: "common",
|
||||
CommonVisualization: vis,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type")
|
||||
}
|
||||
return json.Marshal(s)
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package platform_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/influxdata/platform"
|
||||
)
|
||||
|
||||
func TestDashboardCell_MarshalJSON(t *testing.T) {
|
||||
type args struct {
|
||||
cell platform.DashboardCell
|
||||
}
|
||||
type wants struct {
|
||||
json string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
args: args{
|
||||
cell: platform.DashboardCell{
|
||||
DashboardCellContents: platform.DashboardCellContents{
|
||||
ID: platform.ID("0"), // This ends up being id 30 encoded
|
||||
Name: "hello",
|
||||
X: 10,
|
||||
Y: 10,
|
||||
W: 100,
|
||||
H: 12,
|
||||
},
|
||||
Visualization: platform.CommonVisualization{
|
||||
Query: "SELECT * FROM foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
json: `
|
||||
{
|
||||
"id": "30",
|
||||
"name": "hello",
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
"w": 100,
|
||||
"h": 12,
|
||||
"visualization": {
|
||||
"type": "common",
|
||||
"query": "SELECT * FROM foo"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, err := json.MarshalIndent(tt.args.cell, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("error marshalling json")
|
||||
}
|
||||
|
||||
eq, err := jsonEqual(string(b), tt.wants.json)
|
||||
if err != nil {
|
||||
t.Fatalf("error marshalling json")
|
||||
}
|
||||
if !eq {
|
||||
t.Errorf("JSON did not match\nexpected:%s\ngot:\n%s\n", tt.wants.json, string(b))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEqual(s1, s2 string) (eq bool, err error) {
|
||||
var o1, o2 interface{}
|
||||
|
||||
if err = json.Unmarshal([]byte(s1), &o1); err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal([]byte(s2), &o2); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return cmp.Equal(o1, o2), nil
|
||||
}
|
|
@ -0,0 +1,714 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/influxdata/platform"
|
||||
kerrors "github.com/influxdata/platform/kit/errors"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// DashboardHandler represents an HTTP API handler for dashboards.
|
||||
type DashboardHandler struct {
|
||||
*httprouter.Router
|
||||
|
||||
DashboardService platform.DashboardService
|
||||
}
|
||||
|
||||
// NewDashboardHandler returns a new instance of DashboardHandler.
|
||||
func NewDashboardHandler() *DashboardHandler {
|
||||
h := &DashboardHandler{
|
||||
Router: httprouter.New(),
|
||||
}
|
||||
|
||||
h.HandlerFunc("POST", "/v1/dashboards", h.handlePostDashboard)
|
||||
h.HandlerFunc("GET", "/v1/dashboards", h.handleGetDashboards)
|
||||
h.HandlerFunc("GET", "/v1/dashboards/:id", h.handleGetDashboard)
|
||||
h.HandlerFunc("PATCH", "/v1/dashboards/:id", h.handlePatchDashboard)
|
||||
h.HandlerFunc("DELETE", "/v1/dashboards/:id", h.handleDeleteDashboard)
|
||||
|
||||
h.HandlerFunc("POST", "/v1/dashboards/:id/cells", h.handlePostDashboardCell)
|
||||
h.HandlerFunc("PUT", "/v1/dashboards/:id/cells/:cell_id", h.handlePutDashboardCell)
|
||||
h.HandlerFunc("DELETE", "/v1/dashboards/:id/cells/:cell_id", h.handleDeleteDashboardCell)
|
||||
return h
|
||||
}
|
||||
|
||||
// handlePostDashboard is the HTTP handler for the POST /v1/dashboards route.
|
||||
func (h *DashboardHandler) handlePostDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePostDashboardRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.DashboardService.CreateDashboard(ctx, req.Dashboard); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusCreated, req.Dashboard); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type postDashboardRequest struct {
|
||||
Dashboard *platform.Dashboard
|
||||
}
|
||||
|
||||
func decodePostDashboardRequest(ctx context.Context, r *http.Request) (*postDashboardRequest, error) {
|
||||
b := &platform.Dashboard{}
|
||||
if err := json.NewDecoder(r.Body).Decode(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &postDashboardRequest{
|
||||
Dashboard: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleGetDashboard is the HTTP handler for the GET /v1/dashboards/:id route.
|
||||
func (h *DashboardHandler) handleGetDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeGetDashboardRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := h.DashboardService.FindDashboardByID(ctx, req.DashboardID)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, b); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type getDashboardRequest struct {
|
||||
DashboardID platform.ID
|
||||
}
|
||||
|
||||
func decodeGetDashboardRequest(ctx context.Context, r *http.Request) (*getDashboardRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &getDashboardRequest{
|
||||
DashboardID: i,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// handleDeleteDashboard is the HTTP handler for the DELETE /v1/dashboards/:id route.
|
||||
func (h *DashboardHandler) handleDeleteDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeDeleteDashboardRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.DashboardService.DeleteDashboard(ctx, req.DashboardID); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
type deleteDashboardRequest struct {
|
||||
DashboardID platform.ID
|
||||
}
|
||||
|
||||
func decodeDeleteDashboardRequest(ctx context.Context, r *http.Request) (*deleteDashboardRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &deleteDashboardRequest{
|
||||
DashboardID: i,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// handleGetDashboards is the HTTP handler for the GET /v1/dashboards route.
|
||||
func (h *DashboardHandler) handleGetDashboards(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeGetDashboardsRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
bs, _, err := h.DashboardService.FindDashboards(ctx, req.filter)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, bs); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type getDashboardsRequest struct {
|
||||
filter platform.DashboardFilter
|
||||
}
|
||||
|
||||
func decodeGetDashboardsRequest(ctx context.Context, r *http.Request) (*getDashboardsRequest, error) {
|
||||
qp := r.URL.Query()
|
||||
req := &getDashboardsRequest{}
|
||||
|
||||
if id := qp.Get("orgID"); id != "" {
|
||||
req.filter.OrganizationID = &platform.ID{}
|
||||
if err := req.filter.OrganizationID.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if org := qp.Get("org"); org != "" {
|
||||
req.filter.Organization = &org
|
||||
}
|
||||
|
||||
if id := qp.Get("id"); id != "" {
|
||||
req.filter.ID = &platform.ID{}
|
||||
if err := req.filter.ID.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// handlePatchDashboard is the HTTP handler for the PATH /v1/dashboards route.
|
||||
func (h *DashboardHandler) handlePatchDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePatchDashboardRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := h.DashboardService.UpdateDashboard(ctx, req.DashboardID, req.Update)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, b); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type patchDashboardRequest struct {
|
||||
Update platform.DashboardUpdate
|
||||
DashboardID platform.ID
|
||||
}
|
||||
|
||||
func decodePatchDashboardRequest(ctx context.Context, r *http.Request) (*patchDashboardRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var upd platform.DashboardUpdate
|
||||
if err := json.NewDecoder(r.Body).Decode(&upd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &patchDashboardRequest{
|
||||
Update: upd,
|
||||
DashboardID: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handlePostDashboardCell is the HTTP handler for the POST /v1/dashboards/:id/cells route.
|
||||
func (h *DashboardHandler) handlePostDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePostDashboardCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.DashboardService.AddDashboardCell(ctx, req.DashboardID, req.DashboardCell); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusCreated, req.DashboardCell); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type postDashboardCellRequest struct {
|
||||
DashboardID platform.ID
|
||||
DashboardCell *platform.DashboardCell
|
||||
}
|
||||
|
||||
func decodePostDashboardCellRequest(ctx context.Context, r *http.Request) (*postDashboardCellRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &platform.DashboardCell{}
|
||||
if err := json.NewDecoder(r.Body).Decode(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &postDashboardCellRequest{
|
||||
DashboardCell: c,
|
||||
DashboardID: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handlePutDashboardCell is the HTTP handler for the PUT /v1/dashboards/:id/cells/:cell_id route.
|
||||
func (h *DashboardHandler) handlePutDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePutDashboardCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.DashboardService.ReplaceDashboardCell(ctx, req.DashboardID, req.Cell); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, req.Cell); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type putDashboardCellRequest struct {
|
||||
DashboardID platform.ID
|
||||
Cell *platform.DashboardCell
|
||||
}
|
||||
|
||||
func decodePutDashboardCellRequest(ctx context.Context, r *http.Request) (*putDashboardCellRequest, error) {
|
||||
req := &putDashboardCellRequest{}
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
if err := req.DashboardID.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cellID := params.ByName("cell_id")
|
||||
if cellID == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing cell_id")
|
||||
}
|
||||
var cid platform.ID
|
||||
if err := cid.DecodeFromString(cellID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Cell = &platform.DashboardCell{}
|
||||
if err := json.NewDecoder(r.Body).Decode(req.Cell); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(req.Cell.ID, cid) {
|
||||
return nil, fmt.Errorf("url cell_id does not match id on provided cell")
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// handleDeleteDashboardCell is the HTTP handler for the DELETE /v1/dashboards/:id/cells/:cell_id route.
|
||||
func (h *DashboardHandler) handleDeleteDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeDeleteDashboardCellRequest(ctx, r)
|
||||
if err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.DashboardService.RemoveDashboardCell(ctx, req.DashboardID, req.CellID); err != nil {
|
||||
kerrors.EncodeHTTP(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
type deleteDashboardCellRequest struct {
|
||||
DashboardID platform.ID
|
||||
CellID platform.ID
|
||||
}
|
||||
|
||||
func decodeDeleteDashboardCellRequest(ctx context.Context, r *http.Request) (*deleteDashboardCellRequest, error) {
|
||||
req := &deleteDashboardCellRequest{}
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
if err := req.DashboardID.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cellID := params.ByName("cell_id")
|
||||
if cellID == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing cell_id")
|
||||
}
|
||||
if err := req.CellID.DecodeFromString(cellID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
const (
|
||||
dashboardPath = "/v1/dashboards"
|
||||
)
|
||||
|
||||
// DashboardService connects to Influx via HTTP using tokens to manage dashboards
|
||||
type DashboardService struct {
|
||||
Addr string
|
||||
Token string
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// FindDashboardByID returns a single dashboard by ID.
|
||||
func (s *DashboardService) FindDashboardByID(ctx context.Context, id platform.ID) (*platform.Dashboard, error) {
|
||||
u, err := newURL(s.Addr, dashboardIDPath(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckError(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b platform.Dashboard
|
||||
if err := json.NewDecoder(resp.Body).Decode(&b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
// FindDashboardsByOrganizationID returns a list of dashboards that match filter and the total count of matching dashboards.
|
||||
func (s *DashboardService) FindDashboardsByOrganizationID(ctx context.Context, orgID platform.ID) ([]*platform.Dashboard, int, error) {
|
||||
return s.FindDashboards(ctx, platform.DashboardFilter{OrganizationID: &orgID})
|
||||
}
|
||||
|
||||
// FindDashboardsByOrganizationName returns a list of dashboards that match filter and the total count of matching dashboards.
|
||||
func (s *DashboardService) FindDashboardsByOrganizationName(ctx context.Context, org string) ([]*platform.Dashboard, int, error) {
|
||||
return s.FindDashboards(ctx, platform.DashboardFilter{Organization: &org})
|
||||
}
|
||||
|
||||
// FindDashboards returns a list of dashboards that match filter and the total count of matching dashboards.
|
||||
func (s *DashboardService) FindDashboards(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) {
|
||||
u, err := newURL(s.Addr, dashboardPath)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
if filter.OrganizationID != nil {
|
||||
query.Add("orgID", filter.OrganizationID.String())
|
||||
}
|
||||
if filter.Organization != nil {
|
||||
query.Add("org", *filter.Organization)
|
||||
}
|
||||
if filter.ID != nil {
|
||||
query.Add("id", filter.ID.String())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req.URL.RawQuery = query.Encode()
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if err := CheckError(resp); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var bs []*platform.Dashboard
|
||||
if err := json.NewDecoder(resp.Body).Decode(&bs); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return bs, len(bs), nil
|
||||
}
|
||||
|
||||
// CreateDashboard creates a new dashboard and sets b.ID with the new identifier.
|
||||
func (s *DashboardService) CreateDashboard(ctx context.Context, b *platform.Dashboard) error {
|
||||
u, err := newURL(s.Addr, dashboardPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
octets, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(jsternberg): Should this check for a 201 explicitly?
|
||||
if err := CheckError(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDashboard updates a single dashboard with changeset.
|
||||
// Returns the new dashboard state after update.
|
||||
func (s *DashboardService) UpdateDashboard(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) {
|
||||
u, err := newURL(s.Addr, dashboardIDPath(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
octets, err := json.Marshal(upd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(octets))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckError(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b platform.Dashboard
|
||||
if err := json.NewDecoder(resp.Body).Decode(&b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
// DeleteDashboard removes a dashboard by ID.
|
||||
func (s *DashboardService) DeleteDashboard(ctx context.Context, id platform.ID) error {
|
||||
u, err := newURL(s.Addr, dashboardIDPath(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CheckError(resp)
|
||||
}
|
||||
|
||||
// AddDashboardCell adds a new cell to a dashboard.
|
||||
func (s *DashboardService) AddDashboardCell(ctx context.Context, dashboardID platform.ID, cell *platform.DashboardCell) error {
|
||||
u, err := newURL(s.Addr, dashboardCellsPath(dashboardID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
octets, err := json.Marshal(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(jsternberg): Should this check for a 201 explicitly?
|
||||
if err := CheckError(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(cell); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceDashboardCell replaces a single dashboard cell. It expects ID to be set on the provided cell.
|
||||
func (s *DashboardService) ReplaceDashboardCell(ctx context.Context, dashboardID platform.ID, cell *platform.DashboardCell) error {
|
||||
u, err := newURL(s.Addr, dashboardCellPath(dashboardID, cell.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
octets, err := json.Marshal(cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(octets))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return CheckError(resp)
|
||||
}
|
||||
|
||||
// RemoveDashboardCell removes a cell from a dashboard.
|
||||
func (s *DashboardService) RemoveDashboardCell(ctx context.Context, dashboardID platform.ID, cellID platform.ID) error {
|
||||
u, err := newURL(s.Addr, dashboardCellPath(dashboardID, cellID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Authorization", s.Token)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CheckError(resp)
|
||||
}
|
||||
|
||||
func dashboardIDPath(id platform.ID) string {
|
||||
return path.Join(dashboardPath, id.String())
|
||||
}
|
||||
|
||||
func dashboardCellsPath(dashboardID platform.ID) string {
|
||||
// TODO: what to do about "cells" string
|
||||
return path.Join(dashboardIDPath(dashboardID), "cells")
|
||||
}
|
||||
|
||||
func dashboardCellPath(dashboardID, cellID platform.ID) string {
|
||||
return path.Join(dashboardCellsPath(dashboardID), cellID.String())
|
||||
}
|
|
@ -13,6 +13,7 @@ type PlatformHandler struct {
|
|||
UserHandler *UserHandler
|
||||
OrgHandler *OrgHandler
|
||||
AuthorizationHandler *AuthorizationHandler
|
||||
DashboardHandler *DashboardHandler
|
||||
}
|
||||
|
||||
func setCORSResponseHeaders(w nethttp.ResponseWriter, r *nethttp.Request) {
|
||||
|
@ -55,5 +56,10 @@ func (h *PlatformHandler) ServeHTTP(w nethttp.ResponseWriter, r *nethttp.Request
|
|||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/v1/dashboards") {
|
||||
h.DashboardHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
nethttp.NotFound(w, r)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue