Skip to content

Commit 18762c7

Browse files
myerswxiaoguangsilverwindclaude
authored
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>
1 parent 0ba862c commit 18762c7

15 files changed

Lines changed: 213 additions & 134 deletions

File tree

models/actions/run.go

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ func init() {
7171
db.RegisterModel(new(ActionRunIndex))
7272
}
7373

74-
func (run *ActionRun) HTMLURL() string {
74+
func (run *ActionRun) HTMLURL(ctxOpt ...context.Context) string {
7575
if run.Repo == nil {
7676
return ""
7777
}
78-
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.ID)
78+
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(ctxOpt...), run.ID)
7979
}
8080

8181
func (run *ActionRun) Link() string {
@@ -120,11 +120,7 @@ func (run *ActionRun) RefTooltip() string {
120120
}
121121

122122
// LoadAttributes load Repo TriggerUser if not loaded
123-
func (run *ActionRun) LoadAttributes(ctx context.Context) (err error) {
124-
if run == nil {
125-
return nil
126-
}
127-
123+
func (run *ActionRun) LoadAttributes(ctx context.Context) error {
128124
if err := run.LoadRepo(ctx); err != nil {
129125
return err
130126
}
@@ -133,18 +129,19 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) (err error) {
133129
return err
134130
}
135131

136-
if run.TriggerUser == nil {
137-
run.TriggerUserID, run.TriggerUser, err = user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
138-
if err != nil {
139-
return err
140-
}
141-
}
132+
return run.LoadTriggerUser(ctx)
133+
}
142134

143-
return nil
135+
func (run *ActionRun) LoadTriggerUser(ctx context.Context) (err error) {
136+
if run.TriggerUser != nil {
137+
return nil
138+
}
139+
run.TriggerUserID, run.TriggerUser, err = user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
140+
return err
144141
}
145142

146143
func (run *ActionRun) LoadRepo(ctx context.Context) error {
147-
if run == nil || run.Repo != nil {
144+
if run.Repo != nil {
148145
return nil
149146
}
150147

models/actions/run_attempt.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ func (attempt *ActionRunAttempt) Duration() time.Duration {
5151
}
5252

5353
func (attempt *ActionRunAttempt) LoadAttributes(ctx context.Context) (err error) {
54-
if attempt == nil {
55-
return nil
56-
}
57-
5854
if attempt.Run == nil {
5955
run, err := GetRunByRepoAndID(ctx, attempt.RepoID, attempt.RunID)
6056
if err != nil {

models/actions/run_job.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,6 @@ func (job *ActionRunJob) LoadRepo(ctx context.Context) error {
120120

121121
// LoadAttributes load Run if not loaded
122122
func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
123-
if job == nil {
124-
return nil
125-
}
126-
127123
if err := job.LoadRun(ctx); err != nil {
128124
return err
129125
}

models/actions/run_job_list.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
5656
return err
5757
}
5858
for _, j := range jobs {
59-
if j.RunID > 0 && j.Run == nil {
59+
if j.Run == nil {
6060
j.Run = runs[j.RunID]
61+
}
62+
if j.Run != nil {
6163
j.Run.Repo = j.Repo
6264
}
6365
}

models/actions/run_list.go

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88

99
"code.gitea.io/gitea/models/db"
10+
repo_model "code.gitea.io/gitea/models/repo"
1011
user_model "code.gitea.io/gitea/models/user"
1112
"code.gitea.io/gitea/modules/container"
1213
"code.gitea.io/gitea/modules/translation"
@@ -17,27 +18,39 @@ import (
1718

1819
type RunList []*ActionRun
1920

20-
// GetUserIDs returns a slice of user's id
21-
func (runs RunList) GetUserIDs() []int64 {
22-
return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
23-
return run.TriggerUserID, true
24-
})
25-
}
26-
2721
func (runs RunList) LoadTriggerUser(ctx context.Context) error {
28-
userIDs := runs.GetUserIDs()
22+
userIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
23+
return run.TriggerUserID, run.TriggerUser == nil
24+
})
2925
users := make(map[int64]*user_model.User, len(userIDs))
3026
if err := db.GetEngine(ctx).In("id", userIDs).Find(&users); err != nil {
3127
return err
3228
}
3329
for _, run := range runs {
34-
if run.TriggerUserID == user_model.ActionsUserID {
35-
run.TriggerUser = user_model.NewActionsUser()
36-
} else {
37-
run.TriggerUser = users[run.TriggerUserID]
38-
if run.TriggerUser == nil {
39-
run.TriggerUser = user_model.NewGhostUser()
40-
}
30+
if run.TriggerUser != nil {
31+
continue
32+
}
33+
run.TriggerUser = users[run.TriggerUserID]
34+
if run.TriggerUserID < 0 {
35+
run.TriggerUserID, run.TriggerUser, _ = user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
36+
} else if run.TriggerUser == nil {
37+
run.TriggerUserID, run.TriggerUser, _ = user_model.GetPossibleUserByID(ctx, user_model.GhostUserID)
38+
}
39+
}
40+
return nil
41+
}
42+
43+
func (runs RunList) LoadRepos(ctx context.Context) error {
44+
repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
45+
return run.RepoID, run.Repo == nil
46+
})
47+
repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs)
48+
if err != nil {
49+
return err
50+
}
51+
for _, run := range runs {
52+
if run.Repo == nil {
53+
run.Repo = repos[run.RepoID]
4154
}
4255
}
4356
return nil

models/actions/task.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,6 @@ func (task *ActionTask) LoadJob(ctx context.Context) error {
125125

126126
// LoadAttributes load Job Steps if not loaded
127127
func (task *ActionTask) LoadAttributes(ctx context.Context) error {
128-
if task == nil {
129-
return nil
130-
}
131128
if err := task.LoadJob(ctx); err != nil {
132129
return err
133130
}

models/repo/repo.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,9 @@ func (repo *Repository) CommitLink(commitID string) (result string) {
376376
}
377377

378378
// APIURL returns the repository API URL
379-
func (repo *Repository) APIURL() string {
380-
return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
379+
func (repo *Repository) APIURL(ctxOpt ...context.Context) string {
380+
ctx := util.OptionalArg(ctxOpt, context.TODO())
381+
return httplib.MakeAbsoluteURL(ctx, setting.AppSubURL+"/api/v1/repos/"+url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name))
381382
}
382383

383384
// GetCommitsCountCacheKey returns cache key used for commits count caching.

routers/api/v1/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1272,7 +1272,7 @@ func Routes() *web.Router {
12721272
m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact)
12731273
})
12741274
m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact)
1275-
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
1275+
}, reqRepoReader(unit.TypeActions))
12761276
m.Group("/keys", func() {
12771277
m.Combo("").Get(repo.ListDeployKeys).
12781278
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)

routers/api/v1/repo/action.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,12 @@ func ListActionTasks(ctx *context.APIContext) {
848848
res := new(api.ActionTaskResponse)
849849
res.TotalCount = total
850850

851+
taskList := actions_model.TaskList(tasks)
852+
if err := taskList.LoadAttributes(ctx); err != nil {
853+
ctx.APIErrorInternal(err)
854+
return
855+
}
856+
851857
res.Entries = make([]*api.ActionTask, len(tasks))
852858
for i := range tasks {
853859
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
@@ -859,7 +865,7 @@ func ListActionTasks(ctx *context.APIContext) {
859865
}
860866

861867
ctx.SetLinkHeader(total, listOptions.PageSize)
862-
ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency
868+
ctx.SetTotalCountHeader(total) // Duplicates api response field, but it's better to set it for consistency
863869
ctx.JSON(http.StatusOK, &res)
864870
}
865871

@@ -1155,6 +1161,7 @@ func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionR
11551161
ctx.APIErrorInternal(err)
11561162
return nil
11571163
}
1164+
run.Repo = ctx.Repo.Repository
11581165
return run
11591166
}
11601167

