Add log line anchor for action logs (#25532)

Close #24593

Some behavior:

- If log step line in hash exists, expand the step and scroll to the log
line.
- If step exists but line not exists, the step will be expanded.
- If step not exists, stays on the job's page.

Some Notes:

- Changed mounted to async because need to await for first `loadJob` so
`currentJobStepsStates` can be initialized and used in
`hashChangeListener `.

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
HesterG 2023-07-03 09:08:49 +08:00 committed by GitHub
parent 36f1fa7792
commit 640a88fa09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -204,15 +204,19 @@ const sfc = {
}; };
}, },
mounted() { async mounted() {
// load job data and then auto-reload periodically // load job data and then auto-reload periodically
this.loadJob(); // need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
await this.loadJob();
this.intervalID = setInterval(this.loadJob, 1000); this.intervalID = setInterval(this.loadJob, 1000);
document.body.addEventListener('click', this.closeDropdown); document.body.addEventListener('click', this.closeDropdown);
this.hashChangeListener();
window.addEventListener('hashchange', this.hashChangeListener);
}, },
beforeUnmount() { beforeUnmount() {
document.body.removeEventListener('click', this.closeDropdown); document.body.removeEventListener('click', this.closeDropdown);
window.removeEventListener('hashchange', this.hashChangeListener);
}, },
unmounted() { unmounted() {
@ -280,14 +284,16 @@ const sfc = {
this.fetchPost(`${this.run.link}/approve`); this.fetchPost(`${this.run.link}/approve`);
}, },
createLogLine(line, startTime) { createLogLine(line, startTime, stepIndex) {
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('job-log-line'); div.classList.add('job-log-line');
div.setAttribute('id', `jobstep-${stepIndex}-${line.index}`);
div._jobLogTime = line.timestamp; div._jobLogTime = line.timestamp;
const lineNumber = document.createElement('div'); const lineNumber = document.createElement('a');
lineNumber.className = 'line-num'; lineNumber.classList.add('line-num', 'muted');
lineNumber.textContent = line.index; lineNumber.textContent = line.index;
lineNumber.setAttribute('href', `#jobstep-${stepIndex}-${line.index}`);
div.append(lineNumber); div.append(lineNumber);
// for "Show timestamps" // for "Show timestamps"
@ -318,7 +324,7 @@ const sfc = {
for (const line of logLines) { for (const line of logLines) {
// TODO: group support: ##[group]GroupTitle , ##[endgroup] // TODO: group support: ##[group]GroupTitle , ##[endgroup]
const el = this.getLogsContainer(stepIndex); const el = this.getLogsContainer(stepIndex);
el.append(this.createLogLine(line, startTime)); el.append(this.createLogLine(line, startTime, stepIndex));
} }
}, },
@ -429,6 +435,21 @@ const sfc = {
} else { } else {
actionBodyEl.append(fullScreenEl); actionBodyEl.append(fullScreenEl);
} }
},
async hashChangeListener() {
const selectedLogStep = window.location.hash;
if (!selectedLogStep) return;
const [_, step, _line] = selectedLogStep.split('-');
if (!this.currentJobStepsStates[step]) return;
if (!this.currentJobStepsStates[step].expanded && this.currentJobStepsStates[step].cursor === null) {
this.currentJobStepsStates[step].expanded = true;
// need to await for load job if the step log is loaded for the first time
// so logline can be selected by querySelector
await this.loadJob();
}
const logLine = this.$refs.steps.querySelector(selectedLogStep);
if (!logLine) return;
logLine.querySelector('.line-num').click();
} }
}, },
}; };
@ -802,10 +823,15 @@ export function initRepositoryActionView() {
display: flex; display: flex;
} }
.job-step-section .job-step-logs .job-log-line:hover { .job-log-line:hover,
.job-log-line:target {
background-color: var(--color-console-hover-bg); background-color: var(--color-console-hover-bg);
} }
.job-log-line:target {
scroll-margin-top: 95px;
}
/* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */ /* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */
.job-log-line .line-num, .log-time-seconds { .job-log-line .line-num, .log-time-seconds {
width: 48px; width: 48px;
@ -814,6 +840,11 @@ export function initRepositoryActionView() {
user-select: none; user-select: none;
} }
.job-log-line:target > .line-num {
color: var(--color-primary);
text-decoration: underline;
}
.log-time-seconds { .log-time-seconds {
padding-right: 2px; padding-right: 2px;
} }