feat: Actions jobs that can't be understood display a technical error in the UI, not just server-side logs (#9530)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9530 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
		
				commit
				
					
						068e318629
					
				
			
		
					 13 changed files with 178 additions and 9 deletions
				
			
		| 
						 | 
				
			
			@ -71,6 +71,8 @@ type ActionRun struct {
 | 
			
		|||
 | 
			
		||||
	ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"`
 | 
			
		||||
	ConcurrencyType  ConcurrencyMode
 | 
			
		||||
 | 
			
		||||
	PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -123,6 +123,8 @@ var migrations = []*Migration{
 | 
			
		|||
	NewMigration("Add foreign keys to stopwatch & tracked_time", AddForeignKeysStopwatchTrackedTime),
 | 
			
		||||
	// v41 -> v42
 | 
			
		||||
	NewMigration("Add action_run concurrency fields", AddActionRunConcurrency),
 | 
			
		||||
	// v42 -> v43
 | 
			
		||||
	NewMigration("Add action_run pre_execution_error field", AddActionRunPreExecutionError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCurrentDBVersion returns the current Forgejo database version.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								models/forgejo_migrations/v43.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/forgejo_migrations/v43.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
package forgejo_migrations
 | 
			
		||||
 | 
			
		||||
import "xorm.io/xorm"
 | 
			
		||||
 | 
			
		||||
func AddActionRunPreExecutionError(x *xorm.Engine) error {
 | 
			
		||||
	type ActionRun struct {
 | 
			
		||||
		PreExecutionError string `xorm:"LONGTEXT"`
 | 
			
		||||
	}
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		// Sync drops indexes by default, and this local ActionRun doesn't have all the indexes -- so disable that.
 | 
			
		||||
		IgnoreDropIndices: true,
 | 
			
		||||
	}, new(ActionRun))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -153,6 +153,9 @@
 | 
			
		|||
    "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",
 | 
			
		||||
    "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.",
 | 
			
		||||
    "pulse.n_active_issues": {
 | 
			
		||||
        "one": "%s active issue",
 | 
			
		||||
        "other": "%s active issues"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			@ -386,13 +387,25 @@ func handleWorkflows(
 | 
			
		|||
			log.Error("ConfigureActionRunConcurrency: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		jobs, err := jobParser(dwf.Content, jobparser.WithVars(vars))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
		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.event_detection_error", dwf.EventDetectionError)
 | 
			
		||||
			run.Status = actions_model.StatusFailure
 | 
			
		||||
			log.Info("jobparser.Parse: invalid workflow, setting job status to failed: %v", err)
 | 
			
		||||
			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,
 | 
			
		||||
				}}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if run.ConcurrencyType == actions_model.CancelInProgress {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
package actions
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -208,3 +209,88 @@ func TestActionsNotifierConcurrencyGroup(t *testing.T) {
 | 
			
		|||
	assert.Equal(t, actions_model.CancelInProgress, firstRun.ConcurrencyType)
 | 
			
		||||
	assert.Equal(t, actions_model.StatusCancelled, firstRun.Status)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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=")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"}}"
 | 
			
		||||
	>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.');
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,6 +81,7 @@ export default {
 | 
			
		|||
        canApprove: false,
 | 
			
		||||
        canRerun: false,
 | 
			
		||||
        done: false,
 | 
			
		||||
        preExecutionError: '',
 | 
			
		||||
        jobs: [
 | 
			
		||||
          // {
 | 
			
		||||
          //   id: 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -557,6 +558,12 @@ export default {
 | 
			
		|||
        {{ run.commit.localeWorkflow }}
 | 
			
		||||
        <a class="muted" :href="workflowURL">{{ workflowName }}</a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="ui error message pre-execution-error" v-if="run.preExecutionError">
 | 
			
		||||
        <div class="header">
 | 
			
		||||
          {{ locale.preExecutionError }}
 | 
			
		||||
        </div>
 | 
			
		||||
        {{ run.preExecutionError }}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="action-view-body">
 | 
			
		||||
      <div class="action-view-left" v-if="displayOtherJobs">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@ export async 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'),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue