diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs index e9f1f2e2..581abdaf 100644 --- a/client/.eslintrc.cjs +++ b/client/.eslintrc.cjs @@ -13,7 +13,6 @@ module.exports = { 'vue/setup-compiler-macros': true }, 'rules': { - 'quotes': ['error', 'single'], '@typescript-eslint/no-unused-vars': ['warn'], } } diff --git a/client/src/services/__tests__/circle.spec.ts b/client/src/services/__tests__/circle.spec.ts new file mode 100644 index 00000000..adca77ec --- /dev/null +++ b/client/src/services/__tests__/circle.spec.ts @@ -0,0 +1,126 @@ +import {describe, it} from 'vitest' +import type {Circle} from '../circle'; +import {parseLearningSequences} from '../circle'; + +describe('circleService.parseLearningSequences', () => { + it('can parse learning sequences from api response', () => { + const input = { + "id": 10, + "title": "Analyse", + "slug": "analyse", + "type": "learnpath.Circle", + "translation_key": "c9832aaf-02b2-47af-baeb-bde60d8ec1f5", + "children": [ + { + "id": 18, + "title": "Anwenden", + "slug": "anwenden", + "type": "learnpath.LearningSequence", + "translation_key": "2e4c431a-9602-4398-ad18-20dd4bb189fa", + "icon": "it-icon-ls-apply" + }, + { + "id": 19, + "title": "Prämien einsparen", + "slug": "pramien-einsparen", + "type": "learnpath.LearningUnit", + "translation_key": "75c1f31a-ae25-4d9c-9206-a4e7fdae8c13", + "questions": [] + }, + { + "id": 20, + "title": "Versicherungsbedarf für Familien", + "slug": "versicherungsbedarf-für-familien", + "type": "learnpath.LearningContent", + "translation_key": "2a422da3-a3ad-468a-831e-9141c122ffef", + "minutes": 60, + "contents": [ + { + "type": "exercise", + "value": { + "description": "Beispiel Aufgabe" + }, + "id": "ee0bcef7-702b-42f3-a891-88a0332fce6f" + } + ] + }, + { + "id": 21, + "title": "Alles klar?", + "slug": "alles-klar", + "type": "learnpath.LearningContent", + "translation_key": "7dc9d96d-07f9-4b9f-bec1-43ba67cf9010", + "minutes": 60, + "contents": [ + { + "type": "exercise", + "value": { + "description": "Beispiel Aufgabe" + }, + "id": "a556ebb2-f902-4d78-9b76-38b7933118b8" + } + ] + }, + { + "id": 22, + "title": "Sich selbständig machen", + "slug": "sich-selbstandig-machen", + "type": "learnpath.LearningUnit", + "translation_key": "c40d5266-3c94-4b9b-8469-9ac6b32a6231", + "questions": [] + }, + { + "id": 23, + "title": "GmbH oder AG", + "slug": "gmbh-oder-ag", + "type": "learnpath.LearningContent", + "translation_key": "59331843-9f52-4b41-9cd1-2293a8d90064", + "minutes": 120, + "contents": [ + { + "type": "video", + "value": { + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam", + "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + "id": "a4974834-f404-4fb8-af94-a24c6db56bb8" + } + ] + }, + { + "id": 24, + "title": "Tiertherapie Patrizia Feller", + "slug": "tiertherapie-patrizia-feller", + "type": "learnpath.LearningContent", + "translation_key": "13f6d661-1d10-4b59-b8e5-01fcec47a38f", + "minutes": 120, + "contents": [ + { + "type": "exercise", + "value": { + "description": "Beispiel Aufgabe" + }, + "id": "5947c947-8656-44b5-826c-1787057c2df2" + } + ] + }, + { + "id": 25, + "title": "Auto verkaufen", + "slug": "auto-verkaufen", + "type": "learnpath.LearningUnit", + "translation_key": "3b42e514-0bbe-4c23-9c88-3f5263e47cf9", + "questions": [] + }, + ], + "description": "Nach dem Gespräch werten sie die Analyse aus...", + "job_situations": [], + "goals": [], + "experts": [] + } as Circle; + + const learningSequences = parseLearningSequences(input.children); + + expect(learningSequences.length).toBe(4); + }) +}) diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts new file mode 100644 index 00000000..9ef19e63 --- /dev/null +++ b/client/src/services/circle.ts @@ -0,0 +1,134 @@ +export interface LearningContentBlock { + type: 'video' | 'web-based-training' | 'podcast' | 'competence' | 'exercise' | 'document' | 'knowledge'; + value: { + description: string; + url?: string; + }, + id: string; +} + + +export interface CircleGoal { + type: 'goal'; + value: string; + id: string; +} + + +export interface CircleJobSituation { + type: 'job_situation'; + value: string; + id: string; +} + + +export interface LearningWagtailPage { + id: number; + title: string; + slug: string; + translation_key: string; +} + + +export interface LearningContent extends LearningWagtailPage { + type: 'learnpath.LearningContent'; + minutes: number; + contents: LearningContentBlock[]; +} + + +export interface LearningUnit extends LearningWagtailPage { + type: 'learnpath.LearningUnit'; + questions: []; + learningContents: LearningContent[]; + minutes: number; +} + + +export interface LearningSequence extends LearningWagtailPage { + type: 'learnpath.LearningSequence'; + icon: string; + learningUnits: LearningUnit[]; + minutes: number; +} + + +type CircleChild = LearningContent | LearningUnit | LearningSequence; + +export interface Circle extends LearningWagtailPage { + type: 'learnpath.Circle'; + children: CircleChild[]; + description: string; +} + +function createEmptyLearningUnit(): LearningUnit { + return { + id: -1, + title: '', + slug: '', + translation_key: '', + type: 'learnpath.LearningUnit', + questions: [], + learningContents: [], + minutes: 0, + }; +} + +export function parseLearningSequences (children: CircleChild[]): LearningSequence[] { + let learningSequence:LearningSequence|null = null; + let learningUnit:LearningUnit|null = null; + const result:LearningSequence[] = []; + + children.forEach((child) => { + // FIXME add error detection if the data does not conform to expectations + if (child.type === 'learnpath.LearningSequence') { + if (learningSequence) { + if (learningUnit) { + learningSequence.learningUnits.push(learningUnit); + } + result.push(learningSequence); + } + learningSequence = Object.assign(child, {learningUnits: []}); + + // initialize empty learning unit if there will not come a learning unit next + learningUnit = createEmptyLearningUnit(); + } else if (child.type === 'learnpath.LearningUnit') { + if (learningSequence === null) { + throw new Error('LearningUnit found before LearningSequence'); + } + + if (learningUnit && learningUnit.learningContents.length) { + learningSequence.learningUnits.push(learningUnit); + } + + learningUnit = Object.assign(child, {learningContents: []}); + } else if (child.type === 'learnpath.LearningContent') { + if (learningUnit === null) { + throw new Error('LearningContent found before LearningUnit'); + } + + learningUnit.learningContents.push(child); + } + }); + + if (learningUnit && learningSequence) { + (learningSequence as LearningSequence).learningUnits.push(learningUnit); + result.push(learningSequence); + } else { + throw new Error('Finished with LearningContent but there is no LearningSequence and LearningUnit'); + } + + // sum minutes + result.forEach((learningSequence) => { + learningSequence.minutes = 0; + learningSequence.learningUnits.forEach((learningUnit) => { + learningUnit.minutes = 0; + learningUnit.learningContents.forEach((learningContent) => { + learningUnit.minutes += learningContent.minutes; + }); + learningSequence.minutes += learningUnit.minutes; + }); + }); + + return result; +} diff --git a/client/src/views/CircleView.vue b/client/src/views/CircleView.vue index 3bf1ad21..9e1ea236 100644 --- a/client/src/views/CircleView.vue +++ b/client/src/views/CircleView.vue @@ -4,6 +4,7 @@ import * as log from 'loglevel'; import MainNavigationBar from '../components/MainNavigationBar.vue'; import LearningSequence from '../components/circle/LearningSequence.vue'; import { itGet, itPost } from '../fetchHelpers'; +import { parseLearningSequences } from '../services/circle'; export default { components: { LearningSequence, MainNavigationBar }, @@ -29,56 +30,13 @@ export default { this.completionData = data; }); }, - createLearningSequences(circleData) { - // aggregate wagtail data into LearningSequence > LearningUnit > LearningContent hierarchy - let learningSequence = null; - let learningUnit = null; - circleData.children.forEach((child) => { - // FIXME add error detection if the data does not conform to expectations - if(child.type === 'learnpath.LearningSequence') { - if(learningSequence) { - if(learningUnit) { - learningSequence.learningUnits.push(learningUnit); - } - this.learningSequences.push(learningSequence); - } - learningSequence = Object.assign({}, child, { learningUnits: [] }); - learningUnit = { id: null, title: '', learningContents: [] }; - } else if(child.type === 'learnpath.LearningUnit') { - if(learningUnit && learningUnit.learningContents.length) { - learningSequence.learningUnits.push(learningUnit); - } - learningUnit = Object.assign({}, child, { learningContents: [] }); - } else { - learningUnit.learningContents.push(child); - } - }); - - if(learningUnit) { - learningSequence.learningUnits.push(learningUnit); - } - this.learningSequences.push(learningSequence); - - // sum minutes - this.learningSequences.forEach((learningSequence) => { - learningSequence.minutes = 0; - learningSequence.learningUnits.forEach((learningUnit) => { - learningUnit.minutes = 0; - learningUnit.learningContents.forEach((learningContent) => { - learningUnit.minutes += learningContent.minutes; - }); - learningSequence.minutes += learningUnit.minutes; - }); - }); - - log.debug(this.learningSequences); - }, }, mounted() { log.debug('CircleView mounted', this.circleSlug); itGet(`/learnpath/api/circle/${this.circleSlug}/`).then((data) => { this.circleData = data; - this.createLearningSequences(data); + this.learningSequences = parseLearningSequences(this.circleData.children); + log.debug(this.learningSequences); itGet(`/api/completion/circle/${this.circleData.translation_key}/`).then((completionData) => { this.completionData = completionData;