0
0
Fork 0
mirror of https://github.com/go-gitea/gitea synced 2024-12-25 23:04:45 +01:00

Add auto-expanding running actions step (#30058)

Auto-expands the currently running action step.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
bytedream 2024-12-22 19:57:17 +01:00 committed by GitHub
parent daf2776db7
commit 6279646ee4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 113 additions and 56 deletions

View file

@ -3773,6 +3773,9 @@ variables.creation.success = The variable "%s" has been added.
variables.update.failed = Failed to edit variable. variables.update.failed = Failed to edit variable.
variables.update.success = The variable has been edited. 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] [projects]
deleted.display_name = Deleted Project deleted.display_name = Deleted Project
type-1.display_name = Individual Project type-1.display_name = Individual Project

View file

@ -31,7 +31,11 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte
"##[endgroup]", "##[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 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)] logStr := mockedLogs[int(cur)%len(mockedLogs)]
cur++ cur++
logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step)) 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.Status = actions_model.StatusRunning.String()
resp.State.Run.CanCancel = true resp.State.Run.CanCancel = true
resp.State.Run.CanDeleteArtifact = 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{ resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-a", Name: "artifact-a",
Size: 100 * 1024, Size: 100 * 1024,

View file

@ -1,30 +1,9 @@
{{template "base/head" .}} {{template "base/head" .}}
<div class="page-content"> <div class="page-content">
<div id="repo-action-view" {{template "repo/actions/view_component" (dict
data-run-index="1" "RunIndex" 1
data-job-index="2" "JobIndex" 2
data-actions-url="{{AppSubUrl}}/devtest/actions-mock" "ActionsURL" (print AppSubUrl "/devtest/actions-mock")
data-locale-approve="approve" )}}
data-locale-cancel="cancel"
data-locale-rerun="re-run"
data-locale-rerun-all="re-run all"
data-locale-runs-scheduled="scheduled"
data-locale-runs-commit="commit"
data-locale-runs-pushed-by="pushed by"
data-locale-status-unknown="unknown"
data-locale-status-waiting="waiting"
data-locale-status-running="running"
data-locale-status-success="success"
data-locale-status-failure="failure"
data-locale-status-cancelled="cancelled"
data-locale-status-skipped="skipped"
data-locale-status-blocked="blocked"
data-locale-artifacts-title="artifacts"
data-locale-confirm-delete-artifact="confirm delete artifact"
data-locale-show-timestamps="show timestamps"
data-locale-show-log-seconds="show log seconds"
data-locale-show-full-screen="show full screen"
data-locale-download-logs="download logs"
></div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View file

@ -2,33 +2,11 @@
<div class="page-content repository"> <div class="page-content repository">
{{template "repo/header" .}} {{template "repo/header" .}}
<div id="repo-action-view" {{template "repo/actions/view_component" (dict
data-run-index="{{.RunIndex}}" "RunIndex" .RunIndex
data-job-index="{{.JobIndex}}" "JobIndex" .JobIndex
data-actions-url="{{.ActionsURL}}" "ActionsURL" .ActionsURL
data-locale-approve="{{ctx.Locale.Tr "repo.diff.review.approve"}}" )}}
data-locale-cancel="{{ctx.Locale.Tr "cancel"}}"
data-locale-rerun="{{ctx.Locale.Tr "rerun"}}"
data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}"
data-locale-runs-scheduled="{{ctx.Locale.Tr "actions.runs.scheduled"}}"
data-locale-runs-commit="{{ctx.Locale.Tr "actions.runs.commit"}}"
data-locale-runs-pushed-by="{{ctx.Locale.Tr "actions.runs.pushed_by"}}"
data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}"
data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}"
data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}"
data-locale-status-success="{{ctx.Locale.Tr "actions.status.success"}}"
data-locale-status-failure="{{ctx.Locale.Tr "actions.status.failure"}}"
data-locale-status-cancelled="{{ctx.Locale.Tr "actions.status.cancelled"}}"
data-locale-status-skipped="{{ctx.Locale.Tr "actions.status.skipped"}}"
data-locale-status-blocked="{{ctx.Locale.Tr "actions.status.blocked"}}"
data-locale-artifacts-title="{{ctx.Locale.Tr "artifacts"}}"
data-locale-confirm-delete-artifact="{{ctx.Locale.Tr "confirm_delete_artifact"}}"
data-locale-show-timestamps="{{ctx.Locale.Tr "show_timestamps"}}"
data-locale-show-log-seconds="{{ctx.Locale.Tr "show_log_seconds"}}"
data-locale-show-full-screen="{{ctx.Locale.Tr "show_full_screen"}}"
data-locale-download-logs="{{ctx.Locale.Tr "download_logs"}}"
>
</div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View file

