mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-23 05:42:33 +09:00
Adds `sort` and `order` query parameters to all action job list API
endpoints (`/admin/actions/jobs`, `/repos/{owner}/{repo}/actions/jobs`,
`/repos/{owner}/{repo}/actions/runs/{run}/jobs`, `/user/actions/jobs`),
following the existing `OrderByMap` pattern used by repo/user search
endpoints.
- Default is `id` / `asc` (backwards compatible — matches previous DB
natural order)
- Only `id` sort field for now; the map is extensible for future fields
- Returns 422 for invalid sort/order values
- `ToOrders()` returns empty string when `OrderBy` is unset, so internal
callers (webhook dispatch, concurrency checks) are unaffected
Closes: #37666
Supersedes: #37667
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
155 lines
4.1 KiB
Go
155 lines
4.1 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/container"
|
|
"code.gitea.io/gitea/modules/optional"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
type ActionJobList []*ActionRunJob
|
|
|
|
func (jobs ActionJobList) GetRunIDs() []int64 {
|
|
return container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) {
|
|
return j.RunID, j.RunID != 0
|
|
})
|
|
}
|
|
|
|
// SortMatrixGroupsByName natural-sorts each contiguous run of jobs that share a JobID
|
|
// so matrix expansions (e.g. "test (1)", "test (2)", "test (10)") appear in human order.
|
|
// Input is expected to be in DB id order so JobID groups are contiguous; cross-group order is preserved.
|
|
func (jobs ActionJobList) SortMatrixGroupsByName() {
|
|
for i := 0; i < len(jobs); {
|
|
j := i + 1
|
|
for j < len(jobs) && jobs[j].JobID == jobs[i].JobID {
|
|
j++
|
|
}
|
|
slices.SortFunc(jobs[i:j], func(a, b *ActionRunJob) int {
|
|
return base.NaturalSortCompare(a.Name, b.Name)
|
|
})
|
|
i = j
|
|
}
|
|
}
|
|
|
|
func (jobs ActionJobList) LoadRepos(ctx context.Context) error {
|
|
repoIDs := container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) {
|
|
return j.RepoID, j.RepoID != 0 && j.Repo == nil
|
|
})
|
|
if len(repoIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
|
if err := db.GetEngine(ctx).In("id", repoIDs).Find(&repos); err != nil {
|
|
return err
|
|
}
|
|
for _, j := range jobs {
|
|
if j.RepoID > 0 && j.Repo == nil {
|
|
j.Repo = repos[j.RepoID]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
|
if withRepo {
|
|
if err := jobs.LoadRepos(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
runIDs := jobs.GetRunIDs()
|
|
runs := make(map[int64]*ActionRun, len(runIDs))
|
|
if err := db.GetEngine(ctx).In("id", runIDs).Find(&runs); err != nil {
|
|
return err
|
|
}
|
|
for _, j := range jobs {
|
|
if j.Run == nil {
|
|
j.Run = runs[j.RunID]
|
|
}
|
|
if j.Run != nil {
|
|
j.Run.Repo = j.Repo
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (jobs ActionJobList) LoadAttributes(ctx context.Context, withRepo bool) error {
|
|
return jobs.LoadRuns(ctx, withRepo)
|
|
}
|
|
|
|
type FindRunJobOptions struct {
|
|
db.ListOptions
|
|
RunID int64
|
|
RunAttemptID optional.Option[int64] // use optional to allow filtering by zero (legacy jobs have run_attempt_id=0)
|
|
RepoID int64
|
|
OwnerID int64
|
|
CommitSHA string
|
|
Statuses []Status
|
|
UpdatedBefore timeutil.TimeStamp
|
|
ConcurrencyGroup string
|
|
OrderBy db.SearchOrderBy
|
|
}
|
|
|
|
var JobOrderByMap = map[string]map[string]db.SearchOrderBy{
|
|
"asc": {"id": "`action_run_job`.id ASC"},
|
|
"desc": {"id": "`action_run_job`.id DESC"},
|
|
}
|
|
|
|
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
|
cond := builder.NewCond()
|
|
if opts.RunID > 0 {
|
|
cond = cond.And(builder.Eq{"`action_run_job`.run_id": opts.RunID})
|
|
}
|
|
if opts.RunAttemptID.Has() {
|
|
cond = cond.And(builder.Eq{"`action_run_job`.run_attempt_id": opts.RunAttemptID.Value()})
|
|
}
|
|
if opts.RepoID > 0 {
|
|
cond = cond.And(builder.Eq{"`action_run_job`.repo_id": opts.RepoID})
|
|
}
|
|
if opts.CommitSHA != "" {
|
|
cond = cond.And(builder.Eq{"`action_run_job`.commit_sha": opts.CommitSHA})
|
|
}
|
|
if len(opts.Statuses) > 0 {
|
|
cond = cond.And(builder.In("`action_run_job`.status", opts.Statuses))
|
|
}
|
|
if opts.UpdatedBefore > 0 {
|
|
cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore})
|
|
}
|
|
if opts.ConcurrencyGroup != "" {
|
|
if opts.RepoID == 0 {
|
|
panic("Invalid FindRunJobOptions: repo_id is required")
|
|
}
|
|
cond = cond.And(builder.Eq{"`action_run_job`.concurrency_group": opts.ConcurrencyGroup})
|
|
}
|
|
return cond
|
|
}
|
|
|
|
func (opts FindRunJobOptions) ToJoins() []db.JoinFunc {
|
|
if opts.OwnerID > 0 {
|
|
return []db.JoinFunc{
|
|
func(sess db.Engine) error {
|
|
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (opts FindRunJobOptions) ToOrders() string {
|
|
return string(opts.OrderBy)
|
|
}
|
|
|
|
var _ db.FindOptionsOrder = FindRunJobOptions{}
|