From 4a9cf29d2af2a0f384c811f1a4b768d5333f1b3e Mon Sep 17 00:00:00 2001 From: Monsieur S <143810744+monsieurswag@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:34:59 +0100 Subject: [PATCH] Add documentation score (#1339) * Add documentation score * Handle null score * style: improve score display * locale: add french translation * chore: remove comment * fix migration conflict * ruff * update ruff version * Lean down Score component * Add 'change' event dispatcher to Checkbox component * Adapt table mode and requirement assessment edit to new Score component * chore: Remove dead code * chore: ruff format * Uncomment cache decorators * Fix migration conflicts * chore: ruff format * fix: display default score label when no documentation score * feat: update documentation score if null --------- Co-authored-by: Mohamed-Hacene Co-authored-by: eric-intuitem <71850047+eric-intuitem@users.noreply.github.com> Co-authored-by: Nassim Tabchiche --- .github/workflows/backend-linters.yaml | 2 +- backend/core/helpers.py | 4 + ...sment_show_documentation_score_and_more.py | 24 ++++ backend/core/models.py | 21 ++- backend/core/views.py | 13 ++ frontend/messages/ar.json | 5 +- frontend/messages/cs.json | 4 + frontend/messages/de.json | 5 +- frontend/messages/en.json | 8 +- frontend/messages/es.json | 5 +- frontend/messages/fr.json | 8 +- frontend/messages/hi.json | 5 +- frontend/messages/id.json | 7 +- frontend/messages/it.json | 5 +- frontend/messages/nl.json | 5 +- frontend/messages/pl.json | 5 +- frontend/messages/pt.json | 5 +- frontend/messages/ro.json | 5 +- frontend/messages/sv.json | 5 +- frontend/messages/ur.json | 5 +- .../src/lib/components/Forms/Checkbox.svelte | 9 ++ .../ModelForm/ComplianceAssessmentForm.svelte | 8 ++ .../ModelForm/EntityAssessmentForm.svelte | 1 - .../Forms/ModelForm/SolutionForm.svelte | 1 - .../src/lib/components/Forms/Score.svelte | 122 ++++++------------ frontend/src/lib/utils/helpers.ts | 3 +- frontend/src/lib/utils/schemas.ts | 4 +- .../[id=uuid]/+page.svelte | 9 +- .../[id=uuid]/TreeViewItemContent.svelte | 6 +- .../[id=uuid]/TreeViewItemLead.svelte | 23 +++- .../[id=uuid]/table-mode/+page.server.ts | 17 +-- .../[id=uuid]/table-mode/+page.svelte | 103 +++++++++++---- .../[id=uuid]/+page.svelte | 19 ++- .../[id=uuid]/edit/+page.svelte | 63 ++++++--- 34 files changed, 354 insertions(+), 180 deletions(-) create mode 100644 backend/core/migrations/0049_complianceassessment_show_documentation_score_and_more.py diff --git a/.github/workflows/backend-linters.yaml b/.github/workflows/backend-linters.yaml index a0c6dee3f..c7d4cb288 100644 --- a/.github/workflows/backend-linters.yaml +++ b/.github/workflows/backend-linters.yaml @@ -31,7 +31,7 @@ jobs: - name: Install ruff working-directory: ${{env.working-directory}} run: | - python -m pip install ruff==0.9.0 + python -m pip install ruff==0.9.1 - name: Run ruff format check working-directory: ${{env.working-directory}} run: ruff format --check . diff --git a/backend/core/helpers.py b/backend/core/helpers.py index d9b3c707a..b999f6339 100644 --- a/backend/core/helpers.py +++ b/backend/core/helpers.py @@ -286,6 +286,7 @@ def get_sorted_requirement_nodes_rec(start: list) -> dict: "result": req_as.result if req_as else None, "is_scored": req_as.is_scored if req_as else None, "score": req_as.score if req_as else None, + "documentation_score": req_as.documentation_score if req_as else None, "max_score": max_score if req_as else None, "question": req_as.answer if req_as else node.question, "mapping_inference": req_as.mapping_inference if req_as else None, @@ -324,6 +325,9 @@ def get_sorted_requirement_nodes_rec(start: list) -> dict: "status": child_req_as.status if child_req_as else None, "is_scored": child_req_as.is_scored if child_req_as else None, "score": child_req_as.score if child_req_as else None, + "documentation_score": child_req_as.documentation_score + if child_req_as + else None, "max_score": max_score if child_req_as else None, "question": child_req_as.answer if child_req_as else child.question, "mapping_inference": child_req_as.mapping_inference diff --git a/backend/core/migrations/0049_complianceassessment_show_documentation_score_and_more.py b/backend/core/migrations/0049_complianceassessment_show_documentation_score_and_more.py new file mode 100644 index 000000000..2a852199d --- /dev/null +++ b/backend/core/migrations/0049_complianceassessment_show_documentation_score_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.4 on 2025-01-13 09:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0048_alter_asset_security_objectives"), + ] + + operations = [ + migrations.AddField( + model_name="complianceassessment", + name="show_documentation_score", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="requirementassessment", + name="documentation_score", + field=models.IntegerField( + blank=True, null=True, verbose_name="Documentation Score" + ), + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 5f0cdb49d..0a403d988 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -2732,6 +2732,7 @@ class ComplianceAssessment(Assessment): scores_definition = models.JSONField( blank=True, null=True, verbose_name=_("Score definition") ) + show_documentation_score = models.BooleanField(default=False) class Meta: verbose_name = _("Compliance assessment") @@ -2808,7 +2809,6 @@ def create_requirement_assessments( def get_global_score(self): requirement_assessments_scored = ( RequirementAssessment.objects.filter(compliance_assessment=self) - .exclude(score=None) .exclude(status=RequirementAssessment.Result.NOT_APPLICABLE) .exclude(is_scored=False) .exclude(requirement__assessable=False) @@ -2820,12 +2820,18 @@ def get_global_score(self): ) score = 0 n = 0 + for ras in requirement_assessments_scored: if not (ig) or (ig & set(ras.requirement.implementation_groups)): - score += ras.score + score += ras.score or 0 n += 1 + if self.show_documentation_score: + score += ras.documentation_score or 0 + n += 1 if n > 0: - return round(score / n, 1) + global_score = score / n + # We use this instead of using the python round function so that the python backend outputs the same result as the javascript frontend. + return int(global_score * 10) / 10 else: return -1 @@ -3250,14 +3256,17 @@ class Result(models.TextChoices): verbose_name=_("Result"), default=Result.NOT_ASSESSED, ) + is_scored = models.BooleanField( + default=False, + verbose_name=_("Is scored"), + ) score = models.IntegerField( blank=True, null=True, verbose_name=_("Score"), ) - is_scored = models.BooleanField( - default=False, - verbose_name=_("Is scored"), + documentation_score = models.IntegerField( + blank=True, null=True, verbose_name=_("Documentation Score") ) evidences = models.ManyToManyField( Evidence, diff --git a/backend/core/views.py b/backend/core/views.py index ab298b837..4636b292c 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -2526,6 +2526,18 @@ def perform_create(self, serializer): for requirement_assessment in instance.requirement_assessments.all(): requirement_assessment.create_applied_controls_from_suggestions() + def perform_update(self, serializer): + compliance_assessment = serializer.save() + if compliance_assessment.show_documentation_score: + ra_null_documentation_score = RequirementAssessment.objects.filter( + compliance_assessment=compliance_assessment, + is_scored=True, + documentation_score__isnull=True, + ) + ra_null_documentation_score.update( + documentation_score=compliance_assessment.min_score + ) + @action(detail=False, name="Compliance assessments per status") def per_status(self, request): data = assessment_per_status(request.user, ComplianceAssessment) @@ -2564,6 +2576,7 @@ def global_score(self, request, pk): "scores_definition": get_referential_translation( compliance_assessment.framework, "scores_definition", get_language() ), + "show_documentation_score": compliance_assessment.show_documentation_score, } ) diff --git a/frontend/messages/ar.json b/frontend/messages/ar.json index bc79f2507..8e6a6f8ef 100644 --- a/frontend/messages/ar.json +++ b/frontend/messages/ar.json @@ -862,9 +862,12 @@ "addTag": "إضافة علامة", "tagsHelpText": "تُستخدم العلامات لتصنيف العناصر وتصفيتها. يمكنك إضافة علامات في قسم \"إضافي\"", "forgotPassword": "هل نسيت كلمة السر", - "scoreSemiColon": "نتيجة:", + "score": "نتيجة", "mappingInferenceHelpText": "هذه المتغيرات ثابتة ولن تتغير اعتمادًا على المصدر.", "bringTheEvidences": "أحضر الأدلة", "bringTheEvidencesHelpText": "في حالة التعطيل، سيتم تكرار الكائن بدون أدلته", + "documentationScore": "درجة التوثيق", + "useDocumentationScore": "استخدام درجة التوثيق", + "useDocumentationScoreHelpText": "ستتوفر درجة ثانية (درجة التوثيق) لتقييم المتطلبات.", "publicationDate": "تاريخ النشر" } diff --git a/frontend/messages/cs.json b/frontend/messages/cs.json index 4afa3f11f..0aedc8151 100644 --- a/frontend/messages/cs.json +++ b/frontend/messages/cs.json @@ -555,6 +555,7 @@ "youCanSetNewPassword": "Zde si můžete nastavit nové heslo", "userWillBeDisconnected": "Uživatel bude odpojen a bude se muset znovu přihlásit", "scoresDefinition": "Definice skóre", + "score": "Skóre", "selectedImplementationGroups": "Vybrané implementační skupiny", "implementationGroupsDefinition": "Definice implementačních skupin", "threatRadarChart": "Radarový graf hrozeb", @@ -855,5 +856,8 @@ "tagsHelpText": "Štítky pomáhají kategorizovat a filtrovat více objektů. Můžete je spravovat v menu Extra.", "bringTheEvidences": "Přineste důkazy", "bringTheEvidencesHelpText": "Pokud je zakázáno, objekt bude duplikován bez jeho důkazů", + "documentationScore": "Skóre dokumentace", + "useDocumentationScore": "Použití skóre dokumentace", + "useDocumentationScoreHelpText": "Pro posouzení požadavků bude k dispozici druhé bodové skóre (skóre dokumentace).", "publicationDate": "Datum zveřejnění" } diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 7e72b68e6..23f8637da 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -861,9 +861,12 @@ "addTag": "Tag hinzufügen", "tagsHelpText": "Tags werden zum Kategorisieren und Filtern der Elemente verwendet. Sie können Tags im Abschnitt Extra hinzufügen", "forgotPassword": "Passwort vergessen", - "scoreSemiColon": "Punktzahl:", + "score": "Punktzahl", "mappingInferenceHelpText": "Diese Variablen sind fest und ändern sich je nach Quelle nicht.", "bringTheEvidences": "Bringen Sie die Beweise", "bringTheEvidencesHelpText": "Wenn deaktiviert, wird das Objekt ohne seine Beweise dupliziert", + "documentationScore": "Bewertung der Dokumentation", + "useDocumentationScore": "Dokumentationsnote verwenden", + "useDocumentationScoreHelpText": "Eine zweite Punktzahl (Dokumentationspunktzahl) wird verfügbar sein, um die Anforderungen zu bewerten.", "publicationDate": "Veröffentlichungsdatum" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index f69403365..98da0d81e 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -893,7 +893,7 @@ "youCanSetPasswordHere": "You can set your password here", "forgotPassword": "Forgot password", "ssoSettingsUpdated": "SSO settings updated", - "scoreSemiColon": "Score:", + "score": "Score", "mappingInferenceHelpText": "These variables are fixed and will not change depending on the source.", "priority": "Priority", "p1": "P1", @@ -1083,5 +1083,9 @@ "missionsAndOrganizationalServices": "Missions and organizational services", "human": "Human", "material": "Material", - "environmental": "Environmental" + "environmental": "Environmental", + "documentationScore": "Documentation score", + "implementationScore": "Implementation score", + "useDocumentationScore": "Use documentation score", + "useDocumentationScoreHelpText": "A second score (the documentation score) will be available for scoring the requirements." } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index d31d1cdf2..0733d1efc 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -861,9 +861,12 @@ "addTag": "Agregar etiqueta", "tagsHelpText": "Las etiquetas se utilizan para categorizar y filtrar los elementos. Puedes agregar etiquetas en la sección Extra", "forgotPassword": "Has olvidado tu contraseña", - "scoreSemiColon": "Puntaje:", + "score": "Puntaje", "mappingInferenceHelpText": "Estas variables son fijas y no cambiarán dependiendo de la fuente.", "bringTheEvidences": "Traer las evidencias", "bringTheEvidencesHelpText": "Si está deshabilitado, el objeto se duplicará sin sus evidencias.", + "documentationScore": "Puntuación de la documentación", + "useDocumentationScore": "Utilizar la puntuación de la documentación", + "useDocumentationScoreHelpText": "Se dispondrá de una segunda puntuación (puntuación de la documentación) para evaluar los requisitos.", "publicationDate": "Fecha de publicación" } diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 09bbe25e6..9d3183574 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -893,7 +893,7 @@ "youCanSetPasswordHere": "Vous pouvez définir votre mot de passe ici", "forgotPassword": "Mot de passe oublié", "ssoSettingsUpdated": "Paramètres SSO mis à jour", - "scoreSemiColon": "Score:", + "score": "Score", "mappingInferenceHelpText": "Ces variables sont fixes et ne changeront pas en fonction de la source.", "priority": "Priorité", "p1": "P1", @@ -1083,5 +1083,9 @@ "missionsAndOrganizationalServices": "Missions et service de l'organisation", "human": "Humain", "material": "Matériel", - "environmental": "Environnemental" + "environmental": "Environnemental", + "documentationScore": "Score de documentation", + "implementationScore": "Score d'implémentation", + "useDocumentationScore": "Utiliser le score de documentation", + "useDocumentationScoreHelpText": "Un deuxième score (le score de documentation) sera disponible pour évaluer les exigences." } diff --git a/frontend/messages/hi.json b/frontend/messages/hi.json index 986e63568..a39469b16 100644 --- a/frontend/messages/hi.json +++ b/frontend/messages/hi.json @@ -861,9 +861,12 @@ "addTag": "टैग जोड़ें", "tagsHelpText": "टैग का उपयोग आइटम को वर्गीकृत और फ़िल्टर करने के लिए किया जाता है। आप अतिरिक्त अनुभाग में टैग जोड़ सकते हैं", "forgotPassword": "पासवर्ड भूल गए", - "scoreSemiColon": "अंक:", + "score": "अंक", "mappingInferenceHelpText": "ये चर निश्चित हैं और स्रोत के आधार पर परिवर्तित नहीं होंगे।", "bringTheEvidences": "सबूत लाओ", "bringTheEvidencesHelpText": "यदि अक्षम किया गया है, तो ऑब्जेक्ट को उसके साक्ष्य के बिना डुप्लिकेट किया जाएगा", + "documentationScore": "दस्तावेज़ीकरण स्कोर", + "useDocumentationScore": "दस्तावेज़ स्कोर का उपयोग करें", + "useDocumentationScoreHelpText": "आवश्यकताओं का आकलन करने के लिए दूसरी रेटिंग (दस्तावेज़ीकरण स्कोर) उपलब्ध होगी।", "publicationDate": "प्रकाशन की तिथि" } diff --git a/frontend/messages/id.json b/frontend/messages/id.json index 269ccfeec..4e3c3c519 100644 --- a/frontend/messages/id.json +++ b/frontend/messages/id.json @@ -894,7 +894,7 @@ "youCanSetPasswordHere": "Anda dapat mengatur kata sandi Anda di sini", "forgotPassword": "Lupa kata sandi", "ssoSettingsUpdated": "Pengaturan SSO diperbarui", - "scoreSemiColon": "Skor:", + "score": "Skor", "mappingInferenceHelpText": "Variabel-variabel ini tetap dan tidak akan berubah tergantung pada sumber.", "priority": "Prioritas", "p1": "P1", @@ -1083,5 +1083,8 @@ "missionsAndOrganizationalServices": "Misi dan layanan organisasi", "human": "Manusia", "material": "Materi", - "environmental": "Lingkungan" + "environmental": "Lingkungan", + "documentationScore": "Skor dokumentasi", + "useDocumentationScore": "Gunakan skor dokumentasi", + "useDocumentationScoreHelpText": "Skor kedua (skor dokumentasi) akan tersedia untuk menilai persyaratan." } diff --git a/frontend/messages/it.json b/frontend/messages/it.json index 3e1c81628..93b4b286c 100644 --- a/frontend/messages/it.json +++ b/frontend/messages/it.json @@ -861,9 +861,12 @@ "addTag": "Aggiungi tag", "tagsHelpText": "I tag vengono utilizzati per categorizzare e filtrare gli elementi. Puoi aggiungere tag nella sezione Extra", "forgotPassword": "Ha dimenticato la password", - "scoreSemiColon": "Punto:", + "score": "Punto", "mappingInferenceHelpText": "Queste variabili sono fisse e non cambiano a seconda della fonte.", "bringTheEvidences": "Portare le prove", "bringTheEvidencesHelpText": "Se disabilitato, l'oggetto verrà duplicato senza le sue prove", + "documentationScore": "Punteggio della documentazione", + "useDocumentationScore": "Utilizzare il punteggio della documentazione", + "useDocumentationScoreHelpText": "Un secondo punteggio (punteggio di documentazione) sarà disponibile per valutare i requisiti.", "publicationDate": "Data di pubblicazione" } diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index 5593d85dc..305da9e3a 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -861,9 +861,12 @@ "addTag": "Tag toevoegen", "tagsHelpText": "Tags worden gebruikt om de items te categoriseren en te filteren. U kunt tags toevoegen in de sectie Extra", "forgotPassword": "Wachtwoord vergeten", - "scoreSemiColon": "Punt:", + "score": "Punt", "mappingInferenceHelpText": "Deze variabelen zijn vast en veranderen niet, afhankelijk van de bron.", "bringTheEvidences": "Breng de bewijzen", "bringTheEvidencesHelpText": "Als dit is uitgeschakeld, wordt het object gedupliceerd zonder de bijbehorende bewijzen", + "documentationScore": "Documentatie score", + "useDocumentationScore": "Documentatiescore gebruiken", + "useDocumentationScoreHelpText": "Een tweede score (documentatiescore) zal beschikbaar zijn om de vereisten te beoordelen.", "publicationDate": "Publicatiedatum" } diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index 1497e2878..bc993e1eb 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -861,9 +861,12 @@ "addTag": "Dodaj tag", "tagsHelpText": "Tagi służą do kategoryzowania i filtrowania elementów. Możesz dodać tagi w sekcji Extra", "forgotPassword": "Zapomniałem hasła", - "scoreSemiColon": "Wynik:", + "score": "Wynik", "mappingInferenceHelpText": "Te zmienne są stałe i nie zmieniają się w zależności od źródła.", "bringTheEvidences": "Przynieś dowody", "bringTheEvidencesHelpText": "Jeśli wyłączone, obiekt zostanie zduplikowany bez dowodów", + "documentationScore": "Wynik dokumentacji", + "useDocumentationScore": "Korzystanie z punktacji dokumentacji", + "useDocumentationScoreHelpText": "Drugi wynik (wynik dokumentacji) będzie dostępny do oceny wymagań.", "publicationDate": "Data publikacji" } diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json index ba8c43df0..f54918e64 100644 --- a/frontend/messages/pt.json +++ b/frontend/messages/pt.json @@ -861,9 +861,12 @@ "addTag": "Adicionar etiqueta", "tagsHelpText": "As tags são usadas para categorizar e filtrar os itens. Você pode adicionar tags na seção Extra", "forgotPassword": "Esqueceu sua senha", - "scoreSemiColon": "Pontuação:", + "score": "Pontuação", "mappingInferenceHelpText": "Essas variáveis são fixas e não mudarão dependendo da fonte.", "bringTheEvidences": "Traga as evidências", "bringTheEvidencesHelpText": "Se desabilitado, o objeto será duplicado sem suas evidências", + "documentationScore": "Pontuação da documentação", + "useDocumentationScore": "Utilizar a pontuação da documentação", + "useDocumentationScoreHelpText": "Uma segunda pontuação (a pontuação da documentação) estará disponível para avaliar os requisitos.", "publicationDate": "Data de publicação" } diff --git a/frontend/messages/ro.json b/frontend/messages/ro.json index e4920ee2f..929d17ef0 100644 --- a/frontend/messages/ro.json +++ b/frontend/messages/ro.json @@ -861,9 +861,12 @@ "addTag": "Adăugați etichetă", "tagsHelpText": "Etichetele sunt folosite pentru a clasifica și filtra articolele. Puteți adăuga etichete în secțiunea Extra", "forgotPassword": "Aţi uitat parola", - "scoreSemiColon": "Scor:", + "score": "Scor", "mappingInferenceHelpText": "Aceste variabile sunt fixe și nu se vor modifica în funcție de sursă.", "bringTheEvidences": "Aduceți dovezile", "bringTheEvidencesHelpText": "Dacă este dezactivat, obiectul va fi duplicat fără dovezile sale", + "documentationScore": "Punctajul documentației", + "useDocumentationScore": "Utilizați punctajul documentației", + "useDocumentationScoreHelpText": "Un al doilea punctaj (punctajul pentru documentație) va fi disponibil pentru evaluarea cerințelor.", "publicationDate": "Data publicării" } diff --git a/frontend/messages/sv.json b/frontend/messages/sv.json index 44b0ee563..496c132aa 100644 --- a/frontend/messages/sv.json +++ b/frontend/messages/sv.json @@ -861,9 +861,12 @@ "youCanSetPasswordHere": "Du kan ställa in ditt lösenord här.", "forgotPassword": "Glömt lösenord", "ssoSettingsUpdated": "SSO-inställningar uppdaterade", - "scoreSemiColon": "Göra:", + "score": "Göra", "mappingInferenceHelpText": "Dessa variabler är fasta och kommer inte att ändras beroende på källan.", "bringTheEvidences": "Kom med bevisen", "bringTheEvidencesHelpText": "Om det är inaktiverat kommer objektet att dupliceras utan dess bevis", + "documentationScore": "Poäng för dokumentation", + "useDocumentationScore": "Använd poängsättning av dokumentation", + "useDocumentationScoreHelpText": "En andra poäng (dokumentationspoängen) kommer att finnas tillgänglig för att bedöma kraven.", "publicationDate": "Publiceringsdatum" } diff --git a/frontend/messages/ur.json b/frontend/messages/ur.json index 55c026cc8..8d5bbe8df 100644 --- a/frontend/messages/ur.json +++ b/frontend/messages/ur.json @@ -861,9 +861,12 @@ "addTag": "ٹیگ شامل کریں۔", "tagsHelpText": "ٹیگز اشیاء کی درجہ بندی اور فلٹر کرنے کے لیے استعمال ہوتے ہیں۔ آپ اضافی سیکشن میں ٹیگ شامل کر سکتے ہیں۔", "forgotPassword": "پاس ورڈ بھول گئے۔", - "scoreSemiColon": "سکور:", + "score": "سکور", "mappingInferenceHelpText": "یہ متغیرات طے شدہ ہیں اور ماخذ کے لحاظ سے تبدیل نہیں ہوں گے۔", "bringTheEvidences": "ثبوت لے کر آئیں", "bringTheEvidencesHelpText": "اگر غیر فعال ہو تو، اعتراض کو اس کے ثبوت کے بغیر نقل کر دیا جائے گا۔", + "documentationScore": "دستاویزی سکور", + "useDocumentationScore": "دستاویزی سکور استعمال کریں۔", + "useDocumentationScoreHelpText": "ضروریات کو پورا کرنے کے لیے دوسرا سکور (دستاویزی سکور) دستیاب ہوگا۔", "publicationDate": "اشاعت کی تاریخ" } diff --git a/frontend/src/lib/components/Forms/Checkbox.svelte b/frontend/src/lib/components/Forms/Checkbox.svelte index 75c61c08b..449e5963a 100644 --- a/frontend/src/lib/components/Forms/Checkbox.svelte +++ b/frontend/src/lib/components/Forms/Checkbox.svelte @@ -1,5 +1,6 @@
@@ -83,67 +66,40 @@
{/if}
- {#if isApplicable} -
- -
- {#if !always_enabled} - -

{m.scoringHelpText()}

- {/if} - {#if $isScored && scores_definition && $value !== null} +
+ +
+ +

+ {#if !disabled && scores_definition && $value !== null} {#each scores_definition as definition} {#if definition.score === $value} -

- {definition.name}{definition.description ? `: ${definition.description}` : ''} -

+ {definition.name}{definition.description ? `: ${definition.description}` : ''} {/if} {/each} {/if} - {#if security_objective} - {securityObjectiveDisplay($value) ?? ($isScored ? $value : '--')} - {:else} - {$isScored ? $value : '--'} - {/if} -
-
-
- {:else} -

- {m.notApplicableScore()} -

- {/if} +

+ {!disabled ? $value : '--'} +
+
+
{#if helpText}

{helpText}

diff --git a/frontend/src/lib/utils/helpers.ts b/frontend/src/lib/utils/helpers.ts index 57a134484..85a85848c 100644 --- a/frontend/src/lib/utils/helpers.ts +++ b/frontend/src/lib/utils/helpers.ts @@ -23,7 +23,8 @@ export function getRequirementTitle(ref_id: string, name: string) { return title; } -export function displayScoreColor(value: number, max_score: number, inversedColors = false) { +export function displayScoreColor(value: number | null, max_score: number, inversedColors = false) { + value ??= 0; value = (value * 100) / max_score; if (inversedColors) { if (value < 25) { diff --git a/frontend/src/lib/utils/schemas.ts b/frontend/src/lib/utils/schemas.ts index 80e692d56..ac8c3651c 100644 --- a/frontend/src/lib/utils/schemas.ts +++ b/frontend/src/lib/utils/schemas.ts @@ -219,8 +219,9 @@ export const RequirementAssessmentSchema = z.object({ answer: jsonSchema, status: z.string(), result: z.string(), - score: z.number().optional().nullable(), is_scored: z.boolean().optional(), + score: z.number().optional().nullable(), + documentation_score: z.number().optional().nullable(), comment: z.string().optional().nullable(), folder: z.string(), requirement: z.string(), @@ -264,6 +265,7 @@ export const ComplianceAssessmentSchema = z.object({ status: z.string().optional().nullable(), selected_implementation_groups: z.array(z.string().optional()).optional(), framework: z.string(), + show_documentation_score: z.boolean().optional().default(false), eta: z.union([z.literal('').transform(() => null), z.string().date()]).nullish(), due_date: z.union([z.literal('').transform(() => null), z.string().date()]).nullish(), authors: z.array(z.string().optional()).optional(), diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.svelte index dfd41fa6b..bced2c4dc 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.svelte @@ -41,6 +41,7 @@ import { displayScoreColor } from '$lib/utils/helpers'; import { expandedNodesState } from '$lib/utils/stores'; import { ProgressRadial } from '@skeletonlabs/skeleton'; + import { nodeIsDragged } from '@unovis/ts/components/graph/modules/node/style'; $: tree = data.tree; @@ -90,7 +91,10 @@ } if (node.is_scored && node.assessable && node.result !== 'not_applicable') { resultCounts['scored'] = (resultCounts['scored'] || 0) + 1; - resultCounts['total_score'] = (resultCounts['total_score'] || 0) + node.score; + const nodeMeanScore = data.compliance_assessment.show_documentation_score + ? (node.score + node.documentation_score) / 2 + : node.score; + resultCounts['total_score'] = (resultCounts['total_score'] || 0) + nodeMeanScore; } if (node.children && Object.keys(node.children).length > 0) { @@ -105,7 +109,6 @@ }; function transformToTreeView(nodes: Node[]) { - if (!tree) return []; return nodes.map(([id, node]) => { node.resultCounts = countResults(node); const hasAssessableChildren = Object.keys(node.children || {}).length > 0; @@ -127,7 +130,9 @@ statusColor: complianceStatusColorMap[node.status], resultColor: complianceResultColorMap[node.result], score: node.score, + documentationScore: node.documentation_score, isScored: node.is_scored, + showDocumentationScore: data.compliance_assessment.show_documentation_score, max_score: node.max_score }, children: node.children ? transformToTreeView(Object.entries(node.children)) : [] diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/TreeViewItemContent.svelte b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/TreeViewItemContent.svelte index 1fcbf0078..9a434ef9f 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/TreeViewItemContent.svelte +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/TreeViewItemContent.svelte @@ -93,8 +93,8 @@ } ); - function nodeScore(): number { - if (!resultCounts) return -1; + function nodeScore(): number | null { + if (!resultCounts) return null; let mean = resultCounts['total_score'] / resultCounts['scored']; return Math.floor(mean * 10) / 10; } @@ -277,7 +277,7 @@ {/each} - {#if nodeScore() >= 0} + {#if nodeScore() !== null} {leadResult} - {#if score !== null && statusI18n !== 'notApplicable' && isScored} - + {#if statusI18n !== 'notApplicable' && isScored} + {score} + {#if showDocumentationScore} {score}{documentationScore} - + {/if} {/if} {/if} diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts index 5dd37367c..3fefb325c 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts @@ -1,14 +1,13 @@ +import { nestedWriteFormAction } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; +import { getModelInfo } from '$lib/utils/crud'; import { modelSchema } from '$lib/utils/schemas'; -import { fail, setError, superValidate } from 'sveltekit-superforms'; -import type { PageServerLoad } from './$types'; +import * as m from '$paraglide/messages'; import type { Actions } from '@sveltejs/kit'; +import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { setFlash } from 'sveltekit-flash-message/server'; -import * as m from '$paraglide/messages'; import { z } from 'zod'; -import { nestedWriteFormAction } from '$lib/utils/actions'; +import type { PageServerLoad } from './$types'; export const load = (async ({ fetch, params }) => { const URLModel = 'compliance-assessments'; @@ -24,7 +23,8 @@ export const load = (async ({ fetch, params }) => { const evidenceCreateSchema = modelSchema('evidences'); const scoreSchema = z.object({ is_scored: z.boolean().optional(), - score: z.number().optional().nullable() + score: z.number().optional().nullable(), + documentation_score: z.number().optional().nullable() }); const requirement_assessments = await Promise.all( tableMode.requirement_assessments.map(async (requirementAssessment) => { @@ -43,7 +43,8 @@ export const load = (async ({ fetch, params }) => { const scoreForm = await superValidate( { is_scored: requirementAssessment.is_scored, - score: requirementAssessment.score + score: requirementAssessment.score, + documentation_score: requirementAssessment.documentation_score }, zod(scoreSchema) ); diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte index f2b3bba2e..8f360f584 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte @@ -1,4 +1,7 @@ @@ -90,12 +91,22 @@ {#if data.requirementAssessment.is_scored} {value}{score} + {#if data.complianceAssessmentScore.show_documentation_score} + {documentationScore} + {/if} {/if} {#if data.requirement.description} diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte index 78b5233df..2fdacffe0 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte @@ -44,6 +44,7 @@ import List from '$lib/components/List/List.svelte'; import ConfirmModal from '$lib/components/Modals/ConfirmModal.svelte'; import { zod } from 'sveltekit-superforms/adapters'; + import Checkbox from '$lib/components/Forms/Checkbox.svelte'; function cancel(): void { var currentUrl = window.location.href; @@ -320,6 +321,7 @@ data={data.form} dataType="json" let:form + let:data validators={zod(schema)} action="?/updateRequirementAssessment" {...$$restProps} @@ -348,8 +350,8 @@ type="button" on:click={() => { modalConfirmCreateSuggestedControls( - data.requirementAssessment.id, - data.requirementAssessment.name, + $page.data.requirementAssessment.id, + $page.data.requirementAssessment.name, '?/createSuggestedControls' ); }} @@ -379,13 +381,13 @@ multiple {form} options={getOptions({ - objects: data.model.foreignKeys['applied_controls'], + objects: $page.data.model.foreignKeys['applied_controls'], extra_fields: [['folder', 'str']] })} field="applied_controls" /> @@ -410,13 +412,13 @@ multiple {form} options={getOptions({ - objects: data.model.foreignKeys['evidences'], + objects: $page.data.model.foreignKeys['evidences'], extra_fields: [['folder', 'str']] })} field="evidences" /> @@ -429,29 +431,56 @@
- {#if data.requirementAssessment.answer != null && Object.keys(data.requirementAssessment.answer).length !== 0} + {#if $page.data.requirementAssessment.answer != null && Object.keys($page.data.requirementAssessment.answer).length !== 0} {/if} - +
+ +
+ +
+
+
+ {#if $page.data.compliance_assessment_score.show_documentation_score} + + {/if}