Skip to content

Commit 5f2d8be

Browse files
committed
[FEAT]: New route to view lates run of specific workflows
This adds a new route at `/actions/workflows/{workflow}/runs/latest`, which will redirect to the latest run of the given workflow. It can be further restricted by specifying an optional `?branch={branch}` query parameter. If no branch is specified, the route defaults to using the repo's default branch. This route is meant to go hand in hand with the Badge route that returns the result of the same workflow as a badge. This route can be used to link to the run that produced that result. Fixes go-gitea#2303. Signed-off-by: Gergely Nagy <[email protected]>
1 parent cf1c57b commit 5f2d8be

File tree

3 files changed

+112
-3
lines changed

3 files changed

+112
-3
lines changed

routers/web/repo/actions/view.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"code.gitea.io/gitea/modules/actions"
2323
"code.gitea.io/gitea/modules/base"
2424
context_module "code.gitea.io/gitea/modules/context"
25+
"code.gitea.io/gitea/modules/log"
2526
"code.gitea.io/gitea/modules/storage"
2627
"code.gitea.io/gitea/modules/timeutil"
2728
"code.gitea.io/gitea/modules/util"
@@ -60,6 +61,34 @@ func ViewLatest(ctx *context_module.Context) {
6061
ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect)
6162
}
6263

64+
func ViewLatestWorkflowRun(ctx *context_module.Context) {
65+
branch := ctx.FormString("branch")
66+
if branch == "" {
67+
branch = ctx.Repo.Repository.DefaultBranch
68+
}
69+
branch = fmt.Sprintf("refs/heads/%s", branch)
70+
event := ctx.FormString("event")
71+
72+
workflowFile := ctx.Params("workflow_name")
73+
run, err := actions_model.GetLatestRunForBranchAndWorkflow(ctx, ctx.Repo.Repository.ID, branch, workflowFile, event)
74+
if err != nil {
75+
if errors.Is(err, util.ErrNotExist) {
76+
ctx.NotFound("GetLatestRunForBranchAndWorkflow", err)
77+
} else {
78+
log.Error("GetLatestRunForBranchAndWorkflow: %v", err)
79+
ctx.Error(http.StatusInternalServerError, "Unable to get latest run for workflow on branch")
80+
}
81+
return
82+
}
83+
84+
err = run.LoadAttributes(ctx)
85+
if err != nil {
86+
ctx.ServerError("LoadAttributes", err)
87+
return
88+
}
89+
ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect)
90+
}
91+
6392
type ViewRequest struct {
6493
LogCursors []struct {
6594
Step int `json:"step"`

routers/web/web.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,10 @@ func registerRoutes(m *web.Route) {
14031403
})
14041404
})
14051405

1406-
m.Get("/workflows/{workflow_name}/badge.svg", badges.GetWorkflowBadge)
1406+
m.Group("/workflows/{workflow_name}", func() {
1407+
m.Get("/badge.svg", badges.GetWorkflowBadge)
1408+
m.Get("/runs/latest", actions.ViewLatestWorkflowRun)
1409+
})
14071410
}, reqRepoActionsReader, actions.MustEnableActions)
14081411

