Skip to content

Commit e52badf

Browse files
committed
fix
1 parent 3090f46 commit e52badf

File tree

2 files changed

+49
-33
lines changed

2 files changed

+49
-33
lines changed

routers/web/devtest/mock_actions.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package devtest
66
import (
77
mathRand "math/rand/v2"
88
"net/http"
9+
"slices"
910
"strconv"
1011
"strings"
1112
"time"
@@ -17,24 +18,28 @@ import (
1718
"code.gitea.io/gitea/services/context"
1819
)
1920

20-
func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewStepLog) {
21-
mockedLogs := []string{
22-
"::group::test group for: step={step}, cursor={cursor}",
23-
"in group msg for: step={step}, cursor={cursor}",
24-
"in group msg for: step={step}, cursor={cursor}",
25-
"in group msg for: step={step}, cursor={cursor}",
26-
"::endgroup::",
21+
type generateMockStepsLogOptions struct {
22+
mockCountFirst int
23+
mockCountGeneral int
24+
groupRepeat int
25+
}
26+
27+
func generateMockStepsLog(logCur actions.LogCursor, opts generateMockStepsLogOptions) (stepsLog []*actions.ViewStepLog) {
28+
var mockedLogs []string
29+
mockedLogs = append(mockedLogs, "::group::test group for: step={step}, cursor={cursor}")
30+
mockedLogs = append(mockedLogs, slices.Repeat([]string{"in group msg for: step={step}, cursor={cursor}"}, opts.groupRepeat)...)
31+
mockedLogs = append(mockedLogs, "::endgroup::")
32+
mockedLogs = append(mockedLogs,
2733
"message for: step={step}, cursor={cursor}",
2834
"message for: step={step}, cursor={cursor}",
2935
"##[group]test group for: step={step}, cursor={cursor}",
3036
"in group msg for: step={step}, cursor={cursor}",
3137
"##[endgroup]",
32-
}
33-
cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
34-
mockCount := util.Iif(logCur.Step == 0, 3, 1)
35-
if logCur.Step == 1 && logCur.Cursor == 0 {
36-
mockCount = 30 // for the first batch, return as many as possible to test the auto-expand and auto-scroll
37-
}
38+
)
39+
// usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
40+
cur := logCur.Cursor
41+
// for the first batch, return as many as possible to test the auto-expand and auto-scroll
42+
mockCount := util.Iif(logCur.Cursor == 0, opts.mockCountFirst, opts.mockCountGeneral)
3843
for i := 0; i < mockCount; i++ {
3944
logStr := mockedLogs[int(cur)%len(mockedLogs)]
4045
cur++
@@ -127,21 +132,28 @@ func MockActionsRunsJobs(ctx *context.Context) {
127132
Duration: "3h",
128133
})
129134

135+
var mockLogOptions []generateMockStepsLogOptions
130136
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
131137
Summary: "step 0 (mock slow)",
132138
Duration: time.Hour.String(),
133139
Status: actions_model.StatusRunning.String(),
134140
})
141+
mockLogOptions = append(mockLogOptions, generateMockStepsLogOptions{mockCountFirst: 30, mockCountGeneral: 1, groupRepeat: 3})
142+
135143
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
136144
Summary: "step 1 (mock fast)",
137145
Duration: time.Hour.String(),
138146
Status: actions_model.StatusRunning.String(),
139147
})
148+
mockLogOptions = append(mockLogOptions, generateMockStepsLogOptions{mockCountFirst: 30, mockCountGeneral: 3, groupRepeat: 20})
149+
140150
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
141151
Summary: "step 2 (mock error)",
142152
Duration: time.Hour.String(),
143153
Status: actions_model.StatusRunning.String(),
144154
})
155+
mockLogOptions = append(mockLogOptions, generateMockStepsLogOptions{mockCountFirst: 30, mockCountGeneral: 3, groupRepeat: 3})
156+
145157
if len(req.LogCursors) == 0 {
146158
ctx.JSON(http.StatusOK, resp)
147159
return
@@ -156,7 +168,7 @@ func MockActionsRunsJobs(ctx *context.Context) {
156168
}
157169
doSlowResponse = doSlowResponse || logCur.Step == 0
158170
doErrorResponse = doErrorResponse || logCur.Step == 2
159-
resp.Logs.StepsLog = append(resp.Logs.StepsLog, generateMockStepsLog(logCur)...)
171+
resp.Logs.StepsLog = append(resp.Logs.StepsLog, generateMockStepsLog(logCur, mockLogOptions[logCur.Step])...)
160172
}
161173
if doErrorResponse {
162174
if mathRand.Float64() > 0.5 {

web_src/js/components/RepoActionView.vue

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ type Step = {
4040
status: RunStatus,
4141
}
4242
43+
type JobStepState = {
44+
cursor: string|null,
45+
expanded: boolean,
46+
manuallyCollapsed: boolean, // whether the user manually collapsed the step, used to avoid auto-expanding it again
47+
}
48+
4349
function parseLineCommand(line: LogLine): LogLineCommand | null {
4450
for (const prefix of LogLinePrefixesGroup) {
4551
if (line.message.startsWith(prefix)) {
@@ -56,7 +62,8 @@ function parseLineCommand(line: LogLine): LogLineCommand | null {
5662
5763
function isLogElementInViewport(el: Element): boolean {
5864
const rect = el.getBoundingClientRect();
59-
return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width
65+
// only check whether bottom is in viewport, because the log element can be a log group which is usually tall
66+
return 0 <= rect.bottom && rect.bottom <= window.innerHeight + 10;
6067
}
6168
6269
type LocaleStorageOptions = {
@@ -104,7 +111,7 @@ export default defineComponent({
104111
// internal state
105112
loadingAbortController: null as AbortController | null,
106113
intervalID: null as IntervalId | null,
107-
currentJobStepsStates: [] as Array<Record<string, any>>,
114+
currentJobStepsStates: [] as Array<JobStepState>,
108115
artifacts: [] as Array<Record<string, any>>,
109116
menuVisible: false,
110117
isFullScreen: false,
@@ -252,6 +259,8 @@ export default defineComponent({
252259
this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
253260
if (this.currentJobStepsStates[idx].expanded) {
254261
this.loadJobForce(); // try to load the data immediately instead of waiting for next timer interval
262+
} else if (this.currentJob.steps[idx].status === 'running') {
263+
this.currentJobStepsStates[idx].manuallyCollapsed = true;
255264
}
256265
},
257266
// cancel a run
@@ -343,7 +352,6 @@ export default defineComponent({
343352
const abortController = new AbortController();
344353
this.loadingAbortController = abortController;
345354
try {
346-
const isFirstLoad = !this.run.status;
347355
const job = await this.fetchJobData(abortController);
348356
if (this.loadingAbortController !== abortController) return;
349357
@@ -353,23 +361,15 @@ export default defineComponent({
353361
354362
// sync the currentJobStepsStates to store the job step states
355363
for (let i = 0; i < this.currentJob.steps.length; i++) {
356-
const expanded = isFirstLoad && this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running';
364+
const autoExpand = this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running';
357365
if (!this.currentJobStepsStates[i]) {
358366
// initial states for job steps
359-
this.currentJobStepsStates[i] = {cursor: null, expanded};
360-
}
361-
}
362-
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;
367+
this.currentJobStepsStates[i] = {cursor: null, expanded: autoExpand, manuallyCollapsed: false};
368+
} else {
369+
// if the step is not manually collapsed by user, then auto-expand it if option is enabled
370+
if (autoExpand && !this.currentJobStepsStates[i].manuallyCollapsed) {
371+
this.currentJobStepsStates[i].expanded = true;
372+
}
373373
}
374374
}
375375
@@ -393,7 +393,10 @@ export default defineComponent({
393393
if (!autoScrollStepIndexes.get(stepIndex)) continue;
394394
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
395395
}
396-
autoScrollJobStepElement?.lastElementChild.scrollIntoView({behavior: 'smooth', block: 'nearest'});
396+
const lastLogElem = autoScrollJobStepElement?.lastElementChild;
397+
if (lastLogElem && !isLogElementInViewport(lastLogElem)) {
398+
lastLogElem.scrollIntoView({behavior: 'smooth', block: 'end'});
399+
}
397400
398401
// clear the interval timer if the job is done
399402
if (this.run.done && this.intervalID) {
@@ -432,6 +435,7 @@ export default defineComponent({
432435
this.isFullScreen = !this.isFullScreen;
433436
toggleFullScreen('.action-view-right', this.isFullScreen, '.action-view-body');
434437
},
438+
435439
async hashChangeListener() {
436440
const selectedLogStep = window.location.hash;
437441
if (!selectedLogStep) return;

0 commit comments

Comments
 (0)