mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 14:31:02 +00:00 
			
		
		
		
	[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)
	
	
This commit is contained in:
		
					parent
					
						
							
								65baa64261
							
						
					
				
			
			
				commit
				
					
						d86d71340a
					
				
			
		
					 4 changed files with 130 additions and 11 deletions
				
			
		|  | @ -308,6 +308,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) | ||||
|  |  | |||
|  | @ -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"` | ||||
|  |  | |||
|  | @ -1333,22 +1333,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) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										91
									
								
								tests/integration/actions_route_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								tests/integration/actions_route_test.go
									
										
									
									
									
										Normal 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")) | ||||
| 	}) | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue