Skip to content

Commit

Permalink
Merge branch 'CA-702-workshop-2-UX' into CA-708-workshop-4-UX
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohamed-Hacene committed Dec 13, 2024
2 parents 85c594e + 3d59feb commit 4a6f2d8
Show file tree
Hide file tree
Showing 19 changed files with 502 additions and 102 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 5.1.4 on 2024-12-12 18:40

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("ebios_rm", "0003_remove_ebiosrmstudy_risk_assessments"),
]

operations = [
migrations.RemoveField(
model_name="roto",
name="pertinence",
),
migrations.AlterField(
model_name="roto",
name="feared_events",
field=models.ManyToManyField(
blank=True,
related_name="ro_to_couples",
to="ebios_rm.fearedevent",
verbose_name="Feared events",
),
),
]
24 changes: 18 additions & 6 deletions backend/ebios_rm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ class Pertinence(models.IntegerChoices):
on_delete=models.CASCADE,
)
feared_events = models.ManyToManyField(
FearedEvent, verbose_name=_("Feared events"), related_name="ro_to_couples"
FearedEvent,
verbose_name=_("Feared events"),
related_name="ro_to_couples",
blank=True,
)

risk_origin = models.CharField(
Expand All @@ -211,11 +214,6 @@ class Pertinence(models.IntegerChoices):
choices=Resources.choices,
default=Resources.UNDEFINED,
)
pertinence = models.PositiveSmallIntegerField(
verbose_name=_("Pertinence"),
choices=Pertinence.choices,
default=Pertinence.UNDEFINED,
)
activity = models.PositiveSmallIntegerField(
verbose_name=_("Activity"), default=0, validators=[MaxValueValidator(4)]
)
Expand All @@ -234,6 +232,20 @@ def save(self, *args, **kwargs):
self.folder = self.ebios_rm_study.folder
super().save(*args, **kwargs)

@property
def get_pertinence(self):
PERTINENCE_MATRIX = [
[1, 1, 2, 2],
[1, 2, 3, 3],
[2, 3, 3, 4],
[2, 3, 4, 4],
]
if self.motivation == 0 or self.resources == 0:
return self.Pertinence(self.Pertinence.UNDEFINED).label
return self.Pertinence(
PERTINENCE_MATRIX[self.motivation - 1][self.resources - 1]
).label


class Stakeholder(AbstractBaseModel, FolderMixin):
class Category(models.TextChoices):
Expand Down
2 changes: 1 addition & 1 deletion backend/ebios_rm/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ class RoToReadSerializer(BaseModelSerializer):
folder = FieldsRelatedField()
feared_events = FieldsRelatedField(["folder", "id"], many=True)

pertinence = serializers.CharField(source="get_pertinence_display")
motivation = serializers.CharField(source="get_motivation_display")
resources = serializers.CharField(source="get_resources_display")
pertinence = serializers.CharField(source="get_pertinence")

class Meta:
model = RoTo
Expand Down
8 changes: 1 addition & 7 deletions backend/ebios_rm/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ def likelihood(self, request, pk):
class FearedEventViewSet(BaseModelViewSet):
model = FearedEvent

filterset_fields = [
"ebios_rm_study",
]
filterset_fields = ["ebios_rm_study", "ro_to_couples"]

@action(detail=True, name="Get risk matrix", url_path="risk-matrix")
def risk_matrix(self, request, pk=None):
Expand Down Expand Up @@ -112,10 +110,6 @@ def motivation(self, request):
def resources(self, request):
return Response(dict(RoTo.Resources.choices))

@action(detail=False, name="Get pertinence choices")
def pertinence(self, request):
return Response(dict(RoTo.Pertinence.choices))


