Merge remote-tracking branch 'origin/dev' into feat/user-homepage
# Conflicts: # package-lock.json
This commit is contained in:
		
						commit
						4698311062
					
				
					 7 changed files with 674 additions and 2076 deletions
				
			
		
							
								
								
									
										19
									
								
								.github/workflows/deployment.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/deployment.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| name: Deployment | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|         - main | ||||
| 
 | ||||
| jobs: | ||||
|   docker: | ||||
|     name: Deploy with docker | ||||
|     runs-on: [self-hosted, Linux, X64] | ||||
|     steps: | ||||
|       - | ||||
|         name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - | ||||
|         name: Start docker | ||||
|         run: docker compose -f compose.yml -f compose.prod.yml up --build -d | ||||
|          | ||||
							
								
								
									
										3
									
								
								.github/workflows/lint-action.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/lint-action.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -11,6 +11,8 @@ on: | |||
|     pull_request: | ||||
|         branches: | ||||
|             - dev | ||||
|         types: ["synchronize", "ready_for_review", "opened", "reopened"] | ||||
| 
 | ||||
| 
 | ||||
| # Down scope as necessary via https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token | ||||
| permissions: | ||||
|  | @ -20,6 +22,7 @@ permissions: | |||
| jobs: | ||||
|     run-linters: | ||||
|         name: Run linters | ||||
|         if: '! github.event.pull_request.draft' | ||||
|         runs-on: [self-hosted, Linux, X64] | ||||
| 
 | ||||
