From 241854a7cc8407f88cc436e45547ab98541f1525 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Thu, 13 Mar 2025 16:49:39 +0100 Subject: [PATCH 1/9] chore: lint-action enkel uitvoeren op PRs die klaar zijn voor review --- .github/workflows/lint-action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml index e0f24ba9..b136a850 100644 --- a/.github/workflows/lint-action.yml +++ b/.github/workflows/lint-action.yml @@ -11,6 +11,8 @@ on: pull_request: branches: - dev + types: [ready_for_review] + # Down scope as necessary via https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: From 01b3fab5682131e74dc51cb584d835e6edd0b203 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Mon, 17 Mar 2025 23:11:01 +0100 Subject: [PATCH 2/9] refactor(backend): DatabaseLearningPathProvider (& gerelateerde) overzichtelijker gemaakt. --- .../database-learning-path-provider.ts | 82 ++++-- .../database-learning-path-provider.test.ts | 3 - package-lock.json | 272 ++++++++++++++++-- package.json | 1 + 4 files changed, 298 insertions(+), 60 deletions(-) diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index 68986885..53c005fd 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -39,14 +39,22 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise { + // Fetch the corresponding learning object for each node since some parts of the expected response contains parts + // with information which is not available in the LearningPathNodes themselves. const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); - 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 { @@ -67,34 +75,55 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb }; } +/** + * 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 convertNode( + node: LearningPathNode, + learningObject: FilteredLearningObject, + personalizedFor: PersonalizationTarget | undefined, + nodesToLearningObjects: Map +): Promise { + 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, + start_node: node.startNode, + created_at: node.createdAt.toISOString(), + updatedAt: node.updatedAt.toISOString(), + learningobject_hruid: node.learningObjectHruid, + version: learningObject.version, + 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 - * @param personalizedFor + * + * @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, personalizedFor?: PersonalizationTarget ): Promise { - const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => { - const [node, learningObject] = entry; - const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; - return { - _id: learningObject.uuid, - language: learningObject.language, - start_node: node.startNode, - created_at: node.createdAt.toISOString(), - 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 - }; - }); + const nodesPromise = Array.from(nodesToLearningObjects.entries()) + .map(entry => convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects)); return await Promise.all(nodesPromise); } @@ -112,9 +141,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, diff --git a/backend/tests/services/learning-path/database-learning-path-provider.test.ts b/backend/tests/services/learning-path/database-learning-path-provider.test.ts index 7c7ecdae..fbbf8c7f 100644 --- a/backend/tests/services/learning-path/database-learning-path-provider.test.ts +++ b/backend/tests/services/learning-path/database-learning-path-provider.test.ts @@ -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 { @@ -106,7 +105,6 @@ function expectBranchingObjectNode( } describe('DatabaseLearningPathProvider', () => { - let learningObjectRepo: LearningObjectRepository; let example: { learningObject: LearningObject; learningPath: LearningPath }; let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects; studentA: Student; studentB: Student }; @@ -114,7 +112,6 @@ describe('DatabaseLearningPathProvider', () => { await setupTestApp(); example = await initExampleData(); persTestData = await initPersonalizationTestData(); - learningObjectRepo = getLearningObjectRepository(); }); describe('fetchLearningPaths', () => { diff --git a/package-lock.json b/package-lock.json index 0844e7b1..577325ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dwengo-1-monorepo", - "version": "0.0.1", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dwengo-1-monorepo", - "version": "0.0.1", + "version": "0.1.1", "license": "MIT", "workspaces": [ "backend", @@ -19,6 +19,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", @@ -27,7 +28,7 @@ }, "backend": { "name": "dwengo-1-backend", - "version": "0.0.1", + "version": "0.1.1", "dependencies": { "@mikro-orm/core": "6.4.9", "@mikro-orm/knex": "6.4.9", @@ -89,7 +90,7 @@ }, "frontend": { "name": "dwengo-1-frontend", - "version": "0.0.1", + "version": "0.1.1", "dependencies": { "axios": "^1.8.2", "oidc-client-ts": "^3.1.0", @@ -597,6 +598,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -1018,6 +1029,16 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jercle/yargonaut": { "version": "1.1.5", "dev": true, @@ -1612,7 +1633,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.34.8", @@ -1624,7 +1646,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@scarf/scarf": { "version": "1.4.0", @@ -2046,6 +2069,39 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/coverage-v8": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.8.tgz", + "integrity": "sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.0.8", + "vitest": "3.0.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/eslint-plugin": { "version": "1.1.31", "dev": true, @@ -2066,12 +2122,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.6", - "@vitest/utils": "3.0.6", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2080,11 +2138,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.6", + "@vitest/spy": "3.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2106,6 +2166,8 @@ }, "node_modules/@vitest/mocker/node_modules/estree-walker": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -2113,7 +2175,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -2124,11 +2188,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.6", + "@vitest/utils": "3.0.8", "pathe": "^2.0.3" }, "funding": { @@ -2136,11 +2202,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.6", + "@vitest/pretty-format": "3.0.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2149,7 +2217,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2160,11 +2230,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.6", + "@vitest/pretty-format": "3.0.8", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -2635,6 +2707,8 @@ }, "node_modules/assertion-error": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -2860,6 +2934,8 @@ }, "node_modules/cac": { "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -3014,6 +3090,8 @@ }, "node_modules/chai": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, "license": "MIT", "dependencies": { @@ -3044,6 +3122,8 @@ }, "node_modules/check-error": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", "engines": { @@ -3419,6 +3499,8 @@ }, "node_modules/deep-eql": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -3722,6 +3804,8 @@ }, "node_modules/es-module-lexer": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -4854,6 +4938,13 @@ "node": ">=18" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-tags": { "version": "3.3.1", "dev": true, @@ -5205,6 +5296,71 @@ "node": ">=18" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "dev": true, @@ -5700,6 +5856,8 @@ }, "node_modules/loupe": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, "license": "MIT" }, @@ -5739,6 +5897,34 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "dev": true, @@ -6697,6 +6883,8 @@ }, "node_modules/pathval": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "license": "MIT", "engines": { @@ -8263,6 +8451,21 @@ "node": ">=8.0.0" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -8303,6 +8506,8 @@ }, "node_modules/tinyspy": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -8753,7 +8958,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", "dev": true, "license": "MIT", "dependencies": { @@ -8853,6 +9060,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -8897,17 +9105,19 @@ } }, "node_modules/vitest": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.6", - "@vitest/mocker": "3.0.6", - "@vitest/pretty-format": "^3.0.6", - "@vitest/runner": "3.0.6", - "@vitest/snapshot": "3.0.6", - "@vitest/spy": "3.0.6", - "@vitest/utils": "3.0.6", + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -8919,7 +9129,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.6", + "vite-node": "3.0.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -8935,8 +9145,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.6", - "@vitest/ui": "3.0.6", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 9a69bdb6..7adeb3aa 100644 --- a/package.json +++ b/package.json @@ -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", From 4cc60098b845384c42cde4ed7fe266d42b49d57d Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Tue, 18 Mar 2025 00:10:12 +0100 Subject: [PATCH 3/9] fix(backend): Falende test gerepareerd --- backend/src/entities/assignments/submission.entity.ts | 2 +- .../learning-path/database-learning-path-provider.test.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/entities/assignments/submission.entity.ts b/backend/src/entities/assignments/submission.entity.ts index f008c8c2..fbaa2791 100644 --- a/backend/src/entities/assignments/submission.entity.ts +++ b/backend/src/entities/assignments/submission.entity.ts @@ -19,7 +19,7 @@ export class Submission { learningObjectVersion: number = 1; @PrimaryKey({ type: 'integer', autoincrement: true }) - submissionNumber!: number; + submissionNumber?: number; @ManyToOne({ entity: () => Student, diff --git a/backend/tests/services/learning-path/database-learning-path-provider.test.ts b/backend/tests/services/learning-path/database-learning-path-provider.test.ts index fbbf8c7f..04782df3 100644 --- a/backend/tests/services/learning-path/database-learning-path-provider.test.ts +++ b/backend/tests/services/learning-path/database-learning-path-provider.test.ts @@ -58,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]', @@ -75,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]', From d6d382ef74b010532aacd18893f9efdba190b340 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 17 Mar 2025 23:20:20 +0000 Subject: [PATCH 4/9] style: fix linting issues met ESLint --- .../services/learning-paths/database-learning-path-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index 53c005fd..3fea8796 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -40,7 +40,7 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise { // Fetch the corresponding learning object for each node since some parts of the expected response contains parts - // with information which is not available in the LearningPathNodes themselves. + // With information which is not available in the LearningPathNodes themselves. const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); // The target ages of a learning path are the union of the target ages of all learning objects. From f0938f2e7aa9aab9fa44a3a9fd71017bc0799211 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 17 Mar 2025 23:20:25 +0000 Subject: [PATCH 5/9] style: fix linting issues met Prettier --- .../database-learning-path-provider.ts | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index 3fea8796..bbcb6485 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -44,13 +44,9 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); // 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 || []) - )]; + 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 keywords = [...new Set(Array.from(nodesToLearningObjects.values()).flatMap((it) => it.keywords || []))]; const image = learningPath.image ? learningPath.image.toString('base64') : undefined; @@ -94,11 +90,10 @@ async function convertNode( 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) + !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, @@ -107,7 +102,7 @@ async function convertNode( updatedAt: node.updatedAt.toISOString(), learningobject_hruid: node.learningObjectHruid, version: learningObject.version, - transitions + transitions, }; } @@ -122,8 +117,9 @@ async function convertNodes( nodesToLearningObjects: Map, personalizedFor?: PersonalizationTarget ): Promise { - const nodesPromise = Array.from(nodesToLearningObjects.entries()) - .map(entry => convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects)); + const nodesPromise = Array.from(nodesToLearningObjects.entries()).map((entry) => + convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) + ); return await Promise.all(nodesPromise); } From 4507f908e2f1e40eaee69e958851a0ac59f306a1 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 09:36:04 +0100 Subject: [PATCH 6/9] actions: deployment workflow gemaakt --- .github/workflows/deployment.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/deployment.yml diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml new file mode 100644 index 00000000..674398ca --- /dev/null +++ b/.github/workflows/deployment.yml @@ -0,0 +1,25 @@ +name: Deployment + +on: + push: + branches: + - main + pull_request: + branches: + - main + types: + - closed + +jobs: + docker: + name: Deploy with docker + if: github.event.pull_request.merged == true + runs-on: [self-hosted, Linux, X64] + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Start docker + run: docker compose -f compose.yml -f compose.override.yml up --build -d + \ No newline at end of file From 3e4e786d914761f456c516307bee5b3ce0807ff3 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 09:40:24 +0100 Subject: [PATCH 7/9] actions: deployment workflow update pull_request closed niet nodig --- .github/workflows/deployment.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 674398ca..090c56fa 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -4,16 +4,10 @@ on: push: branches: - main - pull_request: - branches: - - main - types: - - closed jobs: docker: name: Deploy with docker - if: github.event.pull_request.merged == true runs-on: [self-hosted, Linux, X64] steps: - From efb3aa5512cdd62c1f9267e687090fdd64cf27d8 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 09:56:41 +0100 Subject: [PATCH 8/9] actions: update deployment workflow compose.override.yml verandert naar compose.prod.yml --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 090c56fa..865f4524 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -15,5 +15,5 @@ jobs: uses: actions/checkout@v4 - name: Start docker - run: docker compose -f compose.yml -f compose.override.yml up --build -d + run: docker compose -f compose.yml -f compose.prod.yml up --build -d \ No newline at end of file From 50adbda3528c1a321871a04f4d4e60e67bd99c67 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 19:24:02 +0100 Subject: [PATCH 9/9] chore: sync lint-action workflow Dit bestand is nu hetzelfde als op de branch voor de testing workflow om merge conflicts te vermijden Komt door mijn eigen slechte branching... --- .github/workflows/lint-action.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml index b136a850..32823417 100644 --- a/.github/workflows/lint-action.yml +++ b/.github/workflows/lint-action.yml @@ -11,7 +11,7 @@ on: pull_request: branches: - dev - types: [ready_for_review] + 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 @@ -22,6 +22,7 @@ permissions: jobs: run-linters: name: Run linters + if: '! github.event.pull_request.draft' runs-on: [self-hosted, Linux, X64] steps: @@ -44,4 +45,4 @@ jobs: eslint: true eslint_args: '--config eslint.config.ts' prettier: true - commit_message: 'style: fix linting issues met ${linter}' + commit_message: 'style: fix linting issues met ${linter}' \ No newline at end of file