diff --git a/backend/core/migrations/0003_alter_riskscenario_strength_of_knowledge.py b/backend/core/migrations/0003_alter_riskscenario_strength_of_knowledge.py new file mode 100644 index 000000000..011b043b6 --- /dev/null +++ b/backend/core/migrations/0003_alter_riskscenario_strength_of_knowledge.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-02-26 14:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='riskscenario', + name='strength_of_knowledge', + field=models.IntegerField(default=-1, help_text='The strength of the knowledge supporting the assessment', verbose_name='Strength of Knowledge'), + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 5069db3c7..a44996839 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -998,12 +998,35 @@ class RiskScenario(NameDescriptionMixin): ("transfer", _("Transfer")), ] - SOK_OPTIONS = [ - ("--", _("--")), - ("0", _("Low")), - ("1", _("Medium")), - ("2", _("High")), - ] + DEFAULT_SOK_OPTIONS = { + -1: { + "name": _("--"), + "description": _( + "The strength of the knowledge supporting the assessment is undefined" + ), + }, + 0: { + "name": _("Low"), + "description": _( + "The strength of the knowledge supporting the assessment is low" + ), + "symbol": "◇", + }, + 1: { + "name": _("Medium"), + "description": _( + "The strength of the knowledge supporting the assessment is medium" + ), + "symbol": "⬙", + }, + 2: { + "name": _("High"), + "description": _( + "The strength of the knowledge supporting the assessment is high" + ), + "symbol": "◆", + }, + } risk_assessment = models.ForeignKey( RiskAssessment, @@ -1076,11 +1099,10 @@ class RiskScenario(NameDescriptionMixin): verbose_name=_("Treatment status"), ) - strength_of_knowledge = models.CharField( - max_length=20, - choices=SOK_OPTIONS, - default="--", + strength_of_knowledge = models.IntegerField( + default=-1, verbose_name=_("Strength of Knowledge"), + help_text=_("The strength of the knowledge supporting the assessment"), ) justification = models.CharField( max_length=500, blank=True, null=True, verbose_name=_("Justification") @@ -1148,6 +1170,11 @@ def get_residual_proba(self): risk_matrix = self.get_matrix() return risk_matrix["probability"][self.residual_proba] + def get_strength_of_knowledge(self): + if self.strength_of_knowledge < 0: + return self.DEFAULT_SOK_OPTIONS[-1] + return self.DEFAULT_SOK_OPTIONS[self.strength_of_knowledge] + def __str__(self): return ( str(self.parent_project().folder) diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 3981dcc13..c107983c7 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -198,7 +198,10 @@ class RiskScenarioReadSerializer(RiskScenarioWriteSerializer): residual_impact = serializers.CharField(source="get_residual_impact.name") residual_level = serializers.JSONField(source="get_residual_risk") + strength_of_knowledge = serializers.JSONField(source="get_strength_of_knowledge") + security_measures = FieldsRelatedField(many=True) + rid = serializers.CharField() class SecurityMeasureWriteSerializer(BaseModelSerializer): diff --git a/backend/core/views.py b/backend/core/views.py index 968b97076..34f6eb424 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -681,6 +681,29 @@ def impact(self, request, pk): choices = undefined | _choices return Response(choices) + @action(detail=True, name="Get strength of knowledge choices") + def strength_of_knowledge(self, request, pk): + undefined = {-1: RiskScenario.DEFAULT_SOK_OPTIONS[-1]} + _sok_choices = self.get_object().get_matrix().get("strength_of_knowledge") + if _sok_choices is not None: + sok_choices = dict( + zip( + list(range(0, 64)), + [ + { + "name": x["name"], + "description": x.get("description"), + "symbol": x.get("symbol"), + } + for x in _sok_choices + ], + ) + ) + else: + sok_choices = RiskScenario.DEFAULT_SOK_OPTIONS + choices = undefined | sok_choices + return Response(choices) + @action(detail=False, name="Get risk count per level") def count_per_level(self, request): return Response({"results": risks_count_per_level(request.user)}) diff --git a/backend/library/libraries/critical_matrix_3x3.yaml b/backend/library/libraries/critical_matrix_3x3.yaml index 33cc3d15c..ba72f3327 100644 --- a/backend/library/libraries/critical_matrix_3x3.yaml +++ b/backend/library/libraries/critical_matrix_3x3.yaml @@ -8,50 +8,50 @@ provider: intuitem packager: intuitem objects: risk_matrix: - - urn: urn:intuitem:risk:matrix:critical_risk_matrix_3x3 - ref_id: critical_3x3 - name: critical 3x3 - description: critical risk matrix 3x3 - probability: - - abbreviation: L - name: Low - description: Unfrequent event - - abbreviation: M - name: Medium - description: Occasional event - - abbreviation: H - name: High - description: Frequent event - impact: - - abbreviation: L - name: Low - description: Low impact - - abbreviation: M - name: Medium - description: Medium impact - - abbreviation: H - name: High - description: High impact - risk: - - abbreviation: L - name: Low - description: acceptable risk - hexcolor: "#00FF00" - - abbreviation: M - name: Medium - description: risk requiring mitigation within 2 years - hexcolor: "#FFFF00" - - abbreviation: H - name: High - description: unacceptable risk - hexcolor: "#FF0000" - grid: - - - 0 - - 1 - - 1 - - - 1 - - 1 - - 2 - - - 1 - - 2 - - 2 + - urn: urn:intuitem:risk:matrix:critical_risk_matrix_3x3 + ref_id: critical_3x3 + name: critical 3x3 + description: critical risk matrix 3x3 + probability: + - abbreviation: L + name: Low + description: Unfrequent event + - abbreviation: M + name: Medium + description: Occasional event + - abbreviation: H + name: High + description: Frequent event + impact: + - abbreviation: L + name: Low + description: Low impact + - abbreviation: M + name: Medium + description: Medium impact + - abbreviation: H + name: High + description: High impact + risk: + - abbreviation: L + name: Low + description: acceptable risk + hexcolor: "#00FF00" + - abbreviation: M + name: Medium + description: risk requiring mitigation within 2 years + hexcolor: "#FFFF00" + - abbreviation: H + name: High + description: unacceptable risk + hexcolor: "#FF0000" + grid: + - - 0 + - 1 + - 1 + - - 1 + - 1 + - 2 + - - 1 + - 2 + - 2 diff --git a/backend/library/libraries/critical_matrix_5x5.yaml b/backend/library/libraries/critical_matrix_5x5.yaml index c44d2d997..89c3c7f10 100644 --- a/backend/library/libraries/critical_matrix_5x5.yaml +++ b/backend/library/libraries/critical_matrix_5x5.yaml @@ -8,86 +8,86 @@ provider: intuitem packager: intuitem objects: risk_matrix: - - urn: urn:intuitem:risk:matrix:critical_risk_matrix_5x5 - ref_id: critical_5x5 - name: critical 5x5 - description: critical risk matrix 5x5 - probability: - - abbreviation: VL - name: Very Low - description: Very unfrequent event - - abbreviation: L - name: Low - description: Unfrequent event - - abbreviation: M - name: Medium - description: Occasional event - - abbreviation: H - name: High - description: Frequent event - - abbreviation: VH - name: Very High - description: Very frequent event - impact: - - abbreviation: VL - name: Very Low - description: Very low impact - - abbreviation: L - name: Low - description: Low impact - - abbreviation: M - name: Medium - description: Medium impact - - abbreviation: H - name: High - description: High impact - - abbreviation: VH - name: Very High - description: Very high impact - risk: - - abbreviation: VL - name: Very Low - description: negligible risk - hexcolor: "#BBF7D0" - - abbreviation: L - name: Low - description: acceptable risk - hexcolor: "#BEF264" - - abbreviation: M - name: Medium - description: risk requiring mitigation within 2 years - hexcolor: "#FEF08A" - - abbreviation: H - name: High - description: risk requiring mitigation within 6 months - hexcolor: "#FBBF24" - - abbreviation: VH - name: Very High - description: unacceptable risk - hexcolor: "#F87171" - grid: - - - 0 - - 0 - - 1 - - 1 - - 2 - - - 0 - - 1 - - 1 - - 2 - - 2 - - - 1 - - 1 - - 2 - - 2 - - 3 - - - 1 - - 2 - - 2 - - 3 - - 4 - - - 2 - - 2 - - 3 - - 4 - - 4 + - urn: urn:intuitem:risk:matrix:critical_risk_matrix_5x5 + ref_id: critical_5x5 + name: critical 5x5 + description: critical risk matrix 5x5 + probability: + - abbreviation: VL + name: Very Low + description: Very unfrequent event + - abbreviation: L + name: Low + description: Unfrequent event + - abbreviation: M + name: Medium + description: Occasional event + - abbreviation: H + name: High + description: Frequent event + - abbreviation: VH + name: Very High + description: Very frequent event + impact: + - abbreviation: VL + name: Very Low + description: Very low impact + - abbreviation: L + name: Low + description: Low impact + - abbreviation: M + name: Medium + description: Medium impact + - abbreviation: H + name: High + description: High impact + - abbreviation: VH + name: Very High + description: Very high impact + risk: + - abbreviation: VL + name: Very Low + description: negligible risk + hexcolor: "#BBF7D0" + - abbreviation: L + name: Low + description: acceptable risk + hexcolor: "#BEF264" + - abbreviation: M + name: Medium + description: risk requiring mitigation within 2 years + hexcolor: "#FEF08A" + - abbreviation: H + name: High + description: risk requiring mitigation within 6 months + hexcolor: "#FBBF24" + - abbreviation: VH + name: Very High + description: unacceptable risk + hexcolor: "#F87171" + grid: + - - 0 + - 0 + - 1 + - 1 + - 2 + - - 0 + - 1 + - 1 + - 2 + - 2 + - - 1 + - 1 + - 2 + - 2 + - 3 + - - 1 + - 2 + - 2 + - 3 + - 4 + - - 2 + - 2 + - 3 + - 4 + - 4 diff --git a/backend/library/utils.py b/backend/library/utils.py index be7603a03..a02cd557e 100644 --- a/backend/library/utils.py +++ b/backend/library/utils.py @@ -287,7 +287,7 @@ def import_security_function(self, library_object: Library): # The couple (URN, locale) is unique. ===> Check this in the future class RiskMatrixImporter: REQUIRED_FIELDS = {"ref_id", "urn", "json_definition"} - MATRIX_FIELDS = {"probability", "impact", "risk", "grid"} + MATRIX_FIELDS = {"probability", "impact", "risk", "grid", "strength_of_knowledge"} def __init__(self, risk_matrix_data): self.risk_matrix_data = risk_matrix_data diff --git a/frontend/messages/en.json b/frontend/messages/en.json index e5ec8a27e..0be0465df 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -230,12 +230,16 @@ "draft": "Draft", "riskMatrixView": "Risk matrix view", "currentInMatrixView": "Current", - "residualInMatrixView": "Residual", "probability": "Probability", "riskLevels": "Risk levels", "riskLevel": "Risk level", "cancel": "Cancel", "save": "Save", + "assetsImpactedByTheRiskScenario": "Assets impacted by the risk scenario", + "ecistingMeasures": "Existing measures", + "theExistingSecurityMeasuresToManageThisRisk": "The existing security measures to manage this risk", + "currentRiskLevelGivenCurrentMeasures": "The current risk level given the current security measures", + "riskLevelWhenAllExtraMeasuresDone": "The risk level when all extra measures are done", "myUserGroups": "My user groups", "changePasswordText": "You can change your password here. You'll need to log in with your new password after this operation", "oldPassword": "Old password", @@ -340,6 +344,7 @@ "copyright": "Copyright", "uploadYourLibrary": "Upload your own library", "libraryFileInYaml": "Library file in YAML format", + "rid": "RID", "scope": "Scope", "auditor": "Auditor", "lastUpdate": "Last update", diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 90bfcf18b..13778fd5c 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -228,14 +228,21 @@ "asPDF": "en PDF", "asCSV": "en CSV", "draft": "Brouillon", - "riskMatrixView": "Vue de la matrice de risque", + "riskMatrixView": "Vue matricielle", "currentInMatrixView": "Courante", - "residualInMatrixView": "Residuelle", "probability": "Probabilité", "riskLevels": "Niveaux de risque", "riskLevel": "Niveau de risque", "cancel": "Annuler", "save": "Enregistrer", + "currentAssessment": "Évaluation actuelle", + "scope": "Périmètre", + "assetsImpactedByTheRiskScenario": "Biens sensibles impactés par le scénario de risque", + "ecistingMeasures": "Mesures existantes", + "theExistingSecurityMeasuresToManageThisRisk": "Les mesures de sécurité existantes pour gérer ce risque", + "currentRiskLevelGivenCurrentMeasures": "Niveau de risque courant compte tenu des mesures actuelles", + "targetAssessment": "Évaluation cible", + "riskLevelWhenAllExtraMeasuresDone": "Niveau de risque lorsque toutes les mesures supplémentaires sont appliquées", "myUserGroups": "Mes groupes d'utilisateurs", "changePasswordText": "Vous pouvez changer votre mot de passe ici. Vous devrez vous reconnecter avec le nouveau mot de passe après cette opération", "oldPassword": "Ancien mot de passe", diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index a8440f953..73eb6fa2a 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -229,7 +229,7 @@ {value.str ?? '-'} {/if} {:else if value && value.hexcolor} -

{value.name ?? value.str ?? '-'}

+

{value.name ?? value.str ?? '-'}

{:else} {value ?? '-'} {/if} diff --git a/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts b/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts index 532fdc4d4..cfccd5418 100644 --- a/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts +++ b/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/svelte'; import RiskMatrix from './RiskMatrix.svelte'; +import RiskScenarioItem from './RiskScenarioItem.svelte'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories const meta = { @@ -45,12 +46,61 @@ const riskMatrix = { }) }; +const riskScenarios = [ + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS1', + rid: 'R.1', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '◇' + } + }, + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS1', + rid: 'R.2', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '⬙' + } + }, + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS1', + rid: 'R.3', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '◆' + } + }, + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS4', + rid: 'R.1', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '◆' + } + } +}; + const sampleData = [ [['R.1'], ['R.5'], ['R.8']], [['R.7'], [], ['R.9', 'R.6']], [['R.2'], ['R.3'], ['R.4']] ]; +const sampleDataItems = [ + [[riskScenario], [], []], + [[], [], [riskScenario, riskScenario]], + [[], [riskScenario], []] +]; + export default meta; type Story = StoryObj; @@ -66,3 +116,11 @@ export const WithData: Story = { data: sampleData } }; + +export const WithRiskScenarioItemComponent: Story = { + args: { + riskMatrix: riskMatrix, + data: sampleDataItems, + dataItemComponent: RiskScenarioItem + } +}; diff --git a/frontend/src/lib/components/RiskMatrix/RiskMatrix.svelte b/frontend/src/lib/components/RiskMatrix/RiskMatrix.svelte index c249cdf10..64c408e1f 100644 --- a/frontend/src/lib/components/RiskMatrix/RiskMatrix.svelte +++ b/frontend/src/lib/components/RiskMatrix/RiskMatrix.svelte @@ -1,7 +1,8 @@ + +

