aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopherHX <christopher.homberger@web.de>2025-01-05 14:47:18 +0100
committerGitHub <noreply@github.com>2025-01-05 21:47:18 +0800
commit42377360296b7c810b284472ba6743bf684186fb (patch)
treeb1038b9074d19f95f59b6bc9384af8a60846f176
parent3078826d0111e818dc765d94c650f4cb5b3ecce7 (diff)
downloadgitea-42377360296b7c810b284472ba6743bf684186fb.tar.gz
gitea-42377360296b7c810b284472ba6743bf684186fb.zip
workflow_dispatch use workflow from trigger branch (#33098)
* htmx updates the input form on branch switch * add workflow warning to dispatch modal * use name if description of input is empty * show error if workflow_dispatch not available on branch Closes #33073 Closes #33099 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
-rw-r--r--options/locale/locale_en-US.ini1
-rw-r--r--routers/web/repo/actions/actions.go216
-rw-r--r--routers/web/repo/actions/view.go9
-rw-r--r--routers/web/web.go3
-rw-r--r--templates/repo/actions/workflow_dispatch.tmpl27
-rw-r--r--templates/repo/actions/workflow_dispatch_inputs.tmpl45
6 files changed, 184 insertions, 117 deletions
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 07c9ffa9fc..96404a6143 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3765,6 +3765,7 @@ workflow.not_found = Workflow '%s' not found.
workflow.run_success = Workflow '%s' run successfully.
workflow.from_ref = Use workflow from
workflow.has_workflow_dispatch = This workflow has a workflow_dispatch event trigger.
+workflow.has_no_workflow_dispatch = Workflow '%s' has no workflow_dispatch event trigger.
need_approval_desc = Need approval to run workflows for fork pull request.
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index f0d8d81fee..539c4b6ed0 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -32,8 +32,9 @@ import (
)
const (
- tplListActions templates.TplName = "repo/actions/list"
- tplViewActions templates.TplName = "repo/actions/view"
+ tplListActions templates.TplName = "repo/actions/list"
+ tplDispatchInputsActions templates.TplName = "repo/actions/workflow_dispatch_inputs"
+ tplViewActions templates.TplName = "repo/actions/view"
)
type Workflow struct {
@@ -64,107 +65,143 @@ func MustEnableActions(ctx *context.Context) {
func List(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("actions.actions")
ctx.Data["PageIsActions"] = true
+
+ commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
+ if err != nil {
+ ctx.ServerError("GetBranchCommit", err)
+ return
+ }
+
+ workflows := prepareWorkflowDispatchTemplate(ctx, commit)
+ if ctx.Written() {
+ return
+ }
+
+ prepareWorkflowList(ctx, workflows)
+ if ctx.Written() {
+ return
+ }
+
+ ctx.HTML(http.StatusOK, tplListActions)
+}
+
+func WorkflowDispatchInputs(ctx *context.Context) {
+ ref := ctx.FormString("ref")
+ if ref == "" {
+ ctx.NotFound("WorkflowDispatchInputs: no ref", nil)
+ return
+ }
+ // get target commit of run from specified ref
+ refName := git.RefName(ref)
+ var commit *git.Commit
+ var err error
+ if refName.IsTag() {
+ commit, err = ctx.Repo.GitRepo.GetTagCommit(refName.TagName())
+ } else if refName.IsBranch() {
+ commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName.BranchName())
+ } else {
+ ctx.ServerError("UnsupportedRefType", nil)
+ return
+ }
+ if err != nil {
+ ctx.ServerError("GetTagCommit/GetBranchCommit", err)
+ return
+ }
+ prepareWorkflowDispatchTemplate(ctx, commit)
+ if ctx.Written() {
+ return
+ }
+ ctx.HTML(http.StatusOK, tplDispatchInputsActions)
+}
+
+func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (workflows []Workflow) {
workflowID := ctx.FormString("workflow")
- actorID := ctx.FormInt64("actor")
- status := ctx.FormInt("status")
ctx.Data["CurWorkflow"] = workflowID
+ ctx.Data["CurWorkflowExists"] = false
- var workflows []Workflow
var curWorkflow *model.Workflow
- if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
- ctx.ServerError("IsEmpty", err)
- return
- } else if !empty {
- commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
- if err != nil {
- ctx.ServerError("GetBranchCommit", err)
- return
- }
- entries, err := actions.ListWorkflows(commit)
- if err != nil {
- ctx.ServerError("ListWorkflows", err)
- return
- }
- // Get all runner labels
- runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
- RepoID: ctx.Repo.Repository.ID,
- IsOnline: optional.Some(true),
- WithAvailable: true,
- })
+ entries, err := actions.ListWorkflows(commit)
+ if err != nil {
+ ctx.ServerError("ListWorkflows", err)
+ return nil
+ }
+
+ // Get all runner labels
+ runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ IsOnline: optional.Some(true),
+ WithAvailable: true,
+ })
+ if err != nil {
+ ctx.ServerError("FindRunners", err)
+ return nil
+ }
+ allRunnerLabels := make(container.Set[string])
+ for _, r := range runners {
+ allRunnerLabels.AddMultiple(r.AgentLabels...)
+ }
+
+ workflows = make([]Workflow, 0, len(entries))
+ for _, entry := range entries {
+ workflow := Workflow{Entry: *entry}
+ content, err := actions.GetContentFromEntry(entry)
if err != nil {
- ctx.ServerError("FindRunners", err)
- return
+ ctx.ServerError("GetContentFromEntry", err)
+ return nil
}
- allRunnerLabels := make(container.Set[string])
- for _, r := range runners {
- allRunnerLabels.AddMultiple(r.AgentLabels...)
+ wf, err := model.ReadWorkflow(bytes.NewReader(content))
+ if err != nil {
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
+ workflows = append(workflows, workflow)
+ continue
}
-
- workflows = make([]Workflow, 0, len(entries))
- for _, entry := range entries {
- workflow := Workflow{Entry: *entry}
- content, err := actions.GetContentFromEntry(entry)
- if err != nil {
- ctx.ServerError("GetContentFromEntry", err)
- return
- }
- wf, err := model.ReadWorkflow(bytes.NewReader(content))
- if err != nil {
- workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
- workflows = append(workflows, workflow)
+ // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
+ hasJobWithoutNeeds := false
+ // Check whether you have matching runner and a job without "needs"
+ emptyJobsNumber := 0
+ for _, j := range wf.Jobs {
+ if j == nil {
+ emptyJobsNumber++
continue
}
- // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
- hasJobWithoutNeeds := false
- // Check whether have matching runner and a job without "needs"
- emptyJobsNumber := 0
- for _, j := range wf.Jobs {
- if j == nil {
- emptyJobsNumber++
+ if !hasJobWithoutNeeds && len(j.Needs()) == 0 {
+ hasJobWithoutNeeds = true
+ }
+ runsOnList := j.RunsOn()
+ for _, ro := range runsOnList {
+ if strings.Contains(ro, "${{") {
+ // Skip if it contains expressions.
+ // The expressions could be very complex and could not be evaluated here,
+ // so just skip it, it's OK since it's just a tooltip message.
continue
}
- if !hasJobWithoutNeeds && len(j.Needs()) == 0 {
- hasJobWithoutNeeds = true
- }
- runsOnList := j.RunsOn()
- for _, ro := range runsOnList {
- if strings.Contains(ro, "${{") {
- // Skip if it contains expressions.
- // The expressions could be very complex and could not be evaluated here,
- // so just skip it, it's OK since it's just a tooltip message.
- continue
- }
- if !allRunnerLabels.Contains(ro) {
- workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro)
- break
- }
- }
- if workflow.ErrMsg != "" {
+ if !allRunnerLabels.Contains(ro) {
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro)
break
}
}
- if !hasJobWithoutNeeds {
- workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs")
- }
- if emptyJobsNumber == len(wf.Jobs) {
- workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job")
+ if workflow.ErrMsg != "" {
+ break
}
- workflows = append(workflows, workflow)
+ }
+ if !hasJobWithoutNeeds {
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs")
+ }
+ if emptyJobsNumber == len(wf.Jobs) {
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job")
+ }
+ workflows = append(workflows, workflow)
- if workflow.Entry.Name() == workflowID {
- curWorkflow = wf
- }
+ if workflow.Entry.Name() == workflowID {
+ curWorkflow = wf
+ ctx.Data["CurWorkflowExists"] = true
}
}
+
ctx.Data["workflows"] = workflows
ctx.Data["RepoLink"] = ctx.Repo.Repository.Link()
- page := ctx.FormInt("page")
- if page <= 0 {
- page = 1
- }
-
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
ctx.Data["ActionsConfig"] = actionsConfig
@@ -188,7 +225,7 @@ func List(ctx *context.Context) {
branches, err := git_model.FindBranchNames(ctx, branchOpts)
if err != nil {
ctx.ServerError("FindBranchNames", err)
- return
+ return nil
}
// always put default branch on the top if it exists
if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
@@ -200,12 +237,23 @@ func List(ctx *context.Context) {
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("GetTagNamesByRepoID", err)
- return
+ return nil
}
ctx.Data["Tags"] = tags
}
}
}
+ return workflows
+}
+
+func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
+ actorID := ctx.FormInt64("actor")
+ status := ctx.FormInt("status")
+ workflowID := ctx.FormString("workflow")
+ page := ctx.FormInt("page")
+ if page <= 0 {
+ page = 1
+ }
// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
// they will be 0 by default, which indicates get all status or actors
@@ -264,8 +312,6 @@ func List(ctx *context.Context) {
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
-
- ctx.HTML(http.StatusOK, tplListActions)
}
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index ba17fa427d..9a18ca5305 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -812,13 +812,8 @@ func Run(ctx *context_module.Context) {
return
}
- // get workflow entry from default branch commit
- defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
- return
- }
- entries, err := actions.ListWorkflows(defaultBranchCommit)
+ // get workflow entry from runTargetCommit
+ entries, err := actions.ListWorkflows(runTargetCommit)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
diff --git a/routers/web/web.go b/routers/web/web.go
index 5e0995545e..ff91bda3d2 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1412,6 +1412,7 @@ func registerRoutes(m *web.Router) {
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
m.Post("/run", reqRepoActionsWriter, actions.Run)
+ m.Get("/workflow-dispatch-inputs", reqRepoActionsWriter, actions.WorkflowDispatchInputs)
m.Group("/runs/{run}", func() {
m.Combo("").
@@ -1433,7 +1434,7 @@ func registerRoutes(m *web.Router) {
m.Group("/workflows/{workflow_name}", func() {
m.Get("/badge.svg", actions.GetWorkflowBadge)
})
- }, optSignIn, context.RepoAssignment, reqRepoActionsReader, actions.MustEnableActions)
+ }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions)
// end "/{username}/{reponame}/actions"
m.Group("/{username}/{reponame}/wiki", func() {
diff --git a/templates/repo/actions/workflow_dispatch.tmpl b/templates/repo/actions/workflow_dispatch.tmpl
index 21f3ef2077..55fe122419 100644
--- a/templates/repo/actions/workflow_dispatch.tmpl
+++ b/templates/repo/actions/workflow_dispatch.tmpl
@@ -11,7 +11,7 @@
<label>{{ctx.Locale.Tr "actions.workflow.from_ref"}}:</label>
</span>
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-items-nowrap">
- <input type="hidden" name="ref" value="refs/heads/{{index .Branches 0}}">
+ <input type="hidden" name="ref" hx-sync="this:replace" hx-target="#runWorkflowDispatchModalInputs" hx-swap="innerHTML" hx-get="{{$.Link}}/workflow-dispatch-inputs?workflow={{$.CurWorkflow}}" hx-trigger="change" value="refs/heads/{{index .Branches 0}}">
{{svg "octicon-git-branch" 14}}
<div class="default text">{{index .Branches 0}}</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
@@ -49,30 +49,9 @@
<div class="divider"></div>
- {{range $item := .WorkflowDispatchConfig.Inputs}}
- <div class="ui field {{if .Required}}required{{end}}">
- {{if eq .Type "choice"}}
- <label>{{.Description}}:</label>
- <select class="ui selection type dropdown" name="{{.Name}}">
- {{range .Options}}
- <option value="{{.}}" {{if eq $item.Default .}}selected{{end}} >{{.}}</option>
- {{end}}
- </select>
- {{else if eq .Type "boolean"}}
- <div class="ui inline checkbox">
- <label>{{.Description}}</label>
- <input type="checkbox" name="{{.Name}}" {{if eq .Default "true"}}checked{{end}}>
- </div>
- {{else if eq .Type "number"}}
- <label>{{.Description}}:</label>
- <input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
- {{else}}
- <label>{{.Description}}:</label>
- <input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
- {{end}}
+ <div id="runWorkflowDispatchModalInputs">
+ {{template "repo/actions/workflow_dispatch_inputs" .}}
</div>
- {{end}}
- <button class="ui tiny primary button" type="submit">Submit</button>
</form>
</div>
</div>
diff --git a/templates/repo/actions/workflow_dispatch_inputs.tmpl b/templates/repo/actions/workflow_dispatch_inputs.tmpl
new file mode 100644
index 0000000000..8b8292af1d
--- /dev/null
+++ b/templates/repo/actions/workflow_dispatch_inputs.tmpl
@@ -0,0 +1,45 @@
+{{if not .WorkflowDispatchConfig}}
+ <div class="ui error message tw-block">{{/* using "ui message" in "ui form" needs to force to display */}}
+ {{if not .CurWorkflowExists}}
+ {{ctx.Locale.Tr "actions.workflow.not_found" $.CurWorkflow}}
+ {{else}}
+ {{ctx.Locale.Tr "actions.workflow.has_no_workflow_dispatch" $.CurWorkflow}}
+ {{end}}
+ </div>
+{{else}}
+ {{range $item := .WorkflowDispatchConfig.Inputs}}
+ <div class="ui field {{if .Required}}required{{end}}">
+ {{if eq .Type "choice"}}
+ <label>{{or .Description .Name}}:</label>
+ {{/* htmx won't initialize the fomantic dropdown, so it is a standard "select" input */}}
+ <select class="ui selection dropdown" name="{{.Name}}">
+ {{range .Options}}
+ <option value="{{.}}" {{if eq $item.Default .}}selected{{end}}>{{.}}</option>
+ {{end}}
+ </select>
+ {{else if eq .Type "boolean"}}
+ {{/* htmx doesn't trigger our JS code to attach fomantic label to checkbox, so here we use standard checkbox */}}
+ <label class="tw-flex flex-text-inline">
+ <input type="checkbox" name="{{.Name}}" {{if eq .Default "true"}}checked{{end}}>
+ {{or .Description .Name}}
+ </label>
+ {{else if eq .Type "number"}}
+ <label>{{or .Description .Name}}:</label>
+ <input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
+ {{else}}
+ <label>{{or .Description .Name}}:</label>
+ <input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
+ {{end}}
+ </div>
+ {{end}}
+ <div class="ui field">
+ <button class="ui tiny primary button" type="submit">{{ctx.Locale.Tr "actions.workflow.run"}}</button>
+ </div>
+{{end}}
+{{range .workflows}}
+ {{if and .ErrMsg (eq .Entry.Name $.CurWorkflow)}}
+ <div class="ui field">
+ <div>{{svg "octicon-alert" 16 "text red"}} {{.ErrMsg}}</div>
+ </div>
+ {{end}}
+{{end}}