diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue
index 7f6524c7e3ddd..49ba68cb94593 100644
--- a/web_src/js/components/RepoActionView.vue
+++ b/web_src/js/components/RepoActionView.vue
@@ -6,6 +6,7 @@ import {toggleElem} from '../utils/dom.js';
import {formatDatetime} from '../utils/time.js';
import {renderAnsi} from '../render/ansi.js';
import {GET, POST, DELETE} from '../modules/fetch.js';
+import {showErrorToast} from '../modules/toast.js';
const sfc = {
name: 'RepoActionView',
@@ -87,9 +88,11 @@ const sfc = {
async mounted() {
// load job data and then auto-reload periodically
- // 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);
+ // need to await first loadData so this.currentJobStepsStates is initialized and can be used in hashChangeListener
+ await this.loadData();
+ this.intervalID = setInterval(() => {
+ this.loadData();
+ }, 1000);
document.body.addEventListener('click', this.closeDropdown);
this.hashChangeListener();
window.addEventListener('hashchange', this.hashChangeListener);
@@ -142,7 +145,10 @@ const sfc = {
toggleStepLogs(idx) {
this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
if (this.currentJobStepsStates[idx].expanded) {
- this.loadJob(); // try to load the data immediately instead of waiting for next timer interval
+ this.currentJobStepsStates[idx].loading = true;
+ // force-load the data, otherwise the state will end up incorrect if loadData
+ // is already running and the job step will never expand.
+ this.loadData({force: true});
}
},
// cancel a run
@@ -206,7 +212,7 @@ const sfc = {
async deleteArtifact(name) {
if (!window.confirm(this.locale.confirmDeleteArtifact.replace('%s', name))) return;
await DELETE(`${this.run.link}/artifacts/${name}`);
- await this.loadJob();
+ await this.loadData();
},
async fetchJob() {
@@ -222,8 +228,8 @@ const sfc = {
return await resp.json();
},
- async loadJob() {
- if (this.loading) return;
+ async loadData({force = false} = {}) {
+ if (this.loading && !force) return;
try {
this.loading = true;
@@ -235,32 +241,46 @@ const sfc = {
]);
} catch (err) {
if (err instanceof TypeError) return; // avoid network error while unloading page
- throw err;
+ showErrorToast(err.message);
+ // reset all step loading states, we can't easily tell which one failed at this point
+ for (let i = 0; i < this.currentJob.steps.length; i++) {
+ if (this.currentJobStepsStates[i].loading) {
+ this.currentJobStepsStates[i].loading = false;
+ }
+ }
}
- this.artifacts = artifacts['artifacts'] || [];
-
- // save the state to Vue data, then the UI will be updated
- this.run = job.state.run;
- this.currentJob = job.state.currentJob;
+ if (artifacts) {
+ this.artifacts = artifacts['artifacts'] || [];
+ }
- // sync the currentJobStepsStates to store the job step states
- for (let i = 0; i < this.currentJob.steps.length; i++) {
- if (!this.currentJobStepsStates[i]) {
- // initial states for job steps
- this.currentJobStepsStates[i] = {cursor: null, expanded: false};
+ if (job) {
+ // save the state to Vue data, then the UI will be updated
+ this.run = job.state.run;
+ this.currentJob = job.state.currentJob;
+
+ // sync the currentJobStepsStates to store the job step states
+ for (let i = 0; i < this.currentJob.steps.length; i++) {
+ if (!this.currentJobStepsStates[i]) {
+ // initial states for job steps
+ this.currentJobStepsStates[i] = {cursor: null, expanded: false, loading: false};
+ }
+ }
+ for (const logs of job.logs.stepsLog) {
+ // save the cursor, it will be passed to backend next time
+ this.currentJobStepsStates[logs.step].cursor = logs.cursor;
+ // append logs to the UI
+ this.appendLogs(logs.step, logs.lines, logs.started);
+ // update loading state
+ if (this.currentJobStepsStates[logs.step].loading && logs.cursor) {
+ this.currentJobStepsStates[logs.step].loading = false;
+ }
}
- }
- // append logs to the UI
- for (const logs of job.logs.stepsLog) {
- // save the cursor, it will be passed to backend next time
- this.currentJobStepsStates[logs.step].cursor = logs.cursor;
- this.appendLogs(logs.step, logs.lines, logs.started);
- }
- if (this.run.done && this.intervalID) {
- clearInterval(this.intervalID);
- this.intervalID = null;
+ if (this.run.done && this.intervalID) {
+ clearInterval(this.intervalID);
+ this.intervalID = null;
+ }
}
} finally {
this.loading = false;
@@ -313,7 +333,7 @@ const sfc = {
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();
+ await this.loadData();
}
const logLine = this.$refs.steps.querySelector(selectedLogStep);
if (!logLine) return;
@@ -479,7 +499,7 @@ export function initRepositoryActionView() {
-
+