Refactor RepoActionView.vue, add ::group:: support (#32713)

1. make it able to "force reload", then the previous pending request
won't block the new request
2. make it support `::group::`
3. add some TS types (but there are still many variables untyped, this
PR is large enough, the remaining types could be added in the future)
This commit is contained in:
wxiaoguang 2024-12-06 12:04:16 +08:00 committed by GitHub
parent ff14ada965
commit f7f68e4cc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 299 additions and 154 deletions

View File

@ -0,0 +1,108 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package devtest
import (
"fmt"
mathRand "math/rand/v2"
"net/http"
"strings"
"time"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo/actions"
"code.gitea.io/gitea/services/context"
)
func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewStepLog) {
mockedLogs := []string{
"::group::test group for: step={step}, cursor={cursor}",
"in group msg for: step={step}, cursor={cursor}",
"in group msg for: step={step}, cursor={cursor}",
"in group msg for: step={step}, cursor={cursor}",
"::endgroup::",
"message for: step={step}, cursor={cursor}",
"message for: step={step}, cursor={cursor}",
"message for: step={step}, cursor={cursor}",
"message for: step={step}, cursor={cursor}",
"message for: step={step}, cursor={cursor}",
}
cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
for i := 0; i < util.Iif(logCur.Step == 0, 3, 1); i++ {
logStr := mockedLogs[int(cur)%len(mockedLogs)]
cur++
logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step))
logStr = strings.ReplaceAll(logStr, "{cursor}", fmt.Sprintf("%d", cur))
stepsLog = append(stepsLog, &actions.ViewStepLog{
Step: logCur.Step,
Cursor: cur,
Started: time.Now().Unix() - 1,
Lines: []*actions.ViewStepLogLine{
{Index: cur, Message: logStr, Timestamp: float64(time.Now().UnixNano()) / float64(time.Second)},
},
})
}
return stepsLog
}
func MockActionsRunsJobs(ctx *context.Context) {
req := web.GetForm(ctx).(*actions.ViewRequest)
resp := &actions.ViewResponse{}
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-a",
Size: 100 * 1024,
Status: "expired",
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-b",
Size: 1024 * 1024,
Status: "completed",
})
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 0 (mock slow)",
Duration: time.Hour.String(),
Status: actions_model.StatusRunning.String(),
})
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 1 (mock fast)",
Duration: time.Hour.String(),
Status: actions_model.StatusRunning.String(),
})
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 2 (mock error)",
Duration: time.Hour.String(),
Status: actions_model.StatusRunning.String(),
})
if len(req.LogCursors) == 0 {
ctx.JSON(http.StatusOK, resp)
return
}
resp.Logs.StepsLog = []*actions.ViewStepLog{}
doSlowResponse := false
doErrorResponse := false
for _, logCur := range req.LogCursors {
if !logCur.Expanded {
continue
}
doSlowResponse = doSlowResponse || logCur.Step == 0
doErrorResponse = doErrorResponse || logCur.Step == 2
resp.Logs.StepsLog = append(resp.Logs.StepsLog, generateMockStepsLog(logCur)...)
}
if doErrorResponse {
if mathRand.Float64() > 0.5 {
ctx.Error(http.StatusInternalServerError, "devtest mock error response")
return
}
}
if doSlowResponse {
time.Sleep(time.Duration(3000) * time.Millisecond)
} else {
time.Sleep(time.Duration(100) * time.Millisecond) // actually, frontend reload every 1 second, any smaller delay is fine
}
ctx.JSON(http.StatusOK, resp)
}

View File

