diff options
author | ChristopherHX <christopher.homberger@web.de> | 2025-01-05 14:47:18 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-05 21:47:18 +0800 |
commit | 42377360296b7c810b284472ba6743bf684186fb (patch) | |
tree | b1038b9074d19f95f59b6bc9384af8a60846f176 | |
parent | 3078826d0111e818dc765d94c650f4cb5b3ecce7 (diff) | |
download | gitea-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.ini | 1 | ||||
-rw-r--r-- | routers/web/repo/actions/actions.go | 216 | ||||
-rw-r--r-- | routers/web/repo/actions/view.go | 9 | ||||
-rw-r--r-- | routers/web/web.go | 3 | ||||
-rw-r--r-- | templates/repo/actions/workflow_dispatch.tmpl | 27 | ||||
-rw-r--r-- | templates/repo/actions/workflow_dispatch_inputs.tmpl | 45 |
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}} |