influxdb/bolt/dashboard.go

368 lines
9.1 KiB
Go

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)
})
}