@ -66,15 +66,25 @@ func View(ctx *context_module.Context) {
ctx.HTML(http.StatusOK, tplViewActions)
}
type LogCursor struct {
Step int `json:"step"`
Cursor int64 `json:"cursor"`
Expanded bool `json:"expanded"`
}
type ViewRequest struct {
LogCursors []struct {
Step int `json:"step"`
Cursor int64 `json:"cursor"`
Expanded bool `json:"expanded"`
} `json:"logCursors"`
LogCursors []LogCursor `json:"logCursors"`
}
type ArtifactsViewItem struct {
Name string `json:"name"`
Size int64 `json:"size"`
Status string `json:"status"`
}
type ViewResponse struct {
Artifacts []*ArtifactsViewItem `json:"artifacts"`
State struct {
Run struct {
Link string `json:"link"`
@ -146,6 +156,25 @@ type ViewStepLogLine struct {
Timestamp float64 `json:"timestamp"`
}
func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artifactsViewItems []*ArtifactsViewItem, err error) {
run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
if err != nil {
return nil, err
}
artifacts, err := actions_model.ListUploadedArtifactsMeta(ctx, run.ID)
if err != nil {
return nil, err
}
for _, art := range artifacts {
artifactsViewItems = append(artifactsViewItems, &ArtifactsViewItem{
Name: art.ArtifactName,
Size: art.FileSize,
Status: util.Iif(art.Status == actions_model.ArtifactStatusExpired, "expired", "completed"),
})
}
return artifactsViewItems, nil
}
func ViewPost(ctx *context_module.Context) {
req := web.GetForm(ctx).(*ViewRequest)
runIndex := getRunIndex(ctx)
@ -157,11 +186,19 @@ func ViewPost(ctx *context_module.Context) {
}
run := current.Run
if err := run.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
ctx.ServerError("run.LoadAttributes", err)
return
}
var err error
resp := &ViewResponse{}
resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
if !errors.Is(err, util.ErrNotExist) {
ctx.ServerError("getActionsViewArtifacts", err)
return
}
}
resp.State.Run.Title = run.Title
resp.State.Run.Link = run.Link()
@ -205,12 +242,12 @@ func ViewPost(ctx *context_module.Context) {
var err error
task, err = actions_model.GetTaskByID(ctx, current.TaskID)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
ctx.ServerError("actions_model.GetTaskByID", err)
return
}
task.Job = current
if err := task.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
ctx.ServerError("task.LoadAttributes", err)
return
}
}
@ -278,7 +315,7 @@ func ViewPost(ctx *context_module.Context) {
offset := task.LogIndexes[index]
logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
ctx.ServerError("actions.ReadLogs", err)
return
}
@ -555,49 +592,6 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
return jobs[0], jobs
}
type ArtifactsViewResponse struct {
Artifacts []*ArtifactsViewItem `json:"artifacts"`
}
type ArtifactsViewItem struct {
Name string `json:"name"`
Size int64 `json:"size"`
Status string `json:"status"`
}
func ArtifactsView(ctx *context_module.Context) {
runIndex := getRunIndex(ctx)
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, err.Error())
return
}
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
artifacts, err := actions_model.ListUploadedArtifactsMeta(ctx, run.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
artifactsResponse := ArtifactsViewResponse{
Artifacts: make([]*ArtifactsViewItem, 0, len(artifacts)),
}
for _, art := range artifacts {
status := "completed"
if art.Status == actions_model.ArtifactStatusExpired {
status = "expired"
}
artifactsResponse.Artifacts = append(artifactsResponse.Artifacts, &ArtifactsViewItem{
Name: art.ArtifactName,
Size: art.FileSize,
Status: status,
})
}
ctx.JSON(http.StatusOK, artifactsResponse)
}
func ArtifactsDeleteView(ctx *context_module.Context) {
if !ctx.Repo.CanWrite(unit.TypeActions) {
ctx.Error(http.StatusForbidden, "no permission")

View File

@ -1424,7 +1424,6 @@ func registerRoutes(m *web.Router) {
})
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
m.Get("/artifacts", actions.ArtifactsView)
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
m.Delete("/artifacts/{artifact_name}", actions.ArtifactsDeleteView)
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
@ -1626,9 +1625,12 @@ func registerRoutes(m *web.Router) {
}
if !setting.IsProd {
m.Any("/devtest", devtest.List)
m.Any("/devtest/fetch-action-test", devtest.FetchActionTest)
m.Any("/devtest/{sub}", devtest.Tmpl)
m.Group("/devtest", func() {
m.Any("", devtest.List)
m.Any("/fetch-action-test", devtest.FetchActionTest)
m.Any("/{sub}", devtest.Tmpl)
m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
})
}
m.NotFound(func(w http.ResponseWriter, req *http.Request) {

View File

@ -0,0 +1,30 @@
{{template "base/head" .}}
<div class="page-content">
<div id="repo-action-view"
data-run-index="1"
data-job-index="2"
data-actions-url="{{AppSubUrl}}/devtest/actions-mock"
data-locale-approve="approve"
data-locale-cancel="cancel"
data-locale-rerun="re-run"
data-locale-rerun-all="re-run all"
data-locale-runs-scheduled="scheduled"
data-locale-runs-commit="commit"
data-locale-runs-pushed-by="pushed by"
data-locale-status-unknown="unknown"
data-locale-status-waiting="waiting"
data-locale-status-running="running"
data-locale-status-success="success"
data-locale-status-failure="failure"
data-locale-status-cancelled="cancelled"
data-locale-status-skipped="skipped"
data-locale-status-blocked="blocked"
data-locale-artifacts-title="artifacts"
data-locale-confirm-delete-artifact="confirm delete artifact"
data-locale-show-timestamps="show timestamps"
data-locale-show-log-seconds="show log seconds"
data-locale-show-full-screen="show full screen"
data-locale-download-logs="download logs"
></div>
</div>
{{template "base/footer" .}}

File diff suppressed because it is too large Load Diff