Merge remote-tracking branch 'origin/feature/own-learning-objects' into feature/own-learning-objects
This commit is contained in:
		
						commit
						67f8b3e157
					
				
					 7 changed files with 61 additions and 75 deletions
				
			
		|  | @ -97,9 +97,10 @@ async function convertNodes( | ||||||
|                 learningobject_hruid: node.learningObjectHruid, |                 learningobject_hruid: node.learningObjectHruid, | ||||||
|                 version: learningObject.version, |                 version: learningObject.version, | ||||||
|                 transitions: node.transitions |                 transitions: node.transitions | ||||||
|                     .filter((trans) => |                     .filter( | ||||||
|                         !personalizedFor || isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content))  // If we want a personalized learning path, remove all transitions that aren't possible.
 |                         (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
 |                     ) | ||||||
|  |                     .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)), // Then convert all the transition
 | ||||||
|             }; |             }; | ||||||
|         }) |         }) | ||||||
|         .toArray(); |         .toArray(); | ||||||
|  | @ -112,9 +113,8 @@ async function convertNodes( | ||||||
| function optionalJsonStringToObject(jsonString?: string): object | null { | function optionalJsonStringToObject(jsonString?: string): object | null { | ||||||
|     if (!jsonString) { |     if (!jsonString) { | ||||||
|         return null; |         return null; | ||||||
|     } else { |  | ||||||
|         return JSON.parse(jsonString); |  | ||||||
|     } |     } | ||||||
|  |     return JSON.parse(jsonString); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
|  | @ -88,6 +88,6 @@ export function isTransitionPossible(transition: LearningPathTransition, submitt | ||||||
|     if (submitted === null) { |     if (submitted === null) { | ||||||
|         return false; // If the transition is not unconditional and there was no submission, the transition is not possible.
 |         return false; // If the transition is not unconditional and there was no submission, the transition is not possible.
 | ||||||
|     } |     } | ||||||
|     const match = JSONPath({ path: transition.condition, json: {submission: submitted} }) |     const match = JSONPath({ path: transition.condition, json: { submission: submitted } }); | ||||||
|     return match.length === 1; |     return match.length === 1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,7 +5,8 @@ import { LearningPath } from '../../../src/entities/content/learning-path.entity | ||||||
| import { | import { | ||||||
|     getLearningObjectRepository, |     getLearningObjectRepository, | ||||||
|     getLearningPathRepository, |     getLearningPathRepository, | ||||||
|     getStudentRepository, getSubmissionRepository |     getStudentRepository, | ||||||
|  |     getSubmissionRepository, | ||||||
| } from '../../../src/data/repositories'; | } from '../../../src/data/repositories'; | ||||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||||
|  | @ -16,10 +17,10 @@ import learningObjectService from '../../../src/services/learning-objects/learni | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '../../../src/entities/content/language'; | ||||||
| import { | import { | ||||||
|     ConditionTestLearningPathAndLearningObjects, |     ConditionTestLearningPathAndLearningObjects, | ||||||
|     createConditionTestLearningPathAndLearningObjects |     createConditionTestLearningPathAndLearningObjects, | ||||||
| } from "../../test-assets/learning-paths/test-conditions-example"; | } from '../../test-assets/learning-paths/test-conditions-example'; | ||||||
| import {Student} from "../../../src/entities/users/student.entity"; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| import {LearningObjectNode, LearningPathResponse} from "../../../src/interfaces/learning-content"; | import { LearningObjectNode, LearningPathResponse } from '../../../src/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||||
|     const learningObjectRepo = getLearningObjectRepository(); |     const learningObjectRepo = getLearningObjectRepository(); | ||||||
|  | @ -32,9 +33,9 @@ async function initExampleData(): Promise<{ learningObject: LearningObject; lear | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function initPersonalizationTestData(): Promise<{ | async function initPersonalizationTestData(): Promise<{ | ||||||
|     learningContent: ConditionTestLearningPathAndLearningObjects, |     learningContent: ConditionTestLearningPathAndLearningObjects; | ||||||
|     studentA: Student, |     studentA: Student; | ||||||
|     studentB: Student |     studentB: Student; | ||||||
| }> { | }> { | ||||||
|     const studentRepo = getStudentRepository(); |     const studentRepo = getStudentRepository(); | ||||||
|     const submissionRepo = getSubmissionRepository(); |     const submissionRepo = getSubmissionRepository(); | ||||||
|  | @ -49,9 +50,9 @@ async function initPersonalizationTestData(): Promise<{ | ||||||
|     console.log(await getSubmissionRepository().findAll({})); |     console.log(await getSubmissionRepository().findAll({})); | ||||||
| 
 | 
 | ||||||
|     const studentA = studentRepo.create({ |     const studentA = studentRepo.create({ | ||||||
|         username: "student_a", |         username: 'student_a', | ||||||
|         firstName: "Aron", |         firstName: 'Aron', | ||||||
|         lastName: "Student" |         lastName: 'Student', | ||||||
|     }); |     }); | ||||||
|     await studentRepo.save(studentA); |     await studentRepo.save(studentA); | ||||||
|     const submissionA = submissionRepo.create({ |     const submissionA = submissionRepo.create({ | ||||||
|  | @ -61,14 +62,14 @@ async function initPersonalizationTestData(): Promise<{ | ||||||
|         submissionNumber: 0, |         submissionNumber: 0, | ||||||
|         submitter: studentA, |         submitter: studentA, | ||||||
|         submissionTime: new Date(), |         submissionTime: new Date(), | ||||||
|         content: '[0]' |         content: '[0]', | ||||||
|     }); |     }); | ||||||
|     await submissionRepo.save(submissionA); |     await submissionRepo.save(submissionA); | ||||||
| 
 | 
 | ||||||
|     const studentB = studentRepo.create({ |     const studentB = studentRepo.create({ | ||||||
|         username: "student_b", |         username: 'student_b', | ||||||
|         firstName: "Bill", |         firstName: 'Bill', | ||||||
|         lastName: "Student" |         lastName: 'Student', | ||||||
|     }); |     }); | ||||||
|     await studentRepo.save(studentB); |     await studentRepo.save(studentB); | ||||||
|     const submissionB = submissionRepo.create({ |     const submissionB = submissionRepo.create({ | ||||||
|  | @ -78,7 +79,7 @@ async function initPersonalizationTestData(): Promise<{ | ||||||
|         submissionNumber: 1, |         submissionNumber: 1, | ||||||
|         submitter: studentB, |         submitter: studentB, | ||||||
|         submissionTime: new Date(), |         submissionTime: new Date(), | ||||||
|         content: '[1]' |         content: '[1]', | ||||||
|     }); |     }); | ||||||
|     await submissionRepo.save(submissionB); |     await submissionRepo.save(submissionB); | ||||||
| 
 | 
 | ||||||
|  | @ -86,16 +87,19 @@ async function initPersonalizationTestData(): Promise<{ | ||||||
|         learningContent: learningContent, |         learningContent: learningContent, | ||||||
|         studentA: studentA, |         studentA: studentA, | ||||||
|         studentB: studentB, |         studentB: studentB, | ||||||
|     } |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function expectBranchingObjectNode(result: LearningPathResponse, persTestData: { | function expectBranchingObjectNode( | ||||||
|     learningContent: ConditionTestLearningPathAndLearningObjects; |     result: LearningPathResponse, | ||||||
|     studentA: Student; |     persTestData: { | ||||||
|     studentB: Student |         learningContent: ConditionTestLearningPathAndLearningObjects; | ||||||
| }): LearningObjectNode { |         studentA: Student; | ||||||
|     let branchingObjectMatches = result.data![0].nodes.filter( |         studentB: Student; | ||||||
|         it => it.learningobject_hruid === persTestData.learningContent.branchingObject.hruid |     } | ||||||
|  | ): LearningObjectNode { | ||||||
|  |     const branchingObjectMatches = result.data![0].nodes.filter( | ||||||
|  |         (it) => it.learningobject_hruid === persTestData.learningContent.branchingObject.hruid | ||||||
|     ); |     ); | ||||||
|     expect(branchingObjectMatches.length).toBe(1); |     expect(branchingObjectMatches.length).toBe(1); | ||||||
|     return branchingObjectMatches[0]; |     return branchingObjectMatches[0]; | ||||||
|  | @ -104,7 +108,7 @@ function expectBranchingObjectNode(result: LearningPathResponse, persTestData: { | ||||||
| describe('DatabaseLearningPathProvider', () => { | describe('DatabaseLearningPathProvider', () => { | ||||||
|     let learningObjectRepo: LearningObjectRepository; |     let learningObjectRepo: LearningObjectRepository; | ||||||
|     let example: { learningObject: LearningObject; learningPath: LearningPath }; |     let example: { learningObject: LearningObject; learningPath: LearningPath }; | ||||||
|     let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects, studentA: Student, studentB: Student } |     let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects; studentA: Student; studentB: Student }; | ||||||
| 
 | 
 | ||||||
|     beforeAll(async () => { |     beforeAll(async () => { | ||||||
|         await setupTestApp(); |         await setupTestApp(); | ||||||
|  | @ -137,13 +141,13 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
| 
 | 
 | ||||||
|             expectToBeCorrectLearningPath(result.data![0], example.learningPath, learningObjectsOnPath); |             expectToBeCorrectLearningPath(result.data![0], example.learningPath, learningObjectsOnPath); | ||||||
|         }); |         }); | ||||||
|         it("returns the correct personalized learning path", async () => { |         it('returns the correct personalized learning path', async () => { | ||||||
|             // For student A:
 |             // For student A:
 | ||||||
|             let result = await databaseLearningPathProvider.fetchLearningPaths( |             let result = await databaseLearningPathProvider.fetchLearningPaths( | ||||||
|                 [persTestData.learningContent.learningPath.hruid], |                 [persTestData.learningContent.learningPath.hruid], | ||||||
|                 persTestData.learningContent.learningPath.language, |                 persTestData.learningContent.learningPath.language, | ||||||
|                 'the source', |                 'the source', | ||||||
|                 {type: 'student', student: persTestData.studentA} |                 { type: 'student', student: persTestData.studentA } | ||||||
|             ); |             ); | ||||||
|             expect(result.success).toBeTruthy(); |             expect(result.success).toBeTruthy(); | ||||||
|             expect(result.data?.length).toBe(1); |             expect(result.data?.length).toBe(1); | ||||||
|  | @ -151,25 +155,17 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|             // There should be exactly one branching object
 |             // There should be exactly one branching object
 | ||||||
|             let branchingObject = expectBranchingObjectNode(result, persTestData); |             let branchingObject = expectBranchingObjectNode(result, persTestData); | ||||||
| 
 | 
 | ||||||
|             expect( |             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.finalObject.hruid).length).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
 | ||||||
|                 branchingObject |             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.extraExerciseObject.hruid).length).toBe( | ||||||
|                     .transitions |                 1 | ||||||
|                     .filter(it => it.next.hruid === persTestData.learningContent.finalObject.hruid) |             ); // There should however be a path to the extra exercise object.
 | ||||||
|                     .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 === persTestData.learningContent.extraExerciseObject.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( | ||||||
|                 [persTestData.learningContent.learningPath.hruid], |                 [persTestData.learningContent.learningPath.hruid], | ||||||
|                 persTestData.learningContent.learningPath.language, |                 persTestData.learningContent.learningPath.language, | ||||||
|                 'the source', |                 'the source', | ||||||
|                 {type: 'student', student: persTestData.studentB} |                 { type: 'student', student: persTestData.studentB } | ||||||
|             ); |             ); | ||||||
|             expect(result.success).toBeTruthy(); |             expect(result.success).toBeTruthy(); | ||||||
|             expect(result.data?.length).toBe(1); |             expect(result.data?.length).toBe(1); | ||||||
|  | @ -178,18 +174,10 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|             branchingObject = expectBranchingObjectNode(result, persTestData); |             branchingObject = expectBranchingObjectNode(result, persTestData); | ||||||
| 
 | 
 | ||||||
|             // 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 === persTestData.learningContent.finalObject.hruid).length).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
 | ||||||
|                 branchingObject |             expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.extraExerciseObject.hruid).length).toBe( | ||||||
|                     .transitions |                 0 | ||||||
|                     .filter(it => it.next.hruid === persTestData.learningContent.finalObject.hruid) |             ); // There should not be a path anymore to the extra exercise object.
 | ||||||
|                     .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 === persTestData.learningContent.extraExerciseObject.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( | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { LearningObject } from '../../../../src/entities/content/learning-object | ||||||
| import { Language } from '../../../../src/entities/content/language'; | import { Language } from '../../../../src/entities/content/language'; | ||||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||||
| import {EnvVars, getEnvVar} from "../../../../src/util/envvars"; | import { EnvVars, getEnvVar } from '../../../../src/util/envvars'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use |  * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use | ||||||
|  | @ -22,8 +22,8 @@ export function dummyLearningObject(hruid: string, language: Language, title: st | ||||||
|             learningObject.content = Buffer.from('Dummy content'); |             learningObject.content = Buffer.from('Dummy content'); | ||||||
|             learningObject.returnValue = { |             learningObject.returnValue = { | ||||||
|                 callbackUrl: `/learningObject/${hruid}/submissions`, |                 callbackUrl: `/learningObject/${hruid}/submissions`, | ||||||
|                 callbackSchema: "[]" |                 callbackSchema: '[]', | ||||||
|             } |             }; | ||||||
|             return learningObject; |             return learningObject; | ||||||
|         }, |         }, | ||||||
|         createAttachment: {}, |         createAttachment: {}, | ||||||
|  |  | ||||||
|  | @ -16,8 +16,8 @@ const example: LearningObjectExample = { | ||||||
|         learningObject.contentType = DwengoContentType.GIFT; |         learningObject.contentType = DwengoContentType.GIFT; | ||||||
|         learningObject.returnValue = { |         learningObject.returnValue = { | ||||||
|             callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, |             callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, | ||||||
|             callbackSchema: '["antwoord vraag 1"]' |             callbackSchema: '["antwoord vraag 1"]', | ||||||
|         } |         }; | ||||||
|         learningObject.content = loadTestAsset('learning-objects/test-essay/content.txt'); |         learningObject.content = loadTestAsset('learning-objects/test-essay/content.txt'); | ||||||
|         return learningObject; |         return learningObject; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -16,8 +16,8 @@ const example: LearningObjectExample = { | ||||||
|         learningObject.contentType = DwengoContentType.GIFT; |         learningObject.contentType = DwengoContentType.GIFT; | ||||||
|         learningObject.returnValue = { |         learningObject.returnValue = { | ||||||
|             callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, |             callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, | ||||||
|             callbackSchema: '["antwoord vraag 1"]' |             callbackSchema: '["antwoord vraag 1"]', | ||||||
|         } |         }; | ||||||
|         learningObject.content = loadTestAsset('learning-objects/test-multiple-choice/content.txt'); |         learningObject.content = loadTestAsset('learning-objects/test-multiple-choice/content.txt'); | ||||||
|         return learningObject; |         return learningObject; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -3,17 +3,17 @@ import { Language } from '../../../src/entities/content/language'; | ||||||
| import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example'; | import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example'; | ||||||
| import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example'; | import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example'; | ||||||
| import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; | import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; | ||||||
| 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 type ConditionTestLearningPathAndLearningObjects = { | export type ConditionTestLearningPathAndLearningObjects = { | ||||||
|     branchingObject: LearningObject, |     branchingObject: LearningObject; | ||||||
|     extraExerciseObject: LearningObject, |     extraExerciseObject: LearningObject; | ||||||
|     finalObject: LearningObject, |     finalObject: LearningObject; | ||||||
|     learningPath: LearningPath |     learningPath: LearningPath; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function createConditionTestLearningPathAndLearningObjects(){ | export function createConditionTestLearningPathAndLearningObjects() { | ||||||
|     const learningPath = new LearningPath(); |     const learningPath = new LearningPath(); | ||||||
|     learningPath.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_conditions`; |     learningPath.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_conditions`; | ||||||
|     learningPath.language = Language.English; |     learningPath.language = Language.English; | ||||||
|  | @ -80,7 +80,5 @@ export function createConditionTestLearningPathAndLearningObjects(){ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const example: LearningPathExample = { | const example: LearningPathExample = { | ||||||
|     createLearningPath: () => { |     createLearningPath: () => createConditionTestLearningPathAndLearningObjects().learningPath, | ||||||
|         return createConditionTestLearningPathAndLearningObjects().learningPath; |  | ||||||
|     }, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger