From 8215b9a1b2f708d591af23c1e5dca3edc9338cb9 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Wed, 18 Dec 2024 02:31:01 +0100 Subject: [PATCH 01/11] Add meta field to Ebios RM study model --- .../migrations/0007_ebiosrmstudy_meta.py | 20 +++ backend/ebios_rm/models.py | 135 ++++++++++++++++-- 2 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py diff --git a/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py b/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py new file mode 100644 index 000000000..ff9de025b --- /dev/null +++ b/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.4 on 2024-12-18 01:25 + +import core.validators +import ebios_rm.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ebios_rm', '0006_alter_attackpath_stakeholders'), + ] + + operations = [ + migrations.AddField( + model_name='ebiosrmstudy', + name='meta', + field=models.JSONField(default=ebios_rm.models.get_initial_meta, validators=[core.validators.JSONSchemaInstanceValidator({'$id': 'https://ciso-assistant.com/schemas/ebiosrmstudy/meta.schema.json', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'description': 'Metadata of the EBIOS RM Study', 'properties': {'workshops': {'description': 'A list of workshops, each containing steps', 'items': {'additionalProperties': False, 'properties': {'steps': {'description': 'The list of steps in the workshop', 'items': {'additionalProperties': False, 'properties': {'status': {'description': 'The current status of the step', 'enum': ['to_do', 'in_progress', 'done'], 'type': 'string'}}, 'required': ['status'], 'type': 'object'}, 'type': 'array'}}, 'required': ['steps'], 'type': 'object'}, 'type': 'array'}}, 'title': 'Metadata', 'type': 'object'})], verbose_name='Metadata'), + ), + ] diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 9fd9c0cd3..5ed0bbc95 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -1,7 +1,8 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import gettext_lazy as _ -from core.base_models import AbstractBaseModel, NameDescriptionMixin, ETADueDateMixin + +from core.base_models import AbstractBaseModel, ETADueDateMixin, NameDescriptionMixin from core.models import ( AppliedControl, Asset, @@ -10,9 +11,43 @@ RiskMatrix, Threat, ) +from core.validators import ( + JSONSchemaInstanceValidator, +) from iam.models import FolderMixin, User from tprm.models import Entity +INITIAL_META = { + "workshops": [ + { + "steps": [ + {"status": "to_do"}, + {"status": "to_do"}, + {"status": "to_do"}, + {"status": "to_do"}, + ] + }, + {"steps": [{"status": "to_do"}, { + "status": "to_do"}, {"status": "to_do"}]}, + {"steps": [{"status": "to_do"}, { + "status": "to_do"}, {"status": "to_do"}]}, + {"steps": [{"status": "to_do"}, {"status": "to_do"}]}, + { + "steps": [ + {"status": "to_do"}, + {"status": "to_do"}, + {"status": "to_do"}, + {"status": "to_do"}, + {"status": "to_do"}, + ] + }, + ] +} + + +def get_initial_meta(): + return INITIAL_META + class EbiosRMStudy(NameDescriptionMixin, ETADueDateMixin, FolderMixin): class Status(models.TextChoices): @@ -22,6 +57,43 @@ class Status(models.TextChoices): DONE = "done", _("Done") DEPRECATED = "deprecated", _("Deprecated") + META_JSONSCHEMA = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ciso-assistant.com/schemas/ebiosrmstudy/meta.schema.json", + "title": "Metadata", + "description": "Metadata of the EBIOS RM Study", + "type": "object", + "properties": { + "workshops": { + "type": "array", + "description": "A list of workshops, each containing steps", + "items": { + "type": "object", + "properties": { + "steps": { + "type": "array", + "description": "The list of steps in the workshop", + "items": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "The current status of the step", + "enum": ["to_do", "in_progress", "done"], + }, + }, + "required": ["status"], + "additionalProperties": False, + }, + }, + }, + "required": ["steps"], + "additionalProperties": False, + }, + } + }, + } + risk_matrix = models.ForeignKey( RiskMatrix, on_delete=models.PROTECT, @@ -86,7 +158,14 @@ class Status(models.TextChoices): verbose_name=_("Reviewers"), related_name="reviewers", ) - observation = models.TextField(null=True, blank=True, verbose_name=_("Observation")) + observation = models.TextField( + null=True, blank=True, verbose_name=_("Observation")) + + meta = models.JSONField( + default=get_initial_meta, + verbose_name=_("Metadata"), + validators=[JSONSchemaInstanceValidator(META_JSONSCHEMA)], + ) class Meta: verbose_name = _("Ebios RM Study") @@ -97,6 +176,20 @@ class Meta: def parsed_matrix(self): return self.risk_matrix.parse_json_translated() + def update_workshop_step_status(self, workshop: int, step: int, status: str): + if workshop < 1 or workshop > 5: + raise ValueError("Workshop must be between 1 and 5") + if step < 1 or step > len(self.meta["workshops"][workshop - 1]["steps"]): + raise ValueError( + f"Worshop {workshop} has only {len(self.meta['workshops'][workshop - 1]['steps'])} steps" + ) + if not status in ["to_do", "in_progress", "done"]: + raise ValueError( + "Status must be one of 'to_do', 'in_progress', 'done'") + self.meta["workshops"][workshop - + 1]["steps"][step - 1]["status"] = status + return self.save() + class FearedEvent(NameDescriptionMixin, FolderMixin): ebios_rm_study = models.ForeignKey( @@ -121,8 +214,10 @@ class FearedEvent(NameDescriptionMixin, FolderMixin): ref_id = models.CharField(max_length=100, blank=True) gravity = models.SmallIntegerField(default=-1, verbose_name=_("Gravity")) - is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) - justification = models.TextField(verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField( + verbose_name=_("Is selected"), default=False) + justification = models.TextField( + verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Feared event") @@ -217,8 +312,10 @@ class Pertinence(models.IntegerChoices): activity = models.PositiveSmallIntegerField( verbose_name=_("Activity"), default=0, validators=[MaxValueValidator(4)] ) - is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) - justification = models.TextField(verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField( + verbose_name=_("Is selected"), default=False) + justification = models.TextField( + verbose_name=_("Justification"), blank=True) def __str__(self) -> str: return f"{self.get_risk_origin_display()} - {self.target_objective}" @@ -321,8 +418,10 @@ class Category(models.TextChoices): validators=[MinValueValidator(1), MaxValueValidator(4)], ) - is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) - justification = models.TextField(verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField( + verbose_name=_("Is selected"), default=False) + justification = models.TextField( + verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Stakeholder") @@ -398,8 +497,10 @@ class AttackPath(NameDescriptionMixin, FolderMixin): ) ref_id = models.CharField(max_length=100, blank=True) - is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) - justification = models.TextField(verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField( + verbose_name=_("Is selected"), default=False) + justification = models.TextField( + verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Attack path") @@ -435,11 +536,15 @@ class OperationalScenario(AbstractBaseModel, FolderMixin): operating_modes_description = models.TextField( verbose_name=_("Operating modes description"), - help_text=_("Description of the operating modes of the operational scenario"), - ) - likelihood = models.SmallIntegerField(default=-1, verbose_name=_("Likelihood")) - is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) - justification = models.TextField(verbose_name=_("Justification"), blank=True) + help_text=_( + "Description of the operating modes of the operational scenario"), + ) + likelihood = models.SmallIntegerField( + default=-1, verbose_name=_("Likelihood")) + is_selected = models.BooleanField( + verbose_name=_("Is selected"), default=False) + justification = models.TextField( + verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Operational scenario") From ec4508ea3dfd6118c01e739a5274b9a82ee94731 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Wed, 18 Dec 2024 02:31:17 +0100 Subject: [PATCH 02/11] Add endpoint to edit status of step --- backend/ebios_rm/views.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py index 6d35d48a1..faa76e919 100644 --- a/backend/ebios_rm/views.py +++ b/backend/ebios_rm/views.py @@ -9,6 +9,7 @@ AttackPath, OperationalScenario, ) +from .serializers import EbiosRMStudyReadSerializer from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from rest_framework.decorators import action @@ -65,6 +66,21 @@ def likelihood(self, request, pk): choices = undefined | _choices return Response(choices) + @action( + detail=True, + methods=["patch"], + name="Update workshop step status", + url_path="workshop/(?P[1-5])/step/(?P[1-5])", + ) + def update_workshop_step_status(self, request, pk, workshop, step): + ebios_rm_study: EbiosRMStudy = self.get_object() + body = request.data + workshop = int(workshop) + step = int(step) + ebios_rm_study.update_workshop_step_status( + workshop, step, body.get("status")) + return Response(EbiosRMStudyReadSerializer(ebios_rm_study).data) + class FearedEventViewSet(BaseModelViewSet): model = FearedEvent From 949e26eb1cff3f37842ddb6c0007a1bdb25aa462 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Wed, 18 Dec 2024 02:31:27 +0100 Subject: [PATCH 03/11] Get actual status per step --- .../ebios-rm/[id=uuid]/+page.svelte | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte index 35c997cad..20b132b7a 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte @@ -22,95 +22,95 @@ ws1: [ { title: safeTranslate(m.ebiosWs1_1()), - status: 'done', + status: data.data.meta.workshops[0].steps[0].status, href: `${$page.url.pathname}/workshop-one/ebios-rm-study?activity=one&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs1_2()), - status: 'done', + status: data.data.meta.workshops[0].steps[1].status, href: `${$page.url.pathname}/workshop-one/ebios-rm-study?activity=two&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs1_3()), - status: 'to_do', + status: data.data.meta.workshops[0].steps[2].status, href: `${$page.url.pathname}/workshop-one/feared-events?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs1_4()), - status: 'to_do', + status: data.data.meta.workshops[0].steps[3].status, href: `${$page.url.pathname}/workshop-one/baseline?next=${$page.url.pathname}` } ], ws2: [ { title: safeTranslate(m.ebiosWs2_1()), - status: 'to_do', + status: data.data.meta.workshops[1].steps[0].status, href: `${$page.url.pathname}/workshop-two/ro-to?activity=one&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs2_2()), - status: 'to_do', + status: data.data.meta.workshops[1].steps[1].status, href: `${$page.url.pathname}/workshop-two/ro-to?activity=two&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs2_3()), - status: 'to_do', + status: data.data.meta.workshops[1].steps[2].status, href: `${$page.url.pathname}/workshop-two/ro-to?activity=three&next=${$page.url.pathname}` } ], ws3: [ { title: safeTranslate(m.ebiosWs3_1()), - status: 'to_do', + status: data.data.meta.workshops[2].steps[0].status, href: `${$page.url.pathname}/workshop-three/ecosystem?activity=one&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs3_2()), - status: 'to_do', + status: data.data.meta.workshops[2].steps[1].status, href: `${$page.url.pathname}/workshop-three/strategic-scenarios?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs3_3()), - status: 'done', + status: data.data.meta.workshops[2].steps[2].status, href: `${$page.url.pathname}/workshop-three/ecosystem?activity=three&next=${$page.url.pathname}` } ], ws4: [ { title: safeTranslate(m.ebiosWs4_1()), - status: 'to_do', + status: data.data.meta.workshops[3].steps[0].status, href: `${$page.url.pathname}/workshop-four/operational-scenario?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs4_2()), - status: 'to_do', + status: data.data.meta.workshops[3].steps[1].status, href: `${$page.url.pathname}/workshop-four/operational-scenario?next=${$page.url.pathname}` } ], ws5: [ { title: safeTranslate(m.ebiosWs5_1()), - status: riskAnalysisCreated ? 'done' : 'to_do', + status: data.data.meta.workshops[4].steps[0].status, href: '#' }, { title: safeTranslate(m.ebiosWs5_2()), - status: 'done', + status: data.data.meta.workshops[4].steps[1].status, href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs5_3()), - status: 'to_do', + status: data.data.meta.workshops[4].steps[2].status, href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs5_4()), - status: 'to_do', + status: data.data.meta.workshops[4].steps[3].status, href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs5_5()), - status: 'done', + status: data.data.meta.workshops[4].steps[4].status, href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` } ] From a9b5e62080f64a29c5a8e34fe98c1385dfb9c58e Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Wed, 18 Dec 2024 03:10:03 +0100 Subject: [PATCH 04/11] Switch step state --- backend/ebios_rm/models.py | 11 +++++++---- backend/ebios_rm/views.py | 4 +--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 5ed0bbc95..752618f24 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -176,16 +176,19 @@ class Meta: def parsed_matrix(self): return self.risk_matrix.parse_json_translated() - def update_workshop_step_status(self, workshop: int, step: int, status: str): + def switch_workshop_step_status(self, workshop: int, step: int): if workshop < 1 or workshop > 5: raise ValueError("Workshop must be between 1 and 5") if step < 1 or step > len(self.meta["workshops"][workshop - 1]["steps"]): raise ValueError( f"Worshop {workshop} has only {len(self.meta['workshops'][workshop - 1]['steps'])} steps" ) - if not status in ["to_do", "in_progress", "done"]: - raise ValueError( - "Status must be one of 'to_do', 'in_progress', 'done'") + status = ( + "done" + if self.meta["workshops"][workshop - 1]["steps"][step - 1]["status"] + == "in_progress" + else "in_progress" + ) self.meta["workshops"][workshop - 1]["steps"][step - 1]["status"] = status return self.save() diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py index faa76e919..43d00c12b 100644 --- a/backend/ebios_rm/views.py +++ b/backend/ebios_rm/views.py @@ -74,11 +74,9 @@ def likelihood(self, request, pk): ) def update_workshop_step_status(self, request, pk, workshop, step): ebios_rm_study: EbiosRMStudy = self.get_object() - body = request.data workshop = int(workshop) step = int(step) - ebios_rm_study.update_workshop_step_status( - workshop, step, body.get("status")) + ebios_rm_study.switch_workshop_step_status(workshop, step) return Response(EbiosRMStudyReadSerializer(ebios_rm_study).data) From a835db24767fd16842357da43016adf857cbe66c Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Wed, 18 Dec 2024 03:10:13 +0100 Subject: [PATCH 05/11] Rename ebios routes using digits --- .../ebios-rm/[id=uuid]/+page.svelte | 65 +++++++++++------ .../(internal)/ebios-rm/[id=uuid]/Tile.svelte | 72 ++++++++++++------- .../baseline/+page.server.ts | 0 .../baseline/+page.svelte | 0 .../ebios-rm-study/+page.server.ts | 0 .../ebios-rm-study/+page.svelte | 0 .../ebios-rm-study/edit/+page.server.ts | 0 .../ebios-rm-study/edit/+page.svelte | 0 .../feared-events/+page.server.ts | 0 .../feared-events}/+page.svelte | 0 .../ro-to/+page.server.ts | 0 .../ro-to/+page.svelte | 0 .../ecosystem/+page.server.ts | 0 .../ecosystem}/+page.svelte | 0 .../strategic-scenarios/+page.server.ts | 0 .../strategic-scenarios}/+page.svelte | 0 .../operational-scenario/+page.server.ts | 0 .../operational-scenario}/+page.svelte | 0 .../risk-analyses/+page.server.ts | 0 .../risk-analyses/+page.svelte | 0 .../step-[step]/switch/+server.ts | 28 ++++++++ 21 files changed, 118 insertions(+), 47 deletions(-) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/baseline/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/baseline/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/ebios-rm-study/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/ebios-rm-study/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/ebios-rm-study/edit/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/ebios-rm-study/edit/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one => workshop-1}/feared-events/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-four/operational-scenario => workshop-1/feared-events}/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-two => workshop-2}/ro-to/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-two => workshop-2}/ro-to/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-three => workshop-3}/ecosystem/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-one/feared-events => workshop-3/ecosystem}/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-three => workshop-3}/strategic-scenarios/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-three/ecosystem => workshop-3/strategic-scenarios}/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-four => workshop-4}/operational-scenario/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-three/strategic-scenarios => workshop-4/operational-scenario}/+page.svelte (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-five => workshop-5}/risk-analyses/+page.server.ts (100%) rename frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/{workshop-five => workshop-5}/risk-analyses/+page.svelte (100%) create mode 100644 frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte index 20b132b7a..3aed17edd 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte @@ -23,68 +23,68 @@ { title: safeTranslate(m.ebiosWs1_1()), status: data.data.meta.workshops[0].steps[0].status, - href: `${$page.url.pathname}/workshop-one/ebios-rm-study?activity=one&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-1/ebios-rm-study?activity=one&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs1_2()), status: data.data.meta.workshops[0].steps[1].status, - href: `${$page.url.pathname}/workshop-one/ebios-rm-study?activity=two&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-1/ebios-rm-study?activity=two&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs1_3()), status: data.data.meta.workshops[0].steps[2].status, - href: `${$page.url.pathname}/workshop-one/feared-events?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-1/feared-events?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs1_4()), status: data.data.meta.workshops[0].steps[3].status, - href: `${$page.url.pathname}/workshop-one/baseline?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-1/baseline?next=${$page.url.pathname}` } ], ws2: [ { title: safeTranslate(m.ebiosWs2_1()), status: data.data.meta.workshops[1].steps[0].status, - href: `${$page.url.pathname}/workshop-two/ro-to?activity=one&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-2/ro-to?activity=one&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs2_2()), status: data.data.meta.workshops[1].steps[1].status, - href: `${$page.url.pathname}/workshop-two/ro-to?activity=two&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-2/ro-to?activity=two&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs2_3()), status: data.data.meta.workshops[1].steps[2].status, - href: `${$page.url.pathname}/workshop-two/ro-to?activity=three&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-2/ro-to?activity=three&next=${$page.url.pathname}` } ], ws3: [ { title: safeTranslate(m.ebiosWs3_1()), status: data.data.meta.workshops[2].steps[0].status, - href: `${$page.url.pathname}/workshop-three/ecosystem?activity=one&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-3/ecosystem?activity=one&next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs3_2()), status: data.data.meta.workshops[2].steps[1].status, - href: `${$page.url.pathname}/workshop-three/strategic-scenarios?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-3/strategic-scenarios?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs3_3()), status: data.data.meta.workshops[2].steps[2].status, - href: `${$page.url.pathname}/workshop-three/ecosystem?activity=three&next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-3/ecosystem?activity=three&next=${$page.url.pathname}` } ], ws4: [ { title: safeTranslate(m.ebiosWs4_1()), status: data.data.meta.workshops[3].steps[0].status, - href: `${$page.url.pathname}/workshop-four/operational-scenario?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-4/operational-scenario?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs4_2()), status: data.data.meta.workshops[3].steps[1].status, - href: `${$page.url.pathname}/workshop-four/operational-scenario?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-4/operational-scenario?next=${$page.url.pathname}` } ], ws5: [ @@ -96,22 +96,22 @@ { title: safeTranslate(m.ebiosWs5_2()), status: data.data.meta.workshops[4].steps[1].status, - href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-5/risk-analyses?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs5_3()), status: data.data.meta.workshops[4].steps[2].status, - href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-5/risk-analyses?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs5_4()), status: data.data.meta.workshops[4].steps[3].status, - href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-5/risk-analyses?next=${$page.url.pathname}` }, { title: safeTranslate(m.ebiosWs5_5()), status: data.data.meta.workshops[4].steps[4].status, - href: `${$page.url.pathname}/workshop-five/risk-analyses?next=${$page.url.pathname}` + href: `${$page.url.pathname}/workshop-5/risk-analyses?next=${$page.url.pathname}` } ] }; @@ -152,11 +152,36 @@
- - - - + + + + import * as m from '$paraglide/messages'; import { safeTranslate } from '$lib/utils/i18n'; + import { popup, type PopupSettings } from '@skeletonlabs/skeleton'; + import { page } from '$app/stores'; export let title = 'activity'; export let status = ''; export let meta = null; export let accent_color = ''; export let createRiskAnalysis = false; + export let workshop; + + const popupStep: PopupSettings = { + event: 'click', + target: 'popupStep', + placement: 'top' + }; + + async function switchStepState(workshop: number, step: number) { + console.log('switchStepState', workshop, step); + await fetch(`/ebios-rm/${$page.params.id}/workshop-${workshop}/step-${step}/switch`, { + method: 'POST' + }); + }
@@ -28,39 +44,41 @@
    {#each meta as step, i} - {#if step.status == 'done'} -
  1. - {#if createRiskAnalysis && i == 0} - - {:else} - +
  2. + {#if createRiskAnalysis && i == 0} + + {:else} + + {#if step.status == 'done'} - + -

    {m.activity()} {i + 1}

    -

    {step.title}

    -
    - {/if} -
  3. - {:else} -
  4. - {#if createRiskAnalysis && i == 0} - - {:else} - + {:else} - + -

    {m.activity()} {i + 1}

    -

    {step.title}

    -
    - {/if} -
  5. - {/if} + {/if} +

    {m.activity()} {i + 1}

    +

    {step.title}

    + + {/if} + +
    + +
    + {/each}
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/baseline/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/baseline/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/baseline/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/baseline/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/baseline/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/baseline/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/baseline/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/baseline/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/edit/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/edit/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/edit/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/edit/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/edit/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/edit/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/ebios-rm-study/edit/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/feared-events/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/feared-events/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-four/operational-scenario/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/feared-events/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-four/operational-scenario/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-1/feared-events/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-2/ro-to/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-2/ro-to/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-2/ro-to/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-2/ro-to/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/ecosystem/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/ecosystem/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/ecosystem/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/ecosystem/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/ecosystem/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/ecosystem/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/strategic-scenarios/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/strategic-scenarios/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/strategic-scenarios/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/strategic-scenarios/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/ecosystem/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/strategic-scenarios/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/ecosystem/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-3/strategic-scenarios/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-four/operational-scenario/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-4/operational-scenario/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-four/operational-scenario/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-4/operational-scenario/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/strategic-scenarios/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-4/operational-scenario/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-three/strategic-scenarios/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-4/operational-scenario/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-five/risk-analyses/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-5/risk-analyses/+page.server.ts similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-five/risk-analyses/+page.server.ts rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-5/risk-analyses/+page.server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-five/risk-analyses/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-5/risk-analyses/+page.svelte similarity index 100% rename from frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-five/risk-analyses/+page.svelte rename to frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-5/risk-analyses/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts new file mode 100644 index 000000000..0dc5dd905 --- /dev/null +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts @@ -0,0 +1,28 @@ +import { BASE_API_URL } from '$lib/utils/constants'; +import type { RequestHandler } from './$types'; + +export const POST: RequestHandler = async (event) => { + const requestInitOptions: RequestInit = { + method: 'PATCH' + }; + + const endpoint = `${BASE_API_URL}/ebios-rm/studies/${event.params.id}/workshop/${event.params.workshop}/step/${event.params.step}/`; + const res = await event.fetch(endpoint, requestInitOptions); + + if (!res.ok) { + const response = await res.text(); + console.error(response); + return new Response(JSON.stringify(response), { + status: res.status, + headers: { + 'Content-Type': 'application/json' + } + }); + } + + return new Response(null, { + headers: { + 'Content-Type': 'application/json' + } + }); +}; From d41afb85cbbcd1421cd7417af959fe7b72876e86 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 19 Dec 2024 15:53:17 +0100 Subject: [PATCH 06/11] Use a form action instead of an endpoint to switch step status --- .../ebios-rm/[id=uuid]/+page.server.ts | 34 +++++++++++++- .../(internal)/ebios-rm/[id=uuid]/Tile.svelte | 45 +++++++++++-------- .../step-[step]/switch/+server.ts | 28 ------------ 3 files changed, 59 insertions(+), 48 deletions(-) delete mode 100644 frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts index 06293b581..12a053c5c 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts @@ -4,9 +4,10 @@ import { getModelInfo } from '$lib/utils/crud'; import { modelSchema } from '$lib/utils/schemas'; import type { ModelInfo } from '$lib/utils/types'; import { type Actions } from '@sveltejs/kit'; -import { superValidate } from 'sveltekit-superforms'; +import { fail, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import type { PageServerLoad } from './$types'; +import { z } from 'zod'; export const load: PageServerLoad = async ({ params, fetch }) => { const URLModel = 'ebios-rm'; @@ -40,5 +41,36 @@ export const actions: Actions = { action: 'create' // redirectToWrittenObject: redirectToWrittenObject }); + }, + changeStepState: async (event) => { + const formData = await event.request.formData(); + if (!formData) { + return fail(400, { form: null }); + } + + const schema = z.object({ + workshop: z.number(), + step: z.number() + }); + + const form = await superValidate(formData, zod(schema)); + + const workshop = formData.get('workshop'); + const step = formData.get('step'); + + const requestInitOptions: RequestInit = { + method: 'PATCH' + }; + + const endpoint = `${BASE_API_URL}/ebios-rm/studies/${event.params.id}/workshop/${workshop}/step/${step}/`; + const res = await event.fetch(endpoint, requestInitOptions); + + if (!res.ok) { + const response = await res.text(); + console.error(response); + return fail(400, { form }); + } + + return { success: true, form }; } }; diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte index eb4012b4d..1c29b84f5 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte @@ -3,26 +3,15 @@ import { safeTranslate } from '$lib/utils/i18n'; import { popup, type PopupSettings } from '@skeletonlabs/skeleton'; import { page } from '$app/stores'; + import { applyAction, enhance } from '$app/forms'; + import { invalidateAll } from '$app/navigation'; export let title = 'activity'; export let status = ''; export let meta = null; export let accent_color = ''; export let createRiskAnalysis = false; - export let workshop; - - const popupStep: PopupSettings = { - event: 'click', - target: 'popupStep', - placement: 'top' - }; - - async function switchStepState(workshop: number, step: number) { - console.log('switchStepState', workshop, step); - await fetch(`/ebios-rm/${$page.params.id}/workshop-${workshop}/step-${step}/switch`, { - method: 'POST' - }); - } + export let workshop: number;
@@ -66,17 +55,35 @@

{step.title}

{/if} -
- { + return async () => { + step.status = 'done'; + }; + }} > + + + +
{/each} diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts deleted file mode 100644 index 0dc5dd905..000000000 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-[workshop]/step-[step]/switch/+server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BASE_API_URL } from '$lib/utils/constants'; -import type { RequestHandler } from './$types'; - -export const POST: RequestHandler = async (event) => { - const requestInitOptions: RequestInit = { - method: 'PATCH' - }; - - const endpoint = `${BASE_API_URL}/ebios-rm/studies/${event.params.id}/workshop/${event.params.workshop}/step/${event.params.step}/`; - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response = await res.text(); - console.error(response); - return new Response(JSON.stringify(response), { - status: res.status, - headers: { - 'Content-Type': 'application/json' - } - }); - } - - return new Response(null, { - headers: { - 'Content-Type': 'application/json' - } - }); -}; From 552777944595547fc5a1770dad98e8a61dce52c5 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 19 Dec 2024 15:54:03 +0100 Subject: [PATCH 07/11] Set new workshop step as done This is temporary, we will allow picking any status later. --- backend/ebios_rm/models.py | 9 ++------- backend/ebios_rm/views.py | 4 +++- frontend/messages/en.json | 3 ++- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 752618f24..99a988bd1 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -176,19 +176,14 @@ class Meta: def parsed_matrix(self): return self.risk_matrix.parse_json_translated() - def switch_workshop_step_status(self, workshop: int, step: int): + def update_workshop_step_status(self, workshop: int, step: int, new_status: str): if workshop < 1 or workshop > 5: raise ValueError("Workshop must be between 1 and 5") if step < 1 or step > len(self.meta["workshops"][workshop - 1]["steps"]): raise ValueError( f"Worshop {workshop} has only {len(self.meta['workshops'][workshop - 1]['steps'])} steps" ) - status = ( - "done" - if self.meta["workshops"][workshop - 1]["steps"][step - 1]["status"] - == "in_progress" - else "in_progress" - ) + status = new_status self.meta["workshops"][workshop - 1]["steps"][step - 1]["status"] = status return self.save() diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py index 43d00c12b..657037cac 100644 --- a/backend/ebios_rm/views.py +++ b/backend/ebios_rm/views.py @@ -76,7 +76,9 @@ def update_workshop_step_status(self, request, pk, workshop, step): ebios_rm_study: EbiosRMStudy = self.get_object() workshop = int(workshop) step = int(step) - ebios_rm_study.switch_workshop_step_status(workshop, step) + # NOTE: For now, just set it as done. Will allow undoing this later. + ebios_rm_study.update_workshop_step_status( + workshop, step, new_status="done") return Response(EbiosRMStudyReadSerializer(ebios_rm_study).data) diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 659e7ba99..4a07aace3 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -997,5 +997,6 @@ "noReviewer": "No reviewer assigned", "selectAudit": "Select audit", "errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles.", - "addStakeholder": "Add stakeholder" + "addStakeholder": "Add stakeholder", + "markAsDone": "Mark as done" } From 98032bb66a67b509e0d5c6aee3ab3ee35df7b7cc Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 19 Dec 2024 16:08:54 +0100 Subject: [PATCH 08/11] Allow marking steps as done or in progress --- backend/ebios_rm/views.py | 3 ++- frontend/messages/en.json | 3 ++- .../ebios-rm/[id=uuid]/+page.server.ts | 6 ++++-- .../(internal)/ebios-rm/[id=uuid]/Tile.svelte | 17 +++++++++++++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py index 657037cac..6ff5edf53 100644 --- a/backend/ebios_rm/views.py +++ b/backend/ebios_rm/views.py @@ -78,7 +78,8 @@ def update_workshop_step_status(self, request, pk, workshop, step): step = int(step) # NOTE: For now, just set it as done. Will allow undoing this later. ebios_rm_study.update_workshop_step_status( - workshop, step, new_status="done") + workshop, step, new_status=request.data.get("status", "in_progress") + ) return Response(EbiosRMStudyReadSerializer(ebios_rm_study).data) diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 4a07aace3..63e1566da 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -998,5 +998,6 @@ "selectAudit": "Select audit", "errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles.", "addStakeholder": "Add stakeholder", - "markAsDone": "Mark as done" + "markAsDone": "Mark as done", + "markAsInProgress": "Mark as in progress" } diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts index 12a053c5c..a5bb307b1 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts @@ -50,7 +50,8 @@ export const actions: Actions = { const schema = z.object({ workshop: z.number(), - step: z.number() + step: z.number(), + new_status: z.string() }); const form = await superValidate(formData, zod(schema)); @@ -59,7 +60,8 @@ export const actions: Actions = { const step = formData.get('step'); const requestInitOptions: RequestInit = { - method: 'PATCH' + method: 'PATCH', + body: JSON.stringify(form.data) }; const endpoint = `${BASE_API_URL}/ebios-rm/studies/${event.params.id}/workshop/${workshop}/step/${step}/`; diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte index 1c29b84f5..fa53e0dbd 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte @@ -74,15 +74,24 @@ method="POST" use:enhance={() => { return async () => { - step.status = 'done'; + if (step.status !== 'done') step.status = 'done'; + else step.status = 'in_progress'; }; }} > - + {#if step.status === 'done'} + + + {:else} + + + {/if}
From 425fc423cd2d5c5a9163ab2f80e9500276d3738e Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 19 Dec 2024 16:08:59 +0100 Subject: [PATCH 09/11] chore: ruff format --- .../migrations/0007_ebiosrmstudy_meta.py | 56 +++++++++++++++++-- backend/ebios_rm/models.py | 50 ++++++----------- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py b/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py index ff9de025b..5a22e4c5b 100644 --- a/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py +++ b/backend/ebios_rm/migrations/0007_ebiosrmstudy_meta.py @@ -6,15 +6,61 @@ class Migration(migrations.Migration): - dependencies = [ - ('ebios_rm', '0006_alter_attackpath_stakeholders'), + ("ebios_rm", "0006_alter_attackpath_stakeholders"), ] operations = [ migrations.AddField( - model_name='ebiosrmstudy', - name='meta', - field=models.JSONField(default=ebios_rm.models.get_initial_meta, validators=[core.validators.JSONSchemaInstanceValidator({'$id': 'https://ciso-assistant.com/schemas/ebiosrmstudy/meta.schema.json', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'description': 'Metadata of the EBIOS RM Study', 'properties': {'workshops': {'description': 'A list of workshops, each containing steps', 'items': {'additionalProperties': False, 'properties': {'steps': {'description': 'The list of steps in the workshop', 'items': {'additionalProperties': False, 'properties': {'status': {'description': 'The current status of the step', 'enum': ['to_do', 'in_progress', 'done'], 'type': 'string'}}, 'required': ['status'], 'type': 'object'}, 'type': 'array'}}, 'required': ['steps'], 'type': 'object'}, 'type': 'array'}}, 'title': 'Metadata', 'type': 'object'})], verbose_name='Metadata'), + model_name="ebiosrmstudy", + name="meta", + field=models.JSONField( + default=ebios_rm.models.get_initial_meta, + validators=[ + core.validators.JSONSchemaInstanceValidator( + { + "$id": "https://ciso-assistant.com/schemas/ebiosrmstudy/meta.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Metadata of the EBIOS RM Study", + "properties": { + "workshops": { + "description": "A list of workshops, each containing steps", + "items": { + "additionalProperties": False, + "properties": { + "steps": { + "description": "The list of steps in the workshop", + "items": { + "additionalProperties": False, + "properties": { + "status": { + "description": "The current status of the step", + "enum": [ + "to_do", + "in_progress", + "done", + ], + "type": "string", + } + }, + "required": ["status"], + "type": "object", + }, + "type": "array", + } + }, + "required": ["steps"], + "type": "object", + }, + "type": "array", + } + }, + "title": "Metadata", + "type": "object", + } + ) + ], + verbose_name="Metadata", + ), ), ] diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 99a988bd1..8a5deb157 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -27,10 +27,8 @@ {"status": "to_do"}, ] }, - {"steps": [{"status": "to_do"}, { - "status": "to_do"}, {"status": "to_do"}]}, - {"steps": [{"status": "to_do"}, { - "status": "to_do"}, {"status": "to_do"}]}, + {"steps": [{"status": "to_do"}, {"status": "to_do"}, {"status": "to_do"}]}, + {"steps": [{"status": "to_do"}, {"status": "to_do"}, {"status": "to_do"}]}, {"steps": [{"status": "to_do"}, {"status": "to_do"}]}, { "steps": [ @@ -158,8 +156,7 @@ class Status(models.TextChoices): verbose_name=_("Reviewers"), related_name="reviewers", ) - observation = models.TextField( - null=True, blank=True, verbose_name=_("Observation")) + observation = models.TextField(null=True, blank=True, verbose_name=_("Observation")) meta = models.JSONField( default=get_initial_meta, @@ -184,8 +181,7 @@ def update_workshop_step_status(self, workshop: int, step: int, new_status: str) f"Worshop {workshop} has only {len(self.meta['workshops'][workshop - 1]['steps'])} steps" ) status = new_status - self.meta["workshops"][workshop - - 1]["steps"][step - 1]["status"] = status + self.meta["workshops"][workshop - 1]["steps"][step - 1]["status"] = status return self.save() @@ -212,10 +208,8 @@ class FearedEvent(NameDescriptionMixin, FolderMixin): ref_id = models.CharField(max_length=100, blank=True) gravity = models.SmallIntegerField(default=-1, verbose_name=_("Gravity")) - is_selected = models.BooleanField( - verbose_name=_("Is selected"), default=False) - justification = models.TextField( - verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Feared event") @@ -310,10 +304,8 @@ class Pertinence(models.IntegerChoices): activity = models.PositiveSmallIntegerField( verbose_name=_("Activity"), default=0, validators=[MaxValueValidator(4)] ) - is_selected = models.BooleanField( - verbose_name=_("Is selected"), default=False) - justification = models.TextField( - verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) def __str__(self) -> str: return f"{self.get_risk_origin_display()} - {self.target_objective}" @@ -416,10 +408,8 @@ class Category(models.TextChoices): validators=[MinValueValidator(1), MaxValueValidator(4)], ) - is_selected = models.BooleanField( - verbose_name=_("Is selected"), default=False) - justification = models.TextField( - verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Stakeholder") @@ -495,10 +485,8 @@ class AttackPath(NameDescriptionMixin, FolderMixin): ) ref_id = models.CharField(max_length=100, blank=True) - is_selected = models.BooleanField( - verbose_name=_("Is selected"), default=False) - justification = models.TextField( - verbose_name=_("Justification"), blank=True) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Attack path") @@ -534,15 +522,11 @@ class OperationalScenario(AbstractBaseModel, FolderMixin): operating_modes_description = models.TextField( verbose_name=_("Operating modes description"), - help_text=_( - "Description of the operating modes of the operational scenario"), - ) - likelihood = models.SmallIntegerField( - default=-1, verbose_name=_("Likelihood")) - is_selected = models.BooleanField( - verbose_name=_("Is selected"), default=False) - justification = models.TextField( - verbose_name=_("Justification"), blank=True) + help_text=_("Description of the operating modes of the operational scenario"), + ) + likelihood = models.SmallIntegerField(default=-1, verbose_name=_("Likelihood")) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Operational scenario") From d487510eaa306642fd45067b6264cb3cc3837122 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 19 Dec 2024 16:18:51 +0100 Subject: [PATCH 10/11] Fix mismatch in new status field name --- .../(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts | 2 +- .../routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts index a5bb307b1..ccee8126b 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.server.ts @@ -51,7 +51,7 @@ export const actions: Actions = { const schema = z.object({ workshop: z.number(), step: z.number(), - new_status: z.string() + status: z.string() }); const form = await superValidate(formData, zod(schema)); diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte index fa53e0dbd..723f555f6 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte @@ -82,12 +82,12 @@ {#if step.status === 'done'} - + {:else} - + From 466f969578dd7287c76b4699af5a473eb33bbecd Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 19 Dec 2024 16:26:50 +0100 Subject: [PATCH 11/11] chore: Remove unused code --- .../ebios-rm/[id=uuid]/+page.svelte | 36 ++++--------------- .../(internal)/ebios-rm/[id=uuid]/Tile.svelte | 36 +++++++++---------- 2 files changed, 25 insertions(+), 47 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte index 3aed17edd..d0d8bd96b 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte @@ -16,9 +16,7 @@ $: breadcrumbObject.set(data.data); - const riskAnalysisCreated: boolean = data.data.risk_assessments.length > 0; - - const dummydata = { + const workshopsData = { ws1: [ { title: safeTranslate(m.ebiosWs1_1()), @@ -152,40 +150,20 @@
- + - - + +
@@ -200,6 +178,6 @@
- +
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte index 723f555f6..26708b953 100644 --- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte +++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/Tile.svelte @@ -1,29 +1,33 @@
{title}
-
- {#if status == 'to_do'} +
+ {#if workshopStatus == 'to_do'} - {:else if status == 'in_progress'} + {:else if workshopStatus == 'in_progress'} - {:else if status == 'done'} + {:else if workshopStatus == 'done'} {/if}
@@ -83,14 +87,10 @@ {#if step.status === 'done'} - + {:else} - + {/if}