Skip to content

Commit 3090f46

Browse files
committed
fix(actions): auto-expand running steps reliably
1 parent 62ae113 commit 3090f46

File tree

1 file changed

+15
-64
lines changed

1 file changed

+15
-64
lines changed

web_src/js/components/RepoActionView.vue

Lines changed: 15 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import {SvgIcon} from '../svg.ts';
33
import ActionRunStatus from './ActionRunStatus.vue';
4-
import {defineComponent, nextTick, type PropType} from 'vue';
4+
import {defineComponent, type PropType} from 'vue';
55
import {createElementFromAttrs, toggleElem} from '../utils/dom.ts';
66
import {formatDatetime} from '../utils/time.ts';
77
import {renderAnsi} from '../render/ansi.ts';
@@ -104,7 +104,6 @@ export default defineComponent({
104104
// internal state
105105
loadingAbortController: null as AbortController | null,
106106
intervalID: null as IntervalId | null,
107-
mutationObserver: null as MutationObserver | null, // Observer for auto-expand/auto-scroll functionality
108107
currentJobStepsStates: [] as Array<Record<string, any>>,
109108
artifacts: [] as Array<Record<string, any>>,
110109
menuVisible: false,
@@ -186,72 +185,11 @@ export default defineComponent({
186185
document.body.addEventListener('click', this.closeDropdown);
187186
this.hashChangeListener();
188187
window.addEventListener('hashchange', this.hashChangeListener);
189-
190-
// === Auto Expand + Auto Scroll Fix (for Issue #35570) ===
191-
// Ensure Vue has updated DOM for steps after initial load
192-
await nextTick();
193-
194-
// Set up observer on the steps container (safer and more efficient than document.body)
195-
const stepsContainer = (this.$refs.steps as HTMLElement);
196-
if (stepsContainer && typeof MutationObserver !== 'undefined') {
197-
this.mutationObserver = new MutationObserver((mutations) => {
198-
for (const m of mutations) {
199-
// Auto-scroll new log lines as they appear
200-
if (m.type === 'childList') {
201-
for (const n of m.addedNodes) {
202-
if (n.nodeType === 1 && (n as Element).classList.contains('job-log-line')) {
203-
if (this.optionAlwaysAutoScroll) {
204-
try { (n as Element).scrollIntoView({ behavior: 'smooth', block: 'end' }); }
205-
catch { (n as Element).scrollIntoView(); }
206-
}
207-
}
208-
}
209-
}
210-
211-
// Auto-expand running steps when their class changes
212-
if (m.type === 'attributes' && m.attributeName === 'class') {
213-
const t = m.target as Element;
214-
if (t.classList && t.classList.contains('job-step-summary')) {
215-
const stepAttr = t.getAttribute('data-step');
216-
if (!stepAttr) continue;
217-
const stepIndex = Number(stepAttr);
218-
// If expand-running option is on and step is expandable but not selected, open it via state
219-
if (this.optionAlwaysExpandRunning &&
220-
t.classList.contains('step-expandable') &&
221-
!t.classList.contains('selected')) {
222-
// Update state inside nextTick to ensure Vue has finished rendering
223-
nextTick(() => {
224-
if (!this.currentJobStepsStates[stepIndex]?.expanded) {
225-
// Update internal state so logs are immediately loaded
226-
this.currentJobStepsStates[stepIndex].expanded = true;
227-
this.loadJob();
228-
}
229-
});
230-
}
231-
}
232-
}
233-
}
234-
});
235-
236-
// Observe only the steps container subtree (minimized observation area)
237-
this.mutationObserver.observe(stepsContainer, {
238-
childList: true,
239-
subtree: true,
240-
attributes: true,
241-
attributeFilter: ['class'],
242-
});
243-
}
244-
// === End Fix ===
245188
},
246189
247190
beforeUnmount() {
248191
document.body.removeEventListener('click', this.closeDropdown);
249192
window.removeEventListener('hashchange', this.hashChangeListener);
250-
// Clean up MutationObserver to prevent memory leaks
251-
if (this.mutationObserver) {
252-
this.mutationObserver.disconnect();
253-
this.mutationObserver = null;
254-
}
255193
},
256194
257195
unmounted() {
@@ -422,6 +360,19 @@ export default defineComponent({
422360
}
423361
}
424362
363+
// Auto-expand running steps if option is enabled (fix for Issue #35570)
364+
for (let i = 0; i < this.currentJob.steps.length; i++) {
365+
const step = this.currentJob.steps[i];
366+
const state = this.currentJobStepsStates[i];
367+
if (
368+
this.optionAlwaysExpandRunning &&
369+
step.status === 'running' &&
370+
!state.expanded
371+
) {
372+
state.expanded = true;
373+
}
374+
}
375+
425376
// find the step indexes that need to auto-scroll
426377
const autoScrollStepIndexes = new Map<number, boolean>();
427378
for (const logs of job.logs.stepsLog ?? []) {
@@ -630,7 +581,7 @@ export default defineComponent({
630581
</div>
631582
<div class="job-step-container" ref="steps" v-if="currentJob.steps.length">
632583
<div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
633-
<div class="job-step-summary" :data-step="i" @click.stop="isExpandable(jobStep.status) && toggleStepLogs(i)" :class="[currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']">
584+
<div class="job-step-summary" @click.stop="isExpandable(jobStep.status) && toggleStepLogs(i)" :class="[currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']">
634585
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
635586
currentJobStepsStates[i].cursor === null means the log is loaded for the first time
636587
-->

0 commit comments

Comments
 (0)