style: fix linting issues met Prettier
This commit is contained in:
		
							parent
							
								
									af8c783a26
								
							
						
					
					
						commit
						5168ceaee0
					
				
					 56 changed files with 680 additions and 741 deletions
				
			
		|  | @ -5,8 +5,8 @@ import learningPathService from '../services/learning-paths/learning-path-servic | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { BadRequestException } from '../exceptions/bad-request-exception.js'; | import { BadRequestException } from '../exceptions/bad-request-exception.js'; | ||||||
| import { NotFoundException } from '../exceptions/not-found-exception.js'; | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
| import {Group} from "../entities/assignments/group.entity"; | import { Group } from '../entities/assignments/group.entity'; | ||||||
| import {getAssignmentRepository, getGroupRepository} from "../data/repositories"; | import { getAssignmentRepository, getGroupRepository } from '../data/repositories'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Fetch learning paths based on query parameters. |  * Fetch learning paths based on query parameters. | ||||||
|  | @ -27,13 +27,9 @@ export async function getLearningPaths(req: Request, res: Response): Promise<voi | ||||||
|         if (!assignmentNo || !classId) { |         if (!assignmentNo || !classId) { | ||||||
|             throw new BadRequestException('If forGroupNo is specified, assignmentNo and classId must also be specified.'); |             throw new BadRequestException('If forGroupNo is specified, assignmentNo and classId must also be specified.'); | ||||||
|         } |         } | ||||||
|         const assignment = await getAssignmentRepository().findByClassIdAndAssignmentId( |         const assignment = await getAssignmentRepository().findByClassIdAndAssignmentId(classId, parseInt(assignmentNo)); | ||||||
|             classId, parseInt(assignmentNo) |  | ||||||
|         ); |  | ||||||
|         if (assignment) { |         if (assignment) { | ||||||
|             forGroup = await getGroupRepository().findByAssignmentAndGroupNumber( |             forGroup = (await getGroupRepository().findByAssignmentAndGroupNumber(assignment, parseInt(forGroupNo))) ?? undefined; | ||||||
|                 assignment, parseInt(forGroupNo) |  | ||||||
|             ) ?? undefined; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -56,11 +52,6 @@ export async function getLearningPaths(req: Request, res: Response): Promise<voi | ||||||
|         hruidList = themes.flatMap((theme) => theme.hruids); |         hruidList = themes.flatMap((theme) => theme.hruids); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const learningPaths = await learningPathService.fetchLearningPaths( |     const learningPaths = await learningPathService.fetchLearningPaths(hruidList, language as Language, `HRUIDs: ${hruidList.join(', ')}`, forGroup); | ||||||
|         hruidList, |  | ||||||
|         language as Language, |  | ||||||
|         `HRUIDs: ${hruidList.join(', ')}`, |  | ||||||
|         forGroup |  | ||||||
|     ); |  | ||||||
|     res.json(learningPaths.data); |     res.json(learningPaths.data); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -62,18 +62,15 @@ export class SubmissionRepository extends DwengoEntityRepository<Submission> { | ||||||
|     /** |     /** | ||||||
|      * Looks up all submissions for the given learning object which were submitted as part of the given assignment. |      * Looks up all submissions for the given learning object which were submitted as part of the given assignment. | ||||||
|      */ |      */ | ||||||
|     public async findAllSubmissionsForLearningObjectAndAssignment( |     public async findAllSubmissionsForLearningObjectAndAssignment(loId: LearningObjectIdentifier, assignment: Assignment): Promise<Submission[]> { | ||||||
|         loId: LearningObjectIdentifier, |  | ||||||
|         assignment: Assignment, |  | ||||||
|     ): Promise<Submission[]> { |  | ||||||
|         return this.findAll({ |         return this.findAll({ | ||||||
|             where: { |             where: { | ||||||
|                 learningObjectHruid: loId.hruid, |                 learningObjectHruid: loId.hruid, | ||||||
|                 learningObjectLanguage: loId.language, |                 learningObjectLanguage: loId.language, | ||||||
|                 learningObjectVersion: loId.version, |                 learningObjectVersion: loId.version, | ||||||
|                 onBehalfOf: { |                 onBehalfOf: { | ||||||
|                     assignment |                     assignment, | ||||||
|                 } |                 }, | ||||||
|             }, |             }, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | @ -81,16 +78,13 @@ export class SubmissionRepository extends DwengoEntityRepository<Submission> { | ||||||
|     /** |     /** | ||||||
|      * Looks up all submissions for the given learning object which were submitted by the given group |      * Looks up all submissions for the given learning object which were submitted by the given group | ||||||
|      */ |      */ | ||||||
|     public async findAllSubmissionsForLearningObjectAndGroup( |     public async findAllSubmissionsForLearningObjectAndGroup(loId: LearningObjectIdentifier, group: Group): Promise<Submission[]> { | ||||||
|         loId: LearningObjectIdentifier, |  | ||||||
|         group: Group, |  | ||||||
|     ): Promise<Submission[]> { |  | ||||||
|         return this.findAll({ |         return this.findAll({ | ||||||
|             where: { |             where: { | ||||||
|                 learningObjectHruid: loId.hruid, |                 learningObjectHruid: loId.hruid, | ||||||
|                 learningObjectLanguage: loId.language, |                 learningObjectLanguage: loId.language, | ||||||
|                 learningObjectVersion: loId.version, |                 learningObjectVersion: loId.version, | ||||||
|                 onBehalfOf: group |                 onBehalfOf: group, | ||||||
|             }, |             }, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { LearningPath } from '../../entities/content/learning-path.entity.js'; | import { LearningPath } from '../../entities/content/learning-path.entity.js'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {LearningPathNode} from "../../entities/content/learning-path-node.entity"; | import { LearningPathNode } from '../../entities/content/learning-path-node.entity'; | ||||||
| import {RequiredEntityData} from "@mikro-orm/core"; | import { RequiredEntityData } from '@mikro-orm/core'; | ||||||
| import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity"; | import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity'; | ||||||
| import {EntityAlreadyExistsException} from "../../exceptions/entity-already-exists-exception"; | import { EntityAlreadyExistsException } from '../../exceptions/entity-already-exists-exception'; | ||||||
| 
 | 
 | ||||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||||
|     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { |     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||||
|  | @ -28,32 +28,26 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public createNode( |     public createNode(nodeData: RequiredEntityData<LearningPathNode>): LearningPathNode { | ||||||
|         nodeData: RequiredEntityData<LearningPathNode> |  | ||||||
|     ): LearningPathNode { |  | ||||||
|         return this.em.create(LearningPathNode, nodeData); |         return this.em.create(LearningPathNode, nodeData); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public createTransition( |     public createTransition(transitionData: RequiredEntityData<LearningPathTransition>): LearningPathTransition { | ||||||
|         transitionData: RequiredEntityData<LearningPathTransition> |         return this.em.create(LearningPathTransition, transitionData); | ||||||
|     ): LearningPathTransition { |  | ||||||
|         return this.em.create(LearningPathTransition, transitionData) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public async saveLearningPathNodesAndTransitions( |     public async saveLearningPathNodesAndTransitions( | ||||||
|         path: LearningPath, |         path: LearningPath, | ||||||
|         nodes: LearningPathNode[], |         nodes: LearningPathNode[], | ||||||
|         transitions: LearningPathTransition[], |         transitions: LearningPathTransition[], | ||||||
|         options?: {preventOverwrite?: boolean} |         options?: { preventOverwrite?: boolean } | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         if (options?.preventOverwrite && (await this.findOne(path))) { |         if (options?.preventOverwrite && (await this.findOne(path))) { | ||||||
|             throw new EntityAlreadyExistsException( |             throw new EntityAlreadyExistsException('A learning path with this hruid/language combination already exists.'); | ||||||
|                 "A learning path with this hruid/language combination already exists." |  | ||||||
|             ); |  | ||||||
|         } |         } | ||||||
|         const em = this.getEntityManager(); |         const em = this.getEntityManager(); | ||||||
|         await em.persistAndFlush(path); |         await em.persistAndFlush(path); | ||||||
|         await Promise.all(nodes.map(async it => em.persistAndFlush(it))); |         await Promise.all(nodes.map(async (it) => em.persistAndFlush(it))); | ||||||
|         await Promise.all(transitions.map(async it => em.persistAndFlush(it))); |         await Promise.all(transitions.map(async (it) => em.persistAndFlush(it))); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {ArrayType, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property} from '@mikro-orm/core'; | import { ArrayType, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Attachment } from './attachment.entity.js'; | import { Attachment } from './attachment.entity.js'; | ||||||
| import { Teacher } from '../users/teacher.entity.js'; | import { Teacher } from '../users/teacher.entity.js'; | ||||||
| import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; | import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; | ||||||
|  | @ -42,7 +42,7 @@ export class LearningObject { | ||||||
|     @Property({ type: 'array' }) |     @Property({ type: 'array' }) | ||||||
|     keywords: string[] = []; |     keywords: string[] = []; | ||||||
| 
 | 
 | ||||||
|     @Property({ type: new ArrayType(i => Number(i)), nullable: true }) |     @Property({ type: new ArrayType((i) => Number(i)), nullable: true }) | ||||||
|     targetAges?: number[] = []; |     targetAges?: number[] = []; | ||||||
| 
 | 
 | ||||||
|     @Property({ type: 'bool' }) |     @Property({ type: 'bool' }) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel} from '@mikro-orm/core'; | import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; | ||||||
| import { LearningPath } from './learning-path.entity.js'; | import { LearningPath } from './learning-path.entity.js'; | ||||||
| import { LearningPathTransition } from './learning-path-transition.entity.js'; | import { LearningPathTransition } from './learning-path-transition.entity.js'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {Collection, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property} from '@mikro-orm/core'; | import { Collection, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Teacher } from '../users/teacher.entity.js'; | import { Teacher } from '../users/teacher.entity.js'; | ||||||
| import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; | import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; | ||||||
| import { LearningPathNode } from './learning-path-node.entity.js'; | import { LearningPathNode } from './learning-path-node.entity.js'; | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ export function errorHandler(err: unknown, _req: Request, res: Response, _: Next | ||||||
|         logger.warn(`An error occurred while handling a request: ${err} (-> HTTP ${err.status})`); |         logger.warn(`An error occurred while handling a request: ${err} (-> HTTP ${err.status})`); | ||||||
|         res.status(err.status).json(err); |         res.status(err.status).json(err); | ||||||
|     } else { |     } else { | ||||||
|         logger.error(`Unexpected error occurred while handing a request: ${(err as {stack: string})?.stack ?? JSON.stringify(err)}`); |         logger.error(`Unexpected error occurred while handing a request: ${(err as { stack: string })?.stack ?? JSON.stringify(err)}`); | ||||||
|         res.status(500).json(err); |         res.status(500).json(err); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ import { | ||||||
|     LearningPathResponse, |     LearningPathResponse, | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; | } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { getLogger } from '../logging/initalize.js'; | ||||||
| import {v4} from "uuid"; | import { v4 } from 'uuid'; | ||||||
| 
 | 
 | ||||||
| function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { | function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -32,8 +32,7 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL | ||||||
|         educationalGoals: learningObject.educationalGoals, |         educationalGoals: learningObject.educationalGoals, | ||||||
|         returnValue: { |         returnValue: { | ||||||
|             callback_url: learningObject.returnValue.callbackUrl, |             callback_url: learningObject.returnValue.callbackUrl, | ||||||
|             callback_schema: learningObject.returnValue.callbackSchema === "" ? "" |             callback_schema: learningObject.returnValue.callbackSchema === '' ? '' : JSON.parse(learningObject.returnValue.callbackSchema), | ||||||
|                                 : JSON.parse(learningObject.returnValue.callbackSchema), |  | ||||||
|         }, |         }, | ||||||
|         skosConcepts: learningObject.skosConcepts, |         skosConcepts: learningObject.skosConcepts, | ||||||
|         targetAges: learningObject.targetAges || [], |         targetAges: learningObject.targetAges || [], | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ import { | ||||||
|     LearningPathIdentifier, |     LearningPathIdentifier, | ||||||
|     LearningPathResponse, |     LearningPathResponse, | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; | } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import {v4} from "uuid"; | import { v4 } from 'uuid'; | ||||||
| 
 | 
 | ||||||
| const logger: Logger = getLogger(); | const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,9 +13,9 @@ import { | ||||||
|     Transition, |     Transition, | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; | } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {Group} from "../../entities/assignments/group.entity"; | import { Group } from '../../entities/assignments/group.entity'; | ||||||
| import {Collection} from "@mikro-orm/core"; | import { Collection } from '@mikro-orm/core'; | ||||||
| import {v4} from "uuid"; | import { v4 } from 'uuid'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its |  * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its | ||||||
|  | @ -165,7 +165,7 @@ function convertTransition( | ||||||
|             _id: String(index), // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path.
 |             _id: String(index), // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path.
 | ||||||
|             default: false, // We don't work with default transitions but retain this for backwards compatibility.
 |             default: false, // We don't work with default transitions but retain this for backwards compatibility.
 | ||||||
|             next: { |             next: { | ||||||
|                 _id: nextNode._id ? (nextNode._id + index) : v4(), // Construct a unique ID for the transition for backwards compatibility.
 |                 _id: nextNode._id ? nextNode._id + index : v4(), // Construct a unique ID for the transition for backwards compatibility.
 | ||||||
|                 hruid: transition.next.learningObjectHruid, |                 hruid: transition.next.learningObjectHruid, | ||||||
|                 language: nextNode.language, |                 language: nextNode.language, | ||||||
|                 version: nextNode.version, |                 version: nextNode.version, | ||||||
|  | @ -181,12 +181,7 @@ const databaseLearningPathProvider: LearningPathProvider = { | ||||||
|     /** |     /** | ||||||
|      * Fetch the learning paths with the given hruids from the database. |      * Fetch the learning paths with the given hruids from the database. | ||||||
|      */ |      */ | ||||||
|     async fetchLearningPaths( |     async fetchLearningPaths(hruids: string[], language: Language, source: string, personalizedFor?: Group): Promise<LearningPathResponse> { | ||||||
|         hruids: string[], |  | ||||||
|         language: Language, |  | ||||||
|         source: string, |  | ||||||
|         personalizedFor?: Group |  | ||||||
|     ): Promise<LearningPathResponse> { |  | ||||||
|         const learningPathRepo = getLearningPathRepository(); |         const learningPathRepo = getLearningPathRepository(); | ||||||
| 
 | 
 | ||||||
|         const learningPaths = (await Promise.all(hruids.map(async (hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter( |         const learningPaths = (await Promise.all(hruids.map(async (hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter( | ||||||
|  | @ -211,7 +206,7 @@ const databaseLearningPathProvider: LearningPathProvider = { | ||||||
| 
 | 
 | ||||||
|         const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); |         const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); | ||||||
|         return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor))); |         return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor))); | ||||||
|     } |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default databaseLearningPathProvider; | export default databaseLearningPathProvider; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Group } from "../../entities/assignments/group.entity"; | import { Group } from '../../entities/assignments/group.entity'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Generic interface for a service which provides access to learning paths from a data source. |  * Generic interface for a service which provides access to learning paths from a data source. | ||||||
|  |  | ||||||
|  | @ -1,25 +1,23 @@ | ||||||
| import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | ||||||
| import databaseLearningPathProvider from './database-learning-path-provider.js'; | import databaseLearningPathProvider from './database-learning-path-provider.js'; | ||||||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||||
| import {LearningObjectNode, LearningPath, LearningPathResponse} from '@dwengo-1/common/interfaces/learning-content'; | import { LearningObjectNode, LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {Group} from "../../entities/assignments/group.entity"; | import { Group } from '../../entities/assignments/group.entity'; | ||||||
| import {LearningPath as LearningPathEntity} from "../../entities/content/learning-path.entity"; | import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity'; | ||||||
| import {getLearningPathRepository} from "../../data/repositories"; | import { getLearningPathRepository } from '../../data/repositories'; | ||||||
| import {LearningPathNode} from "../../entities/content/learning-path-node.entity"; | import { LearningPathNode } from '../../entities/content/learning-path-node.entity'; | ||||||
| import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity"; | import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity'; | ||||||
| import {base64ToArrayBuffer} from "../../util/base64-buffer-conversion"; | import { base64ToArrayBuffer } from '../../util/base64-buffer-conversion'; | ||||||
| import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; | import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; | ||||||
| import {mapToTeacher} from "../../interfaces/teacher"; | import { mapToTeacher } from '../../interfaces/teacher'; | ||||||
| import {Collection} from "@mikro-orm/core"; | import { Collection } from '@mikro-orm/core'; | ||||||
| 
 | 
 | ||||||
| const userContentPrefix = getEnvVar(envVars.UserContentPrefix); | const userContentPrefix = getEnvVar(envVars.UserContentPrefix); | ||||||
| const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; | const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; | ||||||
| 
 | 
 | ||||||
| export function mapToLearningPath( | export function mapToLearningPath(dto: LearningPath, adminsDto: TeacherDTO[]): LearningPathEntity { | ||||||
|     dto: LearningPath, adminsDto: TeacherDTO[] |     const admins = adminsDto.map((admin) => mapToTeacher(admin)); | ||||||
| ): LearningPathEntity { |  | ||||||
|     const admins = adminsDto.map(admin => mapToTeacher(admin)); |  | ||||||
|     const repo = getLearningPathRepository(); |     const repo = getLearningPathRepository(); | ||||||
|     const path = repo.create({ |     const path = repo.create({ | ||||||
|         hruid: dto.hruid, |         hruid: dto.hruid, | ||||||
|  | @ -27,7 +25,7 @@ export function mapToLearningPath( | ||||||
|         description: dto.description, |         description: dto.description, | ||||||
|         title: dto.title, |         title: dto.title, | ||||||
|         admins, |         admins, | ||||||
|         image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null |         image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null, | ||||||
|     }); |     }); | ||||||
|     const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) => |     const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) => | ||||||
|         repo.createNode({ |         repo.createNode({ | ||||||
|  | @ -38,33 +36,34 @@ export function mapToLearningPath( | ||||||
|             version: nodeDto.version, |             version: nodeDto.version, | ||||||
|             startNode: nodeDto.start_node ?? false, |             startNode: nodeDto.start_node ?? false, | ||||||
|             createdAt: new Date(), |             createdAt: new Date(), | ||||||
|             updatedAt: new Date() |             updatedAt: new Date(), | ||||||
|         }) |         }) | ||||||
|     ); |     ); | ||||||
|     dto.nodes.forEach(nodeDto => { |     dto.nodes.forEach((nodeDto) => { | ||||||
|         const fromNode = nodes.find(it => |         const fromNode = nodes.find( | ||||||
|             it.learningObjectHruid === nodeDto.learningobject_hruid |             (it) => it.learningObjectHruid === nodeDto.learningobject_hruid && it.language === nodeDto.language && it.version === nodeDto.version | ||||||
|             && it.language === nodeDto.language |  | ||||||
|             && it.version === nodeDto.version |  | ||||||
|         )!; |         )!; | ||||||
|         const transitions = nodeDto.transitions.map((transDto, i) => { |         const transitions = nodeDto.transitions | ||||||
|             const toNode = nodes.find(it => |             .map((transDto, i) => { | ||||||
|                 it.learningObjectHruid === transDto.next.hruid |                 const toNode = nodes.find( | ||||||
|                 && it.language === transDto.next.language |                     (it) => | ||||||
|                 && it.version === transDto.next.version |                         it.learningObjectHruid === transDto.next.hruid && | ||||||
|             ); |                         it.language === transDto.next.language && | ||||||
|  |                         it.version === transDto.next.version | ||||||
|  |                 ); | ||||||
| 
 | 
 | ||||||
|             if (toNode) { |                 if (toNode) { | ||||||
|                 return repo.createTransition({ |                     return repo.createTransition({ | ||||||
|                     transitionNumber: i, |                         transitionNumber: i, | ||||||
|                     node: fromNode, |                         node: fromNode, | ||||||
|                     next: toNode, |                         next: toNode, | ||||||
|                     condition: transDto.condition ?? "true" |                         condition: transDto.condition ?? 'true', | ||||||
|                 }); |                     }); | ||||||
|             }  |                 } | ||||||
|                 return undefined; |                 return undefined; | ||||||
|              |             }) | ||||||
|         }).filter(it => it).map(it => it!); |             .filter((it) => it) | ||||||
|  |             .map((it) => it!); | ||||||
| 
 | 
 | ||||||
|         fromNode.transitions = new Collection<LearningPathTransition>(fromNode, transitions); |         fromNode.transitions = new Collection<LearningPathTransition>(fromNode, transitions); | ||||||
|     }); |     }); | ||||||
|  | @ -85,12 +84,7 @@ const learningPathService = { | ||||||
|      * @param source |      * @param source | ||||||
|      * @param personalizedFor If this is set, a learning path personalized for the given group or student will be returned. |      * @param personalizedFor If this is set, a learning path personalized for the given group or student will be returned. | ||||||
|      */ |      */ | ||||||
|     async fetchLearningPaths( |     async fetchLearningPaths(hruids: string[], language: Language, source: string, personalizedFor?: Group): Promise<LearningPathResponse> { | ||||||
|         hruids: string[], |  | ||||||
|         language: Language, |  | ||||||
|         source: string, |  | ||||||
|         personalizedFor?: Group |  | ||||||
|     ): Promise<LearningPathResponse> { |  | ||||||
|         const userContentHruids = hruids.filter((hruid) => hruid.startsWith(userContentPrefix)); |         const userContentHruids = hruids.filter((hruid) => hruid.startsWith(userContentPrefix)); | ||||||
|         const nonUserContentHruids = hruids.filter((hruid) => !hruid.startsWith(userContentPrefix)); |         const nonUserContentHruids = hruids.filter((hruid) => !hruid.startsWith(userContentPrefix)); | ||||||
| 
 | 
 | ||||||
|  | @ -129,8 +123,8 @@ const learningPathService = { | ||||||
|     async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise<void> { |     async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise<void> { | ||||||
|         const repo = getLearningPathRepository(); |         const repo = getLearningPathRepository(); | ||||||
|         const path = mapToLearningPath(dto, admins); |         const path = mapToLearningPath(dto, admins); | ||||||
|         await repo.save(path, {preventOverwrite: true}) |         await repo.save(path, { preventOverwrite: true }); | ||||||
|     } |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default learningPathService; | export default learningPathService; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {getAssignmentRepository, getGroupRepository, getSubmissionRepository} from '../data/repositories.js'; | import { getAssignmentRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { NotFoundException } from '../exceptions/not-found-exception.js'; | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
| import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | ||||||
|  |  | ||||||
|  | @ -27,10 +27,7 @@ export class SqliteAutoincrementSubscriber implements EventSubscriber { | ||||||
| 
 | 
 | ||||||
|         for (const prop of Object.values(args.meta.properties)) { |         for (const prop of Object.values(args.meta.properties)) { | ||||||
|             const property = prop as EntityProperty<T>; |             const property = prop as EntityProperty<T>; | ||||||
|             if ( |             if (property.primary && property.autoincrement && (args.entity as Record<string, unknown>)[property.name] === undefined) { | ||||||
|                 property.primary && property.autoincrement |  | ||||||
|                 && (args.entity as Record<string, unknown>)[property.name] === undefined |  | ||||||
|             ) { |  | ||||||
|                 // Obtain and increment sequence number of this entity.
 |                 // Obtain and increment sequence number of this entity.
 | ||||||
|                 const propertyKey = args.meta.class.name + '.' + property.name; |                 const propertyKey = args.meta.class.name + '.' + property.name; | ||||||
|                 const nextSeqNumber = this.sequenceNumbersForEntityType.get(propertyKey) || 0; |                 const nextSeqNumber = this.sequenceNumbersForEntityType.get(propertyKey) || 0; | ||||||
|  |  | ||||||
|  | @ -33,8 +33,7 @@ describe('AssignmentRepository', () => { | ||||||
| 
 | 
 | ||||||
|     it('should find all by username of the responsible teacher', async () => { |     it('should find all by username of the responsible teacher', async () => { | ||||||
|         const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1'); |         const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1'); | ||||||
|         const resultIds = result.map((it) => it.id) |         const resultIds = result.map((it) => it.id).sort((a, b) => (a ?? 0) - (b ?? 0)); | ||||||
|             .sort((a, b) => (a ?? 0) - (b ?? 0)); |  | ||||||
| 
 | 
 | ||||||
|         expect(resultIds).toEqual([1, 1, 3, 4]); |         expect(resultIds).toEqual([1, 1, 3, 4]); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -91,11 +91,9 @@ describe('SubmissionRepository', () => { | ||||||
|         expect(result[2].submissionNumber).toBe(3); |         expect(result[2].submissionNumber).toBe(3); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it("should find only the submissions for a certain learning object and assignment made for the given group", async () => { |     it('should find only the submissions for a certain learning object and assignment made for the given group', async () => { | ||||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 2); |         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 2); | ||||||
|         const result = await submissionRepository.findAllSubmissionsForLearningObjectAndGroup( |         const result = await submissionRepository.findAllSubmissionsForLearningObjectAndGroup(loId, group!); | ||||||
|             loId, group! |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         expect(result).toHaveLength(1); |         expect(result).toHaveLength(1); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,8 +5,8 @@ import { AttachmentRepository } from '../../../src/data/content/attachment-repos | ||||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; | import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; | ||||||
| import { Attachment } from '../../../src/entities/content/attachment.entity.js'; | import { Attachment } from '../../../src/entities/content/attachment.entity.js'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; | ||||||
| import {testLearningObjectPnNotebooks} from "../../test_assets/content/learning-objects.testdata"; | import { testLearningObjectPnNotebooks } from '../../test_assets/content/learning-objects.testdata'; | ||||||
| import {v4 as uuidV4} from "uuid"; | import { v4 as uuidV4 } from 'uuid'; | ||||||
| 
 | 
 | ||||||
| describe('AttachmentRepository', () => { | describe('AttachmentRepository', () => { | ||||||
|     let attachmentRepo: AttachmentRepository; |     let attachmentRepo: AttachmentRepository; | ||||||
|  | @ -26,7 +26,7 @@ describe('AttachmentRepository', () => { | ||||||
|         newLearningObjectData.version = 101; |         newLearningObjectData.version = 101; | ||||||
|         newLearningObjectData.attachments = []; |         newLearningObjectData.attachments = []; | ||||||
|         newLearningObjectData.uuid = uuidV4(); |         newLearningObjectData.uuid = uuidV4(); | ||||||
|         newLearningObjectData.content = Buffer.from("Content of the newer example"); |         newLearningObjectData.content = Buffer.from('Content of the newer example'); | ||||||
| 
 | 
 | ||||||
|         newLearningObject = learningObjectRepo.create(newLearningObjectData); |         newLearningObject = learningObjectRepo.create(newLearningObjectData); | ||||||
|         await learningObjectRepo.save(newLearningObject); |         await learningObjectRepo.save(newLearningObject); | ||||||
|  | @ -36,7 +36,7 @@ describe('AttachmentRepository', () => { | ||||||
|     it('allows us to add attachments with the same name to a different learning object without throwing an error', async () => { |     it('allows us to add attachments with the same name to a different learning object without throwing an error', async () => { | ||||||
|         attachmentOnlyNewer = structuredClone(attachmentsOlderLearningObject[0]); |         attachmentOnlyNewer = structuredClone(attachmentsOlderLearningObject[0]); | ||||||
|         attachmentOnlyNewer.learningObject = newLearningObject; |         attachmentOnlyNewer.learningObject = newLearningObject; | ||||||
|         attachmentOnlyNewer.content = Buffer.from("New attachment content"); |         attachmentOnlyNewer.content = Buffer.from('New attachment content'); | ||||||
| 
 | 
 | ||||||
|         await attachmentRepo.save(attachmentRepo.create(attachmentOnlyNewer)); |         await attachmentRepo.save(attachmentRepo.create(attachmentOnlyNewer)); | ||||||
|     }); |     }); | ||||||
|  | @ -49,10 +49,7 @@ describe('AttachmentRepository', () => { | ||||||
|             version: testLearningObjectPnNotebooks.version, |             version: testLearningObjectPnNotebooks.version, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         const result = await attachmentRepo.findByLearningObjectIdAndName( |         const result = await attachmentRepo.findByLearningObjectIdAndName(olderLearningObjectId, attachmentsOlderLearningObject[0].name); | ||||||
|             olderLearningObjectId, |  | ||||||
|             attachmentsOlderLearningObject[0].name |  | ||||||
|         ); |  | ||||||
|         expect(result).not.toBeNull(); |         expect(result).not.toBeNull(); | ||||||
|         expect(result!.name).toEqual(attachmentsOlderLearningObject[0].name); |         expect(result!.name).toEqual(attachmentsOlderLearningObject[0].name); | ||||||
|         expect(result!.content).toEqual(attachmentsOlderLearningObject[0].content); |         expect(result!.content).toEqual(attachmentsOlderLearningObject[0].content); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest'; | ||||||
| import { setupTestApp } from '../../setup-tests.js'; | import { setupTestApp } from '../../setup-tests.js'; | ||||||
| import { getAttachmentRepository } from '../../../src/data/repositories.js'; | import { getAttachmentRepository } from '../../../src/data/repositories.js'; | ||||||
| import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; | import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; | ||||||
| import { testLearningObject02 } from "../../test_assets/content/learning-objects.testdata"; | import { testLearningObject02 } from '../../test_assets/content/learning-objects.testdata'; | ||||||
| 
 | 
 | ||||||
| describe('AttachmentRepository', () => { | describe('AttachmentRepository', () => { | ||||||
|     let attachmentRepository: AttachmentRepository; |     let attachmentRepository: AttachmentRepository; | ||||||
|  |  | ||||||
|  | @ -4,12 +4,8 @@ import { setupTestApp } from '../../setup-tests.js'; | ||||||
| import { getLearningObjectRepository } from '../../../src/data/repositories.js'; | import { getLearningObjectRepository } from '../../../src/data/repositories.js'; | ||||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; | import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; | ||||||
| import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; | import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; | ||||||
| import { | import { testLearningObject01, testLearningObject02, testLearningObject03 } from '../../test_assets/content/learning-objects.testdata'; | ||||||
|     testLearningObject01, | import { v4 } from 'uuid'; | ||||||
|     testLearningObject02, |  | ||||||
|     testLearningObject03 |  | ||||||
| } from "../../test_assets/content/learning-objects.testdata"; |  | ||||||
| import {v4} from "uuid"; |  | ||||||
| 
 | 
 | ||||||
| describe('LearningObjectRepository', () => { | describe('LearningObjectRepository', () => { | ||||||
|     let learningObjectRepository: LearningObjectRepository; |     let learningObjectRepository: LearningObjectRepository; | ||||||
|  | @ -43,26 +39,22 @@ describe('LearningObjectRepository', () => { | ||||||
|     it('should allow a learning object with the same id except a different version to be added', async () => { |     it('should allow a learning object with the same id except a different version to be added', async () => { | ||||||
|         const testLearningObject01Newer = structuredClone(testLearningObject01); |         const testLearningObject01Newer = structuredClone(testLearningObject01); | ||||||
|         testLearningObject01Newer.version = 10; |         testLearningObject01Newer.version = 10; | ||||||
|         testLearningObject01Newer.title += " (nieuw)"; |         testLearningObject01Newer.title += ' (nieuw)'; | ||||||
|         testLearningObject01Newer.uuid = v4(); |         testLearningObject01Newer.uuid = v4(); | ||||||
|         testLearningObject01Newer.content = Buffer.from("This is the new content."); |         testLearningObject01Newer.content = Buffer.from('This is the new content.'); | ||||||
|         newerExample = learningObjectRepository.create(testLearningObject01Newer); |         newerExample = learningObjectRepository.create(testLearningObject01Newer); | ||||||
|         await learningObjectRepository.save(newerExample); |         await learningObjectRepository.save(newerExample); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should return the newest version of the learning object when queried by only hruid and language', async () => { |     it('should return the newest version of the learning object when queried by only hruid and language', async () => { | ||||||
|         const result = await learningObjectRepository.findLatestByHruidAndLanguage( |         const result = await learningObjectRepository.findLatestByHruidAndLanguage(newerExample.hruid, newerExample.language); | ||||||
|             newerExample.hruid, newerExample.language |  | ||||||
|         ); |  | ||||||
|         expect(result).toBeInstanceOf(LearningObject); |         expect(result).toBeInstanceOf(LearningObject); | ||||||
|         expect(result?.version).toBe(10); |         expect(result?.version).toBe(10); | ||||||
|         expect(result?.title).toContain('(nieuw)'); |         expect(result?.title).toContain('(nieuw)'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should return null when queried by non-existing hruid or language', async () => { |     it('should return null when queried by non-existing hruid or language', async () => { | ||||||
|         const result = await learningObjectRepository.findLatestByHruidAndLanguage( |         const result = await learningObjectRepository.findLatestByHruidAndLanguage('something_that_does_not_exist', testLearningObject01.language); | ||||||
|             'something_that_does_not_exist', testLearningObject01.language |  | ||||||
|         ); |  | ||||||
|         expect(result).toBe(null); |         expect(result).toBe(null); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { getLearningObjectRepository } from '../../../src/data/repositories'; | ||||||
| import { setupTestApp } from '../../setup-tests'; | import { setupTestApp } from '../../setup-tests'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {testLearningObject01} from "../../test_assets/content/learning-objects.testdata"; | import { testLearningObject01 } from '../../test_assets/content/learning-objects.testdata'; | ||||||
| 
 | 
 | ||||||
| describe('LearningObjectRepository', () => { | describe('LearningObjectRepository', () => { | ||||||
|     let learningObjectRepository: LearningObjectRepository; |     let learningObjectRepository: LearningObjectRepository; | ||||||
|  |  | ||||||
|  | @ -3,14 +3,10 @@ import { setupTestApp } from '../../setup-tests.js'; | ||||||
| import { getLearningPathRepository } from '../../../src/data/repositories.js'; | import { getLearningPathRepository } from '../../../src/data/repositories.js'; | ||||||
| import { LearningPathRepository } from '../../../src/data/content/learning-path-repository.js'; | import { LearningPathRepository } from '../../../src/data/content/learning-path-repository.js'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; | ||||||
| import { | import { expectToBeCorrectEntity, expectToHaveFoundNothing, expectToHaveFoundPrecisely } from '../../test-utils/expectations.js'; | ||||||
|     expectToBeCorrectEntity, |  | ||||||
|     expectToHaveFoundNothing, |  | ||||||
|     expectToHaveFoundPrecisely |  | ||||||
| } from '../../test-utils/expectations.js'; |  | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {testLearningPath01} from "../../test_assets/content/learning-paths.testdata"; | import { testLearningPath01 } from '../../test_assets/content/learning-paths.testdata'; | ||||||
| import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service"; | import { mapToLearningPath } from '../../../src/services/learning-paths/learning-path-service'; | ||||||
| 
 | 
 | ||||||
| describe('LearningPathRepository', () => { | describe('LearningPathRepository', () => { | ||||||
|     let learningPathRepo: LearningPathRepository; |     let learningPathRepo: LearningPathRepository; | ||||||
|  | @ -24,10 +20,7 @@ describe('LearningPathRepository', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should return a learning path when it is queried by hruid and language', async () => { |     it('should return a learning path when it is queried by hruid and language', async () => { | ||||||
|         const result = await learningPathRepo.findByHruidAndLanguage( |         const result = await learningPathRepo.findByHruidAndLanguage(testLearningPath01.hruid, testLearningPath01.language as Language); | ||||||
|             testLearningPath01.hruid, |  | ||||||
|             testLearningPath01.language as Language |  | ||||||
|         ); |  | ||||||
|         expect(result).toBeInstanceOf(LearningPath); |         expect(result).toBeInstanceOf(LearningPath); | ||||||
|         expectToBeCorrectEntity(result!, examplePath); |         expectToBeCorrectEntity(result!, examplePath); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { getLearningPathRepository } from '../../../src/data/repositories'; | ||||||
| import { LearningPathRepository } from '../../../src/data/content/learning-path-repository'; | import { LearningPathRepository } from '../../../src/data/content/learning-path-repository'; | ||||||
| import { setupTestApp } from '../../setup-tests'; | import { setupTestApp } from '../../setup-tests'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {testLearningPath01} from "../../test_assets/content/learning-paths.testdata"; | import { testLearningPath01 } from '../../test_assets/content/learning-paths.testdata'; | ||||||
| 
 | 
 | ||||||
| describe('LearningPathRepository', () => { | describe('LearningPathRepository', () => { | ||||||
|     let learningPathRepository: LearningPathRepository; |     let learningPathRepository: LearningPathRepository; | ||||||
|  | @ -20,9 +20,7 @@ describe('LearningPathRepository', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should return requested learning path', async () => { |     it('should return requested learning path', async () => { | ||||||
|         const learningPath = await learningPathRepository.findByHruidAndLanguage( |         const learningPath = await learningPathRepository.findByHruidAndLanguage(testLearningPath01.hruid, testLearningPath01.language as Language); | ||||||
|             testLearningPath01.hruid, testLearningPath01.language as Language |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         expect(learningPath).toBeTruthy(); |         expect(learningPath).toBeTruthy(); | ||||||
|         expect(learningPath?.title).toBe(testLearningPath01.title); |         expect(learningPath?.title).toBe(testLearningPath01.title); | ||||||
|  |  | ||||||
|  | @ -4,18 +4,12 @@ import { LearningObject } from '../../../src/entities/content/learning-object.en | ||||||
| import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; | import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; | ||||||
| import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; | import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { | import { FilteredLearningObject, LearningObjectNode, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|     FilteredLearningObject, | import { testPartiallyDatabaseAndPartiallyDwengoApiLearningPath } from '../../test_assets/content/learning-paths.testdata'; | ||||||
|     LearningObjectNode, | import { testLearningObjectPnNotebooks } from '../../test_assets/content/learning-objects.testdata'; | ||||||
|     LearningPathIdentifier |  | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; |  | ||||||
| import { |  | ||||||
|     testPartiallyDatabaseAndPartiallyDwengoApiLearningPath |  | ||||||
| } from "../../test_assets/content/learning-paths.testdata"; |  | ||||||
| import {testLearningObjectPnNotebooks} from "../../test_assets/content/learning-objects.testdata"; |  | ||||||
| import { LearningPath } from '@dwengo-1/common/dist/interfaces/learning-content'; | import { LearningPath } from '@dwengo-1/common/dist/interfaces/learning-content'; | ||||||
| import {RequiredEntityData} from "@mikro-orm/core"; | import { RequiredEntityData } from '@mikro-orm/core'; | ||||||
| import {getHtmlRenderingForTestLearningObject} from "../../test-utils/get-html-rendering"; | import { getHtmlRenderingForTestLearningObject } from '../../test-utils/get-html-rendering'; | ||||||
| 
 | 
 | ||||||
| const EXPECTED_TITLE_FROM_DWENGO_LEARNING_OBJECT = 'Notebook opslaan'; | const EXPECTED_TITLE_FROM_DWENGO_LEARNING_OBJECT = 'Notebook opslaan'; | ||||||
| 
 | 
 | ||||||
|  | @ -31,7 +25,7 @@ describe('DatabaseLearningObjectProvider', () => { | ||||||
| 
 | 
 | ||||||
|         exampleLearningPathId = { |         exampleLearningPathId = { | ||||||
|             hruid: exampleLearningPath.hruid, |             hruid: exampleLearningPath.hruid, | ||||||
|             language: exampleLearningPath.language as Language |             language: exampleLearningPath.language as Language, | ||||||
|         }; |         }; | ||||||
|     }); |     }); | ||||||
|     describe('getLearningObjectById', () => { |     describe('getLearningObjectById', () => { | ||||||
|  | @ -75,9 +69,7 @@ describe('DatabaseLearningObjectProvider', () => { | ||||||
|     describe('getLearningObjectIdsFromPath', () => { |     describe('getLearningObjectIdsFromPath', () => { | ||||||
|         it('should return all learning object IDs from a path', async () => { |         it('should return all learning object IDs from a path', async () => { | ||||||
|             const result = await databaseLearningObjectProvider.getLearningObjectIdsFromPath(exampleLearningPathId); |             const result = await databaseLearningObjectProvider.getLearningObjectIdsFromPath(exampleLearningPathId); | ||||||
|             expect(new Set(result)).toEqual( |             expect(new Set(result)).toEqual(new Set(exampleLearningPath.nodes.map((it: LearningObjectNode) => it.learningobject_hruid))); | ||||||
|                 new Set(exampleLearningPath.nodes.map((it: LearningObjectNode) => it.learningobject_hruid)) |  | ||||||
|             ); |  | ||||||
|         }); |         }); | ||||||
|         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { |         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { | ||||||
|             await expect( |             await expect( | ||||||
|  |  | ||||||
|  | @ -3,18 +3,12 @@ import { setupTestApp } from '../../setup-tests'; | ||||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||||
| import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; | import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; | ||||||
| import { envVars, getEnvVar } from '../../../src/util/envVars'; | import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||||
| import { | import { LearningObjectIdentifierDTO, LearningPath as LearningPathDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|     LearningObjectIdentifierDTO, |  | ||||||
|     LearningPath as LearningPathDTO, |  | ||||||
|     LearningPathIdentifier |  | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; |  | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {testLearningObjectPnNotebooks} from "../../test_assets/content/learning-objects.testdata"; | import { testLearningObjectPnNotebooks } from '../../test_assets/content/learning-objects.testdata'; | ||||||
| import { | import { testPartiallyDatabaseAndPartiallyDwengoApiLearningPath } from '../../test_assets/content/learning-paths.testdata'; | ||||||
|     testPartiallyDatabaseAndPartiallyDwengoApiLearningPath | import { RequiredEntityData } from '@mikro-orm/core'; | ||||||
| } from "../../test_assets/content/learning-paths.testdata"; | import { getHtmlRenderingForTestLearningObject } from '../../test-utils/get-html-rendering'; | ||||||
| import {RequiredEntityData} from "@mikro-orm/core"; |  | ||||||
| import {getHtmlRenderingForTestLearningObject} from "../../test-utils/get-html-rendering"; |  | ||||||
| 
 | 
 | ||||||
| const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; | const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; | ||||||
| const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifierDTO = { | const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifierDTO = { | ||||||
|  | @ -41,8 +35,8 @@ describe('LearningObjectService', () => { | ||||||
| 
 | 
 | ||||||
|         exampleLearningPathId = { |         exampleLearningPathId = { | ||||||
|             hruid: exampleLearningPath.hruid, |             hruid: exampleLearningPath.hruid, | ||||||
|             language: exampleLearningPath.language as Language |             language: exampleLearningPath.language as Language, | ||||||
|         } |         }; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('getLearningObjectById', () => { |     describe('getLearningObjectById', () => { | ||||||
|  | @ -99,9 +93,7 @@ describe('LearningObjectService', () => { | ||||||
|     describe('getLearningObjectsFromPath', () => { |     describe('getLearningObjectsFromPath', () => { | ||||||
|         it('returns all learning objects when a learning path in the database is queried', async () => { |         it('returns all learning objects when a learning path in the database is queried', async () => { | ||||||
|             const result = await learningObjectService.getLearningObjectsFromPath(exampleLearningPathId); |             const result = await learningObjectService.getLearningObjectsFromPath(exampleLearningPathId); | ||||||
|             expect(result.map(it=> it.key)).toEqual( |             expect(result.map((it) => it.key)).toEqual(exampleLearningPath.nodes.map((it) => it.learningobject_hruid)); | ||||||
|                 exampleLearningPath.nodes.map(it => it.learningobject_hruid) |  | ||||||
|             ); |  | ||||||
|         }); |         }); | ||||||
|         it('also returns all learning objects when a learning path from the Dwengo API is queried', async () => { |         it('also returns all learning objects when a learning path from the Dwengo API is queried', async () => { | ||||||
|             const result = await learningObjectService.getLearningObjectsFromPath(DWENGO_TEST_LEARNING_PATH_ID); |             const result = await learningObjectService.getLearningObjectsFromPath(DWENGO_TEST_LEARNING_PATH_ID); | ||||||
|  | @ -119,7 +111,7 @@ describe('LearningObjectService', () => { | ||||||
|     describe('getLearningObjectIdsFromPath', () => { |     describe('getLearningObjectIdsFromPath', () => { | ||||||
|         it('returns all learning objects when a learning path in the database is queried', async () => { |         it('returns all learning objects when a learning path in the database is queried', async () => { | ||||||
|             const result = await learningObjectService.getLearningObjectIdsFromPath(exampleLearningPathId); |             const result = await learningObjectService.getLearningObjectIdsFromPath(exampleLearningPathId); | ||||||
|             expect(result).toEqual(exampleLearningPath.nodes.map(it => it.learningobject_hruid)); |             expect(result).toEqual(exampleLearningPath.nodes.map((it) => it.learningobject_hruid)); | ||||||
|         }); |         }); | ||||||
|         it('also returns all learning object hruids when a learning path from the Dwengo API is queried', async () => { |         it('also returns all learning object hruids when a learning path from the Dwengo API is queried', async () => { | ||||||
|             const result = await learningObjectService.getLearningObjectIdsFromPath(DWENGO_TEST_LEARNING_PATH_ID); |             const result = await learningObjectService.getLearningObjectIdsFromPath(DWENGO_TEST_LEARNING_PATH_ID); | ||||||
|  |  | ||||||
|  | @ -1,12 +1,13 @@ | ||||||
| import {beforeAll, describe, expect, it} from 'vitest'; | import { beforeAll, describe, expect, it } from 'vitest'; | ||||||
| import processingService from '../../../../src/services/learning-objects/processing/processing-service'; | import processingService from '../../../../src/services/learning-objects/processing/processing-service'; | ||||||
| import { | import { | ||||||
|     testLearningObjectEssayQuestion, |     testLearningObjectEssayQuestion, | ||||||
|     testLearningObjectMultipleChoice, testLearningObjectPnNotebooks |     testLearningObjectMultipleChoice, | ||||||
| } from "../../../test_assets/content/learning-objects.testdata"; |     testLearningObjectPnNotebooks, | ||||||
| import {getHtmlRenderingForTestLearningObject} from "../../../test-utils/get-html-rendering"; | } from '../../../test_assets/content/learning-objects.testdata'; | ||||||
| import {getLearningObjectRepository} from "../../../../src/data/repositories"; | import { getHtmlRenderingForTestLearningObject } from '../../../test-utils/get-html-rendering'; | ||||||
| import {setupTestApp} from "../../../setup-tests"; | import { getLearningObjectRepository } from '../../../../src/data/repositories'; | ||||||
|  | import { setupTestApp } from '../../../setup-tests'; | ||||||
| 
 | 
 | ||||||
| describe('ProcessingService', () => { | describe('ProcessingService', () => { | ||||||
|     beforeAll(async () => { |     beforeAll(async () => { | ||||||
|  | @ -17,24 +18,18 @@ describe('ProcessingService', () => { | ||||||
|         const markdownLearningObject = getLearningObjectRepository().create(testLearningObjectPnNotebooks); |         const markdownLearningObject = getLearningObjectRepository().create(testLearningObjectPnNotebooks); | ||||||
|         const result = await processingService.render(markdownLearningObject); |         const result = await processingService.render(markdownLearningObject); | ||||||
|         // Set newlines so your tests are platform-independent.
 |         // Set newlines so your tests are platform-independent.
 | ||||||
|         expect(result).toEqual( |         expect(result).toEqual(getHtmlRenderingForTestLearningObject(markdownLearningObject).replace(/\r\n/g, '\n')); | ||||||
|             getHtmlRenderingForTestLearningObject(markdownLearningObject).replace(/\r\n/g, '\n') |  | ||||||
|         ); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('renders a multiple choice question correctly', async () => { |     it('renders a multiple choice question correctly', async () => { | ||||||
|         const testLearningObject = getLearningObjectRepository().create(testLearningObjectMultipleChoice); |         const testLearningObject = getLearningObjectRepository().create(testLearningObjectMultipleChoice); | ||||||
|         const result = await processingService.render(testLearningObject); |         const result = await processingService.render(testLearningObject); | ||||||
|         expect(result).toEqual( |         expect(result).toEqual(getHtmlRenderingForTestLearningObject(testLearningObjectMultipleChoice).replace(/\r\n/g, '\n')); | ||||||
|             getHtmlRenderingForTestLearningObject(testLearningObjectMultipleChoice).replace(/\r\n/g, '\n') |  | ||||||
|         ); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('renders an essay question correctly', async () => { |     it('renders an essay question correctly', async () => { | ||||||
|         const essayLearningObject = getLearningObjectRepository().create(testLearningObjectEssayQuestion); |         const essayLearningObject = getLearningObjectRepository().create(testLearningObjectEssayQuestion); | ||||||
|         const result = await processingService.render(essayLearningObject); |         const result = await processingService.render(essayLearningObject); | ||||||
|         expect(result).toEqual( |         expect(result).toEqual(getHtmlRenderingForTestLearningObject(essayLearningObject).replace(/\r\n/g, '\n')); | ||||||
|             getHtmlRenderingForTestLearningObject(essayLearningObject).replace(/\r\n/g, '\n') |  | ||||||
|         ); |  | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -8,26 +8,20 @@ import databaseLearningPathProvider from '../../../src/services/learning-paths/d | ||||||
| import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js'; | import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
|  | import { LearningObjectNode, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { | import { | ||||||
|     LearningObjectNode, |     testLearningObject01, | ||||||
|     LearningPathResponse |     testLearningObjectEssayQuestion, | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; |     testLearningObjectMultipleChoice, | ||||||
| import { | } from '../../test_assets/content/learning-objects.testdata'; | ||||||
|     testLearningObject01, testLearningObjectEssayQuestion, | import { testLearningPathWithConditions } from '../../test_assets/content/learning-paths.testdata'; | ||||||
|     testLearningObjectMultipleChoice | import { mapToLearningPath } from '../../../src/services/learning-paths/learning-path-service'; | ||||||
| } from "../../test_assets/content/learning-objects.testdata"; | import { getTestGroup01, getTestGroup02 } from '../../test_assets/assignments/groups.testdata'; | ||||||
| import {testLearningPathWithConditions} from "../../test_assets/content/learning-paths.testdata"; |  | ||||||
| import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service"; |  | ||||||
| import {getTestGroup01, getTestGroup02} from "../../test_assets/assignments/groups.testdata"; |  | ||||||
| import { Group } from '../../../src/entities/assignments/group.entity.js'; | import { Group } from '../../../src/entities/assignments/group.entity.js'; | ||||||
| import {RequiredEntityData} from "@mikro-orm/core"; | import { RequiredEntityData } from '@mikro-orm/core'; | ||||||
| 
 | 
 | ||||||
| function expectBranchingObjectNode( | function expectBranchingObjectNode(result: LearningPathResponse): LearningObjectNode { | ||||||
|     result: LearningPathResponse |     const branchingObjectMatches = result.data![0].nodes.filter((it) => it.learningobject_hruid === testLearningObjectMultipleChoice.hruid); | ||||||
| ): LearningObjectNode { |  | ||||||
|     const branchingObjectMatches = result.data![0].nodes.filter( |  | ||||||
|         (it) => it.learningobject_hruid === testLearningObjectMultipleChoice.hruid |  | ||||||
|     ); |  | ||||||
|     expect(branchingObjectMatches.length).toBe(1); |     expect(branchingObjectMatches.length).toBe(1); | ||||||
|     return branchingObjectMatches[0]; |     return branchingObjectMatches[0]; | ||||||
| } | } | ||||||
|  | @ -36,7 +30,7 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|     let testLearningPath: LearningPath; |     let testLearningPath: LearningPath; | ||||||
|     let branchingLearningObject: RequiredEntityData<LearningObject>; |     let branchingLearningObject: RequiredEntityData<LearningObject>; | ||||||
|     let extraExerciseLearningObject: RequiredEntityData<LearningObject>; |     let extraExerciseLearningObject: RequiredEntityData<LearningObject>; | ||||||
|     let finalLearningObject:  RequiredEntityData<LearningObject>; |     let finalLearningObject: RequiredEntityData<LearningObject>; | ||||||
|     let groupA: Group; |     let groupA: Group; | ||||||
|     let groupB: Group; |     let groupB: Group; | ||||||
| 
 | 
 | ||||||
|  | @ -55,10 +49,10 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|             learningObjectHruid: branchingLearningObject.hruid, |             learningObjectHruid: branchingLearningObject.hruid, | ||||||
|             learningObjectLanguage: branchingLearningObject.language, |             learningObjectLanguage: branchingLearningObject.language, | ||||||
|             learningObjectVersion: branchingLearningObject.version, |             learningObjectVersion: branchingLearningObject.version, | ||||||
|             content: "[0]", |             content: '[0]', | ||||||
|             onBehalfOf: groupA, |             onBehalfOf: groupA, | ||||||
|             submissionTime: new Date(), |             submissionTime: new Date(), | ||||||
|             submitter: groupA.members[0] |             submitter: groupA.members[0], | ||||||
|         }); |         }); | ||||||
|         await submissionRepo.save(submissionA); |         await submissionRepo.save(submissionA); | ||||||
| 
 | 
 | ||||||
|  | @ -66,21 +60,17 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|             learningObjectHruid: branchingLearningObject.hruid, |             learningObjectHruid: branchingLearningObject.hruid, | ||||||
|             learningObjectLanguage: branchingLearningObject.language, |             learningObjectLanguage: branchingLearningObject.language, | ||||||
|             learningObjectVersion: branchingLearningObject.version, |             learningObjectVersion: branchingLearningObject.version, | ||||||
|             content: "[1]", |             content: '[1]', | ||||||
|             onBehalfOf: groupB, |             onBehalfOf: groupB, | ||||||
|             submissionTime: new Date(), |             submissionTime: new Date(), | ||||||
|             submitter: groupB.members[0] |             submitter: groupB.members[0], | ||||||
|         }); |         }); | ||||||
|         await submissionRepo.save(submissionB); |         await submissionRepo.save(submissionB); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('fetchLearningPaths', () => { |     describe('fetchLearningPaths', () => { | ||||||
|         it('returns the learning path correctly', async () => { |         it('returns the learning path correctly', async () => { | ||||||
|             const result = await databaseLearningPathProvider.fetchLearningPaths( |             const result = await databaseLearningPathProvider.fetchLearningPaths([testLearningPath.hruid], testLearningPath.language, 'the source'); | ||||||
|                 [testLearningPath.hruid], |  | ||||||
|                 testLearningPath.language, |  | ||||||
|                 'the source' |  | ||||||
|             ); |  | ||||||
|             expect(result.success).toBe(true); |             expect(result.success).toBe(true); | ||||||
|             expect(result.data?.length).toBe(1); |             expect(result.data?.length).toBe(1); | ||||||
| 
 | 
 | ||||||
|  | @ -100,24 +90,11 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|             // There should be exactly one branching object
 |             // There should be exactly one branching object
 | ||||||
|             let branchingObject = expectBranchingObjectNode(result); |             let branchingObject = expectBranchingObjectNode(result); | ||||||
| 
 | 
 | ||||||
|             expect( |             expect(branchingObject.transitions.filter((it) => it.next.hruid === finalLearningObject.hruid).length).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
 | ||||||
|                 branchingObject.transitions.filter( |             expect(branchingObject.transitions.filter((it) => it.next.hruid === extraExerciseLearningObject.hruid).length).toBe(1); // There should however be a path to the extra exercise object.
 | ||||||
|                     it => it.next.hruid === finalLearningObject.hruid |  | ||||||
|                 ).length |  | ||||||
|             ).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
 |  | ||||||
|             expect( |  | ||||||
|                 branchingObject.transitions.filter( |  | ||||||
|                     it => it.next.hruid === extraExerciseLearningObject.hruid |  | ||||||
|                 ).length |  | ||||||
|             ).toBe(1); // There should however be a path to the extra exercise object.
 |  | ||||||
| 
 | 
 | ||||||
|             // For student B:
 |             // For student B:
 | ||||||
|             result = await databaseLearningPathProvider.fetchLearningPaths( |             result = await databaseLearningPathProvider.fetchLearningPaths([testLearningPath.hruid], testLearningPath.language, 'the source', groupB); | ||||||
|                 [testLearningPath.hruid], |  | ||||||
|                 testLearningPath.language, |  | ||||||
|                 'the source', |  | ||||||
|                 groupB |  | ||||||
|             ); |  | ||||||
|             expect(result.success).toBeTruthy(); |             expect(result.success).toBeTruthy(); | ||||||
|             expect(result.data?.length).toBe(1); |             expect(result.data?.length).toBe(1); | ||||||
| 
 | 
 | ||||||
|  | @ -125,16 +102,8 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|             branchingObject = expectBranchingObjectNode(result); |             branchingObject = expectBranchingObjectNode(result); | ||||||
| 
 | 
 | ||||||
|             // However, now the student picks the other option.
 |             // However, now the student picks the other option.
 | ||||||
|             expect( |             expect(branchingObject.transitions.filter((it) => it.next.hruid === finalLearningObject.hruid).length).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
 | ||||||
|                 branchingObject.transitions.filter( |             expect(branchingObject.transitions.filter((it) => it.next.hruid === extraExerciseLearningObject.hruid).length).toBe(0); // There should not be a path anymore to the extra exercise object.
 | ||||||
|                     (it) => it.next.hruid === finalLearningObject.hruid |  | ||||||
|                 ).length |  | ||||||
|             ).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
 |  | ||||||
|             expect( |  | ||||||
|                 branchingObject.transitions.filter( |  | ||||||
|                     (it) => it.next.hruid === extraExerciseLearningObject.hruid |  | ||||||
|                 ).length |  | ||||||
|             ).toBe(0); // There should not be a path anymore to the extra exercise object.
 |  | ||||||
|         }); |         }); | ||||||
|         it('returns a non-successful response if a non-existing learning path is queried', async () => { |         it('returns a non-successful response if a non-existing learning path is queried', async () => { | ||||||
|             const result = await databaseLearningPathProvider.fetchLearningPaths( |             const result = await databaseLearningPathProvider.fetchLearningPaths( | ||||||
|  | @ -149,10 +118,7 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
| 
 | 
 | ||||||
|     describe('searchLearningPaths', () => { |     describe('searchLearningPaths', () => { | ||||||
|         it('returns the correct learning path when queried with a substring of its title', async () => { |         it('returns the correct learning path when queried with a substring of its title', async () => { | ||||||
|             const result = await databaseLearningPathProvider.searchLearningPaths( |             const result = await databaseLearningPathProvider.searchLearningPaths(testLearningPath.title.substring(2, 6), testLearningPath.language); | ||||||
|                 testLearningPath.title.substring(2, 6), |  | ||||||
|                 testLearningPath.language |  | ||||||
|             ); |  | ||||||
|             expect(result.length).toBe(1); |             expect(result.length).toBe(1); | ||||||
|             expect(result[0].title).toBe(testLearningPath.title); |             expect(result[0].title).toBe(testLearningPath.title); | ||||||
|             expect(result[0].description).toBe(testLearningPath.description); |             expect(result[0].description).toBe(testLearningPath.description); | ||||||
|  |  | ||||||
|  | @ -2,10 +2,8 @@ import { beforeAll, describe, expect, it } from 'vitest'; | ||||||
| import { setupTestApp } from '../../setup-tests'; | import { setupTestApp } from '../../setup-tests'; | ||||||
| import learningPathService from '../../../src/services/learning-paths/learning-path-service'; | import learningPathService from '../../../src/services/learning-paths/learning-path-service'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { | import { testPartiallyDatabaseAndPartiallyDwengoApiLearningPath } from '../../test_assets/content/learning-paths.testdata'; | ||||||
|     testPartiallyDatabaseAndPartiallyDwengoApiLearningPath | import { LearningPath as LearningPathDTO } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| } from "../../test_assets/content/learning-paths.testdata"; |  | ||||||
| import {LearningPath as LearningPathDTO} from "@dwengo-1/common/interfaces/learning-content"; |  | ||||||
| 
 | 
 | ||||||
| const TEST_DWENGO_LEARNING_PATH_HRUID = 'pn_werking'; | const TEST_DWENGO_LEARNING_PATH_HRUID = 'pn_werking'; | ||||||
| const TEST_DWENGO_LEARNING_PATH_TITLE = 'Werken met notebooks'; | const TEST_DWENGO_LEARNING_PATH_TITLE = 'Werken met notebooks'; | ||||||
|  | @ -16,7 +14,7 @@ describe('LearningPathService', () => { | ||||||
|     let testLearningPath: LearningPathDTO; |     let testLearningPath: LearningPathDTO; | ||||||
|     beforeAll(async () => { |     beforeAll(async () => { | ||||||
|         await setupTestApp(); |         await setupTestApp(); | ||||||
|         testLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath |         testLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath; | ||||||
|     }); |     }); | ||||||
|     describe('fetchLearningPaths', () => { |     describe('fetchLearningPaths', () => { | ||||||
|         it('should return learning paths both from the database and from the Dwengo API', async () => { |         it('should return learning paths both from the database and from the Dwengo API', async () => { | ||||||
|  |  | ||||||
|  | @ -13,8 +13,8 @@ import { makeTestAttachments } from './test_assets/content/attachments.testdata. | ||||||
| import { makeTestQuestions } from './test_assets/questions/questions.testdata.js'; | import { makeTestQuestions } from './test_assets/questions/questions.testdata.js'; | ||||||
| import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | ||||||
| import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | ||||||
| import {Collection} from "@mikro-orm/core"; | import { Collection } from '@mikro-orm/core'; | ||||||
| import {Group} from "../src/entities/assignments/group.entity"; | import { Group } from '../src/entities/assignments/group.entity'; | ||||||
| 
 | 
 | ||||||
| export async function setupTestApp(): Promise<void> { | export async function setupTestApp(): Promise<void> { | ||||||
|     dotenv.config({ path: '.env.test' }); |     dotenv.config({ path: '.env.test' }); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { AssertionError } from 'node:assert'; | ||||||
| import { LearningObject } from '../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../src/entities/content/learning-object.entity'; | ||||||
| import { expect } from 'vitest'; | import { expect } from 'vitest'; | ||||||
| import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content'; | import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import {RequiredEntityData} from "@mikro-orm/core"; | import { RequiredEntityData } from '@mikro-orm/core'; | ||||||
| 
 | 
 | ||||||
| // Ignored properties because they belang for example to the class, not to the entity itself.
 | // Ignored properties because they belang for example to the class, not to the entity itself.
 | ||||||
| const IGNORE_PROPERTIES = ['parent']; | const IGNORE_PROPERTIES = ['parent']; | ||||||
|  | @ -13,11 +13,7 @@ const IGNORE_PROPERTIES = ['parent']; | ||||||
|  * @param expected The (previously added) entity we would expect to retrieve |  * @param expected The (previously added) entity we would expect to retrieve | ||||||
|  * @param propertyPrefix Prefix to append to property in error messages. |  * @param propertyPrefix Prefix to append to property in error messages. | ||||||
|  */ |  */ | ||||||
| export function expectToBeCorrectEntity<T extends object>( | export function expectToBeCorrectEntity<T extends object>(actual: T, expected: T, propertyPrefix = ''): void { | ||||||
|     actual: T, |  | ||||||
|     expected: T, |  | ||||||
|     propertyPrefix = "" |  | ||||||
| ): void { |  | ||||||
|     for (const property in expected) { |     for (const property in expected) { | ||||||
|         if (Object.prototype.hasOwnProperty.call(expected, property)) { |         if (Object.prototype.hasOwnProperty.call(expected, property)) { | ||||||
|             const prefixedProperty = propertyPrefix + property; |             const prefixedProperty = propertyPrefix + property; | ||||||
|  | @ -41,16 +37,16 @@ export function expectToBeCorrectEntity<T extends object>( | ||||||
|                     } |                     } | ||||||
|                 } else if (typeof expected[property] !== typeof actual[property]) { |                 } else if (typeof expected[property] !== typeof actual[property]) { | ||||||
|                     throw new AssertionError({ |                     throw new AssertionError({ | ||||||
|                         message: `${prefixedProperty} was expected to have type ${typeof expected[property]},` |                         message: | ||||||
|                             + `but had type ${typeof actual[property]}.`, |                             `${prefixedProperty} was expected to have type ${typeof expected[property]},` + | ||||||
|  |                             `but had type ${typeof actual[property]}.`, | ||||||
|                     }); |                     }); | ||||||
|                 } else if (typeof expected[property] === 'object') { |                 } else if (typeof expected[property] === 'object') { | ||||||
|                     expectToBeCorrectEntity(actual[property] as object, expected[property] as object, property); |                     expectToBeCorrectEntity(actual[property] as object, expected[property] as object, property); | ||||||
|                 } else { |                 } else { | ||||||
|                     if (expected[property] !== actual[property]) { |                     if (expected[property] !== actual[property]) { | ||||||
|                         throw new AssertionError({ |                         throw new AssertionError({ | ||||||
|                             message: `${prefixedProperty} was expected to be ${expected[property]}, ` |                             message: `${prefixedProperty} was expected to be ${expected[property]}, ` + `but was ${actual[property]}.`, | ||||||
|                                 + `but was ${actual[property]}.`, |  | ||||||
|                         }); |                         }); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -94,10 +90,7 @@ export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearni | ||||||
|  * @param learningPath The learning path returned by the retriever, service or endpoint |  * @param learningPath The learning path returned by the retriever, service or endpoint | ||||||
|  * @param expected The learning path that should have been returned. |  * @param expected The learning path that should have been returned. | ||||||
|  */ |  */ | ||||||
| export function expectToBeCorrectLearningPath( | export function expectToBeCorrectLearningPath(learningPath: LearningPath, expected: LearningPath): void { | ||||||
|     learningPath: LearningPath, |  | ||||||
|     expected: LearningPath |  | ||||||
| ): void { |  | ||||||
|     expect(learningPath.hruid).toEqual(expected.hruid); |     expect(learningPath.hruid).toEqual(expected.hruid); | ||||||
|     expect(learningPath.language).toEqual(expected.language); |     expect(learningPath.language).toEqual(expected.language); | ||||||
|     expect(learningPath.description).toEqual(expected.description); |     expect(learningPath.description).toEqual(expected.description); | ||||||
|  | @ -113,17 +106,18 @@ export function expectToBeCorrectLearningPath( | ||||||
|     expect(learningPath.image ?? null).toEqual(expected.image ?? null); |     expect(learningPath.image ?? null).toEqual(expected.image ?? null); | ||||||
| 
 | 
 | ||||||
|     for (const node of expected.nodes) { |     for (const node of expected.nodes) { | ||||||
|         const correspondingNode = learningPath.nodes.find(it => |         const correspondingNode = learningPath.nodes.find( | ||||||
|             node.learningobject_hruid === it.learningobject_hruid && node.language === it.language |             (it) => node.learningobject_hruid === it.learningobject_hruid && node.language === it.language && node.version === it.version | ||||||
|             && node.version === it.version |  | ||||||
|         ); |         ); | ||||||
|         expect(correspondingNode).toBeTruthy(); |         expect(correspondingNode).toBeTruthy(); | ||||||
|         expect(Boolean(correspondingNode!.start_node)).toEqual(Boolean(node.start_node)); |         expect(Boolean(correspondingNode!.start_node)).toEqual(Boolean(node.start_node)); | ||||||
| 
 | 
 | ||||||
|         for (const transition of node.transitions) { |         for (const transition of node.transitions) { | ||||||
|             const correspondingTransition = correspondingNode!.transitions.find(it => |             const correspondingTransition = correspondingNode!.transitions.find( | ||||||
|                 it.next.hruid === transition.next.hruid && it.next.language === transition.next.language |                 (it) => | ||||||
|                 && it.next.version === transition.next.version |                     it.next.hruid === transition.next.hruid && | ||||||
|  |                     it.next.language === transition.next.language && | ||||||
|  |                     it.next.version === transition.next.version | ||||||
|             ); |             ); | ||||||
|             expect(correspondingTransition).toBeTruthy(); |             expect(correspondingTransition).toBeTruthy(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,12 +1,10 @@ | ||||||
| import {RequiredEntityData} from "@mikro-orm/core"; | import { RequiredEntityData } from '@mikro-orm/core'; | ||||||
| import {loadTestAsset} from "./load-test-asset"; | import { loadTestAsset } from './load-test-asset'; | ||||||
| import {LearningObject} from "../../src/entities/content/learning-object.entity"; | import { LearningObject } from '../../src/entities/content/learning-object.entity'; | ||||||
| import {envVars, getEnvVar} from "../../src/util/envVars"; | import { envVars, getEnvVar } from '../../src/util/envVars'; | ||||||
| 
 | 
 | ||||||
| export function getHtmlRenderingForTestLearningObject(learningObject: RequiredEntityData<LearningObject>): string { | export function getHtmlRenderingForTestLearningObject(learningObject: RequiredEntityData<LearningObject>): string { | ||||||
|     const userPrefix = getEnvVar(envVars.UserContentPrefix); |     const userPrefix = getEnvVar(envVars.UserContentPrefix); | ||||||
|     const cleanedHruid = learningObject.hruid.startsWith(userPrefix) |     const cleanedHruid = learningObject.hruid.startsWith(userPrefix) ? learningObject.hruid.substring(userPrefix.length) : learningObject.hruid; | ||||||
|                                     ? learningObject.hruid.substring(userPrefix.length) |  | ||||||
|                                     : learningObject.hruid; |  | ||||||
|     return loadTestAsset(`/content/learning-object-resources/${cleanedHruid}/rendering.txt`).toString(); |     return loadTestAsset(`/content/learning-object-resources/${cleanedHruid}/rendering.txt`).toString(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import fs from 'fs'; | import fs from 'fs'; | ||||||
| import path from 'node:path'; | import path from 'node:path'; | ||||||
| import {fileURLToPath} from "node:url"; | import { fileURLToPath } from 'node:url'; | ||||||
| 
 | 
 | ||||||
| const fileName = fileURLToPath(import.meta.url); | const fileName = fileURLToPath(import.meta.url); | ||||||
| const dirName = path.dirname(fileName); | const dirName = path.dirname(fileName); | ||||||
|  |  | ||||||
|  | @ -2,8 +2,8 @@ import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||||
| import { Class } from '../../../src/entities/classes/class.entity'; | import { Class } from '../../../src/entities/classes/class.entity'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {testLearningPathWithConditions} from "../content/learning-paths.testdata"; | import { testLearningPathWithConditions } from '../content/learning-paths.testdata'; | ||||||
| import {getClassWithTestleerlingAndTestleerkracht} from "../classes/classes.testdata"; | import { getClassWithTestleerlingAndTestleerkracht } from '../classes/classes.testdata'; | ||||||
| 
 | 
 | ||||||
| export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] { | export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] { | ||||||
|     assignment01 = em.create(Assignment, { |     assignment01 = em.create(Assignment, { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import {EntityManager} from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Group } from '../../../src/entities/assignments/group.entity'; | import { Group } from '../../../src/entities/assignments/group.entity'; | ||||||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity'; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| import {getConditionalPathAssignment} from "./assignments.testdata"; | import { getConditionalPathAssignment } from './assignments.testdata'; | ||||||
| import {getTestleerling1} from "../users/students.testdata"; | import { getTestleerling1 } from '../users/students.testdata'; | ||||||
| 
 | 
 | ||||||
| export function makeTestGroups(em: EntityManager, students: Student[], assignments: Assignment[]): Group[] { | export function makeTestGroups(em: EntityManager, students: Student[], assignments: Assignment[]): Group[] { | ||||||
|     /* |     /* | ||||||
|  | @ -62,8 +62,8 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen | ||||||
|     group1ConditionalLearningPath = em.create(Group, { |     group1ConditionalLearningPath = em.create(Group, { | ||||||
|         assignment: getConditionalPathAssignment(), |         assignment: getConditionalPathAssignment(), | ||||||
|         groupNumber: 1, |         groupNumber: 1, | ||||||
|         members: [getTestleerling1()] |         members: [getTestleerling1()], | ||||||
|     }) |     }); | ||||||
| 
 | 
 | ||||||
|     return [group01, group02, group03, group04, group05, group1ConditionalLearningPath]; |     return [group01, group02, group03, group04, group05, group1ConditionalLearningPath]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,8 +2,8 @@ import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Class } from '../../../src/entities/classes/class.entity'; | import { Class } from '../../../src/entities/classes/class.entity'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity'; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| import { Teacher } from '../../../src/entities/users/teacher.entity'; | import { Teacher } from '../../../src/entities/users/teacher.entity'; | ||||||
| import {getTestleerkracht1} from "../users/teachers.testdata"; | import { getTestleerkracht1 } from '../users/teachers.testdata'; | ||||||
| import {getTestleerling1} from "../users/students.testdata"; | import { getTestleerling1 } from '../users/students.testdata'; | ||||||
| 
 | 
 | ||||||
| export function makeTestClasses(em: EntityManager, students: Student[], teachers: Teacher[]): Class[] { | export function makeTestClasses(em: EntityManager, students: Student[], teachers: Teacher[]): Class[] { | ||||||
|     const studentsClass01 = students.slice(0, 8); |     const studentsClass01 = students.slice(0, 8); | ||||||
|  | @ -47,10 +47,10 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     classWithTestleerlingAndTestleerkracht = em.create(Class, { |     classWithTestleerlingAndTestleerkracht = em.create(Class, { | ||||||
|         classId: "a75298b5-18aa-471d-8eeb-5d77eb989393", |         classId: 'a75298b5-18aa-471d-8eeb-5d77eb989393', | ||||||
|         displayName: 'Testklasse', |         displayName: 'Testklasse', | ||||||
|         teachers: [getTestleerkracht1()], |         teachers: [getTestleerkracht1()], | ||||||
|         students: [getTestleerling1()] |         students: [getTestleerling1()], | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return [class01, class02, class03, class04, classWithTestleerlingAndTestleerkracht]; |     return [class01, class02, class03, class04, classWithTestleerlingAndTestleerkracht]; | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| import {EntityManager, RequiredEntityData} from '@mikro-orm/core'; | import { EntityManager, RequiredEntityData } from '@mikro-orm/core'; | ||||||
| import {LearningObject} from '../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||||
| import {Language} from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {DwengoContentType} from '../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; | ||||||
| import {ReturnValue} from '../../../src/entities/content/return-value.entity'; | import { ReturnValue } from '../../../src/entities/content/return-value.entity'; | ||||||
| import {envVars, getEnvVar} from "../../../src/util/envVars"; | import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||||
| import {loadTestAsset} from "../../test-utils/load-test-asset"; | import { loadTestAsset } from '../../test-utils/load-test-asset'; | ||||||
| import {v4} from "uuid"; | import { v4 } from 'uuid'; | ||||||
| 
 | 
 | ||||||
| export function makeTestLearningObjects(em: EntityManager): LearningObject[] { | export function makeTestLearningObjects(em: EntityManager): LearningObject[] { | ||||||
|     const returnValue: ReturnValue = new ReturnValue(); |     const returnValue: ReturnValue = new ReturnValue(); | ||||||
|  | @ -19,13 +19,19 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] { | ||||||
|     const learningObject05 = em.create(LearningObject, testLearningObject05); |     const learningObject05 = em.create(LearningObject, testLearningObject05); | ||||||
| 
 | 
 | ||||||
|     const learningObjectMultipleChoice = em.create(LearningObject, testLearningObjectMultipleChoice); |     const learningObjectMultipleChoice = em.create(LearningObject, testLearningObjectMultipleChoice); | ||||||
|     const learningObjectEssayQuestion= em.create(LearningObject, testLearningObjectEssayQuestion); |     const learningObjectEssayQuestion = em.create(LearningObject, testLearningObjectEssayQuestion); | ||||||
| 
 | 
 | ||||||
|     const learningObjectPnNotebooks = em.create(LearningObject, testLearningObjectPnNotebooks); |     const learningObjectPnNotebooks = em.create(LearningObject, testLearningObjectPnNotebooks); | ||||||
| 
 | 
 | ||||||
|     return [ |     return [ | ||||||
|         learningObject01, learningObject02, learningObject03, learningObject04, learningObject05, |         learningObject01, | ||||||
|         learningObjectMultipleChoice, learningObjectEssayQuestion, learningObjectPnNotebooks |         learningObject02, | ||||||
|  |         learningObject03, | ||||||
|  |         learningObject04, | ||||||
|  |         learningObject05, | ||||||
|  |         learningObjectMultipleChoice, | ||||||
|  |         learningObjectEssayQuestion, | ||||||
|  |         learningObjectPnNotebooks, | ||||||
|     ]; |     ]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -164,14 +170,14 @@ export const testLearningObjectMultipleChoice: RequiredEntityData<LearningObject | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`, | ||||||
|     language: Language.English, |     language: Language.English, | ||||||
|     version: 1, |     version: 1, | ||||||
|     title: "Self-evaluation", |     title: 'Self-evaluation', | ||||||
|     description: "Time to evaluate how well you understand what you've learned so far.", |     description: "Time to evaluate how well you understand what you've learned so far.", | ||||||
|     keywords: ["test"], |     keywords: ['test'], | ||||||
|     teacherExclusive: false, |     teacherExclusive: false, | ||||||
|     skosConcepts: [], |     skosConcepts: [], | ||||||
|     educationalGoals: [], |     educationalGoals: [], | ||||||
|     copyright: "Groep 1 SEL-2 2025", |     copyright: 'Groep 1 SEL-2 2025', | ||||||
|     license: "CC0", |     license: 'CC0', | ||||||
|     difficulty: 1, |     difficulty: 1, | ||||||
|     estimatedTime: 1, |     estimatedTime: 1, | ||||||
|     attachments: [], |     attachments: [], | ||||||
|  | @ -183,21 +189,21 @@ export const testLearningObjectMultipleChoice: RequiredEntityData<LearningObject | ||||||
|     returnValue: { |     returnValue: { | ||||||
|         callbackUrl: `%SUBMISSION%`, |         callbackUrl: `%SUBMISSION%`, | ||||||
|         callbackSchema: '["antwoord vraag 1"]', |         callbackSchema: '["antwoord vraag 1"]', | ||||||
|     } |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const testLearningObjectEssayQuestion: RequiredEntityData<LearningObject> = { | export const testLearningObjectEssayQuestion: RequiredEntityData<LearningObject> = { | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}test_essay_question`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}test_essay_question`, | ||||||
|     language: Language.English, |     language: Language.English, | ||||||
|     version: 1, |     version: 1, | ||||||
|     title: "Reflection", |     title: 'Reflection', | ||||||
|     description: "Reflect on your learning progress.", |     description: 'Reflect on your learning progress.', | ||||||
|     keywords: ["test"], |     keywords: ['test'], | ||||||
|     teacherExclusive: false, |     teacherExclusive: false, | ||||||
|     skosConcepts: [], |     skosConcepts: [], | ||||||
|     educationalGoals: [], |     educationalGoals: [], | ||||||
|     copyright: "Groep 1 SEL-2 2025", |     copyright: 'Groep 1 SEL-2 2025', | ||||||
|     license: "CC0", |     license: 'CC0', | ||||||
|     difficulty: 1, |     difficulty: 1, | ||||||
|     estimatedTime: 1, |     estimatedTime: 1, | ||||||
|     attachments: [], |     attachments: [], | ||||||
|  | @ -209,47 +215,47 @@ export const testLearningObjectEssayQuestion: RequiredEntityData<LearningObject> | ||||||
|     returnValue: { |     returnValue: { | ||||||
|         callbackUrl: `%SUBMISSION%`, |         callbackUrl: `%SUBMISSION%`, | ||||||
|         callbackSchema: '["antwoord vraag 1"]', |         callbackSchema: '["antwoord vraag 1"]', | ||||||
|     } |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const testLearningObjectPnNotebooks: RequiredEntityData<LearningObject> = { | export const testLearningObjectPnNotebooks: RequiredEntityData<LearningObject> = { | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`, | ||||||
|     version: 3, |     version: 3, | ||||||
|     language: Language.Dutch, |     language: Language.Dutch, | ||||||
|     title: "Werken met notebooks", |     title: 'Werken met notebooks', | ||||||
|     description: "Leren werken met notebooks", |     description: 'Leren werken met notebooks', | ||||||
|     keywords: ["Python", "KIKS", "Wiskunde", "STEM", "AI"], |     keywords: ['Python', 'KIKS', 'Wiskunde', 'STEM', 'AI'], | ||||||
|     targetAges: [14, 15, 16, 17, 18], |     targetAges: [14, 15, 16, 17, 18], | ||||||
|     admins: [], |     admins: [], | ||||||
|     copyright: "dwengo", |     copyright: 'dwengo', | ||||||
|     educationalGoals: [], |     educationalGoals: [], | ||||||
|     license: "dwengo", |     license: 'dwengo', | ||||||
|     contentType: DwengoContentType.TEXT_MARKDOWN, |     contentType: DwengoContentType.TEXT_MARKDOWN, | ||||||
|     difficulty: 3, |     difficulty: 3, | ||||||
|     estimatedTime: 10, |     estimatedTime: 10, | ||||||
|     uuid: "2adf9929-b424-4650-bf60-186f730d38ab", |     uuid: '2adf9929-b424-4650-bf60-186f730d38ab', | ||||||
|     teacherExclusive: false, |     teacherExclusive: false, | ||||||
|     skosConcepts: [ |     skosConcepts: [ | ||||||
|         "http://ilearn.ilabt.imec.be/vocab/curr1/s-vaktaal", |         'http://ilearn.ilabt.imec.be/vocab/curr1/s-vaktaal', | ||||||
|         "http://ilearn.ilabt.imec.be/vocab/curr1/s-digitale-media-en-toepassingen", |         'http://ilearn.ilabt.imec.be/vocab/curr1/s-digitale-media-en-toepassingen', | ||||||
|         "http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen", |         'http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen', | ||||||
|     ], |     ], | ||||||
|     attachments: [ |     attachments: [ | ||||||
|         { |         { | ||||||
|             name: "dwengo.png", |             name: 'dwengo.png', | ||||||
|             mimeType: "image/png", |             mimeType: 'image/png', | ||||||
|             content: loadTestAsset("/content/learning-object-resources/pn_werkingnotebooks/dwengo.png") |             content: loadTestAsset('/content/learning-object-resources/pn_werkingnotebooks/dwengo.png'), | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             name: "Knop.png", |             name: 'Knop.png', | ||||||
|             mimeType: "image/png", |             mimeType: 'image/png', | ||||||
|             content: loadTestAsset("/content/learning-object-resources/pn_werkingnotebooks/Knop.png") |             content: loadTestAsset('/content/learning-object-resources/pn_werkingnotebooks/Knop.png'), | ||||||
|         } |         }, | ||||||
|     ], |     ], | ||||||
|     available: false, |     available: false, | ||||||
|     content: loadTestAsset("/content/learning-object-resources/pn_werkingnotebooks/content.md"), |     content: loadTestAsset('/content/learning-object-resources/pn_werkingnotebooks/content.md'), | ||||||
|     returnValue: { |     returnValue: { | ||||||
|         callbackUrl: "%SUBMISSION%", |         callbackUrl: '%SUBMISSION%', | ||||||
|         callbackSchema: "[]" |         callbackSchema: '[]', | ||||||
|     } |     }, | ||||||
| } | }; | ||||||
|  |  | ||||||
|  | @ -1,40 +1,39 @@ | ||||||
| import {EntityManager} from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import {LearningPath} from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
| import {Language} from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service"; | import { mapToLearningPath } from '../../../src/services/learning-paths/learning-path-service'; | ||||||
| import {envVars, getEnvVar} from "../../../src/util/envVars"; | import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||||
| import {LearningPath as LearningPathDTO} from "@dwengo-1/common/interfaces/learning-content"; | import { LearningPath as LearningPathDTO } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { | import { | ||||||
|     testLearningObject01, testLearningObject02, testLearningObject03, testLearningObject04, testLearningObject05, |     testLearningObject01, | ||||||
|  |     testLearningObject02, | ||||||
|  |     testLearningObject03, | ||||||
|  |     testLearningObject04, | ||||||
|  |     testLearningObject05, | ||||||
|     testLearningObjectEssayQuestion, |     testLearningObjectEssayQuestion, | ||||||
|     testLearningObjectMultipleChoice, testLearningObjectPnNotebooks |     testLearningObjectMultipleChoice, | ||||||
| } from "./learning-objects.testdata"; |     testLearningObjectPnNotebooks, | ||||||
|  | } from './learning-objects.testdata'; | ||||||
| 
 | 
 | ||||||
| export function makeTestLearningPaths(_em: EntityManager): LearningPath[] { | export function makeTestLearningPaths(_em: EntityManager): LearningPath[] { | ||||||
|     const learningPath01 = mapToLearningPath(testLearningPath01, []); |     const learningPath01 = mapToLearningPath(testLearningPath01, []); | ||||||
|     const learningPath02 = mapToLearningPath(testLearningPath02, []); |     const learningPath02 = mapToLearningPath(testLearningPath02, []); | ||||||
| 
 | 
 | ||||||
|     const partiallyDatabasePartiallyDwengoApiLearningPath |     const partiallyDatabasePartiallyDwengoApiLearningPath = mapToLearningPath(testPartiallyDatabaseAndPartiallyDwengoApiLearningPath, []); | ||||||
|         = mapToLearningPath(testPartiallyDatabaseAndPartiallyDwengoApiLearningPath, []) |     const learningPathWithConditions = mapToLearningPath(testLearningPathWithConditions, []); | ||||||
|     const learningPathWithConditions = mapToLearningPath(testLearningPathWithConditions, []) |  | ||||||
| 
 | 
 | ||||||
|     return [ |     return [learningPath01, learningPath02, partiallyDatabasePartiallyDwengoApiLearningPath, learningPathWithConditions]; | ||||||
|         learningPath01, |  | ||||||
|         learningPath02, |  | ||||||
|         partiallyDatabasePartiallyDwengoApiLearningPath, |  | ||||||
|         learningPathWithConditions |  | ||||||
|     ]; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const nowString = new Date().toString(); | const nowString = new Date().toString(); | ||||||
| 
 | 
 | ||||||
| export const testLearningPath01: LearningPathDTO = { | export const testLearningPath01: LearningPathDTO = { | ||||||
|     keywords: "test", |     keywords: 'test', | ||||||
|     target_ages: [16, 17, 18], |     target_ages: [16, 17, 18], | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id01`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}id01`, | ||||||
|     language: Language.English, |     language: Language.English, | ||||||
|     title: "repertoire Tool", |     title: 'repertoire Tool', | ||||||
|     description: "all about Tool", |     description: 'all about Tool', | ||||||
|     nodes: [ |     nodes: [ | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObject01.hruid, |             learningobject_hruid: testLearningObject01.hruid, | ||||||
|  | @ -48,10 +47,10 @@ export const testLearningPath01: LearningPathDTO = { | ||||||
|                     next: { |                     next: { | ||||||
|                         hruid: testLearningObject02.hruid, |                         hruid: testLearningObject02.hruid, | ||||||
|                         language: testLearningObject02.language, |                         language: testLearningObject02.language, | ||||||
|                         version: testLearningObject02.version |                         version: testLearningObject02.version, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObject02.hruid, |             learningobject_hruid: testLearningObject02.hruid, | ||||||
|  | @ -59,18 +58,18 @@ export const testLearningPath01: LearningPathDTO = { | ||||||
|             version: testLearningObject02.version, |             version: testLearningObject02.version, | ||||||
|             created_at: nowString, |             created_at: nowString, | ||||||
|             updatedAt: nowString, |             updatedAt: nowString, | ||||||
|             transitions: [] |             transitions: [], | ||||||
|         } |         }, | ||||||
|     ] |     ], | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const testLearningPath02: LearningPathDTO = { | export const testLearningPath02: LearningPathDTO = { | ||||||
|     keywords: "test", |     keywords: 'test', | ||||||
|     target_ages: [16, 17, 18], |     target_ages: [16, 17, 18], | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}id02`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}id02`, | ||||||
|     language: Language.English, |     language: Language.English, | ||||||
|     title: "repertoire Dire Straits", |     title: 'repertoire Dire Straits', | ||||||
|     description: "all about Dire Straits", |     description: 'all about Dire Straits', | ||||||
|     nodes: [ |     nodes: [ | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObject03.hruid, |             learningobject_hruid: testLearningObject03.hruid, | ||||||
|  | @ -84,10 +83,10 @@ export const testLearningPath02: LearningPathDTO = { | ||||||
|                     next: { |                     next: { | ||||||
|                         hruid: testLearningObject04.hruid, |                         hruid: testLearningObject04.hruid, | ||||||
|                         language: testLearningObject04.language, |                         language: testLearningObject04.language, | ||||||
|                         version: testLearningObject04.version |                         version: testLearningObject04.version, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObject04.hruid, |             learningobject_hruid: testLearningObject04.hruid, | ||||||
|  | @ -100,10 +99,10 @@ export const testLearningPath02: LearningPathDTO = { | ||||||
|                     next: { |                     next: { | ||||||
|                         hruid: testLearningObject05.hruid, |                         hruid: testLearningObject05.hruid, | ||||||
|                         language: testLearningObject05.language, |                         language: testLearningObject05.language, | ||||||
|                         version: testLearningObject05.version |                         version: testLearningObject05.version, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObject05.hruid, |             learningobject_hruid: testLearningObject05.hruid, | ||||||
|  | @ -111,17 +110,17 @@ export const testLearningPath02: LearningPathDTO = { | ||||||
|             version: testLearningObject05.version, |             version: testLearningObject05.version, | ||||||
|             created_at: nowString, |             created_at: nowString, | ||||||
|             updatedAt: nowString, |             updatedAt: nowString, | ||||||
|             transitions: [] |             transitions: [], | ||||||
|         } |         }, | ||||||
|     ] |     ], | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const testPartiallyDatabaseAndPartiallyDwengoApiLearningPath: LearningPathDTO = { | export const testPartiallyDatabaseAndPartiallyDwengoApiLearningPath: LearningPathDTO = { | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werking`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werking`, | ||||||
|     title: "Werken met notebooks", |     title: 'Werken met notebooks', | ||||||
|     language: Language.Dutch, |     language: Language.Dutch, | ||||||
|     description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?', |     description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?', | ||||||
|     keywords: "Python KIKS Wiskunde STEM AI", |     keywords: 'Python KIKS Wiskunde STEM AI', | ||||||
|     target_ages: [14, 15, 16, 17, 18], |     target_ages: [14, 15, 16, 17, 18], | ||||||
|     nodes: [ |     nodes: [ | ||||||
|         { |         { | ||||||
|  | @ -135,15 +134,15 @@ export const testPartiallyDatabaseAndPartiallyDwengoApiLearningPath: LearningPat | ||||||
|                 { |                 { | ||||||
|                     default: true, |                     default: true, | ||||||
|                     next: { |                     next: { | ||||||
|                         hruid: "pn_werkingnotebooks2", |                         hruid: 'pn_werkingnotebooks2', | ||||||
|                         language: Language.Dutch, |                         language: Language.Dutch, | ||||||
|                         version: 3 |                         version: 3, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: "pn_werkingnotebooks2", |             learningobject_hruid: 'pn_werkingnotebooks2', | ||||||
|             language: Language.Dutch, |             language: Language.Dutch, | ||||||
|             version: 3, |             version: 3, | ||||||
|             created_at: nowString, |             created_at: nowString, | ||||||
|  | @ -152,30 +151,30 @@ export const testPartiallyDatabaseAndPartiallyDwengoApiLearningPath: LearningPat | ||||||
|                 { |                 { | ||||||
|                     default: true, |                     default: true, | ||||||
|                     next: { |                     next: { | ||||||
|                         hruid: "pn_werkingnotebooks3", |                         hruid: 'pn_werkingnotebooks3', | ||||||
|                         language: Language.Dutch, |                         language: Language.Dutch, | ||||||
|                         version: 3 |                         version: 3, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: "pn_werkingnotebooks3", |             learningobject_hruid: 'pn_werkingnotebooks3', | ||||||
|             language: Language.Dutch, |             language: Language.Dutch, | ||||||
|             version: 3, |             version: 3, | ||||||
|             created_at: nowString, |             created_at: nowString, | ||||||
|             updatedAt: nowString, |             updatedAt: nowString, | ||||||
|             transitions: [] |             transitions: [], | ||||||
|         } |         }, | ||||||
|     ] |     ], | ||||||
| } | }; | ||||||
| 
 | 
 | ||||||
| export const testLearningPathWithConditions: LearningPathDTO = { | export const testLearningPathWithConditions: LearningPathDTO = { | ||||||
|     hruid: `${getEnvVar(envVars.UserContentPrefix)}test_conditions`, |     hruid: `${getEnvVar(envVars.UserContentPrefix)}test_conditions`, | ||||||
|     language: Language.English, |     language: Language.English, | ||||||
|     title: 'Example learning path with conditional transitions', |     title: 'Example learning path with conditional transitions', | ||||||
|     description: 'This learning path was made for the purpose of testing conditional transitions', |     description: 'This learning path was made for the purpose of testing conditional transitions', | ||||||
|     keywords: "test", |     keywords: 'test', | ||||||
|     target_ages: [10, 11, 12, 13, 14, 15, 16, 17, 18], |     target_ages: [10, 11, 12, 13, 14, 15, 16, 17, 18], | ||||||
|     nodes: [ |     nodes: [ | ||||||
|         { |         { | ||||||
|  | @ -193,8 +192,8 @@ export const testLearningPathWithConditions: LearningPathDTO = { | ||||||
|                         //... we let the student do an extra exercise.
 |                         //... we let the student do an extra exercise.
 | ||||||
|                         hruid: testLearningObject01.hruid, |                         hruid: testLearningObject01.hruid, | ||||||
|                         language: testLearningObject01.language, |                         language: testLearningObject01.language, | ||||||
|                         version: testLearningObject01.version |                         version: testLearningObject01.version, | ||||||
|                     } |                     }, | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     // If the answer to the first question was the second one (I can follow along):
 |                     // If the answer to the first question was the second one (I can follow along):
 | ||||||
|  | @ -203,10 +202,10 @@ export const testLearningPathWithConditions: LearningPathDTO = { | ||||||
|                         //... we let the student right through to the final question.
 |                         //... we let the student right through to the final question.
 | ||||||
|                         hruid: testLearningObjectEssayQuestion.hruid, |                         hruid: testLearningObjectEssayQuestion.hruid, | ||||||
|                         language: testLearningObjectEssayQuestion.language, |                         language: testLearningObjectEssayQuestion.language, | ||||||
|                         version: testLearningObjectEssayQuestion.version |                         version: testLearningObjectEssayQuestion.version, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObject01.hruid, |             learningobject_hruid: testLearningObject01.hruid, | ||||||
|  | @ -220,10 +219,10 @@ export const testLearningPathWithConditions: LearningPathDTO = { | ||||||
|                     next: { |                     next: { | ||||||
|                         hruid: testLearningObjectEssayQuestion.hruid, |                         hruid: testLearningObjectEssayQuestion.hruid, | ||||||
|                         language: testLearningObjectEssayQuestion.language, |                         language: testLearningObjectEssayQuestion.language, | ||||||
|                         version: testLearningObjectEssayQuestion.version |                         version: testLearningObjectEssayQuestion.version, | ||||||
|                     } |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             learningobject_hruid: testLearningObjectEssayQuestion.hruid, |             learningobject_hruid: testLearningObjectEssayQuestion.hruid, | ||||||
|  | @ -231,7 +230,7 @@ export const testLearningPathWithConditions: LearningPathDTO = { | ||||||
|             version: testLearningObjectEssayQuestion.version, |             version: testLearningObjectEssayQuestion.version, | ||||||
|             created_at: nowString, |             created_at: nowString, | ||||||
|             updatedAt: nowString, |             updatedAt: nowString, | ||||||
|             transitions: [] |             transitions: [], | ||||||
|         } |         }, | ||||||
|     ] |     ], | ||||||
| } | }; | ||||||
|  |  | ||||||
|  | @ -24,5 +24,5 @@ export function makeTestStudents(em: EntityManager): Student[] { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getTestleerling1(): Student { | export function getTestleerling1(): Student { | ||||||
|     return testStudents.find(it => it.username === "testleerling1"); |     return testStudents.find((it) => it.username === 'testleerling1'); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -62,4 +62,3 @@ export function getTeacher04(): Teacher { | ||||||
| export function getTestleerkracht1(): Teacher { | export function getTestleerkracht1(): Teacher { | ||||||
|     return testleerkracht1; |     return testleerkracht1; | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -15,14 +15,14 @@ export class LearningPathController extends BaseController { | ||||||
|     async getBy( |     async getBy( | ||||||
|         hruid: string, |         hruid: string, | ||||||
|         language: Language, |         language: Language, | ||||||
|         forGroup?: { forGroup: number, assignmentNo: number, classId: string }, |         forGroup?: { forGroup: number; assignmentNo: number; classId: string }, | ||||||
|     ): Promise<LearningPath> { |     ): Promise<LearningPath> { | ||||||
|         const dtos = await this.get<LearningPathDTO[]>("/", { |         const dtos = await this.get<LearningPathDTO[]>("/", { | ||||||
|             hruid, |             hruid, | ||||||
|             language, |             language, | ||||||
|             forGroup: forGroup?.forGroup, |             forGroup: forGroup?.forGroup, | ||||||
|             assignmentNo: forGroup?.assignmentNo, |             assignmentNo: forGroup?.assignmentNo, | ||||||
|             classId: forGroup?.classId |             classId: forGroup?.classId, | ||||||
|         }); |         }); | ||||||
|         return LearningPath.fromDTO(single(dtos)); |         return LearningPath.fromDTO(single(dtos)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { BaseController } from "./base-controller"; | import { BaseController } from "./base-controller"; | ||||||
| import type { SubmissionDTO, SubmissionDTOId } from "@dwengo-1/common/interfaces/submission"; | import type { SubmissionDTO, SubmissionDTOId } from "@dwengo-1/common/interfaces/submission"; | ||||||
| import type {Language} from "@dwengo-1/common/util/language"; | import type { Language } from "@dwengo-1/common/util/language"; | ||||||
| 
 | 
 | ||||||
| export interface SubmissionsResponse { | export interface SubmissionsResponse { | ||||||
|     submissions: SubmissionDTO[] | SubmissionDTOId[]; |     submissions: SubmissionDTO[] | SubmissionDTOId[]; | ||||||
|  | @ -11,18 +11,19 @@ export interface SubmissionResponse { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class SubmissionController extends BaseController { | export class SubmissionController extends BaseController { | ||||||
| 
 |  | ||||||
|     constructor(hruid: string) { |     constructor(hruid: string) { | ||||||
|         super(`learningObject/${hruid}/submissions`); |         super(`learningObject/${hruid}/submissions`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getAll( |     async getAll( | ||||||
|         language: Language, version: number, classId: string, assignmentId: number, groupId?: number, full = true |         language: Language, | ||||||
|  |         version: number, | ||||||
|  |         classId: string, | ||||||
|  |         assignmentId: number, | ||||||
|  |         groupId?: number, | ||||||
|  |         full = true, | ||||||
|     ): Promise<SubmissionsResponse> { |     ): Promise<SubmissionsResponse> { | ||||||
|         return this.get<SubmissionsResponse>( |         return this.get<SubmissionsResponse>(`/`, { language, version, classId, assignmentId, groupId, full }); | ||||||
|             `/`, |  | ||||||
|             { language, version, classId, assignmentId, groupId, full } |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getByNumber( |     async getByNumber( | ||||||
|  | @ -31,12 +32,15 @@ export class SubmissionController extends BaseController { | ||||||
|         classId: string, |         classId: string, | ||||||
|         assignmentId: number, |         assignmentId: number, | ||||||
|         groupId: number, |         groupId: number, | ||||||
|         submissionNumber: number |         submissionNumber: number, | ||||||
|     ): Promise<SubmissionResponse> { |     ): Promise<SubmissionResponse> { | ||||||
|         return this.get<SubmissionResponse>( |         return this.get<SubmissionResponse>(`/${submissionNumber}`, { | ||||||
|             `/${submissionNumber}`, |             language, | ||||||
|             { language, version, classId, assignmentId, groupId }, |             version, | ||||||
|         ); |             classId, | ||||||
|  |             assignmentId, | ||||||
|  |             groupId, | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async createSubmission(data: SubmissionDTO): Promise<SubmissionResponse> { |     async createSubmission(data: SubmissionDTO): Promise<SubmissionResponse> { | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ const learningPathController = getLearningPathController(); | ||||||
| export function useGetLearningPathQuery( | export function useGetLearningPathQuery( | ||||||
|     hruid: MaybeRefOrGetter<string>, |     hruid: MaybeRefOrGetter<string>, | ||||||
|     language: MaybeRefOrGetter<Language>, |     language: MaybeRefOrGetter<Language>, | ||||||
|     forGroup?: MaybeRefOrGetter<{forGroup: number, assignmentNo: number, classId: string} | undefined>, |     forGroup?: MaybeRefOrGetter<{ forGroup: number; assignmentNo: number; classId: string } | undefined>, | ||||||
| ): UseQueryReturnType<LearningPath, Error> { | ): UseQueryReturnType<LearningPath, Error> { | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: [LEARNING_PATH_KEY, "get", hruid, language, forGroup], |         queryKey: [LEARNING_PATH_KEY, "get", hruid, language, forGroup], | ||||||
|  | @ -34,7 +34,7 @@ export function useGetAllLearningPathsByThemeQuery( | ||||||
| 
 | 
 | ||||||
| export function useSearchLearningPathQuery( | export function useSearchLearningPathQuery( | ||||||
|     query: MaybeRefOrGetter<string | undefined>, |     query: MaybeRefOrGetter<string | undefined>, | ||||||
|     language: MaybeRefOrGetter<string | undefined> |     language: MaybeRefOrGetter<string | undefined>, | ||||||
| ): UseQueryReturnType<LearningPath[], Error> { | ): UseQueryReturnType<LearningPath[], Error> { | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: [LEARNING_PATH_KEY, "search", query, language], |         queryKey: [LEARNING_PATH_KEY, "search", query, language], | ||||||
|  |  | ||||||
|  | @ -9,9 +9,9 @@ import { | ||||||
|     type UseQueryReturnType, |     type UseQueryReturnType, | ||||||
| } from "@tanstack/vue-query"; | } from "@tanstack/vue-query"; | ||||||
| import { computed, toValue, type MaybeRefOrGetter } from "vue"; | import { computed, toValue, type MaybeRefOrGetter } from "vue"; | ||||||
| import {LEARNING_PATH_KEY} from "@/queries/learning-paths.ts"; | import { LEARNING_PATH_KEY } from "@/queries/learning-paths.ts"; | ||||||
| import {LEARNING_OBJECT_KEY} from "@/queries/learning-objects.ts"; | import { LEARNING_OBJECT_KEY } from "@/queries/learning-objects.ts"; | ||||||
| import type {Language} from "@dwengo-1/common/util/language"; | import type { Language } from "@dwengo-1/common/util/language"; | ||||||
| 
 | 
 | ||||||
| export const SUBMISSION_KEY = "submissions"; | export const SUBMISSION_KEY = "submissions"; | ||||||
| 
 | 
 | ||||||
|  | @ -22,7 +22,7 @@ function submissionQueryKey( | ||||||
|     classid: string, |     classid: string, | ||||||
|     assignmentNumber: number, |     assignmentNumber: number, | ||||||
|     groupNumber: number, |     groupNumber: number, | ||||||
|     submissionNumber: number |     submissionNumber: number, | ||||||
| ) { | ) { | ||||||
|     return ["submission", hruid, language, version, classid, assignmentNumber, groupNumber, submissionNumber]; |     return ["submission", hruid, language, version, classid, assignmentNumber, groupNumber, submissionNumber]; | ||||||
| } | } | ||||||
|  | @ -41,29 +41,37 @@ export async function invalidateAllSubmissionKeys( | ||||||
| 
 | 
 | ||||||
|     for (const key of keys) { |     for (const key of keys) { | ||||||
|         const queryKey = [ |         const queryKey = [ | ||||||
|             key, hruid, language, version, classid, assignmentNumber, groupNumber, submissionNumber |             key, | ||||||
|         ].filter( |             hruid, | ||||||
|             (arg) => arg !== undefined, |             language, | ||||||
|         ); |             version, | ||||||
|  |             classid, | ||||||
|  |             assignmentNumber, | ||||||
|  |             groupNumber, | ||||||
|  |             submissionNumber, | ||||||
|  |         ].filter((arg) => arg !== undefined); | ||||||
|         await queryClient.invalidateQueries({ queryKey: queryKey }); |         await queryClient.invalidateQueries({ queryKey: queryKey }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     await queryClient.invalidateQueries({ |     await queryClient.invalidateQueries({ | ||||||
|         queryKey: ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber] |         queryKey: ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber].filter( | ||||||
|             .filter((arg) => arg !== undefined), |             (arg) => arg !== undefined, | ||||||
|  |         ), | ||||||
|     }); |     }); | ||||||
|     await queryClient.invalidateQueries({ |     await queryClient.invalidateQueries({ | ||||||
|         queryKey: ["group-submissions", hruid, language, version, classid, assignmentNumber, groupNumber] |         queryKey: ["group-submissions", hruid, language, version, classid, assignmentNumber, groupNumber].filter( | ||||||
|             .filter((arg) => arg !== undefined), |             (arg) => arg !== undefined, | ||||||
|  |         ), | ||||||
|     }); |     }); | ||||||
|     await queryClient.invalidateQueries({ |     await queryClient.invalidateQueries({ | ||||||
|         queryKey: ["assignment-submissions", hruid, language, version,classid, assignmentNumber] |         queryKey: ["assignment-submissions", hruid, language, version, classid, assignmentNumber].filter( | ||||||
|             .filter((arg) => arg !== undefined), |             (arg) => arg !== undefined, | ||||||
|  |         ), | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function checkEnabled(properties: MaybeRefOrGetter<unknown>[]): boolean { | function checkEnabled(properties: MaybeRefOrGetter<unknown>[]): boolean { | ||||||
|     return properties.every(prop => Boolean(toValue(prop))); |     return properties.every((prop) => Boolean(toValue(prop))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function useSubmissionsQuery( | export function useSubmissionsQuery( | ||||||
|  | @ -87,9 +95,14 @@ export function useSubmissionsQuery( | ||||||
|             const fullVal = toValue(full); |             const fullVal = toValue(full); | ||||||
| 
 | 
 | ||||||
|             const response = await new SubmissionController(hruidVal!).getAll( |             const response = await new SubmissionController(hruidVal!).getAll( | ||||||
|                 languageVal, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal, fullVal |                 languageVal, | ||||||
|  |                 versionVal!, | ||||||
|  |                 classIdVal!, | ||||||
|  |                 assignmentNumberVal!, | ||||||
|  |                 groupNumberVal, | ||||||
|  |                 fullVal, | ||||||
|             ); |             ); | ||||||
|             return response ? response.submissions as SubmissionDTO[] : undefined; |             return response ? (response.submissions as SubmissionDTO[]) : undefined; | ||||||
|         }, |         }, | ||||||
|         enabled: () => checkEnabled([hruid, language, version, classid, assignmentNumber]), |         enabled: () => checkEnabled([hruid, language, version, classid, assignmentNumber]), | ||||||
|     }); |     }); | ||||||
|  | @ -113,13 +126,33 @@ export function useSubmissionQuery( | ||||||
|     const submissionNumberVal = toValue(submissionNumber); |     const submissionNumberVal = toValue(submissionNumber); | ||||||
| 
 | 
 | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: computed(() => submissionQueryKey( |         queryKey: computed(() => | ||||||
|             hruidVal!, languageVal, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal!, submissionNumberVal! |             submissionQueryKey( | ||||||
|         )), |                 hruidVal!, | ||||||
|         queryFn: async () => new SubmissionController(hruidVal!).getByNumber( |                 languageVal, | ||||||
|             languageVal, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal!, submissionNumberVal! |                 versionVal!, | ||||||
|  |                 classIdVal!, | ||||||
|  |                 assignmentNumberVal!, | ||||||
|  |                 groupNumberVal!, | ||||||
|  |                 submissionNumberVal!, | ||||||
|  |             ), | ||||||
|         ), |         ), | ||||||
|         enabled: () => Boolean(hruidVal) && Boolean(languageVal) && Boolean(versionVal) && Boolean(classIdVal) && Boolean(assignmentNumberVal) && Boolean(submissionNumber), |         queryFn: async () => | ||||||
|  |             new SubmissionController(hruidVal!).getByNumber( | ||||||
|  |                 languageVal, | ||||||
|  |                 versionVal!, | ||||||
|  |                 classIdVal!, | ||||||
|  |                 assignmentNumberVal!, | ||||||
|  |                 groupNumberVal!, | ||||||
|  |                 submissionNumberVal!, | ||||||
|  |             ), | ||||||
|  |         enabled: () => | ||||||
|  |             Boolean(hruidVal) && | ||||||
|  |             Boolean(languageVal) && | ||||||
|  |             Boolean(versionVal) && | ||||||
|  |             Boolean(classIdVal) && | ||||||
|  |             Boolean(assignmentNumberVal) && | ||||||
|  |             Boolean(submissionNumber), | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -132,7 +165,8 @@ export function useCreateSubmissionMutation(): UseMutationReturnType< | ||||||
|     const queryClient = useQueryClient(); |     const queryClient = useQueryClient(); | ||||||
| 
 | 
 | ||||||
|     return useMutation({ |     return useMutation({ | ||||||
|         mutationFn: async ({ data }) => new SubmissionController(data.learningObjectIdentifier.hruid).createSubmission(data), |         mutationFn: async ({ data }) => | ||||||
|  |             new SubmissionController(data.learningObjectIdentifier.hruid).createSubmission(data), | ||||||
|         onSuccess: async (response) => { |         onSuccess: async (response) => { | ||||||
|             if (!response.submission.group) { |             if (!response.submission.group) { | ||||||
|                 await invalidateAllSubmissionKeys(queryClient); |                 await invalidateAllSubmissionKeys(queryClient); | ||||||
|  | @ -144,13 +178,13 @@ export function useCreateSubmissionMutation(): UseMutationReturnType< | ||||||
|                 const an = typeof assignment === "number" ? assignment : assignment.id; |                 const an = typeof assignment === "number" ? assignment : assignment.id; | ||||||
|                 const gn = response.submission.group.groupNumber; |                 const gn = response.submission.group.groupNumber; | ||||||
| 
 | 
 | ||||||
|                 const {hruid, language, version} = response.submission.learningObjectIdentifier; |                 const { hruid, language, version } = response.submission.learningObjectIdentifier; | ||||||
|                 await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn); |                 await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn); | ||||||
| 
 | 
 | ||||||
|                 await queryClient.invalidateQueries({queryKey: [LEARNING_PATH_KEY, "get"]}); |                 await queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY, "get"] }); | ||||||
| 
 | 
 | ||||||
|                 await queryClient.invalidateQueries({ |                 await queryClient.invalidateQueries({ | ||||||
|                     queryKey: [LEARNING_OBJECT_KEY, "metadata", hruid, language, version] |                     queryKey: [LEARNING_OBJECT_KEY, "metadata", hruid, language, version], | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  | @ -178,15 +212,9 @@ export function useDeleteSubmissionMutation(): UseMutationReturnType< | ||||||
|                 const an = typeof assignment === "number" ? assignment : assignment.id; |                 const an = typeof assignment === "number" ? assignment : assignment.id; | ||||||
|                 const gn = response.submission.group.groupNumber; |                 const gn = response.submission.group.groupNumber; | ||||||
| 
 | 
 | ||||||
|                 const {hruid, language, version} = response.submission.learningObjectIdentifier; |                 const { hruid, language, version } = response.submission.learningObjectIdentifier; | ||||||
| 
 | 
 | ||||||
|                 await invalidateAllSubmissionKeys( |                 await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn); | ||||||
|                     queryClient, |  | ||||||
|                     hruid, |  | ||||||
|                     language, |  | ||||||
|                     version, |  | ||||||
|                     cid, an, gn |  | ||||||
|                 ); |  | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -1,20 +1,29 @@ | ||||||
| export function deepEquals<T>(a: T, b: T): boolean { | export function deepEquals<T>(a: T, b: T): boolean { | ||||||
|     if (a === b) {return true;} |     if (a === b) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) |     if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) { | ||||||
|         {return false;} |         return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (Array.isArray(a) !== Array.isArray(b)) {return false;} |     if (Array.isArray(a) !== Array.isArray(b)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (Array.isArray(a) && Array.isArray(b)) { |     if (Array.isArray(a) && Array.isArray(b)) { | ||||||
|         if (a.length !== b.length) {return false;} |         if (a.length !== b.length) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|         return a.every((val, i) => deepEquals(val, b[i])); |         return a.every((val, i) => deepEquals(val, b[i])); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const keysA = Object.keys(a) as (keyof T)[]; |     const keysA = Object.keys(a) as (keyof T)[]; | ||||||
|     const keysB = Object.keys(b) as (keyof T)[]; |     const keysB = Object.keys(b) as (keyof T)[]; | ||||||
| 
 | 
 | ||||||
|     if (keysA.length !== keysB.length) {return false;} |     if (keysA.length !== keysB.length) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return keysA.every(key => deepEquals(a[key], b[key])); |     return keysA.every((key) => deepEquals(a[key], b[key])); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {useRoute} from "vue-router"; |     import { useRoute } from "vue-router"; | ||||||
| 
 | 
 | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -1,24 +1,24 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import {useGroupsQuery} from "@/queries/groups.ts"; |     import { useGroupsQuery } from "@/queries/groups.ts"; | ||||||
|     import type {GroupsResponse} from "@/controllers/groups.ts"; |     import type { GroupsResponse } from "@/controllers/groups.ts"; | ||||||
|     import {useI18n} from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
|     import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |     import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         classId: string, |         classId: string; | ||||||
|         assignmentNumber: number |         assignmentNumber: number; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const model = defineModel<number | undefined>({default: undefined}); |     const model = defineModel<number | undefined>({ default: undefined }); | ||||||
| 
 | 
 | ||||||
|     const groupsQuery = useGroupsQuery(props.classId, props.assignmentNumber, true); |     const groupsQuery = useGroupsQuery(props.classId, props.assignmentNumber, true); | ||||||
| 
 | 
 | ||||||
|     interface GroupSelectorOption { |     interface GroupSelectorOption { | ||||||
|         groupNumber: number | undefined, |         groupNumber: number | undefined; | ||||||
|         label: string |         label: string; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function groupOptions(groups: GroupDTO[]): GroupSelectorOption[] { |     function groupOptions(groups: GroupDTO[]): GroupSelectorOption[] { | ||||||
|  | @ -26,7 +26,7 @@ | ||||||
|             .sort((a, b) => a.groupNumber - b.groupNumber) |             .sort((a, b) => a.groupNumber - b.groupNumber) | ||||||
|             .map((group, index) => ({ |             .map((group, index) => ({ | ||||||
|                 groupNumber: group.groupNumber, |                 groupNumber: group.groupNumber, | ||||||
|                 label: `${index + 1}` |                 label: `${index + 1}`, | ||||||
|             })); |             })); | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
|     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; |     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
|     import { computed, type ComputedRef, ref } from "vue"; |     import { computed, type ComputedRef, ref } from "vue"; | ||||||
|     import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; |     import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; | ||||||
|     import {useRoute, useRouter} from "vue-router"; |     import { useRoute, useRouter } from "vue-router"; | ||||||
|     import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; |     import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; | ||||||
|     import { useI18n } from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
|     import LearningPathSearchField from "@/components/LearningPathSearchField.vue"; |     import LearningPathSearchField from "@/components/LearningPathSearchField.vue"; | ||||||
|  | @ -21,7 +21,7 @@ | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         hruid: string; |         hruid: string; | ||||||
|         language: Language; |         language: Language; | ||||||
|         learningObjectHruid?: string, |         learningObjectHruid?: string; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     interface LearningPathPageQuery { |     interface LearningPathPageQuery { | ||||||
|  | @ -37,7 +37,7 @@ | ||||||
|             return { |             return { | ||||||
|                 forGroup: parseInt(query.value.forGroup), |                 forGroup: parseInt(query.value.forGroup), | ||||||
|                 assignmentNo: parseInt(query.value.assignmentNo), |                 assignmentNo: parseInt(query.value.assignmentNo), | ||||||
|                 classId: query.value.classId |                 classId: query.value.classId, | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  | @ -112,7 +112,7 @@ | ||||||
|             let query = structuredClone(route.query); |             let query = structuredClone(route.query); | ||||||
|             query.forGroup = value; |             query.forGroup = value; | ||||||
|             router.push({ query }); |             router.push({ query }); | ||||||
|         } |         }, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function assign() { |     function assign() { | ||||||
|  | @ -120,8 +120,8 @@ | ||||||
|             path: "/assignment/create", |             path: "/assignment/create", | ||||||
|             query: { |             query: { | ||||||
|                 hruid: props.hruid, |                 hruid: props.hruid, | ||||||
|                 language: props.language |                 language: props.language, | ||||||
|             } |             }, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
|  | @ -136,80 +136,86 @@ | ||||||
|             :width="350" |             :width="350" | ||||||
|         > |         > | ||||||
|             <div class="d-flex flex-column h-100"> |             <div class="d-flex flex-column h-100"> | ||||||
|             <v-list-item> |                 <v-list-item> | ||||||
|                 <template v-slot:title> |                     <template v-slot:title> | ||||||
|                     <div class="learning-path-title">{{ learningPath.data.title }}</div> |                         <div class="learning-path-title">{{ learningPath.data.title }}</div> | ||||||
|                 </template> |  | ||||||
|                 <template v-slot:subtitle> |  | ||||||
|                     <div>{{ learningPath.data.description }}</div> |  | ||||||
|                 </template> |  | ||||||
|             </v-list-item> |  | ||||||
|             <v-list-item> |  | ||||||
|                 <template v-slot:subtitle> |  | ||||||
|                     <p> |  | ||||||
|                         <v-icon |  | ||||||
|                             :color="COLORS.notCompleted" |  | ||||||
|                             :icon="ICONS.notCompleted" |  | ||||||
|                         ></v-icon> |  | ||||||
|                         {{ t("legendNotCompletedYet") }} |  | ||||||
|                     </p> |  | ||||||
|                     <p> |  | ||||||
|                         <v-icon |  | ||||||
|                             :color="COLORS.completed" |  | ||||||
|                             :icon="ICONS.completed" |  | ||||||
|                         ></v-icon> |  | ||||||
|                         {{ t("legendCompleted") }} |  | ||||||
|                     </p> |  | ||||||
|                     <p> |  | ||||||
|                         <v-icon |  | ||||||
|                             :color="COLORS.teacherExclusive" |  | ||||||
|                             :icon="ICONS.teacherExclusive" |  | ||||||
|                         ></v-icon> |  | ||||||
|                         {{ t("legendTeacherExclusive") }} |  | ||||||
|                     </p> |  | ||||||
|                 </template> |  | ||||||
|             </v-list-item> |  | ||||||
|             <v-list-item v-if="query.classId && query.assignmentNo && authService.authState.activeRole === 'teacher'"> |  | ||||||
|                 <template v-slot:default> |  | ||||||
|                     <learning-path-group-selector |  | ||||||
|                         :class-id="query.classId" |  | ||||||
|                         :assignment-number="parseInt(query.assignmentNo)" |  | ||||||
|                         v-model="forGroupQueryParam" |  | ||||||
|                     /> |  | ||||||
|                 </template> |  | ||||||
|             </v-list-item> |  | ||||||
|             <v-divider></v-divider> |  | ||||||
|             <div v-if="props.learningObjectHruid"> |  | ||||||
|                 <using-query-result |  | ||||||
|                     :query-result="learningObjectListQueryResult" |  | ||||||
|                     v-slot="learningObjects: { data: LearningObject[] }" |  | ||||||
|                 > |  | ||||||
|                     <template v-for="node in learningObjects.data"> |  | ||||||
|                         <v-list-item |  | ||||||
|                             link |  | ||||||
|                             :to="{ path: node.key, query: route.query }" |  | ||||||
|                             :title="node.title" |  | ||||||
|                             :active="node.key === props.learningObjectHruid" |  | ||||||
|                             :key="node.key" |  | ||||||
|                             v-if="!node.teacherExclusive || authService.authState.activeRole === 'teacher'" |  | ||||||
|                         > |  | ||||||
|                             <template v-slot:prepend> |  | ||||||
|                                 <v-icon |  | ||||||
|                                     :color="COLORS[getNavItemState(node)]" |  | ||||||
|                                     :icon="ICONS[getNavItemState(node)]" |  | ||||||
|                                 ></v-icon> |  | ||||||
|                             </template> |  | ||||||
|                             <template v-slot:append> {{ node.estimatedTime }}' </template> |  | ||||||
|                         </v-list-item> |  | ||||||
|                     </template> |                     </template> | ||||||
|                 </using-query-result> |                     <template v-slot:subtitle> | ||||||
|             </div> |                         <div>{{ learningPath.data.description }}</div> | ||||||
|             <v-spacer></v-spacer> |                     </template> | ||||||
|             <v-list-item v-if="authService.authState.activeRole === 'teacher'"> |                 </v-list-item> | ||||||
|                 <template v-slot:default> |                 <v-list-item> | ||||||
|                     <v-btn class="button-in-nav" @click="assign()">{{ t("assignLearningPath") }}</v-btn> |                     <template v-slot:subtitle> | ||||||
|                 </template> |                         <p> | ||||||
|             </v-list-item> |                             <v-icon | ||||||
|  |                                 :color="COLORS.notCompleted" | ||||||
|  |                                 :icon="ICONS.notCompleted" | ||||||
|  |                             ></v-icon> | ||||||
|  |                             {{ t("legendNotCompletedYet") }} | ||||||
|  |                         </p> | ||||||
|  |                         <p> | ||||||
|  |                             <v-icon | ||||||
|  |                                 :color="COLORS.completed" | ||||||
|  |                                 :icon="ICONS.completed" | ||||||
|  |                             ></v-icon> | ||||||
|  |                             {{ t("legendCompleted") }} | ||||||
|  |                         </p> | ||||||
|  |                         <p> | ||||||
|  |                             <v-icon | ||||||
|  |                                 :color="COLORS.teacherExclusive" | ||||||
|  |                                 :icon="ICONS.teacherExclusive" | ||||||
|  |                             ></v-icon> | ||||||
|  |                             {{ t("legendTeacherExclusive") }} | ||||||
|  |                         </p> | ||||||
|  |                     </template> | ||||||
|  |                 </v-list-item> | ||||||
|  |                 <v-list-item | ||||||
|  |                     v-if="query.classId && query.assignmentNo && authService.authState.activeRole === 'teacher'" | ||||||
|  |                 > | ||||||
|  |                     <template v-slot:default> | ||||||
|  |                         <learning-path-group-selector | ||||||
|  |                             :class-id="query.classId" | ||||||
|  |                             :assignment-number="parseInt(query.assignmentNo)" | ||||||
|  |                             v-model="forGroupQueryParam" | ||||||
|  |                         /> | ||||||
|  |                     </template> | ||||||
|  |                 </v-list-item> | ||||||
|  |                 <v-divider></v-divider> | ||||||
|  |                 <div v-if="props.learningObjectHruid"> | ||||||
|  |                     <using-query-result | ||||||
|  |                         :query-result="learningObjectListQueryResult" | ||||||
|  |                         v-slot="learningObjects: { data: LearningObject[] }" | ||||||
|  |                     > | ||||||
|  |                         <template v-for="node in learningObjects.data"> | ||||||
|  |                             <v-list-item | ||||||
|  |                                 link | ||||||
|  |                                 :to="{ path: node.key, query: route.query }" | ||||||
|  |                                 :title="node.title" | ||||||
|  |                                 :active="node.key === props.learningObjectHruid" | ||||||
|  |                                 :key="node.key" | ||||||
|  |                                 v-if="!node.teacherExclusive || authService.authState.activeRole === 'teacher'" | ||||||
|  |                             > | ||||||
|  |                                 <template v-slot:prepend> | ||||||
|  |                                     <v-icon | ||||||
|  |                                         :color="COLORS[getNavItemState(node)]" | ||||||
|  |                                         :icon="ICONS[getNavItemState(node)]" | ||||||
|  |                                     ></v-icon> | ||||||
|  |                                 </template> | ||||||
|  |                                 <template v-slot:append> {{ node.estimatedTime }}' </template> | ||||||
|  |                             </v-list-item> | ||||||
|  |                         </template> | ||||||
|  |                     </using-query-result> | ||||||
|  |                 </div> | ||||||
|  |                 <v-spacer></v-spacer> | ||||||
|  |                 <v-list-item v-if="authService.authState.activeRole === 'teacher'"> | ||||||
|  |                     <template v-slot:default> | ||||||
|  |                         <v-btn | ||||||
|  |                             class="button-in-nav" | ||||||
|  |                             @click="assign()" | ||||||
|  |                             >{{ t("assignLearningPath") }}</v-btn | ||||||
|  |                         > | ||||||
|  |                     </template> | ||||||
|  |                 </v-list-item> | ||||||
|             </div> |             </div> | ||||||
|         </v-navigation-drawer> |         </v-navigation-drawer> | ||||||
|         <div class="control-bar-above-content"> |         <div class="control-bar-above-content"> | ||||||
|  |  | ||||||
|  | @ -1,13 +1,18 @@ | ||||||
| export const essayQuestionAdapter: GiftAdapter = { | export const essayQuestionAdapter: GiftAdapter = { | ||||||
|     questionType: "Essay", |     questionType: "Essay", | ||||||
| 
 | 
 | ||||||
|     installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void { |     installListener( | ||||||
|         const textArea = questionElement.querySelector('textarea')!; |         questionElement: Element, | ||||||
|         textArea.addEventListener('input', () => { answerUpdateCallback(textArea.value); }); |         answerUpdateCallback: (newAnswer: string | number | object) => void, | ||||||
|  |     ): void { | ||||||
|  |         const textArea = questionElement.querySelector("textarea")!; | ||||||
|  |         textArea.addEventListener("input", () => { | ||||||
|  |             answerUpdateCallback(textArea.value); | ||||||
|  |         }); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     setAnswer(questionElement: Element, answer: string | number | object): void { |     setAnswer(questionElement: Element, answer: string | number | object): void { | ||||||
|         const textArea = questionElement.querySelector('textarea')!; |         const textArea = questionElement.querySelector("textarea")!; | ||||||
|         textArea.value = String(answer); |         textArea.value = String(answer); | ||||||
|     } |     }, | ||||||
| } | }; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,8 @@ | ||||||
| interface GiftAdapter { | interface GiftAdapter { | ||||||
|     questionType: string; |     questionType: string; | ||||||
|     installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void; |     installListener( | ||||||
|  |         questionElement: Element, | ||||||
|  |         answerUpdateCallback: (newAnswer: string | number | object) => void, | ||||||
|  |     ): void; | ||||||
|     setAnswer(questionElement: Element, answer: string | number | object): void; |     setAnswer(questionElement: Element, answer: string | number | object): void; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import {multipleChoiceQuestionAdapter} from "@/views/learning-paths/gift-adapters/multiple-choice-question-adapter.ts"; | import { multipleChoiceQuestionAdapter } from "@/views/learning-paths/gift-adapters/multiple-choice-question-adapter.ts"; | ||||||
| import {essayQuestionAdapter} from "@/views/learning-paths/gift-adapters/essay-question-adapter.ts"; | import { essayQuestionAdapter } from "@/views/learning-paths/gift-adapters/essay-question-adapter.ts"; | ||||||
| 
 | 
 | ||||||
| export const giftAdapters = [multipleChoiceQuestionAdapter, essayQuestionAdapter]; | export const giftAdapters = [multipleChoiceQuestionAdapter, essayQuestionAdapter]; | ||||||
| 
 | 
 | ||||||
| export function getGiftAdapterForType(questionType: string): GiftAdapter | undefined { | export function getGiftAdapterForType(questionType: string): GiftAdapter | undefined { | ||||||
|     return giftAdapters.find(it => it.questionType === questionType); |     return giftAdapters.find((it) => it.questionType === questionType); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| export const multipleChoiceQuestionAdapter: GiftAdapter = { | export const multipleChoiceQuestionAdapter: GiftAdapter = { | ||||||
|     questionType: "MC", |     questionType: "MC", | ||||||
| 
 | 
 | ||||||
|     installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void { |     installListener( | ||||||
|         questionElement.querySelectorAll('input[type=radio]').forEach(element => { |         questionElement: Element, | ||||||
|  |         answerUpdateCallback: (newAnswer: string | number | object) => void, | ||||||
|  |     ): void { | ||||||
|  |         questionElement.querySelectorAll("input[type=radio]").forEach((element) => { | ||||||
|             const input = element as HTMLInputElement; |             const input = element as HTMLInputElement; | ||||||
| 
 | 
 | ||||||
|             input.addEventListener('change', () => { |             input.addEventListener("change", () => { | ||||||
|                 answerUpdateCallback(parseInt(input.value)); |                 answerUpdateCallback(parseInt(input.value)); | ||||||
|             }); |             }); | ||||||
|             // Optional: initialize value if already selected
 |             // Optional: initialize value if already selected
 | ||||||
|  | @ -16,9 +19,9 @@ export const multipleChoiceQuestionAdapter: GiftAdapter = { | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     setAnswer(questionElement: Element, answer: string | number | object): void { |     setAnswer(questionElement: Element, answer: string | number | object): void { | ||||||
|         questionElement.querySelectorAll('input[type=radio]').forEach(element => { |         questionElement.querySelectorAll("input[type=radio]").forEach((element) => { | ||||||
|             const input = element as HTMLInputElement; |             const input = element as HTMLInputElement; | ||||||
|             input.checked = String(answer) === String(input.value); |             input.checked = String(answer) === String(input.value); | ||||||
|         }); |         }); | ||||||
|     } |     }, | ||||||
| } | }; | ||||||
|  |  | ||||||
|  | @ -3,9 +3,9 @@ | ||||||
|     import type { UseQueryReturnType } from "@tanstack/vue-query"; |     import type { UseQueryReturnType } from "@tanstack/vue-query"; | ||||||
|     import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts"; |     import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import {computed, ref} from "vue"; |     import { computed, ref } from "vue"; | ||||||
|     import authService from "@/services/auth/auth-service.ts"; |     import authService from "@/services/auth/auth-service.ts"; | ||||||
|     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; |     import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|     import LearningObjectContentView from "@/views/learning-paths/learning-object/content/LearningObjectContentView.vue"; |     import LearningObjectContentView from "@/views/learning-paths/learning-object/content/LearningObjectContentView.vue"; | ||||||
|     import LearningObjectSubmissionsView from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsView.vue"; |     import LearningObjectSubmissionsView from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsView.vue"; | ||||||
| 
 | 
 | ||||||
|  | @ -14,8 +14,8 @@ | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         hruid: string; |         hruid: string; | ||||||
|         language: Language; |         language: Language; | ||||||
|         version: number, |         version: number; | ||||||
|         group?: {forGroup: number, assignmentNo: number, classId: string} |         group?: { forGroup: number; assignmentNo: number; classId: string }; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery( |     const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery( | ||||||
|  | @ -35,7 +35,7 @@ | ||||||
|             :learning-object-content="learningPathHtml.data" |             :learning-object-content="learningPathHtml.data" | ||||||
|             v-model:submission-data="currentSubmission" |             v-model:submission-data="currentSubmission" | ||||||
|         /> |         /> | ||||||
|         <div class="content-submissions-spacer"/> |         <div class="content-submissions-spacer" /> | ||||||
|         <learning-object-submissions-view |         <learning-object-submissions-view | ||||||
|             v-if="props.group" |             v-if="props.group" | ||||||
|             :group="props.group" |             :group="props.group" | ||||||
|  | @ -48,26 +48,26 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| :deep(hr) { |     :deep(hr) { | ||||||
|     margin-top: 10px; |         margin-top: 10px; | ||||||
|     margin-bottom: 10px; |         margin-bottom: 10px; | ||||||
| } |     } | ||||||
| :deep(li) { |     :deep(li) { | ||||||
|     margin-left: 30px; |         margin-left: 30px; | ||||||
|     margin-top: 5px; |         margin-top: 5px; | ||||||
|     margin-bottom: 5px; |         margin-bottom: 5px; | ||||||
| } |     } | ||||||
| :deep(img) { |     :deep(img) { | ||||||
|     max-width: 80%; |         max-width: 80%; | ||||||
| } |     } | ||||||
| :deep(h2), |     :deep(h2), | ||||||
| :deep(h3), |     :deep(h3), | ||||||
| :deep(h4), |     :deep(h4), | ||||||
| :deep(h5), |     :deep(h5), | ||||||
| :deep(h6) { |     :deep(h6) { | ||||||
|     margin-top: 10px; |         margin-top: 10px; | ||||||
| } |     } | ||||||
| .content-submissions-spacer { |     .content-submissions-spacer { | ||||||
|     height: 20px; |         height: 20px; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,32 +1,32 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| 
 |     import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; |     import { getGiftAdapterForType } from "@/views/learning-paths/gift-adapters/gift-adapters.ts"; | ||||||
|     import {getGiftAdapterForType} from "@/views/learning-paths/gift-adapters/gift-adapters.ts"; |     import { computed, nextTick, onMounted, watch } from "vue"; | ||||||
|     import {computed, nextTick, onMounted, watch} from "vue"; |     import { copyArrayWith } from "@/utils/array-utils.ts"; | ||||||
|     import {copyArrayWith} from "@/utils/array-utils.ts"; |  | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         learningObjectContent: Document |         learningObjectContent: Document; | ||||||
|         submissionData?: SubmissionData |         submissionData?: SubmissionData; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const emit = defineEmits<{ |     const emit = defineEmits<{ | ||||||
|         (e: "update:submissionData", value: SubmissionData): void |         (e: "update:submissionData", value: SubmissionData): void; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const submissionData = computed<SubmissionData | undefined>({ |     const submissionData = computed<SubmissionData | undefined>({ | ||||||
|         get: () => props.submissionData, |         get: () => props.submissionData, | ||||||
|         set: (v?: SubmissionData) => v ? emit('update:submissionData', v) : undefined, |         set: (v?: SubmissionData) => (v ? emit("update:submissionData", v) : undefined), | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function forEachQuestion( |     function forEachQuestion( | ||||||
|         doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void |         doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void, | ||||||
|     ) { |     ) { | ||||||
|         const questions = document.querySelectorAll(".gift-question"); |         const questions = document.querySelectorAll(".gift-question"); | ||||||
|         questions.forEach(question => { |         questions.forEach((question) => { | ||||||
|             const name = question.id.match(/gift-q(\d+)/)?.[1] |             const name = question.id.match(/gift-q(\d+)/)?.[1]; | ||||||
|             const questionType = question.className.split(" ") |             const questionType = question.className | ||||||
|                 .find(it => it.startsWith("gift-question-type")) |                 .split(" ") | ||||||
|  |                 .find((it) => it.startsWith("gift-question-type")) | ||||||
|                 ?.match(/gift-question-type-([^ ]*)/)?.[1]; |                 ?.match(/gift-question-type-([^ ]*)/)?.[1]; | ||||||
| 
 | 
 | ||||||
|             if (!name || isNaN(parseInt(name)) || !questionType) return; |             if (!name || isNaN(parseInt(name)) || !questionType) return; | ||||||
|  | @ -39,12 +39,9 @@ | ||||||
| 
 | 
 | ||||||
|     function attachQuestionListeners(): void { |     function attachQuestionListeners(): void { | ||||||
|         forEachQuestion((index, _name, type, element) => { |         forEachQuestion((index, _name, type, element) => { | ||||||
|             getGiftAdapterForType(type)?.installListener( |             getGiftAdapterForType(type)?.installListener(element, (newAnswer) => { | ||||||
|                 element, |                 submissionData.value = copyArrayWith(index, newAnswer, submissionData.value ?? []); | ||||||
|                 (newAnswer) => { |             }); | ||||||
|                     submissionData.value = copyArrayWith(index, newAnswer, submissionData.value ?? []) |  | ||||||
|                 } |  | ||||||
|             ); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -62,19 +59,25 @@ | ||||||
| 
 | 
 | ||||||
|     onMounted(() => |     onMounted(() => | ||||||
|         nextTick(() => { |         nextTick(() => { | ||||||
|             attachQuestionListeners() |             attachQuestionListeners(); | ||||||
|             setAnswers(props.submissionData ?? []); |             setAnswers(props.submissionData ?? []); | ||||||
|         }) |         }), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     watch(() => props.learningObjectContent, async () => { |     watch( | ||||||
|         await nextTick(); |         () => props.learningObjectContent, | ||||||
|         attachQuestionListeners(); |         async () => { | ||||||
|     }); |             await nextTick(); | ||||||
|     watch(() => props.submissionData, async () => { |             attachQuestionListeners(); | ||||||
|         await nextTick(); |         }, | ||||||
|         setAnswers(props.submissionData ?? []); |     ); | ||||||
|     }); |     watch( | ||||||
|  |         () => props.submissionData, | ||||||
|  |         async () => { | ||||||
|  |             await nextTick(); | ||||||
|  |             setAnswers(props.submissionData ?? []); | ||||||
|  |         }, | ||||||
|  |     ); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -84,5 +87,4 @@ | ||||||
|     ></div> |     ></div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped></style> | ||||||
| </style> |  | ||||||
|  |  | ||||||
|  | @ -1,36 +1,37 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission"; |     import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission"; | ||||||
|     import {computed} from "vue"; |     import { computed } from "vue"; | ||||||
|     import {useI18n} from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         allSubmissions: SubmissionDTO[] |         allSubmissions: SubmissionDTO[]; | ||||||
|     }>(); |     }>(); | ||||||
|     const emit = defineEmits<{ |     const emit = defineEmits<{ | ||||||
|         (e: "submission-selected", submission: SubmissionDTO): void |         (e: "submission-selected", submission: SubmissionDTO): void; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const headers = computed(() => [ |     const headers = computed(() => [ | ||||||
|         { title: "#", value: "submissionNo", width: "50px" }, |         { title: "#", value: "submissionNo", width: "50px" }, | ||||||
|         { title: t("submittedBy"), value: "submittedBy" }, |         { title: t("submittedBy"), value: "submittedBy" }, | ||||||
|         { title: t("timestamp"), value: "timestamp"}, |         { title: t("timestamp"), value: "timestamp" }, | ||||||
|         { title: "", key: "action", width: "70px", sortable: false }, |         { title: "", key: "action", width: "70px", sortable: false }, | ||||||
|     ]); |     ]); | ||||||
| 
 | 
 | ||||||
|     const data = computed(() => [...props.allSubmissions] |     const data = computed(() => | ||||||
|         .sort((a, b) => (a.submissionNumber ?? 0) - (b.submissionNumber ?? 0)) |         [...props.allSubmissions] | ||||||
|         .map((submission, index) => ({ |             .sort((a, b) => (a.submissionNumber ?? 0) - (b.submissionNumber ?? 0)) | ||||||
|             submissionNo: index + 1, |             .map((submission, index) => ({ | ||||||
|             submittedBy: `${submission.submitter.firstName} ${submission.submitter.lastName}`, |                 submissionNo: index + 1, | ||||||
|             timestamp: submission.time ? new Date(submission.time).toLocaleString(): "-", |                 submittedBy: `${submission.submitter.firstName} ${submission.submitter.lastName}`, | ||||||
|             dto: submission |                 timestamp: submission.time ? new Date(submission.time).toLocaleString() : "-", | ||||||
|         }) |                 dto: submission, | ||||||
|     )); |             })), | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     function selectSubmission(submission: SubmissionDTO) { |     function selectSubmission(submission: SubmissionDTO) { | ||||||
|         emit('submission-selected', submission); |         emit("submission-selected", submission); | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | @ -38,14 +39,19 @@ | ||||||
|     <v-card> |     <v-card> | ||||||
|         <v-card-title>{{ t("groupSubmissions") }}</v-card-title> |         <v-card-title>{{ t("groupSubmissions") }}</v-card-title> | ||||||
|         <v-card-text> |         <v-card-text> | ||||||
|             <v-data-table :headers="headers" |             <v-data-table | ||||||
|                           :items="data" |                 :headers="headers" | ||||||
|                           density="compact" |                 :items="data" | ||||||
|                           hide-default-footer |                 density="compact" | ||||||
|                           :no-data-text="t('noSubmissionsYet')" |                 hide-default-footer | ||||||
|  |                 :no-data-text="t('noSubmissionsYet')" | ||||||
|             > |             > | ||||||
|                 <template v-slot:item.action="{ item }"> |                 <template v-slot:item.action="{ item }"> | ||||||
|                     <v-btn density="compact" variant="plain" @click="selectSubmission(item.dto)"> |                     <v-btn | ||||||
|  |                         density="compact" | ||||||
|  |                         variant="plain" | ||||||
|  |                         @click="selectSubmission(item.dto)" | ||||||
|  |                     > | ||||||
|                         {{ t("loadSubmission") }} |                         {{ t("loadSubmission") }} | ||||||
|                     </v-btn> |                     </v-btn> | ||||||
|                 </template> |                 </template> | ||||||
|  | @ -54,6 +60,4 @@ | ||||||
|     </v-card> |     </v-card> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped></style> | ||||||
| 
 |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
|  | @ -1,26 +1,25 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; |     import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|     import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission"; |     import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission"; | ||||||
|     import {Language} from "@/data-objects/language.ts"; |     import { Language } from "@/data-objects/language.ts"; | ||||||
|     import {useSubmissionsQuery} from "@/queries/submissions.ts"; |     import { useSubmissionsQuery } from "@/queries/submissions.ts"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import SubmitButton from "@/views/learning-paths/learning-object/submissions/SubmitButton.vue"; |     import SubmitButton from "@/views/learning-paths/learning-object/submissions/SubmitButton.vue"; | ||||||
|     import {computed, watch} from "vue"; |     import { computed, watch } from "vue"; | ||||||
|     import LearningObjectSubmissionsTable |     import LearningObjectSubmissionsTable from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsTable.vue"; | ||||||
|         from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsTable.vue"; |     import { useI18n } from "vue-i18n"; | ||||||
|     import {useI18n} from "vue-i18n"; |  | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         submissionData?: SubmissionData, |         submissionData?: SubmissionData; | ||||||
|         hruid: string; |         hruid: string; | ||||||
|         language: Language; |         language: Language; | ||||||
|         version: number, |         version: number; | ||||||
|         group: {forGroup: number, assignmentNo: number, classId: string} |         group: { forGroup: number; assignmentNo: number; classId: string }; | ||||||
|     }>(); |     }>(); | ||||||
|     const emit = defineEmits<{ |     const emit = defineEmits<{ | ||||||
|         (e: "update:submissionData", value: SubmissionData): void |         (e: "update:submissionData", value: SubmissionData): void; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const submissionQuery = useSubmissionsQuery( |     const submissionQuery = useSubmissionsQuery( | ||||||
|  | @ -30,7 +29,7 @@ | ||||||
|         () => props.group.classId, |         () => props.group.classId, | ||||||
|         () => props.group.assignmentNo, |         () => props.group.assignmentNo, | ||||||
|         () => props.group.forGroup, |         () => props.group.forGroup, | ||||||
|         () => true |         () => true, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     function emitSubmissionData(submissionData: SubmissionData) { |     function emitSubmissionData(submissionData: SubmissionData) { | ||||||
|  | @ -58,17 +57,16 @@ | ||||||
|         return JSON.parse(submissions[submissions.length - 1].content); |         return JSON.parse(submissions[submissions.length - 1].content); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const showSubmissionTable = computed(() => |     const showSubmissionTable = computed(() => props.submissionData !== undefined && props.submissionData.length > 0); | ||||||
|         props.submissionData !== undefined && props.submissionData.length > 0 |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     const showIsDoneMessage = computed(() => |     const showIsDoneMessage = computed(() => lastSubmission.value !== undefined && lastSubmission.value.length === 0); | ||||||
|         lastSubmission.value !== undefined && lastSubmission.value.length === 0 |  | ||||||
|     ); |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <using-query-result :query-result="submissionQuery" v-slot="submissions: { data: SubmissionDTO[] }"> |     <using-query-result | ||||||
|  |         :query-result="submissionQuery" | ||||||
|  |         v-slot="submissions: { data: SubmissionDTO[] }" | ||||||
|  |     > | ||||||
|         <submit-button |         <submit-button | ||||||
|             :hruid="props.hruid" |             :hruid="props.hruid" | ||||||
|             :language="props.language" |             :language="props.language" | ||||||
|  | @ -78,12 +76,13 @@ | ||||||
|             :submissions="submissions.data" |             :submissions="submissions.data" | ||||||
|         /> |         /> | ||||||
|         <div class="submit-submissions-spacer"></div> |         <div class="submit-submissions-spacer"></div> | ||||||
|         <v-alert icon="mdi-check" |         <v-alert | ||||||
|                  :text="t('taskCompleted')" |             icon="mdi-check" | ||||||
|                  type="success" |             :text="t('taskCompleted')" | ||||||
|                  variant="tonal" |             type="success" | ||||||
|                  density="compact" |             variant="tonal" | ||||||
|                  v-if="showIsDoneMessage" |             density="compact" | ||||||
|  |             v-if="showIsDoneMessage" | ||||||
|         ></v-alert> |         ></v-alert> | ||||||
|         <learning-object-submissions-table |         <learning-object-submissions-table | ||||||
|             v-if="submissionQuery.data && showSubmissionTable" |             v-if="submissionQuery.data && showSubmissionTable" | ||||||
|  | @ -94,7 +93,7 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .submit-submissions-spacer { |     .submit-submissions-spacer { | ||||||
|     height: 20px; |         height: 20px; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,26 +1,26 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {computed} from "vue"; |     import { computed } from "vue"; | ||||||
|     import authService from "@/services/auth/auth-service.ts"; |     import authService from "@/services/auth/auth-service.ts"; | ||||||
|     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; |     import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|     import {Language} from "@/data-objects/language.ts"; |     import { Language } from "@/data-objects/language.ts"; | ||||||
|     import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission"; |     import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission"; | ||||||
|     import {useCreateSubmissionMutation} from "@/queries/submissions.ts"; |     import { useCreateSubmissionMutation } from "@/queries/submissions.ts"; | ||||||
|     import {deepEquals} from "@/utils/deep-equals.ts"; |     import { deepEquals } from "@/utils/deep-equals.ts"; | ||||||
|     import type {UserProfile} from "oidc-client-ts"; |     import type { UserProfile } from "oidc-client-ts"; | ||||||
|     import type {LearningObjectIdentifierDTO} from "@dwengo-1/common/interfaces/learning-content"; |     import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; | ||||||
|     import type {StudentDTO} from "@dwengo-1/common/interfaces/student"; |     import type { StudentDTO } from "@dwengo-1/common/interfaces/student"; | ||||||
|     import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |     import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||||
|     import {useI18n} from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         submissionData?: SubmissionData, |         submissionData?: SubmissionData; | ||||||
|         submissions: SubmissionDTO[], |         submissions: SubmissionDTO[]; | ||||||
|         hruid: string; |         hruid: string; | ||||||
|         language: Language; |         language: Language; | ||||||
|         version: number, |         version: number; | ||||||
|         group: {forGroup: number, assignmentNo: number, classId: string} |         group: { forGroup: number; assignmentNo: number; classId: string }; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const { |     const { | ||||||
|  | @ -28,7 +28,7 @@ | ||||||
|         isError: submissionFailed, |         isError: submissionFailed, | ||||||
|         error: submissionError, |         error: submissionError, | ||||||
|         isSuccess: submissionSuccess, |         isSuccess: submissionSuccess, | ||||||
|         mutate: submitSolution |         mutate: submitSolution, | ||||||
|     } = useCreateSubmissionMutation(); |     } = useCreateSubmissionMutation(); | ||||||
| 
 | 
 | ||||||
|     const isStudent = computed(() => authService.authState.activeRole === "student"); |     const isStudent = computed(() => authService.authState.activeRole === "student"); | ||||||
|  | @ -37,16 +37,13 @@ | ||||||
|         if (!props.submissionData || props.submissions === undefined) { |         if (!props.submissionData || props.submissions === undefined) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         if (props.submissionData.some(answer => answer === null)) { |         if (props.submissionData.some((answer) => answer === null)) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         if (props.submissions.length === 0) { |         if (props.submissions.length === 0) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         return deepEquals( |         return deepEquals(JSON.parse(props.submissions[props.submissions.length - 1].content), props.submissionData); | ||||||
|             JSON.parse(props.submissions[props.submissions.length - 1].content), |  | ||||||
|             props.submissionData |  | ||||||
|         ); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function submitCurrentAnswer(): void { |     function submitCurrentAnswer(): void { | ||||||
|  | @ -55,25 +52,25 @@ | ||||||
|         const learningObjectIdentifier: LearningObjectIdentifierDTO = { |         const learningObjectIdentifier: LearningObjectIdentifierDTO = { | ||||||
|             hruid: props.hruid, |             hruid: props.hruid, | ||||||
|             language: props.language as Language, |             language: props.language as Language, | ||||||
|             version: props.version |             version: props.version, | ||||||
|         }; |         }; | ||||||
|         const submitter: StudentDTO = { |         const submitter: StudentDTO = { | ||||||
|             id: currentUser.preferred_username!, |             id: currentUser.preferred_username!, | ||||||
|             username: currentUser.preferred_username!, |             username: currentUser.preferred_username!, | ||||||
|             firstName: currentUser.given_name!, |             firstName: currentUser.given_name!, | ||||||
|             lastName: currentUser.family_name! |             lastName: currentUser.family_name!, | ||||||
|         }; |         }; | ||||||
|         const group: GroupDTO = { |         const group: GroupDTO = { | ||||||
|             class: classId, |             class: classId, | ||||||
|             assignment: assignmentNo, |             assignment: assignmentNo, | ||||||
|             groupNumber: forGroup |             groupNumber: forGroup, | ||||||
|         } |         }; | ||||||
|         const submission: SubmissionDTO = { |         const submission: SubmissionDTO = { | ||||||
|             learningObjectIdentifier, |             learningObjectIdentifier, | ||||||
|             submitter, |             submitter, | ||||||
|             group, |             group, | ||||||
|             content: JSON.stringify(props.submissionData) |             content: JSON.stringify(props.submissionData), | ||||||
|         } |         }; | ||||||
|         submitSolution({ data: submission }); |         submitSolution({ data: submission }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -86,17 +83,16 @@ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <v-btn v-if="isStudent && !isSubmitDisabled" |     <v-btn | ||||||
|            prepend-icon="mdi-check" |         v-if="isStudent && !isSubmitDisabled" | ||||||
|            variant="elevated" |         prepend-icon="mdi-check" | ||||||
|            :loading="submissionIsPending" |         variant="elevated" | ||||||
|            :disabled="isSubmitDisabled" |         :loading="submissionIsPending" | ||||||
|            @click="submitCurrentAnswer()" |         :disabled="isSubmitDisabled" | ||||||
|  |         @click="submitCurrentAnswer()" | ||||||
|     > |     > | ||||||
|         {{ buttonText }} |         {{ buttonText }} | ||||||
|     </v-btn> |     </v-btn> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped></style> | ||||||
| 
 |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Lint Action
						Lint Action