From 72abb7079b105e5f206458bd7453913d8788e553 Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Sun, 28 Sep 2025 18:53:18 +0200 Subject: [PATCH 1/4] test: fix 'Missing required prop' warning during RepoActionView frontend tests (#9454) Fixes warnings in the `RepoActionView.test.js`, introduced in #9444 but caused by the frontend tests not providing all required properties to the component. ``` stderr | web_src/js/components/RepoActionView.test.js > processes ##[group] and ##[endgroup] [Vue warn]: Missing required prop: "runIndex" at at ``` ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [x] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9454 Reviewed-by: Earl Warren Co-authored-by: Mathieu Fenniak Co-committed-by: Mathieu Fenniak (cherry picked from commit 0b923a03b4) --- web_src/js/components/RepoActionView.test.js | 74 ++++++-------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/web_src/js/components/RepoActionView.test.js b/web_src/js/components/RepoActionView.test.js index c304e1915d..8107b89c07 100644 --- a/web_src/js/components/RepoActionView.test.js +++ b/web_src/js/components/RepoActionView.test.js @@ -53,6 +53,18 @@ const minimalInitialJobData = { const minimalInitialArtifactData = { artifacts: [], }; +const defaultTestProps = { + actionsURL: 'https://example.com/example-org/example-repo/actions', + jobIndex: '1', + attemptNumber: '1', + runIndex: '10', + runID: '1001', + initialJobData: minimalInitialJobData, + initialArtifactData: minimalInitialArtifactData, + locale: testLocale, + workflowName: 'workflow name', + workflowURL: 'https://example.com/example-org/example-repo/actions?workflow=test.yml', +}; test('processes ##[group] and ##[endgroup]', async () => { Object.defineProperty(document.documentElement, 'lang', {value: 'en'}); @@ -105,13 +117,7 @@ test('processes ##[group] and ##[endgroup]', async () => { }); const wrapper = mount(RepoActionView, { - props: { - jobIndex: '1', - attemptNumber: '1', - initialJobData: minimalInitialJobData, - initialArtifactData: minimalInitialArtifactData, - locale: testLocale, - }, + props: defaultTestProps, }); await flushPromises(); await wrapper.get('.job-step-summary').trigger('click'); @@ -208,15 +214,7 @@ test('load multiple steps on a finished action', async () => { }); const wrapper = mount(RepoActionView, { - props: { - actionsURL: 'https://example.com/example-org/example-repo/actions', - initialJobData: minimalInitialJobData, - initialArtifactData: minimalInitialArtifactData, - runIndex: '1', - jobIndex: '2', - attemptNumber: '1', - locale: testLocale, - }, + props: defaultTestProps, }); wrapper.vm.loadJob(); // simulate intermittent reload immediately so UI switches from minimalInitialJobData to the mock data from the test's fetch spy. await flushPromises(); @@ -288,13 +286,11 @@ function configureForMultipleAttemptTests({viewHistorical}) { const wrapper = mount(RepoActionView, { props: { + ...defaultTestProps, runIndex: '123', - jobIndex: '1', attemptNumber: viewHistorical ? '1' : '2', actionsURL: toAbsoluteUrl('/user1/repo2/actions'), initialJobData: {...minimalInitialJobData, state: myJobState}, - initialArtifactData: minimalInitialArtifactData, - locale: testLocale, }, }); return wrapper; @@ -465,16 +461,7 @@ test('artifacts download links', async () => { }); const wrapper = mount(RepoActionView, { - props: { - actionsURL: 'https://example.com/example-org/example-repo/actions', - initialJobData: minimalInitialJobData, - initialArtifactData: minimalInitialArtifactData, - runIndex: '10', - runID: '1001', - jobIndex: '2', - attemptNumber: '1', - locale: testLocale, - }, + props: defaultTestProps, }); wrapper.vm.loadJob(); // simulate intermittent reload immediately so UI switches from minimalInitialJobData to the mock data from the test's fetch spy. await flushPromises(); @@ -501,11 +488,8 @@ test('initial load schedules refresh when job is not done', async () => { doneInitialJobData.state.run.done = true; const wrapper = mount(RepoActionView, { props: { - jobIndex: '1', - attemptNumber: '1', + ...defaultTestProps, initialJobData: doneInitialJobData, - initialArtifactData: minimalInitialArtifactData, - locale: testLocale, }, }); await flushPromises(); @@ -520,13 +504,7 @@ test('initial load schedules refresh when job is not done', async () => { const runningInitialJobData = structuredClone(minimalInitialJobData); runningInitialJobData.state.run.done = false; const wrapper = mount(RepoActionView, { - props: { - jobIndex: '1', - attemptNumber: '1', - initialJobData: runningInitialJobData, - initialArtifactData: minimalInitialArtifactData, - locale: testLocale, - }, + props: defaultTestProps, }); await flushPromises(); const container = wrapper.find('.action-view-container'); @@ -548,13 +526,7 @@ test('initial load data is used without calling fetch()', async () => { }); mount(RepoActionView, { - props: { - jobIndex: '1', - attemptNumber: '1', - initialJobData: minimalInitialJobData, - initialArtifactData: minimalInitialArtifactData, - locale: testLocale, - }, + props: defaultTestProps, }); await flushPromises(); expect(fetchSpy).not.toHaveBeenCalled(); @@ -564,11 +536,7 @@ test('view non-picked action run job', async () => { Object.defineProperty(document.documentElement, 'lang', {value: 'en'}); const wrapper = mount(RepoActionView, { props: { - actionsURL: 'https://example.com/example-org/example-repo/actions', - runIndex: '10', - runID: '1001', - jobIndex: '2', - attemptNumber: '1', + ...defaultTestProps, initialJobData: { ...minimalInitialJobData, // Definitions here should match the same type of content as the related backend test, @@ -613,8 +581,6 @@ test('view non-picked action run job', async () => { }, }, }, - initialArtifactData: minimalInitialArtifactData, - locale: testLocale, }, }); await flushPromises(); From 6755ff1631066b99897ac4098120c2cfed0e8c5d Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Sat, 4 Oct 2025 13:28:23 -0600 Subject: [PATCH 2/4] save pre-execution errors to the DB when workflow execution is prevented by user data (cherry picked from commit 152b98da90) --- models/actions/run.go | 2 ++ models/forgejo_migrations/migrate.go | 3 ++ options/locale_next/locale_en-US.json | 1 + services/actions/notifier_helper.go | 5 ++- services/actions/notifier_helper_test.go | 42 ++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/models/actions/run.go b/models/actions/run.go index b5f79a0cb3..281904fbb3 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -56,6 +56,8 @@ type ActionRun struct { Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` NotifyEmail bool + + PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow } func init() { diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 71fcf16e7a..7b36a2f264 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -119,6 +119,9 @@ var migrations = []*Migration{ NewMigration("Migrate `data` column of `secret` table to store keying material", MigrateActionSecretsToKeying), // v39 -> v40 NewMigration("Add index for release sha1", AddIndexForReleaseSha1), + // NOTE: v42 -> v43 -- effectively backported into Forgejo v13 as part of backporting + // https://codeberg.org/forgejo/forgejo/pulls/9530, but the migration was omitted to avoid future upgrade conflicts. + // The migration will effectively occur automatically via table `Sync` on DB engine initialization. } // GetCurrentDBVersion returns the current Forgejo database version. diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index 4c74fe493b..1aee88c965 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -150,5 +150,6 @@ "actions.runs.run_attempt_label": "Run attempt #%[1]s (%[2]s)", "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.workflow.job_parsing_error": "Unable to parse jobs in workflow: %v", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index b24090cab4..d5037025cc 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -26,6 +26,7 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/setting" api "forgejo.org/modules/structs" + "forgejo.org/modules/translation" "forgejo.org/modules/util" webhook_module "forgejo.org/modules/webhook" "forgejo.org/services/convert" @@ -380,8 +381,10 @@ func handleWorkflows( jobs, err := jobParser(dwf.Content, jobparser.WithVars(vars)) if err != nil { - run.Status = actions_model.StatusFailure log.Info("jobparser.Parse: invalid workflow, setting job status to failed: %v", err) + tr := translation.NewLocale(input.Doer.Language) + run.PreExecutionError = tr.TrString("actions.workflow.job_parsing_error", err) + run.Status = actions_model.StatusFailure jobs = []*jobparser.SingleWorkflow{{ Name: dwf.EntryName, }} diff --git a/services/actions/notifier_helper_test.go b/services/actions/notifier_helper_test.go index 525103927b..c3890dceee 100644 --- a/services/actions/notifier_helper_test.go +++ b/services/actions/notifier_helper_test.go @@ -144,3 +144,45 @@ func Test_OpenForkPullRequestEvent(t *testing.T) { assert.Equal(t, webhook_module.HookEventPullRequest, runs[0].Event) assert.True(t, runs[0].IsForkPullRequest) } + +func TestActionsPreExecutionErrorInvalidJobs(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) + + commit := &git.Commit{ + ID: git.MustIDFromString("0000000000000000000000000000000000000000"), + CommitMessage: "test", + } + detectedWorkflows := []*actions_module.DetectedWorkflow{ + { + EntryName: "test.yml", + TriggerEvent: &jobparser.Event{ + Name: "pull_request", + }, + Content: []byte("{ on: pull_request, jobs: 'hello, I am the jobs!' }"), + }, + } + input := ¬ifyInput{ + Repo: repo, + Doer: doer, + Event: webhook_module.HookEventPullRequestSync, + PullRequest: pr, + Payload: &api.PullRequestPayload{}, + } + + err := handleWorkflows(db.DefaultContext, detectedWorkflows, commit, input, "refs/head/main") + require.NoError(t, err) + + runs, err := db.Find[actions_model.ActionRun](db.DefaultContext, actions_model.FindRunOptions{ + RepoID: repo.ID, + }) + require.NoError(t, err) + require.Len(t, runs, 1) + createdRun := runs[0] + + assert.Equal(t, actions_model.StatusFailure, createdRun.Status) + assert.Contains(t, createdRun.PreExecutionError, "actions.workflow.job_parsing_error%!(EXTRA *fmt.wrapError=") +} From 877726a2bfbeb8c6bba33918a70e9a44acdcc0a9 Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Sat, 4 Oct 2025 15:33:00 -0600 Subject: [PATCH 3/4] don't execute workflows when event parsing fails; create a pre-execution error instead (cherry picked from commit 25ba696b6a) --- modules/actions/workflows.go | 10 +++--- options/locale_next/locale_en-US.json | 1 + services/actions/notifier_helper.go | 18 +++++++--- services/actions/notifier_helper_test.go | 44 ++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index c3960d140a..f50e5f8289 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -21,9 +21,10 @@ import ( ) type DetectedWorkflow struct { - EntryName string - TriggerEvent *jobparser.Event - Content []byte + EntryName string + TriggerEvent *jobparser.Event + Content []byte + EventDetectionError error } func init() { @@ -127,7 +128,8 @@ func DetectWorkflows( TriggerEvent: &jobparser.Event{ Name: triggedEvent.Event(), }, - Content: content, + Content: content, + EventDetectionError: err, } workflows = append(workflows, dwf) continue diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index 1aee88c965..a316e0b6f9 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -151,5 +151,6 @@ "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.workflow.job_parsing_error": "Unable to parse jobs in workflow: %v", + "actions.workflow.event_detection_error": "Unable to parse supported events in workflow: %v", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index d5037025cc..505e9cb38a 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -379,15 +379,25 @@ func handleWorkflows( continue } - jobs, err := jobParser(dwf.Content, jobparser.WithVars(vars)) - if err != nil { - log.Info("jobparser.Parse: invalid workflow, setting job status to failed: %v", err) + var jobs []*jobparser.SingleWorkflow + if dwf.EventDetectionError != nil { // don't even bother trying to parse jobs due to event detection error tr := translation.NewLocale(input.Doer.Language) - run.PreExecutionError = tr.TrString("actions.workflow.job_parsing_error", err) + run.PreExecutionError = tr.TrString("actions.workflow.event_detection_error", dwf.EventDetectionError) run.Status = actions_model.StatusFailure jobs = []*jobparser.SingleWorkflow{{ Name: dwf.EntryName, }} + } else { + jobs, err = jobParser(dwf.Content, jobparser.WithVars(vars)) + if err != nil { + log.Info("jobparser.Parse: invalid workflow, setting job status to failed: %v", err) + tr := translation.NewLocale(input.Doer.Language) + run.PreExecutionError = tr.TrString("actions.workflow.job_parsing_error", err) + run.Status = actions_model.StatusFailure + jobs = []*jobparser.SingleWorkflow{{ + Name: dwf.EntryName, + }} + } } // cancel running jobs if the event is push or pull_request_sync diff --git a/services/actions/notifier_helper_test.go b/services/actions/notifier_helper_test.go index c3890dceee..0cb02518e0 100644 --- a/services/actions/notifier_helper_test.go +++ b/services/actions/notifier_helper_test.go @@ -4,6 +4,7 @@ package actions import ( + "errors" "testing" actions_model "forgejo.org/models/actions" @@ -186,3 +187,46 @@ func TestActionsPreExecutionErrorInvalidJobs(t *testing.T) { assert.Equal(t, actions_model.StatusFailure, createdRun.Status) assert.Contains(t, createdRun.PreExecutionError, "actions.workflow.job_parsing_error%!(EXTRA *fmt.wrapError=") } + +func TestActionsPreExecutionEventDetectionError(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) + + commit := &git.Commit{ + ID: git.MustIDFromString("0000000000000000000000000000000000000000"), + CommitMessage: "test", + } + detectedWorkflows := []*actions_module.DetectedWorkflow{ + { + EntryName: "test.yml", + TriggerEvent: &jobparser.Event{ + Name: "pull_request", + }, + Content: []byte("{ on: nothing, jobs: { j1: {} }}"), + EventDetectionError: errors.New("nothing is not a valid event"), + }, + } + input := ¬ifyInput{ + Repo: repo, + Doer: doer, + Event: webhook_module.HookEventPullRequestSync, + PullRequest: pr, + Payload: &api.PullRequestPayload{}, + } + + err := handleWorkflows(db.DefaultContext, detectedWorkflows, commit, input, "refs/head/main") + require.NoError(t, err) + + runs, err := db.Find[actions_model.ActionRun](db.DefaultContext, actions_model.FindRunOptions{ + RepoID: repo.ID, + }) + require.NoError(t, err) + require.Len(t, runs, 1) + createdRun := runs[0] + + assert.Equal(t, actions_model.StatusFailure, createdRun.Status) + assert.Equal(t, "actions.workflow.event_detection_error%!(EXTRA *errors.errorString=nothing is not a valid event)", createdRun.PreExecutionError) +} From 029a49377953936690a4305d5666397755a25752 Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Sat, 4 Oct 2025 15:30:17 -0600 Subject: [PATCH 4/4] display pre-execution errors in action view UI (cherry picked from commit 02ea77c6a0) --- options/locale_next/locale_en-US.json | 1 + routers/web/repo/actions/view.go | 2 ++ templates/repo/actions/view.tmpl | 1 + tests/integration/actions_view_test.go | 4 +-- web_src/js/components/RepoActionView.test.js | 33 ++++++++++++++++++++ web_src/js/components/RepoActionView.vue | 8 +++++ 6 files changed, 47 insertions(+), 2 deletions(-) diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index a316e0b6f9..a24912204c 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -152,5 +152,6 @@ "actions.runs.view_most_recent_run": "View most recent run", "actions.workflow.job_parsing_error": "Unable to parse jobs in workflow: %v", "actions.workflow.event_detection_error": "Unable to parse supported events in workflow: %v", + "actions.workflow.pre_execution_error": "Workflow was not executed due to an error that blocked the execution attempt.", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index b5e73e9d33..95f383b9a6 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -178,6 +178,7 @@ type ViewRunInfo struct { Done bool `json:"done"` Jobs []*ViewJob `json:"jobs"` Commit ViewCommit `json:"commit"` + PreExecutionError string `json:"preExecutionError"` } type ViewCurrentJob struct { @@ -285,6 +286,7 @@ func getViewResponse(ctx *context_module.Context, req *ViewRequest, runIndex, jo resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) 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 = run.PreExecutionError // 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 diff --git a/templates/repo/actions/view.tmpl b/templates/repo/actions/view.tmpl index 81f989b8e1..5b8b44639e 100644 --- a/templates/repo/actions/view.tmpl +++ b/templates/repo/actions/view.tmpl @@ -33,6 +33,7 @@ data-locale-run-attempt-label="{{ctx.Locale.Tr "actions.runs.run_attempt_label"}}" data-locale-viewing-out-of-date-run="{{ctx.Locale.Tr "actions.runs.viewing_out_of_date_run"}}" data-locale-view-most-recent-run="{{ctx.Locale.Tr "actions.runs.view_most_recent_run"}}" + data-locale-pre-execution-error="{{ctx.Locale.Tr "actions.workflow.pre_execution_error"}}" > diff --git a/tests/integration/actions_view_test.go b/tests/integration/actions_view_test.go index 1a0cdc2ec1..eb2faf02de 100644 --- a/tests/integration/actions_view_test.go +++ b/tests/integration/actions_view_test.go @@ -152,7 +152,7 @@ func TestActionViewsView(t *testing.T) { re = regexp.MustCompile(pattern) actualClean = re.ReplaceAllString(actualClean, `"time_since_started_html":"_time_"`) - return assert.JSONEq(t, "{\"state\":{\"run\":{\"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\",\"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\",\"detail\":\"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\"},{\"number\":2,\"time_since_started_html\":\"_time_\",\"status\":\"success\"},{\"number\":1,\"time_since_started_html\":\"_time_\",\"status\":\"success\"}]}},\"logs\":{\"stepsLog\":[]}}\n", actualClean) + 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\",\"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\",\"detail\":\"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\"},{\"number\":2,\"time_since_started_html\":\"_time_\",\"status\":\"success\"},{\"number\":1,\"time_since_started_html\":\"_time_\",\"status\":\"success\"}]}},\"logs\":{\"stepsLog\":[]}}\n", actualClean) }) htmlDoc.AssertAttrEqual(t, selector, "data-initial-artifacts-response", "{\"artifacts\":[{\"name\":\"multi-file-download\",\"size\":2048,\"status\":\"completed\"}]}\n") } @@ -184,7 +184,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\":{\"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\",\"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\",\"detail\":\"Waiting\",\"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,\"done\":false,\"jobs\":[{\"id\":396,\"name\":\"job_2\",\"status\":\"waiting\",\"canRerun\":false,\"duration\":\"_duration_\"}],\"commit\":{\"localeCommit\":\"Commit\",\"localePushedBy\":\"pushed by\",\"localeWorkflow\":\"Workflow\",\"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\",\"detail\":\"Waiting\",\"steps\":[],\"allAttempts\":null}},\"logs\":{\"stepsLog\":[]}}\n", actualClean) }) htmlDoc.AssertAttrEqual(t, selector, "data-initial-artifacts-response", "{\"artifacts\":[]}\n") } diff --git a/web_src/js/components/RepoActionView.test.js b/web_src/js/components/RepoActionView.test.js index 8107b89c07..313732cc61 100644 --- a/web_src/js/components/RepoActionView.test.js +++ b/web_src/js/components/RepoActionView.test.js @@ -17,6 +17,7 @@ const testLocale = { runAttemptLabel: 'Run attempt %[1]s %[2]s', viewingOutOfDateRun: 'oh no, out of date since %[1]s give or take or so', viewMostRecentRun: '', + preExecutionError: 'pre-execution error', status: { unknown: '', waiting: '', @@ -590,3 +591,35 @@ test('view non-picked action run job', async () => { expect(wrapper.get('.job-brief-list .job-brief-item:nth-of-type(2) .job-brief-name').text()).toEqual('check-2'); expect(wrapper.get('.job-brief-list .job-brief-item:nth-of-type(3) .job-brief-name').text()).toEqual('check-3'); }); + +test('view without pre-execution error', async () => { + Object.defineProperty(document.documentElement, 'lang', {value: 'en'}); + const wrapper = mount(RepoActionView, { + props: defaultTestProps, + }); + await flushPromises(); + expect(wrapper.find('.pre-execution-error').exists()).toBe(false); +}); + +test('view with pre-execution error', async () => { + Object.defineProperty(document.documentElement, 'lang', {value: 'en'}); + const wrapper = mount(RepoActionView, { + props: { + ...defaultTestProps, + initialJobData: { + ...minimalInitialJobData, + state: { + ...minimalInitialJobData.state, + run: { + ...minimalInitialJobData.state.run, + preExecutionError: 'Oops, I dropped it.', + }, + }, + }, + }, + }); + await flushPromises(); + const block = wrapper.find('.pre-execution-error'); + expect(block.exists()).toBe(true); + expect(block.text()).toBe('pre-execution error Oops, I dropped it.'); +}); diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 24bc32e348..f9d6028b81 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -52,6 +52,7 @@ const sfc = { canApprove: false, canRerun: false, done: false, + preExecutionError: '', jobs: [ // { // id: 0, @@ -525,6 +526,7 @@ export function initRepositoryActionView() { runAttemptLabel: el.getAttribute('data-locale-run-attempt-label'), viewingOutOfDateRun: el.getAttribute('data-locale-viewing-out-of-date-run'), viewMostRecentRun: el.getAttribute('data-locale-view-most-recent-run'), + preExecutionError: el.getAttribute('data-locale-pre-execution-error'), status: { unknown: el.getAttribute('data-locale-status-unknown'), waiting: el.getAttribute('data-locale-status-waiting'), @@ -582,6 +584,12 @@ export function initRepositoryActionView() { {{ run.commit.localeWorkflow }} {{ workflowName }} +
+
+ {{ locale.preExecutionError }} +
+ {{ run.preExecutionError }} +