diff --git a/models/actions/run_job.go b/models/actions/run_job.go index 31f5f44a14..8096bfb6b5 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -6,15 +6,12 @@ package actions import ( "context" "fmt" - "html/template" "slices" - "strings" "time" "forgejo.org/models/db" "forgejo.org/modules/container" "forgejo.org/modules/timeutil" - "forgejo.org/modules/translation" "forgejo.org/modules/util" "code.forgejo.org/forgejo/runner/v12/act/jobparser" @@ -237,27 +234,6 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status { } } -// StatusDiagnostics returns optional diagnostic information to display to the user derived from -// ActionRunJob's current status. It should help the user understand in which state the -// ActionRunJob is and why. -func (job *ActionRunJob) StatusDiagnostics(lang translation.Locale) []template.HTML { - diagnostics := []template.HTML{} - - switch job.Status { - case StatusWaiting: - joinedLabels := strings.Join(job.RunsOn, ", ") - diagnostics = append(diagnostics, lang.TrPluralString(len(job.RunsOn), "actions.status.diagnostics.waiting", joinedLabels)) - default: - diagnostics = append(diagnostics, template.HTML(job.Status.LocaleString(lang))) - } - - if job.Run.NeedApproval { - diagnostics = append(diagnostics, template.HTML(lang.TrString("actions.need_approval_desc"))) - } - - return diagnostics -} - func (job *ActionRunJob) decodeWorkflowPayload() (*jobparser.SingleWorkflow, error) { if job.workflowPayloadDecoded != nil { return job.workflowPayloadDecoded, nil diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go index acab662e60..9cba48705b 100644 --- a/models/actions/run_job_test.go +++ b/models/actions/run_job_test.go @@ -4,12 +4,10 @@ package actions import ( "fmt" - "html/template" "testing" "forgejo.org/models/db" "forgejo.org/models/unittest" - "forgejo.org/modules/translation" "code.forgejo.org/forgejo/runner/v12/act/jobparser" "github.com/stretchr/testify/assert" @@ -74,82 +72,6 @@ func TestActionRunJob_HTMLURL(t *testing.T) { } } -func TestActionRunJob_StatusDiagnostics(t *testing.T) { - translation.InitLocales(t.Context()) - english := translation.NewLocale("en-US") - - tests := []struct { - name string - job ActionRunJob - expected []template.HTML - }{ - { - name: "Unknown status", - job: ActionRunJob{RunsOn: []string{"windows"}, Status: StatusUnknown, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Unknown"}, - }, - { - name: "Waiting without labels", - job: ActionRunJob{RunsOn: []string{}, Status: StatusWaiting, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Waiting for a runner with the following labels: "}, - }, - { - name: "Waiting with one label", - job: ActionRunJob{RunsOn: []string{"freebsd"}, Status: StatusWaiting, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Waiting for a runner with the following label: freebsd"}, - }, - { - name: "Waiting with labels, no approval", - job: ActionRunJob{RunsOn: []string{"docker", "ubuntu"}, Status: StatusWaiting, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Waiting for a runner with the following labels: docker, ubuntu"}, - }, - { - name: "Waiting with labels, approval", - job: ActionRunJob{RunsOn: []string{"docker", "ubuntu"}, Status: StatusWaiting, Run: &ActionRun{NeedApproval: true}}, - expected: []template.HTML{ - "Waiting for a runner with the following labels: docker, ubuntu", - "Need approval to run workflows for fork pull request.", - }, - }, - { - name: "Running", - job: ActionRunJob{RunsOn: []string{"debian"}, Status: StatusRunning, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Running"}, - }, - { - name: "Success", - job: ActionRunJob{RunsOn: []string{"debian"}, Status: StatusSuccess, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Success"}, - }, - { - name: "Failure", - job: ActionRunJob{RunsOn: []string{"debian"}, Status: StatusFailure, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Failure"}, - }, - { - name: "Cancelled", - job: ActionRunJob{RunsOn: []string{"debian"}, Status: StatusCancelled, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Canceled"}, - }, - { - name: "Skipped", - job: ActionRunJob{RunsOn: []string{"debian"}, Status: StatusSkipped, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Skipped"}, - }, - { - name: "Blocked", - job: ActionRunJob{RunsOn: []string{"debian"}, Status: StatusBlocked, Run: &ActionRun{NeedApproval: false}}, - expected: []template.HTML{"Blocked"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, tt.job.StatusDiagnostics(english)) - }) - } -} - func TestActionRunJob_IsIncompleteMatrix(t *testing.T) { tests := []struct { name string diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 8ba701bb28..9b36f7b8cb 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -32,6 +32,7 @@ import ( "forgejo.org/modules/storage" "forgejo.org/modules/templates" "forgejo.org/modules/timeutil" + "forgejo.org/modules/translation" "forgejo.org/modules/util" "forgejo.org/modules/web" "forgejo.org/routers/common" @@ -241,9 +242,10 @@ type ViewStepLogLine struct { } type TaskAttempt struct { - Number int64 `json:"number"` - Started template.HTML `json:"time_since_started_html"` - Status string `json:"status"` + Number int64 `json:"number"` + Started template.HTML `json:"time_since_started_html"` + Status string `json:"status"` + StatusDiagnostics []template.HTML `json:"status_diagnostics"` } func ViewPost(ctx *app_context.Context) { @@ -358,7 +360,7 @@ func getViewResponse(ctx *app_context.Context, req *ViewRequest, runIndex, jobIn } resp.State.CurrentJob.Title = current.Name - resp.State.CurrentJob.Details = current.StatusDiagnostics(ctx.Locale) + resp.State.CurrentJob.Details = statusDiagnostics(current.Status, current, ctx.Locale) resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead of 'null' in json resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead of 'null' in json @@ -372,9 +374,10 @@ func getViewResponse(ctx *app_context.Context, req *ViewRequest, runIndex, jobIn allAttempts := make([]*TaskAttempt, len(taskAttempts)) for i, actionTask := range taskAttempts { allAttempts[i] = &TaskAttempt{ - Number: actionTask.Attempt, - Started: templates.TimeSince(actionTask.Started), - Status: actionTask.Status.String(), + Number: actionTask.Attempt, + Started: templates.TimeSince(actionTask.Started), + Status: actionTask.Status.String(), + StatusDiagnostics: statusDiagnostics(actionTask.Status, task.Job, ctx.Locale), } } resp.State.CurrentJob.AllAttempts = allAttempts @@ -968,3 +971,24 @@ func disableOrEnableWorkflowFile(ctx *app_context.Context, isEnable bool) { url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status"))) ctx.JSONRedirect(redirectURL) } + +// statusDiagnostics returns optional diagnostic information to display to the user. It should help the user understand +// what the current Status means and whether an action needs to be performed, for example, approving a job. +func statusDiagnostics(status actions_model.Status, job *actions_model.ActionRunJob, lang translation.Locale) []template.HTML { + // Initialize as empty container for it to be serialized to an empty JSON array, not `null`. + diagnostics := []template.HTML{} + + switch status { + case actions_model.StatusWaiting: + joinedLabels := strings.Join(job.RunsOn, ", ") + diagnostics = append(diagnostics, lang.TrPluralString(len(job.RunsOn), "actions.status.diagnostics.waiting", joinedLabels)) + default: + diagnostics = append(diagnostics, template.HTML(status.LocaleString(lang))) + } + + if job.Run.NeedApproval { + diagnostics = append(diagnostics, template.HTML(lang.TrString("actions.need_approval_desc"))) + } + + return diagnostics +} diff --git a/routers/web/repo/actions/view_test.go b/routers/web/repo/actions/view_test.go index dfdd11f225..6e15d11df4 100644 --- a/routers/web/repo/actions/view_test.go +++ b/routers/web/repo/actions/view_test.go @@ -13,6 +13,7 @@ import ( repo_model "forgejo.org/models/repo" unittest_model "forgejo.org/models/unittest" "forgejo.org/modules/json" + "forgejo.org/modules/translation" "forgejo.org/modules/web" "forgejo.org/services/contexttest" @@ -20,7 +21,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_getRunByID(t *testing.T) { +func TestActionsViewGetRunByID(t *testing.T) { unittest_model.PrepareTestEnv(t) repo := unittest_model.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 5, ID: 4}) @@ -61,7 +62,7 @@ func Test_getRunByID(t *testing.T) { } } -func Test_artifactsFind(t *testing.T) { +func TestActionsViewArtifactsFind(t *testing.T) { unittest_model.PrepareTestEnv(t) for _, testCase := range []struct { @@ -93,7 +94,7 @@ func Test_artifactsFind(t *testing.T) { } } -func Test_artifactsFindByNameOrID(t *testing.T) { +func TestActionsViewArtifactsFindByNameOrID(t *testing.T) { unittest_model.PrepareTestEnv(t) for _, testCase := range []struct { @@ -195,19 +196,22 @@ func baseExpectedViewResponse() *ViewResponse { }, AllAttempts: []*TaskAttempt{ { - Number: 3, - Started: template.HTML("2023-05-09 12:48:48 +00:00"), - Status: "running", + Number: 3, + Started: template.HTML("2023-05-09 12:48:48 +00:00"), + Status: "running", + StatusDiagnostics: []template.HTML{"actions.status.running"}, }, { - Number: 2, - Started: template.HTML("2023-05-09 12:48:48 +00:00"), - Status: "success", + Number: 2, + Started: template.HTML("2023-05-09 12:48:48 +00:00"), + Status: "success", + StatusDiagnostics: []template.HTML{"actions.status.success"}, }, { - Number: 1, - Started: template.HTML("2023-05-09 12:48:48 +00:00"), - Status: "success", + Number: 1, + Started: template.HTML("2023-05-09 12:48:48 +00:00"), + Status: "success", + StatusDiagnostics: []template.HTML{"actions.status.success"}, }, }, }, @@ -281,9 +285,10 @@ func TestActionsViewViewPost(t *testing.T) { } resp.State.CurrentJob.AllAttempts = []*TaskAttempt{ { - Number: 1, - Started: template.HTML("2023-05-09 12:48:48 +00:00"), - Status: "success", + Number: 1, + Started: template.HTML("2023-05-09 12:48:48 +00:00"), + Status: "success", + StatusDiagnostics: []template.HTML{"actions.status.success"}, }, } @@ -510,3 +515,91 @@ func TestActionsRerun(t *testing.T) { }) } } + +func TestActionsViewStatusDiagnostics(t *testing.T) { + translation.InitLocales(t.Context()) + english := translation.NewLocale("en-US") + + testCases := []struct { + name string + status actions_model.Status + job actions_model.ActionRunJob + expected []template.HTML + }{ + { + name: "Unknown status", + status: actions_model.StatusUnknown, + job: actions_model.ActionRunJob{RunsOn: []string{"windows"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Unknown"}, + }, + { + name: "Waiting without labels", + status: actions_model.StatusWaiting, + job: actions_model.ActionRunJob{RunsOn: []string{}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Waiting for a runner with the following labels: "}, + }, + { + name: "Waiting with one label", + status: actions_model.StatusWaiting, + job: actions_model.ActionRunJob{RunsOn: []string{"freebsd"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Waiting for a runner with the following label: freebsd"}, + }, + { + name: "Waiting with labels, no approval", + status: actions_model.StatusWaiting, + job: actions_model.ActionRunJob{RunsOn: []string{"docker", "ubuntu"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Waiting for a runner with the following labels: docker, ubuntu"}, + }, + { + name: "Waiting with labels, approval", + status: actions_model.StatusWaiting, + job: actions_model.ActionRunJob{RunsOn: []string{"docker", "ubuntu"}, Run: &actions_model.ActionRun{NeedApproval: true}}, + expected: []template.HTML{ + "Waiting for a runner with the following labels: docker, ubuntu", + "Need approval to run workflows for fork pull request.", + }, + }, + { + name: "Running", + status: actions_model.StatusRunning, + job: actions_model.ActionRunJob{RunsOn: []string{"debian"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Running"}, + }, + { + name: "Success", + status: actions_model.StatusSuccess, + job: actions_model.ActionRunJob{RunsOn: []string{"debian"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Success"}, + }, + { + name: "Failure", + status: actions_model.StatusFailure, + job: actions_model.ActionRunJob{RunsOn: []string{"debian"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Failure"}, + }, + { + name: "Cancelled", + status: actions_model.StatusCancelled, + job: actions_model.ActionRunJob{RunsOn: []string{"debian"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Canceled"}, + }, + { + name: "Skipped", + status: actions_model.StatusSkipped, + job: actions_model.ActionRunJob{RunsOn: []string{"debian"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Skipped"}, + }, + { + name: "Blocked", + status: actions_model.StatusBlocked, + job: actions_model.ActionRunJob{RunsOn: []string{"debian"}, Run: &actions_model.ActionRun{NeedApproval: false}}, + expected: []template.HTML{"Blocked"}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expected, statusDiagnostics(testCase.status, &testCase.job, english)) + }) + } +} diff --git a/tests/integration/actions_view_test.go b/tests/integration/actions_view_test.go index 8ccaed8f7b..b3f40f17ff 100644 --- a/tests/integration/actions_view_test.go +++ b/tests/integration/actions_view_test.go @@ -153,7 +153,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\":{\"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\",\"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\"},{\"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\",\"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") } diff --git a/web_src/js/components/RepoActionView.test.js b/web_src/js/components/RepoActionView.test.js index 71ea39a9c2..472763541b 100644 --- a/web_src/js/components/RepoActionView.test.js +++ b/web_src/js/components/RepoActionView.test.js @@ -94,6 +94,7 @@ test('processes ##[group] and ##[endgroup]', async () => { }, }, currentJob: { + title: 'Test', steps: [ { summary: 'Test Job', @@ -101,7 +102,7 @@ test('processes ##[group] and ##[endgroup]', async () => { status: 'success', }, ], - allAttempts: [{number: 1, time_since_started_html: '', status: 'success'}], + allAttempts: [{number: 1, time_since_started_html: '', status: 'success', status_diagnostics: ['Success']}], }, }, logs: { @@ -186,6 +187,7 @@ test('load multiple steps on a finished action', async () => { }, }, currentJob: { + title: 'test', steps: [ { summary: 'Test Step #1', @@ -198,7 +200,7 @@ test('load multiple steps on a finished action', async () => { status: 'success', }, ], - allAttempts: [{number: 1, time_since_started_html: '', status: 'success'}], + allAttempts: [{number: 1, time_since_started_html: '', status: 'success', status_diagnostics: ['Success']}], }, }, logs: { @@ -231,6 +233,11 @@ test('load multiple steps on a finished action', async () => { expect(wrapper.get('.job-step-section:nth-of-type(2) .job-log-line:nth-of-type(1) .log-msg').text()).toEqual('Step #2 Log #1'); expect(wrapper.get('.job-step-section:nth-of-type(2) .job-log-line:nth-of-type(2) .log-msg').text()).toEqual('Step #2 Log #2'); expect(wrapper.get('.job-step-section:nth-of-type(2) .job-log-line:nth-of-type(3) .log-msg').text()).toEqual('Step #2 Log #3'); + + // Attempt status + expect(wrapper.get('.job-info-header h3').text()).toEqual('test'); + expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1); + expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('Success'); }); function configureForMultipleAttemptTests({viewHistorical}) { @@ -246,6 +253,7 @@ function configureForMultipleAttemptTests({viewHistorical}) { }, }, currentJob: { + title: 'test', steps: [ { summary: 'Test Job', @@ -254,8 +262,8 @@ function configureForMultipleAttemptTests({viewHistorical}) { }, ], allAttempts: [ - {number: 2, time_since_started_html: 'yesterday', status: 'success'}, - {number: 1, time_since_started_html: 'two days ago', status: 'failure'}, + {number: 2, time_since_started_html: 'yesterday', status: 'success', status_diagnostics: ['Success']}, + {number: 1, time_since_started_html: 'two days ago', status: 'failure', status_diagnostics: ['Failure']}, ], }, }; @@ -315,6 +323,11 @@ test('display baseline with most-recent attempt', async () => { expect(wrapper.findAll('.job-attempt-dropdown').length).toEqual(1); expect(wrapper.findAll('.job-attempt-dropdown .svg.octicon-check-circle-fill.text.green').length).toEqual(1); expect(wrapper.get('.job-attempt-dropdown .ui.dropdown').text()).toEqual('Run attempt 2 yesterday'); + + // Attempt status + expect(wrapper.get('.job-info-header h3').text()).toEqual('test'); + expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1); + expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('Success'); }); test('display reconfigured for historical attempt', async () => { @@ -342,6 +355,11 @@ test('display reconfigured for historical attempt', async () => { expect(wrapper.findAll('.job-attempt-dropdown').length).toEqual(1); expect(wrapper.findAll('.job-attempt-dropdown .svg.octicon-x-circle-fill.text.red').length).toEqual(1); expect(wrapper.get('.job-attempt-dropdown .ui.dropdown').text()).toEqual('Run attempt 1 two days ago'); + + // Attempt status + expect(wrapper.get('.job-info-header h3').text()).toEqual('test'); + expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1); + expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('Failure'); }); test('historical attempt dropdown interactions', async () => { @@ -476,6 +494,7 @@ test('artifacts download links', async () => { }, }, currentJob: { + title: 'test', steps: [ { summary: 'Test Step #1', @@ -483,7 +502,7 @@ test('artifacts download links', async () => { status: 'success', }, ], - allAttempts: [{number: 1, time_since_started_html: '', status: 'success'}], + allAttempts: [{number: 1, time_since_started_html: '', status: 'success', status_diagnostics: ['Success']}], }, }, logs: { @@ -628,6 +647,11 @@ test('view non-picked action run job', async () => { expect(wrapper.get('.job-brief-list .job-brief-item:nth-of-type(1) .job-brief-name').text()).toEqual('check-1'); 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'); + + // Attempt status + expect(wrapper.get('.job-info-header h3').text()).toEqual('check-1'); + expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1); + expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('waiting (locale)'); }); test('view without pre-execution error', async () => { @@ -686,6 +710,7 @@ test('Offset index', async () => { }, }, currentJob: { + title: 'test', steps: [ { summary: 'Test Job', @@ -693,7 +718,7 @@ test('Offset index', async () => { status: 'success', }, ], - allAttempts: [{number: 1, time_since_started_html: '', status: 'success'}], + allAttempts: [{number: 1, time_since_started_html: '', status: 'success', status_diagnostics: ['Success']}], }, }, logs: { diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 8d2d82d022..e79dae7078 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -123,7 +123,7 @@ export default { // initial value here is configured so that currentingViewingMostRecentAttempt() -> true on the default `data()`, so that the // initial render (before `loadJob`'s first execution is complete) doesn't display "You are viewing an // out-of-date run..." - allAttempts: new Array(parseInt(this.attemptNumber)).fill({index: 0, time_since_started_html: '', status: 'success'}), + allAttempts: new Array(parseInt(this.attemptNumber)).fill({index: 0, time_since_started_html: '', status: 'success', status_diagnostics: []}), }, }; }, @@ -158,6 +158,7 @@ export default { if (!this.currentJob.allAttempts) { return fallback; } + const attempt = this.currentJob.allAttempts.find((attempt) => attempt.number === this.viewingAttemptNumber); return attempt || fallback; }, @@ -181,6 +182,18 @@ export default { return this.locale.viewingOutOfDateRun .replace('%[1]s', this.viewingAttempt.time_since_started_html); }, + + statusDiagnostics() { + if (!this.currentJob.allAttempts) { + return this.currentJob.details; + } + + const useAttempt = this.currentJob.allAttempts.some((attempt) => attempt.number === this.viewingAttemptNumber); + if (useAttempt) { + return this.viewingAttempt.status_diagnostics; + } + return this.currentJob.details; + }, }, async mounted() { @@ -616,7 +629,7 @@ export default { {{ currentJob.title }}