Skip to content

Commit 92fb8bb

Browse files
authored
Merge branch 'main' into lunny/move_archive_gitrepo
2 parents 9813dbe + 0a0baeb commit 92fb8bb

File tree

5 files changed

+85
-27
lines changed

5 files changed

+85
-27
lines changed

options/locale/locale_fr-FR.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ copy_path=Copier le chemin
109109
copy_success=Copié !
110110
copy_error=Échec de la copie
111111
copy_type_unsupported=Ce type de fichier ne peut pas être copié
112+
copy_filename=Copier le nom du fichier
112113
113114
write=Écrire
114115
preview=Aperçu
@@ -3732,6 +3733,9 @@ settings.link.select=Sélectionner un dépôt
37323733
settings.link.button=Actualiser le lien du dépôt
37333734
settings.link.success=Le lien du dépôt a été mis à jour avec succès.
37343735
settings.link.error=Impossible de mettre à jour le lien du dépôt.
3736+
settings.link.repo_not_found=Dépôt %s non trouvé.
3737+
settings.unlink.error=Impossible de supprimer le lien du dépôt.
3738+
settings.unlink.success=Le lien du dépôt a été supprimé.
37353739
settings.delete=Supprimer le paquet
37363740
settings.delete.description=Supprimer un paquet est permanent et irréversible.
37373741
settings.delete.notice=Vous êtes sur le point de supprimer %s (%s). Cette opération est irréversible, êtes-vous sûr ?

options/locale/locale_ga-IE.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,6 +2434,9 @@ settings.event_workflow_job_desc=Gitea Actions Sreabhadh oibre post ciúáilte,
24342434
settings.event_package=Pacáiste
24352435
settings.event_package_desc=Pacáiste a cruthaíodh nó a scriosadh i stóras.
24362436
settings.branch_filter=Scagaire brainse
2437+
settings.branch_filter_desc_1=Liosta ceadanna brainse (agus ainm tagartha) le haghaidh imeachtaí brú, cruthaithe brainse agus scriosadh brainse, sonraithe mar phatrún glob. Más folamh nó <code>*</code> é, tuairiscítear imeachtaí do na brainsí agus na clibeanna uile.
2438+
settings.branch_filter_desc_2=Úsáid réimír <code>refs/heads/</code> nó <code>refs/tags/</code> chun ainmneacha tagartha iomlána a mheaitseáil.
2439+
settings.branch_filter_desc_doc=Féach ar dhoiciméadú <a href="%[1]s">%[2]s</a> le haghaidh comhréir.
24372440
settings.authorization_header=Ceanntásc Údaraithe
24382441
settings.authorization_header_desc=Cuirfear san áireamh mar cheanntásc údaraithe d'iarratais nuair a bheidh ann Samplaí: %s.
24392442
settings.active=Gníomhach

options/locale/locale_pt-PT.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ copy_path=Copiar caminho
109109
copy_success=Copiado!
110110
copy_error=Falha ao copiar
111111
copy_type_unsupported=Este tipo de ficheiro não pode ser copiado
112+
copy_filename=Copiar o nome do ficheiro
112113

113114
write=Escrever
114115
preview=Pré-visualizar
@@ -2433,6 +2434,9 @@ settings.event_workflow_job_desc=O trabalho da sequência de trabalho das opera
24332434
settings.event_package=Pacote
24342435
settings.event_package_desc=Pacote criado ou eliminado num repositório.
24352436
settings.branch_filter=Filtro de ramos
2437+
settings.branch_filter_desc_1=A lista de permissão do ramo (e nome de referência) para eventos de envio, criação de ramos e eliminação de ramos, especificada como padrão glob. Se estiver em branco ou for <code>*</code>, serão reportados eventos para todos os ramos e etiquetas.
2438+
settings.branch_filter_desc_2=Use um prefixo <code>refs/heads/</code> ou <code>refs/tags/</code> para corresponder aos nomes completos de referência.
2439+
settings.branch_filter_desc_doc=Consulte a documentação <a href="%[1]s">%[2]s</a> para ver a sintaxe.
24362440
settings.authorization_header=Cabeçalho de Autorização
24372441
settings.authorization_header_desc=Será incluído como cabeçalho de autorização para pedidos, quando estiver presente. Exemplos: %s.
24382442
settings.active=Em funcionamento

