diff --git a/api/filter/filter.go b/api/filter/filter.go index 413e9a910..444abd84c 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 08d93e81b..607a95625 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 4d974c2d8..d13a0192b 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