tenseleyflow/shithub / fdc8b27

Browse files

repo/actions: show queued runner label wait reason (S41j)

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
fdc8b275379a9a1ff0742817b239b02e8bbc1efc
Parents
91f228d
Tree
83ba60f

5 changed files

StatusFile+-
M internal/web/handlers/repo/actions.go 20 0
M internal/web/handlers/repo/actions_test.go 35 0
M internal/web/handlers/repo/repo_test.go 1 1
M internal/web/static/css/shithub.css 12 0
M internal/web/templates/repo/action_run.html 3 0
internal/web/handlers/repo/actions.gomodified
@@ -131,6 +131,7 @@ type actionsJobDetailView struct {
131131
 	RunsOn     string
132132
 	Needs      []string
133133
 	NeedsText  string
134
+	WaitReason string
134135
 	StateText  string
135136
 	StateClass string
136137
 	StateIcon  string
@@ -821,6 +822,7 @@ func actionsJobDetailViewFromRow(row actionsdb.ListJobsForRunRow, owner, repoNam
821822
 		RunsOn:     row.RunsOn,
822823
 		Needs:      append([]string(nil), row.NeedsJobs...),
823824
 		NeedsText:  strings.Join(row.NeedsJobs, ", "),
825
+		WaitReason: queuedJobWaitReason(row),
824826
 		StateText:  stateText,
825827
 		StateClass: stateClass,
826828
 		StateIcon:  stateIcon,
@@ -834,6 +836,24 @@ func actionsJobDetailViewFromRow(row actionsdb.ListJobsForRunRow, owner, repoNam
834836
 	}
835837
 }
836838
 
839
+func queuedJobWaitReason(row actionsdb.ListJobsForRunRow) string {
840
+	if row.Status != actionsdb.WorkflowJobStatusQueued {
841
+		return ""
842
+	}
843
+	runsOn := strings.TrimSpace(row.RunsOn)
844
+	hasNeeds := len(row.NeedsJobs) > 0
845
+	switch {
846
+	case runsOn != "" && hasNeeds:
847
+		return "Waiting for dependencies or runner with labels: " + runsOn
848
+	case runsOn != "":
849
+		return "Waiting for runner with labels: " + runsOn
850
+	case hasNeeds:
851
+		return "Waiting for dependencies: " + strings.Join(row.NeedsJobs, ", ")
852
+	default:
853
+		return "Waiting for runner"
854
+	}
855
+}
856
+
837857
 func actionsStepDetailViewFromRow(row actionsdb.ListStepsForJobRow, owner, repoName string, runIndex int64, jobIndex int32, now time.Time) actionsStepDetailView {
838858
 	stateText, stateClass, stateIcon := workflowStepState(row.Status, row.Conclusion)
839859
 	name, kind, detail := workflowStepDisplay(row)
internal/web/handlers/repo/actions_test.gomodified
@@ -303,6 +303,41 @@ func TestRepoActionRunRendersWorkflowRunJobsAndSteps(t *testing.T) {
303303
 	}
304304
 }
305305
 
306
+func TestRepoActionRunShowsQueuedRunnerLabelWaitReason(t *testing.T) {
307
+	t.Parallel()
308
+	f := newRepoFixture(t)
309
+	now := time.Date(2026, 5, 11, 12, 0, 0, 0, time.UTC)
310
+	runID := f.insertWorkflowRun(t, workflowRunFixture{
311
+		RunIndex:      8,
312
+		WorkflowFile:  ".shithub/workflows/ci.yml",
313
+		WorkflowName:  "CI",
314
+		HeadRef:       "trunk",
315
+		Event:         actionsdb.WorkflowRunEventPush,
316
+		Status:        actionsdb.WorkflowRunStatusQueued,
317
+		ActorUserID:   f.owner.ID,
318
+		CreatedOffset: -2 * time.Minute,
319
+	}, now)
320
+	f.insertWorkflowJob(t, workflowJobFixture{
321
+		RunID:    runID,
322
+		JobIndex: 0,
323
+		JobKey:   "windows",
324
+		JobName:  "Windows",
325
+		RunsOn:   "windows-latest",
326
+		Status:   actionsdb.WorkflowJobStatusQueued,
327
+	})
328
+
329
+	resp := httptest.NewRecorder()
330
+	req := httptest.NewRequest(http.MethodGet, "/alice/public-repo/actions/runs/8", nil)
331
+	f.actionsMux(viewerFor(f.owner)).ServeHTTP(resp, req)
332
+	if resp.Code != http.StatusOK {
333
+		t.Fatalf("status=%d body=%s", resp.Code, resp.Body.String())
334
+	}
335
+	body := resp.Body.String()
336
+	if !strings.Contains(body, "WAIT=Waiting for runner with labels: windows-latest;") {
337
+		t.Fatalf("wait reason missing: %s", body)
338
+	}
339
+}
340
+
306341
 func TestRepoActionRunRendersCancelControlsForWritersOnly(t *testing.T) {
307342
 	t.Parallel()
308343
 	f := newRepoFixture(t)
internal/web/handlers/repo/repo_test.gomodified
@@ -150,7 +150,7 @@ func minimalTemplatesFS() fstest.MapFS {
150150
 		"repo/new.html":                {Data: []byte(`{{ define "page" }}OWNERS={{ range .Owners }}{{ .Token }}:{{ if eq .Token $.Form.Owner }}selected{{ end }}:{{ .Slug }};{{ end }}{{ end }}`)},
151151
 		"repo/actions.html":            {Data: []byte(`{{ define "page" }}COUNT={{ .RunCount }};FILTERED={{ .FilteredRunCount }};PAGE={{ .Pagination.ResultText }};{{ range .DispatchWorkflows }}DISPATCH={{ .Name }}:{{ .DispatchHref }}:{{ range .Inputs }}{{ .Name }}/{{ .Type }}/{{ .Required }}/{{ .Default }}/{{ range .Options }}{{ .Value }}|{{ end }},{{ end }};{{ end }}{{ range .Workflows }}WF={{ .Name }}:{{ .Count }}:{{ .Active }};{{ end }}{{ range .Runs }}RUN={{ .Title }}:#{{ .RunIndex }}:{{ .Event }}:{{ .HeadRef }}:{{ .ActorUsername }}:{{ .StateClass }};{{ end }}{{ end }}`)},
152152
 		"repo/_action_run_status.html": {Data: []byte(`{{ define "action-run-status" }}STATUS={{ .Run.StateClass }}:{{ .Run.IsTerminal }}:{{ .Run.StatusHref }};{{ end }}`)},
153
-		"repo/action_run.html":         {Data: []byte(`{{ define "page" }}RUN={{ .Run.Title }}:#{{ .Run.RunIndex }}:{{ .Run.Event }}:{{ .Run.ActorUsername }}:{{ .Run.StateClass }};{{ if .Run.ParentRunHref }}PARENT={{ .Run.ParentRunIndex }}:{{ .Run.ParentRunHref }};{{ end }}{{ if .Run.CanRerun }}RERUN={{ .Run.RerunHref }};{{ end }}{{ if .Run.CanCancel }}CANCEL_RUN={{ .Run.CancelHref }};{{ end }}SUMMARY={{ .Run.JobCount }}:{{ .Run.CompletedCount }}:{{ .Run.FailureCount }}:{{ .Run.ArtifactCount }};{{ range .Run.Jobs }}JOB={{ .Name }}:{{ .StateClass }}:{{ .NeedsText }}:{{ .RunsOn }};{{ if .CanCancel }}CANCEL_JOB={{ .CancelHref }};{{ end }}{{ if .CancelRequested }}CANCEL_REQUESTED={{ .Name }};{{ end }}{{ range .Steps }}STEP={{ .Name }}:{{ .StateClass }}:{{ .LogHref }};{{ end }}{{ end }}{{ end }}`)},
153
+		"repo/action_run.html":         {Data: []byte(`{{ define "page" }}RUN={{ .Run.Title }}:#{{ .Run.RunIndex }}:{{ .Run.Event }}:{{ .Run.ActorUsername }}:{{ .Run.StateClass }};{{ if .Run.ParentRunHref }}PARENT={{ .Run.ParentRunIndex }}:{{ .Run.ParentRunHref }};{{ end }}{{ if .Run.CanRerun }}RERUN={{ .Run.RerunHref }};{{ end }}{{ if .Run.CanCancel }}CANCEL_RUN={{ .Run.CancelHref }};{{ end }}SUMMARY={{ .Run.JobCount }}:{{ .Run.CompletedCount }}:{{ .Run.FailureCount }}:{{ .Run.ArtifactCount }};{{ range .Run.Jobs }}JOB={{ .Name }}:{{ .StateClass }}:{{ .NeedsText }}:{{ .RunsOn }};{{ if .WaitReason }}WAIT={{ .WaitReason }};{{ end }}{{ if .CanCancel }}CANCEL_JOB={{ .CancelHref }};{{ end }}{{ if .CancelRequested }}CANCEL_REQUESTED={{ .Name }};{{ end }}{{ range .Steps }}STEP={{ .Name }}:{{ .StateClass }}:{{ .LogHref }};{{ end }}{{ end }}{{ end }}`)},
154154
 		"repo/action_run_status.html":  {Data: []byte(`{{ define "page" }}{{ template "action-run-status" . }}{{ end }}`)},
155155
 		"repo/action_step_log.html":    {Data: []byte(`{{ define "page" }}STEPLOG={{ .Log.Job.Name }}:{{ .Log.Step.Name }}:{{ .Log.LogSource }}:{{ .Log.DownloadURL }}:{{ .Log.LogTruncated }};{{ with .Log.StreamHref }}STREAM={{ . }};{{ end }}{{ with .Log.LogError }}ERROR={{ . }};{{ end }}LOG={{ .Log.LogText }};{{ end }}`)},
156156
 		"repo/settings_secrets.html":   {Data: []byte(`{{ define "page" }}{{ with .Error }}ERROR={{ . }}{{ end }}{{ range .Secrets }}SECRET={{ .Name }};{{ end }}{{ range .Variables }}VAR={{ .Name }}:{{ .Value }};{{ end }}{{ end }}`)},
internal/web/static/css/shithub.cssmodified
@@ -5852,6 +5852,18 @@ button.shithub-repo-action {
58525852
   color: var(--fg-muted);
58535853
   font-size: 0.82rem;
58545854
 }
5855
+.shithub-actions-wait-reason {
5856
+  display: inline-flex;
5857
+  align-items: center;
5858
+  gap: 0.35rem;
5859
+  margin: 0 1rem 0.75rem;
5860
+  padding: 0.45rem 0.6rem;
5861
+  border: 1px solid var(--border-default);
5862
+  border-radius: 6px;
5863
+  background: var(--canvas-subtle);
5864
+  color: var(--fg-muted);
5865
+  font-size: 0.85rem;
5866
+}
58555867
 .shithub-actions-step-list {
58565868
   margin: 0;
58575869
   padding: 0;
internal/web/templates/repo/action_run.htmlmodified
@@ -128,6 +128,9 @@
128128
                 {{ end }}
129129
               </div>
130130
             {{ end }}
131
+            {{ if .WaitReason }}
132
+              <div class="shithub-actions-wait-reason">{{ octicon "clock" }} {{ .WaitReason }}</div>
133
+            {{ end }}
131134
             <ol class="shithub-actions-step-list">
132135
               {{ range .Steps }}
133136
                 <li>