[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 <forgejo@gergo.csillger.hu>
(cherry picked from commit f67ccef1dd)

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 <forgejo@gergo.csillger.hu>
(cherry picked from commit 74e42da563)
(cherry picked from commit f7535a1cef)
(cherry picked from commit 1a90cd37c3)
(cherry picked from commit d86d71340a)
(cherry picked from commit 9e5cce1afc)
(cherry picked from commit 2013fb3fab)
(cherry picked from commit 88b9d21d11)
(cherry picked from commit 72c020298e)
(cherry picked from commit 6525f730df)
This commit is contained in:
Gergely Nagy 2023-12-08 13:41:48 +01:00 committed by Earl Warren
parent 533c87da65
commit fa0759962b
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
4 changed files with 130 additions and 11 deletions

View file

@ -312,6 +312,17 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return commiter.Commit() 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) { func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
var run ActionRun var run ActionRun
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run) has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)

View file

@ -46,6 +46,20 @@ func View(ctx *context_module.Context) {
ctx.HTML(http.StatusOK, tplViewActions) 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 { type ViewRequest struct {
LogCursors []struct { LogCursors []struct {
Step int `json:"step"` Step int `json:"step"`

View file

@ -1347,22 +1347,25 @@ func registerRoutes(m *web.Route) {
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
m.Group("/runs/{run}", func() { m.Group("/runs", func() {
m.Combo(""). m.Get("/latest", actions.ViewLatest)
Get(actions.View). m.Group("/{run}", func() {
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost)
m.Group("/jobs/{job}", func() {
m.Combo(""). m.Combo("").
Get(actions.View). Get(actions.View).
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) 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.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) }, reqRepoActionsReader, actions.MustEnableActions)

View file

@ -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"))
})
}