routers/web/devtest/mock_actions.go

Lines changed: 27 additions & 15 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,25 +18,29 @@ 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-
for i := 0; i < mockCount; i++ {
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)
43+
for range mockCount {
3944
logStr := mockedLogs[int(cur)%len(mockedLogs)]
4045
cur++
4146
logStr = strings.ReplaceAll(logStr, "{step}", strconv.Itoa(logCur.Step))
@@ -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: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import {SvgIcon} from '../svg.ts';
33
import ActionRunStatus from './ActionRunStatus.vue';
44
import {defineComponent, type PropType} from 'vue';
5-
import {createElementFromAttrs, toggleElem} from '../utils/dom.ts';
5+
import {addDelegatedEventListener, createElementFromAttrs, toggleElem} from '../utils/dom.ts';
66
import {formatDatetime} from '../utils/time.ts';
77
import {renderAnsi} from '../render/ansi.ts';
88
import {POST, DELETE} from '../modules/fetch.ts';
@@ -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)) {
@@ -54,9 +60,10 @@ function parseLineCommand(line: LogLine): LogLineCommand | null {
5460
return null;
5561
}
5662
57-
function isLogElementInViewport(el: Element): boolean {
63+
function isLogElementInViewport(el: Element, {extraViewPortHeight}={extraViewPortHeight: 0}): 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 + extraViewPortHeight;
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,
@@ -181,6 +188,19 @@ export default defineComponent({
181188
// load job data and then auto-reload periodically
182189
// need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
183190
await this.loadJob();
191+
192+
// auto-scroll to the bottom of the log group when it is opened
193+
// "toggle" event doesn't bubble, so we need to use 'click' event delegation to handle it
194+
addDelegatedEventListener(this.elStepsContainer(), 'click', 'summary.job-log-group-summary', (el, _) => {
195+
if (!this.optionAlwaysAutoScroll) return;
196+
const elJobLogGroup = el.closest('details.job-log-group') as HTMLDetailsElement;
197+
setTimeout(() => {
198+
if (elJobLogGroup.open && !isLogElementInViewport(elJobLogGroup)) {
199+
elJobLogGroup.scrollIntoView({behavior: 'smooth', block: 'end'});
200+
}
201+
}, 0);
202+
});
203+
184204
this.intervalID = setInterval(() => this.loadJob(), 1000);
185205
document.body.addEventListener('click', this.closeDropdown);
186206
this.hashChangeListener();
@@ -252,6 +272,8 @@ export default defineComponent({
252272
this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
253273
if (this.currentJobStepsStates[idx].expanded) {
254274
this.loadJobForce(); // try to load the data immediately instead of waiting for next timer interval
275+
} else if (this.currentJob.steps[idx].status === 'running') {
276+
this.currentJobStepsStates[idx].manuallyCollapsed = true;
255277
}
256278
},
257279
// cancel a run
@@ -293,7 +315,8 @@ export default defineComponent({
293315
const el = this.getJobStepLogsContainer(stepIndex);
294316
// if the logs container is empty, then auto-scroll if the step is expanded
295317
if (!el.lastChild) return this.currentJobStepsStates[stepIndex].expanded;
296-
return isLogElementInViewport(el.lastChild as Element);
318+
// use extraViewPortHeight to tolerate some extra "virtual view port" height (for example: the last line is partially visible)
319+
return isLogElementInViewport(el.lastChild as Element, {extraViewPortHeight: 5});
297320
},
298321
299322
appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) {
@@ -343,7 +366,6 @@ export default defineComponent({
343366
const abortController = new AbortController();
344367
this.loadingAbortController = abortController;
345368
try {
346-
const isFirstLoad = !this.run.status;
347369
const job = await this.fetchJobData(abortController);
348370
if (this.loadingAbortController !== abortController) return;
349371
@@ -353,10 +375,15 @@ export default defineComponent({
353375
354376
// sync the currentJobStepsStates to store the job step states
355377
for (let i = 0; i < this.currentJob.steps.length; i++) {
356-
const expanded = isFirstLoad && this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running';
378+
const autoExpand = this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running';
357379
if (!this.currentJobStepsStates[i]) {
358380
// initial states for job steps
359-
this.currentJobStepsStates[i] = {cursor: null, expanded};
381+
this.currentJobStepsStates[i] = {cursor: null, expanded: autoExpand, manuallyCollapsed: false};
382+
} else {
383+
// if the step is not manually collapsed by user, then auto-expand it if option is enabled
384+
if (autoExpand && !this.currentJobStepsStates[i].manuallyCollapsed) {
385+
this.currentJobStepsStates[i].expanded = true;
386+
}
360387
}
361388
}
362389
@@ -380,7 +407,10 @@ export default defineComponent({
380407
if (!autoScrollStepIndexes.get(stepIndex)) continue;
381408
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
382409
}
383-
autoScrollJobStepElement?.lastElementChild.scrollIntoView({behavior: 'smooth', block: 'nearest'});
410+
const lastLogElem = autoScrollJobStepElement?.lastElementChild;
411+
if (lastLogElem && !isLogElementInViewport(lastLogElem)) {
412+
lastLogElem.scrollIntoView({behavior: 'smooth', block: 'end'});
413+
}
384414
385415
// clear the interval timer if the job is done
386416
if (this.run.done && this.intervalID) {
@@ -408,9 +438,13 @@ export default defineComponent({
408438
if (this.menuVisible) this.menuVisible = false;
409439
},
410440
441+
elStepsContainer(): HTMLElement {
442+
return this.$refs.stepsContainer as HTMLElement;
443+
},
444+
411445
toggleTimeDisplay(type: 'seconds' | 'stamp') {
412446
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
413-
for (const el of (this.$refs.steps as HTMLElement).querySelectorAll(`.log-time-${type}`)) {
447+
for (const el of this.elStepsContainer().querySelectorAll(`.log-time-${type}`)) {
414448
toggleElem(el, this.timeVisible[`log-time-${type}`]);
415449
}
416450
},
@@ -419,6 +453,7 @@ export default defineComponent({
419453
this.isFullScreen = !this.isFullScreen;
420454
toggleFullScreen('.action-view-right', this.isFullScreen, '.action-view-body');
421455
},
456+
422457
async hashChangeListener() {
423458
const selectedLogStep = window.location.hash;
424459
if (!selectedLogStep) return;
@@ -431,7 +466,7 @@ export default defineComponent({
431466
// so logline can be selected by querySelector
432467
await this.loadJob();
433468
}
434-
const logLine = (this.$refs.steps as HTMLElement).querySelector(selectedLogStep);
469+
const logLine = this.elStepsContainer().querySelector(selectedLogStep);
435470
if (!logLine) return;
436471
logLine.querySelector<HTMLAnchorElement>('.line-num').click();
437472
},
@@ -566,7 +601,7 @@ export default defineComponent({
566601
</div>
567602
</div>
568603
</div>
569-
<div class="job-step-container" ref="steps" v-if="currentJob.steps.length">
604+
<div class="job-step-container" ref="stepsContainer" v-if="currentJob.steps.length">
570605
<div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
571606
<div class="job-step-summary" @click.stop="isExpandable(jobStep.status) && toggleStepLogs(i)" :class="[currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']">
572607
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon

0 commit comments

Comments
 (0)