diff --git a/models/actions/run.go b/models/actions/run.go index 88e8908fed..6e01715f74 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -104,6 +104,13 @@ func (run *ActionRun) Link() string { return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) } +func (run *ActionRun) CommitLink() string { + if run.Repo == nil { + return "" + } + return fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA) +} + // WorkflowPath returns the path in the git repo to the workflow file that this run was based on func (run *ActionRun) WorkflowPath() string { if run.WorkflowDirectory == "" { @@ -240,6 +247,14 @@ func (run *ActionRun) FindOuterWorkflowCall(ctx context.Context, innerCall *Acti return nil, fmt.Errorf("no workflow call with ID %s found in run %d", parent, run.ID) } +func (run *ActionRun) IsScheduledRun() bool { + return run.TriggerEvent == "schedule" +} + +func (run *ActionRun) IsDispatchedRun() bool { + return run.TriggerEvent == "workflow_dispatch" +} + func actionsCountOpenCacheKey(repoID int64) string { return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID) } diff --git a/models/actions/run_test.go b/models/actions/run_test.go index 41703f91e1..541fb059cd 100644 --- a/models/actions/run_test.go +++ b/models/actions/run_test.go @@ -11,6 +11,8 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unittest" "forgejo.org/modules/cache" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "code.forgejo.org/forgejo/runner/v12/act/jobparser" "github.com/stretchr/testify/assert" @@ -53,6 +55,47 @@ func TestGetWorkflowPath(t *testing.T) { assert.Equal(t, ".some/path/to/workflows/ci.yml", run.WorkflowPath()) } +func TestGetCommitLink(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + defer test.MockVariableValue(&setting.AppSubURL, "/sub")() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + run := ActionRun{ + Repo: repo, + CommitSHA: "a356d1f1f82945a039cd16d4ce0137bd55284e77", + } + assert.Equal(t, "/sub/user2/repo1/commit/a356d1f1f82945a039cd16d4ce0137bd55284e77", run.CommitLink()) +} + +func TestIsScheduledRun(t *testing.T) { + scheduledRun := ActionRun{ + CommitSHA: "a356d1f1f82945a039cd16d4ce0137bd55284e77", + TriggerEvent: "schedule", + } + pushRun := ActionRun{ + CommitSHA: "8f9b5c6ab342eb11d7422deecef7195b18058b90", + TriggerEvent: "push", + } + + assert.True(t, scheduledRun.IsScheduledRun()) + assert.False(t, pushRun.IsScheduledRun()) +} + +func TestIsManualRun(t *testing.T) { + manualRunRun := ActionRun{ + CommitSHA: "a356d1f1f82945a039cd16d4ce0137bd55284e77", + TriggerEvent: "workflow_dispatch", + } + pushRun := ActionRun{ + CommitSHA: "8f9b5c6ab342eb11d7422deecef7195b18058b90", + TriggerEvent: "push", + } + + assert.True(t, manualRunRun.IsDispatchedRun()) + assert.False(t, pushRun.IsDispatchedRun()) +} + func TestRepoNumOpenActions(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) err := cache.Init() diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 6a7630548a..99c9446805 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -5,6 +5,7 @@ package actions import ( "bytes" + "fmt" "io" "strings" @@ -30,6 +31,12 @@ type DetectedWorkflow struct { NeedApproval actions_model.ApprovalType } +// GetWorkflowPath returns the full path to the workflow from the repository root, for example, +// .forgejo/workflows/test.yaml. +func (wf *DetectedWorkflow) GetWorkflowPath() string { + return fmt.Sprintf("%s/%s", wf.EntryDirectory, wf.EntryName) +} + func init() { model.OnDecodeNodeError = func(node yaml.Node, out any, err error) { // Log the error instead of panic or fatal. diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index b431989def..911d72a09a 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -19,6 +19,14 @@ import ( "github.com/stretchr/testify/require" ) +func TestDetectedWorkflowGetWorkflowPath(t *testing.T) { + buildWorkflow := DetectedWorkflow{EntryDirectory: ".github/workflows", EntryName: "build.yaml"} + testWorkflow := DetectedWorkflow{EntryDirectory: ".forgejo/workflows", EntryName: "test.yaml"} + + assert.Equal(t, ".github/workflows/build.yaml", buildWorkflow.GetWorkflowPath()) + assert.Equal(t, ".forgejo/workflows/test.yaml", testWorkflow.GetWorkflowPath()) +} + func TestActionsWorkflowsDetectMatched(t *testing.T) { testCases := []struct { desc string diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index 67767b1985..59edecac03 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -513,9 +513,9 @@ "actions.runs.viewing_out_of_date_run": "You are viewing an out-of-date run of this job that was executed %[1]s.", "actions.runs.view_most_recent_run": "View most recent run", "actions.runs.all_workflows": "All workflows", - "actions.runs.commit": "Commit", - "actions.runs.scheduled": "Scheduled", - "actions.runs.pushed_by": "pushed by", + "actions.runs.scheduled_description": "Scheduled run of commit %[2]s", + "actions.runs.workflow_dispatch_description": "Run of commit %[2]s triggered by %[4]s", + "actions.runs.on_push_description": "Commit %[2]s pushed by %[4]s", "actions.runs.workflow": "Workflow", "actions.runs.invalid_workflow_helper": "Workflow config file is invalid. Please check your config file: %s", "actions.runs.no_matching_online_runner.helper": "No matching online runner with label: %s", diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 8deb733e16..c775de7ef5 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -173,6 +173,7 @@ type ViewRunInfo struct { Title string `json:"title"` TitleHTML template.HTML `json:"titleHTML"` Status string `json:"status"` + Description string `json:"description"` CanCancel bool `json:"canCancel"` CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve CanRerun bool `json:"canRerun"` @@ -203,8 +204,6 @@ type ViewJob struct { } type ViewCommit struct { - LocaleCommit string `json:"localeCommit"` - LocalePushedBy string `json:"localePushedBy"` LocaleWorkflow string `json:"localeWorkflow"` LocaleAllRuns string `json:"localeAllRuns"` ShortSha string `json:"shortSHA"` @@ -281,6 +280,18 @@ func getViewResponse(ctx *app_context.Context, req *ViewRequest, runIndex, jobIn metas := ctx.Repo.Repository.ComposeMetas(ctx) + var runDescription string + if run.IsScheduledRun() { + runDescription = ctx.Locale.TrString("actions.runs.scheduled_description", run.CommitLink(), + base.ShortSha(run.CommitSHA)) + } else if run.IsDispatchedRun() { + runDescription = ctx.Locale.TrString("actions.runs.workflow_dispatch_description", run.CommitLink(), + base.ShortSha(run.CommitSHA), run.TriggerUser.HomeLink(), run.TriggerUser.GetDisplayName()) + } else { + runDescription = ctx.Locale.TrString("actions.runs.on_push_description", run.CommitLink(), + base.ShortSha(run.CommitSHA), run.TriggerUser.HomeLink(), run.TriggerUser.GetDisplayName()) + } + resp.State.Run.Title = run.Title resp.State.Run.TitleHTML = templates.RenderCommitMessage(ctx, run.Title, metas) resp.State.Run.Link = run.Link() @@ -290,6 +301,7 @@ func getViewResponse(ctx *app_context.Context, req *ViewRequest, runIndex, jobIn resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead of 'null' in json resp.State.Run.Status = run.Status.String() resp.State.Run.PreExecutionError = actions_model.TranslatePreExecutionError(ctx.Locale, run) + resp.State.Run.Description = runDescription // It's possible for the run to be marked with a finalized status (eg. failure) because of a single job within the // run; eg. one job fails, the run fails. But other jobs can still be running. The frontend RepoActionView uses the @@ -332,8 +344,6 @@ func getViewResponse(ctx *app_context.Context, req *ViewRequest, runIndex, jobIn } resp.State.Run.Commit = ViewCommit{ - LocaleCommit: ctx.Locale.TrString("actions.runs.commit"), - LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"), LocaleWorkflow: ctx.Locale.TrString("actions.runs.workflow"), LocaleAllRuns: ctx.Locale.TrString("actions.runs.all_runs_link"), ShortSha: base.ShortSha(run.CommitSHA), diff --git a/routers/web/repo/actions/view_test.go b/routers/web/repo/actions/view_test.go index 06af165c5c..8d8e82a007 100644 --- a/routers/web/repo/actions/view_test.go +++ b/routers/web/repo/actions/view_test.go @@ -150,6 +150,7 @@ func baseExpectedViewResponse() *ViewResponse { Title: "update actions", TitleHTML: template.HTML("update actions"), Status: "success", + Description: "actions.runs.on_push_description", CanCancel: false, CanApprove: false, CanRerun: false, @@ -165,8 +166,6 @@ func baseExpectedViewResponse() *ViewResponse { }, }, Commit: ViewCommit{ - LocaleCommit: "actions.runs.commit", - LocalePushedBy: "actions.runs.pushed_by", LocaleWorkflow: "actions.runs.workflow", LocaleAllRuns: "actions.runs.all_runs_link", ShortSha: "c2d72f5484", diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 184092e68e..c6af9b5b82 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -574,8 +574,13 @@ func handleSchedules( continue } + title := workflow.Name + if len(title) < 1 { + title = dwf.GetWorkflowPath() + } + run := &actions_model.ActionSchedule{ - Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0], + Title: title, RepoID: input.Repo.ID, OwnerID: input.Repo.OwnerID, WorkflowID: dwf.EntryName, diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl index 3858ae0849..642851309a 100644 --- a/templates/repo/actions/runs_list.tmpl +++ b/templates/repo/actions/runs_list.tmpl @@ -16,14 +16,13 @@
{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}} - - {{if .ScheduleID -}} - {{ctx.Locale.Tr "actions.runs.scheduled"}} - {{- else -}} - {{ctx.Locale.Tr "actions.runs.commit"}} - {{ShortSha .CommitSHA}} - {{ctx.Locale.Tr "actions.runs.pushed_by"}} - {{.TriggerUser.GetDisplayName}} - {{- end -}} + {{if .IsScheduledRun -}} + {{ctx.Locale.Tr "actions.runs.scheduled_description" .CommitLink (ShortSha .CommitSHA)}} + {{else if .IsDispatchedRun -}} + {{ctx.Locale.Tr "actions.runs.workflow_dispatch_description" .CommitLink (ShortSha .CommitSHA) .TriggerUser.HomeLink .TriggerUser.GetDisplayName}} + {{else -}} + {{ctx.Locale.Tr "actions.runs.on_push_description" .CommitLink (ShortSha .CommitSHA) .TriggerUser.HomeLink .TriggerUser.GetDisplayName}} + {{end -}}
diff --git a/tests/integration/actions_runs_list_test.go b/tests/integration/actions_runs_list_test.go index 790e19d71a..e8a33f1a38 100644 --- a/tests/integration/actions_runs_list_test.go +++ b/tests/integration/actions_runs_list_test.go @@ -5,14 +5,17 @@ package integration import ( "net/http" + "regexp" "testing" + "forgejo.org/models/unittest" "forgejo.org/tests" "github.com/stretchr/testify/assert" ) func TestActionRunsList(t *testing.T) { + defer unittest.OverrideFixtures("tests/integration/fixtures/TestActionRunsList")() defer tests.PrepareTestEnv(t)() req := NewRequest(t, "GET", "/user5/repo4/actions") @@ -20,7 +23,28 @@ func TestActionRunsList(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) - runSubLine := htmlDoc.Find(".run-list .flex-item-body").Text() - assert.Contains(t, runSubLine, "Commit") - assert.NotContains(t, runSubLine, "-Commit") + runDescriptions := htmlDoc.Find(".run-list .flex-item-body") + + allWhitespacePattern := regexp.MustCompile(`\s+`) + + assert.Equal(t, 8, runDescriptions.Length()) + + assert.Contains(t, allWhitespacePattern.ReplaceAllString(runDescriptions.Eq(0).Text(), " "), + "dispatch.yaml #210 - Run of commit dc67f0a1a0 triggered by user29") + assert.Equal(t, "/user5/repo4/commit/dc67f0a1a0dc2417cd0b1a9f0b95e2e5d91876e9", + runDescriptions.Eq(0).Find("a").Eq(0).AttrOr("href", "")) + assert.Equal(t, "/user29", + runDescriptions.Eq(0).Find("a").Eq(1).AttrOr("href", "")) + + assert.Contains(t, allWhitespacePattern.ReplaceAllString(runDescriptions.Eq(1).Text(), " "), + "scheduled.yaml #209 - Scheduled run of commit 64357baca8") + assert.Equal(t, "/user5/repo4/commit/64357baca84bfff631e7dfae5a3433b26d005646", + runDescriptions.Eq(1).Find("a").Eq(0).AttrOr("href", "")) + + assert.Contains(t, allWhitespacePattern.ReplaceAllString(runDescriptions.Eq(2).Text(), " "), + "test.yaml #192 - Commit c2d72f5484 pushed by user1") + assert.Equal(t, "/user5/repo4/commit/c2d72f548424103f01ee1dc02889c1e2bff816b0", + runDescriptions.Eq(2).Find("a").Eq(0).AttrOr("href", "")) + assert.Equal(t, "/user1", + runDescriptions.Eq(2).Find("a").Eq(1).AttrOr("href", "")) } diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 2b770c95a1..925434204c 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -1134,3 +1134,83 @@ func TestActionsWorkflowDispatchConcurrencyGroup(t *testing.T) { assert.Equal(t, actions_model.StatusCancelled, firstRunReload.Status) }) } + +func TestActionsScheduledWorkflow(t *testing.T) { + testCases := []struct { + name string + workflowID string + workflowDirectory string + workflowContent string + expectedWorkflowTitle string + expectedCronSpecs []string + }{ + { + name: "GitHub", + workflowID: "scheduled.yml", + workflowDirectory: ".github/workflows", + workflowContent: ` +on: + schedule: + - cron: "30 5,17 * * *" +jobs: + test: + steps: + - run: echo OK +`, + expectedWorkflowTitle: ".github/workflows/scheduled.yml", + expectedCronSpecs: []string{"30 5,17 * * *"}, + }, + { + name: "Gitea", + workflowID: "test.yml", + workflowDirectory: ".gitea/workflows", + workflowContent: ` +name: My scheduled workflow +on: + schedule: + - cron: "* * * * *" +jobs: + test: + steps: + - run: echo OK +`, + expectedWorkflowTitle: "My scheduled workflow", + expectedCronSpecs: []string{"* * * * *"}, + }, + } + onApplicationRun(t, func(t *testing.T, u *url.URL) { + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, sha, f := tests.CreateDeclarativeRepo(t, user2, "repo-workflow-dispatch", + []unit_model.Type{unit_model.TypeActions}, nil, + []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: fmt.Sprintf("%s/%s", testCase.workflowDirectory, testCase.workflowID), + ContentReader: strings.NewReader(testCase.workflowContent), + }, + }, + ) + defer f() + + schedules, err := db.Find[actions_model.ActionSchedule](t.Context(), actions_model.FindScheduleOptions{RepoID: repo.ID}) + require.NoError(t, err) + require.Len(t, schedules, 1) + + assert.Equal(t, testCase.expectedWorkflowTitle, schedules[0].Title) + assert.Equal(t, testCase.expectedCronSpecs, schedules[0].Specs) + assert.Equal(t, repo.ID, schedules[0].RepoID) + assert.Equal(t, repo.OwnerID, schedules[0].OwnerID) + assert.Equal(t, testCase.workflowID, schedules[0].WorkflowID) + assert.Equal(t, testCase.workflowDirectory, schedules[0].WorkflowDirectory) + assert.Equal(t, int64(-2), schedules[0].TriggerUserID) + assert.Equal(t, sha, schedules[0].CommitSHA) + assert.Equal(t, webhook_module.HookEventPush, schedules[0].Event) + assert.Equal(t, []byte(testCase.workflowContent), schedules[0].Content) + }) + } + }) +} diff --git a/tests/integration/actions_view_test.go b/tests/integration/actions_view_test.go index 6314ce1e4f..5275198eff 100644 --- a/tests/integration/actions_view_test.go +++ b/tests/integration/actions_view_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "regexp" + "strconv" "strings" "testing" @@ -128,34 +129,77 @@ func TestActionViewsArtifactDownload(t *testing.T) { } func TestActionViewsView(t *testing.T) { + defer unittest.OverrideFixtures("tests/integration/fixtures/TestActionViewsView")() defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/user5/repo4/actions/runs/187") - intermediateRedirect := MakeRequest(t, req, http.StatusTemporaryRedirect) + testCases := []struct { + name string + url string + runIndex int64 + jobIndex int64 + attempt int64 + expectedJSON string + expectedArtifacts string + }{ + { + name: "push", + url: "/user5/repo4/actions/runs/187", + runIndex: 187, + jobIndex: 0, + attempt: 1, + expectedJSON: "{\"state\":{\"run\":{\"preExecutionError\":\"\",\"link\":\"/user5/repo4/actions/runs/187\",\"title\":\"update actions\",\"titleHTML\":\"update actions\",\"status\":\"success\",\"canCancel\":false,\"canApprove\":false,\"canRerun\":false,\"canDeleteArtifact\":false,\"description\":\"Commit c2d72f5484 pushed by user1\",\"done\":true,\"jobs\":[{\"id\":192,\"name\":\"job_2\",\"status\":\"success\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeWorkflow\":\"Workflow\",\"localeAllRuns\":\"all runs\",\"shortSHA\":\"c2d72f5484\",\"link\":\"/user5/repo4/commit/c2d72f548424103f01ee1dc02889c1e2bff816b0\",\"pusher\":{\"displayName\":\"user1\",\"link\":\"/user1\"},\"branch\":{\"name\":\"master\",\"link\":\"/user5/repo4/src/branch/master\",\"isDeleted\":false}}},\"currentJob\":{\"title\":\"job_2\",\"details\":[\"Success\"],\"steps\":[{\"summary\":\"Set up job\",\"duration\":\"_duration_\",\"status\":\"success\"},{\"summary\":\"Complete job\",\"duration\":\"_duration_\",\"status\":\"success\"}],\"allAttempts\":[{\"number\":3,\"time_since_started_html\":\"_time_\",\"status\":\"running\",\"status_diagnostics\":[\"Running\"]},{\"number\":2,\"time_since_started_html\":\"_time_\",\"status\":\"success\",\"status_diagnostics\":[\"Success\"]},{\"number\":1,\"time_since_started_html\":\"_time_\",\"status\":\"success\",\"status_diagnostics\":[\"Success\"]}]}},\"logs\":{\"stepsLog\":[]}}\n", + expectedArtifacts: "{\"artifacts\":[{\"name\":\"multi-file-download\",\"size\":2048,\"status\":\"completed\"}]}\n", + }, + { + name: "scheduled", + url: "/user5/repo4/actions/runs/209", + runIndex: 209, + jobIndex: 0, + attempt: 1, + expectedJSON: "{\"state\":{\"run\":{\"link\":\"/user5/repo4/actions/runs/209\",\"title\":\"A scheduled workflow\",\"titleHTML\":\"A scheduled workflow\",\"status\":\"waiting\",\"description\":\"Scheduled run of commit \\u003ca href=\\\"/user5/repo4/commit/64357baca84bfff631e7dfae5a3433b26d005646\\\"\\u003e64357baca8\\u003c/a\\u003e\",\"canCancel\":false,\"canApprove\":false,\"canRerun\":false,\"canDeleteArtifact\":false,\"done\":false,\"jobs\":[{\"id\":2153,\"name\":\"job_2\",\"status\":\"waiting\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeWorkflow\":\"Workflow\",\"localeAllRuns\":\"all runs\",\"shortSHA\":\"64357baca8\",\"link\":\"/user5/repo4/commit/64357baca84bfff631e7dfae5a3433b26d005646\",\"pusher\":{\"displayName\":\"forgejo-actions\",\"link\":\"/forgejo-actions\"},\"branch\":{\"name\":\"master\",\"link\":\"/user5/repo4/src/branch/master\",\"isDeleted\":false}},\"preExecutionError\":\"\"},\"currentJob\":{\"title\":\"job_2\",\"details\":[\"Waiting for a runner with the following labels: debian, gpu\"],\"steps\":[{\"summary\":\"Set up job\",\"duration\":\"_duration_\",\"status\":\"success\"},{\"summary\":\"Complete job\",\"duration\":\"_duration_\",\"status\":\"success\"}],\"allAttempts\":[{\"number\":1,\"time_since_started_html\":\"-\",\"status\":\"success\",\"status_diagnostics\":[\"Success\"]}]}},\"logs\":{\"stepsLog\":[]}}\n", + expectedArtifacts: "{\"artifacts\":[]}\n", + }, + { + name: "workflow_dispatch", + url: "/user5/repo4/actions/runs/210", + runIndex: 210, + jobIndex: 0, + attempt: 1, + expectedJSON: "{\"state\":{\"run\":{\"link\":\"/user5/repo4/actions/runs/210\",\"title\":\"A triggered run\",\"titleHTML\":\"A triggered run\",\"status\":\"waiting\",\"description\":\"Run of commit \\u003ca href=\\\"/user5/repo4/commit/f4100ac14112a3740490afb22b07b69b0b5d4e8b\\\"\\u003ef4100ac141\\u003c/a\\u003e triggered by \\u003ca href=\\\"/user29\\\"\\u003euser29\\u003c/a\\u003e\",\"canCancel\":false,\"canApprove\":false,\"canRerun\":false,\"canDeleteArtifact\":false,\"done\":false,\"jobs\":[{\"id\":2154,\"name\":\"mirror\",\"status\":\"waiting\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeWorkflow\":\"Workflow\",\"localeAllRuns\":\"all runs\",\"shortSHA\":\"f4100ac141\",\"link\":\"/user5/repo4/commit/f4100ac14112a3740490afb22b07b69b0b5d4e8b\",\"pusher\":{\"displayName\":\"user29\",\"link\":\"/user29\"},\"branch\":{\"name\":\"master\",\"link\":\"/user5/repo4/src/branch/master\",\"isDeleted\":false}},\"preExecutionError\":\"\"},\"currentJob\":{\"title\":\"mirror\",\"details\":[\"Waiting for a runner with the following label: windows\"],\"steps\":[{\"summary\":\"Set up job\",\"duration\":\"_duration_\",\"status\":\"running\"},{\"summary\":\"Complete job\",\"duration\":\"_duration_\",\"status\":\"waiting\"}],\"allAttempts\":[{\"number\":1,\"time_since_started_html\":\"-\",\"status\":\"waiting\",\"status_diagnostics\":[\"Waiting for a runner with the following label: windows\"]}]}},\"logs\":{\"stepsLog\":[]}}\n", + expectedArtifacts: "{\"artifacts\":[]}\n", + }, + } - finalURL := intermediateRedirect.Result().Header.Get("Location") - req = NewRequest(t, "GET", finalURL) - resp := MakeRequest(t, req, http.StatusOK) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + req := NewRequest(t, "GET", testCase.url) + intermediateRedirect := MakeRequest(t, req, http.StatusTemporaryRedirect) - htmlDoc := NewHTMLParser(t, resp.Body) - selector := "#repo-action-view" - // Verify key properties going into the `repo-action-view` to initialize the Vue component. - htmlDoc.AssertAttrEqual(t, selector, "data-run-index", "187") - htmlDoc.AssertAttrEqual(t, selector, "data-job-index", "0") - htmlDoc.AssertAttrEqual(t, selector, "data-attempt-number", "1") - htmlDoc.AssertAttrPredicate(t, selector, "data-initial-post-response", func(actual string) bool { - // Remove dynamic "duration" fields for comparison. - pattern := `"duration":"[^"]*"` - re := regexp.MustCompile(pattern) - actualClean := re.ReplaceAllString(actual, `"duration":"_duration_"`) - // Remove "time_since_started_html" fields for comparison since they're TZ-sensitive in the test - pattern = `"time_since_started_html":".*?\\u003c/relative-time\\u003e"` - re = regexp.MustCompile(pattern) - actualClean = re.ReplaceAllString(actualClean, `"time_since_started_html":"_time_"`) + finalURL := intermediateRedirect.Result().Header.Get("Location") + req = NewRequest(t, "GET", finalURL) + resp := MakeRequest(t, req, http.StatusOK) - return assert.JSONEq(t, "{\"state\":{\"run\":{\"preExecutionError\":\"\",\"link\":\"/user5/repo4/actions/runs/187\",\"title\":\"update actions\",\"titleHTML\":\"update actions\",\"status\":\"success\",\"canCancel\":false,\"canApprove\":false,\"canRerun\":false,\"canDeleteArtifact\":false,\"done\":true,\"jobs\":[{\"id\":192,\"name\":\"job_2\",\"status\":\"success\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeCommit\":\"Commit\",\"localePushedBy\":\"pushed by\",\"localeWorkflow\":\"Workflow\",\"localeAllRuns\":\"all runs\",\"shortSHA\":\"c2d72f5484\",\"link\":\"/user5/repo4/commit/c2d72f548424103f01ee1dc02889c1e2bff816b0\",\"pusher\":{\"displayName\":\"user1\",\"link\":\"/user1\"},\"branch\":{\"name\":\"master\",\"link\":\"/user5/repo4/src/branch/master\",\"isDeleted\":false}}},\"currentJob\":{\"title\":\"job_2\",\"details\":[\"Success\"],\"steps\":[{\"summary\":\"Set up job\",\"duration\":\"_duration_\",\"status\":\"success\"},{\"summary\":\"Complete job\",\"duration\":\"_duration_\",\"status\":\"success\"}],\"allAttempts\":[{\"number\":3,\"time_since_started_html\":\"_time_\",\"status\":\"running\",\"status_diagnostics\":[\"Running\"]},{\"number\":2,\"time_since_started_html\":\"_time_\",\"status\":\"success\",\"status_diagnostics\":[\"Success\"]},{\"number\":1,\"time_since_started_html\":\"_time_\",\"status\":\"success\",\"status_diagnostics\":[\"Success\"]}]}},\"logs\":{\"stepsLog\":[]}}\n", actualClean) - }) - htmlDoc.AssertAttrEqual(t, selector, "data-initial-artifacts-response", "{\"artifacts\":[{\"name\":\"multi-file-download\",\"size\":2048,\"status\":\"completed\"}]}\n") + htmlDoc := NewHTMLParser(t, resp.Body) + selector := "#repo-action-view" + // Verify key properties going into the `repo-action-view` to initialize the Vue component. + htmlDoc.AssertAttrEqual(t, selector, "data-run-index", strconv.FormatInt(testCase.runIndex, 10)) + htmlDoc.AssertAttrEqual(t, selector, "data-job-index", strconv.FormatInt(testCase.jobIndex, 10)) + htmlDoc.AssertAttrEqual(t, selector, "data-attempt-number", strconv.FormatInt(testCase.attempt, 10)) + htmlDoc.AssertAttrPredicate(t, selector, "data-initial-post-response", func(actual string) bool { + // Remove dynamic "duration" fields for comparison. + pattern := `"duration":"[^"]*"` + re := regexp.MustCompile(pattern) + actualClean := re.ReplaceAllString(actual, `"duration":"_duration_"`) + // Remove "time_since_started_html" fields for comparison since they're TZ-sensitive in the test + pattern = `"time_since_started_html":".*?\\u003c/relative-time\\u003e"` + re = regexp.MustCompile(pattern) + actualClean = re.ReplaceAllString(actualClean, `"time_since_started_html":"_time_"`) + + return assert.JSONEq(t, testCase.expectedJSON, actualClean) + }) + htmlDoc.AssertAttrEqual(t, selector, "data-initial-artifacts-response", testCase.expectedArtifacts) + }) + } } // Action re-run will redirect the user to an attempt that may not exist in the database yet, since attempts are only @@ -185,7 +229,7 @@ func TestActionViewsViewAttemptOutOfRange(t *testing.T) { re = regexp.MustCompile(pattern) actualClean = re.ReplaceAllString(actualClean, `"time_since_started_html":"_time_"`) - return assert.JSONEq(t, "{\"state\":{\"run\":{\"preExecutionError\":\"\",\"link\":\"/user5/repo4/actions/runs/190\",\"title\":\"job output\",\"titleHTML\":\"job output\",\"status\":\"success\",\"canCancel\":false,\"canApprove\":false,\"canRerun\":false,\"canDeleteArtifact\":false,\"done\":false,\"jobs\":[{\"id\":396,\"name\":\"job_2\",\"status\":\"waiting\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeCommit\":\"Commit\",\"localePushedBy\":\"pushed by\",\"localeWorkflow\":\"Workflow\",\"localeAllRuns\":\"all runs\",\"shortSHA\":\"c2d72f5484\",\"link\":\"/user5/repo4/commit/c2d72f548424103f01ee1dc02889c1e2bff816b0\",\"pusher\":{\"displayName\":\"user1\",\"link\":\"/user1\"},\"branch\":{\"name\":\"test\",\"link\":\"/user5/repo4/src/branch/test\",\"isDeleted\":true}}},\"currentJob\":{\"title\":\"job_2\",\"details\":[\"Waiting for a runner with the following label: fedora\"],\"steps\":[],\"allAttempts\":null}},\"logs\":{\"stepsLog\":[]}}\n", actualClean) + return assert.JSONEq(t, "{\"state\":{\"run\":{\"preExecutionError\":\"\",\"link\":\"/user5/repo4/actions/runs/190\",\"title\":\"job output\",\"titleHTML\":\"job output\",\"status\":\"success\",\"canCancel\":false,\"canApprove\":false,\"canRerun\":false,\"canDeleteArtifact\":false,\"description\":\"Commit c2d72f5484 pushed by user1\",\"done\":false,\"jobs\":[{\"id\":396,\"name\":\"job_2\",\"status\":\"waiting\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeWorkflow\":\"Workflow\",\"localeAllRuns\":\"all runs\",\"shortSHA\":\"c2d72f5484\",\"link\":\"/user5/repo4/commit/c2d72f548424103f01ee1dc02889c1e2bff816b0\",\"pusher\":{\"displayName\":\"user1\",\"link\":\"/user1\"},\"branch\":{\"name\":\"test\",\"link\":\"/user5/repo4/src/branch/test\",\"isDeleted\":true}}},\"currentJob\":{\"title\":\"job_2\",\"details\":[\"Waiting for a runner with the following label: fedora\"],\"steps\":[],\"allAttempts\":null}},\"logs\":{\"stepsLog\":[]}}\n", actualClean) }) htmlDoc.AssertAttrEqual(t, selector, "data-initial-artifacts-response", "{\"artifacts\":[]}\n") } diff --git a/tests/integration/fixtures/TestActionRunsList/action_run.yml b/tests/integration/fixtures/TestActionRunsList/action_run.yml new file mode 100644 index 0000000000..096d458672 --- /dev/null +++ b/tests/integration/fixtures/TestActionRunsList/action_run.yml @@ -0,0 +1,34 @@ +- id: 1000 + title: "A scheduled workflow" + repo_id: 4 + owner_id: 5 + workflow_id: "scheduled.yaml" + index: 209 + trigger_user_id: -2 + ref: "refs/heads/main" + commit_sha: "64357baca84bfff631e7dfae5a3433b26d005646" + trigger_event: "schedule" + is_fork_pull_request: false + status: 6 # running + started: 1683636528 + created: 1683636108 + updated: 1683636626 + need_approval: false + approved_by: 0 +- id: 1001 + title: "Running workflow_dispatch" + repo_id: 4 + owner_id: 5 + workflow_id: "dispatch.yaml" + index: 210 + trigger_user_id: 29 + ref: "refs/heads/main" + commit_sha: "dc67f0a1a0dc2417cd0b1a9f0b95e2e5d91876e9" + trigger_event: "workflow_dispatch" + is_fork_pull_request: false + status: 6 # running + started: 1683636528 + created: 1683636108 + updated: 1683636626 + need_approval: false + approved_by: 0 diff --git a/tests/integration/fixtures/TestActionViewsView/action_run.yml b/tests/integration/fixtures/TestActionViewsView/action_run.yml new file mode 100644 index 0000000000..14cfa86468 --- /dev/null +++ b/tests/integration/fixtures/TestActionViewsView/action_run.yml @@ -0,0 +1,34 @@ +- id: 1000 + title: "A scheduled workflow" + repo_id: 4 + owner_id: 5 + workflow_id: "scheduled.yaml" + index: 209 + trigger_user_id: -2 + ref: "refs/heads/master" + commit_sha: "64357baca84bfff631e7dfae5a3433b26d005646" + trigger_event: "schedule" + is_fork_pull_request: false + status: 5 # waiting + started: 0 + created: 1683636108 + updated: 0 + need_approval: false + approved_by: 0 +- id: 1001 + title: "A triggered run" + repo_id: 4 + owner_id: 5 + workflow_id: "dispatch.yaml" + index: 210 + trigger_user_id: 29 + ref: "refs/heads/master" + commit_sha: "f4100ac14112a3740490afb22b07b69b0b5d4e8b" + trigger_event: "workflow_dispatch" + is_fork_pull_request: false + status: 5 # waiting + started: 0 + created: 1683636108 + updated: 0 + need_approval: false + approved_by: 0 diff --git a/tests/integration/fixtures/TestActionViewsView/action_run_job.yml b/tests/integration/fixtures/TestActionViewsView/action_run_job.yml new file mode 100644 index 0000000000..be79240519 --- /dev/null +++ b/tests/integration/fixtures/TestActionViewsView/action_run_job.yml @@ -0,0 +1,28 @@ +- id: 2153 + run_id: 1000 + repo_id: 4 + owner_id: 5 + commit_sha: 64357baca84bfff631e7dfae5a3433b26d005646 + is_fork_pull_request: false + name: job_2 + attempt: 1 + runs_on: ["debian", "gpu"] + job_id: job_2 + task_id: 8753 + status: 5 # StatusWaiting + started: 0 + stopped: 0 +- id: 2154 + run_id: 1001 + repo_id: 4 + owner_id: 5 + runs_on: ["windows"] + commit_sha: f4100ac14112a3740490afb22b07b69b0b5d4e8b + is_fork_pull_request: false + name: mirror + attempt: 1 + job_id: mirror + task_id: 8754 + status: 5 # StatusWaiting + started: 0 + stopped: 0 \ No newline at end of file diff --git a/tests/integration/fixtures/TestActionViewsView/action_task.yml b/tests/integration/fixtures/TestActionViewsView/action_task.yml new file mode 100644 index 0000000000..b139270a2b --- /dev/null +++ b/tests/integration/fixtures/TestActionViewsView/action_task.yml @@ -0,0 +1,28 @@ +- id: 8753 + job_id: 2153 + attempt: 1 + runner_id: 0 + status: 1 # Waiting + started: 0 + stopped: 0 + repo_id: 4 + owner_id: 5 + commit_sha: 64357baca84bfff631e7dfae5a3433b26d005646 + is_fork_pull_request: false + token_hash: 5fd2714302a06027c0184dbaa697dd1bea7ec80823b673aac8e378dbabdb3f4156bfad6995a8701ec18c187483db7253110e + token_salt: ffffffffff + token_last_eight: ffffffff +- id: 8754 + job_id: 2154 + attempt: 1 + runner_id: 0 + status: 5 # Waiting + started: 0 + stopped: 0 + repo_id: 4 + owner_id: 5 + commit_sha: f4100ac14112a3740490afb22b07b69b0b5d4e8b + is_fork_pull_request: false + token_hash: 9c104fe0db29aa6d737001707e1de2201fdfefb6d2316ec4fb38c44a1820a5948af2f9e1cb5e58ab95cc8fbe589fd4e82f95 + token_salt: ffffffffff + token_last_eight: ffffffff diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 3b44cee907..e42ae52fe7 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -82,6 +82,7 @@ export default { title: '', titleHTML: '', status: '', + description: '', canCancel: false, canApprove: false, canRerun: false, @@ -97,8 +98,6 @@ export default { // }, ], commit: { - localeCommit: '', - localePushedBy: '', localeWorkflow: '', localeAllRuns: '', shortSHA: '', @@ -482,10 +481,8 @@ export default {
- {{ run.commit.localeCommit }} - {{ run.commit.shortSHA }} - {{ run.commit.localePushedBy }} - {{ run.commit.pusher.displayName }} + + {{ run.commit.branch.name }} {{ run.commit.branch.name }} @@ -493,7 +490,7 @@ export default {
{{ run.commit.localeWorkflow }} - {{ workflowName }} ({{ run.commit.localeAllRuns }}) + {{ workflowName }} ({{ run.commit.localeAllRuns }})