diff --git a/src/features/workbooks/utils/workbooks.test.ts b/src/features/workbooks/utils/workbooks.test.ts index 2e007d1d8..c1e39a3fc 100644 --- a/src/features/workbooks/utils/workbooks.test.ts +++ b/src/features/workbooks/utils/workbooks.test.ts @@ -9,6 +9,7 @@ import { getUrlSlugFrom, getWorkBooksByType, buildTaskResultsByWorkBookId, + buildTaskIdsFromWorkbooks, calcWorkBookGradeModes, getGradeMode, getTaskResult, @@ -179,6 +180,44 @@ describe('Workbooks', () => { }); }); + describe('buildTaskIdsFromWorkbooks', () => { + test('returns unique task ids deduplicated across workbooks', () => { + const workbooks = [ + createWorkBookListBase({ + id: 1, + workBookTasks: [ + { taskId: 'abc300_a', priority: 1, comment: '' }, + { taskId: 'abc300_b', priority: 2, comment: '' }, + ], + }), + createWorkBookListBase({ + id: 2, + workBookTasks: [ + { taskId: 'abc300_b', priority: 1, comment: '' }, + { taskId: 'abc301_a', priority: 2, comment: '' }, + ], + }), + ]; + const result = buildTaskIdsFromWorkbooks(workbooks); + expect(result).toHaveLength(3); + expect(result).toContain('abc300_a'); + expect(result).toContain('abc300_b'); + expect(result).toContain('abc301_a'); + }); + + test('returns empty array for empty workbooks', () => { + expect(buildTaskIdsFromWorkbooks([])).toEqual([]); + }); + + test('returns empty array when workbooks have no tasks', () => { + const workbooks = [ + createWorkBookListBase({ id: 1, workBookTasks: [] }), + createWorkBookListBase({ id: 2, workBookTasks: [] }), + ]; + expect(buildTaskIdsFromWorkbooks(workbooks)).toEqual([]); + }); + }); + describe('calcWorkBookGradeModes', () => { test('returns most frequent grade for each workbook', () => { const tasksMapByIds: Map = new Map([ diff --git a/src/features/workbooks/utils/workbooks.ts b/src/features/workbooks/utils/workbooks.ts index b638dfee1..e8bc9ce24 100644 --- a/src/features/workbooks/utils/workbooks.ts +++ b/src/features/workbooks/utils/workbooks.ts @@ -71,6 +71,15 @@ export function countReadableWorkbooks(workbooks: WorkbooksList, userId: string) }, 0); } +// Deduplicates task IDs across workbooks to avoid redundant DB fetches. +export function buildTaskIdsFromWorkbooks( + workbooks: { workBookTasks: WorkBookTaskBase[] }[], +): string[] { + return Array.from( + new Set(workbooks.flatMap((workbook) => workbook.workBookTasks.map((task) => task.taskId))), + ); +} + /** * Calculates the grade modes for a list of workbooks in curriculum based on their tasks. * @@ -82,7 +91,7 @@ export function countReadableWorkbooks(workbooks: WorkbooksList, userId: string) */ export function calcWorkBookGradeModes( workbooks: { id: number; workBookTasks: WorkBookTaskBase[] }[], - tasksMapByIds: Map, + tasksMapByIds: Map>, ): Map { const gradeModes: Map = new Map(); diff --git a/src/routes/workbooks/+page.server.ts b/src/routes/workbooks/+page.server.ts index 2268c4946..a17955ad7 100644 --- a/src/routes/workbooks/+page.server.ts +++ b/src/routes/workbooks/+page.server.ts @@ -1,6 +1,7 @@ import { error, redirect } from '@sveltejs/kit'; import * as taskCrud from '$lib/services/tasks'; +import { buildTaskIdsFromWorkbooks } from '$features/workbooks/utils/workbooks'; import * as taskResultsCrud from '$lib/services/task_results'; import * as workBooksCrud from '$features/workbooks/services/workbooks'; @@ -60,25 +61,29 @@ export async function load({ locals, url }) { const adminUser = loggedInUser && isAdmin(loggedInUser.role as Roles); try { - const [ - workbooks, - availableCategories, - solutionCategoryMap, - tasksMapByIds, - taskResultsByTaskId, - ] = await Promise.all([ - fetchWorkbooksByTab(tab, selectedGrade, selectedCategory, !!adminUser), - tab === WorkBookTab.SOLUTION - ? getAvailableSolutionCategories(!!adminUser) - : Promise.resolve([]), - tab === WorkBookTab.SOLUTION && selectedCategory === ALL_SOLUTION_CATEGORIES - ? getSolutionCategoryMapByWorkbookId(!!adminUser) - : Promise.resolve(new Map()), - taskCrud.getTasksByTaskId(), - loggedInUser - ? taskResultsCrud.getTaskResultsOnlyResultExists(loggedInUser.id, true) - : Promise.resolve(new Map()), - ]); + const [workbooks, availableCategories, solutionCategoryMap, taskResultsByTaskId] = + await Promise.all([ + fetchWorkbooksByTab(tab, selectedGrade, selectedCategory, !!adminUser), + tab === WorkBookTab.SOLUTION + ? getAvailableSolutionCategories(!!adminUser) + : Promise.resolve([]), + tab === WorkBookTab.SOLUTION && selectedCategory === ALL_SOLUTION_CATEGORIES + ? getSolutionCategoryMapByWorkbookId(!!adminUser) + : Promise.resolve(new Map()), + loggedInUser + ? taskResultsCrud.getTaskResultsOnlyResultExists(loggedInUser.id, true) + : Promise.resolve(new Map()), + ]); + + // Grade modes are only displayed on the CURRICULUM tab for logged-in users. + // For other tabs / anonymous, the id list is empty and getTasksWithSelectedTaskIds + // returns [] without a query (see tasks.ts guard), so tasksMapByIds becomes an empty Map. + const referencedTaskIds = + tab === WorkBookTab.CURRICULUM && loggedInUser ? buildTaskIdsFromWorkbooks(workbooks) : []; + const referencedTasks = await taskCrud.getTasksWithSelectedTaskIds(referencedTaskIds); + const tasksMapByIds = new Map( + referencedTasks.map((task) => [task.task_id, { grade: task.grade }]), + ); return { workbooks, diff --git a/src/routes/workbooks/+page.svelte b/src/routes/workbooks/+page.svelte index 5f45df0d8..59e6406b7 100644 --- a/src/routes/workbooks/+page.svelte +++ b/src/routes/workbooks/+page.svelte @@ -33,7 +33,7 @@ let loggedInUser = $derived(data.loggedInUser); let role = $derived(loggedInUser?.role as Roles); - const tasksMapByIds = $derived(data.tasksMapByIds as Map); + const tasksMapByIds = $derived(data.tasksMapByIds as Map>); let taskResultsByTaskId = $derived(data.taskResultsByTaskId as Map); const gradeModesEachWorkbook = $derived(calcWorkBookGradeModes(workbooks, tasksMapByIds));