[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)
This commit is contained in:
Gergely Nagy 2023-12-08 13:41:48 +01:00 committed by Earl Warren
parent cd3e0a74e6
commit 9e5cce1afc
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
4 changed files with 130 additions and 11 deletions

View file

@ -308,6 +308,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

@ -1345,22 +1345,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_model.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"))
})
}