Rework system/application sorting via weight
This commit is contained in:
parent
77c754b81b
commit
1e44e4299b
@ -62,6 +62,7 @@ func (c *application) MarshalYAML() (interface{}, error) {
|
|||||||
nn, err := makeMap(
|
nn, err := makeMap(
|
||||||
"name", c.res.Name,
|
"name", c.res.Name,
|
||||||
"enabled", c.res.Enabled,
|
"enabled", c.res.Enabled,
|
||||||
|
"weight", c.res.Weight,
|
||||||
|
|
||||||
"unify", c.res.Unify,
|
"unify", c.res.Unify,
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,9 @@ type (
|
|||||||
|
|
||||||
// ApplicationMetrics (custom function)
|
// ApplicationMetrics (custom function)
|
||||||
ApplicationMetrics(ctx context.Context) (*types.ApplicationMetrics, error)
|
ApplicationMetrics(ctx context.Context) (*types.ApplicationMetrics, error)
|
||||||
|
|
||||||
|
// ReorderApplications (custom function)
|
||||||
|
ReorderApplications(ctx context.Context, _order []uint64) error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,3 +87,7 @@ func TruncateApplications(ctx context.Context, s Applications) error {
|
|||||||
func ApplicationMetrics(ctx context.Context, s Applications) (*types.ApplicationMetrics, error) {
|
func ApplicationMetrics(ctx context.Context, s Applications) (*types.ApplicationMetrics, error) {
|
||||||
return s.ApplicationMetrics(ctx)
|
return s.ApplicationMetrics(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReorderApplications(ctx context.Context, s Applications, _order []uint64) error {
|
||||||
|
return s.ReorderApplications(ctx, _order)
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ fields:
|
|||||||
- { field: Name, sortable: true }
|
- { field: Name, sortable: true }
|
||||||
- { field: OwnerID }
|
- { field: OwnerID }
|
||||||
- { field: Enabled }
|
- { field: Enabled }
|
||||||
|
- { field: Weight, sortable: true }
|
||||||
- { field: Unify }
|
- { field: Unify }
|
||||||
- { field: CreatedAt, sortable: true }
|
- { field: CreatedAt, sortable: true }
|
||||||
- { field: UpdatedAt, sortable: true }
|
- { field: UpdatedAt, sortable: true }
|
||||||
@ -22,6 +23,12 @@ functions:
|
|||||||
- name: ApplicationMetrics
|
- name: ApplicationMetrics
|
||||||
return: [ "*types.ApplicationMetrics", "error" ]
|
return: [ "*types.ApplicationMetrics", "error" ]
|
||||||
|
|
||||||
|
- name: ReorderApplications
|
||||||
|
arguments:
|
||||||
|
- { name: order, type: "[]uint64" }
|
||||||
|
return: [ "error" ]
|
||||||
|
|
||||||
|
|
||||||
rdbms:
|
rdbms:
|
||||||
alias: app
|
alias: app
|
||||||
table: applications
|
table: applications
|
||||||
|
|||||||
@ -435,6 +435,7 @@ func (s Store) internalApplicationRowScanner(row rowScanner) (res *types.Applica
|
|||||||
&res.Name,
|
&res.Name,
|
||||||
&res.OwnerID,
|
&res.OwnerID,
|
||||||
&res.Enabled,
|
&res.Enabled,
|
||||||
|
&res.Weight,
|
||||||
&res.Unify,
|
&res.Unify,
|
||||||
&res.CreatedAt,
|
&res.CreatedAt,
|
||||||
&res.UpdatedAt,
|
&res.UpdatedAt,
|
||||||
@ -482,6 +483,7 @@ func (Store) applicationColumns(aa ...string) []string {
|
|||||||
alias + "name",
|
alias + "name",
|
||||||
alias + "rel_owner",
|
alias + "rel_owner",
|
||||||
alias + "enabled",
|
alias + "enabled",
|
||||||
|
alias + "weight",
|
||||||
alias + "unify",
|
alias + "unify",
|
||||||
alias + "created_at",
|
alias + "created_at",
|
||||||
alias + "updated_at",
|
alias + "updated_at",
|
||||||
@ -496,7 +498,7 @@ func (Store) applicationColumns(aa ...string) []string {
|
|||||||
// With optional string arg, all columns are returned aliased
|
// With optional string arg, all columns are returned aliased
|
||||||
func (Store) sortableApplicationColumns() map[string]string {
|
func (Store) sortableApplicationColumns() map[string]string {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"id": "id", "name": "name", "created_at": "created_at",
|
"id": "id", "name": "name", "weight": "weight", "created_at": "created_at",
|
||||||
"createdat": "created_at",
|
"createdat": "created_at",
|
||||||
"updated_at": "updated_at",
|
"updated_at": "updated_at",
|
||||||
"updatedat": "updated_at",
|
"updatedat": "updated_at",
|
||||||
@ -515,6 +517,7 @@ func (s Store) internalApplicationEncoder(res *types.Application) store.Payload
|
|||||||
"name": res.Name,
|
"name": res.Name,
|
||||||
"rel_owner": res.OwnerID,
|
"rel_owner": res.OwnerID,
|
||||||
"enabled": res.Enabled,
|
"enabled": res.Enabled,
|
||||||
|
"weight": res.Weight,
|
||||||
"unify": res.Unify,
|
"unify": res.Unify,
|
||||||
"created_at": res.CreatedAt,
|
"created_at": res.CreatedAt,
|
||||||
"updated_at": res.UpdatedAt,
|
"updated_at": res.UpdatedAt,
|
||||||
@ -551,6 +554,9 @@ func (s Store) collectApplicationCursorValues(res *types.Application, cc ...*fil
|
|||||||
case "name":
|
case "name":
|
||||||
cursor.Set(c.Column, res.Name, c.Descending)
|
cursor.Set(c.Column, res.Name, c.Descending)
|
||||||
|
|
||||||
|
case "weight":
|
||||||
|
cursor.Set(c.Column, res.Weight, c.Descending)
|
||||||
|
|
||||||
case "created_at":
|
case "created_at":
|
||||||
cursor.Set(c.Column, res.CreatedAt, c.Descending)
|
cursor.Set(c.Column, res.CreatedAt, c.Descending)
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package rdbms
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||||
|
"github.com/cortezaproject/corteza-server/store"
|
||||||
"github.com/cortezaproject/corteza-server/system/types"
|
"github.com/cortezaproject/corteza-server/system/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,3 +58,56 @@ func (s Store) ApplicationMetrics(ctx context.Context) (*types.ApplicationMetric
|
|||||||
|
|
||||||
return rval, nil
|
return rval, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Store) ReorderApplications(ctx context.Context, order []uint64) (err error) {
|
||||||
|
var (
|
||||||
|
apps types.ApplicationSet
|
||||||
|
appMap = map[uint64]bool{}
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
f = types.ApplicationFilter{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if apps, _, err = s.SearchApplications(ctx, f); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, app := range apps {
|
||||||
|
appMap[app.ID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// honor parameter first
|
||||||
|
for _, pageID := range order {
|
||||||
|
if appMap[pageID] {
|
||||||
|
appMap[pageID] = false
|
||||||
|
err = s.execUpdateApplications(ctx,
|
||||||
|
squirrel.Eq{"app.id": pageID},
|
||||||
|
store.Payload{"weight": weight})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
weight++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for pageID, update := range appMap {
|
||||||
|
if !update {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.execUpdateApplications(ctx,
|
||||||
|
squirrel.Eq{"app.id": pageID},
|
||||||
|
store.Payload{"weight": weight})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
weight++
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package rdbms
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/cortezaproject/corteza-server/store/rdbms/ddl"
|
"github.com/cortezaproject/corteza-server/store/rdbms/ddl"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -59,6 +60,10 @@ func (g genericUpgrades) Upgrade(ctx context.Context, t *ddl.Table) error {
|
|||||||
return g.all(ctx,
|
return g.all(ctx,
|
||||||
g.AlterActionlogAddID,
|
g.AlterActionlogAddID,
|
||||||
)
|
)
|
||||||
|
case "applications":
|
||||||
|
return g.all(ctx,
|
||||||
|
g.AddWeightField,
|
||||||
|
)
|
||||||
case "users":
|
case "users":
|
||||||
return g.all(ctx,
|
return g.all(ctx,
|
||||||
g.AlterUsersDropOrganisation,
|
g.AlterUsersDropOrganisation,
|
||||||
@ -243,6 +248,17 @@ func (g genericUpgrades) AlterActionlogAddID(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g genericUpgrades) AddWeightField(ctx context.Context) error {
|
||||||
|
_, err := g.u.AddColumn(ctx, "applications", &ddl.Column{
|
||||||
|
Name: "weight",
|
||||||
|
Type: ddl.ColumnType{Type: ddl.ColumnTypeInteger},
|
||||||
|
IsNull: false,
|
||||||
|
DefaultValue: "0",
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (g genericUpgrades) RenameReminders(ctx context.Context) error {
|
func (g genericUpgrades) RenameReminders(ctx context.Context) error {
|
||||||
return g.RenameTable(ctx, "sys_reminder", "reminders")
|
return g.RenameTable(ctx, "sys_reminder", "reminders")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -221,6 +221,7 @@ func (Schema) Applications() *Table {
|
|||||||
ID,
|
ID,
|
||||||
ColumnDef("name", ColumnTypeText),
|
ColumnDef("name", ColumnTypeText),
|
||||||
ColumnDef("enabled", ColumnTypeBoolean, DefaultValue("true")),
|
ColumnDef("enabled", ColumnTypeBoolean, DefaultValue("true")),
|
||||||
|
ColumnDef("weight", ColumnTypeInteger, DefaultValue("0")),
|
||||||
ColumnDef("unify", ColumnTypeJson),
|
ColumnDef("unify", ColumnTypeJson),
|
||||||
ColumnDef("rel_owner", ColumnTypeIdentifier),
|
ColumnDef("rel_owner", ColumnTypeIdentifier),
|
||||||
CUDTimestamps,
|
CUDTimestamps,
|
||||||
|
|||||||
@ -744,6 +744,10 @@ endpoints:
|
|||||||
type: bool
|
type: bool
|
||||||
required: false
|
required: false
|
||||||
title: Enabled
|
title: Enabled
|
||||||
|
- name: weight
|
||||||
|
type: int
|
||||||
|
required: false
|
||||||
|
title: Weight for sorting
|
||||||
- name: unify
|
- name: unify
|
||||||
type: sqlxTypes.JSONText
|
type: sqlxTypes.JSONText
|
||||||
required: false
|
required: false
|
||||||
@ -775,6 +779,10 @@ endpoints:
|
|||||||
type: bool
|
type: bool
|
||||||
required: false
|
required: false
|
||||||
title: Enabled
|
title: Enabled
|
||||||
|
- name: weight
|
||||||
|
type: int
|
||||||
|
required: false
|
||||||
|
title: Weight for sorting
|
||||||
- name: unify
|
- name: unify
|
||||||
type: sqlxTypes.JSONText
|
type: sqlxTypes.JSONText
|
||||||
required: false
|
required: false
|
||||||
@ -832,6 +840,17 @@ endpoints:
|
|||||||
type: string
|
type: string
|
||||||
title: Script to execute
|
title: Script to execute
|
||||||
required: true
|
required: true
|
||||||
|
- name: reorder
|
||||||
|
method: POST
|
||||||
|
title: Reorder applications
|
||||||
|
path: "/reorder"
|
||||||
|
parameters:
|
||||||
|
post:
|
||||||
|
- name: applicationIDs
|
||||||
|
type: "[]string"
|
||||||
|
required: true
|
||||||
|
title: Application order
|
||||||
|
|
||||||
- title: Permissions
|
- title: Permissions
|
||||||
parameters: {}
|
parameters: {}
|
||||||
entrypoint: permissions
|
entrypoint: permissions
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package rest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/cortezaproject/corteza-server/pkg/api"
|
"github.com/cortezaproject/corteza-server/pkg/api"
|
||||||
"github.com/cortezaproject/corteza-server/pkg/corredor"
|
"github.com/cortezaproject/corteza-server/pkg/corredor"
|
||||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||||
@ -27,6 +29,7 @@ type (
|
|||||||
Update(ctx context.Context, upd *types.Application) (app *types.Application, err error)
|
Update(ctx context.Context, upd *types.Application) (app *types.Application, err error)
|
||||||
Delete(ctx context.Context, ID uint64) (err error)
|
Delete(ctx context.Context, ID uint64) (err error)
|
||||||
Undelete(ctx context.Context, ID uint64) (err error)
|
Undelete(ctx context.Context, ID uint64) (err error)
|
||||||
|
Reorder(ctx context.Context, order []uint64) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationAccessController interface {
|
applicationAccessController interface {
|
||||||
@ -87,6 +90,7 @@ func (ctrl *Application) Create(ctx context.Context, r *request.ApplicationCreat
|
|||||||
app = &types.Application{
|
app = &types.Application{
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
Enabled: r.Enabled,
|
Enabled: r.Enabled,
|
||||||
|
Weight: r.Weight,
|
||||||
Labels: r.Labels,
|
Labels: r.Labels,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -109,6 +113,7 @@ func (ctrl *Application) Update(ctx context.Context, r *request.ApplicationUpdat
|
|||||||
ID: r.ApplicationID,
|
ID: r.ApplicationID,
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
Enabled: r.Enabled,
|
Enabled: r.Enabled,
|
||||||
|
Weight: r.Weight,
|
||||||
Labels: r.Labels,
|
Labels: r.Labels,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -151,6 +156,21 @@ func (ctrl *Application) TriggerScript(ctx context.Context, r *request.Applicati
|
|||||||
return application, err
|
return application, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctrl *Application) Reorder(ctx context.Context, r *request.ApplicationReorder) (interface{}, error) {
|
||||||
|
order := make([]uint64, len(r.ApplicationIDs))
|
||||||
|
|
||||||
|
for i, aid := range r.ApplicationIDs {
|
||||||
|
parsed, err := strconv.ParseUint(aid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
order[i] = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.OK(), ctrl.application.Reorder(ctx, order)
|
||||||
|
}
|
||||||
|
|
||||||
func (ctrl Application) makePayload(ctx context.Context, m *types.Application, err error) (*applicationPayload, error) {
|
func (ctrl Application) makePayload(ctx context.Context, m *types.Application, err error) (*applicationPayload, error) {
|
||||||
if err != nil || m == nil {
|
if err != nil || m == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -26,6 +26,7 @@ type (
|
|||||||
Delete(context.Context, *request.ApplicationDelete) (interface{}, error)
|
Delete(context.Context, *request.ApplicationDelete) (interface{}, error)
|
||||||
Undelete(context.Context, *request.ApplicationUndelete) (interface{}, error)
|
Undelete(context.Context, *request.ApplicationUndelete) (interface{}, error)
|
||||||
TriggerScript(context.Context, *request.ApplicationTriggerScript) (interface{}, error)
|
TriggerScript(context.Context, *request.ApplicationTriggerScript) (interface{}, error)
|
||||||
|
Reorder(context.Context, *request.ApplicationReorder) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP API interface
|
// HTTP API interface
|
||||||
@ -37,6 +38,7 @@ type (
|
|||||||
Delete func(http.ResponseWriter, *http.Request)
|
Delete func(http.ResponseWriter, *http.Request)
|
||||||
Undelete func(http.ResponseWriter, *http.Request)
|
Undelete func(http.ResponseWriter, *http.Request)
|
||||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||||
|
Reorder func(http.ResponseWriter, *http.Request)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -152,6 +154,22 @@ func NewApplication(h ApplicationAPI) *Application {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.Send(w, r, value)
|
||||||
|
},
|
||||||
|
Reorder: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewApplicationReorder()
|
||||||
|
if err := params.Fill(r); err != nil {
|
||||||
|
api.Send(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := h.Reorder(r.Context(), params)
|
||||||
|
if err != nil {
|
||||||
|
api.Send(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
api.Send(w, r, value)
|
api.Send(w, r, value)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -167,5 +185,6 @@ func (h Application) MountRoutes(r chi.Router, middlewares ...func(http.Handler)
|
|||||||
r.Delete("/application/{applicationID}", h.Delete)
|
r.Delete("/application/{applicationID}", h.Delete)
|
||||||
r.Post("/application/{applicationID}/undelete", h.Undelete)
|
r.Post("/application/{applicationID}/undelete", h.Undelete)
|
||||||
r.Post("/application/{applicationID}/trigger", h.TriggerScript)
|
r.Post("/application/{applicationID}/trigger", h.TriggerScript)
|
||||||
|
r.Post("/application/reorder", h.Reorder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,6 +79,11 @@ type (
|
|||||||
// Enabled
|
// Enabled
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
|
||||||
|
// Weight POST parameter
|
||||||
|
//
|
||||||
|
// Weight for sorting
|
||||||
|
Weight int
|
||||||
|
|
||||||
// Unify POST parameter
|
// Unify POST parameter
|
||||||
//
|
//
|
||||||
// Unify properties
|
// Unify properties
|
||||||
@ -111,6 +116,11 @@ type (
|
|||||||
// Enabled
|
// Enabled
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
|
||||||
|
// Weight POST parameter
|
||||||
|
//
|
||||||
|
// Weight for sorting
|
||||||
|
Weight int
|
||||||
|
|
||||||
// Unify POST parameter
|
// Unify POST parameter
|
||||||
//
|
//
|
||||||
// Unify properties
|
// Unify properties
|
||||||
@ -159,6 +169,13 @@ type (
|
|||||||
// Script to execute
|
// Script to execute
|
||||||
Script string
|
Script string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplicationReorder struct {
|
||||||
|
// ApplicationIDs POST parameter
|
||||||
|
//
|
||||||
|
// Application order
|
||||||
|
ApplicationIDs []string
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewApplicationList request
|
// NewApplicationList request
|
||||||
@ -293,6 +310,7 @@ func (r ApplicationCreate) Auditable() map[string]interface{} {
|
|||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"name": r.Name,
|
"name": r.Name,
|
||||||
"enabled": r.Enabled,
|
"enabled": r.Enabled,
|
||||||
|
"weight": r.Weight,
|
||||||
"unify": r.Unify,
|
"unify": r.Unify,
|
||||||
"config": r.Config,
|
"config": r.Config,
|
||||||
"labels": r.Labels,
|
"labels": r.Labels,
|
||||||
@ -309,6 +327,11 @@ func (r ApplicationCreate) GetEnabled() bool {
|
|||||||
return r.Enabled
|
return r.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auditable returns all auditable/loggable parameters
|
||||||
|
func (r ApplicationCreate) GetWeight() int {
|
||||||
|
return r.Weight
|
||||||
|
}
|
||||||
|
|
||||||
// Auditable returns all auditable/loggable parameters
|
// Auditable returns all auditable/loggable parameters
|
||||||
func (r ApplicationCreate) GetUnify() sqlxTypes.JSONText {
|
func (r ApplicationCreate) GetUnify() sqlxTypes.JSONText {
|
||||||
return r.Unify
|
return r.Unify
|
||||||
@ -358,6 +381,13 @@ func (r *ApplicationCreate) Fill(req *http.Request) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, ok := req.Form["weight"]; ok && len(val) > 0 {
|
||||||
|
r.Weight, err = payload.ParseInt(val[0]), nil
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := req.Form["unify"]; ok && len(val) > 0 {
|
if val, ok := req.Form["unify"]; ok && len(val) > 0 {
|
||||||
r.Unify, err = payload.ParseJSONTextWithErr(val[0])
|
r.Unify, err = payload.ParseJSONTextWithErr(val[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -399,6 +429,7 @@ func (r ApplicationUpdate) Auditable() map[string]interface{} {
|
|||||||
"applicationID": r.ApplicationID,
|
"applicationID": r.ApplicationID,
|
||||||
"name": r.Name,
|
"name": r.Name,
|
||||||
"enabled": r.Enabled,
|
"enabled": r.Enabled,
|
||||||
|
"weight": r.Weight,
|
||||||
"unify": r.Unify,
|
"unify": r.Unify,
|
||||||
"config": r.Config,
|
"config": r.Config,
|
||||||
"labels": r.Labels,
|
"labels": r.Labels,
|
||||||
@ -420,6 +451,11 @@ func (r ApplicationUpdate) GetEnabled() bool {
|
|||||||
return r.Enabled
|
return r.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auditable returns all auditable/loggable parameters
|
||||||
|
func (r ApplicationUpdate) GetWeight() int {
|
||||||
|
return r.Weight
|
||||||
|
}
|
||||||
|
|
||||||
// Auditable returns all auditable/loggable parameters
|
// Auditable returns all auditable/loggable parameters
|
||||||
func (r ApplicationUpdate) GetUnify() sqlxTypes.JSONText {
|
func (r ApplicationUpdate) GetUnify() sqlxTypes.JSONText {
|
||||||
return r.Unify
|
return r.Unify
|
||||||
@ -469,6 +505,13 @@ func (r *ApplicationUpdate) Fill(req *http.Request) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, ok := req.Form["weight"]; ok && len(val) > 0 {
|
||||||
|
r.Weight, err = payload.ParseInt(val[0]), nil
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := req.Form["unify"]; ok && len(val) > 0 {
|
if val, ok := req.Form["unify"]; ok && len(val) > 0 {
|
||||||
r.Unify, err = payload.ParseJSONTextWithErr(val[0])
|
r.Unify, err = payload.ParseJSONTextWithErr(val[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -711,3 +754,51 @@ func (r *ApplicationTriggerScript) Fill(req *http.Request) (err error) {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewApplicationReorder request
|
||||||
|
func NewApplicationReorder() *ApplicationReorder {
|
||||||
|
return &ApplicationReorder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auditable returns all auditable/loggable parameters
|
||||||
|
func (r ApplicationReorder) Auditable() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"applicationIDs": r.ApplicationIDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auditable returns all auditable/loggable parameters
|
||||||
|
func (r ApplicationReorder) GetApplicationIDs() []string {
|
||||||
|
return r.ApplicationIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill processes request and fills internal variables
|
||||||
|
func (r *ApplicationReorder) Fill(req *http.Request) (err error) {
|
||||||
|
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(req.Body).Decode(r)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return fmt.Errorf("error parsing http request body: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if err = req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST params
|
||||||
|
|
||||||
|
//if val, ok := req.Form["applicationIDs[]"]; ok && len(val) > 0 {
|
||||||
|
// r.ApplicationIDs, err = val, nil
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||||
@ -182,6 +183,7 @@ func (svc *application) Update(ctx context.Context, upd *types.Application) (app
|
|||||||
// Assign changed values after afterUpdate events are emitted
|
// Assign changed values after afterUpdate events are emitted
|
||||||
app.Name = upd.Name
|
app.Name = upd.Name
|
||||||
app.Enabled = upd.Enabled
|
app.Enabled = upd.Enabled
|
||||||
|
app.Weight = upd.Weight
|
||||||
app.UpdatedAt = now()
|
app.UpdatedAt = now()
|
||||||
|
|
||||||
if upd.Unify != nil {
|
if upd.Unify != nil {
|
||||||
@ -282,6 +284,31 @@ func (svc *application) Undelete(ctx context.Context, ID uint64) (err error) {
|
|||||||
return svc.recordAction(ctx, aaProps, ApplicationActionUndelete, err)
|
return svc.recordAction(ctx, aaProps, ApplicationActionUndelete, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *application) Reorder(ctx context.Context, order []uint64) (err error) {
|
||||||
|
var (
|
||||||
|
aProps = &applicationActionProps{}
|
||||||
|
)
|
||||||
|
|
||||||
|
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) error {
|
||||||
|
for _, id := range order {
|
||||||
|
// This access control creates an aux application so we don't have to fetch them
|
||||||
|
// from the store; the ID is the only thing that matters...
|
||||||
|
auxApp := &types.Application{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !svc.ac.CanUpdateApplication(ctx, auxApp) {
|
||||||
|
aProps.application = auxApp
|
||||||
|
return ApplicationErrNotAllowedToUpdate(aProps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.ReorderApplications(ctx, s, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
return svc.recordAction(ctx, aProps, ApplicationActionReorder, err)
|
||||||
|
}
|
||||||
|
|
||||||
// toLabeledApplications converts to []label.LabeledResource
|
// toLabeledApplications converts to []label.LabeledResource
|
||||||
//
|
//
|
||||||
// This function is auto-generated.
|
// This function is auto-generated.
|
||||||
|
|||||||
@ -265,6 +265,26 @@ func ApplicationActionSearch(props ...*applicationActionProps) *applicationActio
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplicationActionReorder returns "system:application.reorder" action
|
||||||
|
//
|
||||||
|
// This function is auto-generated.
|
||||||
|
//
|
||||||
|
func ApplicationActionReorder(props ...*applicationActionProps) *applicationAction {
|
||||||
|
a := &applicationAction{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
resource: "system:application",
|
||||||
|
action: "reorder",
|
||||||
|
log: "reordered applications",
|
||||||
|
severity: actionlog.Notice,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(props) > 0 {
|
||||||
|
a.props = props[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
// ApplicationActionLookup returns "system:application.lookup" action
|
// ApplicationActionLookup returns "system:application.lookup" action
|
||||||
//
|
//
|
||||||
// This function is auto-generated.
|
// This function is auto-generated.
|
||||||
|
|||||||
@ -31,6 +31,9 @@ actions:
|
|||||||
log: "searched for applications"
|
log: "searched for applications"
|
||||||
severity: info
|
severity: info
|
||||||
|
|
||||||
|
- action: reorder
|
||||||
|
log: "reordered applications"
|
||||||
|
|
||||||
- action: lookup
|
- action: lookup
|
||||||
log: "looked-up for a {application}"
|
log: "looked-up for a {application}"
|
||||||
severity: info
|
severity: info
|
||||||
|
|||||||
@ -3,9 +3,10 @@ package types
|
|||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||||
@ -17,6 +18,7 @@ type (
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
OwnerID uint64 `json:"ownerID"`
|
OwnerID uint64 `json:"ownerID"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
Weight int `json:"weight"`
|
||||||
|
|
||||||
Unify *ApplicationUnify `json:"unify,omitempty"`
|
Unify *ApplicationUnify `json:"unify,omitempty"`
|
||||||
|
|
||||||
@ -34,7 +36,6 @@ type (
|
|||||||
Logo string `json:"logo"`
|
Logo string `json:"logo"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Config string `json:"config"`
|
Config string `json:"config"`
|
||||||
Order uint `json:"order"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationFilter struct {
|
ApplicationFilter struct {
|
||||||
|
|||||||
@ -514,7 +514,6 @@ func TestStoreYaml_base(t *testing.T) {
|
|||||||
req.Equal("logo", app.Unify.Logo)
|
req.Equal("logo", app.Unify.Logo)
|
||||||
req.Equal("url", app.Unify.Url)
|
req.Equal("url", app.Unify.Url)
|
||||||
req.Equal("{\"config\": \"config\"}", app.Unify.Config)
|
req.Equal("{\"config\": \"config\"}", app.Unify.Config)
|
||||||
req.Equal(uint(0), app.Unify.Order)
|
|
||||||
req.Equal(createdAt.Format(time.RFC3339), app.CreatedAt.Format(time.RFC3339))
|
req.Equal(createdAt.Format(time.RFC3339), app.CreatedAt.Format(time.RFC3339))
|
||||||
req.Equal(updatedAt.Format(time.RFC3339), app.UpdatedAt.Format(time.RFC3339))
|
req.Equal(updatedAt.Format(time.RFC3339), app.UpdatedAt.Format(time.RFC3339))
|
||||||
},
|
},
|
||||||
|
|||||||
@ -66,7 +66,6 @@ func sTestApplication(ctx context.Context, t *testing.T, s store.Storer, usrID u
|
|||||||
Logo: "logo",
|
Logo: "logo",
|
||||||
Url: "url",
|
Url: "url",
|
||||||
Config: "{\"config\": \"config\"}",
|
Config: "{\"config\": \"config\"}",
|
||||||
Order: 0,
|
|
||||||
},
|
},
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
|
|||||||
@ -3,17 +3,18 @@ package system
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cortezaproject/corteza-server/pkg/id"
|
"github.com/cortezaproject/corteza-server/pkg/id"
|
||||||
"github.com/cortezaproject/corteza-server/store"
|
"github.com/cortezaproject/corteza-server/store"
|
||||||
"github.com/cortezaproject/corteza-server/system/service"
|
"github.com/cortezaproject/corteza-server/system/service"
|
||||||
"github.com/cortezaproject/corteza-server/system/types"
|
"github.com/cortezaproject/corteza-server/system/types"
|
||||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||||
"github.com/steinfletcher/apitest-jsonpath"
|
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h helper) clearApplications() {
|
func (h helper) clearApplications() {
|
||||||
@ -44,6 +45,17 @@ func (h helper) lookupApplicationByID(ID uint64) *types.Application {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h helper) lookupApplicationByName(name string) *types.Application {
|
||||||
|
res, _, err := store.SearchApplications(context.Background(), service.DefaultStore, types.ApplicationFilter{
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
h.noError(err)
|
||||||
|
if len(res) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return res[0]
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplicationRead(t *testing.T) {
|
func TestApplicationRead(t *testing.T) {
|
||||||
h := newHelper(t)
|
h := newHelper(t)
|
||||||
|
|
||||||
@ -122,6 +134,25 @@ func TestApplicationCreate(t *testing.T) {
|
|||||||
End()
|
End()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplicationCreate_weight(t *testing.T) {
|
||||||
|
h := newHelper(t)
|
||||||
|
h.allow(types.SystemRBACResource, "application.create")
|
||||||
|
name := "name_weight_create_" + rs()
|
||||||
|
|
||||||
|
h.apiInit().
|
||||||
|
Post("/application/").
|
||||||
|
FormData("name", name).
|
||||||
|
FormData("weight", "10").
|
||||||
|
Expect(t).
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Assert(helpers.AssertNoErrors).
|
||||||
|
End()
|
||||||
|
|
||||||
|
res := h.lookupApplicationByName(name)
|
||||||
|
h.a.NotNil(res)
|
||||||
|
h.a.Equal(10, res.Weight)
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplicationUpdateForbidden(t *testing.T) {
|
func TestApplicationUpdateForbidden(t *testing.T) {
|
||||||
h := newHelper(t)
|
h := newHelper(t)
|
||||||
u := h.repoMakeApplication()
|
u := h.repoMakeApplication()
|
||||||
@ -159,6 +190,75 @@ func TestApplicationUpdate(t *testing.T) {
|
|||||||
h.a.Equal(newName, res.Name)
|
h.a.Equal(newName, res.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplicationUpdate_weight(t *testing.T) {
|
||||||
|
h := newHelper(t)
|
||||||
|
res := h.repoMakeApplication()
|
||||||
|
h.allow(types.ApplicationRBACResource.AppendWildcard(), "update")
|
||||||
|
|
||||||
|
newName := "updated-" + rs()
|
||||||
|
newHandle := "updated-" + rs()
|
||||||
|
|
||||||
|
h.apiInit().
|
||||||
|
Put(fmt.Sprintf("/application/%d", res.ID)).
|
||||||
|
Header("Accept", "application/json").
|
||||||
|
FormData("name", newName).
|
||||||
|
FormData("handle", newHandle).
|
||||||
|
FormData("weight", "20").
|
||||||
|
Expect(t).
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Assert(helpers.AssertNoErrors).
|
||||||
|
End()
|
||||||
|
|
||||||
|
res = h.lookupApplicationByID(res.ID)
|
||||||
|
h.a.NotNil(res)
|
||||||
|
h.a.Equal(20, res.Weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplicationReorder_forbiden(t *testing.T) {
|
||||||
|
h := newHelper(t)
|
||||||
|
h.allow(types.ApplicationRBACResource.AppendWildcard(), "update")
|
||||||
|
a := h.repoMakeApplication()
|
||||||
|
b := h.repoMakeApplication()
|
||||||
|
c := h.repoMakeApplication()
|
||||||
|
h.deny(types.ApplicationRBACResource.AppendID(b.ID), "update")
|
||||||
|
|
||||||
|
h.apiInit().
|
||||||
|
Post("/application/reorder").
|
||||||
|
Header("Accept", "application/json").
|
||||||
|
JSON(fmt.Sprintf(`{ "applicationIDs": ["%d", "%d", "%d"] }`, b.ID, a.ID, c.ID)).
|
||||||
|
Expect(t).
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Assert(helpers.AssertError("not allowed to update this application")).
|
||||||
|
End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplicationReorder(t *testing.T) {
|
||||||
|
h := newHelper(t)
|
||||||
|
h.allow(types.ApplicationRBACResource.AppendWildcard(), "update")
|
||||||
|
a := h.repoMakeApplication()
|
||||||
|
b := h.repoMakeApplication()
|
||||||
|
c := h.repoMakeApplication()
|
||||||
|
|
||||||
|
h.apiInit().
|
||||||
|
Post("/application/reorder").
|
||||||
|
Header("Accept", "application/json").
|
||||||
|
JSON(fmt.Sprintf(`{ "applicationIDs": ["%d", "%d", "%d"] }`, b.ID, a.ID, c.ID)).
|
||||||
|
Expect(t).
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Assert(helpers.AssertNoErrors).
|
||||||
|
End()
|
||||||
|
|
||||||
|
check := func(app uint64, w int) {
|
||||||
|
res := h.lookupApplicationByID(app)
|
||||||
|
h.a.NotNil(res)
|
||||||
|
h.a.Equal(w, res.Weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
check(b.ID, 1)
|
||||||
|
check(a.ID, 2)
|
||||||
|
check(c.ID, 3)
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplicationDeleteForbidden(t *testing.T) {
|
func TestApplicationDeleteForbidden(t *testing.T) {
|
||||||
h := newHelper(t)
|
h := newHelper(t)
|
||||||
u := h.repoMakeApplication()
|
u := h.repoMakeApplication()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user