mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-06 18:57:34 +02:00
enhance(actions): improve matrix job titles, bump actionslib (#39485)
Bump actionslib to v1.3.0 and format matrix job titles like GitHub.
- Matrix values are listed in declaration order, nested arrays and
objects are flattened, null and empty values are skipped
- Expression-evaluated names are trimmed and fall back to the job ID
when blank
- Matrix `include` and `exclude` match keys case-insensitively and
coerce numbers like GitHub
| Matrix | Before | After |
|---|---|---|
| `v: ["a,b"]` | `job (a,b)` | `job (a,b)` |
| `v: ["a, b"]` | `job (a, b)` | `job (a, b)` |
| `v: [[a, b]]` | `job ([a b])` | `job (a, b)` |
| `os: [x], arch: [y]` | `job (y, x)` | `job (x, y)` |
| `v: [{t: a, p: "b,c"}]` | `job (map[p:b,c t:a])` | `job (a, b,c)` |
| `v: [{t: a, p: [b, c]}]` | `job (map[p:[b c] t:a])` | `job (a, b, c)`
|
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
6 files changed
+81
-43
No files matched your search
@@ -8,7 +8,7 @@ require (
|
||||
connectrpc.com/connect v1.21.0
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
gitea.dev/actionslib v1.2.1
|
||||
gitea.dev/actionslib v1.3.0
|
||||
gitea.dev/sdk v1.2.0
|
||||
github.com/42wim/httpsig v1.2.4
|
||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
||||
@@ -135,7 +135,7 @@ require (
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.2 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.4 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
|
||||
@@ -12,8 +12,8 @@ gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHq
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
gitea.dev/actionslib v1.2.1 h1:GL//K/0zIZV6h1OOksv1awvFVg0rg7fug2xWcG7mkG0=
|
||||
gitea.dev/actionslib v1.2.1/go.mod h1:1+gqOKGSEPn2IFHgY8S3GC5Ld+Hn8jF3OxiFHQ/fpx4=
|
||||
gitea.dev/actionslib v1.3.0 h1:xZRoTL1+sK/PaglT9uXkZJ9g0nk1WLMEH4FUXdXpKKw=
|
||||
gitea.dev/actionslib v1.3.0/go.mod h1:svCefEsavx4jmn8vJ4wTG7Q0KBcGfrFmGUjrhxuNUAQ=
|
||||
gitea.dev/sdk v1.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
|
||||
gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
|
||||
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||
@@ -108,8 +108,8 @@ github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaT
|
||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
|
||||
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/actionslib/pkg/expreval"
|
||||
@@ -128,7 +127,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
||||
}
|
||||
}
|
||||
// Keep accepting empty exclude mappings for workflow compatibility, although GitHub rejects them.
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).GetMatrixes()
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).MatrixCombinations()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getMatrixes: %w", err)
|
||||
}
|
||||
@@ -169,7 +168,7 @@ func ExpandMatrixWithNeeds(jobID string, job *Job, gitCtx *model.GithubContext,
|
||||
if err := job.Strategy.resolve(expreval.New(NewInterpeter(jobID, nil, nil, gitCtx, results, vars, inputs).Evaluate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).GetMatrixes()
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).MatrixCombinations()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getMatrixes: %w", err)
|
||||
}
|
||||
@@ -224,33 +223,24 @@ func replaceScalars(node *yaml.Node, replace func(string) string) {
|
||||
|
||||
// buildMatrixCombos builds one Job per matrix combination from src, baking the combination into the
|
||||
// strategy and interpolating the name, runs-on and continue-on-error with it.
|
||||
func buildMatrixCombos(jobID string, src *Job, matrixes []map[string]any, gitCtx *model.GithubContext, results map[string]*JobResult, vars map[string]string, inputs map[string]any) ([]*Job, error) {
|
||||
func buildMatrixCombos(jobID string, src *Job, matrixes []model.MatrixCombination, gitCtx *model.GithubContext, results map[string]*JobResult, vars map[string]string, inputs map[string]any) ([]*Job, error) {
|
||||
order, names := make([]int, len(matrixes)), make([]string, len(matrixes))
|
||||
for index, matrix := range matrixes {
|
||||
order[index], names[index] = index, matrixName(matrix)
|
||||
order[index], names[index] = index, matrixName(matrix.NameValues)
|
||||
}
|
||||
slices.SortStableFunc(order, func(a, b int) int { return strings.Compare(names[a], names[b]) })
|
||||
combos := make([]*Job, 0, len(matrixes))
|
||||
var err error
|
||||
for _, index := range order {
|
||||
matrix := matrixes[index]
|
||||
matrix := matrixes[index].Values
|
||||
combo := src.Clone()
|
||||
if combo.Name == "" {
|
||||
combo.Name = jobID
|
||||
}
|
||||
combo.Strategy.RawMatrix = encodeMatrix(matrix)
|
||||
replaceScalars(&combo.Strategy.RawMatrix, escapeExpressions)
|
||||
if src.Strategy.RawMatrix.Kind != 0 {
|
||||
combo.Strategy.JobIndex, combo.Strategy.JobTotal = index, len(matrixes)
|
||||
}
|
||||
evaluator := expreval.New(NewInterpeter(jobID, &combo.Strategy, matrix, gitCtx, results, vars, inputs).Evaluate)
|
||||
if len(matrix) == 0 && gitCtx != nil {
|
||||
combo.Name, err = evaluator.Interpolate(combo.Name)
|
||||
combo.Name = escapeExpressions(combo.Name)
|
||||
} else {
|
||||
combo.Name, err = nameWithMatrix(combo.Name, matrix, evaluator)
|
||||
}
|
||||
if err != nil {
|
||||
if combo.Name, err = jobName(combo.Name, jobID, names[index], evaluator, len(matrix) > 0 || gitCtx != nil); err != nil {
|
||||
return nil, fmt.Errorf("interpolate name for job %q: %w", jobID, err)
|
||||
}
|
||||
if gitCtx != nil { // callers without one don't read runs-on
|
||||
@@ -324,29 +314,36 @@ func encodeMatrix(matrix map[string]any) yaml.Node {
|
||||
return node
|
||||
}
|
||||
|
||||
func nameWithMatrix(name string, m map[string]any, evaluator expreval.Evaluator) (string, error) {
|
||||
if len(m) == 0 {
|
||||
// jobName trims names, gives plain text and lone string literals the suffix, and blank names the job ID
|
||||
func jobName(name, jobID, suffix string, evaluator expreval.Evaluator, evaluate bool) (string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if literal, ok := expreval.Literal(name); ok {
|
||||
if literal == "" {
|
||||
literal = jobID
|
||||
}
|
||||
return escapeExpressions(literal + suffix), nil
|
||||
}
|
||||
if !evaluate {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
if !strings.Contains(name, "${{") || !strings.Contains(name, "}}") {
|
||||
return escapeExpressions(name + " " + matrixName(m)), nil
|
||||
}
|
||||
|
||||
name, err := evaluator.Interpolate(name)
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
name = jobID
|
||||
}
|
||||
return escapeExpressions(name), err
|
||||
}
|
||||
|
||||
func matrixName(m map[string]any) string {
|
||||
ks := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
ks = append(ks, k)
|
||||
// matrixName formats the name suffix, skipping null and empty values
|
||||
func matrixName(values []any) string {
|
||||
var names []string
|
||||
for _, value := range values {
|
||||
if name := exprparser.CoerceToString(value); name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(ks)
|
||||
vs := make([]string, 0, len(m))
|
||||
for _, v := range ks {
|
||||
vs = append(vs, fmt.Sprint(m[v]))
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("(%s)", strings.Join(vs, ", "))
|
||||
return " (" + strings.Join(names, ", ") + ")"
|
||||
}
|
||||
@@ -312,6 +312,48 @@ func TestParseRunsOnFromJSONKeepsWhatGitHubRejectsForTheJobToFail(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJobNames(t *testing.T) {
|
||||
result, err := Parse([]byte(`on: push
|
||||
jobs:
|
||||
scalars:
|
||||
strategy: {matrix: {value: [[1.0, true, false, 0, 1000000000000000, '', null, {os: linux}], ['', null]]}}
|
||||
steps: [{run: echo}]
|
||||
trimmed:
|
||||
name: ' Trimmed '
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
blank:
|
||||
name: ' '
|
||||
steps: [{run: echo}]
|
||||
folded:
|
||||
name: "${{ ' Folded' }}"
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
computed:
|
||||
name: ${{ format(' {0} ', matrix.missing) }}
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
padded:
|
||||
name: ${{ format(' {0} ', matrix.v) }}
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
`), WithGitContext(&model.GithubContext{}))
|
||||
require.NoError(t, err)
|
||||
names := map[string][]string{}
|
||||
for _, parsed := range result {
|
||||
id, job := parsed.Job()
|
||||
names[id] = append(names[id], job.DisplayName())
|
||||
}
|
||||
assert.Equal(t, map[string][]string{
|
||||
"scalars": {"scalars", "scalars (1, true, false, 0, 1E+15, linux)"},
|
||||
"trimmed": {"Trimmed (a)"},
|
||||
"blank": {"blank"},
|
||||
"folded": {" Folded (a)"},
|
||||
"computed": {"computed"},
|
||||
"padded": {"a"},
|
||||
}, names)
|
||||
}
|
||||
|
||||
func TestJobFieldsWithoutMatrix(t *testing.T) {
|
||||
const workflow = `on: push
|
||||
jobs:
|
||||
|
||||
@@ -59,12 +59,13 @@ const wfTest1Jobs: ActionsJob[] = [
|
||||
const mockJob = (id: number, jobId: string, name: string, needs?: string[]): ActionsJob =>
|
||||
({id, link: '', jobId, name, status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '1s', needs});
|
||||
|
||||
test('matrix nodes key on job id, not on the display name', () => {
|
||||
test('matrix nodes key on job id and take the label legs share with or without a suffix', () => {
|
||||
const legs = createWorkflowGraphModel([mockJob(1, 'explicit', 'leg one'), mockJob(2, 'explicit', 'leg two')]);
|
||||
expect(legs.nodes).toHaveLength(1);
|
||||
expect(legs.nodes[0].type).toBe('matrix');
|
||||
expect(legs.nodes[0].name).toBe('explicit');
|
||||
expect(legs.nodes[0].jobs.map((j) => j.id)).toEqual([1, 2]);
|
||||
expect(createWorkflowGraphModel([mockJob(1, 'image', 'Image'), mockJob(2, 'image', 'Image (dind)')]).nodes[0].name).toBe('Image');
|
||||
|
||||
const lookalikes = createWorkflowGraphModel([
|
||||
mockJob(1, 'setup', 'setup'),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {cutString} from '../utils/string.ts';
|
||||
import type {ActionsJob, ActionsStatus} from '../modules/gitea-actions.ts';
|
||||
|
||||
export type GraphNodeType = 'job' | 'matrix' | 'group';
|
||||
@@ -89,10 +90,7 @@ function graphIdForJob(job: ActionsJob): string {
|
||||
|
||||
// matrix legs are named `<job name> (<combination>)`; a workflow-provided `name:` may not be
|
||||
function matrixLabel(matrixJobs: ActionsJob[], jobId: string): string {
|
||||
const prefixes = new Set(matrixJobs.map((job) => {
|
||||
const idx = job.name.indexOf(' (');
|
||||
return idx === -1 ? '' : job.name.slice(0, idx).trim();
|
||||
}));
|
||||
const prefixes = new Set(matrixJobs.map((job) => cutString(job.name, ' (')[0].trim()));
|
||||
const [prefix] = prefixes;
|
||||
return prefixes.size === 1 && prefix ? prefix : jobId;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user