From d320eb66f4ff9f4a6f7ce268aa51c5e13b270878 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Fri, 14 Oct 2022 16:18:14 +0800 Subject: [PATCH] feat: fetch job data --- models/bots/run_job.go | 2 +- models/bots/task_log.go | 6 +- routers/web/dev/buildview.go | 191 ++++++++++++++++++++++++ routers/web/repo/builds/builds.go | 100 ------------- routers/web/web.go | 8 +- web_src/js/components/RepoBuildView.vue | 12 +- 6 files changed, 208 insertions(+), 111 deletions(-) diff --git a/models/bots/run_job.go b/models/bots/run_job.go index 01b190de83..04de1f1a1b 100644 --- a/models/bots/run_job.go +++ b/models/bots/run_job.go @@ -73,7 +73,7 @@ func GetRunJobByID(ctx context.Context, id int64) (*RunJob, error) { if err != nil { return nil, err } else if !has { - return nil, ErrRunNotExist{ + return nil, ErrRunJobNotExist{ ID: id, } } diff --git a/models/bots/task_log.go b/models/bots/task_log.go index 9603be6ac2..5bdd11ec32 100644 --- a/models/bots/task_log.go +++ b/models/bots/task_log.go @@ -14,7 +14,7 @@ import ( // TaskLog represents a task's log, every task has a standalone table type TaskLog struct { - ID int64 + ID int64 `xorm:"pk"` Timestamp timeutil.TimeStamp Content string `xorm:"LONGTEXT"` } @@ -35,10 +35,10 @@ func CreateTaskLog(taskID int64) error { } func GetTaskLogs(taskID, index, length int64) (logs []*TaskLog, err error) { - sess := db.GetEngine(db.DefaultContext).Table(GetBuildLogTableName(taskID)). + sess := db.GetEngine(db.DefaultContext).Table(GetTaskLogTableName(taskID)). Where("id>=?", index).OrderBy("id") - if length > 0 { + if length >= 0 { sess.Limit(int(length)) } diff --git a/routers/web/dev/buildview.go b/routers/web/dev/buildview.go index a4361c848e..cf1fbbfd87 100644 --- a/routers/web/dev/buildview.go +++ b/routers/web/dev/buildview.go @@ -1,7 +1,11 @@ package dev import ( + "code.gitea.io/gitea/core" + bots_model "code.gitea.io/gitea/models/bots" + "code.gitea.io/gitea/modules/web" "net/http" + "strconv" "code.gitea.io/gitea/modules/context" ) @@ -9,3 +13,190 @@ import ( func BuildView(ctx *context.Context) { ctx.HTML(http.StatusOK, "dev/buildview") } + +type BuildViewRequest struct { + StepLogCursors []struct { + StepIndex int `json:"stepIndex"` + Cursor int64 `json:"cursor"` + Expanded bool `json:"expanded"` + } `json:"stepLogCursors"` +} + +type BuildViewResponse struct { + StateData struct { + BuildInfo struct { + Title string `json:"title"` + } `json:"buildInfo"` + AllJobGroups []BuildViewGroup `json:"allJobGroups"` + CurrentJobInfo struct { + Title string `json:"title"` + Detail string `json:"detail"` + } `json:"currentJobInfo"` + CurrentJobSteps []BuildViewJobStep `json:"currentJobSteps"` + } `json:"stateData"` + LogsData struct { + StreamingLogs []BuildViewStepLog `json:"streamingLogs"` + } `json:"logsData"` +} + +type BuildViewGroup struct { + Summary string `json:"summary"` + Jobs []*BuildViewJob `json:"jobs"` +} + +type BuildViewJob struct { + Id int64 `json:"id"` + Name string `json:"name"` + Status core.BuildStatus `json:"status"` +} + +type BuildViewJobStep struct { + Summary string `json:"summary"` + Duration float64 `json:"duration"` + Status core.BuildStatus `json:"status"` +} + +type BuildViewStepLog struct { + StepIndex int `json:"stepIndex"` + Cursor int64 `json:"cursor"` + Lines []BuildViewStepLogLine `json:"lines"` +} + +type BuildViewStepLogLine struct { + Ln int `json:"ln"` + M string `json:"m"` + T float64 `json:"t"` +} + +func BuildViewPost(ctx *context.Context) { + req := web.GetForm(ctx).(*BuildViewRequest) + currentJobID, _ := strconv.ParseInt(ctx.Req.URL.Query().Get("job_id"), 10, 64) + + job, err := bots_model.GetRunJobByID(ctx, currentJobID) + if err != nil { + if _, ok := err.(bots_model.ErrRunJobNotExist); ok { + ctx.Error(http.StatusNotFound, err.Error()) + return + } + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + if err := job.LoadAttributes(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + run := job.Run + jobs, err := bots_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + var task *bots_model.Task + if job.TaskID > 0 { + task, err = bots_model.GetTaskByID(ctx, job.TaskID) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + task.Job = job + if err := task.LoadAttributes(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + } + + resp := &BuildViewResponse{} + resp.StateData.BuildInfo.Title = run.Name + + respJobs := make([]*BuildViewJob, len(jobs)) + for i, v := range jobs { + respJobs[i] = &BuildViewJob{ + Id: v.ID, + Name: v.Name, + Status: v.Status, + } + } + + resp.StateData.AllJobGroups = []BuildViewGroup{ + { + Summary: "Only One Group", // TODO: maybe we don't need job group + Jobs: respJobs, + }, + } + + resp.StateData.CurrentJobInfo.Title = job.Name + resp.LogsData.StreamingLogs = make([]BuildViewStepLog, 0, len(req.StepLogCursors)) + if job.TaskID == 0 { + resp.StateData.CurrentJobInfo.Detail = "wait to be pick up by a runner" + } else { + resp.StateData.CurrentJobInfo.Detail = "TODO: more detail info" // TODO: more detail info + + var firstStep, lastStep *bots_model.TaskStep + if l := len(task.Steps); l > 0 { + firstStep = task.Steps[0] + lastStep = task.Steps[l-1] + } + headStep := &bots_model.TaskStep{ + Name: "Set up job", + LogIndex: 0, + LogLength: -1, // no limit + Started: task.Started, + } + if firstStep != nil { + headStep.LogLength = firstStep.LogIndex + headStep.Stopped = firstStep.Started + } + tailStep := &bots_model.TaskStep{ + Name: "Complete job", + Stopped: task.Stopped, + } + if lastStep != nil { + tailStep.LogIndex = lastStep.LogIndex + lastStep.LogLength + tailStep.LogLength = -1 // no limit + tailStep.Started = lastStep.Stopped + } + steps := make([]*bots_model.TaskStep, 0, len(task.Steps)+2) + steps = append(steps, headStep) + steps = append(steps, task.Steps...) + steps = append(steps, tailStep) + + resp.StateData.CurrentJobSteps = make([]BuildViewJobStep, len(steps)) + for i, v := range steps { + resp.StateData.CurrentJobSteps[i] = BuildViewJobStep{ + Summary: v.Name, + Duration: float64(v.Stopped - v.Started), + Status: core.StatusRunning, // TODO: add status to step, + } + } + + for _, cursor := range req.StepLogCursors { + if cursor.Expanded { + step := steps[cursor.StepIndex] + var logRows []*bots_model.TaskLog + if cursor.Cursor < step.LogLength || step.LogLength < 0 { + logRows, err = bots_model.GetTaskLogs(task.ID, step.LogIndex+cursor.Cursor, step.LogLength-cursor.Cursor) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + } + logLines := make([]BuildViewStepLogLine, len(logRows)) + for i, row := range logRows { + logLines[i] = BuildViewStepLogLine{ + Ln: i, + M: row.Content, + T: float64(row.Timestamp), + } + } + resp.LogsData.StreamingLogs = append(resp.LogsData.StreamingLogs, BuildViewStepLog{ + StepIndex: cursor.StepIndex, + Cursor: cursor.Cursor + int64(len(logLines)), + Lines: logLines, + }) + } + } + } + + ctx.JSON(http.StatusOK, resp) +} diff --git a/routers/web/repo/builds/builds.go b/routers/web/repo/builds/builds.go index 55507fe9af..7058661dba 100644 --- a/routers/web/repo/builds/builds.go +++ b/routers/web/repo/builds/builds.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/core" bots_model "code.gitea.io/gitea/models/bots" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" @@ -148,102 +147,3 @@ LOOP_WORKFLOWS: ctx.JSON(http.StatusOK, logs) } - -type RunState struct { - Title string `json:"title"` - Jobs []*RunStateJob `json:"jobs"` - CurrentJobInfo *RunStateJobInfo `json:"current_job_info"` - CurrentJobSteps []*RunStateJobSteps `json:"current_job_steps"` -} - -type RunStateJob struct { - ID int64 `json:"id"` - Name string `json:"name"` - Status core.BuildStatus `json:"status"` -} - -type RunStateJobInfo struct { - Title string `json:"title"` - Detail string `json:"detail"` -} - -type RunStateJobSteps struct { - Summary string `json:"summary"` - Status core.BuildStatus `json:"status"` - Duration int64 `json:"duration"` // seconds -} - -func GetRunState(ctx *context.Context) { - runID := ctx.ParamsInt64("index") - currentJobID := ctx.ParamsInt64("jobid") - - run, err := bots_model.GetRunByID(ctx, runID) - if err != nil { - if _, ok := err.(bots_model.ErrRunNotExist); ok { - ctx.Error(http.StatusNotFound, err.Error()) - return - } - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - jobs, err := bots_model.GetRunJobsByRunID(ctx, run.ID) - if err != nil { - if _, ok := err.(bots_model.ErrRunJobNotExist); ok { - ctx.Error(http.StatusNotFound, err.Error()) - return - } - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - - state := &RunState{ - Title: run.Name, - Jobs: make([]*RunStateJob, len(jobs)), - } - for i, v := range jobs { - state.Jobs[i] = &RunStateJob{ - ID: v.ID, - Name: v.Name, - Status: v.Status, - } - } - if currentJobID != 0 { - for _, job := range jobs { - if job.ID == currentJobID { - state.CurrentJobInfo = &RunStateJobInfo{ - Title: job.Name, - } - if job.TaskID == 0 { - state.CurrentJobInfo.Detail = "wait to be pick up by a runner" - } - state.CurrentJobInfo.Detail = "TODO: more detail info" // TODO: more detail info, need to be internationalized - - task, err := bots_model.GetTaskByID(ctx, job.TaskID) - if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - task.Job = job - if err := task.LoadAttributes(ctx); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - state.CurrentJobSteps = make([]*RunStateJobSteps, 0, len(task.Steps)+2) - // TODO: add steps "Set up job" and "Complete job" - for _, step := range task.Steps { - state.CurrentJobSteps = append(state.CurrentJobSteps, &RunStateJobSteps{ - Summary: step.Name, - Status: core.StatusRunning, // TODO: add status to step - Duration: int64(step.Stopped - step.Started), - }) - } - } - } - } - - ctx.JSON(http.StatusOK, state) -} - -func GetRunLog(ctx *context.Context) { - // TODO -} diff --git a/routers/web/web.go b/routers/web/web.go index dc0194b668..1c284f9df0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -660,7 +660,9 @@ func RegisterRoutes(m *web.Route) { m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action) if !setting.IsProd { + m.Any("/dev/termdemo", dev.TermDemo) m.Get("/dev/buildview", dev.BuildView) + m.Post("/dev/buildview", bindIgnErr(dev.BuildViewRequest{}), dev.BuildViewPost) } reqRepoAdmin := context.RequireRepoAdmin() @@ -1185,10 +1187,8 @@ func RegisterRoutes(m *web.Route) { m.Group("/builds", func() { m.Get("", builds.List) m.Group("/{index}", func() { - //m.Get("", builds.ViewBuild) - //m.Get("/{workflow}/job/{jobname}/logs", builds.GetBuildJobLogs) - m.Get("", builds.GetRunState) - m.Get("/job/{jobid}", builds.GetRunState) + m.Get("", builds.ViewBuild) + m.Get("/{workflow}/job/{jobname}/logs", builds.GetBuildJobLogs) }) }, reqRepoBuildsReader, builds.MustEnableBuilds) diff --git a/web_src/js/components/RepoBuildView.vue b/web_src/js/components/RepoBuildView.vue index d35a4c4715..90737790dd 100644 --- a/web_src/js/components/RepoBuildView.vue +++ b/web_src/js/components/RepoBuildView.vue @@ -80,6 +80,8 @@ const sfc = { data() { return { + jobId: 120, // TODO: read job id + // internal state loading: false, currentJobStepsStates: [], @@ -248,7 +250,11 @@ const sfc = { }, async fetchJobData(reqData) { - const resp = await fetch(`?job_id=${this.jobId}`, {method: 'POST', body: JSON.stringify(reqData)}); + const resp = await fetch(`?job_id=${this.jobId}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(reqData), + }); return await resp.json(); }, @@ -265,8 +271,8 @@ const sfc = { }); const reqData = {stepLogCursors}; - // const respData = await this.fetchJobData(); - const respData = this.fetchMockData(reqData); + const respData = await this.fetchJobData(reqData); + // const respData = this.fetchMockData(reqData); // console.log('loadJobData by request', reqData, ', get response ', respData);