+ {#if data.strength_of_knowledge && data.strength_of_knowledge.symbol !== undefined}{data.strength_of_knowledge.symbol}{/if}{data.rid} +

diff --git a/frontend/src/lib/utils/locales.ts b/frontend/src/lib/utils/locales.ts index 35fc9d758..30bfa75a3 100644 --- a/frontend/src/lib/utils/locales.ts +++ b/frontend/src/lib/utils/locales.ts @@ -12,7 +12,7 @@ export const LOCALE_MAP = { }; export function toCamelCase(str: string) { - return str.replace(/[_-]\w/g, match => match.charAt(1).toUpperCase()); + return str.replace(/[_-]\w/g, (match) => match.charAt(1).toUpperCase()); } export function capitalizeFirstLetter(str: string) { @@ -126,6 +126,7 @@ export function localItems(languageTag: string): LocalItems { dateJoined: m.dateJoined({ languageTag: languageTag }), version: m.version({ languageTag: languageTag }), treatment: m.treatment({ languageTag: languageTag }), + rid: m.rid({ languageTag: languageTag }), currentProba: m.currentProba({ languageTag: languageTag }), currentImpact: m.currentImpact({ languageTag: languageTag }), residualProba: m.residualProba({ languageTag: languageTag }), @@ -163,7 +164,9 @@ export function localItems(languageTag: string): LocalItems { associatedRiskAssessments: m.associatedRiskAssessments({ languageTag: languageTag }), associatedRiskScenarios: m.associatedRiskScenarios({ languageTag: languageTag }), associatedRiskAcceptances: m.associatedRiskAcceptances({ languageTag: languageTag }), - associatedComplianceAssessments: m.associatedComplianceAssessments({ languageTag: languageTag }), + associatedComplianceAssessments: m.associatedComplianceAssessments({ + languageTag: languageTag + }), associatedEvidences: m.associatedEvidences({ languageTag: languageTag }), associatedDomains: m.associatedDomains({ languageTag: languageTag }), associatedProjects: m.associatedProjects({ languageTag: languageTag }), diff --git a/frontend/src/lib/utils/schemas.ts b/frontend/src/lib/utils/schemas.ts index 7a6125ece..9e3d5c20f 100644 --- a/frontend/src/lib/utils/schemas.ts +++ b/frontend/src/lib/utils/schemas.ts @@ -80,6 +80,7 @@ export const RiskScenarioSchema = baseNamedObject({ residual_proba: z.number().optional(), residual_impact: z.number().optional(), treatment: z.string().optional(), + strength_of_knowledge: z.number().default(-1).optional(), justification: z.string().optional().nullable(), risk_assessment: z.string(), threats: z.string().uuid().optional().array(), diff --git a/frontend/src/lib/utils/types.ts b/frontend/src/lib/utils/types.ts index b351301cb..6a3536d21 100644 --- a/frontend/src/lib/utils/types.ts +++ b/frontend/src/lib/utils/types.ts @@ -97,6 +97,12 @@ export interface RiskLevel { residual: string[]; } +export interface StrengthOfKnowledgeEntry { + name: string; + description: string; + symbol: string; +} + export interface AggregatedData { names: string[]; } diff --git a/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte index 3dd9acd6f..67f7e67f3 100644 --- a/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte @@ -62,7 +62,7 @@ type: 'component', component: modalComponent, // Data - title: localItems(languageTag())['add' + capitalizeFirstLetter(data.model.localName)] + title: localItems(languageTag())['add' + capitalizeFirstLetter(model.info.localName)] }; modalStore.trigger(modal); } @@ -256,12 +256,16 @@ {#if tabSet === index}

- {localItems(languageTag())['associated' + capitalizeFirstLetter(model.info.localNamePlural)]} + {localItems(languageTag())[ + 'associated' + capitalizeFirstLetter(model.info.localNamePlural) + ]}

{localItems(languageTag())[ + 'add' + capitalizeFirstLetter(model.info.localName) + ]}
{#if model.table} diff --git a/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+layout.server.ts b/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+layout.server.ts index 28acea43e..62e9dc2a4 100644 --- a/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+layout.server.ts +++ b/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+layout.server.ts @@ -1,15 +1,11 @@ import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, type ModelMapEntry } from '$lib/utils/crud'; +import { getModelInfo } from '$lib/utils/crud'; import { modelSchema } from '$lib/utils/schemas'; -import { listViewFields } from '$lib/utils/table'; +import { tableSourceMapper, type TableSource } from '@skeletonlabs/skeleton'; import { superValidate } from 'sveltekit-superforms/server'; -import { z, type AnyZodObject } from 'zod'; +import { z } from 'zod'; import type { LayoutServerLoad } from './$types'; -import type { UUID } from 'crypto'; -import { tableSourceMapper, type TableSource } from '@skeletonlabs/skeleton'; -import type { SuperValidated } from 'sveltekit-superforms'; -import type { urlModel } from '$lib/utils/types'; export const load: LayoutServerLoad = async ({ fetch, params }) => { const endpoint = `${BASE_API_URL}/risk-assessments/${params.id}/`; @@ -18,139 +14,86 @@ export const load: LayoutServerLoad = async ({ fetch, params }) => { const scenarios = await fetch(`${BASE_API_URL}/risk-scenarios/?risk_assessment=${params.id}`) .then((res) => res.json()) .then((res) => res.results); - const scenariosFilter: string = - '?' + - scenarios.map((scenario: Record) => `risk_scenarios=${scenario.id}`).join('&'); - - const measures = await fetch(`${BASE_API_URL}/security-measures/${scenariosFilter}`).then((res) => - res.json().then((res) => { - const sorted = res.results.sort((a: Record, b: Record) => { - const dateA = new Date(a.created_at); - const dateB = new Date(b.created_at); - return dateA.getTime() - dateB.getTime(); - }); - return sorted; - }) - ); + const risk_matrix = await fetch( `${BASE_API_URL}/risk-matrices/${risk_assessment.risk_matrix.id}/` ).then((res) => res.json()); - // Create a lookup for measures based on their id - const measureLookup: { [id: string]: Record } = measures.reduce( - (acc: Record, measure: Record) => { - acc[measure.id] = measure; - return acc; - }, - {} - ); - - // Replace the measures' UUIDs in each scenario with the corresponding measure instances - const transformedScenarios = scenarios.map((scenario: Record) => ({ - ...scenario, - security_measures: scenario.security_measures.map((childId: UUID) => measureLookup[childId]) - })); - - risk_assessment.risk_scenarios = transformedScenarios; - risk_assessment.risk_matrix = risk_matrix; - - type RelatedModel = { - urlModel: urlModel; - info: ModelMapEntry; - table: TableSource; - deleteForm: SuperValidated; - createForm: SuperValidated; - foreignKeys: Record; - selectOptions: Record; + const scenariosTable: TableSource = { + head: [ + 'rid', + 'name', + 'threats', + 'existingMeasures', + 'currentLevel', + 'securityMeasures', + 'residualLevel' + ], + body: tableSourceMapper(scenarios, [ + 'rid', + 'name', + 'threats', + 'existing_measures', + 'current_level', + 'security_measures', + 'residual_level' + ]), + meta: scenarios }; - type RelatedModels = { - [K in urlModel]: RelatedModel; - }; + risk_assessment.risk_scenarios = scenarios; + risk_assessment.risk_matrix = risk_matrix; - const model = getModelInfo('risk-assessments'); - const relatedModels = {} as RelatedModels; + const deleteSchema = z.object({ id: z.string() }); + const scenarioDeleteForm = await superValidate(deleteSchema); - if (model.reverseForeignKeyFields) { - await Promise.all( - model.reverseForeignKeyFields.map(async (e) => { - const relEndpoint = `${BASE_API_URL}/${e.urlModel}/?${e.field}=${params.id}`; - const res = await fetch(relEndpoint); - const data = await res.json().then((res) => res.results); + const scenarioSchema = modelSchema('risk-scenarios'); + const initialData = { + risk_assessment: params.id + }; + const scenarioCreateForm = await superValidate(initialData, scenarioSchema, { + errors: false + }); + + const scenarioModel = getModelInfo('risk-scenarios'); + + const foreignKeys: Record = {}; + + if (scenarioModel.foreignKeyFields) { + for (const keyField of scenarioModel.foreignKeyFields) { + const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : ''; + const url = `${BASE_API_URL}/${keyField.urlModel}/${queryParams}`; + const response = await fetch(url); + if (response.ok) { + foreignKeys[keyField.field] = await response.json().then((data) => data.results); + } else { + console.error(`Failed to fetch data for ${keyField.field}: ${response.statusText}`); + } + } + } - const metaData = tableSourceMapper(data, ['id']); + scenarioModel.foreignKeys = foreignKeys; - const bodyData = tableSourceMapper( - data, - listViewFields[e.urlModel].body.filter((field) => field !== 'risk_assessment') - ); + const selectOptions: Record = {}; - const table: TableSource = { - head: listViewFields[e.urlModel].head.filter((field) => field !== 'Risk assessment'), - body: bodyData, - meta: metaData - }; - - const info = getModelInfo(e.urlModel); - const urlModel = e.urlModel; - - const deleteForm = await superValidate(z.object({ id: z.string().uuid() })); - const createSchema = modelSchema(e.urlModel); - const createForm = await superValidate( - { risk_assessment: risk_assessment.id }, - createSchema, - { - errors: false - } + if (scenarioModel.selectFields) { + for (const selectField of scenarioModel.selectFields) { + const url = `${BASE_API_URL}/risk-scenarios/${selectField.field}/`; + const response = await fetch(url); + if (response.ok) { + selectOptions[selectField.field] = await response.json().then((data) => + Object.entries(data).map(([key, value]) => ({ + label: value, + value: key + })) ); - - const foreignKeys: Record = {}; - - if (info.foreignKeyFields) { - for (const keyField of info.foreignKeyFields) { - const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : ''; - const url = `${BASE_API_URL}/${keyField.urlModel}/${queryParams}`; - const response = await fetch(url); - if (response.ok) { - foreignKeys[keyField.field] = await response.json().then((data) => data.results); - } else { - console.error(`Failed to fetch data for ${keyField.field}: ${response.statusText}`); - } - } - } - - const selectOptions: Record = {}; - - if (info.selectFields) { - for (const selectField of info.selectFields) { - const url = `${BASE_API_URL}/${urlModel}/${selectField.field}/`; - const response = await fetch(url); - if (response.ok) { - selectOptions[selectField.field] = await response.json().then((data) => - Object.entries(data).map(([key, value]) => ({ - label: value, - value: key - })) - ); - } else { - console.error( - `Failed to fetch data for ${selectField.field}: ${response.statusText}` - ); - } - } - } - relatedModels[e.urlModel] = { - urlModel, - info, - table, - deleteForm, - createForm, - foreignKeys, - selectOptions - }; - }) - ); + } else { + console.error(`Failed to fetch data for ${selectField.field}: ${response.statusText}`); + } + } } - return { risk_assessment, relatedModels }; + scenarioModel.selectOptions = selectOptions; + + return { risk_assessment, scenarioModel, scenariosTable, scenarioDeleteForm, scenarioCreateForm }; }; diff --git a/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+page.svelte index f221fcbbb..948844bc5 100644 --- a/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/risk-assessments/[id=uuid]/+page.svelte @@ -1,25 +1,23 @@