fix(actions): Fix how jobs in matrixes are grouped (#38980) (#38998)

Backport #38980 

The workflow graph decided which job rows belonged to the same matrix by
parsing display names: it stripped a trailing `" (...)"` off `name` and
grouped rows sharing the prefix. That guesses at a string the user
controls, and it fails both ways. `jobparser` only appends the `
(<combination>)` suffix when `name:` contains no `${{ }}`, so a leg
named `E2E on ${{ matrix.browser }}` never grouped, while two unrelated
jobs `build (fast)` and `build (slow)` folded into one bogus matrix
panel.

Matrix legs already have a real identity: expansion clones one row per
combination, all sharing the workflow's `JobID` and differing only in
`Name`. Group on that instead, so a matrix is whatever the backend says
it is. Matrix expansion state is keyed on the graph node id for the same
reason.

Closes https://github.com/go-gitea/gitea/issues/38975, though that
report's own example already groups on main, since `explicit (${{
matrix.leg }})` interpolates to a name that still ends in a suffix. The
interpolated shapes above are the broken ones.

## Screenshots:

Before:
<img width="1268" height="618" alt="image"
src="https://github.com/user-attachments/assets/2d98dd1f-5454-423c-b610-8db920f1e99c"
/>

after:
<img width="1145" height="607" alt="image"
src="https://github.com/user-attachments/assets/25da1804-8386-4f5b-a4e3-f63380010907"
/>


_Assisted-by: Claude Code:claude-opus-5_

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
bn-zr
2026-08-20 17:39:47 +02:00
committed by GitHub
parent d8e179f28f
commit 4e515cce99
4 changed files with 93 additions and 95 deletions
+14 -14
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import {computed, onMounted, onUnmounted, ref, watch} from 'vue';
import {computed, onMounted, onUnmounted, ref, shallowRef, watch} from 'vue';
import {SvgIcon} from '../svg.ts';
import ActionStatusIcon from './ActionStatusIcon.vue';
import {localUserSettings} from '../modules/user-settings.ts';
@@ -48,17 +48,17 @@ const graphContainer = ref<HTMLElement | null>(null);
const hoveredGraphId = ref<string | null>(null);
const stateKey = () => `${props.store.viewData.currentRun.repoId}-${props.workflowId}`;
const expandedMatrixKeys = ref<Set<string>>(new Set());
const expandedMatrixNodeIds = shallowRef<Set<string>>(new Set()); // always replaced wholesale, never mutated
function isMatrixExpanded(key: string): boolean {
return expandedMatrixKeys.value.has(key);
function isMatrixExpanded(nodeId: string): boolean {
return expandedMatrixNodeIds.value.has(nodeId);
}
function toggleMatrixExpanded(key: string) {
const next = new Set(expandedMatrixKeys.value);
if (next.has(key)) next.delete(key);
else next.add(key);
expandedMatrixKeys.value = next;
function toggleMatrixExpanded(nodeId: string) {
const next = new Set(expandedMatrixNodeIds.value);
if (next.has(nodeId)) next.delete(nodeId);
else next.add(nodeId);
expandedMatrixNodeIds.value = next;
}
const loadSavedState = () => {
@@ -86,7 +86,7 @@ const saveState = () => {
localUserSettings.setJsonObject(settingKeyStates, Object.fromEntries(sortedStates));
};
const graphModel = computed(() => createWorkflowGraphModel(props.jobs, expandedMatrixKeys.value));
const graphModel = computed(() => createWorkflowGraphModel(props.jobs, expandedMatrixNodeIds.value));
const jobsWithLayout = computed(() => graphModel.value.nodes);
const edges = computed(() => graphModel.value.edges);
const routedEdges = computed<RoutedEdge[]>(() => graphModel.value.routedEdges);
@@ -309,15 +309,15 @@ function onNodeClick(job: GraphNode | ActionsJob, event: MouseEvent) {
@mouseenter="handleNodeMouseEnter(job.id)"
@mouseleave="handleNodeMouseLeave"
>
<title>Matrix: {{ job.matrixKey }}</title>
<title>Matrix: {{ job.name }}</title>
<rect :x="job.x" :y="job.y" :width="nodeWidth" :height="job.displayHeight" rx="6" class="job-rect"/>
<foreignObject :x="job.x" :y="job.y" :width="nodeWidth" :height="job.displayHeight" class="matrix-foreign-object">
<div class="matrix-panel" xmlns="http://www.w3.org/1999/xhtml">
<div class="matrix-panel-label" @click.stop="toggleMatrixExpanded(job.matrixKey!)">Matrix: {{ job.matrixKey }}</div>
<div class="matrix-panel-label" @click.stop="toggleMatrixExpanded(job.id)">Matrix: {{ job.name }}</div>
<div
v-if="!isMatrixExpanded(job.matrixKey!)"
v-if="!isMatrixExpanded(job.id)"
class="matrix-panel-collapsed"
@click.stop="toggleMatrixExpanded(job.matrixKey!)"
@click.stop="toggleMatrixExpanded(job.id)"
>
<div class="matrix-panel-summary-row">
<ActionStatusIcon :status="job.status" icon-variant="circle-fill"/>