From 72c020298eebcb0c90e23e7ff35e37be867afc44 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 8 Dec 2023 13:41:48 +0100 Subject: [PATCH] [GITEA] allow viewing the latest Action Run on the web Similar to how some other parts of the web UI support a `/latest` path to directly go to the latest of a certain thing, let the Actions web UI do the same: `/{owner}/{repo}/actions/runs/latest` will redirect to the latest run, if there's one available. Fixes gitea#27991. Signed-off-by: Gergely Nagy (cherry picked from commit f67ccef1dd3146b0b942a94e2482b37595180e91) Code cleanup in the actions.ViewLatest route handler Based on feedback received after the feature was merged, use `ctx.NotFound` and `ctx.ServerError`, and drop the use of the unnecessary `ctx.Written()`. Signed-off-by: Gergely Nagy (cherry picked from commit 74e42da5630f9148faaf6b03bf1ac5724fa86b25) (cherry picked from commit f7535a1cef96ce0589f37907f88b024cd095d0ac) (cherry picked from commit 1a90cd37c31a1b9c770d6d79a4663ed8d67845c0) (cherry picked from commit d86d71340afd372e5b5083d5563c2f5b48d975e6) (cherry picked from commit 9e5cce1afccebcd6146e5e0d364bfdbb840b5276) (cherry picked from commit 2013fb3fab5e23d0088434d835411f26a3fd9905) (cherry picked from commit 88b9d21d1194abd133c3b4cbaa19792da433cb43) --- models/actions/run.go | 11 +++ routers/web/repo/actions/view.go | 14 ++++ routers/web/web.go | 25 ++++--- tests/integration/actions_route_test.go | 91 +++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 tests/integration/actions_route_test.go diff --git a/models/actions/run.go b/models/actions/run.go index fcac58d515..db0f380049 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -312,6 +312,17 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork return commiter.Commit() } +func GetLatestRun(ctx context.Context, repoID int64) (*ActionRun, error) { + var run ActionRun + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).OrderBy("id DESC").Limit(1).Get(&run) + if err != nil { + return nil, err + } else if !has { + return nil, fmt.Errorf("latest run: %w", util.ErrNotExist) + } + return &run, nil +} + func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) { var run ActionRun has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run) diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 9cda30d23d..63e3a352a5 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -46,6 +46,20 @@ func View(ctx *context_module.Context) { ctx.HTML(http.StatusOK, tplViewActions) } +func ViewLatest(ctx *context_module.Context) { + run, err := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID) + if err != nil { + ctx.NotFound("GetLatestRun", err) + return + } + err = run.LoadAttributes(ctx) + if err != nil { + ctx.ServerError("LoadAttributes", err) + return + } + ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect) +} + type ViewRequest struct { LogCursors []struct { Step int `json:"step"` diff --git a/routers/web/web.go b/routers/web/web.go index 552146ccc6..942bab67e5 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1347,22 +1347,25 @@ func registerRoutes(m *web.Route) { m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) - m.Group("/runs/{run}", func() { - m.Combo(""). - Get(actions.View). - Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) - m.Group("/jobs/{job}", func() { + m.Group("/runs", func() { + m.Get("/latest", actions.ViewLatest) + m.Group("/{run}", func() { m.Combo(""). Get(actions.View). Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) + m.Group("/jobs/{job}", func() { + m.Combo(""). + Get(actions.View). + Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) + m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) + m.Get("/logs", actions.Logs) + }) + m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) + m.Post("/approve", reqRepoActionsWriter, actions.Approve) + m.Post("/artifacts", actions.ArtifactsView) + m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView) m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) - m.Get("/logs", actions.Logs) }) - m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) - m.Post("/approve", reqRepoActionsWriter, actions.Approve) - m.Post("/artifacts", actions.ArtifactsView) - m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView) - m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) }) }, reqRepoActionsReader, actions.MustEnableActions) diff --git a/tests/integration/actions_route_test.go b/tests/integration/actions_route_test.go new file mode 100644 index 0000000000..41bd4116c2 --- /dev/null +++ b/tests/integration/actions_route_test.go @@ -0,0 +1,91 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "strings" + "testing" + "time" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + repo_service "code.gitea.io/gitea/services/repository" + files_service "code.gitea.io/gitea/services/repository/files" + + "github.com/stretchr/testify/assert" +) + +func TestActionsWebRouteLatestRun(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "actions-latest", + Description: "test /actions/runs/latest", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // enable actions + err = repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{ + RepoID: repo.ID, + Type: unit_model.TypeActions, + }}, nil) + assert.NoError(t, err) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/pr.yml", + ContentReader: strings.NewReader("name: test\non:\n push:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + Name: user2.Name, + Email: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + Name: user2.Name, + Email: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + // a run has been created + assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID})) + + // Hit the `/actions/runs/latest` route + req := NewRequest(t, "GET", fmt.Sprintf("%s/actions/runs/latest", repo.HTMLURL())) + resp := MakeRequest(t, req, http.StatusTemporaryRedirect) + + // Verify that it redirects to the run we just created + expectedURI := fmt.Sprintf("%s/actions/runs/1", repo.HTMLURL()) + assert.Equal(t, expectedURI, resp.Header().Get("Location")) + }) +}