feat: support cancel button

This commit is contained in:
Jason Song 2022-11-18 18:38:20 +08:00
parent f3a9a25682
commit 736275f0b1
5 changed files with 99 additions and 40 deletions

View File

@ -430,8 +430,10 @@ func UpdateTaskByState(state *runnerv1.TaskState) (*Task, error) {
e := db.GetEngine(ctx)
task := &Task{}
if _, err := e.ID(state.Id).Get(task); err != nil {
if has, err := e.ID(state.Id).Get(task); err != nil {
return nil, err
} else if !has {
return nil, util.ErrNotExist
}
if state.Result != runnerv1.Result_RESULT_UNSPECIFIED {
@ -483,54 +485,55 @@ func UpdateTaskByState(state *runnerv1.TaskState) (*Task, error) {
return task, nil
}
func StopTask(ctx context.Context, task *Task, result runnerv1.Result) (*Task, error) {
ctx, commiter, err := db.TxContext(ctx)
if err != nil {
return nil, err
func StopTask(ctx context.Context, taskID int64, status Status) error {
if !status.IsDone() {
return fmt.Errorf("cannot stop task with status %v", status)
}
defer commiter.Close()
e := db.GetEngine(ctx)
task := &Task{}
if has, err := e.ID(taskID).Get(task); err != nil {
return err
} else if !has {
return util.ErrNotExist
}
if task.Status.IsDone() {
return nil
}
now := timeutil.TimeStampNow()
if result != runnerv1.Result_RESULT_UNSPECIFIED {
task.Status = Status(result)
task.Stopped = now
if _, err := UpdateRunJob(ctx, &RunJob{
ID: task.JobID,
Status: task.Status,
Stopped: task.Stopped,
}, nil); err != nil {
return nil, err
}
task.Status = status
task.Stopped = now
if _, err := UpdateRunJob(ctx, &RunJob{
ID: task.JobID,
Status: task.Status,
Stopped: task.Stopped,
}, nil); err != nil {
return err
}
if _, err := e.ID(task.ID).Update(task); err != nil {
return nil, err
return err
}
if err := task.LoadAttributes(ctx); err != nil {
return nil, err
return err
}
for _, step := range task.Steps {
if !step.Status.IsDone() {
step.Status = Status(result)
step.Status = status
if step.Started == 0 {
step.Started = now
}
step.Stopped = now
}
if _, err := e.ID(step.ID).Update(step); err != nil {
return nil, err
return err
}
}
if err := commiter.Commit(); err != nil {
return nil, err
}
return task, nil
return nil
}
func isSubset(set, subset []string) bool {

View File

@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/bots"
context_module "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
runnerv1 "gitea.com/gitea/proto-go/runner/v1"
@ -45,8 +46,9 @@ type ViewRequest struct {
type ViewResponse struct {
StateData struct {
BuildInfo struct {
HTMLURL string `json:"htmlurl"`
Title string `json:"title"`
HTMLURL string `json:"htmlurl"`
Title string `json:"title"`
Cancelable bool `json:"cancelable"`
} `json:"buildInfo"`
AllJobGroups []ViewGroup `json:"allJobGroups"`
CurrentJobInfo struct {
@ -103,6 +105,7 @@ func ViewPost(ctx *context_module.Context) {
resp := &ViewResponse{}
resp.StateData.BuildInfo.Title = run.Title
resp.StateData.BuildInfo.HTMLURL = run.HTMLURL()
resp.StateData.BuildInfo.Cancelable = !run.Status.IsDone()
respJobs := make([]*ViewJob, len(jobs))
for i, v := range jobs {
@ -115,8 +118,8 @@ func ViewPost(ctx *context_module.Context) {
resp.StateData.AllJobGroups = []ViewGroup{
{
Summary: "Only One Group", // TODO: maybe we don't need job group
Jobs: respJobs,
// TODO: maybe we don't need job group
Jobs: respJobs,
},
}
@ -221,6 +224,45 @@ func Rerun(ctx *context_module.Context) {
ctx.JSON(http.StatusOK, struct{}{})
}
func Cancel(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
_, jobs := getRunJobs(ctx, runIndex, -1)
if ctx.Written() {
return
}
if err := db.WithTx(ctx, func(ctx context.Context) error {
for _, job := range jobs {
status := job.Status
if status.IsDone() {
continue
}
if job.TaskID == 0 {
job.Status = bots_model.StatusCancelled
job.Stopped = timeutil.TimeStampNow()
n, err := bots_model.UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("job has changed, try again")
}
continue
}
if err := bots_model.StopTask(ctx, job.TaskID, bots_model.StatusCancelled); err != nil {
return err
}
}
return nil
}); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
ctx.JSON(http.StatusOK, struct{}{})
}
func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (current *bots_model.RunJob, jobs []*bots_model.RunJob) {
run, err := bots_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
@ -242,12 +284,12 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (current
v.Run = run
}
if jobIndex < 0 || jobIndex >= int64(len(jobs)) {
if jobIndex >= 0 && jobIndex < int64(len(jobs)) {
if len(jobs) == 0 {
ctx.Error(http.StatusNotFound, fmt.Sprintf("run %v has no job %v", runIndex, jobIndex))
return nil, nil
}
current = jobs[jobIndex]
}
current = jobs[jobIndex]
return
}

View File

@ -1225,7 +1225,7 @@ func RegisterRoutes(m *web.Route) {
Post(bindIgnErr(builds.ViewRequest{}), builds.ViewPost)
m.Post("/rerun", builds.Rerun)
})
m.Post("/cancel", builds.Cancel)
})
}, reqRepoBuildsReader, builds.MustEnableBuilds)

View File

@ -12,8 +12,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
runnerv1 "gitea.com/gitea/proto-go/runner/v1"
)
const (
@ -34,7 +32,9 @@ func StopZombieTasks(ctx context.Context) error {
}
for _, task := range tasks {
if _, err := bots_model.StopTask(ctx, task, runnerv1.Result_RESULT_FAILURE); err != nil {
if err := db.WithTx(ctx, func(ctx context.Context) error {
return bots_model.StopTask(ctx, task.ID, bots_model.StatusFailure)
}); err != nil {
log.Warn("stop zombie task %v: %v", task.ID, err)
// go on
}
@ -54,7 +54,9 @@ func StopEndlessTasks(ctx context.Context) error {
}
for _, task := range tasks {
if _, err := bots_model.StopTask(ctx, task, runnerv1.Result_RESULT_FAILURE); err != nil {
if err := db.WithTx(ctx, func(ctx context.Context) error {
return bots_model.StopTask(ctx, task.ID, bots_model.StatusFailure)
}); err != nil {
log.Warn("stop endless task %v: %v", task.ID, err)
// go on
}

View File

@ -6,9 +6,9 @@
</div>
<div class="job-group-section" v-for="(jobGroup, i) in allJobGroups" :key="i">
<div class="job-group-summary">
{{ jobGroup.summary }}
</div>
<!-- <div class="job-group-summary">-->
<!-- {{ jobGroup.summary }}-->
<!-- </div>-->
<div class="job-brief-list">
<a class="job-brief-item" v-for="(job, index) in jobGroup.jobs" :key="job.id" v-bind:href="buildInfo.htmlurl+'/jobs/'+index">
<SvgIcon name="octicon-check-circle-fill" class="green" v-if="job.status === 'success'"/>
@ -23,6 +23,7 @@
</button>
</a>
</div>
<button class="ui fluid tiny basic red button" @click="cancelRun()" v-if="buildInfo.cancelable">Cancel</button>
</div>
</div>
@ -160,6 +161,17 @@ const sfc = {
body: {},
});
},
// cancel a run
cancelRun() {
fetch(this.buildInfo.htmlurl+'/cancel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Csrf-Token': csrfToken,
},
body: {},
});
},
formatDuration(d) {
d = Math.round(d);