diff --git a/api/filter/filter.go b/api/filter/filter.go index 44f113a8e..413e9a910 100644 --- a/api/filter/filter.go +++ b/api/filter/filter.go @@ -127,7 +127,7 @@ func (f *Filter) With(selector ...string) (out Filter) { return } -// Renamed filter with predicate field renamed. +// Renamed return a filter with predicate field renamed. func (f *Filter) Renamed(name, renamed string) (out Filter) { var predicates []Predicate for _, p := range f.predicates { @@ -142,6 +142,21 @@ func (f *Filter) Renamed(name, renamed string) (out Filter) { return } +// Revalued return a filter with the named field value replaced. +func (f *Filter) Revalued(name string, value Value) (out Filter) { + var predicates []Predicate + for _, p := range f.predicates { + if p.Field.Value == name { + p.Value = value + } + predicates = append( + predicates, + p) + } + out.predicates = predicates + return +} + // Delete specified fields. func (f *Filter) Delete(name string) (found bool) { var wanted []Predicate diff --git a/api/filter/parser.go b/api/filter/parser.go index 5bde583ef..d87f35d7b 100644 --- a/api/filter/parser.go +++ b/api/filter/parser.go @@ -97,6 +97,17 @@ func (r *Value) Operator(operator byte) (matched bool) { return } +// Join values with operator. +func (r *Value) Join(operator byte) (out Value) { + for i := range r.ByKind(LITERAL, STRING) { + if i > 0 { + out = append(out, Token{Kind: OPERATOR, Value: string(operator)}) + } + out = append(out, (*r)[i]) + } + return +} + // List construct. // Example: (red|blue|green) type List struct { diff --git a/api/task.go b/api/task.go index 51b3f61bf..15a489524 100644 --- a/api/task.go +++ b/api/task.go @@ -26,6 +26,8 @@ import ( // Routes const ( TasksRoot = "/tasks" + TasksReportRoot = TasksRoot + "/report" + TasksReportQueueRoot = TasksReportRoot + "/queue" TaskRoot = TasksRoot + "/:" + ID TaskReportRoot = TaskRoot + "/report" TaskAttachedRoot = TaskRoot + "/attached" @@ -54,6 +56,7 @@ func (h TaskHandler) AddRoutes(e *gin.Engine) { routeGroup.GET(TaskRoot, h.Get) routeGroup.PUT(TaskRoot, h.Update) routeGroup.DELETE(TaskRoot, h.Delete) + routeGroup.GET(TasksReportQueueRoot, h.Queued) // Actions routeGroup.PUT(TaskSubmitRoot, h.Submit, h.Update) routeGroup.PUT(TaskCancelRoot, h.Cancel) @@ -116,6 +119,7 @@ func (h TaskHandler) Get(ctx *gin.Context) { // @description - locator // @description - state // @description - application.id +// @description The state=queued is an alias for queued states. // @tags tasks // @produce json // @success 200 {object} []api.Task @@ -135,6 +139,24 @@ func (h TaskHandler) List(ctx *gin.Context) { _ = ctx.Error(err) return } + if state, found := filter.Field("state"); found { + values := qf.Value{} + for _, v := range state.Value.ByKind(qf.LITERAL, qf.STRING) { + switch v.Value { + case "queued": + values = append( + values, + qf.Token{Kind: qf.STRING, Value: tasking.Ready}, + qf.Token{Kind: qf.STRING, Value: tasking.Postponed}, + qf.Token{Kind: qf.STRING, Value: tasking.Pending}, + qf.Token{Kind: qf.STRING, Value: tasking.Running}) + default: + values = append(values, v) + } + } + values = values.Join(qf.OR) + filter = filter.Revalued("state", values) + } sort := Sort{} err = sort.With(ctx, &model.Issue{}) if err != nil { @@ -178,6 +200,64 @@ func (h TaskHandler) List(ctx *gin.Context) { h.Respond(ctx, http.StatusOK, resources) } +// Queued godoc +// @summary Queued queued task report. +// @description Queued queued task report. +// @description Filters: +// @description - addon +// @tags tasks +// @produce json +// @success 200 {object} []api.TaskQueue +// @router /tasks [get] +func (h TaskHandler) Queued(ctx *gin.Context) { + r := TaskQueue{} + filter, err := qf.New(ctx, + []qf.Assert{ + {Field: "addon", Kind: qf.STRING}, + }) + if err != nil { + _ = ctx.Error(err) + return + } + db := h.DB(ctx) + db = db.Table("task") + db = filter.Where(db) + type M struct { + State string + Count int + } + db = db.Select("State", "COUNT(*)") + db = db.Where( + "State", []string{ + tasking.Ready, + tasking.Postponed, + tasking.Pending, + tasking.Ready, + }) + db = db.Group("State") + var list []M + err = db.Find(&list).Error + if err != nil { + _ = ctx.Error(err) + return + } + for _, q := range list { + r.Total += q.Count + switch q.State { + case tasking.Ready: + r.Ready = q.Count + case tasking.Postponed: + r.Postponed = q.Count + case tasking.Pending: + r.Pending = q.Count + case tasking.Running: + r.Running = q.Count + } + } + + h.Respond(ctx, http.StatusOK, r) +} + // Create godoc // @summary Create a task. // @description Create a task. @@ -805,3 +885,12 @@ func (r *TaskReport) Model() (m *model.TaskReport) { } return } + +// TaskQueue report. +type TaskQueue struct { + Total int `json:"total"` + Ready int `json:"ready"` + Postponed int `json:"postponed"` + Pending int `json:"pending"` + Running int `json:"running"` +}