*: abort async fetch on component destroy

* Use AbortController to signal fetch to stop when component is destroyed
* Remove polling and just schedule a new fetch at the end of the current one
when not aborted.
This commit is contained in:
Simone Gotti 2019-09-20 09:37:43 +02:00
parent c6ab1ae54e
commit 32bd112516
10 changed files with 364 additions and 147 deletions

View File

@ -29,6 +29,8 @@ export default {
formatter.use_classes = true; formatter.use_classes = true;
return { return {
fetchAbort: null,
items: [], items: [],
lastitem: "", lastitem: "",
lines: [], lines: [],
@ -62,75 +64,106 @@ export default {
path += "&follow"; path += "&follow";
} }
let res = await fetch(apiurl(path)); try {
if (res.status == 200) { this.fetching = true;
const reader = res.body.getReader(); let res = await fetch(apiurl(path), { signal: this.fetchAbort.signal });
if (res.status == 200) {
const reader = res.body.getReader();
let lastline = ""; let lastline = "";
let j = 0; let j = 0;
for (;;) { for (;;) {
let { done, value } = await reader.read(); let { done, value } = await reader.read();
if (done) { if (done) {
return; this.fetching = false;
} return;
let data = new TextDecoder("utf-8").decode(value, { stream: true });
let part = "";
for (var i = 0; i < data.length; i++) {
let c = data.charAt(i);
if (c == "\r") {
// replace lastline from start, simulating line feed (go to start of line)
// this isn't perfect since the previous line contents could have
// been written using different colors and this will lose them but
// in practically all cases this won't happen
lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length);
j = 0;
this.lastitem = this.formatter.ansi_to_html(lastline);
part = "";
} else if (c == "\n") {
lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length);
j += part.length;
this.lastitem = this.formatter.ansi_to_html(lastline);
this.items.push(this.lastitem);
this.lastitem = "";
lastline = "";
j = 0;
part = "";
} else {
part += c;
} }
let data = new TextDecoder("utf-8").decode(value, { stream: true });
let part = "";
for (var i = 0; i < data.length; i++) {
let c = data.charAt(i);
if (c == "\r") {
// replace lastline from start, simulating line feed (go to start of line)
// this isn't perfect since the previous line contents could have
// been written using different colors and this will lose them but
// in practically all cases this won't happen
lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length);
j = 0;
this.lastitem = this.formatter.ansi_to_html(lastline);
part = "";
} else if (c == "\n") {
lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length);
j += part.length;
this.lastitem = this.formatter.ansi_to_html(lastline);
this.items.push(this.lastitem);
this.lastitem = "";
lastline = "";
j = 0;
part = "";
} else {
part += c;
}
}
lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length);
j += part.length;
this.lastitem = this.formatter.ansi_to_html(lastline);
} }
lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length);
j += part.length;
this.lastitem = this.formatter.ansi_to_html(lastline);
} }
} catch (e) {
// TODO(sgotti) show that log fetching has failed
} }
this.fetching = false;
},
abortFetch() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
} }
}, },
watch: { watch: {
show: function(post, pre) { show: function(post, pre) {
if (pre == false && post == true) { if (pre == false && post == true) {
this.abortFetch();
this.fetch(); this.fetch();
} }
if (pre == true && post == false) {
this.abortFetch();
}
}, },
stepphase: function(post, pre) { stepphase: function(post) {
if (pre == "notstarted" && post == "running") { if (!this.show) {
return;
}
if (this.fetching) {
return;
}
if (post == "running") {
this.abortFetch();
this.getLogs(true); this.getLogs(true);
} else { } else {
this.abortFetch();
this.getLogs(false); this.getLogs(false);
} }
} }
}, },
created: function() { created: function() {
this.fetchAbort = new AbortController();
if (this.show) { if (this.show) {
this.fetch(); this.fetch();
} }
}, },
beforeDestroy() { beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
if (this.es !== null) { if (this.es !== null) {
this.es.close(); this.es.close();
} }

View File

@ -59,16 +59,21 @@ export default {
}, },
data() { data() {
return { return {
fetchAbort: null,
fetchProjectGroupsLoading: false, fetchProjectGroupsLoading: false,
fetchProjectsLoading: false, fetchProjectsLoading: false,
projects: [], projects: [],
projectgroups: [], projectgroups: []
polling: null
}; };
}, },
watch: { watch: {
$route: async function() { $route: async function() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
this.fetchProjects(this.ownertype, this.ownername); this.fetchProjects(this.ownertype, this.ownername);
this.fetchProjectGroups(this.ownertype, this.ownername); this.fetchProjectGroups(this.ownertype, this.ownername);
} }
@ -101,10 +106,14 @@ export default {
} }
this.startFetchProjectsLoading(); this.startFetchProjectsLoading();
let { data, error } = await fetchProjectGroupProjects( let { data, error, aborted } = await fetchProjectGroupProjects(
projectgroupref.join("/") projectgroupref.join("/"),
this.fetchAbort.signal
); );
this.stopFetchProjectsLoading(); this.stopFetchProjectsLoading();
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
@ -117,10 +126,14 @@ export default {
projectgroupref.push(...this.projectgroupref); projectgroupref.push(...this.projectgroupref);
} }
this.startFetchProjectGroupsLoading(); this.startFetchProjectGroupsLoading();
let { data, error } = await fetchProjectGroupSubgroups( let { data, error, aborted } = await fetchProjectGroupSubgroups(
projectgroupref.join("/") projectgroupref.join("/"),
this.fetchAbort.signal
); );
this.stopFetchProjectGroupsLoading(); this.stopFetchProjectGroupsLoading();
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
@ -131,8 +144,14 @@ export default {
projectGroupLink: projectGroupLink projectGroupLink: projectGroupLink
}, },
created: function() { created: function() {
this.fetchAbort = new AbortController();
this.fetchProjects(this.ownertype, this.ownername); this.fetchProjects(this.ownertype, this.ownername);
this.fetchProjectGroups(this.ownertype, this.ownername); this.fetchProjectGroups(this.ownertype, this.ownername);
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
} }
}; };
</script> </script>