@ -0,0 +1,30 @@
<div id="repo-action-view"
data-run-index="{{.RunIndex}}"
data-job-index="{{.JobIndex}}"
data-actions-url="{{.ActionsURL}}"
data-locale-approve="{{ctx.Locale.Tr "repo.diff.review.approve"}}"
data-locale-cancel="{{ctx.Locale.Tr "cancel"}}"
data-locale-rerun="{{ctx.Locale.Tr "rerun"}}"
data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}"
data-locale-runs-scheduled="{{ctx.Locale.Tr "actions.runs.scheduled"}}"
data-locale-runs-commit="{{ctx.Locale.Tr "actions.runs.commit"}}"
data-locale-runs-pushed-by="{{ctx.Locale.Tr "actions.runs.pushed_by"}}"
data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}"
data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}"
data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}"
data-locale-status-success="{{ctx.Locale.Tr "actions.status.success"}}"
data-locale-status-failure="{{ctx.Locale.Tr "actions.status.failure"}}"
data-locale-status-cancelled="{{ctx.Locale.Tr "actions.status.cancelled"}}"
data-locale-status-skipped="{{ctx.Locale.Tr "actions.status.skipped"}}"
data-locale-status-blocked="{{ctx.Locale.Tr "actions.status.blocked"}}"
data-locale-artifacts-title="{{ctx.Locale.Tr "artifacts"}}"
data-locale-confirm-delete-artifact="{{ctx.Locale.Tr "confirm_delete_artifact"}}"
data-locale-show-timestamps="{{ctx.Locale.Tr "show_timestamps"}}"
data-locale-show-log-seconds="{{ctx.Locale.Tr "show_log_seconds"}}"
data-locale-show-full-screen="{{ctx.Locale.Tr "show_full_screen"}}"
data-locale-download-logs="{{ctx.Locale.Tr "download_logs"}}"
data-locale-logs-always-auto-scroll="{{ctx.Locale.Tr "actions.logs.always_auto_scroll"}}"
data-locale-logs-always-expand-running="{{ctx.Locale.Tr "actions.logs.always_expand_running"}}"
>
</div>

View file

@ -43,6 +43,20 @@ function isLogElementInViewport(el: HTMLElement): boolean {
return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width 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 = { const sfc = {
name: 'RepoActionView', name: 'RepoActionView',
components: { components: {
@ -56,7 +70,17 @@ const sfc = {
locale: Object, locale: Object,
}, },
watch: {
optionAlwaysAutoScroll() {
this.saveLocaleStorageOptions();
},
optionAlwaysExpandRunning() {
this.saveLocaleStorageOptions();
},
},
data() { data() {
const {autoScroll, expandRunning} = getLocaleStorageOptions();
return { return {
// internal state // internal state
loadingAbortController: null, loadingAbortController: null,
@ -70,6 +94,8 @@ const sfc = {
'log-time-stamp': false, 'log-time-stamp': false,
'log-time-seconds': false, 'log-time-seconds': false,
}, },
optionAlwaysAutoScroll: autoScroll ?? false,
optionAlwaysExpandRunning: expandRunning ?? false,
// provided by backend // provided by backend
run: { run: {
@ -147,6 +173,11 @@ const sfc = {
}, },
methods: { 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') // get the job step logs container ('.job-step-logs')
getJobStepLogsContainer(stepIndex: number): HTMLElement { getJobStepLogsContainer(stepIndex: number): HTMLElement {
return this.$refs.logs[stepIndex]; return this.$refs.logs[stepIndex];
@ -228,8 +259,10 @@ const sfc = {
}, },
shouldAutoScroll(stepIndex: number): boolean { shouldAutoScroll(stepIndex: number): boolean {
if (!this.optionAlwaysAutoScroll) return false;
const el = this.getJobStepLogsContainer(stepIndex); 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); return isLogElementInViewport(el.lastChild);
}, },
@ -280,6 +313,7 @@ const sfc = {
const abortController = new AbortController(); const abortController = new AbortController();
this.loadingAbortController = abortController; this.loadingAbortController = abortController;
try { try {
const isFirstLoad = !this.run.status;
const job = await this.fetchJobData(abortController); const job = await this.fetchJobData(abortController);
if (this.loadingAbortController !== abortController) return; if (this.loadingAbortController !== abortController) return;
@ -289,9 +323,10 @@ const sfc = {
// sync the currentJobStepsStates to store the job step states // sync the currentJobStepsStates to store the job step states
for (let i = 0; i < this.currentJob.steps.length; i++) { 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]) { if (!this.currentJobStepsStates[i]) {
// initial states for job steps // 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'), skipped: el.getAttribute('data-locale-status-skipped'),
blocked: el.getAttribute('data-locale-status-blocked'), 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); view.mount(el);
@ -528,6 +565,17 @@ export function initRepositoryActionView() {
<i class="icon"><SvgIcon :name="isFullScreen ? 'octicon-check' : 'gitea-empty-checkbox'"/></i> <i class="icon"><SvgIcon :name="isFullScreen ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.showFullScreen }} {{ locale.showFullScreen }}
</a> </a>
<div class="divider"/>
<a class="item" @click="optionAlwaysAutoScroll = !optionAlwaysAutoScroll">
<i class="icon"><SvgIcon :name="optionAlwaysAutoScroll ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.logsAlwaysAutoScroll }}
</a>
<a class="item" @click="optionAlwaysExpandRunning = !optionAlwaysExpandRunning">
<i class="icon"><SvgIcon :name="optionAlwaysExpandRunning ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.logsAlwaysExpandRunning }}
</a>
<div class="divider"/> <div class="divider"/>
<a :class="['item', !currentJob.steps.length ? 'disabled' : '']" :href="run.link+'/jobs/'+jobIndex+'/logs'" target="_blank"> <a :class="['item', !currentJob.steps.length ? 'disabled' : '']" :href="run.link+'/jobs/'+jobIndex+'/logs'" target="_blank">
<i class="icon"><SvgIcon name="octicon-download"/></i> <i class="icon"><SvgIcon name="octicon-download"/></i>