Replace index with id in actions routes (#36842)
This PR migrates the web Actions run/job routes from index-based `runIndex` or `jobIndex` to database IDs. **⚠️ BREAKING ⚠️**: Existing saved links/bookmarks that use the old index-based URLs will no longer resolve after this change. Improvements of this change: - Previously, `jobIndex` depended on list order, making it hard to locate a specific job. Using `jobID` provides stable addressing. - Web routes now align with API, which already use IDs. - Behavior is closer to GitHub, which exposes run/job IDs in URLs. - Provides a cleaner base for future features without relying on list order. - #36388 this PR improves the support for reusable workflows. If a job uses a reusable workflow, it may contain multiple child jobs, which makes relying on job index to locate a job much more complicated --------- Signed-off-by: Zettat123 <zettat123@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -38,11 +38,11 @@ import (
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
func getRunIndex(ctx *context_module.Context) int64 {
|
||||
// if run param is "latest", get the latest run index
|
||||
func getRunID(ctx *context_module.Context) int64 {
|
||||
// if run param is "latest", get the latest run id
|
||||
if ctx.PathParam("run") == "latest" {
|
||||
if run, _ := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID); run != nil {
|
||||
return run.Index
|
||||
return run.ID
|
||||
}
|
||||
}
|
||||
return ctx.PathParamInt64("run")
|
||||
@@ -50,24 +50,25 @@ func getRunIndex(ctx *context_module.Context) int64 {
|
||||
|
||||
func View(ctx *context_module.Context) {
|
||||
ctx.Data["PageIsActions"] = true
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndex := ctx.PathParamInt("job")
|
||||
ctx.Data["RunIndex"] = runIndex
|
||||
ctx.Data["JobIndex"] = jobIndex
|
||||
ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions"
|
||||
runID := getRunID(ctx)
|
||||
|
||||
if getRunJobs(ctx, runIndex, jobIndex); ctx.Written() {
|
||||
_, _, current := getRunJobsAndCurrentJob(ctx, runID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["RunID"] = runID
|
||||
ctx.Data["JobID"] = current.ID
|
||||
ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions"
|
||||
|
||||
ctx.HTML(http.StatusOK, tplViewActions)
|
||||
}
|
||||
|
||||
func ViewWorkflowFile(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
runID := getRunID(ctx)
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
|
||||
ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
return
|
||||
@@ -187,8 +188,8 @@ type ViewStepLogLine struct {
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artifactsViewItems []*ArtifactsViewItem, err error) {
|
||||
run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
|
||||
func getActionsViewArtifacts(ctx context.Context, repoID, runID int64) (artifactsViewItems []*ArtifactsViewItem, err error) {
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -208,14 +209,12 @@ func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artif
|
||||
|
||||
func ViewPost(ctx *context_module.Context) {
|
||||
req := web.GetForm(ctx).(*ViewRequest)
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndex := ctx.PathParamInt("job")
|
||||
runID := getRunID(ctx)
|
||||
|
||||
current, jobs := getRunJobs(ctx, runIndex, jobIndex)
|
||||
run, jobs, current := getRunJobsAndCurrentJob(ctx, runID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
run := current.Run
|
||||
if err := run.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("run.LoadAttributes", err)
|
||||
return
|
||||
@@ -223,7 +222,7 @@ func ViewPost(ctx *context_module.Context) {
|
||||
|
||||
var err error
|
||||
resp := &ViewResponse{}
|
||||
resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.ServerError("getActionsViewArtifacts", err)
|
||||
@@ -400,15 +399,12 @@ func convertToViewModel(ctx context.Context, locale translation.Locale, cursors
|
||||
}
|
||||
|
||||
// Rerun will rerun jobs in the given run
|
||||
// If jobIndexStr is a blank string, it means rerun all jobs
|
||||
// If jobIDStr is a blank string, it means rerun all jobs
|
||||
func Rerun(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndexHas := ctx.PathParam("job") != ""
|
||||
jobIndex := ctx.PathParamInt("job")
|
||||
runID := getRunID(ctx)
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRunByIndex", err)
|
||||
run, jobs, currentJob := getRunJobsAndCurrentJob(ctx, runID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -426,19 +422,9 @@ func Rerun(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRunJobsByRunID", err)
|
||||
return
|
||||
}
|
||||
|
||||
var targetJob *actions_model.ActionRunJob // nil means rerun all jobs
|
||||
if jobIndexHas {
|
||||
if jobIndex < 0 || jobIndex >= len(jobs) {
|
||||
ctx.JSONError(ctx.Locale.Tr("error.not_found"))
|
||||
return
|
||||
}
|
||||
targetJob = jobs[jobIndex] // only rerun the selected job
|
||||
if ctx.PathParam("job") != "" {
|
||||
targetJob = currentJob
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil {
|
||||
@@ -450,28 +436,28 @@ func Rerun(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
func Logs(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndex := ctx.PathParamInt64("job")
|
||||
runID := getRunID(ctx)
|
||||
jobID := ctx.PathParamInt64("job")
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
|
||||
ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = common.DownloadActionsRunJobLogsWithIndex(ctx.Base, ctx.Repo.Repository, run.ID, jobIndex); err != nil {
|
||||
ctx.NotFoundOrServerError("DownloadActionsRunJobLogsWithIndex", func(err error) bool {
|
||||
if err = common.DownloadActionsRunJobLogsWithID(ctx.Base, ctx.Repo.Repository, run.ID, jobID); err != nil {
|
||||
ctx.NotFoundOrServerError("DownloadActionsRunJobLogsWithID", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Cancel(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
runID := getRunID(ctx)
|
||||
|
||||
firstJob, jobs := getRunJobs(ctx, runIndex, -1)
|
||||
run, jobs, _ := getRunJobsAndCurrentJob(ctx, runID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -490,7 +476,7 @@ func Cancel(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
actions_service.CreateCommitStatusForRunJobs(ctx, firstJob.Run, jobs...)
|
||||
actions_service.CreateCommitStatusForRunJobs(ctx, run, jobs...)
|
||||
actions_service.EmitJobsIfReadyByJobs(updatedJobs)
|
||||
|
||||
for _, job := range updatedJobs {
|
||||
@@ -505,9 +491,9 @@ func Cancel(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
func Approve(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
runID := getRunID(ctx)
|
||||
|
||||
approveRuns(ctx, []int64{runIndex})
|
||||
approveRuns(ctx, []int64{runID})
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -515,17 +501,17 @@ func Approve(ctx *context_module.Context) {
|
||||
ctx.JSONOK()
|
||||
}
|
||||
|
||||
func approveRuns(ctx *context_module.Context, runIndexes []int64) {
|
||||
func approveRuns(ctx *context_module.Context, runIDs []int64) {
|
||||
doer := ctx.Doer
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
updatedJobs := make([]*actions_model.ActionRunJob, 0)
|
||||
runMap := make(map[int64]*actions_model.ActionRun, len(runIndexes))
|
||||
runJobs := make(map[int64][]*actions_model.ActionRunJob, len(runIndexes))
|
||||
runMap := make(map[int64]*actions_model.ActionRun, len(runIDs))
|
||||
runJobs := make(map[int64][]*actions_model.ActionRunJob, len(runIDs))
|
||||
|
||||
err := db.WithTx(ctx, func(ctx context.Context) (err error) {
|
||||
for _, runIndex := range runIndexes {
|
||||
run, err := actions_model.GetRunByIndex(ctx, repo.ID, runIndex)
|
||||
for _, runID := range runIDs {
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, repo.ID, runID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -560,7 +546,9 @@ func approveRuns(ctx *context_module.Context, runIndexes []int64) {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("UpdateRunJob", err)
|
||||
ctx.NotFoundOrServerError("approveRuns", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -580,16 +568,16 @@ func approveRuns(ctx *context_module.Context, runIndexes []int64) {
|
||||
}
|
||||
|
||||
func Delete(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
runID := getRunID(ctx)
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetRunByIndex", err)
|
||||
ctx.ServerError("GetRunByRepoAndID", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -606,47 +594,54 @@ func Delete(ctx *context_module.Context) {
|
||||
ctx.JSONOK()
|
||||
}
|
||||
|
||||
// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
|
||||
// Any error will be written to the ctx.
|
||||
// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
|
||||
func getRunJobs(ctx *context_module.Context, runIndex int64, jobIndex int) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) {
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
// getRunJobsAndCurrentJob loads the run and its jobs for runID, and returns the selected job based on the optional "job" path param (or the first job by default).
|
||||
// Any error will be written to the ctx, and nils are returned in that case.
|
||||
func getRunJobsAndCurrentJob(ctx *context_module.Context, runID int64) (*actions_model.ActionRun, []*actions_model.ActionRunJob, *actions_model.ActionRunJob) {
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.NotFound(nil)
|
||||
return nil, nil
|
||||
}
|
||||
ctx.ServerError("GetRunByIndex", err)
|
||||
return nil, nil
|
||||
ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
run.Repo = ctx.Repo.Repository
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRunJobsByRunID", err)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
if len(jobs) == 0 {
|
||||
ctx.NotFound(nil)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
for _, v := range jobs {
|
||||
v.Run = run
|
||||
for _, job := range jobs {
|
||||
job.Run = run
|
||||
}
|
||||
|
||||
if jobIndex >= 0 && jobIndex < len(jobs) {
|
||||
return jobs[jobIndex], jobs
|
||||
current := jobs[0]
|
||||
if ctx.PathParam("job") != "" {
|
||||
jobID := ctx.PathParamInt64("job")
|
||||
current, err = actions_model.GetRunJobByRunAndID(ctx, run.ID, jobID)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetRunJobByRunAndID", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
current.Run = run
|
||||
}
|
||||
return jobs[0], jobs
|
||||
|
||||
return run, jobs, current
|
||||
}
|
||||
|
||||
func ArtifactsDeleteView(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
runID := getRunID(ctx)
|
||||
artifactName := ctx.PathParam("artifact_name")
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
|
||||
ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool {
|
||||
return errors.Is(err, util.ErrNotExist)
|
||||
}, err)
|
||||
return
|
||||
@@ -659,16 +654,16 @@ func ArtifactsDeleteView(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
func ArtifactsDownloadView(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
runID := getRunID(ctx)
|
||||
artifactName := ctx.PathParam("artifact_name")
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.HTTPError(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetRunByIndex", err)
|
||||
ctx.ServerError("GetRunByRepoAndID", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -754,19 +749,19 @@ func ApproveAllChecks(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
runIndexes := make([]int64, 0, len(runs))
|
||||
runIDs := make([]int64, 0, len(runs))
|
||||
for _, run := range runs {
|
||||
if run.NeedApproval {
|
||||
runIndexes = append(runIndexes, run.Index)
|
||||
runIDs = append(runIDs, run.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(runIndexes) == 0 {
|
||||
if len(runIDs) == 0 {
|
||||
ctx.JSONOK()
|
||||
return
|
||||
}
|
||||
|
||||
approveRuns(ctx, runIndexes)
|
||||
approveRuns(ctx, runIDs)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user