aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-07-03 12:23:52 +0000
committerDavid Siegfried <david.siegfried@uni-vechta.de>2024-07-03 12:23:52 +0000
commite699246d87a28f38dae2b1bb756a8c6778f45cca (patch)
treed40366ec344e09fd412abc13811cc5381ea5cc5b
parent7e3801daaf99b26ede1a63d93c58a825ab0a5242 (diff)
limit number of parallel requests and cache gained info, fixes #4358
Closes #4358 Merge request studip/studip!3161
-rw-r--r--resources/assets/javascripts/lib/chunked-requester.ts76
-rw-r--r--resources/vue/components/tree/StudipTreeList.vue2
-rw-r--r--resources/vue/components/tree/StudipTreeTable.vue2
-rw-r--r--resources/vue/components/tree/TreeNodeCourseInfo.vue12
-rw-r--r--resources/vue/mixins/TreeMixin.js23
5 files changed, 104 insertions, 11 deletions
diff --git a/resources/assets/javascripts/lib/chunked-requester.ts b/resources/assets/javascripts/lib/chunked-requester.ts
new file mode 100644
index 0000000..6f53c01
--- /dev/null
+++ b/resources/assets/javascripts/lib/chunked-requester.ts
@@ -0,0 +1,76 @@
+import axios from "axios";
+
+interface ChunkedRequest {
+ url: string,
+ parameters: object,
+ resolve(value: any): any,
+ reject(): any,
+}
+
+export default class ChunkedRequester
+{
+ #requests: ChunkedRequest[] = [];
+
+ readonly #delay: number;
+ readonly #limit: number;
+ #timeout: any = null;
+
+ constructor(limit: number = 16, delay: number = 500) {
+ if (limit < 1) {
+ throw new Error('Limit must be positive');
+ }
+
+ this.#limit = limit;
+ this.#delay = delay;
+ }
+
+ addRequest(url: string, parameters: object = {}): Promise<any>
+ {
+ return new Promise((resolve, reject) => {
+ this.#requests.push({
+ url,
+ parameters,
+ resolve,
+ reject
+ });
+ this.#startRequests();
+ });
+ }
+
+ #startRequests(): void
+ {
+ if (this.#requests.length === 0) {
+ return;
+ }
+
+ if (this.#requests.length < this.#limit) {
+ this.clearTimeout();
+ }
+
+ if (this.#timeout !== null) {
+ return;
+ }
+
+ this.#timeout = setTimeout(
+ () => {
+ Promise.all(
+ this.#requests
+ .splice(0, this.#limit)
+ .map(({url, parameters, resolve, reject}) => {
+ return axios.get(url, {params: parameters}).then(resolve, reject);
+ })
+ ).then(() => {
+ this.clearTimeout();
+ this.#startRequests();
+ });
+ }
+ , this.#delay
+ );
+ }
+
+ clearTimeout(): void
+ {
+ clearTimeout(this.#timeout);
+ this.#timeout = null;
+ }
+}
diff --git a/resources/vue/components/tree/StudipTreeList.vue b/resources/vue/components/tree/StudipTreeList.vue
index 6214234..155503b 100644
--- a/resources/vue/components/tree/StudipTreeList.vue
+++ b/resources/vue/components/tree/StudipTreeList.vue
@@ -222,7 +222,7 @@ export default {
courses: [],
assistiveLive: '',
subLevelsCourses: 0,
- thisLevelCourses: 0,
+ thisLevelCourses: this.getCachedNodeCourseInfo(this.node.id, this.semester, this.semClass),
showingAllCourses: false
}
},
diff --git a/resources/vue/components/tree/StudipTreeTable.vue b/resources/vue/components/tree/StudipTreeTable.vue
index 1dc45a0..1703025 100644
--- a/resources/vue/components/tree/StudipTreeTable.vue
+++ b/resources/vue/components/tree/StudipTreeTable.vue
@@ -245,7 +245,7 @@ export default {
courses: [],
assistiveLive: '',
subLevelsCourses: 0,
- thisLevelCourses: 0,
+ thisLevelCourses: this.getCachedNodeCourseInfo(this.node.id, this.semester, this.semClass),
showingAllCourses: false
}
},
diff --git a/resources/vue/components/tree/TreeNodeCourseInfo.vue b/resources/vue/components/tree/TreeNodeCourseInfo.vue
index 3f3777c..859429e 100644
--- a/resources/vue/components/tree/TreeNodeCourseInfo.vue
+++ b/resources/vue/components/tree/TreeNodeCourseInfo.vue
@@ -32,22 +32,24 @@ export default {
},
data() {
return {
- isLoading: false,
- courseCount: 0,
+ courseCount: this.getCachedNodeCourseInfo(this.node, this.semester, this.semClass),
showingAllCourses: false
}
},
+ computed: {
+ isLoading() {
+ return this.courseCount === null;
+ }
+ },
methods: {
showAllCourses(state) {
this.showingAllCourses = state;
this.$emit('showAllCourses', state);
},
loadNodeInfo(node) {
- this.isLoading = true;
this.getNodeCourseInfo(node, this.semester, this.semClass)
.then(info => {
- this.courseCount = info?.data.courses;
- this.isLoading = false;
+ this.courseCount = info?.data.courses ?? 0;
});
}
},
diff --git a/resources/vue/mixins/TreeMixin.js b/resources/vue/mixins/TreeMixin.js
index 4263eaf..1269ffa 100644
--- a/resources/vue/mixins/TreeMixin.js
+++ b/resources/vue/mixins/TreeMixin.js
@@ -1,4 +1,9 @@
import axios from 'axios';
+import ChunkedRequester from '@/assets/javascripts/lib/chunked-requester';
+import Cache from '@/assets/javascripts/lib/cache';
+
+const requester = new ChunkedRequester();
+const cache = Cache.getInstance('tree-info/');
export const TreeMixin = {
data() {
@@ -56,7 +61,10 @@ export const TreeMixin = {
{params: parameters}
);
},
- async getNodeCourseInfo(node, semesterId, semClass = 0) {
+ getCachedNodeCourseInfo(node, semesterId, semClass) {
+ return cache.get(['course-info', node.id, semesterId, semClass].join('/')) ?? null;
+ },
+ getNodeCourseInfo(node, semesterId, semClass = 0) {
let parameters = {};
if (semesterId !== 'all' && semesterId !== '0') {
@@ -67,10 +75,17 @@ export const TreeMixin = {
parameters['filter[semclass]'] = semClass;
}
- return axios.get(
+ return requester.addRequest(
STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/' + node.id + '/courseinfo'),
- { params: parameters }
- );
+ parameters
+ ).then(courseinfo => {
+ cache.set(
+ ['course-info', node.id, semesterId, semClass].join('/'),
+ courseinfo.data.courses ?? 0,
+ 3 * 60 * 60
+ );
+ return courseinfo;
+ });
},
nodeUrl(node_id, semester = null ) {
return STUDIP.URLHelper.getURL('', { node_id, semester })