|         steps: | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ export class Submission { | |||
|     learningObjectVersion: number = 1; | ||||
| 
 | ||||
|     @PrimaryKey({ type: 'integer', autoincrement: true }) | ||||
|     submissionNumber!: number; | ||||
|     submissionNumber?: number; | ||||
| 
 | ||||
|     @ManyToOne({ | ||||
|         entity: () => Student, | ||||
|  |  | |||
|  | @ -39,14 +39,18 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Ma | |||
|  * Convert the given learning path entity to an object which conforms to the learning path content. | ||||
|  */ | ||||
| async function convertLearningPath(learningPath: LearningPathEntity, order: number, personalizedFor?: PersonalizationTarget): Promise<LearningPath> { | ||||
|     // Fetch the corresponding learning object for each node since some parts of the expected response contains parts
 | ||||
|     // With information which is not available in the LearningPathNodes themselves.
 | ||||
|     const nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> = await getLearningObjectsForNodes(learningPath.nodes); | ||||
| 
 | ||||
|     const targetAges = Array.from(nodesToLearningObjects.values()).flatMap((it) => it.targetAges || []); | ||||
| 
 | ||||
|     const keywords = Array.from(nodesToLearningObjects.values()).flatMap((it) => it.keywords || []); | ||||
|     // The target ages of a learning path are the union of the target ages of all learning objects.
 | ||||
|     const targetAges = [...new Set(Array.from(nodesToLearningObjects.values()).flatMap((it) => it.targetAges || []))]; | ||||
|     // The keywords of the learning path consist of the union of the keywords of all learning objects.
 | ||||
|     const keywords = [...new Set(Array.from(nodesToLearningObjects.values()).flatMap((it) => it.keywords || []))]; | ||||
| 
 | ||||
|     const image = learningPath.image ? learningPath.image.toString('base64') : undefined; | ||||
| 
 | ||||
|     // Convert the learning object notes as retrieved from the database into the expected response format-
 | ||||
|     const convertedNodes = await convertNodes(nodesToLearningObjects, personalizedFor); | ||||
| 
 | ||||
|     return { | ||||
|  | @ -68,18 +72,28 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb | |||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Helper function converting pairs of learning path nodes (as represented in the database) and the corresponding | ||||
|  * learning objects into a list of learning path nodes as they should be represented in the API. | ||||
|  * @param nodesToLearningObjects | ||||
|  * @param personalizedFor | ||||
|  * Helper function converting a single learning path node (as represented in the database) and the corresponding | ||||
|  * learning object into a learning path node as it should be represented in the API. | ||||
|  * | ||||
|  * @param node Learning path node as represented in the database. | ||||
|  * @param learningObject Learning object the learning path node refers to. | ||||
|  * @param personalizedFor Personalization target if a personalized learning path is desired. | ||||
|  * @param nodesToLearningObjects Mapping from learning path nodes to the corresponding learning objects. | ||||
|  */ | ||||
| async function convertNodes( | ||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, | ||||
|     personalizedFor?: PersonalizationTarget | ||||
| ): Promise<LearningObjectNode[]> { | ||||
|     const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => { | ||||
|         const [node, learningObject] = entry; | ||||
| async function convertNode( | ||||
|     node: LearningPathNode, | ||||
|     learningObject: FilteredLearningObject, | ||||
|     personalizedFor: PersonalizationTarget | undefined, | ||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> | ||||
| ): Promise<LearningObjectNode> { | ||||
|     const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; | ||||
|     const transitions = node.transitions | ||||
|         .filter( | ||||
|             (trans) => | ||||
|                 !personalizedFor || // If we do not want a personalized learning path, keep all transitions
 | ||||
|                 isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // Otherwise remove all transitions that aren't possible.
 | ||||
|         ) | ||||
|         .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)); | ||||
|     return { | ||||
|         _id: learningObject.uuid, | ||||
|         language: learningObject.language, | ||||
|  | @ -88,13 +102,24 @@ async function convertNodes( | |||
|         updatedAt: node.updatedAt.toISOString(), | ||||
|         learningobject_hruid: node.learningObjectHruid, | ||||
|         version: learningObject.version, | ||||
|             transitions: node.transitions | ||||
|                 .filter( | ||||
|                     (trans) => !personalizedFor || isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // If we want a personalized learning path, remove all transitions that aren't possible.
 | ||||
|                 ) | ||||
|                 .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)), // Then convert all the transition
 | ||||
|         transitions, | ||||
|     }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Helper function converting pairs of learning path nodes (as represented in the database) and the corresponding | ||||
|  * learning objects into a list of learning path nodes as they should be represented in the API. | ||||
|  * | ||||
|  * @param nodesToLearningObjects Mapping from learning path nodes to the corresponding learning objects. | ||||
|  * @param personalizedFor Personalization target if a personalized learning path is desired. | ||||
|  */ | ||||
| async function convertNodes( | ||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, | ||||
|     personalizedFor?: PersonalizationTarget | ||||
| ): Promise<LearningObjectNode[]> { | ||||
|     const nodesPromise = Array.from(nodesToLearningObjects.entries()).map((entry) => | ||||
|         convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) | ||||
|     ); | ||||
|     return await Promise.all(nodesPromise); | ||||
| } | ||||
| 
 | ||||
|  | @ -112,9 +137,10 @@ function optionalJsonStringToObject(jsonString?: string): object | null { | |||
|  * Helper function which converts a transition in the database representation to a transition in the representation | ||||
|  * the Dwengo API uses. | ||||
|  * | ||||
|  * @param transition | ||||
|  * @param index | ||||
|  * @param nodesToLearningObjects | ||||
|  * @param transition The transition to convert | ||||
|  * @param index The sequence number of the transition to convert | ||||
|  * @param nodesToLearningObjects Map which maps each learning path node of the current learning path to the learning | ||||
|  *                               object it refers to. | ||||
|  */ | ||||
| function convertTransition( | ||||
|     transition: LearningPathTransition, | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ import learningObjectExample from '../../test-assets/learning-objects/pn-werking | |||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example.js'; | ||||
| import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js'; | ||||
| import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js'; | ||||
| import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; | ||||
| import learningObjectService from '../../../src/services/learning-objects/learning-object-service.js'; | ||||
| import { Language } from '../../../src/entities/content/language.js'; | ||||
| import { | ||||
|  | @ -59,7 +58,6 @@ async function initPersonalizationTestData(): Promise<{ | |||
|         learningObjectHruid: learningContent.branchingObject.hruid, | ||||
|         learningObjectLanguage: learningContent.branchingObject.language, | ||||
|         learningObjectVersion: learningContent.branchingObject.version, | ||||
|         submissionNumber: 0, | ||||
|         submitter: studentA, | ||||
|         submissionTime: new Date(), | ||||
|         content: '[0]', | ||||
|  | @ -76,7 +74,6 @@ async function initPersonalizationTestData(): Promise<{ | |||
|         learningObjectHruid: learningContent.branchingObject.hruid, | ||||
|         learningObjectLanguage: learningContent.branchingObject.language, | ||||
|         learningObjectVersion: learningContent.branchingObject.version, | ||||
|         submissionNumber: 1, | ||||
|         submitter: studentB, | ||||
|         submissionTime: new Date(), | ||||
|         content: '[1]', | ||||
|  | @ -106,7 +103,6 @@ function expectBranchingObjectNode( | |||
| } | ||||
| 
 | ||||
| describe('DatabaseLearningPathProvider', () => { | ||||
|     let learningObjectRepo: LearningObjectRepository; | ||||
|     let example: { learningObject: LearningObject; learningPath: LearningPath }; | ||||
|     let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects; studentA: Student; studentB: Student }; | ||||
| 
 | ||||
|  | @ -114,7 +110,6 @@ describe('DatabaseLearningPathProvider', () => { | |||
|         await setupTestApp(); | ||||
|         example = await initExampleData(); | ||||
|         persTestData = await initPersonalizationTestData(); | ||||
|         learningObjectRepo = getLearningObjectRepository(); | ||||
|     }); | ||||
| 
 | ||||
|     describe('fetchLearningPaths', () => { | ||||
|  |  | |||
							
								
								
									
										2640
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2640
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -31,6 +31,7 @@ | |||
|         "@types/eslint-config-prettier": "^6.11.3", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.24.1", | ||||
|         "@typescript-eslint/parser": "^8.24.1", | ||||
|         "@vitest/coverage-v8": "^3.0.8", | ||||
|         "eslint": "^9.20.1", | ||||
|         "eslint-config-prettier": "^10.0.1", | ||||
|         "jiti": "^2.4.2", | ||||
|  |  | |||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl