diff --git a/backend/src/controllers/learning-paths.ts b/backend/src/controllers/learning-paths.ts index 0097d568..5a450299 100644 --- a/backend/src/controllers/learning-paths.ts +++ b/backend/src/controllers/learning-paths.ts @@ -3,13 +3,10 @@ import { themes } from '../data/themes.js'; import { FALLBACK_LANG } from '../config.js'; import learningPathService from '../services/learning-paths/learning-path-service.js'; import { Language } from '@dwengo-1/common/util/language'; -import { - PersonalizationTarget, - personalizedForGroup, - personalizedForStudent, -} from '../services/learning-paths/learning-path-personalization-util.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; +import {Group} from "../entities/assignments/group.entity"; +import {getGroupRepository} from "../data/repositories"; /** * Fetch learning paths based on query parameters. @@ -20,20 +17,25 @@ export async function getLearningPaths(req: Request, res: Response): Promise { +async function convertLearningPath(learningPath: LearningPathEntity, order: number, personalizedFor?: Group): Promise { // Fetch the corresponding learning object for each node since some parts of the expected response contains parts // With information which is not available in the LearningPathNodes themselves. const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); @@ -89,10 +90,10 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb async function convertNode( node: LearningPathNode, learningObject: FilteredLearningObject, - personalizedFor: PersonalizationTarget | undefined, + personalizedFor: Group | undefined, nodesToLearningObjects: Map ): Promise { - const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; + const lastSubmission = personalizedFor ? await getLastSubmissionForGroup(node, personalizedFor) : null; const transitions = node.transitions .filter( (trans) => @@ -121,7 +122,7 @@ async function convertNode( */ async function convertNodes( nodesToLearningObjects: Map, - personalizedFor?: PersonalizationTarget + personalizedFor?: Group ): Promise { const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) @@ -181,7 +182,7 @@ const databaseLearningPathProvider: LearningPathProvider = { hruids: string[], language: Language, source: string, - personalizedFor?: PersonalizationTarget + personalizedFor?: Group ): Promise { const learningPathRepo = getLearningPathRepository(); @@ -202,7 +203,7 @@ const databaseLearningPathProvider: LearningPathProvider = { /** * Search learning paths in the database using the given search string. */ - async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise { + async searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise { const learningPathRepo = getLearningPathRepository(); const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); diff --git a/backend/src/services/learning-paths/learning-path-personalization-util.ts b/backend/src/services/learning-paths/learning-path-personalization-util.ts index a9175d13..a10d5ead 100644 --- a/backend/src/services/learning-paths/learning-path-personalization-util.ts +++ b/backend/src/services/learning-paths/learning-path-personalization-util.ts @@ -1,76 +1,22 @@ import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; -import { Student } from '../../entities/users/student.entity.js'; import { Group } from '../../entities/assignments/group.entity.js'; import { Submission } from '../../entities/assignments/submission.entity.js'; -import { getClassRepository, getGroupRepository, getStudentRepository, getSubmissionRepository } from '../../data/repositories.js'; +import { getSubmissionRepository } from '../../data/repositories.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; import { JSONPath } from 'jsonpath-plus'; -export type PersonalizationTarget = { type: 'student'; student: Student } | { type: 'group'; group: Group }; - /** - * Shortcut function to easily create a PersonalizationTarget object for a student by his/her username. - * @param username Username of the student we want to generate a personalized learning path for. - * If there is no student with this username, return undefined. + * Returns the last submission for the learning object associated with the given node and for the group */ -export async function personalizedForStudent(username: string): Promise { - const student = await getStudentRepository().findByUsername(username); - if (student) { - return { - type: 'student', - student: student, - }; - } - return undefined; -} - -/** - * Shortcut function to easily create a PersonalizationTarget object for a group by class name, assignment number and - * group number. - * @param classId Id of the class in which this group was created - * @param assignmentNumber Number of the assignment for which this group was created - * @param groupNumber Number of the group for which we want to personalize the learning path. - */ -export async function personalizedForGroup( - classId: string, - assignmentNumber: number, - groupNumber: number -): Promise { - const clazz = await getClassRepository().findById(classId); - if (!clazz) { - return undefined; - } - const group = await getGroupRepository().findOne({ - assignment: { - within: clazz, - id: assignmentNumber, - }, - groupNumber: groupNumber, - }); - if (group) { - return { - type: 'group', - group: group, - }; - } - return undefined; -} - -/** - * Returns the last submission for the learning object associated with the given node and for the student or group - */ -export async function getLastSubmissionForCustomizationTarget(node: LearningPathNode, pathFor: PersonalizationTarget): Promise { +export async function getLastSubmissionForGroup(node: LearningPathNode, pathFor: Group): Promise { const submissionRepo = getSubmissionRepository(); const learningObjectId: LearningObjectIdentifier = { hruid: node.learningObjectHruid, language: node.language, version: node.version, }; - if (pathFor.type === 'group') { - return await submissionRepo.findMostRecentSubmissionForGroup(learningObjectId, pathFor.group); - } - return await submissionRepo.findMostRecentSubmissionForStudent(learningObjectId, pathFor.student); + return await submissionRepo.findMostRecentSubmissionForGroup(learningObjectId, pathFor); } /** diff --git a/backend/src/services/learning-paths/learning-path-provider.ts b/backend/src/services/learning-paths/learning-path-provider.ts index 3bf734e7..bc067a41 100644 --- a/backend/src/services/learning-paths/learning-path-provider.ts +++ b/backend/src/services/learning-paths/learning-path-provider.ts @@ -1,6 +1,6 @@ import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; -import { PersonalizationTarget } from './learning-path-personalization-util.js'; import { Language } from '@dwengo-1/common/util/language'; +import { Group } from "../../entities/assignments/group.entity"; /** * Generic interface for a service which provides access to learning paths from a data source. @@ -9,10 +9,10 @@ export interface LearningPathProvider { /** * Fetch the learning paths with the given hruids from the data source. */ - fetchLearningPaths(hruids: string[], language: Language, source: string, personalizedFor?: PersonalizationTarget): Promise; + fetchLearningPaths(hruids: string[], language: Language, source: string, personalizedFor?: Group): Promise; /** * Search learning paths in the data source using the given search string. */ - searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise; + searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise; } diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 0e4d2c5e..73315769 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -1,9 +1,9 @@ import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; import databaseLearningPathProvider from './database-learning-path-provider.js'; import { envVars, getEnvVar } from '../../util/envVars.js'; -import { PersonalizationTarget } from './learning-path-personalization-util.js'; import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; import { Language } from '@dwengo-1/common/util/language'; +import {Group} from "../../entities/assignments/group.entity"; const userContentPrefix = getEnvVar(envVars.UserContentPrefix); const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; @@ -23,7 +23,7 @@ const learningPathService = { hruids: string[], language: Language, source: string, - personalizedFor?: PersonalizationTarget + personalizedFor?: Group ): Promise { const userContentHruids = hruids.filter((hruid) => hruid.startsWith(userContentPrefix)); const nonUserContentHruids = hruids.filter((hruid) => !hruid.startsWith(userContentPrefix)); @@ -48,7 +48,7 @@ const learningPathService = { /** * Search learning paths in the data source using the given search string. */ - async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise { + async searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise { const providerResponses = await Promise.all( allProviders.map(async (provider) => provider.searchLearningPaths(query, language, personalizedFor)) ); diff --git a/backend/tests/setup-tests.ts b/backend/tests/setup-tests.ts index 5bd2fbd6..0d71b434 100644 --- a/backend/tests/setup-tests.ts +++ b/backend/tests/setup-tests.ts @@ -13,6 +13,8 @@ import { makeTestAttachments } from './test_assets/content/attachments.testdata. import { makeTestQuestions } from './test_assets/questions/questions.testdata.js'; import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; +import {Collection} from "@mikro-orm/core"; +import {Group} from "../src/entities/assignments/group.entity"; export async function setupTestApp(): Promise { dotenv.config({ path: '.env.test' }); @@ -28,8 +30,8 @@ export async function setupTestApp(): Promise { const assignments = makeTestAssignemnts(em, classes); const groups = makeTestGroups(em, students, assignments); - assignments[0].groups = groups.slice(0, 3); - assignments[1].groups = groups.slice(3, 4); + assignments[0].groups = new Collection(groups.slice(0, 3)); + assignments[1].groups = new Collection(groups.slice(3, 4)); const teacherInvitations = makeTestTeacherInvitations(em, teachers, classes); const classJoinRequests = makeTestClassJoinRequests(em, students, classes); diff --git a/backend/tool/seed.ts b/backend/tool/seed.ts index 3ded9379..f1742b69 100644 --- a/backend/tool/seed.ts +++ b/backend/tool/seed.ts @@ -15,7 +15,7 @@ import { makeTestStudents } from '../tests/test_assets/users/students.testdata.j import { makeTestTeachers } from '../tests/test_assets/users/teachers.testdata.js'; import { getLogger, Logger } from '../src/logging/initalize.js'; import { Collection } from '@mikro-orm/core'; -import { Group } from '../dist/entities/assignments/group.entity.js'; +import { Group } from '../src/entities/assignments/group.entity'; const logger: Logger = getLogger(); diff --git a/frontend/package.json b/frontend/package.json index b6bd5deb..4fbfb0cf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,10 +10,11 @@ "preview": "vite preview", "type-check": "vue-tsc --build", "format": "prettier --write src/", + "test:e2e": "playwright test", "format-check": "prettier --check src/", "lint": "eslint . --fix", - "test:unit": "vitest --run", - "test:e2e": "playwright test" + "pretest:unit": "tsx ../docs/api/generate.ts && npm run build", + "test:unit": "vitest --run" }, "dependencies": { "@tanstack/react-query": "^5.69.0",