3
0

Implement support for labels on compose resources

This commit is contained in:
Denis Arh
2020-10-28 16:40:32 +01:00
parent 8189f40fc0
commit b82ddad83a
39 changed files with 2013 additions and 181 deletions

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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
}
}
}
{

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}
}
}
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"`

View File

@@ -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"`

View File

@@ -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"`

View File

@@ -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

View File

@@ -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

View File

@@ -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"`

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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{

View File

@@ -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 {

View File

@@ -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 (

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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