@@ -1226,7 +1233,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
12261233
return
12271234
}
12281235

1229-
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, nil)
1236+
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
12301237
if err != nil {
12311238
ctx.APIErrorInternal(err)
12321239
return
@@ -1275,7 +1282,7 @@ func GetWorkflowRunAttempt(ctx *context.APIContext) {
12751282
return
12761283
}
12771284

1278-
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, attempt)
1285+
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt)
12791286
if err != nil {
12801287
ctx.APIErrorInternal(err)
12811288
return
@@ -1330,7 +1337,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
13301337
return
13311338
}
13321339

1333-
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, nil)
1340+
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
13341341
if err != nil {
13351342
ctx.APIErrorInternal(err)
13361343
return

routers/api/v1/shared/action.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"code.gitea.io/gitea/models/db"
1212
repo_model "code.gitea.io/gitea/models/repo"
1313
user_model "code.gitea.io/gitea/models/user"
14+
"code.gitea.io/gitea/modules/container"
1415
"code.gitea.io/gitea/modules/git"
1516
"code.gitea.io/gitea/modules/optional"
1617
"code.gitea.io/gitea/modules/setting"
@@ -62,6 +63,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI
6263
res := new(api.ActionWorkflowJobsResponse)
6364
res.TotalCount = total
6465

66+
jobList := actions_model.ActionJobList(jobs)
67+
if err := jobList.LoadAttributes(ctx, true); err != nil {
68+
ctx.APIErrorInternal(err)
69+
return
70+
}
71+
6572
res.Entries = make([]*api.ActionWorkflowJob, len(jobs))
6673

6774
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
7077
if isRepoLevel {
7178
repository = ctx.Repo.Repository
7279
} else {
73-
repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID)
74-
if err != nil {
75-
ctx.APIErrorInternal(err)
80+
if jobs[i].Run == nil || jobs[i].Run.Repo == nil {
81+
ctx.APIErrorInternal(fmt.Errorf("job %d is missing its run or repository", jobs[i].ID))
7682
return
7783
}
84+
repository = jobs[i].Run.Repo
7885
}
7986

8087
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i])
@@ -169,21 +176,28 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
169176
res := new(api.ActionWorkflowRunsResponse)
170177
res.TotalCount = total
171178

179+
runList := actions_model.RunList(runs)
180+
if err := runList.LoadTriggerUser(ctx); err != nil {
181+
ctx.APIErrorInternal(err)
182+
return
183+
}
184+
185+
if err := runList.LoadRepos(ctx); err != nil {
186+
ctx.APIErrorInternal(err)
187+
return
188+
}
189+
repos := repo_model.RepositoryList(container.FilterSlice(runs, func(r *actions_model.ActionRun) (*repo_model.Repository, bool) {
190+
return r.Repo, r.Repo != nil
191+
}))
192+
if err := repos.LoadOwners(ctx); err != nil {
193+
ctx.APIErrorInternal(err)
194+
return
195+
}
196+
172197
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
173-
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
174198
for i := range runs {
175-
var repository *repo_model.Repository
176-
if isRepoLevel {
177-
repository = ctx.Repo.Repository
178-
} else {
179-
repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID)
180-
if err != nil {
181-
ctx.APIErrorInternal(err)
182-
return
183-
}
184-
}
185-
186-
convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i], nil)
199+
// TODO: load run attempts in batch
200+
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil)
187201
if err != nil {
188202
ctx.APIErrorInternal(err)
189203
return

0 commit comments

Comments
 (0)