loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add search action jobs for API routes, repo, org and global level (#6300)

This PR wants to improve information of the tasks waiting to be executed on a global, organization, user and repository leve.
The main motivation is explained here https://codeberg.org/forgejo/discussions/issues/241

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
- [x] in their respective `*_test.go` for unit tests.
- [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
- [ ] in `web_src/js/*.test.js` if it can be unit tested.
- [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Features
- [PR](https://codeberg.org/forgejo/forgejo/pulls/6300): <!--number 6300 --><!--line 0 --><!--description QWRkIHNlYXJjaCBhY3Rpb24gam9icyBmb3IgQVBJIHJvdXRlcywgcmVwbywgb3JnIGFuZCBnbG9iYWwgbGV2ZWw=-->Add search action jobs for API routes, repo, org and global level<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: jaime merino <jaime.merino_mora@mail.schwarzª>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6300
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Jaime merino <cobak78@gmail.com>
Co-committed-by: Jaime merino <cobak78@gmail.com>

authored by

Jaime merino
jaime merino
Jaime merino
and committed by
Earl Warren
9f842f0d a013acb6

+640 -22
+10
models/actions/run_job.go
··· 10 10 "time" 11 11 12 12 "code.gitea.io/gitea/models/db" 13 + "code.gitea.io/gitea/modules/container" 13 14 "code.gitea.io/gitea/modules/timeutil" 14 15 "code.gitea.io/gitea/modules/util" 15 16 ··· 69 70 } 70 71 71 72 return job.Run.LoadAttributes(ctx) 73 + } 74 + 75 + func (job *ActionRunJob) ItRunsOn(labels []string) bool { 76 + if len(labels) == 0 || len(job.RunsOn) == 0 { 77 + return false 78 + } 79 + labelSet := make(container.Set[string]) 80 + labelSet.AddMultiple(labels...) 81 + return labelSet.IsSubset(job.RunsOn) 72 82 } 73 83 74 84 func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
+29
models/actions/run_job_test.go
··· 1 + // SPDX-License-Identifier: MIT 2 + 3 + package actions 4 + 5 + import ( 6 + "testing" 7 + 8 + "github.com/stretchr/testify/assert" 9 + ) 10 + 11 + func TestActionRunJob_ItRunsOn(t *testing.T) { 12 + actionJob := ActionRunJob{RunsOn: []string{"ubuntu"}} 13 + agentLabels := []string{"ubuntu", "node-20"} 14 + 15 + assert.True(t, actionJob.ItRunsOn(agentLabels)) 16 + assert.False(t, actionJob.ItRunsOn([]string{})) 17 + 18 + actionJob.RunsOn = append(actionJob.RunsOn, "node-20") 19 + 20 + assert.True(t, actionJob.ItRunsOn(agentLabels)) 21 + 22 + agentLabels = []string{"ubuntu"} 23 + 24 + assert.False(t, actionJob.ItRunsOn(agentLabels)) 25 + 26 + actionJob.RunsOn = []string{} 27 + 28 + assert.False(t, actionJob.ItRunsOn(agentLabels)) 29 + }
+1 -16
models/actions/task.go
··· 12 12 auth_model "code.gitea.io/gitea/models/auth" 13 13 "code.gitea.io/gitea/models/db" 14 14 "code.gitea.io/gitea/models/unit" 15 - "code.gitea.io/gitea/modules/container" 16 15 "code.gitea.io/gitea/modules/log" 17 16 "code.gitea.io/gitea/modules/setting" 18 17 "code.gitea.io/gitea/modules/timeutil" ··· 245 244 var job *ActionRunJob 246 245 log.Trace("runner labels: %v", runner.AgentLabels) 247 246 for _, v := range jobs { 248 - if isSubset(runner.AgentLabels, v.RunsOn) { 247 + if v.ItRunsOn(runner.AgentLabels) { 249 248 job = v 250 249 break 251 250 } ··· 480 479 return tasks, e.Where("stopped > 0 AND stopped < ? AND log_expired = ?", olderThan, false). 481 480 Limit(limit). 482 481 Find(&tasks) 483 - } 484 - 485 - func isSubset(set, subset []string) bool { 486 - m := make(container.Set[string], len(set)) 487 - for _, v := range set { 488 - m.Add(v) 489 - } 490 - 491 - for _, v := range subset { 492 - if !m.Contains(v) { 493 - return false 494 - } 495 - } 496 - return true 497 482 } 498 483 499 484 func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
+3 -3
models/actions/task_list.go
··· 50 50 RepoID int64 51 51 OwnerID int64 52 52 CommitSHA string 53 - Status Status 53 + Status []Status 54 54 UpdatedBefore timeutil.TimeStamp 55 55 StartedBefore timeutil.TimeStamp 56 56 RunnerID int64 ··· 67 67 if opts.CommitSHA != "" { 68 68 cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA}) 69 69 } 70 - if opts.Status > StatusUnknown { 71 - cond = cond.And(builder.Eq{"status": opts.Status}) 70 + if opts.Status != nil { 71 + cond = cond.And(builder.In("status", opts.Status)) 72 72 } 73 73 if opts.UpdatedBefore > 0 { 74 74 cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
+45
models/fixtures/action_run_job.yml
··· 83 83 status: 1 84 84 started: 1683636528 85 85 stopped: 1683636626 86 + - 87 + id: 393 88 + run_id: 891 89 + repo_id: 1 90 + owner_id: 1 91 + commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee 92 + is_fork_pull_request: 0 93 + name: job_2 94 + attempt: 1 95 + job_id: job_2 96 + task_id: 47 97 + status: 5 98 + runs_on: '["ubuntu-latest"]' 99 + started: 1683636528 100 + stopped: 1683636626 101 + - 102 + id: 394 103 + run_id: 891 104 + repo_id: 1 105 + owner_id: 2 106 + commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee 107 + is_fork_pull_request: 0 108 + name: job_2 109 + attempt: 1 110 + job_id: job_2 111 + task_id: 47 112 + status: 5 113 + runs_on: '["debian-latest"]' 114 + started: 1683636528 115 + stopped: 1683636626 116 + - 117 + id: 395 118 + run_id: 891 119 + repo_id: 1 120 + owner_id: 3 121 + commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee 122 + is_fork_pull_request: 0 123 + name: job_2 124 + attempt: 1 125 + job_id: job_2 126 + task_id: 47 127 + status: 5 128 + runs_on: '["fedora"]' 129 + started: 1683636528 130 + stopped: 1683636626
+9
modules/container/set.go
··· 29 29 } 30 30 } 31 31 32 + func (s Set[T]) IsSubset(subset []T) bool { 33 + for _, v := range subset { 34 + if !s.Contains(v) { 35 + return false 36 + } 37 + } 38 + return true 39 + } 40 + 32 41 // Contains determines whether a set contains the specified element. 33 42 // Returns true if the set contains the specified element; otherwise, false. 34 43 func (s Set[T]) Contains(value T) bool {
+5
modules/container/set_test.go
··· 33 33 assert.False(t, s.Contains("key1")) 34 34 assert.True(t, s.Contains("key6")) 35 35 assert.True(t, s.Contains("key7")) 36 + 37 + assert.True(t, s.IsSubset([]string{"key6", "key7"})) 38 + assert.False(t, s.IsSubset([]string{"key1"})) 39 + 40 + assert.True(t, s.IsSubset([]string{})) 36 41 }
+25
modules/structs/action.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package structs 5 + 6 + // ActionRunJob represents a job of a run 7 + // swagger:model 8 + type ActionRunJob struct { 9 + // the action run job id 10 + ID int64 `json:"id"` 11 + // the repository id 12 + RepoID int64 `json:"repo_id"` 13 + // the owner id 14 + OwnerID int64 `json:"owner_id"` 15 + // the action run job name 16 + Name string `json:"name"` 17 + // the action run job needed ids 18 + Needs []string `json:"needs"` 19 + // the action run job labels to run on 20 + RunsOn []string `json:"runs_on"` 21 + // the action run job latest task id 22 + TaskID int64 `json:"task_id"` 23 + // the action run job status 24 + Status string `json:"status"` 25 + }
+20
routers/api/v1/admin/runners.go
··· 24 24 25 25 shared.GetRegistrationToken(ctx, 0, 0) 26 26 } 27 + 28 + // SearchActionRunJobs return a list of actions jobs filtered by the provided parameters 29 + func SearchActionRunJobs(ctx *context.APIContext) { 30 + // swagger:operation GET /admin/runners/jobs admin adminSearchRunJobs 31 + // --- 32 + // summary: Search action jobs according filter conditions 33 + // produces: 34 + // - application/json 35 + // parameters: 36 + // - name: labels 37 + // in: query 38 + // description: a comma separated list of run job labels to search for 39 + // type: string 40 + // responses: 41 + // "200": 42 + // "$ref": "#/responses/RunJobList" 43 + // "403": 44 + // "$ref": "#/responses/forbidden" 45 + shared.GetActionRunJobs(ctx, 0, 0) 46 + }
+3
routers/api/v1/api.go
··· 822 822 823 823 m.Group("/runners", func() { 824 824 m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken) 825 + m.Get("/jobs", reqToken(), reqChecker, act.SearchActionRunJobs) 825 826 }) 826 827 }) 827 828 } ··· 975 976 976 977 m.Group("/runners", func() { 977 978 m.Get("/registration-token", reqToken(), user.GetRegistrationToken) 979 + m.Get("/jobs", reqToken(), user.SearchActionRunJobs) 978 980 }) 979 981 }) 980 982 ··· 1631 1633 }) 1632 1634 m.Group("/runners", func() { 1633 1635 m.Get("/registration-token", admin.GetRegistrationToken) 1636 + m.Get("/jobs", admin.SearchActionRunJobs) 1634 1637 }) 1635 1638 if setting.Quota.Enabled { 1636 1639 m.Group("/quota", func() {
+25
routers/api/v1/org/action.go
··· 189 189 shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0) 190 190 } 191 191 192 + // SearchActionRunJobs return a list of actions jobs filtered by the provided parameters 193 + func (Action) SearchActionRunJobs(ctx *context.APIContext) { 194 + // swagger:operation GET /orgs/{org}/actions/runners/jobs organization orgSearchRunJobs 195 + // --- 196 + // summary: Search for organization's action jobs according filter conditions 197 + // produces: 198 + // - application/json 199 + // parameters: 200 + // - name: org 201 + // in: path 202 + // description: name of the organization 203 + // type: string 204 + // required: true 205 + // - name: labels 206 + // in: query 207 + // description: a comma separated list of run job labels to search for 208 + // type: string 209 + // responses: 210 + // "200": 211 + // "$ref": "#/responses/RunJobList" 212 + // "403": 213 + // "$ref": "#/responses/forbidden" 214 + shared.GetActionRunJobs(ctx, ctx.Org.Organization.ID, 0) 215 + } 216 + 192 217 // ListVariables list org-level variables 193 218 func (Action) ListVariables(ctx *context.APIContext) { 194 219 // swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
+30
routers/api/v1/repo/action.go
··· 507 507 shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID) 508 508 } 509 509 510 + // SearchActionRunJobs return a list of actions jobs filtered by the provided parameters 511 + func (Action) SearchActionRunJobs(ctx *context.APIContext) { 512 + // swagger:operation GET /repos/{owner}/{repo}/actions/runners/jobs repository repoSearchRunJobs 513 + // --- 514 + // summary: Search for repository's action jobs according filter conditions 515 + // produces: 516 + // - application/json 517 + // parameters: 518 + // - name: owner 519 + // in: path 520 + // description: owner of the repo 521 + // type: string 522 + // required: true 523 + // - name: repo 524 + // in: path 525 + // description: name of the repo 526 + // type: string 527 + // required: true 528 + // - name: labels 529 + // in: query 530 + // description: a comma separated list of run job labels to search for 531 + // type: string 532 + // responses: 533 + // "200": 534 + // "$ref": "#/responses/RunJobList" 535 + // "403": 536 + // "$ref": "#/responses/forbidden" 537 + shared.GetActionRunJobs(ctx, 0, ctx.Repo.Repository.ID) 538 + } 539 + 510 540 var _ actions_service.API = new(Action) 511 541 512 542 // Action implements actions_service.API
+48
routers/api/v1/shared/runners.go
··· 6 6 import ( 7 7 "errors" 8 8 "net/http" 9 + "strings" 9 10 10 11 actions_model "code.gitea.io/gitea/models/actions" 12 + "code.gitea.io/gitea/models/db" 13 + "code.gitea.io/gitea/modules/structs" 11 14 "code.gitea.io/gitea/modules/util" 12 15 "code.gitea.io/gitea/services/context" 13 16 ) ··· 30 33 31 34 ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token}) 32 35 } 36 + 37 + // RunJobList is a list of action run jobs 38 + // swagger:response RunJobList 39 + type RunJobList struct { 40 + // in:body 41 + Body []*structs.ActionRunJob `json:"body"` 42 + } 43 + 44 + func GetActionRunJobs(ctx *context.APIContext, ownerID, repoID int64) { 45 + labels := strings.Split(ctx.FormTrim("labels"), ",") 46 + 47 + total, err := db.Find[actions_model.ActionRunJob](ctx, &actions_model.FindTaskOptions{ 48 + Status: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusRunning}, 49 + OwnerID: ownerID, 50 + RepoID: repoID, 51 + }) 52 + if err != nil { 53 + ctx.Error(http.StatusInternalServerError, "CountWaitingActionRunJobs", err) 54 + return 55 + } 56 + 57 + res := new(RunJobList) 58 + res.Body = fromRunJobModelToResponse(total, labels) 59 + 60 + ctx.JSON(http.StatusOK, res) 61 + } 62 + 63 + func fromRunJobModelToResponse(job []*actions_model.ActionRunJob, labels []string) []*structs.ActionRunJob { 64 + var res []*structs.ActionRunJob 65 + for i := range job { 66 + if job[i].ItRunsOn(labels) { 67 + res = append(res, &structs.ActionRunJob{ 68 + ID: job[i].ID, 69 + RepoID: job[i].RepoID, 70 + OwnerID: job[i].OwnerID, 71 + Name: job[i].Name, 72 + Needs: job[i].Needs, 73 + RunsOn: job[i].RunsOn, 74 + TaskID: job[i].TaskID, 75 + Status: job[i].Status.String(), 76 + }) 77 + } 78 + } 79 + return res 80 + }
+22
routers/api/v1/user/runners.go
··· 28 28 29 29 shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0) 30 30 } 31 + 32 + // SearchActionRunJobs return a list of actions jobs filtered by the provided parameters 33 + func SearchActionRunJobs(ctx *context.APIContext) { 34 + // swagger:operation GET /user/actions/runners/jobs user userSearchRunJobs 35 + // --- 36 + // summary: Search for user's action jobs according filter conditions 37 + // produces: 38 + // - application/json 39 + // parameters: 40 + // - name: labels 41 + // in: query 42 + // description: a comma separated list of run job labels to search for 43 + // type: string 44 + // responses: 45 + // "200": 46 + // "$ref": "#/responses/RunJobList" 47 + // "401": 48 + // "$ref": "#/responses/unauthorized" 49 + // "403": 50 + // "$ref": "#/responses/forbidden" 51 + shared.GetActionRunJobs(ctx, ctx.Doer.ID, 0) 52 + }
+1 -1
routers/web/shared/actions/runners.go
··· 79 79 Page: page, 80 80 PageSize: 30, 81 81 }, 82 - Status: actions_model.StatusUnknown, // Unknown means all 82 + Status: []actions_model.Status{actions_model.StatusUnknown}, // Unknown means all 83 83 RunnerID: runner.ID, 84 84 } 85 85
+2 -2
services/actions/clear_tasks.go
··· 19 19 // StopZombieTasks stops the task which have running status, but haven't been updated for a long time 20 20 func StopZombieTasks(ctx context.Context) error { 21 21 return stopTasks(ctx, actions_model.FindTaskOptions{ 22 - Status: actions_model.StatusRunning, 22 + Status: []actions_model.Status{actions_model.StatusRunning}, 23 23 UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.ZombieTaskTimeout).Unix()), 24 24 }) 25 25 } ··· 27 27 // StopEndlessTasks stops the tasks which have running status and continuous updates, but don't end for a long time 28 28 func StopEndlessTasks(ctx context.Context) error { 29 29 return stopTasks(ctx, actions_model.FindTaskOptions{ 30 - Status: actions_model.StatusRunning, 30 + Status: []actions_model.Status{actions_model.StatusRunning}, 31 31 StartedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.EndlessTaskTimeout).Unix()), 32 32 }) 33 33 }
+2
services/actions/interface.go
··· 25 25 UpdateVariable(*context.APIContext) 26 26 // GetRegistrationToken get registration token 27 27 GetRegistrationToken(*context.APIContext) 28 + // SearchActionRunJobs get pending Action run jobs 29 + SearchActionRunJobs(*context.APIContext) 28 30 }
+202
templates/swagger/v1_json.tmpl
··· 992 992 } 993 993 } 994 994 }, 995 + "/admin/runners/jobs": { 996 + "get": { 997 + "produces": [ 998 + "application/json" 999 + ], 1000 + "tags": [ 1001 + "admin" 1002 + ], 1003 + "summary": "Search action jobs according filter conditions", 1004 + "operationId": "adminSearchRunJobs", 1005 + "parameters": [ 1006 + { 1007 + "type": "string", 1008 + "description": "a comma separated list of run job labels to search for", 1009 + "name": "labels", 1010 + "in": "query" 1011 + } 1012 + ], 1013 + "responses": { 1014 + "200": { 1015 + "$ref": "#/responses/RunJobList" 1016 + }, 1017 + "403": { 1018 + "$ref": "#/responses/forbidden" 1019 + } 1020 + } 1021 + } 1022 + }, 995 1023 "/admin/runners/registration-token": { 996 1024 "get": { 997 1025 "produces": [ ··· 2280 2308 }, 2281 2309 "422": { 2282 2310 "$ref": "#/responses/error" 2311 + } 2312 + } 2313 + } 2314 + }, 2315 + "/orgs/{org}/actions/runners/jobs": { 2316 + "get": { 2317 + "produces": [ 2318 + "application/json" 2319 + ], 2320 + "tags": [ 2321 + "organization" 2322 + ], 2323 + "summary": "Search for organization's action jobs according filter conditions", 2324 + "operationId": "orgSearchRunJobs", 2325 + "parameters": [ 2326 + { 2327 + "type": "string", 2328 + "description": "name of the organization", 2329 + "name": "org", 2330 + "in": "path", 2331 + "required": true 2332 + }, 2333 + { 2334 + "type": "string", 2335 + "description": "a comma separated list of run job labels to search for", 2336 + "name": "labels", 2337 + "in": "query" 2338 + } 2339 + ], 2340 + "responses": { 2341 + "200": { 2342 + "$ref": "#/responses/RunJobList" 2343 + }, 2344 + "403": { 2345 + "$ref": "#/responses/forbidden" 2283 2346 } 2284 2347 } 2285 2348 } ··· 4639 4702 } 4640 4703 } 4641 4704 }, 4705 + "/repos/{owner}/{repo}/actions/runners/jobs": { 4706 + "get": { 4707 + "produces": [ 4708 + "application/json" 4709 + ], 4710 + "tags": [ 4711 + "repository" 4712 + ], 4713 + "summary": "Search for repository's action jobs according filter conditions", 4714 + "operationId": "repoSearchRunJobs", 4715 + "parameters": [ 4716 + { 4717 + "type": "string", 4718 + "description": "owner of the repo", 4719 + "name": "owner", 4720 + "in": "path", 4721 + "required": true 4722 + }, 4723 + { 4724 + "type": "string", 4725 + "description": "name of the repo", 4726 + "name": "repo", 4727 + "in": "path", 4728 + "required": true 4729 + }, 4730 + { 4731 + "type": "string", 4732 + "description": "a comma separated list of run job labels to search for", 4733 + "name": "labels", 4734 + "in": "query" 4735 + } 4736 + ], 4737 + "responses": { 4738 + "200": { 4739 + "$ref": "#/responses/RunJobList" 4740 + }, 4741 + "403": { 4742 + "$ref": "#/responses/forbidden" 4743 + } 4744 + } 4745 + } 4746 + }, 4642 4747 "/repos/{owner}/{repo}/actions/runners/registration-token": { 4643 4748 "get": { 4644 4749 "produces": [ ··· 17399 17504 } 17400 17505 } 17401 17506 }, 17507 + "/user/actions/runners/jobs": { 17508 + "get": { 17509 + "produces": [ 17510 + "application/json" 17511 + ], 17512 + "tags": [ 17513 + "user" 17514 + ], 17515 + "summary": "Search for user's action jobs according filter conditions", 17516 + "operationId": "userSearchRunJobs", 17517 + "parameters": [ 17518 + { 17519 + "type": "string", 17520 + "description": "a comma separated list of run job labels to search for", 17521 + "name": "labels", 17522 + "in": "query" 17523 + } 17524 + ], 17525 + "responses": { 17526 + "200": { 17527 + "$ref": "#/responses/RunJobList" 17528 + }, 17529 + "401": { 17530 + "$ref": "#/responses/unauthorized" 17531 + }, 17532 + "403": { 17533 + "$ref": "#/responses/forbidden" 17534 + } 17535 + } 17536 + } 17537 + }, 17402 17538 "/user/actions/runners/registration-token": { 17403 17539 "get": { 17404 17540 "produces": [ ··· 20383 20519 "token_last_eight": { 20384 20520 "type": "string", 20385 20521 "x-go-name": "TokenLastEight" 20522 + } 20523 + }, 20524 + "x-go-package": "code.gitea.io/gitea/modules/structs" 20525 + }, 20526 + "ActionRunJob": { 20527 + "description": "ActionRunJob represents a job of a run", 20528 + "type": "object", 20529 + "properties": { 20530 + "id": { 20531 + "description": "the action run job id", 20532 + "type": "integer", 20533 + "format": "int64", 20534 + "x-go-name": "ID" 20535 + }, 20536 + "name": { 20537 + "description": "the action run job name", 20538 + "type": "string", 20539 + "x-go-name": "Name" 20540 + }, 20541 + "needs": { 20542 + "description": "the action run job needed ids", 20543 + "type": "array", 20544 + "items": { 20545 + "type": "string" 20546 + }, 20547 + "x-go-name": "Needs" 20548 + }, 20549 + "owner_id": { 20550 + "description": "the owner id", 20551 + "type": "integer", 20552 + "format": "int64", 20553 + "x-go-name": "OwnerID" 20554 + }, 20555 + "repo_id": { 20556 + "description": "the repository id", 20557 + "type": "integer", 20558 + "format": "int64", 20559 + "x-go-name": "RepoID" 20560 + }, 20561 + "runs_on": { 20562 + "description": "the action run job labels to run on", 20563 + "type": "array", 20564 + "items": { 20565 + "type": "string" 20566 + }, 20567 + "x-go-name": "RunsOn" 20568 + }, 20569 + "status": { 20570 + "description": "the action run job status", 20571 + "type": "string", 20572 + "x-go-name": "Status" 20573 + }, 20574 + "task_id": { 20575 + "description": "the action run job latest task id", 20576 + "type": "integer", 20577 + "format": "int64", 20578 + "x-go-name": "TaskID" 20386 20579 } 20387 20580 }, 20388 20581 "x-go-package": "code.gitea.io/gitea/modules/structs" ··· 28675 28868 "type": "array", 28676 28869 "items": { 28677 28870 "$ref": "#/definitions/Repository" 28871 + } 28872 + } 28873 + }, 28874 + "RunJobList": { 28875 + "description": "RunJobList is a list of action run jobs", 28876 + "schema": { 28877 + "type": "array", 28878 + "items": { 28879 + "$ref": "#/definitions/ActionRunJob" 28678 28880 } 28679 28881 } 28680 28882 },
+39
tests/integration/api_admin_actions_test.go
··· 1 + // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "testing" 10 + 11 + actions_model "code.gitea.io/gitea/models/actions" 12 + auth_model "code.gitea.io/gitea/models/auth" 13 + "code.gitea.io/gitea/models/unittest" 14 + "code.gitea.io/gitea/routers/api/v1/shared" 15 + "code.gitea.io/gitea/tests" 16 + 17 + "github.com/stretchr/testify/assert" 18 + ) 19 + 20 + func TestAPISearchActionJobs_GlobalRunner(t *testing.T) { 21 + defer tests.PrepareTestEnv(t)() 22 + 23 + job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393}) 24 + adminUsername := "user1" 25 + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) 26 + 27 + req := NewRequest( 28 + t, 29 + "GET", 30 + fmt.Sprintf("/api/v1/admin/runners/jobs?labels=%s", "ubuntu-latest"), 31 + ).AddTokenAuth(token) 32 + res := MakeRequest(t, req, http.StatusOK) 33 + 34 + var jobs shared.RunJobList 35 + DecodeJSON(t, res, &jobs) 36 + 37 + assert.Len(t, jobs.Body, 1) 38 + assert.EqualValues(t, job.ID, jobs.Body[0].ID) 39 + }
+38
tests/integration/api_org_actions_test.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "testing" 10 + 11 + actions_model "code.gitea.io/gitea/models/actions" 12 + auth_model "code.gitea.io/gitea/models/auth" 13 + "code.gitea.io/gitea/models/unittest" 14 + "code.gitea.io/gitea/routers/api/v1/shared" 15 + "code.gitea.io/gitea/tests" 16 + 17 + "github.com/stretchr/testify/assert" 18 + ) 19 + 20 + func TestAPISearchActionJobs_OrgRunner(t *testing.T) { 21 + defer tests.PrepareTestEnv(t)() 22 + 23 + session := loginUser(t, "user1") 24 + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) 25 + 26 + job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 395}) 27 + 28 + req := NewRequest(t, "GET", 29 + fmt.Sprintf("/api/v1/orgs/org3/actions/runners/jobs?labels=%s", "fedora")). 30 + AddTokenAuth(token) 31 + res := MakeRequest(t, req, http.StatusOK) 32 + 33 + var jobs shared.RunJobList 34 + DecodeJSON(t, res, &jobs) 35 + 36 + assert.Len(t, jobs.Body, 1) 37 + assert.EqualValues(t, job.ID, jobs.Body[0].ID) 38 + }
+43
tests/integration/api_repo_actions_test.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "net/http" 8 + "testing" 9 + 10 + actions_model "code.gitea.io/gitea/models/actions" 11 + auth_model "code.gitea.io/gitea/models/auth" 12 + repo_model "code.gitea.io/gitea/models/repo" 13 + "code.gitea.io/gitea/models/unittest" 14 + user_model "code.gitea.io/gitea/models/user" 15 + "code.gitea.io/gitea/routers/api/v1/shared" 16 + "code.gitea.io/gitea/tests" 17 + 18 + "github.com/stretchr/testify/assert" 19 + ) 20 + 21 + func TestAPISearchActionJobs_RepoRunner(t *testing.T) { 22 + defer tests.PrepareTestEnv(t)() 23 + 24 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 25 + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 26 + token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository) 27 + job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393}) 28 + 29 + req := NewRequestf( 30 + t, 31 + "GET", 32 + "/api/v1/repos/%s/%s/actions/runners/jobs?labels=%s", 33 + repo.OwnerName, repo.Name, 34 + "ubuntu-latest", 35 + ).AddTokenAuth(token) 36 + res := MakeRequest(t, req, http.StatusOK) 37 + 38 + var jobs shared.RunJobList 39 + DecodeJSON(t, res, &jobs) 40 + 41 + assert.Len(t, jobs.Body, 1) 42 + assert.EqualValues(t, job.ID, jobs.Body[0].ID) 43 + }
+38
tests/integration/api_user_actions_test.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "testing" 10 + 11 + actions_model "code.gitea.io/gitea/models/actions" 12 + auth_model "code.gitea.io/gitea/models/auth" 13 + "code.gitea.io/gitea/models/unittest" 14 + "code.gitea.io/gitea/routers/api/v1/shared" 15 + "code.gitea.io/gitea/tests" 16 + 17 + "github.com/stretchr/testify/assert" 18 + ) 19 + 20 + func TestAPISearchActionJobs_UserRunner(t *testing.T) { 21 + defer tests.PrepareTestEnv(t)() 22 + 23 + normalUsername := "user2" 24 + session := loginUser(t, normalUsername) 25 + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) 26 + job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 394}) 27 + 28 + req := NewRequest(t, "GET", 29 + fmt.Sprintf("/api/v1/user/actions/runners/jobs?labels=%s", "debian-latest")). 30 + AddTokenAuth(token) 31 + res := MakeRequest(t, req, http.StatusOK) 32 + 33 + var jobs shared.RunJobList 34 + DecodeJSON(t, res, &jobs) 35 + 36 + assert.Len(t, jobs.Body, 1) 37 + assert.EqualValues(t, job.ID, jobs.Body[0].ID) 38 + }