View File

@ -125,13 +125,17 @@ export default {
data() { data() {
return { return {
now: moment(), now: moment(),
fetchAbort: null,
fetchRunsLoading: false, fetchRunsLoading: false,
fetchRunsLoadingTimeout: false, fetchRunsLoadingTimeout: false,
fetchRunsError: null, fetchRunsError: null,
fetchRunsSchedule: null,
runs: null, runs: null,
wantedRunsNumber: 25, wantedRunsNumber: 25,
hasMoreRuns: false, hasMoreRuns: false,
polling: null,
project: null, project: null,
user: null user: null
}; };
@ -163,22 +167,32 @@ export default {
return run.tasks_waiting_approval.length > 0; return run.tasks_waiting_approval.length > 0;
}, },
update() { update() {
clearInterval(this.polling); if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunsSchedule);
if (this.projectref !== undefined) { if (this.projectref !== undefined) {
this.fetchProject(); this.fetchProject();
} else { } else {
this.fetchUser(); this.fetchUser();
} }
this.pollData();
}, },
async fetchProject() { async fetchProject() {
this.fetchAbort = new AbortController();
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectref ...this.projectref
].join("/"); ].join("/");
let { data, error } = await fetchProject(projectref); let { data, error, aborted } = await fetchProject(
projectref,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
@ -188,7 +202,15 @@ export default {
this.fetchRuns(true); this.fetchRuns(true);
}, },
async fetchUser() { async fetchUser() {
let { data, error } = await fetchUser(this.ownername); this.fetchAbort = new AbortController();
let { data, error, aborted } = await fetchUser(
this.ownername,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
@ -227,10 +249,20 @@ export default {
if (loading) this.startFetchRunsLoading(); if (loading) this.startFetchRunsLoading();
while (!stopFetch) { while (!stopFetch) {
let { data, error } = await fetchRuns(group, startRunID, lastrun); let { data, error, aborted } = await fetchRuns(
group,
startRunID,
lastrun,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.stopFetchRunsLoading(); this.stopFetchRunsLoading();
this.fetchRunsError = error; this.fetchRunsError = error;
this.scheduleFetchRuns();
return; return;
} }
this.fetchRunsError = null; this.fetchRunsError = null;
@ -247,10 +279,12 @@ export default {
this.stopFetchRunsLoading(); this.stopFetchRunsLoading();
this.runs = newRuns; this.runs = newRuns;
this.hasMoreRuns = hasMoreRuns; this.hasMoreRuns = hasMoreRuns;
this.scheduleFetchRuns();
}, },
pollData() { scheduleFetchRuns() {
clearInterval(this.polling); clearTimeout(this.fetchRunsSchedule);
this.polling = setInterval(() => { this.fetchRunsSchedule = setTimeout(() => {
this.fetchRuns(); this.fetchRuns();
}, 2000); }, 2000);
}, },
@ -293,7 +327,10 @@ export default {
this.update(); this.update();
}, },
beforeDestroy() { beforeDestroy() {
clearInterval(this.polling); if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunsSchedule);
} }
}; };
</script> </script>

View File

@ -68,9 +68,11 @@ export default {
}, },
data() { data() {
return { return {
fetchAbort: null,
fetchRunError: null, fetchRunError: null,
fetchRunSchedule: null,
run: null, run: null,
polling: null,
taskWidth: 200, taskWidth: 200,
taskHeight: 40, taskHeight: 40,
@ -81,6 +83,18 @@ export default {
tasksDisplay: "graph" tasksDisplay: "graph"
}; };
}, },
watch: {
$route: async function() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
this.fetchAbort = new AbortController();
this.fetchRun();
}
},
methods: { methods: {
runTaskLink(task) { runTaskLink(task) {
if (this.projectref) { if (this.projectref) {
@ -108,9 +122,17 @@ export default {
return "unknown"; return "unknown";
}, },
async fetchRun() { async fetchRun() {
let { data, error } = await fetchRun(this.runid); let { data, error, aborted } = await fetchRun(
this.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.fetchRunError = error; this.fetchRunError = error;
this.scheduleFetchRun();
return; return;
} }
this.fetchRunError = null; this.fetchRunError = null;
@ -127,19 +149,25 @@ export default {
taskID taskID
); );
} }
this.scheduleFetchRun();
}, },
pollData() { scheduleFetchRun() {
this.polling = setInterval(() => { clearTimeout(this.fetchRunSchedule);
this.fetchRunSchedule = setTimeout(() => {
this.fetchRun(); this.fetchRun();
}, 2000); }, 2000);
} }
}, },
created: function() { created: function() {
this.fetchAbort = new AbortController();
this.fetchRun(); this.fetchRun();
this.pollData();
}, },
beforeDestroy() { beforeDestroy() {
clearInterval(this.polling); if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
} }
}; };
</script> </script>

