Batch-load related data in actions run, job, and task API endpoints (#37032)

Avoid per-item DB queries in ListRuns, ListJobs, and ListActionTasks by
batch-loading trigger users, repositories, and task attributes before
the conversion loop. Remove ReferencesGitRepo from the /actions route
group since no task/run endpoints use it.

Added tests for these endpoints as well.

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
Myers Carpenter
2026-04-29 04:39:43 -04:00
committed by GitHub
parent 0ba862cb97
commit 18762c7748
15 changed files with 214 additions and 135 deletions
+1 -1
View File
@@ -1272,7 +1272,7 @@ func Routes() *web.Router {
m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact)
})
m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact)
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
}, reqRepoReader(unit.TypeActions))
m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys).
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
+11 -4
View File
@@ -848,6 +848,12 @@ func ListActionTasks(ctx *context.APIContext) {
res := new(api.ActionTaskResponse)
res.TotalCount = total
taskList := actions_model.TaskList(tasks)
if err := taskList.LoadAttributes(ctx); err != nil {
ctx.APIErrorInternal(err)
return
}
res.Entries = make([]*api.ActionTask, len(tasks))
for i := range tasks {
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
@@ -859,7 +865,7 @@ func ListActionTasks(ctx *context.APIContext) {
}
ctx.SetLinkHeader(total, listOptions.PageSize)
ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency
ctx.SetTotalCountHeader(total) // Duplicates api response field, but it's better to set it for consistency
ctx.JSON(http.StatusOK, &res)
}
@@ -1155,6 +1161,7 @@ func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionR
ctx.APIErrorInternal(err)
return nil
}
run.Repo = ctx.Repo.Repository
return run
}
@@ -1226,7 +1233,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
return
}
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, nil)
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -1275,7 +1282,7 @@ func GetWorkflowRunAttempt(ctx *context.APIContext) {
return
}
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, attempt)
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -1330,7 +1337,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
return
}
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, nil)
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
if err != nil {
ctx.APIErrorInternal(err)
return
+31 -17
View File
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -62,6 +63,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI
res := new(api.ActionWorkflowJobsResponse)
res.TotalCount = total
jobList := actions_model.ActionJobList(jobs)
if err := jobList.LoadAttributes(ctx, true); err != nil {
ctx.APIErrorInternal(err)
return
}
res.Entries = make([]*api.ActionWorkflowJob, len(jobs))
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
@@ -70,11 +77,11 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI
if isRepoLevel {
repository = ctx.Repo.Repository
} else {
repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID)
if err != nil {
ctx.APIErrorInternal(err)
if jobs[i].Run == nil || jobs[i].Run.Repo == nil {
ctx.APIErrorInternal(fmt.Errorf("job %d is missing its run or repository", jobs[i].ID))
return
}
repository = jobs[i].Run.Repo
}
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i])
@@ -169,21 +176,28 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
res := new(api.ActionWorkflowRunsResponse)
res.TotalCount = total
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
for i := range runs {
var repository *repo_model.Repository
if isRepoLevel {
repository = ctx.Repo.Repository
} else {
repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
}
runList := actions_model.RunList(runs)
if err := runList.LoadTriggerUser(ctx); err != nil {
ctx.APIErrorInternal(err)
return
}
convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i], nil)
if err := runList.LoadRepos(ctx); err != nil {
ctx.APIErrorInternal(err)
return
}
repos := repo_model.RepositoryList(container.FilterSlice(runs, func(r *actions_model.ActionRun) (*repo_model.Repository, bool) {
return r.Repo, r.Repo != nil
}))
if err := repos.LoadOwners(ctx); err != nil {
ctx.APIErrorInternal(err)
return
}
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
for i := range runs {
// TODO: load run attempts in batch
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil)
if err != nil {
ctx.APIErrorInternal(err)
return