From 1366d95b62ac7717826fd9f64c9e268013107008 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 13 Jun 2024 14:36:32 -0500 Subject: [PATCH] :bug: Fix task filtering and sorting. (#659) Fix issues reported [here](https://github.com/konveyor/tackle2-ui/issues/1931#issuecomment-2163782267). 1. sorting seems to work only for `id` field i.e. 2. filtering not supported for `createUser` and `id` fields( both listed in the enhancement) 3. application name is missing in the response and in filtering. Filtering by application ID works but this is not so nice. 4. `priority` is missing in the response and in the filtering 5. for `kind` the filtering is supported by the server but it seems it's not included in the response --- Filter and sorting: needed to support GORM join queries which qualifies fields by table. Example: For a task query joined with application, produces all of the task fields qualified as table.column: ``` SELECT task.id, task.name ... ``` And the (joined) application fields prefixed with `APPLICATION__` ``` SELECT APPLICATION__ID, APPLICATION__NAME ... ``` --------- Signed-off-by: Jeff Ortel --- api/filter/filter.go | 5 +++++ api/sort/sort.go | 53 ++++++++++++++++++++++++++++++++++++-------- api/task.go | 23 ++++++++++++++++--- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/api/filter/filter.go b/api/filter/filter.go index 413e9a91..444abd84 100644 --- a/api/filter/filter.go +++ b/api/filter/filter.go @@ -332,6 +332,7 @@ func (f *Field) Expand() (expanded []Field) { // split field name. // format: resource.name // The resource may be "" (anonymous). +// The (.) separator is escaped when preceded by (\). func (f *Field) split() (relation string, name string) { s := f.Field.Value mark := strings.Index(s, ".") @@ -339,6 +340,10 @@ func (f *Field) split() (relation string, name string) { name = s return } + if mark > 0 && s[mark-1] == '\\' { + name = s[:mark-1] + s[mark:] + return + } relation = s[:mark] name = s[mark+1:] return diff --git a/api/sort/sort.go b/api/sort/sort.go index 08d93e81..607a9562 100644 --- a/api/sort/sort.go +++ b/api/sort/sort.go @@ -16,17 +16,29 @@ type Clause struct { // Sort provides sorting. type Sort struct { - fields map[string]interface{} + fields map[string]any + alias map[string]string clauses []Clause } +// Add adds virtual field and aliases. +func (r *Sort) Add(name string, aliases ...string) { + r.init() + for _, alias := range aliases { + alias = strings.ToLower(alias) + r.fields[alias] = 0 + r.alias[alias] = name + } +} + // With context. -func (r *Sort) With(ctx *gin.Context, m interface{}) (err error) { +func (r *Sort) With(ctx *gin.Context, m any) (err error) { param := ctx.Query("sort") if param == "" { return } - r.fields = r.inspect(m) + r.init() + r.inspect(m) for _, s := range strings.Split(param, ",") { clause := Clause{} s = strings.TrimSpace(s) @@ -38,7 +50,7 @@ func (r *Sort) With(ctx *gin.Context, m interface{}) (err error) { err = &SortError{s} return } - clause.name = s + clause.name = r.resolved(s) r.clauses = append( r.clauses, clause) @@ -55,7 +67,7 @@ func (r *Sort) With(ctx *gin.Context, m interface{}) (err error) { err = &SortError{s} return } - clause.name = s + clause.name = r.resolved(s) r.clauses = append( r.clauses, clause) @@ -66,6 +78,7 @@ func (r *Sort) With(ctx *gin.Context, m interface{}) (err error) { // Sorted returns sorted DB. func (r *Sort) Sorted(in *gorm.DB) (out *gorm.DB) { + r.init() out = in if len(r.clauses) == 0 { return @@ -78,11 +91,33 @@ func (r *Sort) Sorted(in *gorm.DB) (out *gorm.DB) { return } +// init allocate maps. +func (r *Sort) init() { + if r.fields == nil { + r.fields = make(map[string]any) + } + if r.alias == nil { + r.alias = make(map[string]string) + } +} + // inspect object and return fields. -func (r *Sort) inspect(m interface{}) (fields map[string]interface{}) { - fields = reflect.Fields(m) - for key, v := range fields { - fields[strings.ToLower(key)] = v +func (r *Sort) inspect(m any) { + r.init() + for key, v := range reflect.Fields(m) { + key = strings.ToLower(key) + r.fields[key] = v + } + return +} + +// resolved returns field names with alias resolved. +func (r *Sort) resolved(in string) (out string) { + r.init() + out = in + alias, found := r.alias[in] + if found { + out = alias } return } diff --git a/api/task.go b/api/task.go index 4d974c2d..d13a0192 100644 --- a/api/task.go +++ b/api/task.go @@ -114,11 +114,13 @@ func (h TaskHandler) Get(ctx *gin.Context) { // @description List all tasks. // @description Filters: // @description - kind +// @description - createUser // @description - addon // @description - name // @description - locator // @description - state // @description - application.id +// @description - application.name // @description The state=queued is an alias for queued states. // @tags tasks // @produce json @@ -126,14 +128,18 @@ func (h TaskHandler) Get(ctx *gin.Context) { // @router /tasks [get] func (h TaskHandler) List(ctx *gin.Context) { resources := []Task{} + // filter filter, err := qf.New(ctx, []qf.Assert{ + {Field: "id", Kind: qf.LITERAL}, + {Field: "createUser", Kind: qf.STRING}, {Field: "kind", Kind: qf.STRING}, {Field: "addon", Kind: qf.STRING}, {Field: "name", Kind: qf.STRING}, {Field: "locator", Kind: qf.STRING}, {Field: "state", Kind: qf.STRING}, {Field: "application.id", Kind: qf.STRING}, + {Field: "application.name", Kind: qf.STRING}, }) if err != nil { _ = ctx.Error(err) @@ -157,17 +163,28 @@ func (h TaskHandler) List(ctx *gin.Context) { values = values.Join(qf.OR) filter = filter.Revalued("state", values) } + filter = filter.Renamed("application.id", "application__id") + filter = filter.Renamed("application.name", "application__name") + filter = filter.Renamed("createUser", "task\\.createUser") + filter = filter.Renamed("id", "task\\.id") + filter = filter.Renamed("name", "task\\.name") + // sort sort := Sort{} - err = sort.With(ctx, &model.Issue{}) + sort.Add("task.id", "id") + sort.Add("task.createUser", "createUser") + sort.Add("task.name", "name") + sort.Add("application__id", "application.id") + sort.Add("application__name", "application.name") + err = sort.With(ctx, &model.Task{}) if err != nil { _ = ctx.Error(err) return } + // Fetch db := h.DB(ctx) db = db.Model(&model.Task{}) - db = db.Preload(clause.Associations) + db = db.Joins("Application") db = sort.Sorted(db) - filter = filter.Renamed("application.id", "applicationId") db = filter.Where(db) var m model.Task var list []model.Task