Labels v2 (#11198)
* WIP first-class endpoint for labels * WIP * WIP * add all the boilerplate * fix boltdb * fix http label test * fix test * WIP * fix test failures * reenable all tests * add failing test for label mappings * add label mapping bolt bucket * implement resource -> label mapping fn * add inmem label mapping * delete label mappings * remove unused stuff * add missing functions * add POST endpoint for labels * add GET route for label * delete label endpoint * add label patch endpoint * remove commented code * add label service to api handler * update comment * add FindLabelByID test * use platform.Error * change path name * formatting * remove label patch from swagger * avoid potential orphaned mapping bug * guard against creating label mappings from nonexistent labels * update swagger * update swagger * update swagger * fix swagger indentation * update swaggerpull/11301/head
parent
220c66dc9a
commit
8a1d7ba1ba
|
@ -602,11 +602,6 @@ func (c *Client) deleteBucket(ctx context.Context, tx *bolt.Tx, id platform.ID)
|
|||
}
|
||||
}
|
||||
|
||||
if err := c.deleteLabels(ctx, tx, platform.LabelFilter{ResourceID: id}); err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -823,13 +823,6 @@ func (c *Client) deleteDashboard(ctx context.Context, tx *bolt.Tx, id platform.I
|
|||
}
|
||||
}
|
||||
|
||||
err = c.deleteLabels(ctx, tx, platform.LabelFilter{ResourceID: id})
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(desa): add DeleteKeyValueLog method and use it here.
|
||||
err = c.deleteUserResourceMappings(ctx, tx, platform.UserResourceMappingFilter{
|
||||
ResourceID: id,
|
||||
|
|
396
bolt/label.go
396
bolt/label.go
|
@ -1,47 +1,87 @@
|
|||
package bolt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
platform "github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
var (
|
||||
labelBucket = []byte("labelsv1")
|
||||
labelBucket = []byte("labelsv1")
|
||||
labelMappingBucket = []byte("labelmappingsv1")
|
||||
)
|
||||
|
||||
func (c *Client) initializeLabels(ctx context.Context, tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucketIfNotExists([]byte(labelBucket)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.CreateBucketIfNotExists([]byte(labelMappingBucket)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindLabelByID finds a label by its ID
|
||||
func (c *Client) FindLabelByID(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
var l *platform.Label
|
||||
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
label, pe := c.findLabelByID(ctx, tx, id)
|
||||
if pe != nil {
|
||||
return pe
|
||||
}
|
||||
l = label
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, &platform.Error{
|
||||
Op: getOp(platform.OpFindLabelByID),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (c *Client) findLabelByID(ctx context.Context, tx *bolt.Tx, id platform.ID) (*platform.Label, *platform.Error) {
|
||||
encodedID, err := id.Encode()
|
||||
if err != nil {
|
||||
return nil, &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
var l platform.Label
|
||||
v := tx.Bucket(labelBucket).Get(encodedID)
|
||||
|
||||
if len(v) == 0 {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Msg: "label not found",
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(v, &l); err != nil {
|
||||
return nil, &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
func filterLabelsFn(filter platform.LabelFilter) func(l *platform.Label) bool {
|
||||
return func(label *platform.Label) bool {
|
||||
return (filter.Name == "" || (filter.Name == label.Name)) &&
|
||||
(!filter.ResourceID.Valid() || (filter.ResourceID == label.ResourceID))
|
||||
return (filter.Name == "" || (filter.Name == label.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// func (c *Client) findLabel(ctx context.Context, tx *bolt.Tx, resourceID platform.ID, name string) (*platform.Label, error) {
|
||||
// key, err := labelKey(&platform.Label{ResourceID: resourceID, Name: name})
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// l := &platform.Label{}
|
||||
// v := tx.Bucket(labelBucket).Get(key)
|
||||
// if err := json.Unmarshal(v, l); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// return l, nil
|
||||
// }
|
||||
|
||||
// FindLabels returns a list of labels that match a filter.
|
||||
func (c *Client) FindLabels(ctx context.Context, filter platform.LabelFilter, opt ...platform.FindOptions) ([]*platform.Label, error) {
|
||||
ls := []*platform.Label{}
|
||||
|
@ -78,42 +118,156 @@ func (c *Client) findLabels(ctx context.Context, tx *bolt.Tx, filter platform.La
|
|||
return ls, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateLabel(ctx context.Context, l *platform.Label) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.createLabel(ctx, tx, l)
|
||||
})
|
||||
// func filterMappingsFn(filter platform.LabelMappingFilter) func(m *platform.LabelMapping) bool {
|
||||
// return func(mapping *platform.LabelMapping) bool {
|
||||
// return (filter.ResourceID.String() == mapping.ResourceID.String()) &&
|
||||
// (filter.LabelID == nil || filter.LabelID == mapping.LabelID)
|
||||
// }
|
||||
// }
|
||||
|
||||
func decodeLabelMappingKey(key []byte) (resourceID platform.ID, labelID platform.ID, err error) {
|
||||
if len(key) != 2*platform.IDLength {
|
||||
return 0, 0, &platform.Error{Code: platform.EInvalid, Msg: "malformed label mapping key (please report this error)"}
|
||||
}
|
||||
|
||||
if err := (&resourceID).Decode(key[:platform.IDLength]); err != nil {
|
||||
return 0, 0, &platform.Error{Code: platform.EInvalid, Msg: "bad resource id", Err: platform.ErrInvalidID}
|
||||
}
|
||||
|
||||
if err := (&labelID).Decode(key[platform.IDLength:]); err != nil {
|
||||
return 0, 0, &platform.Error{Code: platform.EInvalid, Msg: "bad label id", Err: platform.ErrInvalidID}
|
||||
}
|
||||
|
||||
return resourceID, labelID, nil
|
||||
}
|
||||
|
||||
func (c *Client) createLabel(ctx context.Context, tx *bolt.Tx, l *platform.Label) error {
|
||||
unique := c.uniqueLabel(ctx, tx, l)
|
||||
func (c *Client) FindResourceLabels(ctx context.Context, filter platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
if !filter.ResourceID.Valid() {
|
||||
return nil, &platform.Error{Code: platform.EInvalid, Msg: "filter requires a valid resource id", Err: platform.ErrInvalidID}
|
||||
}
|
||||
|
||||
if !unique {
|
||||
return &platform.Error{
|
||||
Code: platform.EConflict,
|
||||
Op: getOp(platform.OpCreateLabel),
|
||||
Msg: fmt.Sprintf("label %s already exists", l.Name),
|
||||
ls := []*platform.Label{}
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
cur := tx.Bucket(labelMappingBucket).Cursor()
|
||||
prefix, err := filter.ResourceID.Encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, _ := cur.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = cur.Next() {
|
||||
_, id, err := decodeLabelMappingKey(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l, err := c.findLabelByID(ctx, tx, id)
|
||||
if l == nil && err != nil {
|
||||
// TODO(jm): return error instead of continuing once orphaned mappings are fixed
|
||||
// (see https://github.com/influxdata/influxdb/issues/11278)
|
||||
continue
|
||||
}
|
||||
|
||||
ls = append(ls, l)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, &platform.Error{
|
||||
Err: err,
|
||||
Op: getOp(platform.OpFindLabelMapping),
|
||||
}
|
||||
}
|
||||
|
||||
v, err := json.Marshal(l)
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
// CreateLabelMapping creates a new mapping between a resource and a label.
|
||||
func (c *Client) CreateLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
_, err := c.FindLabelByID(ctx, *m.LabelID)
|
||||
if err != nil {
|
||||
return err
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
Op: getOp(platform.OpCreateLabel),
|
||||
}
|
||||
}
|
||||
|
||||
key, err := labelKey(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.putLabelMapping(ctx, tx, m)
|
||||
})
|
||||
|
||||
if err := tx.Bucket(labelBucket).Put(key, v); err != nil {
|
||||
return err
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
Op: getOp(platform.OpCreateLabel),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func labelKey(l *platform.Label) ([]byte, error) {
|
||||
encodedResourceID, err := l.ResourceID.Encode()
|
||||
// DeleteLabelMapping deletes a label mapping.
|
||||
func (c *Client) DeleteLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
err := c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.deleteLabelMapping(ctx, tx, m)
|
||||
})
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Op: getOp(platform.OpDeleteLabelMapping),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) deleteLabelMapping(ctx context.Context, tx *bolt.Tx, m *platform.LabelMapping) error {
|
||||
key, err := labelMappingKey(m)
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Bucket(labelMappingBucket).Delete(key); err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLabel creates a new label.
|
||||
func (c *Client) CreateLabel(ctx context.Context, l *platform.Label) error {
|
||||
err := c.db.Update(func(tx *bolt.Tx) error {
|
||||
l.ID = c.IDGenerator.ID()
|
||||
|
||||
return c.putLabel(ctx, tx, l)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
Op: getOp(platform.OpCreateLabel),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutLabel creates a label from the provided struct, without generating a new ID.
|
||||
func (c *Client) PutLabel(ctx context.Context, l *platform.Label) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
pe := c.putLabel(ctx, tx, l)
|
||||
if pe != nil {
|
||||
err = pe
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func labelMappingKey(m *platform.LabelMapping) ([]byte, error) {
|
||||
lid, err := m.LabelID.Encode()
|
||||
if err != nil {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
|
@ -121,9 +275,17 @@ func labelKey(l *platform.Label) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
key := make([]byte, len(encodedResourceID)+len(l.Name))
|
||||
copy(key, encodedResourceID)
|
||||
copy(key[len(encodedResourceID):], l.Name)
|
||||
rid, err := m.ResourceID.Encode()
|
||||
if err != nil {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
key := make([]byte, len(rid)+len(lid))
|
||||
copy(key, rid)
|
||||
copy(key[len(rid):], lid)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
@ -143,21 +305,11 @@ func (c *Client) forEachLabel(ctx context.Context, tx *bolt.Tx, fn func(*platfor
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) uniqueLabel(ctx context.Context, tx *bolt.Tx, l *platform.Label) bool {
|
||||
key, err := labelKey(l)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
v := tx.Bucket(labelBucket).Get(key)
|
||||
return len(v) == 0
|
||||
}
|
||||
|
||||
// UpdateLabel updates a label.
|
||||
func (c *Client) UpdateLabel(ctx context.Context, l *platform.Label, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
func (c *Client) UpdateLabel(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
var label *platform.Label
|
||||
err := c.db.Update(func(tx *bolt.Tx) error {
|
||||
labelResponse, pe := c.updateLabel(ctx, tx, l, upd)
|
||||
labelResponse, pe := c.updateLabel(ctx, tx, id, upd)
|
||||
if pe != nil {
|
||||
return &platform.Error{
|
||||
Err: pe,
|
||||
|
@ -171,19 +323,11 @@ func (c *Client) UpdateLabel(ctx context.Context, l *platform.Label, upd platfor
|
|||
return label, err
|
||||
}
|
||||
|
||||
func (c *Client) updateLabel(ctx context.Context, tx *bolt.Tx, l *platform.Label, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
ls, err := c.findLabels(ctx, tx, platform.LabelFilter{Name: l.Name, ResourceID: l.ResourceID})
|
||||
func (c *Client) updateLabel(ctx context.Context, tx *bolt.Tx, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
label, err := c.findLabelByID(ctx, tx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ls) == 0 {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Err: platform.ErrLabelNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
label := ls[0]
|
||||
|
||||
if label.Properties == nil {
|
||||
label.Properties = make(map[string]string)
|
||||
|
@ -222,12 +366,50 @@ func (c *Client) putLabel(ctx context.Context, tx *bolt.Tx, l *platform.Label) e
|
|||
}
|
||||
}
|
||||
|
||||
key, pe := labelKey(l)
|
||||
if pe != nil {
|
||||
return pe
|
||||
encodedID, err := l.ID.Encode()
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Bucket(labelBucket).Put(key, v); err != nil {
|
||||
if err := tx.Bucket(labelBucket).Put(encodedID, v); err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutLabelMapping writes a label mapping to boltdb
|
||||
func (c *Client) PutLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
pe := c.putLabelMapping(ctx, tx, m)
|
||||
if pe != nil {
|
||||
err = pe
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) putLabelMapping(ctx context.Context, tx *bolt.Tx, m *platform.LabelMapping) error {
|
||||
v, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
key, err := labelMappingKey(m)
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Bucket(labelMappingBucket).Put(key, v); err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
}
|
||||
|
@ -237,46 +419,50 @@ func (c *Client) putLabel(ctx context.Context, tx *bolt.Tx, l *platform.Label) e
|
|||
}
|
||||
|
||||
// DeleteLabel deletes a label.
|
||||
func (c *Client) DeleteLabel(ctx context.Context, l platform.Label) error {
|
||||
return c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.deleteLabel(ctx, tx, platform.LabelFilter{Name: l.Name, ResourceID: l.ResourceID})
|
||||
func (c *Client) DeleteLabel(ctx context.Context, id platform.ID) error {
|
||||
err := c.db.Update(func(tx *bolt.Tx) error {
|
||||
return c.deleteLabel(ctx, tx, id)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) deleteLabel(ctx context.Context, tx *bolt.Tx, filter platform.LabelFilter) error {
|
||||
ls, err := c.findLabels(ctx, tx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ls) == 0 {
|
||||
return &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: getOp(platform.OpDeleteLabel),
|
||||
Err: platform.ErrLabelNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
key, err := labelKey(ls[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Bucket(labelBucket).Delete(key)
|
||||
}
|
||||
|
||||
func (c *Client) deleteLabels(ctx context.Context, tx *bolt.Tx, filter platform.LabelFilter) error {
|
||||
ls, err := c.findLabels(ctx, tx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range ls {
|
||||
key, err := labelKey(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tx.Bucket(labelBucket).Delete(key); err != nil {
|
||||
return err
|
||||
Op: getOp(platform.OpDeleteLabel),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) deleteLabel(ctx context.Context, tx *bolt.Tx, id platform.ID) error {
|
||||
_, err := c.findLabelByID(ctx, tx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encodedID, idErr := id.Encode()
|
||||
if idErr != nil {
|
||||
return &platform.Error{
|
||||
Err: idErr,
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Bucket(labelBucket).Delete(encodedID)
|
||||
}
|
||||
|
||||
// func (c *Client) deleteLabels(ctx context.Context, tx *bolt.Tx, filter platform.LabelFilter) error {
|
||||
// ls, err := c.findLabels(ctx, tx, filter)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// for _, l := range ls {
|
||||
// encodedID, idErr := l.ID.Encode()
|
||||
// if idErr != nil {
|
||||
// return &platform.Error{
|
||||
// Err: idErr,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if err = tx.Bucket(labelBucket).Delete(encodedID); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
|
|
@ -14,17 +14,25 @@ func initLabelService(f platformtesting.LabelFields, t *testing.T) (platform.Lab
|
|||
if err != nil {
|
||||
t.Fatalf("failed to create new bolt client: %v", err)
|
||||
}
|
||||
c.IDGenerator = f.IDGenerator
|
||||
|
||||
ctx := context.Background()
|
||||
for _, l := range f.Labels {
|
||||
if err := c.CreateLabel(ctx, l); err != nil {
|
||||
t.Fatalf("failed to populate labels")
|
||||
if err := c.PutLabel(ctx, l); err != nil {
|
||||
t.Fatalf("failed to populate labels: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range f.Mappings {
|
||||
if err := c.PutLabelMapping(ctx, m); err != nil {
|
||||
t.Fatalf("failed to populate label mappings: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return c, bolt.OpPrefix, func() {
|
||||
defer closeFn()
|
||||
for _, l := range f.Labels {
|
||||
if err := c.DeleteLabel(ctx, *l); err != nil {
|
||||
if err := c.DeleteLabel(ctx, l.ID); err != nil {
|
||||
t.Logf("failed to remove label: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ type APIHandler struct {
|
|||
OrgHandler *OrgHandler
|
||||
AuthorizationHandler *AuthorizationHandler
|
||||
DashboardHandler *DashboardHandler
|
||||
LabelHandler *LabelHandler
|
||||
AssetHandler *AssetHandler
|
||||
ChronografHandler *ChronografHandler
|
||||
ScraperHandler *ScraperHandler
|
||||
|
@ -81,6 +82,9 @@ func NewAPIHandler(b *APIBackend) *APIHandler {
|
|||
h.BucketHandler.OrganizationService = b.OrganizationService
|
||||
h.BucketHandler.BucketOperationLogService = b.BucketOperationLogService
|
||||
|
||||
h.LabelHandler = NewLabelHandler()
|
||||
h.LabelHandler.LabelService = b.LabelService
|
||||
|
||||
h.OrgHandler = NewOrgHandler(b.UserResourceMappingService, b.LabelService, b.UserService)
|
||||
h.OrgHandler.OrganizationService = authorizer.NewOrgService(b.OrganizationService)
|
||||
h.OrgHandler.OrganizationOperationLogService = b.OrganizationOperationLogService
|
||||
|
@ -159,6 +163,7 @@ var apiLinks = map[string]interface{}{
|
|||
"external": map[string]string{
|
||||
"statusFeed": "https://www.influxdata.com/feed/json",
|
||||
},
|
||||
"labels": "/api/v2/labels",
|
||||
"macros": "/api/v2/macros",
|
||||
"me": "/api/v2/me",
|
||||
"orgs": "/api/v2/orgs",
|
||||
|
@ -232,6 +237,11 @@ func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/v2/labels") {
|
||||
h.LabelHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/v2/users") {
|
||||
h.UserHandler.ServeHTTP(w, r)
|
||||
return
|
||||
|
|
|
@ -31,15 +31,15 @@ type BucketHandler struct {
|
|||
}
|
||||
|
||||
const (
|
||||
bucketsPath = "/api/v2/buckets"
|
||||
bucketsIDPath = "/api/v2/buckets/:id"
|
||||
bucketsIDLogPath = "/api/v2/buckets/:id/log"
|
||||
bucketsIDMembersPath = "/api/v2/buckets/:id/members"
|
||||
bucketsIDMembersIDPath = "/api/v2/buckets/:id/members/:userID"
|
||||
bucketsIDOwnersPath = "/api/v2/buckets/:id/owners"
|
||||
bucketsIDOwnersIDPath = "/api/v2/buckets/:id/owners/:userID"
|
||||
bucketsIDLabelsPath = "/api/v2/buckets/:id/labels"
|
||||
bucketsIDLabelsNamePath = "/api/v2/buckets/:id/labels/:name"
|
||||
bucketsPath = "/api/v2/buckets"
|
||||
bucketsIDPath = "/api/v2/buckets/:id"
|
||||
bucketsIDLogPath = "/api/v2/buckets/:id/log"
|
||||
bucketsIDMembersPath = "/api/v2/buckets/:id/members"
|
||||
bucketsIDMembersIDPath = "/api/v2/buckets/:id/members/:userID"
|
||||
bucketsIDOwnersPath = "/api/v2/buckets/:id/owners"
|
||||
bucketsIDOwnersIDPath = "/api/v2/buckets/:id/owners/:userID"
|
||||
bucketsIDLabelsPath = "/api/v2/buckets/:id/labels"
|
||||
bucketsIDLabelsIDPath = "/api/v2/buckets/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewBucketHandler returns a new instance of BucketHandler.
|
||||
|
@ -70,8 +70,7 @@ func NewBucketHandler(mappingService platform.UserResourceMappingService, labelS
|
|||
|
||||
h.HandlerFunc("GET", bucketsIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
||||
h.HandlerFunc("POST", bucketsIDLabelsPath, newPostLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", bucketsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("PATCH", bucketsIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", bucketsIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
||||
|
||||
return h
|
||||
}
|
||||
|
@ -220,7 +219,7 @@ type bucketsResponse struct {
|
|||
func newBucketsResponse(ctx context.Context, opts platform.FindOptions, f platform.BucketFilter, bs []*platform.Bucket, labelService platform.LabelService) *bucketsResponse {
|
||||
rs := make([]*bucketResponse, 0, len(bs))
|
||||
for _, b := range bs {
|
||||
labels, _ := labelService.FindLabels(ctx, platform.LabelFilter{ResourceID: b.ID})
|
||||
labels, _ := labelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: b.ID})
|
||||
rs = append(rs, newBucketResponse(b, labels))
|
||||
}
|
||||
return &bucketsResponse{
|
||||
|
@ -305,7 +304,7 @@ func (h *BucketHandler) handleGetBucket(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: b.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: b.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
@ -436,7 +435,7 @@ func decodeGetBucketsRequest(ctx context.Context, r *http.Request) (*getBucketsR
|
|||
return req, nil
|
||||
}
|
||||
|
||||
// handlePatchBucket is the HTTP handler for the PATH /api/v2/buckets route.
|
||||
// handlePatchBucket is the HTTP handler for the PATCH /api/v2/buckets route.
|
||||
func (h *BucketHandler) handlePatchBucket(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
|
@ -452,7 +451,7 @@ func (h *BucketHandler) handlePatchBucket(w http.ResponseWriter, r *http.Request
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: b.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: b.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
|
|
@ -60,11 +60,11 @@ func TestService_handleGetBuckets(t *testing.T) {
|
|||
},
|
||||
},
|
||||
&mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, f platform.LabelFilter) ([]*platform.Label, error) {
|
||||
FindResourceLabelsFn: func(ctx context.Context, f platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
labels := []*platform.Label{
|
||||
{
|
||||
ResourceID: f.ResourceID,
|
||||
Name: "label",
|
||||
ID: platformtesting.MustIDBase16("fc3dc670a4be9b9a"),
|
||||
Name: "label",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -102,7 +102,7 @@ func TestService_handleGetBuckets(t *testing.T) {
|
|||
"retentionRules": [{"type": "expire", "everySeconds": 2}],
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "0b501e7e557ab1ed",
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
|
@ -123,7 +123,7 @@ func TestService_handleGetBuckets(t *testing.T) {
|
|||
"retentionRules": [{"type": "expire", "everySeconds": 86400}],
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "c0175f0077a77005",
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
|
|
|
@ -41,7 +41,7 @@ const (
|
|||
dashboardsIDOwnersPath = "/api/v2/dashboards/:id/owners"
|
||||
dashboardsIDOwnersIDPath = "/api/v2/dashboards/:id/owners/:userID"
|
||||
dashboardsIDLabelsPath = "/api/v2/dashboards/:id/labels"
|
||||
dashboardsIDLabelsNamePath = "/api/v2/dashboards/:id/labels/:name"
|
||||
dashboardsIDLabelsIDPath = "/api/v2/dashboards/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewDashboardHandler returns a new instance of DashboardHandler.
|
||||
|
@ -80,8 +80,7 @@ func NewDashboardHandler(mappingService platform.UserResourceMappingService, lab
|
|||
|
||||
h.HandlerFunc("GET", dashboardsIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
||||
h.HandlerFunc("POST", dashboardsIDLabelsPath, newPostLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", dashboardsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("PATCH", dashboardsIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", dashboardsIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
||||
|
||||
return h
|
||||
}
|
||||
|
@ -332,7 +331,7 @@ func newGetDashboardsResponse(ctx context.Context, dashboards []*platform.Dashbo
|
|||
|
||||
for _, dashboard := range dashboards {
|
||||
if dashboard != nil {
|
||||
labels, _ := labelService.FindLabels(ctx, platform.LabelFilter{ResourceID: dashboard.ID})
|
||||
labels, _ := labelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: dashboard.ID})
|
||||
res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard, labels))
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +389,7 @@ func (h *DashboardHandler) handleGetDashboard(w http.ResponseWriter, r *http.Req
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: dashboard.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: dashboard.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
@ -542,7 +541,7 @@ func (h *DashboardHandler) handlePatchDashboard(w http.ResponseWriter, r *http.R
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: dashboard.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: dashboard.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
|
|
@ -76,11 +76,11 @@ func TestService_handleGetDashboards(t *testing.T) {
|
|||
},
|
||||
},
|
||||
&mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, f platform.LabelFilter) ([]*platform.Label, error) {
|
||||
FindResourceLabelsFn: func(ctx context.Context, f platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
labels := []*platform.Label{
|
||||
{
|
||||
ResourceID: f.ResourceID,
|
||||
Name: "label",
|
||||
ID: platformtesting.MustIDBase16("fc3dc670a4be9b9a"),
|
||||
Name: "label",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -107,7 +107,7 @@ func TestService_handleGetDashboards(t *testing.T) {
|
|||
"description": "oh hello there!",
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "da7aba5e5d81e550",
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
|
@ -148,7 +148,7 @@ func TestService_handleGetDashboards(t *testing.T) {
|
|||
"description": "",
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "0ca2204eca2204e0",
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
|
@ -184,7 +184,7 @@ func TestService_handleGetDashboards(t *testing.T) {
|
|||
},
|
||||
},
|
||||
&mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, f platform.LabelFilter) ([]*platform.Label, error) {
|
||||
FindResourceLabelsFn: func(ctx context.Context, f platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
return []*platform.Label{}, nil
|
||||
},
|
||||
},
|
||||
|
@ -231,11 +231,11 @@ func TestService_handleGetDashboards(t *testing.T) {
|
|||
},
|
||||
},
|
||||
&mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, f platform.LabelFilter) ([]*platform.Label, error) {
|
||||
FindResourceLabelsFn: func(ctx context.Context, f platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
labels := []*platform.Label{
|
||||
{
|
||||
ResourceID: f.ResourceID,
|
||||
Name: "label",
|
||||
ID: platformtesting.MustIDBase16("fc3dc670a4be9b9a"),
|
||||
Name: "label",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -267,16 +267,16 @@ func TestService_handleGetDashboards(t *testing.T) {
|
|||
"meta": {
|
||||
"createdAt": "2009-11-10T23:00:00Z",
|
||||
"updatedAt": "2009-11-11T00:00:00Z"
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "da7aba5e5d81e550",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"cells": [
|
||||
{
|
||||
"id": "da7aba5e5d81e550",
|
||||
|
|
|
@ -8,28 +8,268 @@ import (
|
|||
"net/http"
|
||||
"path"
|
||||
|
||||
plat "github.com/influxdata/influxdb"
|
||||
platform "github.com/influxdata/influxdb"
|
||||
"github.com/influxdata/influxdb/kit/errors"
|
||||
kerrors "github.com/influxdata/influxdb/kit/errors"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// LabelHandler represents an HTTP API handler for labels
|
||||
type LabelHandler struct {
|
||||
*httprouter.Router
|
||||
|
||||
Logger *zap.Logger
|
||||
|
||||
LabelService platform.LabelService
|
||||
}
|
||||
|
||||
const (
|
||||
labelsPath = "/api/v2/labels"
|
||||
labelsIDPath = "/api/v2/labels/:id"
|
||||
)
|
||||
|
||||
// NewLabelHandler returns a new instance of LabelHandler
|
||||
func NewLabelHandler() *LabelHandler {
|
||||
h := &LabelHandler{
|
||||
Router: NewRouter(),
|
||||
Logger: zap.NewNop(),
|
||||
}
|
||||
|
||||
h.HandlerFunc("POST", labelsPath, h.handlePostLabel)
|
||||
h.HandlerFunc("GET", labelsPath, h.handleGetLabels)
|
||||
|
||||
h.HandlerFunc("GET", labelsIDPath, h.handleGetLabel)
|
||||
h.HandlerFunc("PATCH", labelsIDPath, h.handlePatchLabel)
|
||||
h.HandlerFunc("DELETE", labelsIDPath, h.handleDeleteLabel)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// handlePostLabel is the HTTP handler for the POST /api/v2/labels route.
|
||||
func (h *LabelHandler) handlePostLabel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePostLabelRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.LabelService.CreateLabel(ctx, req.Label); err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusCreated, newLabelResponse(req.Label)); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type postLabelRequest struct {
|
||||
Label *platform.Label
|
||||
}
|
||||
|
||||
func (b postLabelRequest) Validate() error {
|
||||
if b.Label.Name == "" {
|
||||
return &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "label requires a name",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodePostLabelRequest(ctx context.Context, r *http.Request) (*postLabelRequest, error) {
|
||||
l := &platform.Label{}
|
||||
if err := json.NewDecoder(r.Body).Decode(l); err != nil {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "unable to decode label request",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
req := &postLabelRequest{
|
||||
Label: l,
|
||||
}
|
||||
|
||||
return req, req.Validate()
|
||||
}
|
||||
|
||||
// handleGetLabels is the HTTP handler for the GET /api/v2/labels route.
|
||||
func (h *LabelHandler) handleGetLabels(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
err = encodeResponse(ctx, w, http.StatusOK, newLabelsResponse(labels))
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetLabel is the HTTP handler for the GET /api/v2/labels/id route.
|
||||
func (h *LabelHandler) handleGetLabel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeGetLabelRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
l, err := h.LabelService.FindLabelByID(ctx, req.LabelID)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newLabelResponse(l)); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type getLabelRequest struct {
|
||||
LabelID platform.ID
|
||||
}
|
||||
|
||||
func decodeGetLabelRequest(ctx context.Context, r *http.Request) (*getLabelRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "label id is not valid",
|
||||
}
|
||||
}
|
||||
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &getLabelRequest{
|
||||
LabelID: i,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// handleDeleteLabel is the HTTP handler for the DELETE /api/v2/labels/:id route.
|
||||
func (h *LabelHandler) handleDeleteLabel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeDeleteLabelRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.LabelService.DeleteLabel(ctx, req.LabelID); err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
type deleteLabelRequest struct {
|
||||
LabelID platform.ID
|
||||
}
|
||||
|
||||
func decodeDeleteLabelRequest(ctx context.Context, r *http.Request) (*deleteLabelRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, errors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &deleteLabelRequest{
|
||||
LabelID: i,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// handlePatchLabel is the HTTP handler for the PATCH /api/v2/labels route.
|
||||
func (h *LabelHandler) handlePatchLabel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePatchLabelRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
l, err := h.LabelService.UpdateLabel(ctx, req.LabelID, req.Update)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newLabelResponse(l)); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type patchLabelRequest struct {
|
||||
Update platform.LabelUpdate
|
||||
LabelID platform.ID
|
||||
}
|
||||
|
||||
func decodePatchLabelRequest(ctx context.Context, r *http.Request) (*patchLabelRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, errors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upd := &platform.LabelUpdate{}
|
||||
if err := json.NewDecoder(r.Body).Decode(upd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &patchLabelRequest{
|
||||
Update: *upd,
|
||||
LabelID: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LabelService connects to Influx via HTTP using tokens to manage labels
|
||||
type LabelService struct {
|
||||
Addr string
|
||||
Token string
|
||||
InsecureSkipVerify bool
|
||||
BasePath string
|
||||
OpPrefix string
|
||||
}
|
||||
|
||||
type labelResponse struct {
|
||||
Links map[string]string `json:"links"`
|
||||
Label plat.Label `json:"label"`
|
||||
Label platform.Label `json:"label"`
|
||||
}
|
||||
|
||||
// TODO: remove "dashboard" from this
|
||||
func newLabelResponse(l *plat.Label) *labelResponse {
|
||||
func newLabelResponse(l *platform.Label) *labelResponse {
|
||||
return &labelResponse{
|
||||
Links: map[string]string{
|
||||
"resource": fmt.Sprintf("/api/v2/%ss/%s", "dashboard", l.ResourceID),
|
||||
"self": fmt.Sprintf("/api/v2/labels/%s", l.ID),
|
||||
},
|
||||
Label: *l,
|
||||
}
|
||||
|
@ -37,21 +277,20 @@ func newLabelResponse(l *plat.Label) *labelResponse {
|
|||
|
||||
type labelsResponse struct {
|
||||
Links map[string]string `json:"links"`
|
||||
Labels []*plat.Label `json:"labels"`
|
||||
Labels []*platform.Label `json:"labels"`
|
||||
}
|
||||
|
||||
func newLabelsResponse(opts plat.FindOptions, f plat.LabelFilter, ls []*plat.Label) *labelsResponse {
|
||||
// TODO: Remove "dashboard" from this
|
||||
func newLabelsResponse(ls []*platform.Label) *labelsResponse {
|
||||
return &labelsResponse{
|
||||
Links: map[string]string{
|
||||
"resource": fmt.Sprintf("/api/v2/%ss/%s", "dashboard", f.ResourceID),
|
||||
"self": fmt.Sprintf("/api/v2/labels"),
|
||||
},
|
||||
Labels: ls,
|
||||
}
|
||||
}
|
||||
|
||||
// newGetLabelsHandler returns a handler func for a GET to /labels endpoints
|
||||
func newGetLabelsHandler(s plat.LabelService) http.HandlerFunc {
|
||||
func newGetLabelsHandler(s platform.LabelService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
|
@ -61,14 +300,13 @@ func newGetLabelsHandler(s plat.LabelService) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
opts := plat.FindOptions{}
|
||||
labels, err := s.FindLabels(ctx, req.filter)
|
||||
labels, err := s.FindResourceLabels(ctx, req.filter)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newLabelsResponse(opts, req.filter, labels)); err != nil {
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newLabelsResponse(labels)); err != nil {
|
||||
// TODO: this can potentially result in calling w.WriteHeader multiple times, we need to pass a logger in here
|
||||
// some how. This isn't as simple as simply passing in a logger to this function since the time that this function
|
||||
// is called is distinct from the time that a potential logger is set.
|
||||
|
@ -79,11 +317,10 @@ func newGetLabelsHandler(s plat.LabelService) http.HandlerFunc {
|
|||
}
|
||||
|
||||
type getLabelsRequest struct {
|
||||
filter plat.LabelFilter
|
||||
filter platform.LabelMappingFilter
|
||||
}
|
||||
|
||||
func decodeGetLabelsRequest(ctx context.Context, r *http.Request) (*getLabelsRequest, error) {
|
||||
qp := r.URL.Query()
|
||||
req := &getLabelsRequest{}
|
||||
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
|
@ -92,41 +329,43 @@ func decodeGetLabelsRequest(ctx context.Context, r *http.Request) (*getLabelsReq
|
|||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var i plat.ID
|
||||
var i platform.ID
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.filter.ResourceID = i
|
||||
|
||||
if name := qp.Get("name"); name != "" {
|
||||
req.filter.Name = name
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// newPostLabelHandler returns a handler func for a POST to /labels endpoints
|
||||
func newPostLabelHandler(s plat.LabelService) http.HandlerFunc {
|
||||
func newPostLabelHandler(s platform.LabelService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePostLabelRequest(ctx, r)
|
||||
req, err := decodePostLabelMappingRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.Label.Validate(); err != nil {
|
||||
if err := req.Mapping.Validate(); err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.CreateLabel(ctx, &req.Label); err != nil {
|
||||
if err := s.CreateLabelMapping(ctx, &req.Mapping); err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusCreated, newLabelResponse(&req.Label)); err != nil {
|
||||
label, err := s.FindLabelByID(ctx, *req.Mapping.LabelID)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusCreated, newLabelResponse(label)); err != nil {
|
||||
// TODO: this can potentially result in calling w.WriteHeader multiple times, we need to pass a logger in here
|
||||
// some how. This isn't as simple as simply passing in a logger to this function since the time that this function
|
||||
// is called is distinct from the time that a potential logger is set.
|
||||
|
@ -136,123 +375,57 @@ func newPostLabelHandler(s plat.LabelService) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
type postLabelRequest struct {
|
||||
Label plat.Label
|
||||
type postLabelMappingRequest struct {
|
||||
Mapping platform.LabelMapping
|
||||
}
|
||||
|
||||
func decodePostLabelRequest(ctx context.Context, r *http.Request) (*postLabelRequest, error) {
|
||||
func decodePostLabelMappingRequest(ctx context.Context, r *http.Request) (*postLabelMappingRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, kerrors.InvalidDataf("url missing id")
|
||||
}
|
||||
|
||||
var rid plat.ID
|
||||
var rid platform.ID
|
||||
if err := rid.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label := &plat.Label{}
|
||||
if err := json.NewDecoder(r.Body).Decode(label); err != nil {
|
||||
mapping := &platform.LabelMapping{}
|
||||
if err := json.NewDecoder(r.Body).Decode(mapping); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label.ResourceID = rid
|
||||
mapping.ResourceID = &rid
|
||||
|
||||
if err := label.Validate(); err != nil {
|
||||
if err := mapping.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &postLabelRequest{
|
||||
Label: *label,
|
||||
req := &postLabelMappingRequest{
|
||||
Mapping: *mapping,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type patchLabelRequest struct {
|
||||
label *plat.Label
|
||||
upd plat.LabelUpdate
|
||||
}
|
||||
|
||||
func decodePatchLabelRequest(ctx context.Context, r *http.Request) (*patchLabelRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, &plat.Error{
|
||||
Code: plat.EInvalid,
|
||||
Msg: "url missing resource id",
|
||||
}
|
||||
}
|
||||
|
||||
name := params.ByName("name")
|
||||
if name == "" {
|
||||
return nil, &plat.Error{
|
||||
Code: plat.EInvalid,
|
||||
Msg: "label name is missing",
|
||||
}
|
||||
}
|
||||
|
||||
var rid plat.ID
|
||||
if err := rid.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upd := &plat.LabelUpdate{}
|
||||
if err := json.NewDecoder(r.Body).Decode(upd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &patchLabelRequest{
|
||||
label: &plat.Label{ResourceID: rid, Name: name},
|
||||
upd: *upd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newPatchLabelHandler returns a handler func for a PATCH to /labels endpoints
|
||||
func newPatchLabelHandler(s plat.LabelService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodePatchLabelRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
label, err := s.UpdateLabel(ctx, req.label, req.upd)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newLabelResponse(label)); err != nil {
|
||||
// TODO: this can potentially result in calling w.WriteHeader multiple times, we need to pass a logger in here
|
||||
// some how. This isn't as simple as simply passing in a logger to this function since the time that this function
|
||||
// is called is distinct from the time that a potential logger is set.
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newDeleteLabelHandler returns a handler func for a DELETE to /labels endpoints
|
||||
func newDeleteLabelHandler(s plat.LabelService) http.HandlerFunc {
|
||||
func newDeleteLabelHandler(s platform.LabelService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := decodeDeleteLabelRequest(ctx, r)
|
||||
req, err := decodeDeleteLabelMappingRequest(ctx, r)
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
label := plat.Label{
|
||||
ResourceID: req.ResourceID,
|
||||
Name: req.Name,
|
||||
mapping := &platform.LabelMapping{
|
||||
LabelID: &req.LabelID,
|
||||
ResourceID: &req.ResourceID,
|
||||
}
|
||||
|
||||
if err := s.DeleteLabel(ctx, label); err != nil {
|
||||
if err := s.DeleteLabelMapping(ctx, mapping); err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
@ -261,42 +434,85 @@ func newDeleteLabelHandler(s plat.LabelService) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
type deleteLabelRequest struct {
|
||||
ResourceID plat.ID
|
||||
Name string
|
||||
type deleteLabelMappingRequest struct {
|
||||
ResourceID platform.ID
|
||||
LabelID platform.ID
|
||||
}
|
||||
|
||||
func decodeDeleteLabelRequest(ctx context.Context, r *http.Request) (*deleteLabelRequest, error) {
|
||||
func decodeDeleteLabelMappingRequest(ctx context.Context, r *http.Request) (*deleteLabelMappingRequest, error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, &plat.Error{
|
||||
Code: plat.EInvalid,
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "url missing resource id",
|
||||
}
|
||||
}
|
||||
|
||||
name := params.ByName("name")
|
||||
if name == "" {
|
||||
return nil, &plat.Error{
|
||||
Code: plat.EInvalid,
|
||||
Msg: "label name is missing",
|
||||
}
|
||||
}
|
||||
|
||||
var rid plat.ID
|
||||
var rid platform.ID
|
||||
if err := rid.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deleteLabelRequest{
|
||||
Name: name,
|
||||
id = params.ByName("lid")
|
||||
if id == "" {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "label id is missing",
|
||||
}
|
||||
}
|
||||
|
||||
var lid platform.ID
|
||||
if err := lid.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deleteLabelMappingRequest{
|
||||
LabelID: lid,
|
||||
ResourceID: rid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindLabels returns a slice of labels
|
||||
func (s *LabelService) FindLabels(ctx context.Context, filter plat.LabelFilter, opt ...plat.FindOptions) ([]*plat.Label, error) {
|
||||
func labelIDPath(id platform.ID) string {
|
||||
return path.Join(labelsPath, id.String())
|
||||
}
|
||||
|
||||
// FindLabelByID returns a single label by ID.
|
||||
func (s *LabelService) FindLabelByID(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
u, err := newURL(s.Addr, labelIDPath(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
SetToken(s.Token, req)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckError(resp, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lr labelResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lr.Label, nil
|
||||
}
|
||||
|
||||
func (s *LabelService) FindLabels(ctx context.Context, filter platform.LabelFilter, opt ...platform.FindOptions) ([]*platform.Label, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FindResourceLabels returns a list of labels, derived from a label mapping filter.
|
||||
func (s *LabelService) FindResourceLabels(ctx context.Context, filter platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
url, err := newURL(s.Addr, resourceIDPath(s.BasePath, filter.ResourceID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -328,12 +544,9 @@ func (s *LabelService) FindLabels(ctx context.Context, filter plat.LabelFilter,
|
|||
return r.Labels, nil
|
||||
}
|
||||
|
||||
func (s *LabelService) CreateLabel(ctx context.Context, l *plat.Label) error {
|
||||
if err := l.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url, err := newURL(s.Addr, resourceIDPath(s.BasePath, l.ResourceID))
|
||||
// CreateLabel creates a new label.
|
||||
func (s *LabelService) CreateLabel(ctx context.Context, l *platform.Label) error {
|
||||
u, err := newURL(s.Addr, labelsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -343,6 +556,49 @@ func (s *LabelService) CreateLabel(ctx context.Context, l *plat.Label) error {
|
|||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
SetToken(s.Token, req)
|
||||
|
||||
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, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var lr labelResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LabelService) CreateLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
if err := m.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url, err := newURL(s.Addr, resourceIDPath(s.BasePath, *m.ResourceID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
octets, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url.String(), bytes.NewReader(octets))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -362,15 +618,74 @@ func (s *LabelService) CreateLabel(ctx context.Context, l *plat.Label) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(l); err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LabelService) DeleteLabel(ctx context.Context, l plat.Label) error {
|
||||
url, err := newURL(s.Addr, labelNamePath(s.BasePath, l.ResourceID, l.Name))
|
||||
// UpdateLabel updates a label and returns the updated label.
|
||||
func (s *LabelService) UpdateLabel(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
u, err := newURL(s.Addr, labelIDPath(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")
|
||||
SetToken(s.Token, req)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckError(resp, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lr labelResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lr.Label, nil
|
||||
}
|
||||
|
||||
// DeleteLabel removes a label by ID.
|
||||
func (s *LabelService) DeleteLabel(ctx context.Context, id platform.ID) error {
|
||||
u, err := newURL(s.Addr, labelIDPath(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
SetToken(s.Token, req)
|
||||
|
||||
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CheckError(resp, true)
|
||||
}
|
||||
|
||||
func (s *LabelService) DeleteLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
url, err := newURL(s.Addr, labelNamePath(s.BasePath, *m.ResourceID, *m.LabelID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -389,6 +704,6 @@ func (s *LabelService) DeleteLabel(ctx context.Context, l plat.Label) error {
|
|||
return CheckError(resp)
|
||||
}
|
||||
|
||||
func labelNamePath(basePath string, resourceID plat.ID, name string) string {
|
||||
return path.Join(basePath, resourceID.String(), "labels", name)
|
||||
func labelNamePath(basePath string, resourceID platform.ID, labelID platform.ID) string {
|
||||
return path.Join(basePath, resourceID.String(), "labels", labelID.String())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,582 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
http "net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
platform "github.com/influxdata/influxdb"
|
||||
"github.com/influxdata/influxdb/mock"
|
||||
platformtesting "github.com/influxdata/influxdb/testing"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func TestService_handleGetLabels(t *testing.T) {
|
||||
type fields struct {
|
||||
LabelService platform.LabelService
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "get all labels",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, filter platform.LabelFilter) ([]*platform.Label, error) {
|
||||
return []*platform.Label{
|
||||
{
|
||||
ID: platformtesting.MustIDBase16("0b501e7e557ab1ed"),
|
||||
Name: "hello",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: platformtesting.MustIDBase16("c0175f0077a77005"),
|
||||
Name: "example",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/api/v2/labels"
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"id": "0b501e7e557ab1ed",
|
||||
"name": "hello",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c0175f0077a77005",
|
||||
"name": "example",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get all labels when there are none",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, filter platform.LabelFilter) ([]*platform.Label, error) {
|
||||
return []*platform.Label{}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/api/v2/labels"
|
||||
},
|
||||
"labels": []
|
||||
}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewLabelHandler()
|
||||
h.LabelService = tt.fields.LabelService
|
||||
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.handleGetLabels(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. handleGetLabels() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. handleGetLabels() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil || tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. handleGetLabels() = ***%v***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_handleGetLabel(t *testing.T) {
|
||||
type fields struct {
|
||||
LabelService platform.LabelService
|
||||
}
|
||||
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 label by id",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
FindLabelByIDFn: func(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
if id == platformtesting.MustIDBase16("020f755c3c082000") {
|
||||
return &platform.Label{
|
||||
ID: platformtesting.MustIDBase16("020f755c3c082000"),
|
||||
Name: "mylabel",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: "020f755c3c082000",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/api/v2/labels/020f755c3c082000"
|
||||
},
|
||||
"label": {
|
||||
"id": "020f755c3c082000",
|
||||
"name": "mylabel",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
FindLabelByIDFn: func(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Msg: "label not found",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: "020f755c3c082000",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewLabelHandler()
|
||||
h.LabelService = tt.fields.LabelService
|
||||
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
|
||||
r = r.WithContext(context.WithValue(
|
||||
context.Background(),
|
||||
httprouter.ParamsKey,
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.args.id,
|
||||
},
|
||||
}))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.handleGetLabel(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. handleGetLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. handleGetLabel() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, diff, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. handleGetLabel() = ***%v***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_handlePostLabel(t *testing.T) {
|
||||
type fields struct {
|
||||
LabelService platform.LabelService
|
||||
}
|
||||
type args struct {
|
||||
label *platform.Label
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "create a new label",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
CreateLabelFn: func(ctx context.Context, l *platform.Label) error {
|
||||
l.ID = platformtesting.MustIDBase16("020f755c3c082000")
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
label: &platform.Label{
|
||||
Name: "mylabel",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusCreated,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/api/v2/labels/020f755c3c082000"
|
||||
},
|
||||
"label": {
|
||||
"id": "020f755c3c082000",
|
||||
"name": "mylabel"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewLabelHandler()
|
||||
h.LabelService = tt.fields.LabelService
|
||||
|
||||
l, err := json.Marshal(tt.args.label)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal label: %v", err)
|
||||
}
|
||||
|
||||
r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(l))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.handlePostLabel(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. handlePostLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. handlePostLabel() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil || tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. handlePostLabel() = ***%v***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_handleDeleteLabel(t *testing.T) {
|
||||
type fields struct {
|
||||
LabelService platform.LabelService
|
||||
}
|
||||
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 label by id",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
DeleteLabelFn: func(ctx context.Context, id platform.ID) error {
|
||||
if id == platformtesting.MustIDBase16("020f755c3c082000") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("wrong id")
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: "020f755c3c082000",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusNoContent,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "label not found",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
DeleteLabelFn: func(ctx context.Context, id platform.ID) error {
|
||||
return &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Msg: "label not found",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: "020f755c3c082000",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewLabelHandler()
|
||||
h.LabelService = tt.fields.LabelService
|
||||
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
|
||||
r = r.WithContext(context.WithValue(
|
||||
context.Background(),
|
||||
httprouter.ParamsKey,
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.args.id,
|
||||
},
|
||||
}))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.handleDeleteLabel(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. handlePostLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. handlePostLabel() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, diff, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. handlePostLabel() = ***%v***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_handlePatchLabel(t *testing.T) {
|
||||
type fields struct {
|
||||
LabelService platform.LabelService
|
||||
}
|
||||
type args struct {
|
||||
id string
|
||||
properties map[string]string
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "update label properties",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
UpdateLabelFn: func(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
if id == platformtesting.MustIDBase16("020f755c3c082000") {
|
||||
l := &platform.Label{
|
||||
ID: platformtesting.MustIDBase16("020f755c3c082000"),
|
||||
Name: "mylabel",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range upd.Properties {
|
||||
if v == "" {
|
||||
delete(l.Properties, k)
|
||||
} else {
|
||||
l.Properties[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: "020f755c3c082000",
|
||||
properties: map[string]string{
|
||||
"color": "aaabbb",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/api/v2/labels/020f755c3c082000"
|
||||
},
|
||||
"label": {
|
||||
"id": "020f755c3c082000",
|
||||
"name": "mylabel",
|
||||
"properties": {
|
||||
"color": "aaabbb"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "label not found",
|
||||
fields: fields{
|
||||
&mock.LabelService{
|
||||
UpdateLabelFn: func(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Msg: "label not found",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: "020f755c3c082000",
|
||||
properties: map[string]string{
|
||||
"color": "aaabbb",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewLabelHandler()
|
||||
h.LabelService = tt.fields.LabelService
|
||||
|
||||
upd := platform.LabelUpdate{}
|
||||
if len(tt.args.properties) > 0 {
|
||||
upd.Properties = tt.args.properties
|
||||
}
|
||||
|
||||
l, err := json.Marshal(upd)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal label update: %v", err)
|
||||
}
|
||||
|
||||
r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(l))
|
||||
|
||||
r = r.WithContext(context.WithValue(
|
||||
context.Background(),
|
||||
httprouter.ParamsKey,
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.args.id,
|
||||
},
|
||||
}))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.handlePatchLabel(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. handlePatchLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. handlePatchLabel() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, diff, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. handlePatchLabel() = ***%v***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ const (
|
|||
// TODO(desa): need a way to specify which secrets to delete. this should work for now
|
||||
organizationsIDSecretsDeletePath = "/api/v2/orgs/:id/secrets/delete"
|
||||
organizationsIDLabelsPath = "/api/v2/orgs/:id/labels"
|
||||
organizationsIDLabelsNamePath = "/api/v2/orgs/:id/labels/:name"
|
||||
organizationsIDLabelsIDPath = "/api/v2/orgs/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewOrgHandler returns a new instance of OrgHandler.
|
||||
|
@ -78,8 +78,7 @@ func NewOrgHandler(mappingService platform.UserResourceMappingService,
|
|||
|
||||
h.HandlerFunc("GET", organizationsIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
||||
h.HandlerFunc("POST", organizationsIDLabelsPath, newPostLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", organizationsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("PATCH", organizationsIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", organizationsIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
424
http/swagger.yml
424
http/swagger.yml
|
@ -326,7 +326,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
$ref: "#/components/schemas/LabelMapping"
|
||||
responses:
|
||||
'200':
|
||||
description: a list of all labels for a telegraf config
|
||||
|
@ -345,7 +345,7 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/telegrafs/{telegrafID}/labels/{label}':
|
||||
'/telegrafs/{telegrafID}/labels/{labelID}':
|
||||
delete:
|
||||
tags:
|
||||
- Telegrafs
|
||||
|
@ -359,11 +359,11 @@ paths:
|
|||
required: true
|
||||
description: ID of the telegraf config
|
||||
- in: path
|
||||
name: label
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
description: the label ID
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
|
@ -379,46 +379,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
patch:
|
||||
tags:
|
||||
- Telegrafs
|
||||
summary: update a label from a telegraf config
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: telegrafID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of the telegraf config
|
||||
- in: path
|
||||
name: label
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
requestBody:
|
||||
description: label update to apply
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
responses:
|
||||
'200':
|
||||
description: updated successfully
|
||||
'404':
|
||||
description: telegraf config not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/telegrafs/{telegrafID}/members':
|
||||
get:
|
||||
tags:
|
||||
|
@ -1409,7 +1369,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
$ref: "#/components/schemas/LabelMapping"
|
||||
responses:
|
||||
'200':
|
||||
description: a list of all labels for a view
|
||||
|
@ -1428,7 +1388,7 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/views/{viewID}/labels/{label}':
|
||||
'/views/{viewID}/labels/{labelID}':
|
||||
delete:
|
||||
tags:
|
||||
- Views
|
||||
|
@ -1442,11 +1402,11 @@ paths:
|
|||
required: true
|
||||
description: ID of the view
|
||||
- in: path
|
||||
name: label
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
description: the label id
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
|
@ -1462,46 +1422,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
patch:
|
||||
tags:
|
||||
- Views
|
||||
summary: update a label from a view
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: viewID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of the view
|
||||
- in: path
|
||||
name: label
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
requestBody:
|
||||
description: label update to apply
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
responses:
|
||||
'200':
|
||||
description: updated successfully
|
||||
'404':
|
||||
description: view not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/views/{viewID}/members':
|
||||
get:
|
||||
tags:
|
||||
|
@ -1680,6 +1600,139 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/labels:
|
||||
post:
|
||||
tags:
|
||||
- Labels
|
||||
summary: Create a label
|
||||
requestBody:
|
||||
description: label to create
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
responses:
|
||||
'201':
|
||||
description: Added label
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
get:
|
||||
tags:
|
||||
- Labels
|
||||
summary: Get all labels
|
||||
responses:
|
||||
'200':
|
||||
description: all labels
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Labels"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/labels/{labelID}:
|
||||
get:
|
||||
tags:
|
||||
- Labels
|
||||
summary: Get a label
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of label to update
|
||||
responses:
|
||||
'200':
|
||||
description: a label
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
patch:
|
||||
tags:
|
||||
- Labels
|
||||
summary: Update a single label
|
||||
requestBody:
|
||||
description: label update
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/LabelUpdate"
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of label to update
|
||||
responses:
|
||||
'200':
|
||||
description: Updated label
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
'404':
|
||||
description: label not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
delete:
|
||||
tags:
|
||||
- Labels
|
||||
summary: Delete a label
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of label to delete
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
'404':
|
||||
description: label not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/dashboards:
|
||||
post:
|
||||
tags:
|
||||
|
@ -2148,7 +2201,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
$ref: "#/components/schemas/LabelMapping"
|
||||
responses:
|
||||
'200':
|
||||
description: a list of all labels for a dashboard
|
||||
|
@ -2167,6 +2220,40 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/dashboards/{dashboardID}/labels/{labelID}':
|
||||
delete:
|
||||
tags:
|
||||
- Dashboards
|
||||
summary: delete a label from a dashboard
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: dashboardID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of the dashboard
|
||||
- in: path
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label id to delete
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
'404':
|
||||
description: dashboard not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/dashboards/{dashboardID}/members':
|
||||
get:
|
||||
tags:
|
||||
|
@ -2937,7 +3024,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
$ref: "#/components/schemas/LabelMapping"
|
||||
responses:
|
||||
'200':
|
||||
description: a list of all labels for a bucket
|
||||
|
@ -2956,7 +3043,7 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/buckets/{bucketID}/labels/{label}':
|
||||
'/buckets/{bucketID}/labels/{labelID}':
|
||||
delete:
|
||||
tags:
|
||||
- Buckets
|
||||
|
@ -2970,11 +3057,11 @@ paths:
|
|||
required: true
|
||||
description: ID of the bucket
|
||||
- in: path
|
||||
name: label
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
description: the label id to delete
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
|
@ -2990,46 +3077,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
patch:
|
||||
tags:
|
||||
- Buckets
|
||||
summary: update a label from a bucket
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: bucketID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of the bucket
|
||||
- in: path
|
||||
name: label
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
requestBody:
|
||||
description: label update to apply
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
responses:
|
||||
'200':
|
||||
description: updated successfully
|
||||
'404':
|
||||
description: bucket not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/buckets/{bucketID}/members':
|
||||
get:
|
||||
tags:
|
||||
|
@ -3386,7 +3433,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
$ref: "#/components/schemas/LabelMapping"
|
||||
responses:
|
||||
'200':
|
||||
description: a list of all labels for an organization
|
||||
|
@ -3405,7 +3452,7 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/orgs/{orgID}/labels/{label}':
|
||||
'/orgs/{orgID}/labels/{labelID}':
|
||||
delete:
|
||||
tags:
|
||||
- Organizations
|
||||
|
@ -3419,11 +3466,11 @@ paths:
|
|||
required: true
|
||||
description: ID of the organization
|
||||
- in: path
|
||||
name: label
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
description: the label id
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
|
@ -3439,46 +3486,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
patch:
|
||||
tags:
|
||||
- Organizations
|
||||
summary: update a label from an organization
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: orgID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of the organization
|
||||
- in: path
|
||||
name: label
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
requestBody:
|
||||
description: label update to apply
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
responses:
|
||||
'200':
|
||||
description: updated successfully
|
||||
'404':
|
||||
description: organization not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/orgs/{orgID}/secrets':
|
||||
get:
|
||||
tags:
|
||||
|
@ -4150,7 +4157,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
$ref: "#/components/schemas/LabelMapping"
|
||||
responses:
|
||||
'200':
|
||||
description: a list of all labels for a task
|
||||
|
@ -4171,7 +4178,7 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'/tasks/{taskID}/labels/{label}':
|
||||
'/tasks/{taskID}/labels/{labelID}':
|
||||
delete:
|
||||
tags:
|
||||
- Tasks
|
||||
|
@ -4185,11 +4192,11 @@ paths:
|
|||
required: true
|
||||
description: ID of the task
|
||||
- in: path
|
||||
name: label
|
||||
name: labelID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
description: the label id
|
||||
responses:
|
||||
'204':
|
||||
description: delete has been accepted
|
||||
|
@ -4205,46 +4212,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
patch:
|
||||
tags:
|
||||
- Tasks
|
||||
summary: update a label from a task
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TraceSpan'
|
||||
- in: path
|
||||
name: taskID
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: ID of the task
|
||||
- in: path
|
||||
name: label
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the label name
|
||||
requestBody:
|
||||
description: label update to apply
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Label"
|
||||
responses:
|
||||
'200':
|
||||
description: updated successfully
|
||||
'404':
|
||||
description: task not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/me:
|
||||
get:
|
||||
tags:
|
||||
|
@ -7054,9 +7021,24 @@ components:
|
|||
Label:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
readOnly: true
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
properties:
|
||||
type: object
|
||||
description: Key/Value pairs associated with this label. Keys can be removed by sending an update with an empty value.
|
||||
example: {"color": "ffb3b3", "description": "this is a description"}
|
||||
LabelUpdate:
|
||||
type: object
|
||||
properties:
|
||||
properties:
|
||||
type: object
|
||||
description: Key/Value pairs associated with this label. Keys can be removed by sending an update with an empty value.
|
||||
example: {"color": "ffb3b3", "description": "this is a description"}
|
||||
LabelMapping:
|
||||
type: object
|
||||
properties:
|
||||
labelID:
|
||||
type: string
|
||||
|
|
|
@ -47,7 +47,7 @@ const (
|
|||
tasksIDRunsIDLogsPath = "/api/v2/tasks/:id/runs/:rid/logs"
|
||||
tasksIDRunsIDRetryPath = "/api/v2/tasks/:id/runs/:rid/retry"
|
||||
tasksIDLabelsPath = "/api/v2/tasks/:id/labels"
|
||||
tasksIDLabelsNamePath = "/api/v2/tasks/:id/labels/:name"
|
||||
tasksIDLabelsIDPath = "/api/v2/tasks/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewTaskHandler returns a new instance of TaskHandler.
|
||||
|
@ -87,8 +87,7 @@ func NewTaskHandler(mappingService platform.UserResourceMappingService, labelSer
|
|||
|
||||
h.HandlerFunc("GET", tasksIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
||||
h.HandlerFunc("POST", tasksIDLabelsPath, newPostLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", tasksIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("PATCH", tasksIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", tasksIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
||||
|
||||
return h
|
||||
}
|
||||
|
@ -157,7 +156,7 @@ func newTasksResponse(ctx context.Context, ts []*platform.Task, labelService pla
|
|||
}
|
||||
|
||||
for i := range ts {
|
||||
labels, _ := labelService.FindLabels(ctx, platform.LabelFilter{ResourceID: ts[i].ID})
|
||||
labels, _ := labelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: ts[i].ID})
|
||||
rs.Tasks[i] = newTaskResponse(*ts[i], labels)
|
||||
}
|
||||
return rs
|
||||
|
@ -359,7 +358,7 @@ func (h *TaskHandler) handleGetTask(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: task.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: task.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
@ -408,7 +407,7 @@ func (h *TaskHandler) handleUpdateTask(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: task.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: task.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/influxdata/influxdb/logger"
|
||||
"github.com/influxdata/influxdb/mock"
|
||||
_ "github.com/influxdata/influxdb/query/builtin"
|
||||
platformtesting "github.com/influxdata/influxdb/testing"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
|
@ -76,11 +77,11 @@ func TestTaskHandler_handleGetTasks(t *testing.T) {
|
|||
},
|
||||
},
|
||||
labelService: &mock.LabelService{
|
||||
FindLabelsFn: func(ctx context.Context, f platform.LabelFilter) ([]*platform.Label, error) {
|
||||
FindResourceLabelsFn: func(ctx context.Context, f platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
labels := []*platform.Label{
|
||||
{
|
||||
ResourceID: f.ResourceID,
|
||||
Name: "label",
|
||||
ID: platformtesting.MustIDBase16("fc3dc670a4be9b9a"),
|
||||
Name: "label",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -112,7 +113,7 @@ func TestTaskHandler_handleGetTasks(t *testing.T) {
|
|||
"name": "task1",
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "0000000000000001",
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
|
@ -141,7 +142,7 @@ func TestTaskHandler_handleGetTasks(t *testing.T) {
|
|||
"name": "task2",
|
||||
"labels": [
|
||||
{
|
||||
"resourceID": "0000000000000002",
|
||||
"id": "fc3dc670a4be9b9a",
|
||||
"name": "label",
|
||||
"properties": {
|
||||
"color": "fff000"
|
||||
|
|
|
@ -28,14 +28,14 @@ type TelegrafHandler struct {
|
|||
}
|
||||
|
||||
const (
|
||||
telegrafsPath = "/api/v2/telegrafs"
|
||||
telegrafsIDPath = "/api/v2/telegrafs/:id"
|
||||
telegrafsIDMembersPath = "/api/v2/telegrafs/:id/members"
|
||||
telegrafsIDMembersIDPath = "/api/v2/telegrafs/:id/members/:userID"
|
||||
telegrafsIDOwnersPath = "/api/v2/telegrafs/:id/owners"
|
||||
telegrafsIDOwnersIDPath = "/api/v2/telegrafs/:id/owners/:userID"
|
||||
telegrafsIDLabelsPath = "/api/v2/telegrafs/:id/labels"
|
||||
telegrafsIDLabelsNamePath = "/api/v2/telegrafs/:id/labels/:name"
|
||||
telegrafsPath = "/api/v2/telegrafs"
|
||||
telegrafsIDPath = "/api/v2/telegrafs/:id"
|
||||
telegrafsIDMembersPath = "/api/v2/telegrafs/:id/members"
|
||||
telegrafsIDMembersIDPath = "/api/v2/telegrafs/:id/members/:userID"
|
||||
telegrafsIDOwnersPath = "/api/v2/telegrafs/:id/owners"
|
||||
telegrafsIDOwnersIDPath = "/api/v2/telegrafs/:id/owners/:userID"
|
||||
telegrafsIDLabelsPath = "/api/v2/telegrafs/:id/labels"
|
||||
telegrafsIDLabelsIDPath = "/api/v2/telegrafs/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewTelegrafHandler returns a new instance of TelegrafHandler.
|
||||
|
@ -73,8 +73,7 @@ func NewTelegrafHandler(
|
|||
|
||||
h.HandlerFunc("GET", telegrafsIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
||||
h.HandlerFunc("POST", telegrafsIDLabelsPath, newPostLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", telegrafsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("PATCH", telegrafsIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", telegrafsIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
||||
|
||||
return h
|
||||
}
|
||||
|
@ -116,7 +115,7 @@ func newTelegrafResponses(ctx context.Context, tcs []*platform.TelegrafConfig, l
|
|||
TelegrafConfigs: make([]telegrafResponse, len(tcs)),
|
||||
}
|
||||
for i, c := range tcs {
|
||||
labels, _ := labelService.FindLabels(ctx, platform.LabelFilter{ResourceID: c.ID})
|
||||
labels, _ := labelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: c.ID})
|
||||
resp.TelegrafConfigs[i] = newTelegrafResponse(c, labels)
|
||||
}
|
||||
return resp
|
||||
|
@ -177,7 +176,7 @@ func (h *TelegrafHandler) handleGetTelegraf(w http.ResponseWriter, r *http.Reque
|
|||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(tc.TOML()))
|
||||
case "application/json":
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: tc.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: tc.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
@ -312,7 +311,7 @@ func (h *TelegrafHandler) handlePutTelegraf(w http.ResponseWriter, r *http.Reque
|
|||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindLabels(ctx, platform.LabelFilter{ResourceID: tc.ID})
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: tc.ID})
|
||||
if err != nil {
|
||||
EncodeError(ctx, err, w)
|
||||
return
|
||||
|
|
|
@ -27,14 +27,14 @@ type ViewHandler struct {
|
|||
}
|
||||
|
||||
const (
|
||||
viewsPath = "/api/v2/views"
|
||||
viewsIDPath = "/api/v2/views/:id"
|
||||
viewsIDMembersPath = "/api/v2/views/:id/members"
|
||||
viewsIDMembersIDPath = "/api/v2/views/:id/members/:userID"
|
||||
viewsIDOwnersPath = "/api/v2/views/:id/owners"
|
||||
viewsIDOwnersIDPath = "/api/v2/views/:id/owners/:userID"
|
||||
viewsIDLabelsPath = "/api/v2/views/:id/labels"
|
||||
viewsIDLabelsNamePath = "/api/v2/views/:id/labels/:name"
|
||||
viewsPath = "/api/v2/views"
|
||||
viewsIDPath = "/api/v2/views/:id"
|
||||
viewsIDMembersPath = "/api/v2/views/:id/members"
|
||||
viewsIDMembersIDPath = "/api/v2/views/:id/members/:userID"
|
||||
viewsIDOwnersPath = "/api/v2/views/:id/owners"
|
||||
viewsIDOwnersIDPath = "/api/v2/views/:id/owners/:userID"
|
||||
viewsIDLabelsPath = "/api/v2/views/:id/labels"
|
||||
viewsIDLabelsIDPath = "/api/v2/views/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewViewHandler returns a new instance of ViewHandler.
|
||||
|
@ -65,8 +65,7 @@ func NewViewHandler(mappingService platform.UserResourceMappingService, labelSer
|
|||
|
||||
h.HandlerFunc("GET", viewsIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
||||
h.HandlerFunc("POST", viewsIDLabelsPath, newPostLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", viewsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("PATCH", viewsIDLabelsNamePath, newPatchLabelHandler(h.LabelService))
|
||||
h.HandlerFunc("DELETE", viewsIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -308,7 +308,9 @@ func (s *Service) DeleteBucket(ctx context.Context, id platform.ID) error {
|
|||
}
|
||||
}
|
||||
s.bucketKV.Delete(id.String())
|
||||
return s.deleteLabel(ctx, platform.LabelFilter{ResourceID: id})
|
||||
|
||||
// return s.deleteLabel(ctx, platform.LabelFilter{ResourceID: id})
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteOrganizationBuckets removes all the buckets for a given org
|
||||
|
|
|
@ -209,13 +209,13 @@ func (s *Service) DeleteDashboard(ctx context.Context, id platform.ID) error {
|
|||
}
|
||||
}
|
||||
s.dashboardKV.Delete(id.String())
|
||||
err := s.deleteLabel(ctx, platform.LabelFilter{ResourceID: id})
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
Op: op,
|
||||
}
|
||||
}
|
||||
// err := s.deleteLabel(ctx, platform.LabelFilter{ResourceID: id})
|
||||
// if err != nil {
|
||||
// return &platform.Error{
|
||||
// Err: err,
|
||||
// Op: op,
|
||||
// }
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -8,14 +8,13 @@ import (
|
|||
platform "github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
func encodeLabelKey(resourceID platform.ID, name string) string {
|
||||
return path.Join(resourceID.String(), name)
|
||||
}
|
||||
|
||||
func (s *Service) loadLabel(ctx context.Context, resourceID platform.ID, name string) (*platform.Label, error) {
|
||||
i, ok := s.labelKV.Load(encodeLabelKey(resourceID, name))
|
||||
func (s *Service) loadLabel(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
i, ok := s.labelKV.Load(id.String())
|
||||
if !ok {
|
||||
return nil, platform.ErrLabelNotFound
|
||||
return nil, &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Msg: "label not found",
|
||||
}
|
||||
}
|
||||
|
||||
l, ok := i.(platform.Label)
|
||||
|
@ -26,10 +25,6 @@ func (s *Service) loadLabel(ctx context.Context, resourceID platform.ID, name st
|
|||
return &l, nil
|
||||
}
|
||||
|
||||
func (s *Service) FindLabelBy(ctx context.Context, resourceID platform.ID, name string) (*platform.Label, error) {
|
||||
return s.loadLabel(ctx, resourceID, name)
|
||||
}
|
||||
|
||||
func (s *Service) forEachLabel(ctx context.Context, fn func(m *platform.Label) bool) error {
|
||||
var err error
|
||||
s.labelKV.Range(func(k, v interface{}) bool {
|
||||
|
@ -44,6 +39,20 @@ func (s *Service) forEachLabel(ctx context.Context, fn func(m *platform.Label) b
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *Service) forEachLabelMapping(ctx context.Context, fn func(m *platform.LabelMapping) bool) error {
|
||||
var err error
|
||||
s.labelMappingKV.Range(func(k, v interface{}) bool {
|
||||
m, ok := v.(platform.LabelMapping)
|
||||
if !ok {
|
||||
err = fmt.Errorf("type %T is not a label mapping", v)
|
||||
return false
|
||||
}
|
||||
return fn(&m)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) filterLabels(ctx context.Context, fn func(m *platform.Label) bool) ([]*platform.Label, error) {
|
||||
labels := []*platform.Label{}
|
||||
err := s.forEachLabel(ctx, func(l *platform.Label) bool {
|
||||
|
@ -60,9 +69,35 @@ func (s *Service) filterLabels(ctx context.Context, fn func(m *platform.Label) b
|
|||
return labels, nil
|
||||
}
|
||||
|
||||
func (s *Service) filterLabelMappings(ctx context.Context, fn func(m *platform.LabelMapping) bool) ([]*platform.LabelMapping, error) {
|
||||
mappings := []*platform.LabelMapping{}
|
||||
err := s.forEachLabelMapping(ctx, func(m *platform.LabelMapping) bool {
|
||||
if fn(m) {
|
||||
mappings = append(mappings, m)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappings, nil
|
||||
}
|
||||
|
||||
func encodeLabelMappingKey(m *platform.LabelMapping) string {
|
||||
return path.Join(m.ResourceID.String(), m.LabelID.String())
|
||||
}
|
||||
|
||||
// FindLabelByID returns a single user by ID.
|
||||
func (s *Service) FindLabelByID(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
return s.loadLabel(ctx, id)
|
||||
}
|
||||
|
||||
// FindLabels will retrieve a list of labels from storage.
|
||||
func (s *Service) FindLabels(ctx context.Context, filter platform.LabelFilter, opt ...platform.FindOptions) ([]*platform.Label, error) {
|
||||
if filter.ResourceID.Valid() && filter.Name != "" {
|
||||
l, err := s.FindLabelBy(ctx, filter.ResourceID, filter.Name)
|
||||
if filter.ID.Valid() {
|
||||
l, err := s.FindLabelByID(ctx, filter.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -70,8 +105,7 @@ func (s *Service) FindLabels(ctx context.Context, filter platform.LabelFilter, o
|
|||
}
|
||||
|
||||
filterFunc := func(label *platform.Label) bool {
|
||||
return (!filter.ResourceID.Valid() || (filter.ResourceID == label.ResourceID)) &&
|
||||
(filter.Name == "" || (filter.Name == label.Name))
|
||||
return (filter.Name == "" || (filter.Name == label.Name))
|
||||
}
|
||||
|
||||
labels, err := s.filterLabels(ctx, filterFunc)
|
||||
|
@ -82,27 +116,60 @@ func (s *Service) FindLabels(ctx context.Context, filter platform.LabelFilter, o
|
|||
return labels, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateLabel(ctx context.Context, l *platform.Label) error {
|
||||
label, _ := s.FindLabelBy(ctx, l.ResourceID, l.Name)
|
||||
if label != nil {
|
||||
return &platform.Error{
|
||||
Code: platform.EConflict,
|
||||
Op: OpPrefix + platform.OpCreateLabel,
|
||||
Msg: fmt.Sprintf("label %s already exists", l.Name),
|
||||
}
|
||||
// FindResourceLabels returns a list of labels that are mapped to a resource.
|
||||
func (s *Service) FindResourceLabels(ctx context.Context, filter platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
filterFunc := func(mapping *platform.LabelMapping) bool {
|
||||
return (filter.ResourceID.String() == mapping.ResourceID.String())
|
||||
}
|
||||
|
||||
s.labelKV.Store(encodeLabelKey(l.ResourceID, l.Name), *l)
|
||||
mappings, err := s.filterLabelMappings(ctx, filterFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls := []*platform.Label{}
|
||||
for _, m := range mappings {
|
||||
l, err := s.FindLabelByID(ctx, *m.LabelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls = append(ls, l)
|
||||
}
|
||||
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
// CreateLabel creates a new label.
|
||||
func (s *Service) CreateLabel(ctx context.Context, l *platform.Label) error {
|
||||
l.ID = s.IDGenerator.ID()
|
||||
s.labelKV.Store(l.ID, *l)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateLabel(ctx context.Context, l *platform.Label, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
label, err := s.FindLabelBy(ctx, l.ResourceID, l.Name)
|
||||
// CreateLabelMapping creates a mapping that associates a label to a resource.
|
||||
func (s *Service) CreateLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
_, err := s.FindLabelByID(ctx, *m.LabelID)
|
||||
if err != nil {
|
||||
return &platform.Error{
|
||||
Err: err,
|
||||
Op: platform.OpCreateLabel,
|
||||
}
|
||||
}
|
||||
|
||||
s.labelMappingKV.Store(encodeLabelMappingKey(m), *m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLabel updates a label.
|
||||
func (s *Service) UpdateLabel(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
label, err := s.FindLabelByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: OpPrefix + platform.OpUpdateLabel,
|
||||
Err: err,
|
||||
Msg: "label not found",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,38 +193,36 @@ func (s *Service) UpdateLabel(ctx context.Context, l *platform.Label, upd platfo
|
|||
}
|
||||
}
|
||||
|
||||
s.labelKV.Store(encodeLabelKey(label.ResourceID, label.Name), *label)
|
||||
s.labelKV.Store(label.ID.String(), *label)
|
||||
|
||||
return label, nil
|
||||
}
|
||||
|
||||
// PutLabel writes a label directly to the database without generating IDs
|
||||
// or making checks.
|
||||
func (s *Service) PutLabel(ctx context.Context, l *platform.Label) error {
|
||||
s.labelKV.Store(encodeLabelKey(l.ResourceID, l.Name), *l)
|
||||
s.labelKV.Store(l.ID.String(), *l)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteLabel(ctx context.Context, l platform.Label) error {
|
||||
label, err := s.FindLabelBy(ctx, l.ResourceID, l.Name)
|
||||
// DeleteLabel deletes a label.
|
||||
func (s *Service) DeleteLabel(ctx context.Context, id platform.ID) error {
|
||||
label, err := s.FindLabelByID(ctx, id)
|
||||
if label == nil && err != nil {
|
||||
return &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: OpPrefix + platform.OpDeleteLabel,
|
||||
Err: platform.ErrLabelNotFound,
|
||||
Msg: "label not found",
|
||||
}
|
||||
}
|
||||
|
||||
s.labelKV.Delete(encodeLabelKey(l.ResourceID, l.Name))
|
||||
s.labelKV.Delete(id.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) deleteLabel(ctx context.Context, filter platform.LabelFilter) error {
|
||||
labels, err := s.FindLabels(ctx, filter)
|
||||
if labels == nil && err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range labels {
|
||||
s.labelKV.Delete(encodeLabelKey(l.ResourceID, l.Name))
|
||||
}
|
||||
|
||||
// DeleteLabelMapping deletes a label mapping.
|
||||
func (s *Service) DeleteLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
s.labelMappingKV.Delete(encodeLabelMappingKey(m))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,16 +10,24 @@ import (
|
|||
|
||||
func initLabelService(f platformtesting.LabelFields, t *testing.T) (platform.LabelService, string, func()) {
|
||||
s := NewService()
|
||||
ctx := context.TODO()
|
||||
for _, m := range f.Labels {
|
||||
if err := s.CreateLabel(ctx, m); err != nil {
|
||||
s.IDGenerator = f.IDGenerator
|
||||
ctx := context.Background()
|
||||
for _, l := range f.Labels {
|
||||
if err := s.PutLabel(ctx, l); err != nil {
|
||||
t.Fatalf("failed to populate labels")
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range f.Mappings {
|
||||
if err := s.CreateLabelMapping(ctx, m); err != nil {
|
||||
t.Fatalf("failed to populate label mappings")
|
||||
}
|
||||
}
|
||||
|
||||
return s, OpPrefix, func() {}
|
||||
}
|
||||
|
||||
func TestLabelService(t *testing.T) {
|
||||
t.Parallel()
|
||||
platformtesting.LabelService(initLabelService, t)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ type Service struct {
|
|||
dbrpMappingKV sync.Map
|
||||
userResourceMappingKV sync.Map
|
||||
labelKV sync.Map
|
||||
labelMappingKV sync.Map
|
||||
scraperTargetKV sync.Map
|
||||
telegrafConfigKV sync.Map
|
||||
onboardingKV sync.Map
|
||||
|
|
70
label.go
70
label.go
|
@ -8,41 +8,52 @@ import (
|
|||
const ErrLabelNotFound = ChronografError("label not found")
|
||||
|
||||
const (
|
||||
OpFindLabels = "FindLabels"
|
||||
OpCreateLabel = "CreateLabel"
|
||||
OpUpdateLabel = "UpdateLabel"
|
||||
OpDeleteLabel = "DeleteLabel"
|
||||
OpFindLabels = "FindLabels"
|
||||
OpFindLabelByID = "FindLabelByID"
|
||||
OpFindLabelMapping = "FindLabelMapping"
|
||||
OpCreateLabel = "CreateLabel"
|
||||
OpCreateLabelMapping = "CreateLabelMapping"
|
||||
OpUpdateLabel = "UpdateLabel"
|
||||
OpDeleteLabel = "DeleteLabel"
|
||||
OpDeleteLabelMapping = "DeleteLabelMapping"
|
||||
)
|
||||
|
||||
// LabelService represents a service for managing resource labels
|
||||
type LabelService interface {
|
||||
// FindLabelByID a single label by ID.
|
||||
FindLabelByID(ctx context.Context, id ID) (*Label, error)
|
||||
|
||||
// FindLabels returns a list of labels that match a filter
|
||||
FindLabels(ctx context.Context, filter LabelFilter, opt ...FindOptions) ([]*Label, error)
|
||||
|
||||
// FindResourceLabels returns a list of labels that belong to a resource
|
||||
FindResourceLabels(ctx context.Context, filter LabelMappingFilter) ([]*Label, error)
|
||||
|
||||
// CreateLabel creates a new label
|
||||
CreateLabel(ctx context.Context, l *Label) error
|
||||
|
||||
// CreateLabel maps a resource to an existing label
|
||||
CreateLabelMapping(ctx context.Context, m *LabelMapping) error
|
||||
|
||||
// UpdateLabel updates a label with a changeset.
|
||||
UpdateLabel(ctx context.Context, l *Label, upd LabelUpdate) (*Label, error)
|
||||
UpdateLabel(ctx context.Context, id ID, upd LabelUpdate) (*Label, error)
|
||||
|
||||
// DeleteLabel deletes a label
|
||||
DeleteLabel(ctx context.Context, l Label) error
|
||||
DeleteLabel(ctx context.Context, id ID) error
|
||||
|
||||
// DeleteLabelMapping deletes a label mapping
|
||||
DeleteLabelMapping(ctx context.Context, m *LabelMapping) error
|
||||
}
|
||||
|
||||
// Label is a tag set on a resource, typically used for filtering on a UI.
|
||||
type Label struct {
|
||||
ResourceID ID `json:"resourceID"`
|
||||
ID ID `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// Validate returns an error if the label is invalid.
|
||||
func (l *Label) Validate() error {
|
||||
if !l.ResourceID.Valid() {
|
||||
return &Error{
|
||||
Code: EInvalid,
|
||||
Msg: "resourceID is required",
|
||||
}
|
||||
}
|
||||
|
||||
if l.Name == "" {
|
||||
return &Error{
|
||||
Code: EInvalid,
|
||||
|
@ -53,13 +64,38 @@ func (l *Label) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// LabelMapping is used to map resource to its labels.
|
||||
// It should not be shared directly over the HTTP API.
|
||||
type LabelMapping struct {
|
||||
LabelID *ID `json:"labelID"`
|
||||
ResourceID *ID
|
||||
}
|
||||
|
||||
// Validate returns an error if the mapping is invalid.
|
||||
func (l *LabelMapping) Validate() error {
|
||||
if !l.ResourceID.Valid() {
|
||||
return &Error{
|
||||
Code: EInvalid,
|
||||
Msg: "resourceID is required",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LabelUpdate represents a changeset for a label.
|
||||
// Only fields which are set are updated.
|
||||
type LabelUpdate struct {
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// LabelFilter represents a set of filters that restrict the returned results.
|
||||
type LabelFilter struct {
|
||||
ResourceID ID
|
||||
Name string
|
||||
ID ID
|
||||
Name string
|
||||
}
|
||||
|
||||
// LabelMappingFilter represents a set of filters that restrict the returned results.
|
||||
type LabelMappingFilter struct {
|
||||
ResourceID ID
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"testing"
|
||||
|
||||
platform "github.com/influxdata/influxdb"
|
||||
platformtesting "github.com/influxdata/influxdb/testing"
|
||||
)
|
||||
|
||||
func TestLabelValidate(t *testing.T) {
|
||||
|
@ -19,31 +18,20 @@ func TestLabelValidate(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "valid label",
|
||||
fields: fields{
|
||||
ResourceID: platformtesting.MustIDBase16("020f755c3c082000"),
|
||||
Name: "iot",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "label requires a resourceid",
|
||||
fields: fields{
|
||||
Name: "iot",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "label requires a name",
|
||||
fields: fields{
|
||||
ResourceID: platformtesting.MustIDBase16("020f755c3c082000"),
|
||||
},
|
||||
name: "label requires a name",
|
||||
fields: fields{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := platform.Label{
|
||||
ResourceID: tt.fields.ResourceID,
|
||||
Name: tt.fields.Name,
|
||||
Name: tt.fields.Name,
|
||||
}
|
||||
if err := m.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Label.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
|
|
@ -10,41 +10,73 @@ var _ platform.LabelService = &LabelService{}
|
|||
|
||||
// LabelService is a mock implementation of platform.LabelService
|
||||
type LabelService struct {
|
||||
FindLabelsFn func(context.Context, platform.LabelFilter) ([]*platform.Label, error)
|
||||
CreateLabelFn func(context.Context, *platform.Label) error
|
||||
UpdateLabelFn func(context.Context, *platform.Label, platform.LabelUpdate) (*platform.Label, error)
|
||||
DeleteLabelFn func(context.Context, platform.Label) error
|
||||
FindLabelByIDFn func(ctx context.Context, id platform.ID) (*platform.Label, error)
|
||||
FindLabelsFn func(context.Context, platform.LabelFilter) ([]*platform.Label, error)
|
||||
FindResourceLabelsFn func(context.Context, platform.LabelMappingFilter) ([]*platform.Label, error)
|
||||
CreateLabelFn func(context.Context, *platform.Label) error
|
||||
CreateLabelMappingFn func(context.Context, *platform.LabelMapping) error
|
||||
UpdateLabelFn func(context.Context, platform.ID, platform.LabelUpdate) (*platform.Label, error)
|
||||
DeleteLabelFn func(context.Context, platform.ID) error
|
||||
DeleteLabelMappingFn func(context.Context, *platform.LabelMapping) error
|
||||
}
|
||||
|
||||
// NewLabelService returns a mock of LabelService
|
||||
// where its methods will return zero values.
|
||||
func NewLabelService() *LabelService {
|
||||
return &LabelService{
|
||||
FindLabelByIDFn: func(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
return nil, nil
|
||||
},
|
||||
FindLabelsFn: func(context.Context, platform.LabelFilter) ([]*platform.Label, error) {
|
||||
return nil, nil
|
||||
},
|
||||
CreateLabelFn: func(context.Context, *platform.Label) error { return nil },
|
||||
UpdateLabelFn: func(context.Context, *platform.Label, platform.LabelUpdate) (*platform.Label, error) { return nil, nil },
|
||||
DeleteLabelFn: func(context.Context, platform.Label) error { return nil },
|
||||
FindResourceLabelsFn: func(context.Context, platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
return []*platform.Label{}, nil
|
||||
},
|
||||
CreateLabelFn: func(context.Context, *platform.Label) error { return nil },
|
||||
CreateLabelMappingFn: func(context.Context, *platform.LabelMapping) error { return nil },
|
||||
UpdateLabelFn: func(context.Context, platform.ID, platform.LabelUpdate) (*platform.Label, error) { return nil, nil },
|
||||
DeleteLabelFn: func(context.Context, platform.ID) error { return nil },
|
||||
DeleteLabelMappingFn: func(context.Context, *platform.LabelMapping) error { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
// FindLabelByID finds mappings by their ID
|
||||
func (s *LabelService) FindLabelByID(ctx context.Context, id platform.ID) (*platform.Label, error) {
|
||||
return s.FindLabelByIDFn(ctx, id)
|
||||
}
|
||||
|
||||
// FindLabels finds mappings that match a given filter.
|
||||
func (s *LabelService) FindLabels(ctx context.Context, filter platform.LabelFilter, opt ...platform.FindOptions) ([]*platform.Label, error) {
|
||||
return s.FindLabelsFn(ctx, filter)
|
||||
}
|
||||
|
||||
// FindResourceLabels finds mappings that match a given filter.
|
||||
func (s *LabelService) FindResourceLabels(ctx context.Context, filter platform.LabelMappingFilter) ([]*platform.Label, error) {
|
||||
return s.FindResourceLabelsFn(ctx, filter)
|
||||
}
|
||||
|
||||
// CreateLabel creates a new Label.
|
||||
func (s *LabelService) CreateLabel(ctx context.Context, l *platform.Label) error {
|
||||
return s.CreateLabelFn(ctx, l)
|
||||
}
|
||||
|
||||
// CreateLabelMapping creates a new Label mapping.
|
||||
func (s *LabelService) CreateLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
return s.CreateLabelMappingFn(ctx, m)
|
||||
}
|
||||
|
||||
// UpdateLabel updates a label.
|
||||
func (s *LabelService) UpdateLabel(ctx context.Context, l *platform.Label, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
return s.UpdateLabelFn(ctx, l, upd)
|
||||
func (s *LabelService) UpdateLabel(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
|
||||
return s.UpdateLabelFn(ctx, id, upd)
|
||||
}
|
||||
|
||||
// DeleteLabel removes a Label.
|
||||
func (s *LabelService) DeleteLabel(ctx context.Context, l platform.Label) error {
|
||||
return s.DeleteLabelFn(ctx, l)
|
||||
func (s *LabelService) DeleteLabel(ctx context.Context, id platform.ID) error {
|
||||
return s.DeleteLabelFn(ctx, id)
|
||||
}
|
||||
|
||||
// DeleteLabelMapping removes a Label mapping.
|
||||
func (s *LabelService) DeleteLabelMapping(ctx context.Context, m *platform.LabelMapping) error {
|
||||
return s.DeleteLabelMappingFn(ctx, m)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,12 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
platform "github.com/influxdata/influxdb"
|
||||
"github.com/influxdata/influxdb/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
labelOneID = "41a9f7288d4e2d64"
|
||||
labelTwoID = "b7c5355e1134b11c"
|
||||
)
|
||||
|
||||
var labelCmpOptions = cmp.Options{
|
||||
|
@ -17,17 +23,17 @@ var labelCmpOptions = cmp.Options{
|
|||
cmp.Transformer("Sort", func(in []*platform.Label) []*platform.Label {
|
||||
out := append([]*platform.Label(nil), in...) // Copy input to avoid mutating it
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if out[i].Name != out[j].Name {
|
||||
return out[i].Name < out[j].Name
|
||||
}
|
||||
return out[i].ResourceID.String() < out[j].ResourceID.String()
|
||||
return out[i].Name < out[j].Name
|
||||
})
|
||||
return out
|
||||
}),
|
||||
}
|
||||
|
||||
// LabelFields include the IDGenerator, labels and their mappings
|
||||
type LabelFields struct {
|
||||
Labels []*platform.Label
|
||||
Labels []*platform.Label
|
||||
Mappings []*platform.LabelMapping
|
||||
IDGenerator platform.IDGenerator
|
||||
}
|
||||
|
||||
type labelServiceF func(
|
||||
|
@ -48,10 +54,18 @@ func LabelService(
|
|||
name: "CreateLabel",
|
||||
fn: CreateLabel,
|
||||
},
|
||||
{
|
||||
name: "CreateLabelMapping",
|
||||
fn: CreateLabelMapping,
|
||||
},
|
||||
{
|
||||
name: "FindLabels",
|
||||
fn: FindLabels,
|
||||
},
|
||||
{
|
||||
name: "FindLabelByID",
|
||||
fn: FindLabelByID,
|
||||
},
|
||||
{
|
||||
name: "UpdateLabel",
|
||||
fn: UpdateLabel,
|
||||
|
@ -60,6 +74,10 @@ func LabelService(
|
|||
name: "DeleteLabel",
|
||||
fn: DeleteLabel,
|
||||
},
|
||||
{
|
||||
name: "DeleteLabelMapping",
|
||||
fn: DeleteLabelMapping,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -89,17 +107,12 @@ func CreateLabel(
|
|||
{
|
||||
name: "basic create label",
|
||||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
IDGenerator: mock.NewIDGenerator(labelOneID, t),
|
||||
Labels: []*platform.Label{},
|
||||
},
|
||||
args: args{
|
||||
label: &platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
Name: "Tag2",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -108,12 +121,8 @@ func CreateLabel(
|
|||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag2",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -121,36 +130,34 @@ func CreateLabel(
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate labels fail",
|
||||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
label: &platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
err: &platform.Error{
|
||||
Code: platform.EConflict,
|
||||
Op: platform.OpCreateLabel,
|
||||
Msg: "label Tag1 already exists",
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "duplicate labels fail",
|
||||
// fields: LabelFields{
|
||||
// IDGenerator: mock.NewIDGenerator(labelTwoID, t),
|
||||
// Labels: []*platform.Label{
|
||||
// {
|
||||
// Name: "Tag1",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// args: args{
|
||||
// label: &platform.Label{
|
||||
// Name: "Tag1",
|
||||
// },
|
||||
// },
|
||||
// wants: wants{
|
||||
// labels: []*platform.Label{
|
||||
// {
|
||||
// Name: "Tag1",
|
||||
// },
|
||||
// },
|
||||
// err: &platform.Error{
|
||||
// Code: platform.EConflict,
|
||||
// Op: platform.OpCreateLabel,
|
||||
// Msg: "label Tag1 already exists",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -161,7 +168,7 @@ func CreateLabel(
|
|||
err := s.CreateLabel(ctx, tt.args.label)
|
||||
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
|
||||
|
||||
defer s.DeleteLabel(ctx, *tt.args.label)
|
||||
defer s.DeleteLabel(ctx, tt.args.label.ID)
|
||||
|
||||
labels, err := s.FindLabels(ctx, platform.LabelFilter{})
|
||||
if err != nil {
|
||||
|
@ -197,12 +204,12 @@ func FindLabels(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
ID: MustIDBase16(labelTwoID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -212,12 +219,12 @@ func FindLabels(
|
|||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
ID: MustIDBase16(labelTwoID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -227,30 +234,25 @@ func FindLabels(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketTwoID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelTwoID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
filter: platform.LabelFilter{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -272,13 +274,88 @@ func FindLabels(
|
|||
}
|
||||
}
|
||||
|
||||
func FindLabelByID(
|
||||
init func(LabelFields, *testing.T) (platform.LabelService, string, func()),
|
||||
t *testing.T,
|
||||
) {
|
||||
type args struct {
|
||||
id platform.ID
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
label *platform.Label
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields LabelFields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "find label by ID",
|
||||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
{
|
||||
ID: MustIDBase16(labelTwoID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
id: MustIDBase16(labelOneID),
|
||||
},
|
||||
wants: wants{
|
||||
label: &platform.Label{
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "label does not exist",
|
||||
fields: LabelFields{
|
||||
Labels: []*platform.Label{},
|
||||
},
|
||||
args: args{
|
||||
id: MustIDBase16(labelOneID),
|
||||
},
|
||||
wants: wants{
|
||||
err: &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: platform.OpFindLabelByID,
|
||||
Msg: "label not found",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s, opPrefix, done := init(tt.fields, t)
|
||||
defer done()
|
||||
ctx := context.Background()
|
||||
label, err := s.FindLabelByID(ctx, tt.args.id)
|
||||
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
|
||||
|
||||
if diff := cmp.Diff(label, tt.wants.label, labelCmpOptions...); diff != "" {
|
||||
t.Errorf("labels are different -got/+want\ndiff %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLabel(
|
||||
init func(LabelFields, *testing.T) (platform.LabelService, string, func()),
|
||||
t *testing.T,
|
||||
) {
|
||||
type args struct {
|
||||
label platform.Label
|
||||
update platform.LabelUpdate
|
||||
labelID platform.ID
|
||||
update platform.LabelUpdate
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
|
@ -296,16 +373,13 @@ func UpdateLabel(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
label: platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
labelID: MustIDBase16(labelOneID),
|
||||
update: platform.LabelUpdate{
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
|
@ -315,8 +389,8 @@ func UpdateLabel(
|
|||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -329,8 +403,8 @@ func UpdateLabel(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
"description": "description",
|
||||
|
@ -339,10 +413,7 @@ func UpdateLabel(
|
|||
},
|
||||
},
|
||||
args: args{
|
||||
label: platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
labelID: MustIDBase16(labelOneID),
|
||||
update: platform.LabelUpdate{
|
||||
Properties: map[string]string{
|
||||
"color": "abc123",
|
||||
|
@ -352,8 +423,8 @@ func UpdateLabel(
|
|||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
Properties: map[string]string{
|
||||
"color": "abc123",
|
||||
"description": "description",
|
||||
|
@ -367,8 +438,8 @@ func UpdateLabel(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
"description": "description",
|
||||
|
@ -377,10 +448,7 @@ func UpdateLabel(
|
|||
},
|
||||
},
|
||||
args: args{
|
||||
label: platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
labelID: MustIDBase16(labelOneID),
|
||||
update: platform.LabelUpdate{
|
||||
Properties: map[string]string{
|
||||
"description": "",
|
||||
|
@ -390,8 +458,8 @@ func UpdateLabel(
|
|||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
},
|
||||
|
@ -443,10 +511,7 @@ func UpdateLabel(
|
|||
Labels: []*platform.Label{},
|
||||
},
|
||||
args: args{
|
||||
label: platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
labelID: MustIDBase16(labelOneID),
|
||||
update: platform.LabelUpdate{
|
||||
Properties: map[string]string{
|
||||
"color": "fff000",
|
||||
|
@ -458,6 +523,7 @@ func UpdateLabel(
|
|||
err: &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: platform.OpUpdateLabel,
|
||||
Msg: "label not found",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -468,7 +534,7 @@ func UpdateLabel(
|
|||
s, opPrefix, done := init(tt.fields, t)
|
||||
defer done()
|
||||
ctx := context.Background()
|
||||
_, err := s.UpdateLabel(ctx, &tt.args.label, tt.args.update)
|
||||
_, err := s.UpdateLabel(ctx, tt.args.labelID, tt.args.update)
|
||||
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
|
||||
|
||||
labels, err := s.FindLabels(ctx, platform.LabelFilter{})
|
||||
|
@ -487,7 +553,7 @@ func DeleteLabel(
|
|||
t *testing.T,
|
||||
) {
|
||||
type args struct {
|
||||
label platform.Label
|
||||
labelID platform.ID
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
|
@ -505,26 +571,23 @@ func DeleteLabel(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
ID: MustIDBase16(labelTwoID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
label: platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
labelID: MustIDBase16(labelOneID),
|
||||
},
|
||||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
ID: MustIDBase16(labelTwoID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -534,27 +597,25 @@ func DeleteLabel(
|
|||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
label: platform.Label{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag2",
|
||||
},
|
||||
labelID: MustIDBase16(labelTwoID),
|
||||
},
|
||||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
Name: "Tag1",
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
err: &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: platform.OpDeleteLabel,
|
||||
Msg: "label not found",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -565,7 +626,7 @@ func DeleteLabel(
|
|||
s, opPrefix, done := init(tt.fields, t)
|
||||
defer done()
|
||||
ctx := context.Background()
|
||||
err := s.DeleteLabel(ctx, tt.args.label)
|
||||
err := s.DeleteLabel(ctx, tt.args.labelID)
|
||||
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
|
||||
|
||||
labels, err := s.FindLabels(ctx, platform.LabelFilter{})
|
||||
|
@ -578,3 +639,192 @@ func DeleteLabel(
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func CreateLabelMapping(
|
||||
init func(LabelFields, *testing.T) (platform.LabelService, string, func()),
|
||||
t *testing.T,
|
||||
) {
|
||||
type args struct {
|
||||
mapping *platform.LabelMapping
|
||||
filter *platform.LabelMappingFilter
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
labels []*platform.Label
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields LabelFields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "create label mapping",
|
||||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
mapping: &platform.LabelMapping{
|
||||
LabelID: IDPtr(MustIDBase16(labelOneID)),
|
||||
ResourceID: IDPtr(MustIDBase16(bucketOneID)),
|
||||
},
|
||||
filter: &platform.LabelMappingFilter{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
labels: []*platform.Label{
|
||||
{
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mapping to a nonexistent label",
|
||||
fields: LabelFields{
|
||||
IDGenerator: mock.NewIDGenerator(labelOneID, t),
|
||||
Labels: []*platform.Label{},
|
||||
},
|
||||
args: args{
|
||||
mapping: &platform.LabelMapping{
|
||||
LabelID: IDPtr(MustIDBase16(labelOneID)),
|
||||
ResourceID: IDPtr(MustIDBase16(bucketOneID)),
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &platform.Error{
|
||||
Code: platform.ENotFound,
|
||||
Op: platform.OpDeleteLabel,
|
||||
Msg: "label not found",
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "duplicate label mappings",
|
||||
// fields: LabelFields{
|
||||
// IDGenerator: mock.NewIDGenerator(labelOneID, t),
|
||||
// Labels: []*platform.Label{},
|
||||
// },
|
||||
// args: args{
|
||||
// label: &platform.Label{
|
||||
// Name: "Tag2",
|
||||
// Properties: map[string]string{
|
||||
// "color": "fff000",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// wants: wants{
|
||||
// labels: []*platform.Label{
|
||||
// {
|
||||
// ID: MustIDBase16(labelOneID),
|
||||
// Name: "Tag2",
|
||||
// Properties: map[string]string{
|
||||
// "color": "fff000",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s, opPrefix, done := init(tt.fields, t)
|
||||
defer done()
|
||||
ctx := context.Background()
|
||||
err := s.CreateLabelMapping(ctx, tt.args.mapping)
|
||||
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
|
||||
|
||||
defer s.DeleteLabelMapping(ctx, tt.args.mapping)
|
||||
|
||||
if tt.args.filter == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := s.FindResourceLabels(ctx, *tt.args.filter)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve labels: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
|
||||
t.Errorf("labels are different -got/+want\ndiff %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteLabelMapping(
|
||||
init func(LabelFields, *testing.T) (platform.LabelService, string, func()),
|
||||
t *testing.T,
|
||||
) {
|
||||
type args struct {
|
||||
mapping *platform.LabelMapping
|
||||
filter platform.LabelMappingFilter
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
labels []*platform.Label
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields LabelFields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "delete label mapping",
|
||||
fields: LabelFields{
|
||||
Labels: []*platform.Label{
|
||||
{
|
||||
ID: MustIDBase16(labelOneID),
|
||||
Name: "Tag1",
|
||||
},
|
||||
},
|
||||
Mappings: []*platform.LabelMapping{
|
||||
{
|
||||
LabelID: IDPtr(MustIDBase16(labelOneID)),
|
||||
ResourceID: IDPtr(MustIDBase16(bucketOneID)),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
mapping: &platform.LabelMapping{
|
||||
LabelID: IDPtr(MustIDBase16(labelOneID)),
|
||||
ResourceID: IDPtr(MustIDBase16(bucketOneID)),
|
||||
},
|
||||
filter: platform.LabelMappingFilter{
|
||||
ResourceID: MustIDBase16(bucketOneID),
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
labels: []*platform.Label{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s, opPrefix, done := init(tt.fields, t)
|
||||
defer done()
|
||||
ctx := context.Background()
|
||||
err := s.DeleteLabelMapping(ctx, tt.args.mapping)
|
||||
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
|
||||
|
||||
labels, err := s.FindResourceLabels(ctx, tt.args.filter)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve labels: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
|
||||
t.Errorf("labels are different -got/+want\ndiff %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue