Implement support for labels on compose resources
This commit is contained in:
@@ -11,6 +11,7 @@ endpoints:
|
||||
path: "/namespace"
|
||||
authentication: []
|
||||
imports:
|
||||
- github.com/cortezaproject/corteza-server/pkg/label
|
||||
- sqlxTypes github.com/jmoiron/sqlx/types
|
||||
- time
|
||||
apis:
|
||||
@@ -31,6 +32,10 @@ endpoints:
|
||||
- type: uint
|
||||
name: limit
|
||||
title: Limit
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: string
|
||||
name: pageCursor
|
||||
title: Page cursor
|
||||
@@ -47,6 +52,10 @@ endpoints:
|
||||
name: name
|
||||
required: true
|
||||
title: Name
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: string
|
||||
name: slug
|
||||
required: false
|
||||
@@ -96,6 +105,10 @@ endpoints:
|
||||
name: meta
|
||||
required: true
|
||||
title: Meta data
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: "*time.Time"
|
||||
name: updatedAt
|
||||
required: false
|
||||
@@ -132,6 +145,7 @@ endpoints:
|
||||
authentication: []
|
||||
imports:
|
||||
- sqlxTypes github.com/jmoiron/sqlx/types
|
||||
- github.com/cortezaproject/corteza-server/pkg/label
|
||||
parameters:
|
||||
path:
|
||||
- type: uint64
|
||||
@@ -157,6 +171,10 @@ endpoints:
|
||||
name: handle
|
||||
required: false
|
||||
title: Search by handle
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: uint
|
||||
name: limit
|
||||
title: Limit
|
||||
@@ -196,6 +214,10 @@ endpoints:
|
||||
name: weight
|
||||
required: false
|
||||
title: Page tree weight
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: bool
|
||||
name: visible
|
||||
required: false
|
||||
@@ -254,6 +276,10 @@ endpoints:
|
||||
name: weight
|
||||
required: false
|
||||
title: Page tree weight
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: bool
|
||||
name: visible
|
||||
required: false
|
||||
@@ -331,6 +357,7 @@ endpoints:
|
||||
imports:
|
||||
- sqlxTypes github.com/jmoiron/sqlx/types
|
||||
- github.com/cortezaproject/corteza-server/compose/types
|
||||
- github.com/cortezaproject/corteza-server/pkg/label
|
||||
- time
|
||||
apis:
|
||||
- name: list
|
||||
@@ -357,6 +384,10 @@ endpoints:
|
||||
- type: string
|
||||
name: pageCursor
|
||||
title: Page cursor
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: string
|
||||
name: sort
|
||||
title: Sort items
|
||||
@@ -382,6 +413,10 @@ endpoints:
|
||||
name: meta
|
||||
required: true
|
||||
title: Module meta data
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Module labels
|
||||
parser: label.ParseStrings
|
||||
- name: read
|
||||
method: GET
|
||||
title: Read module
|
||||
@@ -423,6 +458,10 @@ endpoints:
|
||||
name: updatedAt
|
||||
required: false
|
||||
title: Last update (or creation) date
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Module labels
|
||||
parser: label.ParseStrings
|
||||
- name: delete
|
||||
method: DELETE
|
||||
title: Delete module
|
||||
@@ -454,6 +493,7 @@ endpoints:
|
||||
path: "/namespace/{namespaceID}/module/{moduleID}/record"
|
||||
authentication: []
|
||||
imports:
|
||||
- github.com/cortezaproject/corteza-server/pkg/label
|
||||
- github.com/cortezaproject/corteza-server/compose/types
|
||||
parameters:
|
||||
path:
|
||||
@@ -498,6 +538,10 @@ endpoints:
|
||||
type: string
|
||||
required: false
|
||||
title: Filtering condition (same as query, deprecated)
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- name: deleted
|
||||
required: false
|
||||
title: Exclude (0, default), include (1) or return only (2) deleted records
|
||||
@@ -606,6 +650,10 @@ endpoints:
|
||||
name: records
|
||||
required: false
|
||||
title: Records
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- name: read
|
||||
method: GET
|
||||
title: Read records by ID from module section
|
||||
@@ -635,6 +683,10 @@ endpoints:
|
||||
name: records
|
||||
required: false
|
||||
title: Records
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- name: bulkDelete
|
||||
method: DELETE
|
||||
title: Delete record row from module section
|
||||
@@ -712,6 +764,7 @@ endpoints:
|
||||
authentication: []
|
||||
imports:
|
||||
- sqlxTypes github.com/jmoiron/sqlx/types
|
||||
- github.com/cortezaproject/corteza-server/pkg/label
|
||||
- time
|
||||
parameters:
|
||||
path:
|
||||
@@ -734,6 +787,10 @@ endpoints:
|
||||
required: false
|
||||
title: Search charts by handle
|
||||
type: string
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: uint
|
||||
name: limit
|
||||
title: Limit
|
||||
@@ -761,6 +818,10 @@ endpoints:
|
||||
title: Chart handle
|
||||
type: string
|
||||
required: false
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- name: read
|
||||
method: GET
|
||||
title: Read charts by ID
|
||||
@@ -794,6 +855,10 @@ endpoints:
|
||||
title: Chart handle
|
||||
type: string
|
||||
required: false
|
||||
- type: map[string]string
|
||||
name: labels
|
||||
title: Labels
|
||||
parser: label.ParseStrings
|
||||
- type: "*time.Time"
|
||||
name: updatedAt
|
||||
required: false
|
||||
|
||||
@@ -54,6 +54,7 @@ func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{},
|
||||
|
||||
Handle: r.Handle,
|
||||
Query: r.Query,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -75,6 +76,7 @@ func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface
|
||||
NamespaceID: r.NamespaceID,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
|
||||
if len(r.Config) > 2 {
|
||||
@@ -101,6 +103,7 @@ func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface
|
||||
Handle: r.Handle,
|
||||
NamespaceID: r.NamespaceID,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{
|
||||
Query: r.Query,
|
||||
Name: r.Name,
|
||||
Handle: r.Handle,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -108,6 +109,7 @@ func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interf
|
||||
Handle: r.Handle,
|
||||
Fields: r.Fields,
|
||||
Meta: r.Meta,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -125,6 +127,7 @@ func (ctrl *Module) Update(ctx context.Context, r *request.ModuleUpdate) (interf
|
||||
Handle: r.Handle,
|
||||
Fields: r.Fields,
|
||||
Meta: r.Meta,
|
||||
Labels: r.Labels,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,8 +58,9 @@ func (ctrl Namespace) List(ctx context.Context, r *request.NamespaceList) (inter
|
||||
var (
|
||||
err error
|
||||
f = types.NamespaceFilter{
|
||||
Query: r.Query,
|
||||
Slug: r.Slug,
|
||||
Query: r.Query,
|
||||
Slug: r.Slug,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -82,6 +83,7 @@ func (ctrl Namespace) Create(ctx context.Context, r *request.NamespaceCreate) (i
|
||||
Name: r.Name,
|
||||
Slug: r.Slug,
|
||||
Enabled: r.Enabled,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -106,6 +108,7 @@ func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (i
|
||||
Name: r.Name,
|
||||
Slug: r.Slug,
|
||||
Enabled: r.Enabled,
|
||||
Labels: r.Labels,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,6 +58,7 @@ func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, e
|
||||
f = types.PageFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
ParentID: r.SelfID,
|
||||
Labels: r.Labels,
|
||||
|
||||
Handle: r.Handle,
|
||||
Query: r.Query,
|
||||
@@ -93,6 +94,7 @@ func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{
|
||||
Description: r.Description,
|
||||
Visible: r.Visible,
|
||||
Weight: r.Weight,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -128,6 +130,7 @@ func (ctrl *Page) Update(ctx context.Context, r *request.PageUpdate) (interface{
|
||||
Description: r.Description,
|
||||
Visible: r.Visible,
|
||||
Weight: r.Weight,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{
|
||||
f = types.RecordFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
ModuleID: r.ModuleID,
|
||||
Labels: r.Labels,
|
||||
Deleted: filter.State(r.Deleted),
|
||||
}
|
||||
)
|
||||
@@ -153,6 +154,7 @@ func (ctrl *Record) Create(ctx context.Context, r *request.RecordCreate) (interf
|
||||
NamespaceID: r.NamespaceID,
|
||||
ModuleID: r.ModuleID,
|
||||
Values: r.Values,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
oo = append(oo, &types.RecordBulkOperation{
|
||||
Record: rr,
|
||||
@@ -201,6 +203,7 @@ func (ctrl *Record) Update(ctx context.Context, r *request.RecordUpdate) (interf
|
||||
NamespaceID: r.NamespaceID,
|
||||
ModuleID: r.ModuleID,
|
||||
Values: r.Values,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
oo = append(oo, &types.RecordBulkOperation{
|
||||
Record: rr,
|
||||
|
||||
@@ -11,6 +11,7 @@ package request
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/go-chi/chi"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
@@ -47,6 +48,11 @@ type (
|
||||
// Search charts by handle
|
||||
Handle string
|
||||
|
||||
// Labels GET parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
@@ -83,6 +89,11 @@ type (
|
||||
//
|
||||
// Chart handle
|
||||
Handle string
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
ChartRead struct {
|
||||
@@ -123,6 +134,11 @@ type (
|
||||
// Chart handle
|
||||
Handle string
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// UpdatedAt POST parameter
|
||||
//
|
||||
// Last update (or creation) date
|
||||
@@ -153,6 +169,7 @@ func (r ChartList) Auditable() map[string]interface{} {
|
||||
"namespaceID": r.NamespaceID,
|
||||
"query": r.Query,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
"limit": r.Limit,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
@@ -174,6 +191,11 @@ func (r ChartList) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartList) GetLimit() uint {
|
||||
return r.Limit
|
||||
@@ -218,6 +240,17 @@ func (r *ChartList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
@@ -265,6 +298,7 @@ func (r ChartCreate) Auditable() map[string]interface{} {
|
||||
"config": r.Config,
|
||||
"name": r.Name,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +322,11 @@ func (r ChartCreate) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartCreate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ChartCreate) Fill(req *http.Request) (err error) {
|
||||
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||
@@ -328,6 +367,18 @@ func (r *ChartCreate) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -415,6 +466,7 @@ func (r ChartUpdate) Auditable() map[string]interface{} {
|
||||
"config": r.Config,
|
||||
"name": r.Name,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
"updatedAt": r.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -444,6 +496,11 @@ func (r ChartUpdate) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ChartUpdate) GetUpdatedAt() *time.Time {
|
||||
return r.UpdatedAt
|
||||
@@ -490,6 +547,18 @@ func (r *ChartUpdate) Fill(req *http.Request) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["updatedAt"]; ok && len(val) > 0 {
|
||||
r.UpdatedAt, err = payload.ParseISODatePtrWithErr(val[0])
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/go-chi/chi"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
@@ -63,6 +64,11 @@ type (
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
|
||||
// Labels GET parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Sort GET parameter
|
||||
//
|
||||
// Sort items
|
||||
@@ -94,6 +100,11 @@ type (
|
||||
//
|
||||
// Module meta data
|
||||
Meta sqlxTypes.JSONText
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Module labels
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
ModuleRead struct {
|
||||
@@ -143,6 +154,11 @@ type (
|
||||
//
|
||||
// Last update (or creation) date
|
||||
UpdatedAt *time.Time
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Module labels
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
ModuleDelete struct {
|
||||
@@ -189,6 +205,7 @@ func (r ModuleList) Auditable() map[string]interface{} {
|
||||
"handle": r.Handle,
|
||||
"limit": r.Limit,
|
||||
"pageCursor": r.PageCursor,
|
||||
"labels": r.Labels,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
}
|
||||
@@ -223,6 +240,11 @@ func (r ModuleList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ModuleList) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ModuleList) GetSort() string {
|
||||
return r.Sort
|
||||
@@ -275,6 +297,17 @@ func (r *ModuleList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["sort"]; ok && len(val) > 0 {
|
||||
r.Sort, err = val[0], nil
|
||||
if err != nil {
|
||||
@@ -311,6 +344,7 @@ func (r ModuleCreate) Auditable() map[string]interface{} {
|
||||
"handle": r.Handle,
|
||||
"fields": r.Fields,
|
||||
"meta": r.Meta,
|
||||
"labels": r.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +373,11 @@ func (r ModuleCreate) GetMeta() sqlxTypes.JSONText {
|
||||
return r.Meta
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ModuleCreate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ModuleCreate) Fill(req *http.Request) (err error) {
|
||||
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||
@@ -386,6 +425,18 @@ func (r *ModuleCreate) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -475,6 +526,7 @@ func (r ModuleUpdate) Auditable() map[string]interface{} {
|
||||
"fields": r.Fields,
|
||||
"meta": r.Meta,
|
||||
"updatedAt": r.UpdatedAt,
|
||||
"labels": r.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,6 +565,11 @@ func (r ModuleUpdate) GetUpdatedAt() *time.Time {
|
||||
return r.UpdatedAt
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r ModuleUpdate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *ModuleUpdate) Fill(req *http.Request) (err error) {
|
||||
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||
@@ -567,6 +624,18 @@ func (r *ModuleUpdate) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ package request
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/go-chi/chi"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
@@ -47,6 +48,11 @@ type (
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// Labels GET parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Page cursor
|
||||
@@ -64,6 +70,11 @@ type (
|
||||
// Name
|
||||
Name string
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Slug POST parameter
|
||||
//
|
||||
// Slug (url path part)
|
||||
@@ -113,6 +124,11 @@ type (
|
||||
// Meta data
|
||||
Meta sqlxTypes.JSONText
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// UpdatedAt POST parameter
|
||||
//
|
||||
// Last update (or creation) date
|
||||
@@ -150,6 +166,7 @@ func (r NamespaceList) Auditable() map[string]interface{} {
|
||||
"query": r.Query,
|
||||
"slug": r.Slug,
|
||||
"limit": r.Limit,
|
||||
"labels": r.Labels,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
@@ -170,6 +187,11 @@ func (r NamespaceList) GetLimit() uint {
|
||||
return r.Limit
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NamespaceList) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NamespaceList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
@@ -215,6 +237,17 @@ func (r *NamespaceList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
@@ -241,6 +274,7 @@ func NewNamespaceCreate() *NamespaceCreate {
|
||||
func (r NamespaceCreate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"name": r.Name,
|
||||
"labels": r.Labels,
|
||||
"slug": r.Slug,
|
||||
"enabled": r.Enabled,
|
||||
"meta": r.Meta,
|
||||
@@ -252,6 +286,11 @@ func (r NamespaceCreate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NamespaceCreate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NamespaceCreate) GetSlug() string {
|
||||
return r.Slug
|
||||
@@ -294,6 +333,18 @@ func (r *NamespaceCreate) Fill(req *http.Request) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["slug"]; ok && len(val) > 0 {
|
||||
r.Slug, err = val[0], nil
|
||||
if err != nil {
|
||||
@@ -377,6 +428,7 @@ func (r NamespaceUpdate) Auditable() map[string]interface{} {
|
||||
"slug": r.Slug,
|
||||
"enabled": r.Enabled,
|
||||
"meta": r.Meta,
|
||||
"labels": r.Labels,
|
||||
"updatedAt": r.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -406,6 +458,11 @@ func (r NamespaceUpdate) GetMeta() sqlxTypes.JSONText {
|
||||
return r.Meta
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NamespaceUpdate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r NamespaceUpdate) GetUpdatedAt() *time.Time {
|
||||
return r.UpdatedAt
|
||||
@@ -459,6 +516,18 @@ func (r *NamespaceUpdate) Fill(req *http.Request) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["updatedAt"]; ok && len(val) > 0 {
|
||||
r.UpdatedAt, err = payload.ParseISODatePtrWithErr(val[0])
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ package request
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/go-chi/chi"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
@@ -51,6 +52,11 @@ type (
|
||||
// Search by handle
|
||||
Handle string
|
||||
|
||||
// Labels GET parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
@@ -103,6 +109,11 @@ type (
|
||||
// Page tree weight
|
||||
Weight int
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Visible POST parameter
|
||||
//
|
||||
// Visible in navigation
|
||||
@@ -174,6 +185,11 @@ type (
|
||||
// Page tree weight
|
||||
Weight int
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Visible POST parameter
|
||||
//
|
||||
// Visible in navigation
|
||||
@@ -261,6 +277,7 @@ func (r PageList) Auditable() map[string]interface{} {
|
||||
"selfID": r.SelfID,
|
||||
"query": r.Query,
|
||||
"handle": r.Handle,
|
||||
"labels": r.Labels,
|
||||
"limit": r.Limit,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
@@ -287,6 +304,11 @@ func (r PageList) GetHandle() string {
|
||||
return r.Handle
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageList) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageList) GetLimit() uint {
|
||||
return r.Limit
|
||||
@@ -337,6 +359,17 @@ func (r *PageList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
@@ -387,6 +420,7 @@ func (r PageCreate) Auditable() map[string]interface{} {
|
||||
"handle": r.Handle,
|
||||
"description": r.Description,
|
||||
"weight": r.Weight,
|
||||
"labels": r.Labels,
|
||||
"visible": r.Visible,
|
||||
"blocks": r.Blocks,
|
||||
}
|
||||
@@ -427,6 +461,11 @@ func (r PageCreate) GetWeight() int {
|
||||
return r.Weight
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageCreate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageCreate) GetVisible() bool {
|
||||
return r.Visible
|
||||
@@ -499,6 +538,18 @@ func (r *PageCreate) Fill(req *http.Request) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["visible"]; ok && len(val) > 0 {
|
||||
r.Visible, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
@@ -647,6 +698,7 @@ func (r PageUpdate) Auditable() map[string]interface{} {
|
||||
"handle": r.Handle,
|
||||
"description": r.Description,
|
||||
"weight": r.Weight,
|
||||
"labels": r.Labels,
|
||||
"visible": r.Visible,
|
||||
"blocks": r.Blocks,
|
||||
}
|
||||
@@ -692,6 +744,11 @@ func (r PageUpdate) GetWeight() int {
|
||||
return r.Weight
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdate) GetVisible() bool {
|
||||
return r.Visible
|
||||
@@ -764,6 +821,18 @@ func (r *PageUpdate) Fill(req *http.Request) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["visible"]; ok && len(val) > 0 {
|
||||
r.Visible, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/go-chi/chi"
|
||||
"io"
|
||||
@@ -78,6 +79,11 @@ type (
|
||||
// Filtering condition (same as query, deprecated)
|
||||
Filter string
|
||||
|
||||
// Labels GET parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Deleted GET parameter
|
||||
//
|
||||
// Exclude (0, default), include (1) or return only (2) deleted records
|
||||
@@ -239,6 +245,11 @@ type (
|
||||
//
|
||||
// Records
|
||||
Records types.RecordBulkSet
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
RecordRead struct {
|
||||
@@ -283,6 +294,11 @@ type (
|
||||
//
|
||||
// Records
|
||||
Records types.RecordBulkSet
|
||||
|
||||
// Labels POST parameter
|
||||
//
|
||||
// Labels
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
RecordBulkDelete struct {
|
||||
@@ -507,6 +523,7 @@ func (r RecordList) Auditable() map[string]interface{} {
|
||||
"moduleID": r.ModuleID,
|
||||
"query": r.Query,
|
||||
"filter": r.Filter,
|
||||
"labels": r.Labels,
|
||||
"deleted": r.Deleted,
|
||||
"limit": r.Limit,
|
||||
"pageCursor": r.PageCursor,
|
||||
@@ -534,6 +551,11 @@ func (r RecordList) GetFilter() string {
|
||||
return r.Filter
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordList) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordList) GetDeleted() uint {
|
||||
return r.Deleted
|
||||
@@ -583,6 +605,17 @@ func (r *RecordList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["deleted"]; ok && len(val) > 0 {
|
||||
r.Deleted, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
@@ -1108,6 +1141,7 @@ func (r RecordCreate) Auditable() map[string]interface{} {
|
||||
"moduleID": r.ModuleID,
|
||||
"values": r.Values,
|
||||
"records": r.Records,
|
||||
"labels": r.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1131,6 +1165,11 @@ func (r RecordCreate) GetRecords() types.RecordBulkSet {
|
||||
return r.Records
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordCreate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *RecordCreate) Fill(req *http.Request) (err error) {
|
||||
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||
@@ -1164,6 +1203,18 @@ func (r *RecordCreate) Fill(req *http.Request) (err error) {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1269,6 +1320,7 @@ func (r RecordUpdate) Auditable() map[string]interface{} {
|
||||
"recordID": r.RecordID,
|
||||
"values": r.Values,
|
||||
"records": r.Records,
|
||||
"labels": r.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1297,6 +1349,11 @@ func (r RecordUpdate) GetRecords() types.RecordBulkSet {
|
||||
return r.Records
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordUpdate) GetLabels() map[string]string {
|
||||
return r.Labels
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *RecordUpdate) Fill(req *http.Request) (err error) {
|
||||
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||
@@ -1330,6 +1387,18 @@ func (r *RecordUpdate) Fill(req *http.Request) (err error) {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
|
||||
if val, ok := req.Form["labels[]"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["labels"]; ok {
|
||||
r.Labels, err = label.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -37,7 +39,15 @@ type (
|
||||
DeleteByID(namespaceID, chartID uint64) error
|
||||
}
|
||||
|
||||
chartUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error)
|
||||
chartUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error)
|
||||
|
||||
chartChanges uint8
|
||||
)
|
||||
|
||||
const (
|
||||
chartUnchanged chartChanges = 0
|
||||
chartChanged chartChanges = 1
|
||||
chartLabelsChanged chartChanges = 2
|
||||
)
|
||||
|
||||
func Chart() ChartService {
|
||||
@@ -77,10 +87,33 @@ func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Cha
|
||||
aProps.setNamespace(ns)
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
svc.ctx,
|
||||
svc.store,
|
||||
types.Chart{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
filter.ChartID...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if set, f, err = store.SearchComposeCharts(svc.ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, toLabeledCharts(set)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -116,32 +149,40 @@ func (svc chart) Create(new *types.Chart) (*types.Chart, error) {
|
||||
aProps = &chartActionProps{changed: new}
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
if ns, err = loadNamespace(svc.ctx, svc.store, new.NamespaceID); err != nil {
|
||||
if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
|
||||
if !svc.ac.CanCreateChart(svc.ctx, ns) {
|
||||
if !svc.ac.CanCreateChart(ctx, ns) {
|
||||
return ChartErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
if err = svc.uniqueCheck(new); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
new.ID = nextID()
|
||||
new.CreatedAt = *now()
|
||||
new.UpdatedAt = nil
|
||||
new.DeletedAt = nil
|
||||
|
||||
if err = svc.uniqueCheck(new); err != nil {
|
||||
if err = store.CreateComposeChart(ctx, s, new); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.CreateComposeChart(svc.ctx, svc.store, new)
|
||||
}()
|
||||
if err = label.Create(ctx, s, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return new, svc.recordAction(svc.ctx, aProps, ChartActionCreate, err)
|
||||
}
|
||||
@@ -181,6 +222,10 @@ func (svc chart) lookup(namespaceID uint64, lookup func(*chartActionProps) (*typ
|
||||
return ChartErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -189,7 +234,7 @@ func (svc chart) lookup(namespaceID uint64, lookup func(*chartActionProps) (*typ
|
||||
|
||||
func (svc chart) updater(namespaceID, chartID uint64, action func(...*chartActionProps) *chartAction, fn chartUpdateHandler) (*types.Chart, error) {
|
||||
var (
|
||||
changed bool
|
||||
changes chartChanges
|
||||
ns *types.Namespace
|
||||
c *types.Chart
|
||||
aProps = &chartActionProps{chart: &types.Chart{ID: chartID, NamespaceID: namespaceID}}
|
||||
@@ -202,16 +247,30 @@ func (svc chart) updater(namespaceID, chartID uint64, action func(...*chartActio
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
aProps.setChanged(c)
|
||||
|
||||
if changed, err = fn(ctx, ns, c); err != nil {
|
||||
if changes, err = fn(ctx, ns, c); err != nil {
|
||||
return err
|
||||
} else if !changed {
|
||||
return
|
||||
}
|
||||
|
||||
return store.UpdateComposeChart(ctx, s, c)
|
||||
if changes&chartChanged > 0 {
|
||||
if err = store.UpdateComposeChart(ctx, s, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if changes&chartLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return c, svc.recordAction(svc.ctx, aProps, action, err)
|
||||
@@ -228,57 +287,79 @@ func (svc chart) uniqueCheck(c *types.Chart) (err error) {
|
||||
}
|
||||
|
||||
func (svc chart) handleUpdate(upd *types.Chart) chartUpdateHandler {
|
||||
return func(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) {
|
||||
if isStale(upd.UpdatedAt, c.UpdatedAt, c.CreatedAt) {
|
||||
return false, ChartErrStaleData()
|
||||
return func(ctx context.Context, ns *types.Namespace, res *types.Chart) (changes chartChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return chartUnchanged, ChartErrStaleData()
|
||||
}
|
||||
|
||||
if upd.Handle != c.Handle && !handle.IsValid(upd.Handle) {
|
||||
return false, ChartErrInvalidHandle()
|
||||
if upd.Handle != res.Handle && !handle.IsValid(upd.Handle) {
|
||||
return chartUnchanged, ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err := svc.uniqueCheck(upd); err != nil {
|
||||
return false, err
|
||||
return chartUnchanged, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateChart(svc.ctx, c) {
|
||||
return false, ChartErrNotAllowedToUpdate()
|
||||
if !svc.ac.CanUpdateChart(svc.ctx, res) {
|
||||
return chartUnchanged, ChartErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
c.Name = upd.Name
|
||||
c.Handle = upd.Handle
|
||||
c.Config = upd.Config
|
||||
c.UpdatedAt = now()
|
||||
return true, nil
|
||||
if res.Name != upd.Name {
|
||||
changes |= chartChanged
|
||||
res.Name = upd.Name
|
||||
}
|
||||
|
||||
if res.Handle != upd.Handle {
|
||||
changes |= chartChanged
|
||||
res.Handle = upd.Handle
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(upd.Config, res.Config) {
|
||||
changes |= chartChanged
|
||||
res.Config = upd.Config
|
||||
}
|
||||
|
||||
if changes&chartChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= chartLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc chart) handleDelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) {
|
||||
func (svc chart) handleDelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error) {
|
||||
if !svc.ac.CanDeleteChart(ctx, c) {
|
||||
return false, ChartErrNotAllowedToDelete()
|
||||
return chartUnchanged, ChartErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if c.DeletedAt != nil {
|
||||
// chart already deleted
|
||||
return false, nil
|
||||
return chartUnchanged, nil
|
||||
}
|
||||
|
||||
c.DeletedAt = now()
|
||||
return true, nil
|
||||
return chartChanged, nil
|
||||
}
|
||||
|
||||
func (svc chart) handleUndelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) {
|
||||
func (svc chart) handleUndelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error) {
|
||||
if !svc.ac.CanDeleteChart(ctx, c) {
|
||||
return false, ChartErrNotAllowedToUndelete()
|
||||
return chartUnchanged, ChartErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
if c.DeletedAt == nil {
|
||||
// chart not deleted
|
||||
return false, nil
|
||||
return chartUnchanged, nil
|
||||
}
|
||||
|
||||
c.DeletedAt = nil
|
||||
return true, nil
|
||||
return chartChanged, nil
|
||||
}
|
||||
|
||||
func loadChart(ctx context.Context, s store.Storer, namespaceID, chartID uint64) (ns *types.Namespace, c *types.Chart, err error) {
|
||||
@@ -303,3 +384,19 @@ func loadChart(ctx context.Context, s store.Storer, namespaceID, chartID uint64)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledCharts converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledCharts(set []*types.Chart) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
@@ -46,7 +48,16 @@ type (
|
||||
DeleteByID(namespaceID, moduleID uint64) error
|
||||
}
|
||||
|
||||
moduleUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Module) (bool, bool, error)
|
||||
moduleUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Module) (moduleChanges, error)
|
||||
|
||||
moduleChanges uint8
|
||||
)
|
||||
|
||||
const (
|
||||
moduleUnchanged moduleChanges = 0
|
||||
moduleChanged moduleChanges = 1
|
||||
moduleLabelsChanged moduleChanges = 2
|
||||
moduleFieldsChanged moduleChanges = 4
|
||||
)
|
||||
|
||||
func Module() ModuleService {
|
||||
@@ -82,16 +93,40 @@ func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
|
||||
if ns, err := loadNamespace(svc.ctx, svc.store, filter.NamespaceID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
aProps.setNamespace(ns)
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
svc.ctx,
|
||||
svc.store,
|
||||
types.Module{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
filter.ModuleID...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if set, f, err = store.SearchComposeModules(svc.ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = loadModuleLabels(svc.ctx, svc.store, set...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return loadModuleFields(svc.ctx, svc.store, set...)
|
||||
}()
|
||||
|
||||
@@ -171,12 +206,12 @@ func (svc module) Create(new *types.Module) (*types.Module, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
|
||||
if !svc.ac.CanCreateModule(ctx, ns) {
|
||||
return ModuleErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
|
||||
// Calling before-create scripts
|
||||
if err = svc.eventbus.WaitFor(ctx, event.ModuleBeforeCreate(new, nil, ns)); err != nil {
|
||||
return err
|
||||
@@ -212,6 +247,10 @@ func (svc module) Create(new *types.Module) (*types.Module, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, s, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = svc.eventbus.WaitFor(ctx, event.ModuleAfterCreate(new, nil, ns))
|
||||
return nil
|
||||
})
|
||||
@@ -233,7 +272,7 @@ func (svc module) UndeleteByID(namespaceID, moduleID uint64) error {
|
||||
|
||||
func (svc module) updater(namespaceID, moduleID uint64, action func(...*moduleActionProps) *moduleAction, fn moduleUpdateHandler) (*types.Module, error) {
|
||||
var (
|
||||
moduleChanged, fieldsChanged bool
|
||||
changes moduleChanges
|
||||
|
||||
ns *types.Namespace
|
||||
m, old *types.Module
|
||||
@@ -247,6 +286,10 @@ func (svc module) updater(namespaceID, moduleID uint64, action func(...*moduleAc
|
||||
return
|
||||
}
|
||||
|
||||
if err = loadModuleLabels(svc.ctx, svc.store, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old = m.Clone()
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
@@ -262,17 +305,17 @@ func (svc module) updater(namespaceID, moduleID uint64, action func(...*moduleAc
|
||||
return
|
||||
}
|
||||
|
||||
if moduleChanged, fieldsChanged, err = fn(svc.ctx, ns, m); err != nil {
|
||||
if changes, err = fn(svc.ctx, ns, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if moduleChanged {
|
||||
if changes&moduleChanged > 0 {
|
||||
if err = store.UpdateComposeModule(svc.ctx, svc.store, m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if fieldsChanged {
|
||||
if changes&moduleFieldsChanged > 0 {
|
||||
var (
|
||||
hasRecords bool
|
||||
set types.RecordSet
|
||||
@@ -289,6 +332,12 @@ func (svc module) updater(namespaceID, moduleID uint64, action func(...*moduleAc
|
||||
}
|
||||
}
|
||||
|
||||
if changes&moduleLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, m); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if m.DeletedAt == nil {
|
||||
err = svc.eventbus.WaitFor(svc.ctx, event.ModuleAfterUpdate(m, old, ns))
|
||||
} else {
|
||||
@@ -324,6 +373,10 @@ func (svc module) lookup(namespaceID uint64, lookup func(*moduleActionProps) (*t
|
||||
return ModuleErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
if err = loadModuleLabels(svc.ctx, svc.store, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return loadModuleFields(svc.ctx, svc.store, m)
|
||||
|
||||
}()
|
||||
@@ -348,79 +401,99 @@ func (svc module) uniqueCheck(m *types.Module) (err error) {
|
||||
}
|
||||
|
||||
func (svc module) handleUpdate(upd *types.Module) moduleUpdateHandler {
|
||||
return func(ctx context.Context, ns *types.Namespace, m *types.Module) (mch bool, fch bool, err error) {
|
||||
if isStale(upd.UpdatedAt, m.UpdatedAt, m.CreatedAt) {
|
||||
return false, false, ModuleErrStaleData()
|
||||
return func(ctx context.Context, ns *types.Namespace, res *types.Module) (changes moduleChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return moduleUnchanged, ModuleErrStaleData()
|
||||
}
|
||||
|
||||
if upd.Handle != m.Handle && !handle.IsValid(upd.Handle) {
|
||||
return false, false, ModuleErrInvalidHandle()
|
||||
if upd.Handle != res.Handle && !handle.IsValid(upd.Handle) {
|
||||
return moduleUnchanged, ModuleErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err = svc.uniqueCheck(upd); err != nil {
|
||||
return false, false, err
|
||||
return moduleUnchanged, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateModule(svc.ctx, m) {
|
||||
return false, false, ModuleErrNotAllowedToUpdate()
|
||||
if !svc.ac.CanUpdateModule(svc.ctx, res) {
|
||||
return moduleUnchanged, ModuleErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if m.Name != upd.Name {
|
||||
mch = true
|
||||
m.Name = upd.Name
|
||||
if res.Name != upd.Name {
|
||||
changes |= moduleChanged
|
||||
res.Name = upd.Name
|
||||
}
|
||||
|
||||
if m.Handle != upd.Handle {
|
||||
mch = true
|
||||
m.Handle = upd.Handle
|
||||
if res.Handle != upd.Handle {
|
||||
changes |= moduleChanged
|
||||
res.Handle = upd.Handle
|
||||
}
|
||||
|
||||
if m.Meta.String() != upd.Meta.String() {
|
||||
mch = true
|
||||
m.Meta = upd.Meta
|
||||
{
|
||||
oldMeta := res.Meta.String()
|
||||
if oldMeta == "{}" {
|
||||
oldMeta = ""
|
||||
}
|
||||
|
||||
newMeta := upd.Meta.String()
|
||||
if newMeta == "{}" {
|
||||
newMeta = ""
|
||||
}
|
||||
|
||||
if oldMeta != newMeta {
|
||||
changes |= moduleChanged
|
||||
res.Meta = upd.Meta
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @todo make field-change detection more optimal
|
||||
if len(upd.Fields) > 0 {
|
||||
fch = true
|
||||
m.Fields = upd.Fields
|
||||
if !reflect.DeepEqual(res.Fields, upd.Fields) {
|
||||
changes |= moduleFieldsChanged
|
||||
res.Fields = upd.Fields
|
||||
}
|
||||
|
||||
if mch {
|
||||
m.UpdatedAt = now()
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= moduleLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
if changes&moduleChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
// for now, we assume that
|
||||
return mch, fch, nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc module) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Module) (bool, bool, error) {
|
||||
func (svc module) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Module) (moduleChanges, error) {
|
||||
if !svc.ac.CanDeleteModule(ctx, m) {
|
||||
return false, false, ModuleErrNotAllowedToDelete()
|
||||
return moduleUnchanged, ModuleErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if m.DeletedAt != nil {
|
||||
// module already deleted
|
||||
return false, false, nil
|
||||
return moduleUnchanged, nil
|
||||
}
|
||||
|
||||
m.DeletedAt = now()
|
||||
return true, false, nil
|
||||
return moduleChanged, nil
|
||||
}
|
||||
|
||||
func (svc module) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Module) (bool, bool, error) {
|
||||
func (svc module) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Module) (moduleChanges, error) {
|
||||
if !svc.ac.CanDeleteModule(ctx, m) {
|
||||
return false, false, ModuleErrNotAllowedToUndelete()
|
||||
return moduleUnchanged, ModuleErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
if m.DeletedAt == nil {
|
||||
// module not deleted
|
||||
return false, false, nil
|
||||
return moduleUnchanged, nil
|
||||
}
|
||||
|
||||
m.DeletedAt = nil
|
||||
return true, false, nil
|
||||
return moduleChanged, nil
|
||||
}
|
||||
|
||||
// updates module fields
|
||||
@@ -445,7 +518,7 @@ func updateModuleFields(ctx context.Context, s store.Storer, m *types.Module, ne
|
||||
|
||||
for _, ef := range m.Fields {
|
||||
f := newFields.FindByID(ef.ID)
|
||||
if f != nil || f.DeletedAt == nil {
|
||||
if f == nil || f.DeletedAt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -474,17 +547,39 @@ func updateModuleFields(ctx context.Context, s store.Storer, m *types.Module, ne
|
||||
f.UpdatedAt = now()
|
||||
|
||||
err = store.UpdateComposeModuleField(ctx, s, f)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if label.Changed(f.Labels, e.Labels) {
|
||||
if err = label.Update(ctx, s, f); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// override existing with new field
|
||||
*e = *f
|
||||
|
||||
} else {
|
||||
f.ID = nextID()
|
||||
f.CreatedAt = *now()
|
||||
err = store.CreateComposeModuleField(ctx, s, f)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Update(ctx, s, f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Fields = append(m.Fields, f)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(m.Fields)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -551,3 +646,31 @@ func loadModule(ctx context.Context, s store.Storer, moduleID uint64) (m *types.
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// loadLabeledModules loads labels on one or more modules and their fields
|
||||
//
|
||||
func loadModuleLabels(ctx context.Context, s store.Labels, set ...*types.Module) error {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mll := make([]label.LabeledResource, 0, len(set))
|
||||
fll := make([]label.LabeledResource, 0, len(set)*10)
|
||||
for i := range set {
|
||||
mll = append(mll, set[i])
|
||||
|
||||
for j := range set[i].Fields {
|
||||
fll = append(fll, set[i].Fields[j])
|
||||
}
|
||||
}
|
||||
|
||||
if err := label.Load(ctx, s, mll...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := label.Load(ctx, s, fll...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/store/sqlite3"
|
||||
@@ -17,6 +17,9 @@ func TestModules(t *testing.T) {
|
||||
ctx = context.Background()
|
||||
s, err = sqlite3.ConnectInMemory(ctx)
|
||||
|
||||
// ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
|
||||
// s, err = sqlite3.ConnectInMemoryWithDebug(ctx)
|
||||
|
||||
namespaceID = nextID()
|
||||
ns *types.Namespace
|
||||
)
|
||||
@@ -33,8 +36,8 @@ func TestModules(t *testing.T) {
|
||||
t.Fatalf("failed to truncate compose namespaces: %v", err)
|
||||
}
|
||||
|
||||
if err = s.TruncateComposeCharts(ctx); err != nil {
|
||||
t.Fatalf("failed to truncate compose charts: %v", err)
|
||||
if err = s.TruncateComposeModules(ctx); err != nil {
|
||||
t.Fatalf("failed to truncate compose modules: %v", err)
|
||||
}
|
||||
|
||||
ns = &types.Namespace{Name: "testing", ID: namespaceID, CreatedAt: *now()}
|
||||
@@ -44,56 +47,167 @@ func TestModules(t *testing.T) {
|
||||
|
||||
t.Run("crud", func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
svc := chart{
|
||||
store: s,
|
||||
ctx: context.Background(),
|
||||
ac: AccessControl(&rbac.ServiceAllowAll{}),
|
||||
svc := module{
|
||||
store: s,
|
||||
ctx: context.Background(),
|
||||
ac: AccessControl(&rbac.ServiceAllowAll{}),
|
||||
eventbus: eventbus.New(),
|
||||
}
|
||||
res, err := svc.Create(&types.Chart{Name: "My first chart", NamespaceID: namespaceID})
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
res, err := svc.Create(&types.Module{Name: "My first module", NamespaceID: namespaceID})
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
|
||||
res, err = svc.FindByID(namespaceID, res.ID)
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
|
||||
res, err = svc.FindByHandle(namespaceID, res.Handle)
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
|
||||
res.Name = "Changed"
|
||||
res, err = svc.Update(res)
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
req.NotNil(res.UpdatedAt)
|
||||
req.Equal(res.Name, "Changed")
|
||||
|
||||
res, err = svc.FindByID(namespaceID, res.ID)
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
req.Equal(res.Name, "Changed")
|
||||
|
||||
err = svc.DeleteByID(namespaceID, res.ID)
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
|
||||
// this works because we're allowed to do everything
|
||||
res, err = svc.FindByID(namespaceID, res.ID)
|
||||
req.NoError(unwrapModuleInternal(err))
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
req.NotNil(res.DeletedAt)
|
||||
})
|
||||
|
||||
t.Run("labels", func(t *testing.T) {
|
||||
t.Run("search", func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
svc := module{
|
||||
store: s,
|
||||
ctx: ctx,
|
||||
ac: AccessControl(&rbac.ServiceAllowAll{}),
|
||||
eventbus: eventbus.New(),
|
||||
}
|
||||
|
||||
makeModule := func(n ...string) *types.Module {
|
||||
mod := &types.Module{
|
||||
NamespaceID: namespaceID,
|
||||
Name: n[0],
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
|
||||
for i := 1; i < len(n); i += 2 {
|
||||
mod.Labels[n[i]] = n[i+1]
|
||||
}
|
||||
|
||||
out, err := svc.Create(mod)
|
||||
|
||||
req.NoError(err)
|
||||
return out
|
||||
}
|
||||
|
||||
findModules := func(labels map[string]string, IDs []uint64) types.ModuleSet {
|
||||
f := types.ModuleFilter{NamespaceID: namespaceID, Labels: labels, ModuleID: IDs}
|
||||
set, _, err := svc.Find(f)
|
||||
req.NoError(err)
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
makeModule("labeled module 1", "label1", "value1", "label2", "value2")
|
||||
m2 := makeModule("labeled module 2", "label1", "value1")
|
||||
m3 := makeModule("labeled module 3")
|
||||
|
||||
// return all -- no label/ID filter, return all
|
||||
req.Len(findModules(nil, nil), 3)
|
||||
|
||||
// return 2 - both that have label1=valu1
|
||||
req.Len(findModules(map[string]string{"label1": "value1"}, nil), 2)
|
||||
|
||||
// return 0 - none have foo=foo
|
||||
req.Len(findModules(map[string]string{"missing": "missing"}, nil), 0)
|
||||
|
||||
// one has label2=value2
|
||||
req.Len(findModules(map[string]string{"label2": "value2"}, nil), 1)
|
||||
|
||||
// explicit by ID and label
|
||||
req.Len(findModules(map[string]string{"label1": "value1"}, []uint64{m2.ID}), 1)
|
||||
|
||||
// none with this combo
|
||||
req.Len(findModules(map[string]string{"foo": "foo"}, []uint64{m3.ID}), 0)
|
||||
|
||||
// one with explicit ID (regression) and nil for label filter
|
||||
req.Len(findModules(nil, []uint64{m3.ID}), 1)
|
||||
|
||||
// one with explicit ID (regression) and empty map for label filter
|
||||
req.Len(findModules(map[string]string{}, []uint64{m3.ID}), 1)
|
||||
})
|
||||
|
||||
t.Run("CRUD", func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
svc := module{
|
||||
store: s,
|
||||
ctx: ctx,
|
||||
ac: AccessControl(&rbac.ServiceAllowAll{}),
|
||||
eventbus: eventbus.New(),
|
||||
}
|
||||
|
||||
findAndReturnLabel := func(id uint64) map[string]string {
|
||||
res, err := svc.FindByID(namespaceID, id)
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
return res.Labels
|
||||
}
|
||||
|
||||
// create unlabeled module
|
||||
res, err := svc.Create(&types.Module{Name: "unLabeledIDs", NamespaceID: namespaceID})
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
req.Nil(res.Labels)
|
||||
|
||||
// no labels should be present
|
||||
req.Nil(findAndReturnLabel(res.ID))
|
||||
|
||||
// update the module with labels
|
||||
res.Labels = map[string]string{"label1": "1st"}
|
||||
res, err = svc.Update(res)
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
req.Contains(res.Labels, "label1")
|
||||
|
||||
// must contain the added label
|
||||
req.Contains(findAndReturnLabel(res.ID), "label1")
|
||||
|
||||
res, err = svc.Create(&types.Module{Name: "LabeledIDs", NamespaceID: namespaceID, Labels: map[string]string{"label2": "2nd"}})
|
||||
req.NoError(err)
|
||||
req.NotNil(res)
|
||||
req.Contains(res.Labels, "label2")
|
||||
|
||||
// must contain the added label
|
||||
req.Contains(findAndReturnLabel(res.ID), "label2")
|
||||
|
||||
// update with Labels:nil (should keep labels intact)
|
||||
res.Labels = nil
|
||||
res, err = svc.Update(res)
|
||||
req.NoError(err)
|
||||
|
||||
req.Contains(findAndReturnLabel(res.ID), "label2")
|
||||
|
||||
// update with Labels:empty-map (should remove all labels)
|
||||
res.Labels = map[string]string{}
|
||||
res, err = svc.Update(res)
|
||||
req.NoError(err)
|
||||
|
||||
req.Empty(findAndReturnLabel(res.ID))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func unwrapModuleInternal(err error) error {
|
||||
g := ModuleErrGeneric()
|
||||
for {
|
||||
if errors.Is(err, g) {
|
||||
err = errors.Unwrap(err)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -44,7 +46,14 @@ type (
|
||||
DeleteByID(namespaceID uint64) error
|
||||
}
|
||||
|
||||
namespaceUpdateHandler func(ctx context.Context, ns *types.Namespace) (bool, error)
|
||||
namespaceUpdateHandler func(ctx context.Context, ns *types.Namespace) (namespaceChanges, error)
|
||||
namespaceChanges uint8
|
||||
)
|
||||
|
||||
const (
|
||||
namespaceUnchanged namespaceChanges = 0
|
||||
namespaceChanged namespaceChanges = 1
|
||||
namespaceLabelsChanged namespaceChanges = 2
|
||||
)
|
||||
|
||||
func Namespace() NamespaceService {
|
||||
@@ -80,10 +89,32 @@ func (svc namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet,
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
svc.ctx,
|
||||
svc.store,
|
||||
types.Namespace{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if set, f, err = store.SearchComposeNamespaces(svc.ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, toLabeledNamespaces(set)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -173,6 +204,10 @@ func (svc namespace) Create(new *types.Namespace) (*types.Namespace, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, s, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterCreate(new, nil))
|
||||
return nil
|
||||
})
|
||||
@@ -194,7 +229,7 @@ func (svc namespace) UndeleteByID(namespaceID uint64) error {
|
||||
|
||||
func (svc namespace) updater(namespaceID uint64, action func(...*namespaceActionProps) *namespaceAction, fn namespaceUpdateHandler) (*types.Namespace, error) {
|
||||
var (
|
||||
changed bool
|
||||
changes namespaceChanges
|
||||
ns, old *types.Namespace
|
||||
aProps = &namespaceActionProps{namespace: &types.Namespace{ID: namespaceID}}
|
||||
err error
|
||||
@@ -206,6 +241,10 @@ func (svc namespace) updater(namespaceID uint64, action func(...*namespaceAction
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, ns); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old = ns.Clone()
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
@@ -221,16 +260,22 @@ func (svc namespace) updater(namespaceID uint64, action func(...*namespaceAction
|
||||
return
|
||||
}
|
||||
|
||||
if changed, err = fn(svc.ctx, ns); err != nil {
|
||||
if changes, err = fn(svc.ctx, ns); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if changed {
|
||||
if changes&namespaceChanged > 0 {
|
||||
if err = store.UpdateComposeNamespace(svc.ctx, svc.store, ns); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if changes&namespaceLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, ns); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ns.DeletedAt == nil {
|
||||
err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterUpdate(ns, old))
|
||||
} else {
|
||||
@@ -260,6 +305,10 @@ func (svc namespace) lookup(lookup func(*namespaceActionProps) (*types.Namespace
|
||||
return NamespaceErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, ns); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -277,59 +326,84 @@ func (svc namespace) uniqueCheck(ns *types.Namespace) (err error) {
|
||||
}
|
||||
|
||||
func (svc namespace) handleUpdate(upd *types.Namespace) namespaceUpdateHandler {
|
||||
return func(ctx context.Context, ns *types.Namespace) (bool, error) {
|
||||
if isStale(upd.UpdatedAt, ns.UpdatedAt, ns.CreatedAt) {
|
||||
return false, NamespaceErrStaleData()
|
||||
return func(ctx context.Context, res *types.Namespace) (changes namespaceChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return namespaceUnchanged, NamespaceErrStaleData()
|
||||
}
|
||||
|
||||
if upd.Slug != ns.Slug && !handle.IsValid(upd.Slug) {
|
||||
return false, NamespaceErrInvalidHandle()
|
||||
if upd.Slug != res.Slug && !handle.IsValid(upd.Slug) {
|
||||
return namespaceUnchanged, NamespaceErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err := svc.uniqueCheck(upd); err != nil {
|
||||
return false, err
|
||||
return namespaceUnchanged, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateNamespace(svc.ctx, ns) {
|
||||
return false, NamespaceErrNotAllowedToUpdate()
|
||||
if !svc.ac.CanUpdateNamespace(svc.ctx, res) {
|
||||
return namespaceUnchanged, NamespaceErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
ns.Name = upd.Name
|
||||
ns.Slug = upd.Slug
|
||||
ns.Meta = upd.Meta
|
||||
ns.Enabled = upd.Enabled
|
||||
ns.UpdatedAt = now()
|
||||
if res.Name != upd.Name {
|
||||
changes |= namespaceChanged
|
||||
res.Name = upd.Name
|
||||
}
|
||||
|
||||
return true, nil
|
||||
if res.Slug != upd.Slug {
|
||||
changes |= namespaceChanged
|
||||
res.Slug = upd.Slug
|
||||
}
|
||||
|
||||
if res.Enabled != upd.Enabled {
|
||||
changes |= namespaceChanged
|
||||
res.Enabled = upd.Enabled
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(upd.Meta, res.Meta) {
|
||||
changes |= namespaceChanged
|
||||
res.Meta = upd.Meta
|
||||
}
|
||||
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= namespaceLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
if changes&namespaceChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc namespace) handleDelete(ctx context.Context, ns *types.Namespace) (bool, error) {
|
||||
func (svc namespace) handleDelete(ctx context.Context, ns *types.Namespace) (namespaceChanges, error) {
|
||||
if !svc.ac.CanDeleteNamespace(ctx, ns) {
|
||||
return false, NamespaceErrNotAllowedToDelete()
|
||||
return namespaceUnchanged, NamespaceErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if ns.DeletedAt != nil {
|
||||
// namespace already deleted
|
||||
return false, nil
|
||||
return namespaceUnchanged, nil
|
||||
}
|
||||
|
||||
ns.DeletedAt = now()
|
||||
return true, nil
|
||||
return namespaceChanged, nil
|
||||
}
|
||||
|
||||
func (svc namespace) handleUndelete(ctx context.Context, ns *types.Namespace) (bool, error) {
|
||||
func (svc namespace) handleUndelete(ctx context.Context, ns *types.Namespace) (namespaceChanges, error) {
|
||||
if !svc.ac.CanDeleteNamespace(ctx, ns) {
|
||||
return false, NamespaceErrNotAllowedToUndelete()
|
||||
return namespaceUnchanged, NamespaceErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
if ns.DeletedAt == nil {
|
||||
// namespace not deleted
|
||||
return false, nil
|
||||
return namespaceUnchanged, nil
|
||||
}
|
||||
|
||||
ns.DeletedAt = nil
|
||||
return true, nil
|
||||
return namespaceChanged, nil
|
||||
}
|
||||
|
||||
func loadNamespace(ctx context.Context, s store.Storer, namespaceID uint64) (ns *types.Namespace, err error) {
|
||||
@@ -343,3 +417,19 @@ func loadNamespace(ctx context.Context, s store.Storer, namespaceID uint64) (ns
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledNamespaces converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledNamespaces(set []*types.Namespace) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -45,7 +47,14 @@ type (
|
||||
Reorder(namespaceID, selfID uint64, pageIDs []uint64) error
|
||||
}
|
||||
|
||||
pageUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Page) (bool, error)
|
||||
pageUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Page) (pageChanges, error)
|
||||
pageChanges uint8
|
||||
)
|
||||
|
||||
const (
|
||||
pageUnchanged pageChanges = 0
|
||||
pageChanged pageChanges = 1
|
||||
pageLabelsChanged pageChanges = 2
|
||||
)
|
||||
|
||||
func Page() PageService {
|
||||
@@ -125,10 +134,32 @@ func (svc page) search(filter types.PageFilter) (set types.PageSet, f types.Page
|
||||
aProps.setNamespace(ns)
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
svc.ctx,
|
||||
svc.store,
|
||||
types.Page{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if set, f, err = store.SearchComposePages(svc.ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, toLabeledPages(set)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -271,6 +302,10 @@ func (svc page) Create(new *types.Page) (*types.Page, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, s, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = svc.eventbus.WaitFor(svc.ctx, event.PageAfterCreate(new, nil, ns))
|
||||
return err
|
||||
})
|
||||
@@ -292,7 +327,7 @@ func (svc page) UndeleteByID(namespaceID, pageID uint64) error {
|
||||
|
||||
func (svc page) updater(namespaceID, pageID uint64, action func(...*pageActionProps) *pageAction, fn pageUpdateHandler) (*types.Page, error) {
|
||||
var (
|
||||
changed bool
|
||||
changes pageChanges
|
||||
|
||||
ns *types.Namespace
|
||||
p, old *types.Page
|
||||
@@ -306,6 +341,10 @@ func (svc page) updater(namespaceID, pageID uint64, action func(...*pageActionPr
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old = p.Clone()
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
@@ -321,16 +360,22 @@ func (svc page) updater(namespaceID, pageID uint64, action func(...*pageActionPr
|
||||
return
|
||||
}
|
||||
|
||||
if changed, err = fn(svc.ctx, ns, p); err != nil {
|
||||
if changes, err = fn(svc.ctx, ns, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if changed {
|
||||
if changes&pageChanged > 0 {
|
||||
if err = store.UpdateComposePage(svc.ctx, svc.store, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if changes&pageLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, p); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.DeletedAt == nil {
|
||||
err = svc.eventbus.WaitFor(svc.ctx, event.PageAfterUpdate(p, old, ns))
|
||||
} else {
|
||||
@@ -366,6 +411,10 @@ func (svc page) lookup(namespaceID uint64, lookup func(*pageActionProps) (*types
|
||||
return PageErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -389,63 +438,104 @@ func (svc page) uniqueCheck(p *types.Page) (err error) {
|
||||
}
|
||||
|
||||
func (svc page) handleUpdate(upd *types.Page) pageUpdateHandler {
|
||||
return func(ctx context.Context, ns *types.Namespace, p *types.Page) (bool, error) {
|
||||
if isStale(upd.UpdatedAt, p.UpdatedAt, p.CreatedAt) {
|
||||
return false, PageErrStaleData()
|
||||
return func(ctx context.Context, ns *types.Namespace, res *types.Page) (changes pageChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return pageUnchanged, PageErrStaleData()
|
||||
}
|
||||
|
||||
if upd.Handle != p.Handle && !handle.IsValid(upd.Handle) {
|
||||
return false, PageErrInvalidHandle()
|
||||
if upd.Handle != res.Handle && !handle.IsValid(upd.Handle) {
|
||||
return pageUnchanged, PageErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err := svc.uniqueCheck(upd); err != nil {
|
||||
return false, err
|
||||
return pageUnchanged, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdatePage(svc.ctx, p) {
|
||||
return false, PageErrNotAllowedToUpdate()
|
||||
if !svc.ac.CanUpdatePage(svc.ctx, res) {
|
||||
return pageUnchanged, PageErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
p.ID = upd.ID
|
||||
p.SelfID = upd.SelfID
|
||||
p.Blocks = upd.Blocks
|
||||
p.Title = upd.Title
|
||||
p.Handle = upd.Handle
|
||||
p.Description = upd.Description
|
||||
p.Visible = upd.Visible
|
||||
p.Weight = upd.Weight
|
||||
p.UpdatedAt = now()
|
||||
if res.ID != upd.ID {
|
||||
res.ID = upd.ID
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
return true, nil
|
||||
if res.SelfID != upd.SelfID {
|
||||
res.SelfID = upd.SelfID
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(res.Blocks, upd.Blocks) {
|
||||
res.Blocks = upd.Blocks
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if res.Title != upd.Title {
|
||||
res.Title = upd.Title
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if res.Handle != upd.Handle {
|
||||
res.Handle = upd.Handle
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if res.Description != upd.Description {
|
||||
res.Description = upd.Description
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if res.Visible != upd.Visible {
|
||||
res.Visible = upd.Visible
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if res.Weight != upd.Weight {
|
||||
res.Weight = upd.Weight
|
||||
changes |= pageChanged
|
||||
}
|
||||
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= pageLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
if changes&pageChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc page) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Page) (bool, error) {
|
||||
func (svc page) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Page) (pageChanges, error) {
|
||||
if !svc.ac.CanDeletePage(ctx, m) {
|
||||
return false, PageErrNotAllowedToDelete()
|
||||
return pageUnchanged, PageErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if m.DeletedAt != nil {
|
||||
// page already deleted
|
||||
return false, nil
|
||||
return pageUnchanged, nil
|
||||
}
|
||||
|
||||
m.DeletedAt = now()
|
||||
return true, nil
|
||||
return pageChanged, nil
|
||||
}
|
||||
|
||||
func (svc page) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Page) (bool, error) {
|
||||
func (svc page) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Page) (pageChanges, error) {
|
||||
if !svc.ac.CanDeletePage(ctx, m) {
|
||||
return false, PageErrNotAllowedToUndelete()
|
||||
return pageUnchanged, PageErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
if m.DeletedAt == nil {
|
||||
// page not deleted
|
||||
return false, nil
|
||||
return pageUnchanged, nil
|
||||
}
|
||||
|
||||
m.DeletedAt = nil
|
||||
return true, nil
|
||||
return pageChanged, nil
|
||||
}
|
||||
|
||||
func loadPage(ctx context.Context, s store.Storer, namespaceID, pageID uint64) (ns *types.Namespace, m *types.Page, err error) {
|
||||
@@ -470,3 +560,19 @@ func loadPage(ctx context.Context, s store.Storer, namespaceID, pageID uint64) (
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledPages converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledPages(set []*types.Page) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/corredor"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -226,6 +227,10 @@ func (svc record) lookup(namespaceID, moduleID uint64, lookup func(*types.Module
|
||||
|
||||
trimUnreadableRecordFields(svc.ctx, svc.ac, m, r)
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
@@ -273,11 +278,33 @@ func (svc record) Find(filter types.RecordFilter) (set types.RecordSet, f types.
|
||||
return err
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
svc.ctx,
|
||||
svc.store,
|
||||
types.Record{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
set, f, err = store.SearchComposeRecords(svc.ctx, svc.store, m, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(svc.ctx, svc.store, toLabeledRecords(set)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trimUnreadableRecordFields(svc.ctx, svc.ac, m, set...)
|
||||
|
||||
return nil
|
||||
@@ -540,6 +567,10 @@ func (svc record) create(new *types.Record) (rec *types.Record, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = label.Create(svc.ctx, svc.store, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// At this point we can return the value
|
||||
rec = new
|
||||
|
||||
@@ -628,8 +659,13 @@ func (svc record) update(upd *types.Record) (rec *types.Record, err error) {
|
||||
}
|
||||
|
||||
err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storer) error {
|
||||
return store.UpdateComposeRecord(ctx, s, m, upd)
|
||||
if label.Changed(old.Labels, upd.Labels) {
|
||||
if err = label.Update(ctx, s, upd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return store.UpdateComposeRecord(ctx, s, m, upd)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -1362,3 +1398,19 @@ func loadRecordCombo(ctx context.Context, s store.Storer, namespaceID, moduleID,
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledRecords converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledRecords(set []*types.Record) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ type (
|
||||
Name string `json:"name"`
|
||||
Config ChartConfig `json:"config"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
@@ -40,9 +42,13 @@ type (
|
||||
}
|
||||
|
||||
ChartFilter struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
Handle string `json:"handle"`
|
||||
Query string `json:"query"`
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
ChartID []uint64 `json:"chartID"`
|
||||
Handle string `json:"handle"`
|
||||
Query string `json:"query"`
|
||||
|
||||
LabeledIDs []uint64 `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@ package types
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -17,6 +15,8 @@ type (
|
||||
Meta types.JSONText `json:"meta"`
|
||||
Fields ModuleFieldSet `json:"fields"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
@@ -25,10 +25,14 @@ type (
|
||||
}
|
||||
|
||||
ModuleFilter struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
Query string `json:"query"`
|
||||
Handle string `json:"handle"`
|
||||
Name string `json:"name"`
|
||||
ModuleID []uint64 `json:"moduleID"`
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
Query string `json:"query"`
|
||||
Handle string `json:"handle"`
|
||||
Name string `json:"name"`
|
||||
|
||||
LabeledIDs []uint64 `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ type (
|
||||
Multi bool `json:"isMulti"`
|
||||
DefaultValue RecordValueSet `json:"defaultValue"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
|
||||
@@ -19,6 +19,8 @@ type (
|
||||
Enabled bool `json:"enabled"`
|
||||
Meta NamespaceMeta `json:"meta"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
@@ -29,6 +31,9 @@ type (
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
|
||||
LabeledIDs []uint64 `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
|
||||
@@ -29,6 +29,8 @@ type (
|
||||
|
||||
Children PageSet `json:"children,omitempty"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Visible bool `json:"visible"`
|
||||
Weight int `json:"weight"`
|
||||
|
||||
@@ -59,6 +61,9 @@ type (
|
||||
Handle string `json:"handle"`
|
||||
Query string `json:"query"`
|
||||
|
||||
LabeledIDs []uint64 `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
|
||||
@@ -37,6 +37,8 @@ type (
|
||||
|
||||
Values RecordValueSet `json:"values,omitempty"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
|
||||
OwnedBy uint64 `json:"ownedBy,string"`
|
||||
@@ -53,8 +55,8 @@ type (
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
Query string `json:"query"`
|
||||
|
||||
// Preloaded set of additional modules that are used for record filtering
|
||||
// Modules ModuleSet
|
||||
LabeledIDs []uint64 `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
|
||||
153
compose/types/type_labels.gen.go
Normal file
153
compose/types/type_labels.gen.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package types
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
// Definitions file that controls how this file is generated:
|
||||
// compose/types/types.yaml
|
||||
|
||||
// SetLabel adds new label to label map
|
||||
func (m *Chart) SetLabel(key string, value string) {
|
||||
if m.Labels == nil {
|
||||
m.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
m.Labels[key] = value
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Chart) GetLabels() map[string]string {
|
||||
return m.Labels
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (Chart) LabelResourceKind() string {
|
||||
return "compose:chart"
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Chart) LabelResourceID() uint64 {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// SetLabel adds new label to label map
|
||||
func (m *Module) SetLabel(key string, value string) {
|
||||
if m.Labels == nil {
|
||||
m.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
m.Labels[key] = value
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Module) GetLabels() map[string]string {
|
||||
return m.Labels
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (Module) LabelResourceKind() string {
|
||||
return "compose:module"
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Module) LabelResourceID() uint64 {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// SetLabel adds new label to label map
|
||||
func (m *ModuleField) SetLabel(key string, value string) {
|
||||
if m.Labels == nil {
|
||||
m.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
m.Labels[key] = value
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m ModuleField) GetLabels() map[string]string {
|
||||
return m.Labels
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (ModuleField) LabelResourceKind() string {
|
||||
return "compose:module:field"
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m ModuleField) LabelResourceID() uint64 {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// SetLabel adds new label to label map
|
||||
func (m *Namespace) SetLabel(key string, value string) {
|
||||
if m.Labels == nil {
|
||||
m.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
m.Labels[key] = value
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Namespace) GetLabels() map[string]string {
|
||||
return m.Labels
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (Namespace) LabelResourceKind() string {
|
||||
return "compose:namespace"
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Namespace) LabelResourceID() uint64 {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// SetLabel adds new label to label map
|
||||
func (m *Page) SetLabel(key string, value string) {
|
||||
if m.Labels == nil {
|
||||
m.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
m.Labels[key] = value
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Page) GetLabels() map[string]string {
|
||||
return m.Labels
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (Page) LabelResourceKind() string {
|
||||
return "compose:page"
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Page) LabelResourceID() uint64 {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// SetLabel adds new label to label map
|
||||
func (m *Record) SetLabel(key string, value string) {
|
||||
if m.Labels == nil {
|
||||
m.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
m.Labels[key] = value
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Record) GetLabels() map[string]string {
|
||||
return m.Labels
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (Record) LabelResourceKind() string {
|
||||
return "compose:record"
|
||||
}
|
||||
|
||||
// GetLabels adds new label to label map
|
||||
func (m Record) LabelResourceID() uint64 {
|
||||
return m.ID
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
types:
|
||||
Namespace:
|
||||
labelResourceType: compose:namespace
|
||||
Attachment:
|
||||
Module:
|
||||
Page:
|
||||
Chart:
|
||||
Record:
|
||||
labelResourceType: compose:module
|
||||
ModuleField:
|
||||
labelResourceType: compose:module:field
|
||||
Page:
|
||||
labelResourceType: compose:page
|
||||
Chart:
|
||||
labelResourceType: compose:chart
|
||||
Record:
|
||||
labelResourceType: compose:record
|
||||
RecordValue:
|
||||
noIdField: true
|
||||
|
||||
|
||||
@@ -12,6 +12,14 @@ func (s Store) convertComposeChartFilter(f types.ChartFilter) (query squirrel.Se
|
||||
|
||||
query = filter.StateCondition(query, "cch.deleted_at", f.Deleted)
|
||||
|
||||
if len(f.ChartID) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cch.id": f.ChartID})
|
||||
}
|
||||
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cch.id": f.LabeledIDs})
|
||||
}
|
||||
|
||||
if f.NamespaceID > 0 {
|
||||
query = query.Where("cch.rel_namespace = ?", f.NamespaceID)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,14 @@ func (s Store) convertComposeModuleFilter(f types.ModuleFilter) (query squirrel.
|
||||
|
||||
query = filter.StateCondition(query, "cmd.deleted_at", f.Deleted)
|
||||
|
||||
if len(f.ModuleID) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cmd.id": f.ModuleID})
|
||||
}
|
||||
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cmd.id": f.LabeledIDs})
|
||||
}
|
||||
|
||||
if f.NamespaceID > 0 {
|
||||
query = query.Where("cmd.rel_namespace = ?", f.NamespaceID)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ func (s Store) convertComposeNamespaceFilter(f types.NamespaceFilter) (query squ
|
||||
|
||||
query = filter.StateCondition(query, "cns.deleted_at", f.Deleted)
|
||||
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cns.id": f.LabeledIDs})
|
||||
}
|
||||
|
||||
if f.Query != "" {
|
||||
q := "%" + strings.ToLower(f.Query) + "%"
|
||||
query = query.Where(squirrel.Or{
|
||||
|
||||
@@ -18,6 +18,10 @@ func (s Store) convertComposePageFilter(f types.PageFilter) (query squirrel.Sele
|
||||
query = query.Where("cpg.rel_namespace = ?", f.NamespaceID)
|
||||
}
|
||||
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cpg.id": f.LabeledIDs})
|
||||
}
|
||||
|
||||
if f.ParentID > 0 {
|
||||
query = query.Where("self_id = ?", f.ParentID)
|
||||
} else if f.Root {
|
||||
|
||||
@@ -247,6 +247,10 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter)
|
||||
// Inc/exclude deleted records according to filter settings
|
||||
query = filter.StateCondition(query, "crd.deleted_at", f.Deleted)
|
||||
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
query = query.Where(squirrel.Eq{"crd.id": f.LabeledIDs})
|
||||
}
|
||||
|
||||
// Parse filters.
|
||||
if f.Query != "" {
|
||||
var (
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -226,3 +228,89 @@ func TestChartDelete(t *testing.T) {
|
||||
res = h.lookupChartByID(res.ID)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestChartLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "chart.create")
|
||||
h.allow(types.ChartRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.ChartRBACResource.AppendWildcard(), "update")
|
||||
h.allow(types.ChartRBACResource.AppendWildcard(), "delete")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Chart{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/chart/", ns.ID),
|
||||
types.Chart{Labels: map[string]string{"foo": "bar", "bar": "42"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"], "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"], "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Chart{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, ID),
|
||||
types.Chart{Labels: map[string]string{"foo": "baz", "baz": "123"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"], "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"], "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.ChartSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/chart/", ns.ID),
|
||||
&set, url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func init() {
|
||||
|
||||
func InitTestApp() {
|
||||
if testApp == nil {
|
||||
ctx := cli.Context()
|
||||
ctx := logger.ContextWithValue(cli.Context(), logger.MakeDebugLogger())
|
||||
|
||||
testApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
|
||||
service.DefaultStore = app.Store
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -133,7 +135,7 @@ func TestModuleListQuery(t *testing.T) {
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleList_filterForbiden(t *testing.T) {
|
||||
func TestModuleList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
@@ -334,3 +336,156 @@ func TestModuleDelete(t *testing.T) {
|
||||
res = h.lookupModuleByID(res.ID)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestModuleLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "module.create")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "update")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "delete")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
fieldID, ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Module{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/", ns.ID),
|
||||
types.Module{Labels: map[string]string{"foo": "bar", "bar": "42"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"], "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"], "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Module{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/%d", ns.ID, ID),
|
||||
types.Module{Labels: map[string]string{"foo": "baz", "baz": "123"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"], "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"], "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.ModuleSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/", ns.ID),
|
||||
&set,
|
||||
url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
|
||||
t.Run("field create", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
mod, err = store.LookupComposeModuleByID(ctx, service.DefaultStore, ID)
|
||||
|
||||
payload = struct{ Response *types.Module }{}
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NotNil(mod)
|
||||
mod.Fields = append(mod.Fields, &types.ModuleField{
|
||||
Kind: "String",
|
||||
Name: "labeled",
|
||||
Label: "",
|
||||
Labels: map[string]string{"fldfoo": "fldbar"},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, ID)).
|
||||
JSON(helpers.JSON(mod)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.fields[0].labels.fldfoo`, "fldbar")).
|
||||
End().
|
||||
JSON(&payload)
|
||||
|
||||
fieldID = payload.Response.Fields[0].ID
|
||||
|
||||
})
|
||||
|
||||
t.Run("field update", func(t *testing.T) {
|
||||
if fieldID == 0 {
|
||||
t.Skip("label/field create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
mod, err = store.LookupComposeModuleByID(ctx, service.DefaultStore, ID)
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NotNil(mod)
|
||||
mod.Fields = append(mod.Fields, &types.ModuleField{
|
||||
ID: fieldID,
|
||||
Kind: "String",
|
||||
Name: "labeled",
|
||||
Label: "",
|
||||
Labels: map[string]string{"fldfoo": "fldbaz"},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, ID)).
|
||||
JSON(helpers.JSON(mod)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.fields[0].labels.fldfoo`, "fldbaz")).
|
||||
End()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -197,3 +199,84 @@ func TestNamespaceDelete(t *testing.T) {
|
||||
ns = h.lookupNamespaceByID(ns.ID)
|
||||
h.a.NotNil(ns.DeletedAt)
|
||||
}
|
||||
|
||||
func TestNamespaceLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
h.allow(types.ComposeRBACResource, "namespace.create")
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "update")
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "delete")
|
||||
|
||||
var (
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Namespace{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
"/namespace/",
|
||||
types.Namespace{Labels: map[string]string{"foo": "bar", "bar": "42"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"], "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"], "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Namespace{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d", ID),
|
||||
&types.Namespace{Labels: map[string]string{"foo": "baz", "baz": "123"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"], "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"], "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.NamespaceSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/namespace/", &set, url.Values{"labels": []string{"baz=123"}})
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -262,3 +264,91 @@ func TestPageTreeRead(t *testing.T) {
|
||||
Assert(jsonpath.Equal(`$.response[3].title`, "p4")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "page.create")
|
||||
h.allow(types.PageRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.PageRBACResource.AppendWildcard(), "update")
|
||||
h.allow(types.PageRBACResource.AppendWildcard(), "delete")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Page{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/page/", ns.ID),
|
||||
types.Page{Labels: map[string]string{"foo": "bar", "bar": "42"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"], "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"], "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Page{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(),
|
||||
t,
|
||||
fmt.Sprintf("/namespace/%d/page/%d", ns.ID, ID),
|
||||
types.Page{Labels: map[string]string{"foo": "baz", "baz": "123"}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"], "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"], "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.PageSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/page/", ns.ID),
|
||||
&set,
|
||||
url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||
"github.com/steinfletcher/apitest"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -558,5 +560,124 @@ func TestRecordFieldModulePermissionCheck(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRecordLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearRecords()
|
||||
|
||||
h.allow(types.NamespaceRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "read")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "record.create")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "record.update")
|
||||
h.allow(types.ModuleRBACResource.AppendWildcard(), "record.read")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
mod = h.makeModule(ns, "some-module", &types.ModuleField{Kind: "String", Name: "dummy"})
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
payload = struct {
|
||||
Response *types.Record
|
||||
}{}
|
||||
|
||||
rec = &types.Record{
|
||||
Values: types.RecordValueSet{&types.RecordValue{Name: "dummy", Value: "dummy"}},
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "42",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", ns.ID, mod.ID)).
|
||||
JSON(helpers.JSON(rec)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&payload)
|
||||
|
||||
req.NotNil(payload.Response)
|
||||
req.NotZero(payload.Response.ID)
|
||||
|
||||
h.a.Equal(payload.Response.Labels["foo"], "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Response.Labels["bar"], "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(payload.Response.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.Response.LabelResourceKind(), payload.Response.ID),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.Response.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
payload = struct {
|
||||
Response *types.Record
|
||||
}{}
|
||||
|
||||
rec = &types.Record{
|
||||
ID: ID,
|
||||
Values: types.RecordValueSet{&types.RecordValue{Name: "dummy", Value: "dummy"}},
|
||||
Labels: map[string]string{
|
||||
"foo": "baz",
|
||||
"baz": "123",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", ns.ID, mod.ID, ID)).
|
||||
JSON(helpers.JSON(rec)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&payload)
|
||||
req.NotZero(payload.Response.ID)
|
||||
|
||||
// disabled for now
|
||||
//req.Nil(payload.Response.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Response.Labels["foo"], "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Response.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Response.Labels["baz"], "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(payload.Response.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.Response.LabelResourceKind(), payload.Response.ID),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.RecordSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/%d/record/", ns.ID, mod.ID),
|
||||
&set, url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func NewIntegrationTestApp(ctx context.Context, initTestServices func(*app.CortezaApp) error) *app.CortezaApp {
|
||||
logger.Init()
|
||||
|
||||
var (
|
||||
a = app.New()
|
||||
)
|
||||
@@ -24,7 +26,7 @@ func NewIntegrationTestApp(ctx context.Context, initTestServices func(*app.Corte
|
||||
a.Opt.Auth.Secret = string(rand.Bytes(32))
|
||||
a.Opt.Auth.Expiry = time.Minute
|
||||
|
||||
a.Log = logger.MakeDebugLogger()
|
||||
a.Log = logger.Default()
|
||||
|
||||
cli.HandleError(a.InitStore(ctx))
|
||||
cli.HandleError(initTestServices(a))
|
||||
|
||||
11
tests/helpers/json.go
Normal file
11
tests/helpers/json.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package helpers
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func JSON(i interface{}) string {
|
||||
if b, err := json.Marshal(i); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
63
tests/helpers/labels.go
Normal file
63
tests/helpers/labels.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/steinfletcher/apitest"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func SetLabelsViaAPI(api *apitest.APITest, t *testing.T, endpoint string, in, out interface{}) {
|
||||
var (
|
||||
payload = struct {
|
||||
Response interface{}
|
||||
}{}
|
||||
req *apitest.Request
|
||||
)
|
||||
|
||||
payload.Response = out
|
||||
|
||||
if strings.HasPrefix(endpoint, "PUT ") {
|
||||
// a little workaround for our inconsistencies...
|
||||
req = api.Put(endpoint[4:])
|
||||
} else {
|
||||
req = api.Post(endpoint)
|
||||
}
|
||||
|
||||
req.JSON(JSON(in)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(AssertNoErrors).
|
||||
End().
|
||||
JSON(&payload)
|
||||
}
|
||||
|
||||
func SearchWithLabelsViaAPI(api *apitest.APITest, t *testing.T, endpoint string, res interface{}, labels url.Values) {
|
||||
var payload = struct{ Response struct{ Set interface{} } }{}
|
||||
|
||||
payload.Response.Set = res
|
||||
|
||||
api.Get(endpoint).
|
||||
QueryCollection(labels).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(AssertNoErrors).
|
||||
End().
|
||||
JSON(&payload)
|
||||
}
|
||||
|
||||
func LoadLabelsFromStore(t *testing.T, s store.Storer, resKind string, id uint64) (sl map[string]string) {
|
||||
ll, _, err := s.SearchLabels(context.Background(), types.LabelFilter{Kind: resKind})
|
||||
require.NoError(t, err)
|
||||
if len(ll) == 0 {
|
||||
// small adjustment
|
||||
return nil
|
||||
}
|
||||
|
||||
return ll.FilterByResource(resKind, id)
|
||||
}
|
||||
Reference in New Issue
Block a user