mirror of
https://github.com/go-gitea/gitea.git
synced 2025-01-19 02:22:47 +08:00
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>
This commit is contained in:
parent
3078826d01
commit
4237736029
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
workflowID := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
ctx.Data["CurWorkflow"] = workflowID
|
||||
|
||||
var workflows []Workflow
|
||||
var curWorkflow *model.Workflow
|
||||
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
|
||||
ctx.ServerError("IsEmpty", err)
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommit", 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,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("FindRunners", err)
|
||||
return
|
||||
}
|
||||
allRunnerLabels := make(container.Set[string])
|
||||
for _, r := range runners {
|
||||
allRunnerLabels.AddMultiple(r.AgentLabels...)
|
||||
}
|
||||
workflows := prepareWorkflowDispatchTemplate(ctx, commit)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
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")
|
||||
ctx.Data["CurWorkflow"] = workflowID
|
||||
ctx.Data["CurWorkflowExists"] = false
|
||||
|
||||
var curWorkflow *model.Workflow
|
||||
|
||||
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("GetContentFromEntry", err)
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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")
|
||||
}
|
||||
workflows = append(workflows, workflow)
|
||||
|
||||
if workflow.Entry.Name() == workflowID {
|
||||
curWorkflow = wf
|
||||
if workflow.ErrMsg != "" {
|
||||
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")
|
||||
}
|
||||
workflows = append(workflows, workflow)
|
||||
|
||||
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
45
templates/repo/actions/workflow_dispatch_inputs.tmpl
Normal file
45
templates/repo/actions/workflow_dispatch_inputs.tmpl
Normal file
|
@ -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}}
|
Loading…
Reference in New Issue
Block a user