14091412
m.Group("/wiki", func() {

tests/integration/actions_route_test.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
23
// SPDX-License-Identifier: MIT
34

45
package integration
56

67
import (
8+
"context"
79
"fmt"
810
"net/http"
911
"net/url"
@@ -15,10 +17,82 @@ import (
1517
"code.gitea.io/gitea/models/unittest"
1618
user_model "code.gitea.io/gitea/models/user"
1719
files_service "code.gitea.io/gitea/services/repository/files"
20+
"code.gitea.io/gitea/tests"
1821

1922
"github.com/stretchr/testify/assert"
2023
)
2124

25+
func TestActionsWebRouteLatestWorkflowRun(t *testing.T) {
26+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
27+
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
28+
29+
// create the repo
30+
repo, _, f := CreateDeclarativeRepo(t, user2, "",
31+
[]unit_model.Type{unit_model.TypeActions}, nil,
32+
[]*files_service.ChangeRepoFile{
33+
{
34+
Operation: "create",
35+
TreePath: ".gitea/workflows/workflow-1.yml",
36+
ContentReader: strings.NewReader("name: workflow-1\non:\n push:\njobs:\n job-1:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"),
37+
},
38+
{
39+
Operation: "create",
40+
TreePath: ".gitea/workflows/workflow-2.yml",
41+
ContentReader: strings.NewReader("name: workflow-2\non:\n push:\njobs:\n job-2:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"),
42+
},
43+
},
44+
)
45+
defer f()
46+
47+
t.Run("valid workflows", func(t *testing.T) {
48+
defer tests.PrintCurrentTest(t)()
49+
50+
// helpers
51+
getWorkflowRunRedirectURI := func(workflow string) string {
52+
req := NewRequest(t, "GET", fmt.Sprintf("%s/actions/workflows/%s/runs/latest", repo.HTMLURL(), workflow))
53+
resp := MakeRequest(t, req, http.StatusTemporaryRedirect)
54+
55+
return resp.Header().Get("Location")
56+
}
57+
58+
// two runs have been created
59+
assert.Equal(t, 2, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
60+
61+
// Get the redirect URIs for both workflows
62+
workflowOneURI := getWorkflowRunRedirectURI("workflow-1.yml")
63+
workflowTwoURI := getWorkflowRunRedirectURI("workflow-2.yml")
64+
65+
// Verify that the two are different.
66+
assert.NotEqual(t, workflowOneURI, workflowTwoURI)
67+
68+
// Verify that each points to the correct workflow.
69+
workflowOne := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID, Index: 1})
70+
err := workflowOne.LoadAttributes(context.Background())
71+
assert.NoError(t, err)
72+
assert.Equal(t, workflowOneURI, workflowOne.HTMLURL())
73+
74+
workflowTwo := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID, Index: 2})
75+
err = workflowTwo.LoadAttributes(context.Background())
76+
assert.NoError(t, err)
77+
assert.Equal(t, workflowTwoURI, workflowTwo.HTMLURL())
78+
})
79+
80+
t.Run("existing workflow, non-existent branch", func(t *testing.T) {
81+
defer tests.PrintCurrentTest(t)()
82+
83+
req := NewRequest(t, "GET", fmt.Sprintf("%s/actions/workflows/workflow-1.yml/runs/latest?branch=foobar", repo.HTMLURL()))
84+
MakeRequest(t, req, http.StatusNotFound)
85+
})
86+
87+
t.Run("non-existing workflow", func(t *testing.T) {
88+
defer tests.PrintCurrentTest(t)()
89+
90+
req := NewRequest(t, "GET", fmt.Sprintf("%s/actions/workflows/workflow-3.yml/runs/latest", repo.HTMLURL()))
91+
MakeRequest(t, req, http.StatusNotFound)
92+
})
93+
})
94+
}
95+
2296
func TestActionsWebRouteLatestRun(t *testing.T) {
2397
onGiteaRun(t, func(t *testing.T, u *url.URL) {
2498
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
@@ -44,7 +118,10 @@ func TestActionsWebRouteLatestRun(t *testing.T) {
44118
resp := MakeRequest(t, req, http.StatusTemporaryRedirect)
45119

46120
// Verify that it redirects to the run we just created
47-
expectedURI := fmt.Sprintf("%s/actions/runs/1", repo.HTMLURL())
48-
assert.Equal(t, expectedURI, resp.Header().Get("Location"))
121+
workflow := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID})
122+
err := workflow.LoadAttributes(context.Background())
123+
assert.NoError(t, err)
124+
125+
assert.Equal(t, workflow.HTMLURL(), resp.Header().Get("Location"))
49126
})
50127
}

0 commit comments

Comments
 (0)