Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/features/workbooks/utils/workbooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getUrlSlugFrom,
getWorkBooksByType,
buildTaskResultsByWorkBookId,
buildTaskIdsFromWorkbooks,
calcWorkBookGradeModes,
getGradeMode,
getTaskResult,
Expand Down Expand Up @@ -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<string, Task> = new Map([
Expand Down
11 changes: 10 additions & 1 deletion src/features/workbooks/utils/workbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -82,7 +91,7 @@ export function countReadableWorkbooks(workbooks: WorkbooksList, userId: string)
*/
export function calcWorkBookGradeModes(
workbooks: { id: number; workBookTasks: WorkBookTaskBase[] }[],
tasksMapByIds: Map<string, Task>,
tasksMapByIds: Map<string, Pick<Task, 'grade'>>,
): Map<number, TaskGrade> {
const gradeModes: Map<number, TaskGrade> = new Map();

Expand Down
43 changes: 24 additions & 19 deletions src/routes/workbooks/+page.server.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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<number, SolutionCategory>()),
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<number, SolutionCategory>()),
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,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/workbooks/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
let loggedInUser = $derived(data.loggedInUser);
let role = $derived(loggedInUser?.role as Roles);

const tasksMapByIds = $derived(data.tasksMapByIds as Map<string, Task>);
const tasksMapByIds = $derived(data.tasksMapByIds as Map<string, Pick<Task, 'grade'>>);
let taskResultsByTaskId = $derived(data.taskResultsByTaskId as Map<string, TaskResult>);

const gradeModesEachWorkbook = $derived(calcWorkBookGradeModes(workbooks, tasksMapByIds));
Expand Down
Loading