From e4c9a3ef4479c334ac10771b23266d0165d70daa Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Thu, 21 Mar 2024 11:20:52 +0100 Subject: [PATCH 1/7] Sanitize worksheet titles --- server/vbv_lernwelt/feedback/services.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/server/vbv_lernwelt/feedback/services.py b/server/vbv_lernwelt/feedback/services.py index cd597e4e..b593992a 100644 --- a/server/vbv_lernwelt/feedback/services.py +++ b/server/vbv_lernwelt/feedback/services.py @@ -185,7 +185,7 @@ def export_feedback(course_session_ids: list[str], save_as_file: bool): def _create_sheet(wb: Workbook, title: str, data: list[FeedbackResponse]): - sheet = wb.create_sheet(title=title) + sheet = wb.create_sheet(title=_sanitize_sheet_name(title)) if len(data) == 0: return sheet @@ -208,6 +208,24 @@ def _create_sheet(wb: Workbook, title: str, data: list[FeedbackResponse]): return sheet +def _sanitize_sheet_name(text, default_name="DefaultSheet"): + if text is None: + return default_name + + prohibited_chars = ["\\", "/", "*", "?", ":", "[", "]"] + for char in prohibited_chars: + text = text.replace(char, "") + + text = text.strip("'") + + text = text[:31] + + if len(text) == 0: + return default_name + + return text + + def _add_rows(sheet, data, question_data): for row_idx, feedback in enumerate(data, start=2): sheet.cell(row=row_idx, column=1, value=feedback.course_session.title) From 3573a6f54b91331846a239be28b1fd7ed90f38ce Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 21 Mar 2024 14:03:57 +0100 Subject: [PATCH 2/7] fix: re-define completed for self-evaluation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit we define completed as at-least one evaluation was clicked.  --- .../src/pages/learningPath/circlePage/LearningSequence.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/pages/learningPath/circlePage/LearningSequence.vue b/client/src/pages/learningPath/circlePage/LearningSequence.vue index 9862868c..7a2ae090 100644 --- a/client/src/pages/learningPath/circlePage/LearningSequence.vue +++ b/client/src/pages/learningPath/circlePage/LearningSequence.vue @@ -41,7 +41,6 @@ const someFinished = computed(() => { if (props.learningSequence) { return someFinishedInLearningSequence(props.learningSequence); } - return false; }); @@ -235,7 +234,11 @@ type LearninContentWithCompetenceCertificate = : 'no-status' " > - + {{ $t("a.Selbsteinschätzung") }} From 260cdf2ba1efd8b4320cf6775be8905e03759498 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 21 Mar 2024 15:11:53 +0100 Subject: [PATCH 3/7] fix: do not just take the first course session be explicit about the course session ID to use --- server/vbv_lernwelt/course/consts.py | 1 + .../course/creators/test_utils.py | 3 ++- .../shop/tests/test_datatrans_webhook.py | 19 +++++++++++++++--- server/vbv_lernwelt/shop/views.py | 20 ++++++++----------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/server/vbv_lernwelt/course/consts.py b/server/vbv_lernwelt/course/consts.py index 831d5987..a0dd7b27 100644 --- a/server/vbv_lernwelt/course/consts.py +++ b/server/vbv_lernwelt/course/consts.py @@ -1,3 +1,4 @@ +# Course IDs COURSE_TEST_ID = -1 COURSE_UK = -3 COURSE_VERSICHERUNGSVERMITTLERIN_ID = -4 diff --git a/server/vbv_lernwelt/course/creators/test_utils.py b/server/vbv_lernwelt/course/creators/test_utils.py index ac99904b..f9d65487 100644 --- a/server/vbv_lernwelt/course/creators/test_utils.py +++ b/server/vbv_lernwelt/course/creators/test_utils.py @@ -88,9 +88,10 @@ def create_user(username: str) -> User: def create_course_session( - course: Course, title: str, generation: str = "2023" + course: Course, title: str, generation: str = "2023", _id=None ) -> CourseSession: return CourseSession.objects.create( + id=_id, course=course, title=title, import_id=title, diff --git a/server/vbv_lernwelt/shop/tests/test_datatrans_webhook.py b/server/vbv_lernwelt/shop/tests/test_datatrans_webhook.py index 5850ea11..c18f7e6c 100644 --- a/server/vbv_lernwelt/shop/tests/test_datatrans_webhook.py +++ b/server/vbv_lernwelt/shop/tests/test_datatrans_webhook.py @@ -35,7 +35,13 @@ class DatatransWebhookTestCase(APITestCase): _id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, ) - create_course_session(course=course, title="Versicherungsvermittler/-in DE") + create_course_session( + course=course, + title="Versicherungsvermittler/-in DE", + # MUST be 1 -> this is the correct + # VV DE course session ID in production + _id=1, + ) self.user = User.objects.create_user( username="testuser", @@ -145,14 +151,21 @@ class DatatransWebhookTestCase(APITestCase): CourseSessionUser.objects.count(), ) + csu = CourseSessionUser.objects.first() + self.assertEqual( self.user, - CourseSessionUser.objects.first().user, + csu.user, + ) + + self.assertEqual( + 1, + csu.course_session.id, ) self.assertEqual( COURSE_VERSICHERUNGSVERMITTLERIN_ID, - CourseSessionUser.objects.first().course_session.course.id, + csu.course_session.course.id, ) self.assertEqual( diff --git a/server/vbv_lernwelt/shop/views.py b/server/vbv_lernwelt/shop/views.py index 3f5e4a97..06672826 100644 --- a/server/vbv_lernwelt/shop/views.py +++ b/server/vbv_lernwelt/shop/views.py @@ -8,11 +8,6 @@ from rest_framework.response import Response from sentry_sdk import capture_exception from vbv_lernwelt.core.models import Country, User -from vbv_lernwelt.course.consts import ( - COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID, - COURSE_VERSICHERUNGSVERMITTLERIN_ID, - COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID, -) from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email from vbv_lernwelt.shop.const import ( @@ -37,10 +32,11 @@ from vbv_lernwelt.shop.services import ( logger = structlog.get_logger(__name__) -PRODUCT_SKU_TO_COURSE = { - VV_DE_PRODUCT_SKU: COURSE_VERSICHERUNGSVERMITTLERIN_ID, - VV_FR_PRODUCT_SKU: COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID, - VV_IT_PRODUCT_SKU: COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID, +PRODUCT_SKU_TO_COURSE_SESSION_ID = { + # CourseSession IDs PROD/STAGING + VV_DE_PRODUCT_SKU: 1, # vv-de + VV_FR_PRODUCT_SKU: 2, # vv-fr + VV_IT_PRODUCT_SKU: 3, # vv-it } @@ -229,9 +225,9 @@ def create_vv_course_session_user(checkout_info: CheckoutInformation): _, created = CourseSessionUser.objects.get_or_create( user=checkout_info.user, role=CourseSessionUser.Role.MEMBER, - course_session=CourseSession.objects.filter( - course_id=PRODUCT_SKU_TO_COURSE[checkout_info.product_sku] - ).first(), + course_session=CourseSession.objects.get( + id=PRODUCT_SKU_TO_COURSE_SESSION_ID[checkout_info.product_sku] + ), ) if created: From 1831dcc363f57c3db53d5470ad7532e5ddc7d5b9 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 22 Mar 2024 11:22:53 +0100 Subject: [PATCH 4/7] fix: apply new definition of completed to allFinishedInLearningSequence --- client/src/services/circle.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts index 2ee177aa..44874dc7 100644 --- a/client/src/services/circle.ts +++ b/client/src/services/circle.ts @@ -43,10 +43,26 @@ export function someFinishedInLearningSequence(ls: LearningSequence) { }); } +/** + * Completed is defined as having completed all learning contents, + * ranked at least one performance criteria (but not necessarily all) + * was ranked as success or fail. + */ export function allFinishedInLearningSequence(ls: LearningSequence) { - return learningSequenceFlatChildren(ls).every((lc) => { - return lc.completion_status === "SUCCESS"; - }); + const allLearningContentsCompleted = ls.learning_units + .flatMap((lu) => lu.learning_contents) + .every((lc) => lc.completion_status === "SUCCESS"); + + const performanceCriteria = ls.learning_units.flatMap( + (lu) => lu.performance_criteria + ); + + const somePerformanceCriteriaRanked = + performanceCriteria.some((pc) => pc.completion_status === "SUCCESS") || + performanceCriteria.some((pc) => pc.completion_status === "FAIL") || + performanceCriteria.length === 0; + + return allLearningContentsCompleted && somePerformanceCriteriaRanked; } export function performanceCriteriaStatusCount( From 5d14bde80570392ce0f6ab311d477daf20330038 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 22 Mar 2024 11:29:45 +0100 Subject: [PATCH 5/7] chore: use same function to check performance criteria status --- client/src/services/circle.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts index 44874dc7..e0ab8021 100644 --- a/client/src/services/circle.ts +++ b/client/src/services/circle.ts @@ -58,9 +58,8 @@ export function allFinishedInLearningSequence(ls: LearningSequence) { ); const somePerformanceCriteriaRanked = - performanceCriteria.some((pc) => pc.completion_status === "SUCCESS") || - performanceCriteria.some((pc) => pc.completion_status === "FAIL") || - performanceCriteria.length === 0; + performanceCriteriaHasStatus(performanceCriteria) || + performanceCriteria.length === 0; // no performance criteria -> treat as completed return allLearningContentsCompleted && somePerformanceCriteriaRanked; } From e232aa48202a76892d39f3358d390d055cf0154c Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 22 Mar 2024 11:34:09 +0100 Subject: [PATCH 6/7] chore: cleanup --- client/src/services/circle.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts index e0ab8021..ddb4962e 100644 --- a/client/src/services/circle.ts +++ b/client/src/services/circle.ts @@ -44,9 +44,8 @@ export function someFinishedInLearningSequence(ls: LearningSequence) { } /** - * Completed is defined as having completed all learning contents, - * ranked at least one performance criteria (but not necessarily all) - * was ranked as success or fail. + * Completed is defined as having completed all learning contents, ranked at + * least one performance criteria (but not necessarily all) as success or fail. */ export function allFinishedInLearningSequence(ls: LearningSequence) { const allLearningContentsCompleted = ls.learning_units @@ -59,7 +58,7 @@ export function allFinishedInLearningSequence(ls: LearningSequence) { const somePerformanceCriteriaRanked = performanceCriteriaHasStatus(performanceCriteria) || - performanceCriteria.length === 0; // no performance criteria -> treat as completed + performanceCriteria.length === 0; // -> treat as completed return allLearningContentsCompleted && somePerformanceCriteriaRanked; } From 10449ebed3c7eb60c19b6266de9fb50b161ace2a Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Mon, 25 Mar 2024 09:42:21 +0100 Subject: [PATCH 7/7] Remove scrollbar by restricting the height --- .../learningPath/learningContentPage/blocks/IframeBlock.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/pages/learningPath/learningContentPage/blocks/IframeBlock.vue b/client/src/pages/learningPath/learningContentPage/blocks/IframeBlock.vue index 82664498..2c73d056 100644 --- a/client/src/pages/learningPath/learningContentPage/blocks/IframeBlock.vue +++ b/client/src/pages/learningPath/learningContentPage/blocks/IframeBlock.vue @@ -6,10 +6,9 @@ const props = defineProps<{ content: LearningContent; }>(); -