View File

@ -8,7 +8,7 @@
<div>Error fetching Run: {{ fetchRunError }}</div> <div>Error fetching Run: {{ fetchRunError }}</div>
<div>Error fetching Task: {{ fetchTaskError }}</div> <div>Error fetching Task: {{ fetchTaskError }}</div>
</div> </div>
<rundetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref"/> <rundetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref" />
<div v-if="task != null"> <div v-if="task != null">
<div class="mt-8 mb-4 flex justify-between items-center"> <div class="mt-8 mb-4 flex justify-between items-center">
<div class="flex items-center"> <div class="flex items-center">
@ -64,13 +64,29 @@ export default {
}, },
data() { data() {
return { return {
fetchAbort: null,
fetchRunError: null, fetchRunError: null,
fetchTaskError: null, fetchTaskError: null,
run: null, run: null,
task: null, task: null
polling: null
}; };
}, },
watch: {
$route: async function() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
clearTimeout(this.fetchTaskSchedule);
this.fetchAbort = new AbortController();
this.fetchRun();
this.fetchTask();
}
},
methods: { methods: {
taskClass(task) { taskClass(task) {
if (task.status == "success") return "is-success"; if (task.status == "success") return "is-success";
@ -81,38 +97,66 @@ export default {
return "unknown"; return "unknown";
}, },
async fetchRun() { async fetchRun() {
let { data, error } = await fetchRun(this.runid); let { data, error, aborted } = await fetchRun(
this.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.fetchRunError = error; this.fetchRunError = error;
this.scheduleFetchRun();
return; return;
} }
this.fetchRunError = error; this.fetchRunError = error;
this.run = data; this.run = data;
this.scheduleFetchRun();
}, },
async fetchTask() { async fetchTask() {
let { data, error } = await fetchTask(this.runid, this.taskid); let { data, error, aborted } = await fetchTask(
this.runid,
this.taskid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.fetchTaskError = error; this.fetchTaskError = error;
this.scheduleFetchTask();
return; return;
} }
this.fetchTaskError = error; this.fetchTaskError = error;
this.task = data; this.task = data;
this.scheduleFetchTask();
}, },
pollData() { scheduleFetchRun() {
this.polling = setInterval(() => { clearTimeout(this.fetchRunSchedule);
this.fetchTask(); this.fetchRunSchedule = setTimeout(() => {
this.fetchRun(); this.fetchRun();
}, 2000); }, 2000);
}, },
scheduleFetchTask() {
clearTimeout(this.fetchTaskSchedule);
this.fetchTaskSchedule = setTimeout(() => {
this.fetchTask();
}, 2000);
},
approveTask: approveTask approveTask: approveTask
}, },
created: function() { created: function() {
this.fetchAbort = new AbortController();
this.fetchRun(); this.fetchRun();
this.fetchTask(); this.fetchTask();
this.pollData();
}, },
beforeDestroy() { beforeDestroy() {
clearInterval(this.polling); if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
clearTimeout(this.fetchTaskSchedule);
} }
}; };
</script> </script>

View File

@ -71,13 +71,16 @@ export async function registerapi(init) {
} }
} }
export async function fetch(url, init) { export async function fetch(url, init, signal) {
if (init === undefined) { if (!init) {
init = {} init = {}
} }
if (init.headers === undefined) { if (init.headers === undefined) {
init["headers"] = {} init["headers"] = {}
} }
if (signal) {
init["signal"] = signal;
}
let idToken = getIdToken(); let idToken = getIdToken();
if (idToken) { if (idToken) {
init.headers["Authorization"] = "bearer " + idToken init.headers["Authorization"] = "bearer " + idToken

View File

@ -1,10 +1,9 @@
import { apiurl, loginapi, registerapi } from "@/util/auth";
import { fetch as authfetch } from "@/util/auth";
import router from "@/router"; import router from "@/router";
import { apiurl, fetch as authfetch, loginapi, registerapi } from "@/util/auth";
export async function fetch(url, init) { export async function fetch(url, init, signal) {
try { try {
let res = await authfetch(url, init) let res = await authfetch(url, init, signal)
if (!res.ok) { if (!res.ok) {
if (res.status === 401) { if (res.status === 401) {
router.push({ name: "login" }) router.push({ name: "login" })
@ -31,6 +30,9 @@ export async function fetch(url, init) {
return { data: await res.json(), error: null } return { data: await res.json(), error: null }
} }
} catch (e) { } catch (e) {
if (e.name == 'AbortError') {
return { data: null, error: null, aborted: true, }
}
return { data: null, error: "api call failed: " + e.message } return { data: null, error: "api call failed: " + e.message }
} }
} }
@ -82,17 +84,17 @@ export async function register(username, remotesourcename, remoteloginname, remo
} }
} }
export async function fetchCurrentUser() { export async function fetchCurrentUser(signal) {
let path = "/user" let path = "/user"
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchOrgMembers(orgref) { export async function fetchOrgMembers(orgref, signal) {
let path = "/orgs/" + orgref + "/members" let path = "/orgs/" + orgref + "/members"
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchRuns(group, startRunID, lastrun) { export async function fetchRuns(group, startRunID, lastrun, signal) {
let u = apiurl("/runs"); let u = apiurl("/runs");
if (group) { if (group) {
u.searchParams.append("group", group) u.searchParams.append("group", group)
@ -104,49 +106,49 @@ export async function fetchRuns(group, startRunID, lastrun) {
u.searchParams.append("start", startRunID) u.searchParams.append("start", startRunID)
} }
return await fetch(u) return await fetch(u, null, signal)
} }
export async function fetchRun(runid) { export async function fetchRun(runid, signal) {
return await fetch(apiurl("/runs/" + runid)); return await fetch(apiurl("/runs/" + runid), null, signal);
} }
export async function fetchTask(runid, taskid) { export async function fetchTask(runid, taskid, signal) {
return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid)) return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid), signal)
} }
export async function fetchUser(username) { export async function fetchUser(username, signal) {
let path = "/users/" + username let path = "/users/" + username
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProjectGroup(projectgroupref) { export async function fetchProjectGroup(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref) let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProjectGroupSubgroups(projectgroupref) { export async function fetchProjectGroupSubgroups(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref) let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
path += "/subgroups"; path += "/subgroups";
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProjectGroupProjects(projectgroupref) { export async function fetchProjectGroupProjects(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref) let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
path += "/projects"; path += "/projects";
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProject(ref) { export async function fetchProject(ref, signal) {
let path = "/projects/" + encodeURIComponent(ref) let path = "/projects/" + encodeURIComponent(ref)
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchSecrets(ownertype, ref, all) { export async function fetchSecrets(ownertype, ref, all, signal) {
let path let path
if (ownertype == "project") { if (ownertype == "project") {
path = "/projects/" path = "/projects/"
@ -158,10 +160,10 @@ export async function fetchSecrets(ownertype, ref, all) {
if (all) { if (all) {
path += "?tree&removeoverridden"; path += "?tree&removeoverridden";
} }
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function fetchVariables(ownertype, ref, all) { export async function fetchVariables(ownertype, ref, all, signal) {
let path let path
if (ownertype == "project") { if (ownertype == "project") {
path = "/projects/" path = "/projects/"
@ -173,10 +175,10 @@ export async function fetchVariables(ownertype, ref, all) {
if (all) { if (all) {
path += "?tree&removeoverridden"; path += "?tree&removeoverridden";
} }
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function createOrganization(orgname, visibility) { export async function createOrganization(orgname, visibility, signal) {
let path = "/orgs" let path = "/orgs"
let init = { let init = {
method: "POST", method: "POST",
@ -185,10 +187,10 @@ export async function createOrganization(orgname, visibility) {
visibility: visibility, visibility: visibility,
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function createUserToken(username, tokenname) { export async function createUserToken(username, tokenname, signal) {
let path = "/users/" + username + "/tokens" let path = "/users/" + username + "/tokens"
let init = { let init = {
method: "POST", method: "POST",
@ -196,18 +198,18 @@ export async function createUserToken(username, tokenname) {
token_name: tokenname, token_name: tokenname,
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function deleteUserToken(username, tokenname) { export async function deleteUserToken(username, tokenname, signal) {
let path = "/users/" + username + "/tokens/" + tokenname let path = "/users/" + username + "/tokens/" + tokenname
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function createUserLinkedAccount(username, remotesourcename, loginname, password) { export async function createUserLinkedAccount(username, remotesourcename, loginname, password, signal) {
let path = "/users/" + username + "/linkedaccounts" let path = "/users/" + username + "/linkedaccounts"
let init = { let init = {
method: "POST", method: "POST",
@ -217,18 +219,18 @@ export async function createUserLinkedAccount(username, remotesourcename, loginn
remote_source_login_password: password, remote_source_login_password: password,
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function deleteLinkedAccount(username, laid) { export async function deleteLinkedAccount(username, laid, signal) {
let path = "/users/" + username + "/linkedaccounts/" + laid let path = "/users/" + username + "/linkedaccounts/" + laid
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function restartRun(runid, fromStart) { export async function restartRun(runid, fromStart, signal) {
let path = "/runs/" + runid + "/actions" let path = "/runs/" + runid + "/actions"
let init = { let init = {
method: "PUT", method: "PUT",
@ -237,10 +239,10 @@ export async function restartRun(runid, fromStart) {
from_start: fromStart from_start: fromStart
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function cancelRun(runid) { export async function cancelRun(runid, signal) {
let path = "/runs/" + runid + "/actions" let path = "/runs/" + runid + "/actions"
let init = { let init = {
method: "PUT", method: "PUT",
@ -248,10 +250,10 @@ export async function cancelRun(runid) {
action_type: "cancel" action_type: "cancel"
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function stopRun(runid) { export async function stopRun(runid, signal) {
let path = "/runs/" + runid + "/actions" let path = "/runs/" + runid + "/actions"
let init = { let init = {
method: "PUT", method: "PUT",
@ -259,10 +261,10 @@ export async function stopRun(runid) {
action_type: "stop" action_type: "stop"
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function approveTask(runid, taskid) { export async function approveTask(runid, taskid, signal) {
let path = "/runs/" + runid + "/tasks/" + taskid + "/actions" let path = "/runs/" + runid + "/tasks/" + taskid + "/actions"
let init = { let init = {
method: "PUT", method: "PUT",
@ -270,20 +272,20 @@ export async function approveTask(runid, taskid) {
action_type: "approve" action_type: "approve"
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function fetchRemoteSources() { export async function fetchRemoteSources(signal) {
let path = "/remotesources" let path = "/remotesources"
return await fetch(apiurl(path)); return await fetch(apiurl(path), null, signal);
} }
export async function userRemoteRepos(remotesourceid) { export async function userRemoteRepos(remotesourceid, signal) {
let path = "/user/remoterepos/" + remotesourceid let path = "/user/remoterepos/" + remotesourceid
return await fetch(apiurl(path)); return await fetch(apiurl(path, null, signal));
} }
export async function createProjectGroup(parentref, name, visibility) { export async function createProjectGroup(parentref, name, visibility, signal) {
let path = "/projectgroups" let path = "/projectgroups"
let init = { let init = {
method: "POST", method: "POST",
@ -293,10 +295,10 @@ export async function createProjectGroup(parentref, name, visibility) {
visibility: visibility visibility: visibility
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function updateProjectGroup(projectgroupref, name, visibility) { export async function updateProjectGroup(projectgroupref, name, visibility, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref) let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
let init = { let init = {
method: "PUT", method: "PUT",
@ -305,10 +307,10 @@ export async function updateProjectGroup(projectgroupref, name, visibility) {
visibility: visibility, visibility: visibility,
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function createProject(parentref, name, visibility, remotesourcename, remoterepopath) { export async function createProject(parentref, name, visibility, remotesourcename, remoterepopath, signal) {
let path = "/projects" let path = "/projects"
let init = { let init = {
method: "POST", method: "POST",
@ -320,10 +322,10 @@ export async function createProject(parentref, name, visibility, remotesourcenam
repo_path: remoterepopath, repo_path: remoterepopath,
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function updateProject(projectref, name, visibility) { export async function updateProject(projectref, name, visibility, signal) {
let path = "/projects/" + encodeURIComponent(projectref) let path = "/projects/" + encodeURIComponent(projectref)
let init = { let init = {
method: "PUT", method: "PUT",
@ -332,29 +334,29 @@ export async function updateProject(projectref, name, visibility) {
visibility: visibility, visibility: visibility,
}) })
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function deleteProject(projectref) { export async function deleteProject(projectref, signal) {
let path = "/projects/" + encodeURIComponent(projectref) let path = "/projects/" + encodeURIComponent(projectref)
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function projectUpdateRepoLinkedAccount(projectref) { export async function projectUpdateRepoLinkedAccount(projectref, signal) {
let path = "/projects/" + encodeURIComponent(projectref) + "/updaterepolinkedaccount" let path = "/projects/" + encodeURIComponent(projectref) + "/updaterepolinkedaccount"
let init = { let init = {
method: "PUT", method: "PUT",
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }
export async function deleteProjectGroup(projectgroupref) { export async function deleteProjectGroup(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref) let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
return await fetch(apiurl(path), init) return await fetch(apiurl(path), init, signal)
} }

View File

@ -24,7 +24,6 @@ export default {
error: null, error: null,
run: null, run: null,
code: this.$route.query.code, code: this.$route.query.code,
polling: null,
username: null username: null
}; };
}, },

View File

@ -168,15 +168,28 @@ export default {
}, },
data() { data() {
return { return {
fetchAbort: null,
dropdownActive: false, dropdownActive: false,
run: null run: null
}; };
}, },
watch: { watch: {
$route: async function(route) { $route: async function(route) {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
this.run = null; this.run = null;
if (route.params.runid) { if (route.params.runid) {
let { data, error } = await fetchRun(route.params.runid); let { data, error, aborted } = await fetchRun(
route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
@ -199,14 +212,27 @@ export default {
} }
}, },
created: async function() { created: async function() {
this.fetchAbort = new AbortController();
if (this.$route.params.runid) { if (this.$route.params.runid) {
let { data, error } = await fetchRun(this.$route.params.runid); let { data, error } = await fetchRun(
this.$route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
} }
this.run = data; this.run = data;
} }
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
} }
}; };
</script> </script>

View File

@ -164,15 +164,28 @@ export default {
}, },
data() { data() {
return { return {
fetchAbort: null,
dropdownActive: false, dropdownActive: false,
run: null run: null
}; };
}, },
watch: { watch: {
$route: async function(route) { $route: async function(route) {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
this.run = null; this.run = null;
if (route.params.runid) { if (route.params.runid) {
let { data, error } = await fetchRun(route.params.runid); let { data, error, aborted } = await fetchRun(
route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
@ -204,14 +217,27 @@ export default {
} }
}, },
created: async function() { created: async function() {
this.fetchAbort = new AbortController();
if (this.$route.params.runid) { if (this.$route.params.runid) {
let { data, error } = await fetchRun(this.$route.params.runid); let { data, error, aborted } = await fetchRun(
this.$route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch("setError", error);
return; return;
} }
this.run = data; this.run = data;
} }
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
} }
}; };
</script> </script>