enhance(actions): show the steps the runner reports (#39720)

Gitea derives the steps of a job from the workflow file, so the `pre`
and `post` steps of actions only show up inside "Set up job" and
"Complete job".
Fixes https://github.com/go-gitea/gitea/issues/24604.

A runner can now report the steps it actually runs, and Gitea shows
those instead. Runners that don't report them keep the current view, and
older Gitea versions ignore them.

Related:
- Proto: https://gitea.com/gitea/actions-proto-def/pulls/19
- Runner: https://gitea.com/gitea/runner/pulls/1293
This commit is contained in:
bircni authored and GitHub committed 2026-10-12 22:36:25 +02:00
1 parent c501255d47
commit 4b5f1f46de
11 files changed
+337 -29

No files matched your search

+22 -3
View File
@@ -334,8 +334,12 @@ async function loadJob() {
store.viewData.runArtifacts = runJobResp.artifacts || [];
store.viewData.currentRun = runJobResp.state.run;
const previousSteps = currentJob.value.steps;
currentJob.value = runJobResp.state.currentJob;
const jobLogs = runJobResp.logs.stepsLog ?? [];
// a runner reporting its own steps can change them while the job runs, which moves the logs of each step position
const stepsChanged = previousSteps.length > 0 && !isSameSteps(previousSteps, currentJob.value.steps);
if (stepsChanged) resetStepsLogs(previousSteps);
const jobLogs = stepsChanged ? [] : runJobResp.logs.stepsLog ?? [];
// sync the currentJobStepsStates to store the job step states
for (let i = 0; i < currentJob.value.steps.length; i++) {
@@ -381,8 +385,8 @@ async function loadJob() {
lastLogElem.scrollIntoView({behavior: 'smooth', block: 'end'});
}
// clear the interval timer if the job is done
if (run.value.done && intervalID) {
// clear the interval timer if the job is done, the logs of changed steps still need to be loaded
if (run.value.done && intervalID && !stepsChanged) {
clearInterval(intervalID);
intervalID = null;
}
@@ -395,6 +399,21 @@ async function loadJob() {
}
}
function isSameSteps(a: Array<Step>, b: Array<Step>) {
return a.length === b.length && a.every((step, idx) => step.summary === b[idx].summary);
}
function resetStepsLogs(previousSteps: Array<Step>) {
const expandedSummaries = new Set(previousSteps.filter((_, idx) => currentJobStepsStates.value[idx].expanded).map((step) => step.summary));
currentJobStepsStates.value = currentJob.value.steps.map((step) => ({cursor: null, expanded: expandedSummaries.has(step.summary), manuallyCollapsed: false}));
stepAnsiRenderers.length = 0;
for (const el of jobStepLogs.value) {
if (!el) continue;
el.replaceChildren();
el._stepLogsActiveContainer = undefined;
}
}
function isDone(status: ActionsStatus) {
return ['success', 'skipped', 'failure', 'cancelled'].includes(status);
}