Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Give workflows and jobs a name and description

1cde8b9ea2ba1483beb90f19cde0f58aede2a661 · Keenan Nemetz · 8d ago · parent 30de4a44

Give workflows and jobs a name and description

The workflows page showed the file path and job IDs, so a reader had to
open the YAML comments to learn what a workflow was for. Add optional
top-level and job-level name and description fields, default them to the
file stem and job ID, pin them with a queued run, and show them on the
workflows and run pages.

Also document what the workflow format still cannot express, and state
that CI jobs are independent and unordered: only a local run uses the
declaration order.

Part of: #138

ci/workflow_test.go +30

@@ -8,11 +8,15 @@ import (
8 8 )
9 9
10 10 const validCI = `version: 1
11 name: Verify main
12 description: Build, test and lint every push to main.
11 13 on:
12 14 push:
13 15 branches: [main, "release/*"]
14 16 jobs:
15 17 test:
18 name: Unit tests
19 description: Runs the test suite.
16 20 image: alpine:3.22
17 21 steps:
18 22 - name: build
@@ -30,6 +34,12 @@ func TestValidWorkflowParses(t *testing.T) {
30 34 if len(wf.Jobs) != 1 || wf.Jobs[0].ID != "test" {
31 35 t.Fatalf("jobs = %+v", wf.Jobs)
32 36 }
37 if wf.Name != "Verify main" || wf.Description != "Build, test and lint every push to main." {
38 t.Fatalf("workflow metadata = %q / %q", wf.Name, wf.Description)
39 }
40 if wf.Jobs[0].Name != "Unit tests" || wf.Jobs[0].Description != "Runs the test suite." {
41 t.Fatalf("job metadata = %q / %q", wf.Jobs[0].Name, wf.Jobs[0].Description)
42 }
33 43 if got := len(wf.Jobs[0].Steps); got != 2 {
34 44 t.Fatalf("steps = %d, want 2", got)
35 45 }
@@ -51,6 +61,24 @@ func TestValidWorkflowParses(t *testing.T) {
51 61 }
52 62 }
53 63
64 func TestNamesDefaultToTheFileAndJobID(t *testing.T) {
65 t.Parallel()
66 wf, problems := Parse(".vole/workflows/verify.yaml",
67 "version: 1\njobs:\n test:\n image: alpine:3.22\n steps:\n - run: echo hi\n")
68 if len(problems) > 0 {
69 t.Fatalf("rejected: %v", problems)
70 }
71 if wf.Name != "verify" {
72 t.Fatalf("default workflow name = %q, want the file base name", wf.Name)
73 }
74 if wf.Description != "" || wf.Jobs[0].Description != "" {
75 t.Fatalf("an absent description became %q / %q", wf.Description, wf.Jobs[0].Description)
76 }
77 if wf.Jobs[0].Name != "test" {
78 t.Fatalf("default job name = %q, want the job ID", wf.Jobs[0].Name)
79 }
80 }
81
54 82 func TestBranchesDefaultToAll(t *testing.T) {
55 83 t.Parallel()
56 84 wf, problems := Parse("w.yaml", "version: 1\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo hi\n")
@@ -83,6 +111,8 @@ func TestInvalidWorkflowsRejected(t *testing.T) {
83 111 {"typo key", "version: 1\njobs:\n j:\n run-on: x\n image: alpine:3.22\n steps:\n - run: echo\n", "is not a known key"},
84 112 {"empty", "", "empty"},
85 113 {"bad glob", "version: 1\non:\n push:\n branches: [\"a b\"]\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n", "not a branch pattern"},
114 {"multiline description", "version: 1\ndescription: \"one\\ntwo\"\njobs:\n j:\n image: alpine:3.22\n steps:\n - run: echo\n", "must be one line"},
115 {"long job description", "version: 1\njobs:\n j:\n image: alpine:3.22\n description: \"" + strings.Repeat("x", MaxDescriptionBytes+1) + "\"\n steps:\n - run: echo\n", "limit"},
86 116 }
87 117 for _, tc := range cases {
88 118 t.Run(tc.name, func(t *testing.T) {