diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 73041ceee5..aa01e8699a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3773,6 +3773,9 @@ variables.creation.success = The variable "%s" has been added. variables.update.failed = Failed to edit variable. variables.update.success = The variable has been edited. +logs.always_auto_scroll = Always auto scroll logs +logs.always_expand_running = Always expand running logs + [projects] deleted.display_name = Deleted Project type-1.display_name = Individual Project diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index 46e302d634..f29b8e4046 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -31,7 +31,11 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte "##[endgroup]", } cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally - for i := 0; i < util.Iif(logCur.Step == 0, 3, 1); i++ { + mockCount := util.Iif(logCur.Step == 0, 3, 1) + if logCur.Step == 1 && logCur.Cursor == 0 { + mockCount = 30 // for the first batch, return as many as possible to test the auto-expand and auto-scroll + } + for i := 0; i < mockCount; i++ { logStr := mockedLogs[int(cur)%len(mockedLogs)] cur++ logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step)) @@ -56,6 +60,21 @@ func MockActionsRunsJobs(ctx *context.Context) { resp.State.Run.Status = actions_model.StatusRunning.String() resp.State.Run.CanCancel = true resp.State.Run.CanDeleteArtifact = true + resp.State.Run.WorkflowID = "workflow-id" + resp.State.Run.WorkflowLink = "./workflow-link" + resp.State.Run.Commit = actions.ViewCommit{ + ShortSha: "ccccdddd", + Link: "./commit-link", + Pusher: actions.ViewUser{ + DisplayName: "pusher user", + Link: "./pusher-link", + }, + Branch: actions.ViewBranch{ + Name: "commit-branch", + Link: "./branch-link", + IsDeleted: false, + }, + } resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{ Name: "artifact-a", Size: 100 * 1024, diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl index 1fa71c0e5f..9c6bdf13da 100644 --- a/templates/devtest/repo-action-view.tmpl +++ b/templates/devtest/repo-action-view.tmpl @@ -1,30 +1,9 @@ {{template "base/head" .}}
-
+ {{template "repo/actions/view_component" (dict + "RunIndex" 1 + "JobIndex" 2 + "ActionsURL" (print AppSubUrl "/devtest/actions-mock") + )}}
{{template "base/footer" .}} diff --git a/templates/repo/actions/view.tmpl b/templates/repo/actions/view.tmpl index f7b03608ee..bde579f882 100644 --- a/templates/repo/actions/view.tmpl +++ b/templates/repo/actions/view.tmpl @@ -2,33 +2,11 @@
{{template "repo/header" .}} -
-
+ {{template "repo/actions/view_component" (dict + "RunIndex" .RunIndex + "JobIndex" .JobIndex + "ActionsURL" .ActionsURL + )}}
{{template "base/footer" .}} diff --git a/templates/repo/actions/view_component.tmpl b/templates/repo/actions/view_component.tmpl new file mode 100644 index 0000000000..8d1de41f70 --- /dev/null +++ b/templates/repo/actions/view_component.tmpl @@ -0,0 +1,30 @@ +
+
diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index b083fb0b77..914c9e76de 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -43,6 +43,20 @@ function isLogElementInViewport(el: HTMLElement): boolean { return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width } +type LocaleStorageOptions = { + autoScroll: boolean; + expandRunning: boolean; +}; + +function getLocaleStorageOptions(): LocaleStorageOptions { + try { + const optsJson = localStorage.getItem('actions-view-options'); + if (optsJson) return JSON.parse(optsJson); + } catch {} + // if no options in localStorage, or failed to parse, return default options + return {autoScroll: true, expandRunning: false}; +} + const sfc = { name: 'RepoActionView', components: { @@ -56,7 +70,17 @@ const sfc = { locale: Object, }, + watch: { + optionAlwaysAutoScroll() { + this.saveLocaleStorageOptions(); + }, + optionAlwaysExpandRunning() { + this.saveLocaleStorageOptions(); + }, + }, + data() { + const {autoScroll, expandRunning} = getLocaleStorageOptions(); return { // internal state loadingAbortController: null, @@ -70,6 +94,8 @@ const sfc = { 'log-time-stamp': false, 'log-time-seconds': false, }, + optionAlwaysAutoScroll: autoScroll ?? false, + optionAlwaysExpandRunning: expandRunning ?? false, // provided by backend run: { @@ -147,6 +173,11 @@ const sfc = { }, methods: { + saveLocaleStorageOptions() { + const opts: LocaleStorageOptions = {autoScroll: this.optionAlwaysAutoScroll, expandRunning: this.optionAlwaysExpandRunning}; + localStorage.setItem('actions-view-options', JSON.stringify(opts)); + }, + // get the job step logs container ('.job-step-logs') getJobStepLogsContainer(stepIndex: number): HTMLElement { return this.$refs.logs[stepIndex]; @@ -228,8 +259,10 @@ const sfc = { }, shouldAutoScroll(stepIndex: number): boolean { + if (!this.optionAlwaysAutoScroll) return false; const el = this.getJobStepLogsContainer(stepIndex); - if (!el.lastChild) return false; + // if the logs container is empty, then auto-scroll if the step is expanded + if (!el.lastChild) return this.currentJobStepsStates[stepIndex].expanded; return isLogElementInViewport(el.lastChild); }, @@ -280,6 +313,7 @@ const sfc = { const abortController = new AbortController(); this.loadingAbortController = abortController; try { + const isFirstLoad = !this.run.status; const job = await this.fetchJobData(abortController); if (this.loadingAbortController !== abortController) return; @@ -289,9 +323,10 @@ const sfc = { // sync the currentJobStepsStates to store the job step states for (let i = 0; i < this.currentJob.steps.length; i++) { + const expanded = isFirstLoad && this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running'; if (!this.currentJobStepsStates[i]) { // initial states for job steps - this.currentJobStepsStates[i] = {cursor: null, expanded: false}; + this.currentJobStepsStates[i] = {cursor: null, expanded}; } } @@ -426,6 +461,8 @@ export function initRepositoryActionView() { skipped: el.getAttribute('data-locale-status-skipped'), blocked: el.getAttribute('data-locale-status-blocked'), }, + logsAlwaysAutoScroll: el.getAttribute('data-locale-logs-always-auto-scroll'), + logsAlwaysExpandRunning: el.getAttribute('data-locale-logs-always-expand-running'), }, }); view.mount(el); @@ -528,6 +565,17 @@ export function initRepositoryActionView() { {{ locale.showFullScreen }} + +
+ + + {{ locale.logsAlwaysAutoScroll }} + + + + {{ locale.logsAlwaysExpandRunning }} + +