Feature: Add button to re-run failed jobs in Actions (#36924)
Fixes #35997 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -1259,6 +1259,7 @@ func Routes() *web.Router {
|
||||
m.Get("", repo.GetWorkflowRun)
|
||||
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
||||
m.Post("/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowRun)
|
||||
m.Post("/rerun-failed-jobs", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunFailedWorkflowRun)
|
||||
m.Get("/jobs", repo.ListWorkflowRunJobs)
|
||||
m.Post("/jobs/{job_id}/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowJob)
|
||||
m.Get("/artifacts", repo.GetArtifactsOfRun)
|
||||
|
||||
@@ -1255,7 +1255,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, nil); err != nil {
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
@@ -1268,6 +1268,52 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusCreated, convertedRun)
|
||||
}
|
||||
|
||||
// RerunFailedWorkflowRun Reruns all failed jobs in a workflow run.
|
||||
func RerunFailedWorkflowRun(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/rerun-failed-jobs repository rerunFailedWorkflowRun
|
||||
// ---
|
||||
// summary: Reruns all failed jobs in a workflow run
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
run, jobs := getCurrentRepoActionRunJobsByID(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetFailedRerunJobs(jobs)); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// RerunWorkflowJob Reruns a specific workflow job in a run.
|
||||
func RerunWorkflowJob(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/jobs/{job_id}/rerun repository rerunWorkflowJob
|
||||
@@ -1321,7 +1367,7 @@ func RerunWorkflowJob(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
targetJob := jobs[jobIdx]
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil {
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetAllRerunJobs(targetJob, jobs)); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ func MockActionsRunsJobs(ctx *context.Context) {
|
||||
resp.State.Run.CanCancel = runID == 10
|
||||
resp.State.Run.CanApprove = runID == 20
|
||||
resp.State.Run.CanRerun = runID == 30
|
||||
resp.State.Run.CanRerunFailed = runID == 30
|
||||
resp.State.Run.CanDeleteArtifact = true
|
||||
resp.State.Run.WorkflowID = "workflow-id"
|
||||
resp.State.Run.WorkflowLink = "./workflow-link"
|
||||
|
||||
@@ -122,6 +122,7 @@ type ViewResponse struct {
|
||||
CanCancel bool `json:"canCancel"`
|
||||
CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
|
||||
CanRerun bool `json:"canRerun"`
|
||||
CanRerunFailed bool `json:"canRerunFailed"`
|
||||
CanDeleteArtifact bool `json:"canDeleteArtifact"`
|
||||
Done bool `json:"done"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
@@ -238,6 +239,14 @@ func ViewPost(ctx *context_module.Context) {
|
||||
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
|
||||
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
||||
resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
||||
if resp.State.Run.CanRerun {
|
||||
for _, job := range jobs {
|
||||
if job.Status == actions_model.StatusFailure || job.Status == actions_model.StatusCancelled {
|
||||
resp.State.Run.CanRerunFailed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.State.Run.Done = run.Status.IsDone()
|
||||
resp.State.Run.WorkflowID = run.WorkflowID
|
||||
resp.State.Run.WorkflowLink = run.WorkflowLink()
|
||||
@@ -398,6 +407,22 @@ func convertToViewModel(ctx context.Context, locale translation.Locale, cursors
|
||||
return viewJobs, logs, nil
|
||||
}
|
||||
|
||||
// checkRunRerunAllowed checks whether a rerun is permitted for the given run,
|
||||
// writing the appropriate JSON error to ctx and returning false when it is not.
|
||||
func checkRunRerunAllowed(ctx *context_module.Context, run *actions_model.ActionRun) bool {
|
||||
if !run.Status.IsDone() {
|
||||
ctx.JSONError(ctx.Locale.Tr("actions.runs.not_done"))
|
||||
return false
|
||||
}
|
||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
||||
cfg := cfgUnit.ActionsConfig()
|
||||
if cfg.IsWorkflowDisabled(run.WorkflowID) {
|
||||
ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled"))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Rerun will rerun jobs in the given run
|
||||
// If jobIDStr is a blank string, it means rerun all jobs
|
||||
func Rerun(ctx *context_module.Context) {
|
||||
@@ -408,26 +433,39 @@ func Rerun(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// rerun is not allowed if the run is not done
|
||||
if !run.Status.IsDone() {
|
||||
ctx.JSONError(ctx.Locale.Tr("actions.runs.not_done"))
|
||||
if !checkRunRerunAllowed(ctx, run) {
|
||||
return
|
||||
}
|
||||
|
||||
// can not rerun job when workflow is disabled
|
||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
||||
cfg := cfgUnit.ActionsConfig()
|
||||
if cfg.IsWorkflowDisabled(run.WorkflowID) {
|
||||
ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled"))
|
||||
return
|
||||
}
|
||||
|
||||
var targetJob *actions_model.ActionRunJob // nil means rerun all jobs
|
||||
var jobsToRerun []*actions_model.ActionRunJob
|
||||
if ctx.PathParam("job") != "" {
|
||||
targetJob = currentJob
|
||||
jobsToRerun = actions_service.GetAllRerunJobs(currentJob, jobs)
|
||||
} else {
|
||||
jobsToRerun = jobs
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil {
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobsToRerun); err != nil {
|
||||
ctx.ServerError("RerunWorkflowRunJobs", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSONOK()
|
||||
}
|
||||
|
||||
// RerunFailed reruns all failed jobs in the given run
|
||||
func RerunFailed(ctx *context_module.Context) {
|
||||
runID := getRunID(ctx)
|
||||
|
||||
run, jobs, _ := getRunJobsAndCurrentJob(ctx, runID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if !checkRunRerunAllowed(ctx, run) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetFailedRerunJobs(jobs)); err != nil {
|
||||
ctx.ServerError("RerunWorkflowRunJobs", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1529,6 +1529,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
|
||||
m.Delete("/artifacts/{artifact_name}", reqRepoActionsWriter, actions.ArtifactsDeleteView)
|
||||
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
||||
m.Post("/rerun-failed", reqRepoActionsWriter, actions.RerunFailed)
|
||||
})
|
||||
m.Group("/workflows/{workflow_name}", func() {
|
||||
m.Get("/badge.svg", webAuth.AllowBasic, webAuth.AllowOAuth2, actions.GetWorkflowBadge)
|
||||
|
||||
Reference in New Issue
Block a user