class StakeholderViewSet(BaseModelViewSet):
model = Stakeholder
Expand Down
6 changes: 5 additions & 1 deletion frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@
"ebiosRmMatrixHelpText": "Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`",
"activityOne": "Activity 1",
"activityTwo": "Activity 2",
"activityThree": "Activity 3",
"ebiosRmStudy": "Ebios RM study",
"qualifications": "Qualifications",
"impacts": "Impacts",
Expand Down Expand Up @@ -980,7 +981,10 @@
"attackPaths": "Attack paths",
"currentCriticality": "Current criticality",
"residualCriticality": "Residual criticality",
"errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles.",
"notSelected": "Not selected",
"identifyRoTo": "Identify RO/TO",
"evaluateRoTo": "Evaluate RO/TO",
"selectRoTo": "Select RO/TO",
"resetPasswordHere": "You can reset your password here.",
"resetPassword": "Reset password",
"ebiosRm": "Ebios RM",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/Forms/ModelForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@
{:else if URLModel === 'feared-events'}
<FearedEventForm {form} {model} {cacheLocks} {formDataCache} {initialData} />
{:else if URLModel === 'ro-to'}
<RoToForm {form} {model} {cacheLocks} {formDataCache} {initialData} />
<RoToForm {form} {model} {cacheLocks} {formDataCache} {initialData} {context} />
{:else if URLModel === 'stakeholders'}
<StakeholderForm {form} {model} {cacheLocks} {formDataCache} {context} />
{:else if URLModel === 'attack-paths'}
Expand Down
184 changes: 119 additions & 65 deletions frontend/src/lib/components/Forms/ModelForm/RoToForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@
import { getOptions } from '$lib/utils/crud';
import TextArea from '../TextArea.svelte';
import NumberField from '../NumberField.svelte';
import { page } from '$app/stores';
export let form: SuperValidated<any>;
export let model: ModelInfo;
export let cacheLocks: Record<string, CacheLock> = {};
export let formDataCache: Record<string, any> = {};
export let initialData: Record<string, any> = {};
export let context: string;
const activityBackground = context === 'edit' ? 'bg-white' : 'bg-surface-100-800-token';
let activeActivity: string | null = null;
$page.url.searchParams.forEach((value, key) => {
if (key === 'activity' && value === 'one') {
activeActivity = 'one';
} else if (key === 'activity' && value === 'two') {
activeActivity = 'two';
} else if (key === 'activity' && value === 'three') {
activeActivity = 'three';
}
});
</script>

<AutocompleteSelect
Expand All @@ -25,68 +40,107 @@
label={m.ebiosRmStudy()}
hidden={initialData.ebios_rm_study}
/>
<AutocompleteSelect
multiple
{form}
options={getOptions({
objects: model.foreignKeys['feared_events'],
extra_fields: [['folder', 'str']],
label: 'auto'
})}
field="feared_events"
label={m.fearedEvents()}
/>
<Select
{form}
options={model.selectOptions['risk-origin']}
field="risk_origin"
label={m.riskOrigin()}
cacheLock={cacheLocks['risk_origin']}
bind:cachedValue={formDataCache['risk_origin']}
/>
<TextArea
{form}
field="target_objective"
label={m.targetObjective()}
cacheLock={cacheLocks['target_objective']}
bind:cachedValue={formDataCache['target_objective']}
/>
<Select
{form}
options={model.selectOptions['motivation']}
field="motivation"
label={m.motivation()}
cacheLock={cacheLocks['motivation']}
bind:cachedValue={formDataCache['motivation']}
/>
<Select
{form}
options={model.selectOptions['resources']}
field="resources"
label={m.resources()}
cacheLock={cacheLocks['resources']}
bind:cachedValue={formDataCache['resources']}
/>
<Select
{form}
options={model.selectOptions['pertinence']}
field="pertinence"
label={m.pertinence()}
cacheLock={cacheLocks['pertinence']}
bind:cachedValue={formDataCache['pertinence']}
/>
<NumberField
{form}
field="activity"
label={m.activity()}
cacheLock={cacheLocks['activity']}
bind:cachedValue={formDataCache['activity']}
/>
<Checkbox {form} field="is_selected" label={m.isSelected()} />
<TextArea
{form}
field="justification"
label={m.justification()}
cacheLock={cacheLocks['justification']}
bind:cachedValue={formDataCache['justification']}
/>
<div
class="relative p-2 space-y-2 rounded-md {activeActivity === 'one'
? 'border-2 border-primary-500'
: 'border-2 border-gray-300 border-dashed'}"
>
<p
class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'one'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityOne()}
</p>
<Select
{form}
options={model.selectOptions['risk-origin']}
field="risk_origin"
label={m.riskOrigin()}
cacheLock={cacheLocks['risk_origin']}
bind:cachedValue={formDataCache['risk_origin']}
/>
<TextArea
{form}
field="target_objective"
label={m.targetObjective()}
cacheLock={cacheLocks['target_objective']}
bind:cachedValue={formDataCache['target_objective']}
/>
</div>
<div
class="relative p-2 space-y-2 rounded-md {activeActivity === 'two'
? 'border-2 border-primary-500'
: 'border-2 border-gray-300 border-dashed'}"
>
<p
class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'two'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityTwo()}
</p>
<Select
{form}
options={model.selectOptions['motivation']}
field="motivation"
label={m.motivation()}
cacheLock={cacheLocks['motivation']}
bind:cachedValue={formDataCache['motivation']}
/>
<Select
{form}
options={model.selectOptions['resources']}
field="resources"
label={m.resources()}
cacheLock={cacheLocks['resources']}
bind:cachedValue={formDataCache['resources']}
/>
<!-- <Select
{form}
options={model.selectOptions['pertinence']}
field="pertinence"
label={m.pertinence()}
cacheLock={cacheLocks['pertinence']}
bind:cachedValue={formDataCache['pertinence']}
/> -->
<NumberField
{form}
field="activity"
label={m.activity()}
cacheLock={cacheLocks['activity']}
bind:cachedValue={formDataCache['activity']}
/>
</div>
<div
class="relative p-2 space-y-2 rounded-md {activeActivity === 'three'
? 'border-2 border-primary-500'
: 'border-2 border-gray-300 border-dashed'}"
>
<p
class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'three'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityThree()}
</p>
<Checkbox {form} field="is_selected" label={m.isSelected()} />
<AutocompleteSelect
multiple
{form}
options={getOptions({
objects: model.foreignKeys['feared_events'],
extra_fields: [['folder', 'str']],
label: 'auto'
})}
field="feared_events"
label={m.fearedEvents()}
/>
<TextArea
{form}
field="justification"
label={m.justification()}
cacheLock={cacheLocks['justification']}
bind:cachedValue={formDataCache['justification']}
/>
</div>
14 changes: 10 additions & 4 deletions frontend/src/lib/utils/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ interface ForeignKeyField {
urlModel: urlModel;
endpointUrl?: string;
urlParams?: string;
detail?: boolean;
}

