Skip to content

Commit 16a6c5d

Browse files
authored
Merge pull request #274 from GBSL-Informatik/chore/fetch-multiple-docs-with-post
chore: fetch multiple documents with post instead of query param
2 parents 8236da8 + 16a6d0a commit 16a6c5d

9 files changed

Lines changed: 157 additions & 94 deletions

File tree

src/api/OfflineApi/index.ts

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,56 @@ export default class OfflineApi {
159159

160160
// Method to handle POST requests
161161
async post<T = any>(url: string, data: T, ...config: any): AxiosPromise<T> {
162-
const { model, id, query } = urlParts(url);
162+
const { model, id, query, parts } = urlParts(url);
163163
log('post', url, data);
164164
switch (model) {
165165
case 'admin':
166166
return resolveResponse(data as unknown as T);
167167
case 'cms':
168168
return resolveResponse({} as unknown as T);
169+
case 'users':
170+
if (parts.length === 1 && parts[0] === 'documentRoots') {
171+
const { documentRootIds, type } = data as {
172+
documentRootIds: string[];
173+
type?: DocumentType;
174+
};
175+
if (documentRootIds.length === 0) {
176+
resolveResponse([] as unknown as T);
177+
}
178+
const documentRootDocs = await Promise.all(
179+
documentRootIds.map((id) => this.documentsBy(id))
180+
);
181+
const filteredDocs = type
182+
? documentRootDocs.map((docs) => docs.filter((doc) => doc.type === type))
183+
: documentRootDocs;
184+
185+
const documenRoots = documentRootIds.map((rid) => {
186+
return {
187+
id: rid,
188+
access: Access.RW_DocumentRoot,
189+
sharedAccess: Access.RW_DocumentRoot,
190+
userPermissions: [],
191+
groupPermissions: [],
192+
documents:
193+
filteredDocs.find(
194+
(docs) => docs.length > 0 && docs[0].documentRootId === rid
195+
) || []
196+
};
197+
}) as unknown as T;
198+
log('-> post', url, documenRoots);
199+
return resolveResponse(documenRoots);
200+
}
201+
return resolveResponse([] as unknown as T);
169202
case 'documents':
203+
if (url === 'documents/multiple') {
204+
const documentRootIds = new Set((data as { documentRootIds: string[] }).documentRootIds);
205+
const allDocuments = await this.dbAdapter.getAll<Document<any>>(DOCUMENTS_STORE);
206+
207+
const filteredDocuments = allDocuments.filter((doc) =>
208+
documentRootIds.has(doc.documentRootId)
209+
);
210+
return resolveResponse(filteredDocuments as unknown as T);
211+
}
170212
const document = await this.upsertDocumentRecord<any>(
171213
data as Partial<Document<any>>,
172214
query.has('uniqueMain')
@@ -214,35 +256,6 @@ export default class OfflineApi {
214256
switch (model) {
215257
case 'user':
216258
return resolveResponse(OfflineUser as unknown as T);
217-
case 'users':
218-
if (parts.length === 1 && parts[0] === 'documentRoots') {
219-
const ids = query.getAll('ids');
220-
const docType = query.get('type') as DocumentType | null;
221-
if (ids.length === 0) {
222-
resolveResponse([] as unknown as T);
223-
}
224-
const documentRootDocs = await Promise.all(ids.map((id) => this.documentsBy(id)));
225-
const filteredDocs = docType
226-
? documentRootDocs.map((docs) => docs.filter((doc) => doc.type === docType))
227-
: documentRootDocs;
228-
229-
const documenRoots = ids.map((rid) => {
230-
return {
231-
id: rid,
232-
access: Access.RW_DocumentRoot,
233-
sharedAccess: Access.RW_DocumentRoot,
234-
userPermissions: [],
235-
groupPermissions: [],
236-
documents:
237-
filteredDocs.find(
238-
(docs) => docs.length > 0 && docs[0].documentRootId === rid
239-
) || []
240-
};
241-
}) as unknown as T;
242-
log('-> get', url, documenRoots);
243-
return resolveResponse(documenRoots);
244-
}
245-
return resolveResponse([OfflineUser] as unknown as T);
246259
case 'admin':
247260
return resolveResponse([] as unknown as T);
248261
case 'allowedActions':
@@ -255,6 +268,7 @@ export default class OfflineApi {
255268
}
256269
return resolveResponse(null);
257270
}
271+
// TODO: is this needed/used at all?
258272
if (query.has('ids')) {
259273
const ids = query.getAll('ids');
260274
const filteredDocuments: Document<any>[] = [];
@@ -266,19 +280,12 @@ export default class OfflineApi {
266280
}
267281
return resolveResponse(filteredDocuments as unknown as T);
268282
}
269-
if (query.has('rids')) {
270-
const rids = query.getAll('rids');
271-
272-
const allDocuments = await this.dbAdapter.getAll<Document<any>>(DOCUMENTS_STORE);
273-
274-
const filteredDocuments = allDocuments.filter((doc) => rids.includes(doc.documentRootId));
275-
276-
return resolveResponse(filteredDocuments as unknown as T);
277-
}
278283

279284
return resolveResponse(
280285
(await this.dbAdapter.getAll<Document<any>>(DOCUMENTS_STORE)) as unknown as T
281286
);
287+
case 'users':
288+
return resolveResponse([OfflineUser] as unknown as T);
282289
case 'documentRoots':
283290
if (parts[0] === 'permissions') {
284291
return resolveResponse({

src/api/document.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,7 @@ export function update<Type extends DocumentType>(
249249
* TODO: would it be better to only grab documents from a specific student group?
250250
*/
251251
export function allDocuments(documentRootIds: string[], signal: AbortSignal): AxiosPromise<Document<any>[]> {
252-
return api.get(`/documents?${documentRootIds.map((id) => `rids=${id}`).join('&')}`, {
253-
signal
254-
});
252+
return api.post('/documents/multiple', { documentRootIds }, { signal });
255253
}
256254

257255
export function linkTo<Type extends DocumentType>(

src/api/documentRoot.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,20 @@ export function findManyFor(
5858
if (documentType) {
5959
params.append('type', documentType);
6060
}
61-
return api.get(`/users/${userId}/documentRoots?${params.toString()}`, { signal });
61+
const data: {
62+
documentRootIds: string[];
63+
ignoreMissingRoots?: boolean;
64+
type?: string;
65+
} = {
66+
documentRootIds: ids
67+
};
68+
if (ignoreMissingRoots) {
69+
data.ignoreMissingRoots = true;
70+
}
71+
if (documentType) {
72+
data.type = documentType;
73+
}
74+
return api.post(`/users/${userId}/documentRoots`, data, { signal });
6275
}
6376

6477
export function create(

src/models/Page.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,20 @@ export default class Page {
113113
.sort((a, b) => a!.root!.meta!.pagePosition - b!.root!.meta.pagePosition);
114114
}
115115

116+
@computed
117+
get primaryStudentGroupName() {
118+
return this._primaryStudentGroupName ?? this.store.currentStudentGroupName;
119+
}
120+
116121
@action
117122
setPrimaryStudentGroupName(name?: string) {
118123
this._primaryStudentGroupName = name;
119124
}
120125

126+
get hasCustomPrimaryStudentGroup() {
127+
return !!this._primaryStudentGroupName;
128+
}
129+
121130
@computed
122131
get primaryStudentGroup() {
123132
return this._primaryStudentGroupName

src/stores/DocumentRootStore.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,12 @@ export class DocumentRootStore extends iStore {
161161
access: accessConfig || {}
162162
});
163163
this.loadQueued();
164-
if (this.queued.size > 42) {
165-
// max 2048 characters in URL - flush if too many
166-
this.loadQueued.flush();
167-
}
168164
}
169165

170166
/**
171167
* load the documentRoots only
172168
* - after 20 ms of "silence" (=no further load-requests during this period)
173169
* - or after 25ms have elapsed
174-
* - or when more then 42 records are queued (@see loadInNextBatch)
175-
* (otherwise the URL maxlength would be reached)
176170
*/
177171
loadQueued = _.debounce(action(this._loadQueued), 25, {
178172
leading: false,
@@ -192,12 +186,6 @@ export class DocumentRootStore extends iStore {
192186
}
193187
const batch = [...this.queued];
194188
this.queued.clear();
195-
if (batch.length > 42) {
196-
const postponed = batch.splice(42);
197-
postponed.forEach((item) => this.queued.set(item[0], item[1]));
198-
this.loadQueued();
199-
console.log('Postponing', postponed.length, 'document roots for next batch');
200-
}
201189
const currentBatch = new Map(batch);
202190
/**
203191
* if the user is not logged in, we can't load the documents

src/stores/PageStore.ts

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import globalData from '@generated/globalData';
1212
const ensureTrailingSlash = (str: string) => {
1313
return str.endsWith('/') ? str : `${str}/`;
1414
};
15+
const ensureLeadingSlash = (str: string) => {
16+
return str.startsWith('/') ? str : `/${str}`;
17+
};
18+
const BasePathRegex = new RegExp(`^${siteConfig.baseUrl}`, 'i');
1519
export const AUTO_GENERATED_PAGE_PREFIX = '__auto_generated__';
1620
export const SidebarVersions = (
1721
globalData['docusaurus-plugin-content-docs'].default as GlobalPluginData
@@ -43,12 +47,14 @@ interface PagesIndex {
4347
export class PageStore extends iStore {
4448
readonly root: RootStore;
4549

46-
pages = observable<Page>([]);
50+
pages = observable<Page>([], { deep: false });
4751

4852
@observable accessor currentPageId: string | undefined = undefined;
4953
@observable accessor runningTurtleScriptId: string | undefined = undefined;
5054

5155
@observable.ref accessor _pageIndex: PageIndex[] = [];
56+
@observable accessor currentPath: string | undefined = undefined;
57+
loadedPageIndices = new Set<string>();
5258

5359
constructor(store: RootStore) {
5460
super();
@@ -59,14 +65,24 @@ export class PageStore extends iStore {
5965
return SidebarVersions;
6066
}
6167

68+
@computed
69+
get sidebarVersionPaths() {
70+
return SidebarVersions.map((version) => version.versionPath);
71+
}
72+
6273
@computed
6374
get landingPages() {
6475
return this.pages.filter((page) => page.isLandingpage);
6576
}
6677

78+
@computed
79+
get isPageIndexLoaded() {
80+
return this._pageIndex.length > 0;
81+
}
82+
6783
@action
6884
loadPageIndex(force: boolean = false) {
69-
if (!force && this._pageIndex.length > 0) {
85+
if (!force && this.isPageIndexLoaded) {
7086
return Promise.resolve();
7187
}
7288
return fetch(`${siteConfig.baseUrl}tdev-artifacts/page-progress-state/pageIndex.json`)
@@ -96,23 +112,72 @@ export class PageStore extends iStore {
96112
})
97113
)
98114
.then(() => {
99-
this.loadTaskableDocuments();
115+
if (this.currentPath) {
116+
this.loadTaskableDocuments(this.currentStudentGroupName);
117+
}
100118
})
101119
.catch((err) => {
102120
console.error('Failed to load page index', err);
103121
});
104122
}
105123

106124
@action
107-
loadTaskableDocuments() {
108-
this.pages.forEach((page) => {
109-
page.taskableDocumentRootIds.forEach((id) => {
110-
this.root.documentRootStore.loadInNextBatch(id, undefined, {
111-
skipCreate: true,
112-
documentRoot: 'addIfMissing'
125+
setCurrentPath(path: string | undefined) {
126+
if (path === this.currentPath) {
127+
return;
128+
}
129+
if (!path) {
130+
this.currentPath = undefined;
131+
return;
132+
}
133+
this.currentPath = path.replace(BasePathRegex, '/');
134+
if (this.isPageIndexLoaded) {
135+
this.loadTaskableDocuments(this.currentStudentGroupName);
136+
}
137+
this.resetPagesStudentGroups();
138+
}
139+
140+
@action
141+
resetPagesStudentGroups() {
142+
this.pages
143+
.filter((p) => p.hasCustomPrimaryStudentGroup)
144+
.forEach((p) => p.setPrimaryStudentGroupName(undefined));
145+
}
146+
147+
@computed
148+
get currentPathParts() {
149+
if (!this.currentPath) {
150+
return [];
151+
}
152+
return this.currentPath.split('/').filter((p) => p.length > 0);
153+
}
154+
155+
@computed
156+
get currentStudentGroupName() {
157+
return this.currentPathParts[0];
158+
}
159+
160+
@action
161+
loadTaskableDocuments(pathPrefix: string | undefined, force?: boolean) {
162+
const prefix = ensureLeadingSlash(ensureTrailingSlash(pathPrefix ?? ''));
163+
const isEntryPoint = this.sidebarVersionPaths.includes(prefix);
164+
if (!isEntryPoint) {
165+
return;
166+
}
167+
if (!force && this.loadedPageIndices.has(prefix)) {
168+
return;
169+
}
170+
this.loadedPageIndices.add(prefix);
171+
this.pages
172+
.filter((p) => p.path.startsWith(prefix) && !p.isAutoGenerated)
173+
.forEach((page) => {
174+
page.taskableDocumentRootIds.forEach((id) => {
175+
this.root.documentRootStore.loadInNextBatch(id, undefined, {
176+
skipCreate: true,
177+
documentRoot: 'addIfMissing'
178+
});
113179
});
114180
});
115-
});
116181
}
117182

118183
find = computedFn(

src/stores/UserStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export class UserStore extends iStore<`update-${string}`> {
160160
return this.withAbortController(`load-all`, async (ct) => {
161161
return apiAll(ct.signal).then(
162162
action((res) => {
163-
const models = res.data.map((d) => this.createModel(d));
163+
const models = res.data?.map((d) => this.createModel(d));
164164
this.users.replace(models);
165165
})
166166
);

src/theme/DocItem/Content/index.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)