Skip to content

Commit 9cc80fe

Browse files
committed
add quick approve button
1 parent ebd88af commit 9cc80fe

File tree

7 files changed

+191
-28
lines changed

7 files changed

+191
-28
lines changed

models/git/commit_status.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,21 +211,26 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string {
211211

212212
// HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions
213213
func (status *CommitStatus) HideActionsURL(ctx context.Context) {
214+
if status.CreatedByGiteaActions(ctx) {
215+
status.TargetURL = ""
216+
}
217+
}
218+
219+
// CreatedByGiteaActions returns true if the commit status is created by Gitea Actions
220+
func (status *CommitStatus) CreatedByGiteaActions(ctx context.Context) bool {
214221
if status.RepoID == 0 {
215-
return
222+
return false
216223
}
217224

218225
if status.Repo == nil {
219226
if err := status.loadRepository(ctx); err != nil {
220227
log.Error("loadRepository: %v", err)
221-
return
228+
return false
222229
}
223230
}
224231

225232
prefix := status.Repo.Link() + "/actions"
226-
if strings.HasPrefix(status.TargetURL, prefix) {
227-
status.TargetURL = ""
228-
}
233+
return strings.HasPrefix(status.TargetURL, prefix)
229234
}
230235

231236
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc

routers/web/repo/actions/view.go

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -605,33 +605,53 @@ func Cancel(ctx *context_module.Context) {
605605
func Approve(ctx *context_module.Context) {
606606
runIndex := getRunIndex(ctx)
607607

608-
current, jobs := getRunJobs(ctx, runIndex, -1)
608+
approveRuns(ctx, []int64{runIndex})
609609
if ctx.Written() {
610610
return
611611
}
612-
run := current.Run
612+
613+
ctx.JSONOK()
614+
}
615+
616+
func approveRuns(ctx *context_module.Context, runIndexes []int64) {
613617
doer := ctx.Doer
618+
repo := ctx.Repo.Repository
614619

615-
var updatedJobs []*actions_model.ActionRunJob
620+
updatedJobs := make([]*actions_model.ActionRunJob, 0)
621+
runMap := make(map[int64]*actions_model.ActionRun, len(runIndexes))
622+
runJobs := make(map[int64][]*actions_model.ActionRunJob, len(runIndexes))
616623

617624
err := db.WithTx(ctx, func(ctx context.Context) (err error) {
618-
run.NeedApproval = false
619-
run.ApprovedBy = doer.ID
620-
if err := actions_model.UpdateRun(ctx, run, "need_approval", "approved_by"); err != nil {
621-
return err
622-
}
623-
for _, job := range jobs {
624-
job.Status, err = actions_service.PrepareToStartJobWithConcurrency(ctx, job)
625+
for _, runIndex := range runIndexes {
626+
run, err := actions_model.GetRunByIndex(ctx, repo.ID, runIndex)
627+
if err != nil {
628+
return err
629+
}
630+
runMap[run.ID] = run
631+
run.Repo = repo
632+
run.NeedApproval = false
633+
run.ApprovedBy = doer.ID
634+
if err := actions_model.UpdateRun(ctx, run, "need_approval", "approved_by"); err != nil {
635+
return err
636+
}
637+
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
625638
if err != nil {
626639
return err
627640
}
628-
if job.Status == actions_model.StatusWaiting {
629-
n, err := actions_model.UpdateRunJob(ctx, job, nil, "status")
641+
runJobs[run.ID] = jobs
642+
for _, job := range jobs {
643+
job.Status, err = actions_service.PrepareToStartJobWithConcurrency(ctx, job)
630644
if err != nil {
631645
return err
632646
}
633-
if n > 0 {
634-
updatedJobs = append(updatedJobs, job)
647+
if job.Status == actions_model.StatusWaiting {
648+
n, err := actions_model.UpdateRunJob(ctx, job, nil, "status")
649+
if err != nil {
650+
return err
651+
}
652+
if n > 0 {
653+
updatedJobs = append(updatedJobs, job)
654+
}
635655
}
636656
}
637657
}
@@ -642,7 +662,9 @@ func Approve(ctx *context_module.Context) {
642662
return
643663
}
644664

645-
actions_service.CreateCommitStatusForRunJobs(ctx, current.Run, jobs...)
665+
for runID, run := range runMap {
666+
actions_service.CreateCommitStatusForRunJobs(ctx, run, runJobs[runID]...)
667+
}
646668

647669
if len(updatedJobs) > 0 {
648670
job := updatedJobs[0]
@@ -653,8 +675,6 @@ func Approve(ctx *context_module.Context) {
653675
_ = job.LoadAttributes(ctx)
654676
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
655677
}
656-
657-
ctx.JSONOK()
658678
}
659679

660680
func Delete(ctx *context_module.Context) {
@@ -817,6 +837,43 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
817837
}
818838
}
819839

840+
func ApproveAllChecks(ctx *context_module.Context) {
841+
repo := ctx.Repo.Repository
842+
sha := ctx.FormString("sha")
843+
redirect := ctx.FormString("redirect")
844+
845+
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
846+
if err != nil {
847+
ctx.ServerError("GetLatestCommitStatus", err)
848+
return
849+
}
850+
runs, _, err := actions_service.GetRunsAndJobsFromCommitStatuses(ctx, commitStatuses)
851+
if err != nil {
852+
ctx.ServerError("GetRunsAndJobsFromCommitStatuses", err)
853+
return
854+
}
855+
856+
runIndexes := make([]int64, 0, len(runs))
857+
for _, run := range runs {
858+
if run.NeedApproval {
859+
runIndexes = append(runIndexes, run.Index)
860+
}
861+
}
862+
863+
if len(runIndexes) == 0 {
864+
ctx.Redirect(redirect)
865+
return
866+
}
867+
868+
approveRuns(ctx, runIndexes)
869+
if ctx.Written() {
870+
return
871+
}
872+
873+
ctx.Flash.Success("Successfully approved all pending checks")
874+
ctx.Redirect(redirect)
875+
}
876+
820877
func DisableWorkflowFile(ctx *context_module.Context) {
821878
disableOrEnableWorkflowFile(ctx, false)
822879
}

routers/web/repo/pull.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"code.gitea.io/gitea/modules/web"
3939
"code.gitea.io/gitea/routers/utils"
4040
shared_user "code.gitea.io/gitea/routers/web/shared/user"
41+
actions_service "code.gitea.io/gitea/services/actions"
4142
asymkey_service "code.gitea.io/gitea/services/asymkey"
4243
"code.gitea.io/gitea/services/automerge"
4344
"code.gitea.io/gitea/services/context"
@@ -455,6 +456,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_
455456
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err)
456457
return nil
457458
}
459+
ctx.Data["SHA"] = sha
458460

459461
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
460462
if err != nil {
@@ -465,6 +467,20 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_
465467
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
466468
}
467469

470+
runs, _, err := actions_service.GetRunsAndJobsFromCommitStatuses(ctx, commitStatuses)
471+
if err != nil {
472+
ctx.ServerError("GetRunsAndJobsFromCommitStatuses", err)
473+
return nil
474+
}
475+
for _, run := range runs {
476+
if run.NeedApproval {
477+
ctx.Data["RequireApproval"] = true
478+
}
479+
}
480+
if ctx.Data["RequireApproval"] == true {
481+
ctx.Data["CanApprove"] = ctx.Repo.CanWrite(unit.TypeActions)
482+
}
483+
468484
if len(commitStatuses) > 0 {
469485
ctx.Data["LatestCommitStatuses"] = commitStatuses
470486
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)

routers/web/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,7 @@ func registerWebRoutes(m *web.Router) {
14591459
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
14601460
m.Post("/run", reqRepoActionsWriter, actions.Run)
14611461
m.Get("/workflow-dispatch-inputs", reqRepoActionsWriter, actions.WorkflowDispatchInputs)
1462+
m.Post("/approve-all-checks", reqRepoActionsWriter, actions.ApproveAllChecks)
14621463

14631464
m.Group("/runs/{run}", func() {
14641465
m.Combo("").

services/actions/commit_status.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"errors"
99
"fmt"
1010
"path"
11+
"regexp"
1112
"strconv"
13+
"strings"
1214

1315
actions_model "code.gitea.io/gitea/models/actions"
1416
"code.gitea.io/gitea/models/db"
@@ -52,6 +54,74 @@ func CreateCommitStatusForRunJobs(ctx context.Context, run *actions_model.Action
5254
}
5355
}
5456

57+
func GetRunsAndJobsFromCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) ([]*actions_model.ActionRun, []*actions_model.ActionRunJob, error) {
58+
jobMap := make(map[int64]*actions_model.ActionRunJob)
59+
runMap := make(map[int64]*actions_model.ActionRun)
60+
jobsMap := make(map[int64][]*actions_model.ActionRunJob)
61+
for _, status := range statuses {
62+
if !status.CreatedByGiteaActions(ctx) {
63+
continue
64+
}
65+
runIndex, jobIndex, err := getActionRunAndJobIndexFromCommitStatus(status)
66+
if err != nil {
67+
return nil, nil, fmt.Errorf("getActionRunAndJobIndexFromCommitStatus: %w", err)
68+
}
69+
run, ok := runMap[runIndex]
70+
if !ok {
71+
run, err = actions_model.GetRunByIndex(ctx, status.RepoID, runIndex)
72+
if err != nil {
73+
return nil, nil, fmt.Errorf("GetRunByIndex: %w", err)
74+
}
75+
runMap[runIndex] = run
76+
}
77+
jobs, ok := jobsMap[runIndex]
78+
if !ok {
79+
jobs, err = actions_model.GetRunJobsByRunID(ctx, run.ID)
80+
if err != nil {
81+
return nil, nil, fmt.Errorf("GetRunJobByIndex: %w", err)
82+
}
83+
jobsMap[runIndex] = jobs
84+
}
85+
if jobIndex < 0 || jobIndex >= int64(len(jobs)) {
86+
return nil, nil, fmt.Errorf("job index %d out of range for run %d", jobIndex, runIndex)
87+
}
88+
job := jobs[jobIndex]
89+
jobMap[job.ID] = job
90+
}
91+
runs := make([]*actions_model.ActionRun, 0, len(runMap))
92+
for _, run := range runMap {
93+
runs = append(runs, run)
94+
}
95+
jobs := make([]*actions_model.ActionRunJob, 0, len(jobMap))
96+
for _, job := range jobMap {
97+
jobs = append(jobs, job)
98+
}
99+
return runs, jobs, nil
100+
}
101+
102+
func getActionRunAndJobIndexFromCommitStatus(status *git_model.CommitStatus) (int64, int64, error) {
103+
actionsLink, _ := strings.CutPrefix(status.TargetURL, status.Repo.Link()+"/actions/")
104+
// actionsLink should be like "runs/<run_index>/jobs/<job_index>"
105+
106+
re := regexp.MustCompile(`runs/(\d+)/jobs/(\d+)`)
107+
matches := re.FindStringSubmatch(actionsLink)
108+
109+
if len(matches) != 3 {
110+
return 0, 0, fmt.Errorf("%s is not a Gitea Actions link", status.TargetURL)
111+
}
112+
113+
runIndex, err := strconv.ParseInt(matches[1], 10, 64)
114+
if err != nil {
115+
return 0, 0, fmt.Errorf("parse run index: %w", err)
116+
}
117+
jobIndex, err := strconv.ParseInt(matches[2], 10, 64)
118+
if err != nil {
119+
return 0, 0, fmt.Errorf("parse job index: %w", err)
120+
}
121+
122+
return runIndex, jobIndex, nil
123+
}
124+
55125
func getCommitStatusEventNameAndCommitID(run *actions_model.ActionRun) (event, commitID string, _ error) {
56126
switch run.Event {
57127
case webhook_module.HookEventPush:

templates/repo/issue/view_content/pull_merge_box.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
"MissingRequiredChecks" .MissingRequiredChecks
3535
"ShowHideChecks" true
3636
"is_context_required" .is_context_required
37+
"SHA" .SHA
38+
"RequireApproval" .RequireApproval
39+
"CanApprove" .CanApprove
40+
"RepoLink" .RepoLink
41+
"IssueLink" .Issue.Link
3742
)}}
3843
</div>
3944
{{end}}

templates/repo/pulls/status.tmpl

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,23 @@
2323
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
2424
{{end}}
2525

26-
{{if .ShowHideChecks}}
2726
<div class="ui right">
28-
<button class="commit-status-hide-checks btn interact-fg"
29-
data-show-all="{{ctx.Locale.Tr "repo.pulls.status_checks_show_all"}}"
30-
data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}">
31-
{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}</button>
27+
{{if and .RequireApproval .CanApprove}}
28+
<form method="post" action="{{.RepoLink}}/actions/approve-all-checks?redirect={{.IssueLink}}">
29+
{{ctx.RootData.CsrfTokenHtml}}
30+
<input type="hidden" name="sha" value="{{.SHA}}">
31+
<button type="submit" class="ui small tiny compact button primary">
32+
Approve workflows to run
33+
</button>
34+
</form>
35+
{{end}}
36+
{{if .ShowHideChecks}}
37+
<button class="commit-status-hide-checks btn interact-fg"
38+
data-show-all="{{ctx.Locale.Tr "repo.pulls.status_checks_show_all"}}"
39+
data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}">
40+
{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}</button>
41+
{{end}}
3242
</div>
33-
{{end}}
3443
</div>
3544

3645
<div class="commit-status-list">

0 commit comments

Comments
 (0)