interface Field {
Expand Down Expand Up @@ -624,7 +625,7 @@ export const URL_MODEL_MAP: ModelMap = {
verboseNamePlural: 'Feared events',
foreignKeyFields: [
{ field: 'ebios_rm_study', urlModel: 'ebios-rm', endpointUrl: 'ebios-rm/studies' },
{ field: 'assets', urlModel: 'assets', urlParams: 'ebios_rm_studies=' },
{ field: 'assets', urlModel: 'assets', urlParams: 'ebios_rm_studies=', detail: true },
{ field: 'qualifications', urlModel: 'qualifications' }
],
selectFields: [{ field: 'gravity', valueType: 'number', detail: true }]
Expand All @@ -638,13 +639,18 @@ export const URL_MODEL_MAP: ModelMap = {
verboseNamePlural: 'Ro to',
foreignKeyFields: [
{ field: 'ebios_rm_study', urlModel: 'ebios-rm', endpointUrl: 'ebios-rm/studies' },
{ field: 'feared_events', urlModel: 'feared-events', endpointUrl: 'ebios-rm/feared-events' }
{
field: 'feared_events',
urlModel: 'feared-events',
endpointUrl: 'ebios-rm/feared-events',
urlParams: 'ebios_rm_study=',
detail: true
}
],
selectFields: [
{ field: 'risk-origin' },
{ field: 'motivation', valueType: 'number' },
{ field: 'resources', valueType: 'number' },
{ field: 'pertinence', valueType: 'number' }
{ field: 'resources', valueType: 'number' }
]
},
stakeholders: {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/utils/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const loadDetail = async ({ event, model, id }) => {
const initialData = {};
await Promise.all(
model.reverseForeignKeyFields.map(async (e) => {
const relEndpoint = `${BASE_API_URL}/${e.urlModel}/?${e.field}=${event.params.id}`;
const relEndpoint = `${BASE_API_URL}/${e.endpointUrl || e.urlModel}/?${e.field}=${event.params.id}`;
const res = await event.fetch(relEndpoint);
const revData = await res.json().then((res) => res.results);

Expand Down Expand Up @@ -103,7 +103,7 @@ export const loadDetail = async ({ event, model, id }) => {
if (info.selectFields) {
await Promise.all(
info.selectFields.map(async (selectField) => {
const url = `${BASE_API_URL}/${urlModel}/${selectField.field}/`;
const url = `${BASE_API_URL}/${e.endpointUrl || e.urlModel}/${selectField.field}/`;
const response = await event.fetch(url);
if (response.ok) {
selectOptions[selectField.field] = await response.json().then((data) =>
Expand Down
Loading

0 comments on commit 4a6f2d8

Please sign in to comment.