From 85abe8b8218a624374396c1ffa4a8d58c59adf76 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Fri, 20 Sep 2024 12:19:20 +0200 Subject: [PATCH 01/26] feat: add entity assessment author help text --- frontend/messages/en.json | 3 ++- frontend/src/lib/components/Forms/ModelForm.svelte | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 16217e94c..a33e5a16e 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -729,5 +729,6 @@ "createUser": "Create user", "createUserHelpText": "Create or link a third party user to the representative based on the email", "nameDuplicate": "Name already exists", - "noAnswer": "No answer" + "noAnswer": "No answer", + "entityAssessmentAuthorHelpText": "The representative who is responsible for questionnaire completion" } diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index b3d18a1a0..b6c4fcc59 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -890,6 +890,7 @@ multiple options={getOptions({ objects: model.foreignKeys['authors'], label: 'email' })} field="authors" + helpText={m.entityAssessmentAuthorHelpText()} cacheLock={cacheLocks['authors']} bind:cachedValue={formDataCache['authors']} label={m.authors()} From d915a21a9cda4a59e6561612721139b732aca7d9 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Mon, 23 Sep 2024 12:54:08 +0200 Subject: [PATCH 02/26] feat: add send mail button for questionnaire --- backend/core/views.py | 29 +++++++-- backend/tprm/serializers.py | 17 ------ frontend/messages/en.json | 6 +- .../components/DetailView/DetailView.svelte | 59 ++++++++++++++++--- .../[id=uuid]/+page.server.ts | 35 ++++++++++- .../entity-assessments/[id=uuid]/+page.svelte | 6 +- 6 files changed, 121 insertions(+), 31 deletions(-) diff --git a/backend/core/views.py b/backend/core/views.py index ba1358b78..06f104fd4 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -11,10 +11,7 @@ from datetime import date, timedelta import django_filters as df -from ciso_assistant.settings import ( - BUILD, - VERSION, -) +from ciso_assistant.settings import BUILD, VERSION, EMAIL_HOST, EMAIL_HOST_RESCUE from django.contrib.auth.models import Permission from django.core.files.storage import default_storage from django.db import models @@ -27,6 +24,7 @@ from django_filters.rest_framework import DjangoFilterBackend from iam.models import Folder, RoleAssignment, User, UserGroup from rest_framework import filters, permissions, status, viewsets +from django.utils.translation import gettext_lazy as _ from rest_framework.decorators import ( action, api_view, @@ -1591,6 +1589,29 @@ def action_plan_pdf(self, request, pk): else: return Response({"error": "Permission denied"}) + @action( + detail=True, + methods=["post"], + name="Send compliance assessment by mail to authors", + ) + def mailing(self, request, pk): + instance = self.get_object() + if EMAIL_HOST or EMAIL_HOST_RESCUE: + for author in instance.authors.all(): + try: + author.mailing( + email_template_name="tprm/third_party_email.html", + subject=_( + "CISO Assistant: A questionnaire has been assigned to you" + ), + object="compliance-assessments", + object_id=instance.id, + ) + except Exception as e: + print(f"Failed to send email to {author}: {e}") + return Response({"results": "mail sent"}) + return Response({"results": "mail not sent"}) + def perform_create(self, serializer): """ Create RequirementAssessment objects for the newly created ComplianceAssessment diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index a5218959f..398b8ab19 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -120,27 +120,11 @@ def _assign_third_party_respondents( logger.warning("User is not a third-party", user=user) user.user_groups.add(respondents) - def _send_author_emails(self, instance, authors_to_email: set): - if EMAIL_HOST or EMAIL_HOST_RESCUE: - for author in authors_to_email: - try: - author.mailing( - email_template_name="tprm/third_party_email.html", - subject=_( - "CISO Assistant: A questionnaire has been assigned to you" - ), - object="compliance-assessments", - object_id=instance.compliance_assessment.id, - ) - except Exception as e: - print(f"Failed to send email to {author}: {e}") - def create(self, validated_data): audit_data = self._extract_audit_data(validated_data) instance = super().create(validated_data) self._create_or_update_audit(instance, audit_data) self._assign_third_party_respondents(instance, set(instance.authors.all())) - self._send_author_emails(instance, set(instance.authors.all())) return instance def update(self, instance: EntityAssessment, validated_data): @@ -154,7 +138,6 @@ def update(self, instance: EntityAssessment, validated_data): self._create_or_update_audit(instance, audit_data) self._assign_third_party_respondents(instance, new_authors) - self._send_author_emails(instance, new_authors) return instance class Meta: diff --git a/frontend/messages/en.json b/frontend/messages/en.json index a33e5a16e..0fe80eb21 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -730,5 +730,9 @@ "createUserHelpText": "Create or link a third party user to the representative based on the email", "nameDuplicate": "Name already exists", "noAnswer": "No answer", - "entityAssessmentAuthorHelpText": "The representative who is responsible for questionnaire completion" + "entityAssessmentAuthorHelpText": "The representative who is responsible for questionnaire completion", + "sendQuestionnaire": "Send questionnaire", + "sureToSendMail": "Are you sure you want to send the mail?", + "mailSuccessfullySent": "The mail has been successfully sent", + "mailFailedToSend": "The mail failed to be sent" } diff --git a/frontend/src/lib/components/DetailView/DetailView.svelte b/frontend/src/lib/components/DetailView/DetailView.svelte index ec59b9440..506d96545 100644 --- a/frontend/src/lib/components/DetailView/DetailView.svelte +++ b/frontend/src/lib/components/DetailView/DetailView.svelte @@ -28,6 +28,7 @@ const defaultExcludes = ['id', 'is_published', 'localization_dict']; export let data; + export let mailing = false; export let fields: string[] = []; export let exclude: string[] = []; @@ -107,6 +108,27 @@ modalStore.trigger(modal); } + function modalMailConfirm(id: string, name: string, action: string): void { + const modalComponent: ModalComponent = { + ref: ConfirmModal, + props: { + _form: data.form, + id: id, + debug: false, + URLModel: getModelInfo('compliance-assessments').urlModel, + formAction: action + } + }; + const modal: ModalSettings = { + type: 'component', + component: modalComponent, + // Data + title: m.confirmModalTitle(), + body: m.sureToSendMail() + }; + modalStore.trigger(modal); + } + function getForms(model: Record) { let { form: createForm, message: createMessage } = superForm(model.createForm, { onUpdated: ({ form }) => @@ -254,13 +276,36 @@ {/each} - {#if displayEditButton()} - {m.edit()} - {/if} +
+ {#if mailing} + + {/if} + {#if displayEditButton()} + {m.edit()} + {/if} +
diff --git a/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.server.ts index 877881e67..2628a6ebb 100644 --- a/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.server.ts @@ -1,7 +1,40 @@ +import { BASE_API_URL } from '$lib/utils/constants'; import { getModelInfo } from '$lib/utils/crud'; import { loadDetail } from '$lib/utils/load'; -import type { PageServerLoad } from './$types'; +import { fail, superValidate } from 'sveltekit-superforms'; +import type { Actions, PageServerLoad } from './$types'; +import { z } from 'zod'; +import { zod } from 'sveltekit-superforms/adapters'; +import * as m from '$paraglide/messages'; +import { setFlash } from 'sveltekit-flash-message/server'; export const load: PageServerLoad = async (event) => { return loadDetail({ event, model: getModelInfo('entity-assessments'), id: event.params.id }); }; + +export const actions: Actions = { + mailing: async ({ request, fetch, cookies }) => { + const formData = await request.formData(); + const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); + const ComplianceAssessmentForm = await superValidate(formData, zod(schema)); + + const urlmodel = ComplianceAssessmentForm.data.urlmodel; + const id = ComplianceAssessmentForm.data.id; + const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/mailing/`; + + if (!ComplianceAssessmentForm.valid) { + return fail(400, { form: ComplianceAssessmentForm }); + } + + const requestInitOptions: RequestInit = { + method: 'POST' + }; + const res = await fetch(endpoint, requestInitOptions); + if (!res.ok) { + setFlash({ type: 'error', message: m.mailFailedToSend() }, cookies); + return fail(400, { form: ComplianceAssessmentForm }); + } + setFlash({ type: 'success', message: m.mailSuccessfullySent() }, cookies); + return { ComplianceAssessmentForm }; + } +}; diff --git a/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.svelte index 221006076..ff43763c8 100644 --- a/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/entity-assessments/[id=uuid]/+page.svelte @@ -12,11 +12,14 @@ import { breadcrumbObject } from '$lib/utils/stores'; breadcrumbObject.set(data.data); + + const mailing = Boolean(data.data.compliance_assessment) && Boolean(data.data.authors.length);
{#if data.data.compliance_assessment} @@ -34,7 +37,8 @@ goto(href); } }} - >{m.questionnaire()} + > + {m.questionnaire()} {#if Object.hasOwn($page.state, 'auditTableMode')}
From 62121913c90d14e0436e02a4b52e15f8860f579b Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Mon, 23 Sep 2024 13:11:05 +0200 Subject: [PATCH 03/26] style: make assessment/questionnaire mode sticky top --- .../[id=uuid]/table-mode/+page.svelte | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) 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 63d2aef92..3d26dad7c 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 @@ -157,26 +157,28 @@ class="card px-6 py-4 bg-white flex flex-col justify-between shadow-lg w-full h-full space-y-2" > {#if !(questionnaireOnly ? !assessmentOnly : assessmentOnly)} -
- {#if questionnaireMode} -

{m.assessmentMode()}

- {:else} -

{m.assessmentMode()}

- {/if} - (questionnaireMode = !questionnaireMode)} - > +
+
{#if questionnaireMode} -

{m.questionnaireMode()}

+

{m.assessmentMode()}

{:else} -

{m.questionnaireMode()}

+

{m.assessmentMode()}

{/if} - + (questionnaireMode = !questionnaireMode)} + > + {#if questionnaireMode} +

{m.questionnaireMode()}

+ {:else} +

{m.questionnaireMode()}

+ {/if} +
+
{/if} {#each data.requirements as requirementAssessment} From 59cf03b8ee94d8bc415a7b155bf51a84d738014d Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 25 Sep 2024 15:12:01 +0200 Subject: [PATCH 04/26] feat: allow assessment mode for third party --- .../compliance-assessments/[id=uuid]/table-mode/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3d26dad7c..b7072ca5e 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 @@ -31,7 +31,7 @@ export let shallow = false; export let actionPath: string = ''; - export let questionnaireOnly: boolean = $page.data.user.is_third_party; + export let questionnaireOnly: boolean = false; export let assessmentOnly: boolean = false; export let invalidateAll: boolean = true; From 1918317807c9918cd58391d973f760e4c474476a Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 25 Sep 2024 15:25:49 +0200 Subject: [PATCH 05/26] feat: allow requirement assessments for third party --- backend/core/startup.py | 1 + .../[id=uuid]/+page.svelte | 131 +++++++++--------- .../[id=uuid]/+page.server.ts | 0 .../[id=uuid]/+page.svelte | 0 .../[id=uuid]/edit/+page.server.ts | 0 .../[id=uuid]/edit/+page.svelte | 12 +- 6 files changed, 72 insertions(+), 72 deletions(-) rename frontend/src/routes/(app)/{(internal) => (third-party)}/requirement-assessments/[id=uuid]/+page.server.ts (100%) rename frontend/src/routes/(app)/{(internal) => (third-party)}/requirement-assessments/[id=uuid]/+page.svelte (100%) rename frontend/src/routes/(app)/{(internal) => (third-party)}/requirement-assessments/[id=uuid]/edit/+page.server.ts (100%) rename frontend/src/routes/(app)/{(internal) => (third-party)}/requirement-assessments/[id=uuid]/edit/+page.svelte (97%) diff --git a/backend/core/startup.py b/backend/core/startup.py index 8c2deb181..f6755dffd 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -284,6 +284,7 @@ "view_complianceassessment", "view_requirementassessment", "change_requirementassessment", + "view_requirementnode", "view_evidence", "add_evidence", "change_evidence", 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 aad68ae6e..c74c5af59 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 @@ -242,51 +242,47 @@
{/each}
- {#if !$page.data.user.is_third_party} -
- {#if data.global_score.score >= 0} -
{m.maturity()}
-
- - {data.global_score.score} - -
- {/if} -
- -
- object.itemStyle.color - )} - /> -
-
- object.itemStyle.color - )} - /> -
- {/if} +
+ {#if data.global_score.score >= 0} +
{m.maturity()}
+
+ + {data.global_score.score} + +
+ {/if} +
+
+ object.itemStyle.color + )} + /> +
+
+ object.itemStyle.color + )} + /> +
- {#if !$page.data.user.is_third_party}
... {m.asZIP()} -

{m.actionPlan()}

- ... {m.asPDF()} + {#if !$page.data.user.is_third_party} +

{m.actionPlan()}

+ ... {m.asPDF()} + {/if}
{#if canEditObject} {/if}
- {m.actionPlan()} - {/if} + {#if !$page.data.user.is_third_party} + {m.actionPlan()} + {/if} Power-ups: {#if !$page.data.user.is_third_party}
- {#if !$page.data.user.is_third_party} -
-

- {m.associatedRequirements()} - - {assessableNodesCount(treeViewNodes)} - -

-
- -

{m.mappingInferenceTip()}

-
- +
+

+ {m.associatedRequirements()} + + {assessableNodesCount(treeViewNodes)} + +

+
+ +

{m.mappingInferenceTip()}

- {/if} + +
diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/+page.server.ts rename to frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/+page.svelte rename to frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts rename to frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte similarity index 97% rename from frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.svelte rename to frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte index 926160ef4..447fa4d48 100644 --- a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte @@ -182,7 +182,7 @@ $: classesText = complianceResultColorMap[mappingInference.result] === '#000000' ? 'text-white' : ''; - let tabSet = 0; + let tabSet = $page.data.user.is_third_party ? 1: 0;
@@ -326,12 +326,14 @@ >
- {m.appliedControls()} - + {#if !$page.data.user.is_third_party} + {m.appliedControls()} + + {/if} {m.evidences()} - {#if tabSet === 0} + {#if tabSet === 0 && !$page.data.user.is_third_party}

{m.requirementAppliedControlHelpText()}

From 65f1bd75cae634abaf5c0014a0b834408dfbf10d Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 25 Sep 2024 16:13:56 +0200 Subject: [PATCH 06/26] feat: add question tag in audit tree view --- backend/core/helpers.py | 2 ++ frontend/messages/en.json | 3 ++- .../[id=uuid]/TreeViewItemContent.svelte | 5 +++++ .../requirement-assessments/[id=uuid]/+page.svelte | 13 ++++++++----- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/core/helpers.py b/backend/core/helpers.py index d139eeefe..6745f371b 100644 --- a/backend/core/helpers.py +++ b/backend/core/helpers.py @@ -286,6 +286,7 @@ def get_sorted_requirement_nodes_rec(start: list) -> dict: "is_scored": req_as.is_scored if req_as else None, "score": req_as.score if req_as else None, "max_score": max_score if req_as else None, + "question": req_as.answer if req_as else None, "mapping_inference": req_as.mapping_inference if req_as else None, "status_display": req_as.get_status_display() if req_as else None, "status_i18n": camel_case(req_as.status) if req_as else None, @@ -323,6 +324,7 @@ def get_sorted_requirement_nodes_rec(start: list) -> dict: "is_scored": child_req_as.is_scored if child_req_as else None, "score": child_req_as.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 None, "mapping_inference": child_req_as.mapping_inference if child_req_as else None, diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 0fe80eb21..105885750 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -734,5 +734,6 @@ "sendQuestionnaire": "Send questionnaire", "sureToSendMail": "Are you sure you want to send the mail?", "mailSuccessfullySent": "The mail has been successfully sent", - "mailFailedToSend": "The mail failed to be sent" + "mailFailedToSend": "The mail failed to be sent", + "questionOrQuestions": "Question(s)" } 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 292f849bf..35493c800 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 @@ -7,6 +7,7 @@ import { displayScoreColor, formatScoreValue } from '$lib/utils/helpers'; import { safeTranslate } from '$lib/utils/i18n'; import type { z } from 'zod'; + import * as m from '$paraglide/messages'; export let ref_id: string; export let name: string; @@ -152,6 +153,10 @@ {/if} {/each} {/if} + {#if node.question.questions} + {node.question.questions.length} {m.questionOrQuestions()} + {/if}
{#if (threats && threats.length > 0) || (reference_controls && reference_controls.length > 0)} diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte index b896c16e1..9d5df5109 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte @@ -9,6 +9,7 @@ import ModelTable from '$lib/components/ModelTable/ModelTable.svelte'; import { ProgressRadial, Tab, TabGroup } from '@skeletonlabs/skeleton'; import { displayScoreColor, formatScoreValue, getSecureRedirect } from '$lib/utils/helpers'; + import { page } from '$app/stores'; export let data: PageData; const threats = data.requirement.threats; @@ -65,7 +66,7 @@ const max_score = data.complianceAssessmentScore.max_score; const value = data.requirementAssessment.score; - let tabSet = 0; + let tabSet = $page.data.user.is_third_party ? 1: 0;
@@ -222,12 +223,14 @@ {/if}
- {m.appliedControls()} - + {#if !$page.data.user.is_third_party} + {m.appliedControls()} + + {/if} {m.evidences()} - {#if tabSet === 0} + {#if tabSet === 0 && !$page.data.user.is_third_party}

{m.requirementAppliedControlHelpText()}

From 622222b82477e8ff8762aab5cd47e915fe471743 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 25 Sep 2024 16:14:35 +0200 Subject: [PATCH 07/26] chore: run format --- .../[id=uuid]/+page.svelte | 64 +++++++++---------- .../[id=uuid]/TreeViewItemContent.svelte | 5 +- .../[id=uuid]/+page.svelte | 2 +- .../[id=uuid]/edit/+page.svelte | 2 +- 4 files changed, 37 insertions(+), 36 deletions(-) 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 c74c5af59..f8f4d9887 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 @@ -283,45 +283,45 @@ />
-
- + +
+

{m.complianceAssessment()}

+ +
... {m.asCSV()} -
... {m.asZIP()} -

{m.complianceAssessment()}

- - ... {m.asCSV()} + {#if !$page.data.user.is_third_party} +

{m.actionPlan()}

... {m.asZIP()} - {#if !$page.data.user.is_third_party} -

{m.actionPlan()}

- ... {m.asPDF()} - {/if} -
- {#if canEditObject} - {m.edit()}... {m.asPDF()} {/if}
- {#if !$page.data.user.is_third_party} - {m.actionPlan()} {m.edit()} - {/if} + {/if} +
+ {#if !$page.data.user.is_third_party} + {m.actionPlan()} + {/if} Power-ups: {#if !$page.data.user.is_third_party} {node.question.questions.length} {m.questionOrQuestions()} + {node.question.questions.length} {m.questionOrQuestions()} {/if}
diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte index 9d5df5109..f1a4b46d9 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte @@ -66,7 +66,7 @@ const max_score = data.complianceAssessmentScore.max_score; const value = data.requirementAssessment.score; - let tabSet = $page.data.user.is_third_party ? 1: 0; + let tabSet = $page.data.user.is_third_party ? 1 : 0;
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 447fa4d48..b3dec230d 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 @@ -182,7 +182,7 @@ $: classesText = complianceResultColorMap[mappingInference.result] === '#000000' ? 'text-white' : ''; - let tabSet = $page.data.user.is_third_party ? 1: 0; + let tabSet = $page.data.user.is_third_party ? 1 : 0;
From 773e52c722937b87fe593b94c757a9176342f0f6 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 25 Sep 2024 16:30:37 +0200 Subject: [PATCH 08/26] feat: add questions in audit report --- backend/core/locale/fr/LC_MESSAGES/django.po | 326 +++++++++--------- backend/core/templates/snippets/req_node.html | 11 + backend/locale/fr/LC_MESSAGES/django.po | 11 +- 3 files changed, 185 insertions(+), 163 deletions(-) diff --git a/backend/core/locale/fr/LC_MESSAGES/django.po b/backend/core/locale/fr/LC_MESSAGES/django.po index 4c182e090..061ba6e20 100644 --- a/backend/core/locale/fr/LC_MESSAGES/django.po +++ b/backend/core/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-09-13 19:22+0000\n" +"POT-Creation-Date: 2024-09-25 14:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -52,7 +52,7 @@ msgstr "Nom" msgid "Description" msgstr "Description" -#: core/base_models.py:126 core/models.py:1309 +#: core/base_models.py:126 core/models.py:1310 #: core/templates/core/action_plan_pdf.html:46 #: core/templates/snippets/mp_data.html:62 msgid "ETA" @@ -232,7 +232,7 @@ msgstr "Locale par défaut" msgid "Copyright" msgstr "Copyright" -#: core/models.py:189 core/models.py:1443 +#: core/models.py:189 core/models.py:1444 msgid "Version" msgstr "Version" @@ -252,11 +252,11 @@ msgstr "Dépendances" msgid "Threat" msgstr "Menace" -#: core/models.py:709 core/models.py:960 core/models.py:1849 +#: core/models.py:709 core/models.py:960 core/models.py:1850 msgid "Threats" msgstr "Menaces" -#: core/models.py:729 core/models.py:1414 +#: core/models.py:729 core/models.py:1415 msgid "Policy" msgstr "Politique" @@ -296,12 +296,12 @@ msgstr "" msgid "Recover" msgstr "" -#: core/models.py:757 core/models.py:1283 +#: core/models.py:757 core/models.py:1284 #: core/templates/core/action_plan_pdf.html:44 msgid "Category" msgstr "Catégorie" -#: core/models.py:765 core/models.py:1290 +#: core/models.py:765 core/models.py:1291 #, fuzzy #| msgid "Search function..." msgid "CSF Function" @@ -343,15 +343,15 @@ msgstr "" "Si la matrice de risque est désactivée, elle ne sera pas disponible pour la " "sélection lors de nouvelles évaluations de risque." -#: core/models.py:891 core/models.py:2065 +#: core/models.py:891 core/models.py:2066 msgid "Minimum score" msgstr "Score minimum" -#: core/models.py:892 core/models.py:2066 +#: core/models.py:892 core/models.py:2067 msgid "Maximum score" msgstr "Score maximum" -#: core/models.py:894 core/models.py:2068 +#: core/models.py:894 core/models.py:2069 msgid "Score definition" msgstr "Définition du score" @@ -359,7 +359,7 @@ msgstr "Définition du score" msgid "Implementation groups definition" msgstr "Définition des groupes d'implémentation" -#: core/models.py:908 core/models.py:974 core/models.py:2059 +#: core/models.py:908 core/models.py:974 core/models.py:2060 #: core/templates/core/action_plan_pdf.html:20 msgid "Framework" msgstr "Référentiel" @@ -486,7 +486,7 @@ msgstr "Exigence source" msgid "Strength of relationship" msgstr "" -#: core/models.py:1103 core/models.py:1251 +#: core/models.py:1103 core/models.py:1252 msgid "Undefined" msgstr "Non défini" @@ -514,13 +514,13 @@ msgstr "Abandonné" msgid "Internal reference" msgstr "Référence interne" -#: core/models.py:1117 core/models.py:1297 core/models.py:1450 -#: core/models.py:2497 core/templates/snippets/mp_data.html:66 +#: core/models.py:1117 core/models.py:1298 core/models.py:1451 +#: core/models.py:2498 core/templates/snippets/mp_data.html:66 #: core/templates/snippets/ri_list_nested.html:31 msgid "Status" msgstr "Statut" -#: core/models.py:1122 core/models.py:1436 core/models.py:1929 +#: core/models.py:1122 core/models.py:1437 core/models.py:1930 #: core/templates/core/action_plan_pdf.html:16 #: core/templates/snippets/mp_data.html:7 msgid "Project" @@ -550,7 +550,7 @@ msgstr "type" msgid "parent assets" msgstr "actifs parents" -#: core/models.py:1172 core/models.py:1836 +#: core/models.py:1172 core/models.py:1837 msgid "Assets" msgstr "Actifs" @@ -567,181 +567,181 @@ msgstr "" msgid "Attachment" msgstr "Pièce jointe" -#: core/models.py:1210 +#: core/models.py:1211 msgid "Link to the evidence (eg. Jira ticket, etc.)" msgstr "Lien vers la preuve (par ex. billet Jira, etc.)" -#: core/models.py:1211 core/models.py:1322 +#: core/models.py:1212 core/models.py:1323 #: core/templates/snippets/mp_data.html:65 msgid "Link" msgstr "Lien" -#: core/models.py:1217 +#: core/models.py:1218 msgid "Evidence" msgstr "Preuve" -#: core/models.py:1218 core/models.py:1275 core/models.py:2517 +#: core/models.py:1219 core/models.py:1276 core/models.py:2518 msgid "Evidences" msgstr "Preuves" -#: core/models.py:1246 core/models.py:2481 +#: core/models.py:1247 core/models.py:2482 msgid "To do" msgstr "À faire" -#: core/models.py:1247 core/models.py:1430 core/models.py:2482 +#: core/models.py:1248 core/models.py:1431 core/models.py:2483 msgid "In progress" msgstr "En cours" -#: core/models.py:1248 +#: core/models.py:1249 msgid "On hold" msgstr "" -#: core/models.py:1249 +#: core/models.py:1250 msgid "Active" msgstr "Actif" -#: core/models.py:1250 core/models.py:1433 +#: core/models.py:1251 core/models.py:1434 msgid "Deprecated" msgstr "Déprécié" -#: core/models.py:1257 +#: core/models.py:1258 msgid "Small" msgstr "Petit" -#: core/models.py:1258 core/models.py:1813 core/tests/test_helpers.py:77 +#: core/models.py:1259 core/models.py:1814 core/tests/test_helpers.py:77 #: core/tests/test_helpers.py:142 msgid "Medium" msgstr "Moyen" -#: core/models.py:1259 +#: core/models.py:1260 msgid "Large" msgstr "Grand" -#: core/models.py:1260 +#: core/models.py:1261 msgid "Extra Large" msgstr "Extra large" -#: core/models.py:1270 +#: core/models.py:1271 msgid "Reference Control" msgstr "Mesure de référence" -#: core/models.py:1302 core/models.py:1865 +#: core/models.py:1303 core/models.py:1866 msgid "Owner" msgstr "Propriétaire" -#: core/models.py:1308 +#: core/models.py:1309 msgid "Estimated Time of Arrival" msgstr "Heure d'arrivée estimée" -#: core/models.py:1314 +#: core/models.py:1315 msgid "Date after which the applied control is no longer valid" msgstr "Date à partir de laquelle la mesure appliquée n'est plus valide" -#: core/models.py:1315 core/models.py:2625 +#: core/models.py:1316 core/models.py:2626 #: core/templates/core/action_plan_pdf.html:47 msgid "Expiry date" msgstr "Date d'expiration" -#: core/models.py:1321 +#: core/models.py:1322 msgid "External url for action follow-up (eg. Jira ticket)" msgstr "URL externe pour le suivi de l'action (par ex. billet Jira)" -#: core/models.py:1329 +#: core/models.py:1330 msgid "Relative effort of the measure (using T-Shirt sizing)" msgstr "Effort relatif de la mesure (en utilisant la taille des T-shirts)" -#: core/models.py:1330 core/templates/core/action_plan_pdf.html:48 +#: core/models.py:1331 core/templates/core/action_plan_pdf.html:48 #: core/templates/snippets/mp_data.html:63 msgid "Effort" msgstr "Effort" -#: core/models.py:1334 +#: core/models.py:1335 msgid "Cost of the measure (using globally-chosen currency)" msgstr "" -#: core/models.py:1335 core/templates/core/action_plan_pdf.html:49 +#: core/models.py:1336 core/templates/core/action_plan_pdf.html:49 #: core/templates/snippets/mp_data.html:64 msgid "Cost" msgstr "" -#: core/models.py:1341 +#: core/models.py:1342 msgid "Applied control" msgstr "Mesure appliqué" -#: core/models.py:1342 core/models.py:1843 core/models.py:2533 +#: core/models.py:1343 core/models.py:1844 core/models.py:2534 msgid "Applied controls" msgstr "Mesures appliquées" -#: core/models.py:1415 +#: core/models.py:1416 msgid "Policies" msgstr "Politiques" -#: core/models.py:1429 +#: core/models.py:1430 msgid "Planned" msgstr "Planifié" -#: core/models.py:1431 core/models.py:2483 +#: core/models.py:1432 core/models.py:2484 msgid "In review" msgstr "En examen" -#: core/models.py:1432 core/models.py:2484 +#: core/models.py:1433 core/models.py:2485 msgid "Done" msgstr "Terminé" -#: core/models.py:1442 +#: core/models.py:1443 msgid "Version of the compliance assessment (eg. 1.0, 2.0, etc.)" msgstr "Version de l'évaluation de conformité (par ex. 1.0, 2.0, etc.)" -#: core/models.py:1457 +#: core/models.py:1458 msgid "Authors" msgstr "Auteurs" -#: core/models.py:1463 core/templates/core/audit_report.html:35 +#: core/models.py:1464 core/templates/core/audit_report.html:35 msgid "Reviewers" msgstr "Relecteurs" -#: core/models.py:1466 core/models.py:2520 +#: core/models.py:1467 core/models.py:2521 msgid "Observation" msgstr "Observation" -#: core/models.py:1483 +#: core/models.py:1484 msgid "WARNING! After choosing it, you will not be able to change it" msgstr "" "ATTENTION ! Une fois que vous l'aurez choisi, vous ne pourrez plus le " "modifier." -#: core/models.py:1484 +#: core/models.py:1485 msgid "Risk matrix" msgstr "Matrice de risque" -#: core/models.py:1488 core/templates/snippets/mp_data.html:9 +#: core/models.py:1489 core/templates/snippets/mp_data.html:9 #: core/templates/snippets/ra_data.html:8 msgid "Risk assessment" msgstr "Évaluation de risque" -#: core/models.py:1489 +#: core/models.py:1490 msgid "Risk assessments" msgstr "Évaluations de risque" -#: core/models.py:1513 +#: core/models.py:1514 msgid "{}: Risk assessment is still in progress" msgstr "{} : L'évaluation des risques est toujours en cours" -#: core/models.py:1524 +#: core/models.py:1525 msgid "{}: No author assigned to this risk assessment" msgstr "{} : Aucun auteur n'a été affecté à cette évaluation des risques" -#: core/models.py:1536 +#: core/models.py:1537 msgid "{}: RiskAssessment is empty. No risk scenario declared yet" msgstr "" "{} : L'évaluation des risques est vide. Aucun scénario de risque n'a encore " "été déclaré." -#: core/models.py:1557 +#: core/models.py:1558 msgid "{} current risk level has not been assessed" msgstr "{} : Le niveau de risque actuel n'a pas été évalué" -#: core/models.py:1570 +#: core/models.py:1571 msgid "" "{} residual risk level has not been assessed. If no additional measures are " "applied, it should be at the same level as the current risk" @@ -750,43 +750,43 @@ msgstr "" "mesures supplémentaires appliquées, il devrait être au même niveau que le " "risque actuel." -#: core/models.py:1581 +#: core/models.py:1582 msgid "{} residual risk level is higher than the current one" msgstr "{} : Le niveau de risque résiduel est supérieur à l'actuel" -#: core/models.py:1593 +#: core/models.py:1594 msgid "{} residual risk probability is higher than the current one" msgstr "{} : La probabilité de risque résiduel est supérieure à l'actuelle" -#: core/models.py:1605 +#: core/models.py:1606 msgid "{} residual risk impact is higher than the current one" msgstr "{} : L'impact du risque résiduel est supérieur à l'actuel" -#: core/models.py:1626 +#: core/models.py:1627 msgid "{}: residual risk level has been lowered without any specific measure" msgstr "" "{} : Le niveau de risque résiduel a été réduit sans aucune mesure spécifique" -#: core/models.py:1640 +#: core/models.py:1641 msgid "{} risk accepted but no risk acceptance attached" msgstr "{} : Risque accepté mais aucune acceptation de risque jointe" -#: core/models.py:1663 +#: core/models.py:1664 msgid "{} does not have an ETA" msgstr "{} : Pas d'ETA" -#: core/models.py:1675 +#: core/models.py:1676 msgid "{} ETA is in the past now. Consider updating its status or the date" msgstr "" "{} : L'ETA est dans le passé maintenant. Envisagez de mettre à jour son " "statut ou la date." -#: core/models.py:1688 +#: core/models.py:1689 msgid "" "{} does not have an estimated effort. This will help you for prioritization" msgstr "{} : Pas d'effort estimé. Cela vous aidera pour la prioritisation" -#: core/models.py:1701 +#: core/models.py:1702 #, fuzzy #| msgid "" #| "{} does not have an estimated effort. This will help you for " @@ -795,7 +795,7 @@ msgid "" "{} does not have an estimated cost. This will help you for prioritization" msgstr "{} : Pas d'effort estimé. Cela vous aidera pour la prioritisation" -#: core/models.py:1714 +#: core/models.py:1715 msgid "" "{}: Applied control does not have an external link attached. This will help " "you for follow-up" @@ -803,112 +803,112 @@ msgstr "" "{} : La mesure appliquée ne comporte pas de lien externe joint. Cela vous " "aidera pour le suivi" -#: core/models.py:1737 +#: core/models.py:1738 msgid "{}: Acceptance has no expiry date" msgstr "{} : L'acceptation n'a pas de date d'expiration" -#: core/models.py:1751 +#: core/models.py:1752 msgid "{}: Acceptance has expired. Consider updating the status or the date" msgstr "" "{} : L'acceptation a expiré. Envisagez de mettre à jour le statut ou la date." -#: core/models.py:1780 +#: core/models.py:1781 msgid "Open" msgstr "Ouvert" -#: core/models.py:1781 +#: core/models.py:1782 msgid "Mitigate" msgstr "Atténuer" -#: core/models.py:1782 +#: core/models.py:1783 msgid "Accept" msgstr "Accepter" -#: core/models.py:1783 +#: core/models.py:1784 msgid "Avoid" msgstr "Éviter" -#: core/models.py:1784 +#: core/models.py:1785 msgid "Transfer" msgstr "Transférer" -#: core/models.py:1788 +#: core/models.py:1789 msgid "Financial" msgstr "" -#: core/models.py:1789 +#: core/models.py:1790 msgid "Legal" msgstr "" -#: core/models.py:1790 +#: core/models.py:1791 #, fuzzy #| msgid "Rationale" msgid "Reputation" msgstr "Raison" -#: core/models.py:1791 +#: core/models.py:1792 #, fuzzy #| msgid "Observation" msgid "Operational" msgstr "Observation" -#: core/models.py:1792 +#: core/models.py:1793 msgid "Confidentiality" msgstr "" -#: core/models.py:1793 +#: core/models.py:1794 msgid "Integrity" msgstr "" -#: core/models.py:1794 +#: core/models.py:1795 #, fuzzy #| msgid "Probability" msgid "Availability" msgstr "Probabilité" -#: core/models.py:1795 +#: core/models.py:1796 msgid "Authenticity" msgstr "" -#: core/models.py:1800 +#: core/models.py:1801 msgid "--" msgstr "--" -#: core/models.py:1802 +#: core/models.py:1803 msgid "The strength of the knowledge supporting the assessment is undefined" msgstr "La force de la connaissance soutenant l'évaluation est indéfinie" -#: core/models.py:1806 core/tests/test_helpers.py:76 +#: core/models.py:1807 core/tests/test_helpers.py:76 #: core/tests/test_helpers.py:141 msgid "Low" msgstr "Faible" -#: core/models.py:1808 +#: core/models.py:1809 msgid "The strength of the knowledge supporting the assessment is low" msgstr "La force de la connaissance soutenant l'évaluation est faible" -#: core/models.py:1815 +#: core/models.py:1816 msgid "The strength of the knowledge supporting the assessment is medium" msgstr "La force de la connaissance soutenant l'évaluation est moyenne" -#: core/models.py:1820 core/tests/test_helpers.py:78 +#: core/models.py:1821 core/tests/test_helpers.py:78 #: core/tests/test_helpers.py:143 msgid "High" msgstr "Élevé" -#: core/models.py:1822 +#: core/models.py:1823 msgid "The strength of the knowledge supporting the assessment is high" msgstr "La force de la connaissance soutenant l'évaluation est élevée" -#: core/models.py:1831 +#: core/models.py:1832 msgid "RiskAssessment" msgstr "ÉvaluationDesRisques" -#: core/models.py:1838 +#: core/models.py:1839 msgid "Assets impacted by the risk scenario" msgstr "Actifs impactés par le scénario de risque" -#: core/models.py:1856 +#: core/models.py:1857 msgid "" "The existing controls to manage this risk. Edit the risk scenario to add " "extra applied controls." @@ -916,24 +916,24 @@ msgstr "" "Les mesures existantes pour gérer ce risque. Modifiez le scénario de risque " "pour ajouter des mesures appliquées supplémentaires." -#: core/models.py:1858 core/templates/snippets/mp_data.html:48 +#: core/models.py:1859 core/templates/snippets/mp_data.html:48 #: core/templates/snippets/ri_list_nested.html:22 msgid "Existing controls" msgstr "Mesures existantes" -#: core/models.py:1870 +#: core/models.py:1871 msgid "Current probability" msgstr "Probabilité actuelle" -#: core/models.py:1873 +#: core/models.py:1874 msgid "Current impact" msgstr "Impact actuel" -#: core/models.py:1877 +#: core/models.py:1878 msgid "Current level" msgstr "Niveau actuel" -#: core/models.py:1879 +#: core/models.py:1880 msgid "" "The risk level given the current measures. Automatically updated on Save, " "based on the chosen risk matrix" @@ -942,19 +942,19 @@ msgstr "" "automatiquement lors de l'enregistrement, sur la base de la matrice de " "risque choisie." -#: core/models.py:1885 +#: core/models.py:1886 msgid "Residual probability" msgstr "Probabilité résiduelle" -#: core/models.py:1888 +#: core/models.py:1889 msgid "Residual impact" msgstr "Impact résiduel" -#: core/models.py:1892 +#: core/models.py:1893 msgid "Residual level" msgstr "Niveau résiduel" -#: core/models.py:1894 +#: core/models.py:1895 msgid "" "The risk level when all the extra measures are done. Automatically updated " "on Save, based on the chosen risk matrix" @@ -963,61 +963,61 @@ msgstr "" "œuvre. Mis à jour automatiquement lors de l'enregistrement, sur la base de " "la matrice de risque choisie." -#: core/models.py:1902 +#: core/models.py:1903 msgid "Treatment status" msgstr "Statut de traitement" -#: core/models.py:1905 +#: core/models.py:1906 #, fuzzy #| msgid "Justification" msgid "Qualifications" msgstr "Justification" -#: core/models.py:1909 +#: core/models.py:1910 msgid "Strength of Knowledge" msgstr "Force de la connaissance" -#: core/models.py:1910 +#: core/models.py:1911 msgid "The strength of the knowledge supporting the assessment" msgstr "La force de la connaissance soutenant l'évaluation" -#: core/models.py:1913 core/models.py:2637 +#: core/models.py:1914 core/models.py:2638 msgid "Justification" msgstr "Justification" -#: core/models.py:1919 +#: core/models.py:1920 msgid "Risk scenario" msgstr "Scénario de risque" -#: core/models.py:1920 core/models.py:2602 +#: core/models.py:1921 core/models.py:2603 msgid "Risk scenarios" msgstr "Scénarios de risque" -#: core/models.py:2030 +#: core/models.py:2031 msgid ": " msgstr ": " -#: core/models.py:2062 core/templates/core/audit_report.html:43 +#: core/models.py:2063 core/templates/core/audit_report.html:43 msgid "Selected implementation groups" msgstr "Groupes d'implémentation sélectionnés" -#: core/models.py:2072 core/models.py:2524 +#: core/models.py:2073 core/models.py:2525 msgid "Compliance assessment" msgstr "Évaluation de conformité" -#: core/models.py:2073 +#: core/models.py:2074 msgid "Compliance assessments" msgstr "Évaluations de conformité" -#: core/models.py:2310 +#: core/models.py:2311 msgid "{}: Compliance assessment is still in progress" msgstr "{} : L'évaluation de la conformité est toujours en cours" -#: core/models.py:2323 +#: core/models.py:2324 msgid "{}: No author assigned to this compliance assessment" msgstr "{} : Aucun auteur n'a été affecté à cette évaluation de conformité" -#: core/models.py:2350 +#: core/models.py:2351 #, fuzzy #| msgid "" #| "{}: Requirement assessment status is compliant or partially compliant " @@ -1029,94 +1029,94 @@ msgstr "" "{} : Le statut de l'évaluation de l'exigence est conforme ou partiellement " "conforme, sans aucun mesure appliquée" -#: core/models.py:2375 +#: core/models.py:2376 msgid "{}: Applied control has no reference control selected" msgstr "" "{} : La mesure appliquée ne comporte pas de mesure de référence sélectionné" -#: core/models.py:2401 +#: core/models.py:2402 msgid "{}: Evidence has no file uploaded" msgstr "{} : La preuve ne comporte pas de fichier téléchargé" -#: core/models.py:2487 +#: core/models.py:2488 msgid "Not assessed" msgstr "Non évalué" -#: core/models.py:2488 +#: core/models.py:2489 msgid "Partially compliant" msgstr "Partiellement conforme" -#: core/models.py:2489 +#: core/models.py:2490 #, fuzzy #| msgid "Non compliant" msgid "Non-compliant" msgstr "Non conforme" -#: core/models.py:2490 +#: core/models.py:2491 msgid "Compliant" msgstr "Conforme" -#: core/models.py:2491 +#: core/models.py:2492 msgid "Not applicable" msgstr "Non applicable" -#: core/models.py:2502 +#: core/models.py:2503 msgid "Result" msgstr "Résultat" -#: core/models.py:2508 +#: core/models.py:2509 msgid "Score" msgstr "Score" -#: core/models.py:2512 +#: core/models.py:2513 msgid "Is scored" msgstr "Est noté" -#: core/models.py:2528 +#: core/models.py:2529 msgid "Requirement" msgstr "Exigence" -#: core/models.py:2538 +#: core/models.py:2539 msgid "Selected" msgstr "Sélectionné" -#: core/models.py:2542 +#: core/models.py:2543 msgid "Mapping inference" msgstr "Inference de mapping" -#: core/models.py:2547 +#: core/models.py:2548 msgid "Answer" msgstr "" -#: core/models.py:2584 +#: core/models.py:2585 msgid "Requirement assessment" msgstr "Évaluation de l'exigence" -#: core/models.py:2585 +#: core/models.py:2586 msgid "Requirement assessments" msgstr "Évaluations de l'exigence" -#: core/models.py:2593 +#: core/models.py:2594 msgid "Created" msgstr "Créé" -#: core/models.py:2594 +#: core/models.py:2595 msgid "Submitted" msgstr "Soumis" -#: core/models.py:2595 +#: core/models.py:2596 msgid "Accepted" msgstr "Accepté" -#: core/models.py:2596 +#: core/models.py:2597 msgid "Rejected" msgstr "Rejeté" -#: core/models.py:2597 +#: core/models.py:2598 msgid "Revoked" msgstr "Révoqué" -#: core/models.py:2604 +#: core/models.py:2605 msgid "" "Select the risk scenarios to be accepted, attention they must be part of the " "chosen domain" @@ -1124,39 +1124,39 @@ msgstr "" "Sélectionnez les scénarios de risque à accepter, faites attention, ils " "doivent faire partie du domaine choisi" -#: core/models.py:2610 +#: core/models.py:2611 msgid "Risk owner and approver identity" msgstr "Identité du propriétaire du risque et de l'approbateur" -#: core/models.py:2611 core/utils.py:50 core/utils.py:57 core/utils.py:60 +#: core/models.py:2612 core/utils.py:52 core/utils.py:60 core/utils.py:63 msgid "Approver" msgstr "Approbateur" -#: core/models.py:2620 +#: core/models.py:2621 msgid "State" msgstr "État" -#: core/models.py:2623 +#: core/models.py:2624 msgid "Specify when the risk acceptance will no longer apply" msgstr "Précisez quand l'acceptation du risque ne sera plus valable" -#: core/models.py:2628 +#: core/models.py:2629 msgid "Acceptance date" msgstr "Date d'acceptation" -#: core/models.py:2631 +#: core/models.py:2632 msgid "Rejection date" msgstr "Date de rejet" -#: core/models.py:2634 +#: core/models.py:2635 msgid "Revocation date" msgstr "Date de révocation" -#: core/models.py:2646 +#: core/models.py:2647 msgid "Risk acceptance" msgstr "Acceptation du risque" -#: core/models.py:2647 +#: core/models.py:2648 msgid "Risk acceptances" msgstr "Acceptations du risque" @@ -1375,21 +1375,25 @@ msgstr "Actuel" msgid "Residual" msgstr "Résiduel" -#: core/templates/snippets/req_node.html:27 +#: core/templates/snippets/req_node.html:32 +msgid "No answer" +msgstr "Aucun réponse" + +#: core/templates/snippets/req_node.html:38 #, fuzzy #| msgid "Observation" msgid "Observation:" msgstr "Observation" -#: core/templates/snippets/req_node.html:36 +#: core/templates/snippets/req_node.html:47 msgid "Associated evidence:" msgstr "Preuves associées :" -#: core/templates/snippets/req_node.html:50 +#: core/templates/snippets/req_node.html:61 msgid "Applied controls:" msgstr "Mesures appliquées :" -#: core/templates/snippets/req_node.html:51 +#: core/templates/snippets/req_node.html:62 msgid "Evidence of applied controls:" msgstr "Preuves des mesures appliquées :" @@ -1443,30 +1447,38 @@ msgstr "Très faible" msgid "Very High" msgstr "Très élevé" -#: core/utils.py:47 core/utils.py:55 +#: core/utils.py:49 core/utils.py:58 msgid "Administrator" msgstr "Administrateur" -#: core/utils.py:48 core/utils.py:58 +#: core/utils.py:50 core/utils.py:61 msgid "Domain manager" msgstr "Gestionnaire de domaine" -#: core/utils.py:49 core/utils.py:59 +#: core/utils.py:51 core/utils.py:62 msgid "Analyst" msgstr "Analyste" -#: core/utils.py:51 core/utils.py:56 core/utils.py:61 +#: core/utils.py:53 core/utils.py:59 core/utils.py:64 msgid "Reader" msgstr "Lecteur" -#: core/utils.py:70 +#: core/utils.py:54 core/utils.py:65 +msgid "Third-party respondent" +msgstr "" + +#: core/utils.py:74 msgid "French" msgstr "Français" -#: core/utils.py:71 +#: core/utils.py:75 msgid "English" msgstr "Anglais" +#: core/views.py:1668 +msgid "CISO Assistant: A questionnaire has been assigned to you" +msgstr "" + #~ msgid "Inactive" #~ msgstr "Inactif" diff --git a/backend/core/templates/snippets/req_node.html b/backend/core/templates/snippets/req_node.html index 091e8439b..d90c910fe 100644 --- a/backend/core/templates/snippets/req_node.html +++ b/backend/core/templates/snippets/req_node.html @@ -23,6 +23,17 @@ {% if node.assessments.requirement.description %}
{{ node.assessments.requirement.get_description_translated }}
{% endif %} + {% if node.assessments.answer %} + {% for question in node.assessments.answer.questions %} +

{{ question.text }}

+ {% if question.answer %} + {{ question.answer }} + {% else %} + {% trans "No answer" %} + {% endif %} +
+ {% endfor %} + {% endif %} {% if node.assessments.observation %}

{% trans "Observation:" %}

{{ node.assessments.observation }}

{% endif %} diff --git a/backend/locale/fr/LC_MESSAGES/django.po b/backend/locale/fr/LC_MESSAGES/django.po index 0f8fd93e6..01d714301 100644 --- a/backend/locale/fr/LC_MESSAGES/django.po +++ b/backend/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-09-13 19:22+0000\n" +"POT-Creation-Date: 2024-09-25 14:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -156,7 +156,7 @@ msgstr "" msgid "Key" msgstr "" -#: iam/views.py:96 +#: iam/views.py:97 msgid "CISO Assistant: Password Reset" msgstr "" @@ -236,10 +236,9 @@ msgstr "" msgid "Solution" msgstr "" -#: tprm/serializers.py:61 tprm/serializers.py:96 +#: tprm/serializers.py:73 msgid "Framework required" msgstr "" -#: tprm/serializers.py:116 -msgid "CISO Assistant: A questionnaire has been assigned to you" -msgstr "CISO Assistant: Un questionnaire vous a été assigné" +#~ msgid "CISO Assistant: A questionnaire has been assigned to you" +#~ msgstr "CISO Assistant: Un questionnaire vous a été assigné" From 4457da6eb89f06f6b3ec357f8b514b72221f68c2 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 25 Sep 2024 16:33:46 +0200 Subject: [PATCH 09/26] feat: hide csv export for third party --- .../compliance-assessments/[id=uuid]/+page.svelte | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 f8f4d9887..f6571e628 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 @@ -292,11 +292,12 @@ data-popup="popupDownload" >

{m.complianceAssessment()}

- -
... {m.asCSV()} + {#if !$page.data.user.is_third_party} + ... {m.asCSV()} + {/if} ... {m.asZIP()} Date: Fri, 27 Sep 2024 10:51:06 +0200 Subject: [PATCH 10/26] feat: align tprm permissions with access control model --- backend/core/startup.py | 1 + backend/iam/models.py | 8 +++++++- backend/iam/views.py | 1 + .../src/lib/components/SideBar/SideBarNavigation.svelte | 6 +++++- frontend/src/lib/components/SideBar/navData.ts | 3 ++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/backend/core/startup.py b/backend/core/startup.py index f6755dffd..7d19b5120 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -289,6 +289,7 @@ "add_evidence", "change_evidence", "delete_evidence", + "view_folder" ] diff --git a/backend/iam/models.py b/backend/iam/models.py index 08b21d686..788fe6cfc 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -469,6 +469,12 @@ def mailing(self, email_template_name, subject, object="", object_id="", pk=Fals def get_user_groups(self): """get the list of user groups containing the user in the form (group_name, builtin)""" return [(x.__str__(), x.builtin) for x in self.user_groups.all()] + + def get_roles(self): + """get the list of roles attached to the user""" + return list(self.user_groups.all() + .values_list('roleassignment__role__name', flat=True) + .distinct()) @property def has_backup_permission(self) -> bool: @@ -655,7 +661,7 @@ def get_accessible_object_ids( for ra in [ x for x in RoleAssignment.get_role_assignments(user) - if ref_permission in x.role.permissions.all() or user.is_third_party + if ref_permission in x.role.permissions.all() ]: ra_permissions = ra.role.permissions.all() for my_folder in perimeter & set(ra.perimeter_folders.all()): diff --git a/backend/iam/views.py b/backend/iam/views.py index 72e4b2f9e..942ae18b5 100644 --- a/backend/iam/views.py +++ b/backend/iam/views.py @@ -75,6 +75,7 @@ def get(self, request) -> Response: "is_active": request.user.is_active, "date_joined": request.user.date_joined, "user_groups": request.user.get_user_groups(), + "roles": request.user.get_roles(), "permissions": request.user.permissions, "is_third_party": request.user.is_third_party, } diff --git a/frontend/src/lib/components/SideBar/SideBarNavigation.svelte b/frontend/src/lib/components/SideBar/SideBarNavigation.svelte index 3d01ba382..273771c29 100644 --- a/frontend/src/lib/components/SideBar/SideBarNavigation.svelte +++ b/frontend/src/lib/components/SideBar/SideBarNavigation.svelte @@ -27,7 +27,11 @@ .map((item) => { // Check and filter the sub-items based on user permissions const filteredSubItems = item.items.filter((subItem) => { - if (subItem.permissions) { + if (subItem.exclude){ + return subItem.exclude.some((role) => + !user.roles.includes(role) + ); + } else if (subItem.permissions) { return subItem.permissions.some((permission) => Object.hasOwn(user.permissions, permission) ); diff --git a/frontend/src/lib/components/SideBar/navData.ts b/frontend/src/lib/components/SideBar/navData.ts index baa13b253..5842e1417 100644 --- a/frontend/src/lib/components/SideBar/navData.ts +++ b/frontend/src/lib/components/SideBar/navData.ts @@ -127,7 +127,8 @@ export const navData = { { name: 'domains', fa_icon: 'fa-solid fa-sitemap', - href: '/folders' + href: '/folders', + exclude: ['BI-RL-TPR'] }, { name: 'projects', From 4e9bce629396e44556524953f3bad11ec64e4497 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Fri, 27 Sep 2024 10:51:55 +0200 Subject: [PATCH 11/26] chore: run format --- backend/core/startup.py | 2 +- backend/iam/models.py | 10 ++++++---- .../lib/components/SideBar/SideBarNavigation.svelte | 6 ++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/core/startup.py b/backend/core/startup.py index 7d19b5120..ec5079096 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -289,7 +289,7 @@ "add_evidence", "change_evidence", "delete_evidence", - "view_folder" + "view_folder", ] diff --git a/backend/iam/models.py b/backend/iam/models.py index 788fe6cfc..c0b6ccd8c 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -469,12 +469,14 @@ def mailing(self, email_template_name, subject, object="", object_id="", pk=Fals def get_user_groups(self): """get the list of user groups containing the user in the form (group_name, builtin)""" return [(x.__str__(), x.builtin) for x in self.user_groups.all()] - + def get_roles(self): """get the list of roles attached to the user""" - return list(self.user_groups.all() - .values_list('roleassignment__role__name', flat=True) - .distinct()) + return list( + self.user_groups.all() + .values_list("roleassignment__role__name", flat=True) + .distinct() + ) @property def has_backup_permission(self) -> bool: diff --git a/frontend/src/lib/components/SideBar/SideBarNavigation.svelte b/frontend/src/lib/components/SideBar/SideBarNavigation.svelte index 273771c29..375fe0f5b 100644 --- a/frontend/src/lib/components/SideBar/SideBarNavigation.svelte +++ b/frontend/src/lib/components/SideBar/SideBarNavigation.svelte @@ -27,10 +27,8 @@ .map((item) => { // Check and filter the sub-items based on user permissions const filteredSubItems = item.items.filter((subItem) => { - if (subItem.exclude){ - return subItem.exclude.some((role) => - !user.roles.includes(role) - ); + if (subItem.exclude) { + return subItem.exclude.some((role) => !user.roles.includes(role)); } else if (subItem.permissions) { return subItem.permissions.some((permission) => Object.hasOwn(user.permissions, permission) From 3b0b85b34b59a6c771181353c9994dc1aec0340c Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Tue, 1 Oct 2024 12:27:29 +0200 Subject: [PATCH 12/26] feat: add representatives field in entity assessment --- .../0003_entityassessment_representatives.py | 24 +++++++++ backend/tprm/models.py | 6 +++ backend/tprm/serializers.py | 54 ++++++++++++------- .../ModelForm/EntityAssessmentForm.svelte | 11 +++- frontend/src/lib/utils/crud.ts | 3 +- frontend/src/lib/utils/schemas.ts | 3 +- 6 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 backend/tprm/migrations/0003_entityassessment_representatives.py diff --git a/backend/tprm/migrations/0003_entityassessment_representatives.py b/backend/tprm/migrations/0003_entityassessment_representatives.py new file mode 100644 index 000000000..66d0fb8e1 --- /dev/null +++ b/backend/tprm/migrations/0003_entityassessment_representatives.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1 on 2024-10-01 09:14 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tprm", "0002_alter_entity_reference_link"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="entityassessment", + name="representatives", + field=models.ManyToManyField( + blank=True, + related_name="entity_assessments", + to=settings.AUTH_USER_MODEL, + verbose_name="Representative", + ), + ), + ] diff --git a/backend/tprm/models.py b/backend/tprm/models.py index 6fe6815bd..9a5063f65 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/models.py @@ -40,6 +40,12 @@ class Conclusion(models.TextChoices): dependency = models.IntegerField(default=0, verbose_name=_("Dependency")) maturity = models.IntegerField(default=0, verbose_name=_("Maturity")) trust = models.IntegerField(default=0, verbose_name=_("Trust")) + representatives = models.ManyToManyField( + User, + blank=True, + verbose_name=_("Representative"), + related_name="entity_assessments", + ) entity = models.ForeignKey( Entity, on_delete=models.CASCADE, diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index 398b8ab19..b5fd5dbda 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -8,7 +8,6 @@ from django.contrib.auth import get_user_model from tprm.models import Entity, EntityAssessment, Representative, Solution from django.utils.translation import gettext_lazy as _ -from ciso_assistant.settings import EMAIL_HOST, EMAIL_HOST_RESCUE import structlog @@ -39,7 +38,7 @@ class EntityAssessmentReadSerializer(BaseModelSerializer): entity = FieldsRelatedField() folder = FieldsRelatedField() solutions = FieldsRelatedField(many=True) - authors = FieldsRelatedField(many=True) + representatives = FieldsRelatedField(many=True) reviewers = FieldsRelatedField(many=True) class Meta: @@ -48,7 +47,7 @@ class Meta: class EntityAssessmentWriteSerializer(BaseModelSerializer): - create_audit = serializers.BooleanField(default=True) + create_audit = serializers.BooleanField(default=False) framework = serializers.PrimaryKeyRelatedField( queryset=Framework.objects.all(), required=False ) @@ -82,23 +81,31 @@ def _create_or_update_audit(self, instance, audit_data): ], ) - if not instance.compliance_assessment: - enclave = Folder.objects.create( - content_type=Folder.ContentType.ENCLAVE, - name=f"{instance.project.name}/{instance.name}", - parent_folder=instance.folder, - ) - audit.folder = enclave - audit.save() + enclave = Folder.objects.create( + content_type=Folder.ContentType.ENCLAVE, + name=f"{instance.project.name}/{instance.name}", + parent_folder=instance.folder, + ) + audit.folder = enclave + audit.save() audit.create_requirement_assessments() - audit.authors.set(instance.authors.all()) audit.reviewers.set(instance.reviewers.all()) + audit.authors.set(instance.representatives.all()) instance.compliance_assessment = audit instance.save() + else: + if instance.compliance_assessment: + audit = instance.compliance_assessment + audit.reviewers.set(instance.reviewers.all()) + audit.authors.set(instance.representatives.all()) + instance.save() def _assign_third_party_respondents( - self, instance: EntityAssessment, third_party_users: set[User] + self, + instance: EntityAssessment, + third_party_users: set[User], + old_third_party_users: set[User] = set(), ): if instance.compliance_assessment: enclave = instance.compliance_assessment.folder @@ -119,25 +126,32 @@ def _assign_third_party_respondents( if not user.is_third_party: logger.warning("User is not a third-party", user=user) user.user_groups.add(respondents) + for user in old_third_party_users: + if not user.is_third_party: + logger.warning("User is not a third-party", user=user) + user.user_groups.remove(respondents) def create(self, validated_data): audit_data = self._extract_audit_data(validated_data) instance = super().create(validated_data) self._create_or_update_audit(instance, audit_data) - self._assign_third_party_respondents(instance, set(instance.authors.all())) + self._assign_third_party_respondents( + instance, set(instance.representatives.all()) + ) return instance def update(self, instance: EntityAssessment, validated_data): audit_data = self._extract_audit_data(validated_data) - new_authors = set(validated_data.get("authors", [])) - set( - instance.authors.all() + representatives = set(validated_data.get("representatives", [])) + old_representatives = set(instance.representatives.all()) - set( + validated_data.get("representatives", []) ) instance = super().update(instance, validated_data) - if not instance.compliance_assessment: - self._create_or_update_audit(instance, audit_data) - - self._assign_third_party_respondents(instance, new_authors) + self._create_or_update_audit(instance, audit_data) + self._assign_third_party_respondents( + instance, representatives, old_representatives + ) return instance class Meta: diff --git a/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte b/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte index c349a41ba..c49383b70 100644 --- a/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte @@ -144,11 +144,20 @@ multiple options={getOptions({ objects: model.foreignKeys['authors'], label: 'email' })} field="authors" - helpText={m.entityAssessmentAuthorHelpText()} cacheLock={cacheLocks['authors']} bind:cachedValue={formDataCache['authors']} label={m.authors()} /> + Date: Thu, 3 Oct 2024 12:33:35 +0200 Subject: [PATCH 13/26] feat: improve layout --- frontend/messages/en.json | 5 +++-- .../components/Forms/ModelForm/EntityAssessmentForm.svelte | 3 ++- .../(internal)/entity-assessments/[id=uuid]/+page.svelte | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 7be636d6b..8a047525b 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -739,7 +739,7 @@ "createUserHelpText": "Create or link a third party user to the representative based on the email", "nameDuplicate": "Name already exists", "noAnswer": "No answer", - "entityAssessmentAuthorHelpText": "The representative who is responsible for questionnaire completion", + "entityAssessmentRepresentativesHelpText": "The third party users who are responsible for questionnaire completion", "sendQuestionnaire": "Send questionnaire", "sureToSendMail": "Are you sure you want to send the mail?", "mailSuccessfullySent": "The mail has been successfully sent", @@ -755,5 +755,6 @@ "theFollowingControlsWillBeAddedColon": "The following controls will be added:", "ShowAllNodesMessage": "Show all", "ShowOnlyAssessable": "Only assessable", - "NoPreviewMessage": "No preview available." + "NoPreviewMessage": "No preview available.", + "entityAssessmentEvidenceHelpText": "An external questionnaire" } diff --git a/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte b/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte index c49383b70..dc8ed0042 100644 --- a/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte @@ -153,7 +153,7 @@ multiple options={getOptions({ objects: model.foreignKeys['representatives'], label: 'email' })} field="representatives" - helpText={m.entityAssessmentAuthorHelpText()} + helpText={m.entityAssessmentRepresentativesHelpText()} cacheLock={cacheLocks['representatives']} bind:cachedValue={formDataCache['representatives']} label={m.representatives()} @@ -187,6 +187,7 @@ cacheLock={cacheLocks['evidence']} bind:cachedValue={formDataCache['evidence']} label={m.evidence()} + helpText={m.entityAssessmentEvidenceHelpText()} /> - - - {#if shape.reference_control} - { - if (e.detail) { - await fetch(`/reference-controls/${e.detail}`) - .then((r) => r.json()) - .then((r) => { - form.form.update((currentData) => { - if ( - context === 'edit' && - currentData['reference_control'] === initialData['reference_control'] && - !updated_fields.has('reference_control') - ) { - return currentData; // Keep the current values in the edit form. - } - updated_fields.add('reference_control'); - return { ...currentData, category: r.category, csf_function: r.csf_function }; - }); - }); - } - }} - /> - {/if} - {#if shape.name} - - {/if} - {#if shape.description} -