enhance: Adjust Workflow Graph styling (#37497)

- Fix workflow dependency graph overflow by making the graph container
scrollable (no more clipped DAGs; addresses #37493).
- Improve Actions job list readability by keeping durations
fixed-width/right-aligned so long times don’t squeeze job names.
- Make workflow graph layout more intuitive by vertically centering
shorter columns to reduce misleading “looks like it depends on”
alignments (addresses #37395).

### Screenshot
<img width="966" height="439"
src="https://github.com/user-attachments/assets/c180c5a2-4f56-4287-bcaa-f2735ba72949"
/>

<img width="949" height="559"
src="https://github.com/user-attachments/assets/a383511d-a962-4920-b792-69f556847eff"
/>



Fixes #37493
Fixes #37395

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
bircni
2026-06-07 16:45:20 +00:00
committed by GitHub
co-authored by silverwind Claude wxiaoguang
parent ea35af1b68
commit 1c289df6eb
8 changed files with 1246 additions and 597 deletions
+70 -1
View File
@@ -214,6 +214,75 @@ func MockActionsRunsJobs(ctx *context.Context) {
return fmt.Sprintf("%s/jobs/%d", resp.State.Run.Link, jobID)
}
// Keep devtest mock runs minimal: use run 10 as a "complex graph" repro.
// This combines long durations, parallel roots, and a multi-dependency downstream job
// to validate the workflow graph rendering.
if runID == 10 {
resp.State.Run.WorkflowID = "workflow-devtest-complex"
resp.State.Run.Duration = "7h 12m 34s"
type mj struct {
jobID string
name string
status actions_model.Status
duration string
needs []string
}
mockJobs := []mj{
{jobID: "job-100", name: "job-100", status: actions_model.StatusSuccess, duration: "3s", needs: nil},
{jobID: "job-101", name: "job-101", status: actions_model.StatusSuccess, duration: "3s", needs: []string{"job-100"}},
{jobID: "job-102", name: "job-102", status: actions_model.StatusSuccess, duration: "4s", needs: []string{"job-100", "job-101"}},
{jobID: "job-103", name: "job-103", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"job-100"}},
{jobID: "prep-jdk", name: "prep-jdk", status: actions_model.StatusSuccess, duration: "3s", needs: nil},
{jobID: "code-analysis", name: "code-analysis", status: actions_model.StatusSuccess, duration: "3s", needs: nil},
// Matrix expansion (the " (...)" suffix is the heuristic the frontend uses to group rows)
{jobID: "matrix-e2e-1-chromium", name: "matrix-e2e (1, chromium)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}},
{jobID: "matrix-e2e-1-firefox", name: "matrix-e2e (1, firefox)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}},
{jobID: "matrix-e2e-2-chromium", name: "matrix-e2e (2, chromium)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}},
{jobID: "matrix-e2e-3-chromium", name: "matrix-e2e (3, chromium)", status: actions_model.StatusSuccess, duration: "4s", needs: []string{"prep-jdk"}},
{jobID: "matrix-e2e-3-firefox", name: "matrix-e2e (3, firefox)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}},
{jobID: "matrix-e2e-99-webkit", name: "matrix-e2e (99, webkit)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}},
{jobID: "unit-test", name: "unit-test", status: actions_model.StatusSuccess, duration: "3s", needs: []string{"prep-jdk"}},
{jobID: "arch-test", name: "arch-test", status: actions_model.StatusSuccess, duration: "3s", needs: []string{"prep-jdk"}},
{jobID: "integration-test", name: "integration-test", status: actions_model.StatusSuccess, duration: "4s", needs: []string{"prep-jdk"}},
{jobID: "build-image", name: "build-image", status: actions_model.StatusSuccess, duration: "3s", needs: []string{
"unit-test",
"arch-test",
"integration-test",
"code-analysis",
"matrix-e2e-1-chromium",
"matrix-e2e-1-firefox",
"matrix-e2e-2-chromium",
"matrix-e2e-3-chromium",
"matrix-e2e-3-firefox",
"matrix-e2e-99-webkit",
}},
}
resp.State.Run.Jobs = nil
for i, j := range mockJobs {
id := runID*1000 + int64(i)
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: id,
Link: jobLink(id),
JobID: j.jobID,
Name: j.name,
Status: j.status.String(),
CanRerun: j.jobID == "job-100",
Duration: j.duration,
Needs: j.needs,
})
}
fillViewRunResponseCurrentJob(ctx, resp)
ctx.JSON(http.StatusOK, resp)
return
}
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID * 10,
Link: jobLink(runID * 10),
@@ -240,7 +309,7 @@ func MockActionsRunsJobs(ctx *context.Context) {
Name: "ULTRA LOOOOOOOOOOOONG job name 102 that exceeds the limit",
Status: actions_model.StatusFailure.String(),
CanRerun: false,
Duration: "3h",
Duration: "3h35m10s",
Needs: []string{"job-100", "job-101"},
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{