Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ca 701 workshop 1 ux #1170

Merged
merged 11 commits into from
Dec 12, 2024
8 changes: 8 additions & 0 deletions backend/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ class Meta:


class AssetWriteSerializer(BaseModelSerializer):
ebios_rm_studies = serializers.PrimaryKeyRelatedField(
many=True,
queryset=EbiosRMStudy.objects.all(),
required=False,
allow_null=True,
write_only=True,
)

class Meta:
model = Asset
fields = "__all__"
Expand Down
8 changes: 8 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -980,5 +980,13 @@
"attackPaths": "Attack paths",
"currentCriticality": "Current criticality",
"residualCriticality": "Residual criticality",
"ebiosRm": "Ebios RM",
"workshopOne": "Workshop 1",
"refIdSemiColon": "Refence ID:",
"addFearedEvent": "Add feared event",
"addEbiosRMstudy": "Add Ebios RM Study",
"noAuthor": "No author assigned",
"noReviewer": "No reviewer assigned",
"selectAudit": "Select audit",
"errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles."
}
5 changes: 3 additions & 2 deletions frontend/src/lib/components/Forms/ModelForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
export let suggestions: { [key: string]: any } = {};
export let cancelButton = true;
export let duplicate = false;
export let customNameDescription = false;

const URLModel = model.urlModel as urlModel;
export let schema = modelSchema(URLModel);
Expand Down Expand Up @@ -163,7 +164,7 @@
}}
/>
{/if}
{#if shape.name}
{#if shape.name && !customNameDescription}
<TextField
{form}
field="name"
Expand All @@ -173,7 +174,7 @@
data-focusindex="0"
/>
{/if}
{#if shape.description}
{#if shape.description && !customNameDescription}
<TextArea
{form}
field="description"
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/lib/components/Forms/ModelForm/AssetForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,15 @@
label={m.labels()}
allowUserOptions="append"
/>
{#if initialData.ebios_rm_studies}
<AutocompleteSelect
{form}
field="ebios_rm_studies"
multiple
cacheLock={cacheLocks['ebios_rm_studies']}
bind:cachedValue={formDataCache['ebios_rm_studies']}
label={m.ebiosRmStudies()}
options={getOptions({ objects: model.foreignKeys['ebios_rm_studies'] })}
hidden
/>
{/if}
84 changes: 71 additions & 13 deletions frontend/src/lib/components/Forms/ModelForm/EbiosRmForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,40 @@
import type { ModelInfo, CacheLock } from '$lib/utils/types';
import TextField from '$lib/components/Forms/TextField.svelte';
import AutocompleteSelect from '$lib/components/Forms/AutocompleteSelect.svelte';
import Select from '$lib/components/Forms/Select.svelte';
import * as m from '$paraglide/messages.js';
import { getOptions } from '$lib/utils/crud';
import TextArea from '../TextArea.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;

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';
}
});
</script>

{#if context !== 'ebiosRmStudy'}
{#if context != 'selectAudit'}
<TextField
{form}
field="name"
label={m.name()}
cacheLock={cacheLocks['name']}
bind:cachedValue={formDataCache['name']}
data-focusindex="0"
/>
{/if}
{#if context !== 'ebiosRmStudy' && context !== 'selectAudit'}
<TextField
{form}
field="version"
Expand Down Expand Up @@ -50,8 +70,26 @@
helpText={m.ebiosRmMatrixHelpText()}
/>
{:else if context === 'ebiosRmStudy'}
<div class="relative p-2 space-y-2 border rounded-md">
<p class="absolute -top-3 bg-white font-bold">{m.activityOne()}</p>
<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 bg-white font-bold {activeActivity === 'one'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityOne()}
</p>
<TextArea
{form}
field="description"
label={m.description()}
cacheLock={cacheLocks['description']}
bind:cachedValue={formDataCache['description']}
data-focusindex="1"
/>
<TextField
{form}
field="version"
Expand All @@ -66,13 +104,6 @@
cacheLock={cacheLocks['ref_id']}
bind:cachedValue={formDataCache['ref_id']}
/>
<TextArea
{form}
field="observation"
label={m.observation()}
cacheLock={cacheLocks['observation']}
bind:cachedValue={formDataCache['observation']}
/>
<AutocompleteSelect
multiple
{form}
Expand All @@ -92,8 +123,18 @@
label={m.reviewers()}
/>
</div>
<div class="relative p-2 space-y-2 border rounded-md">
<p class="absolute -top-3 bg-white font-bold">{m.activityTwo()}</p>
<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 bg-white font-bold {activeActivity === 'two'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityTwo()}
</p>
<AutocompleteSelect
multiple
{form}
Expand All @@ -106,4 +147,21 @@
label={m.assets()}
/>
</div>
<TextArea
{form}
field="observation"
label={m.observation()}
cacheLock={cacheLocks['observation']}
bind:cachedValue={formDataCache['observation']}
/>
{:else}
<AutocompleteSelect
multiple
{form}
options={getOptions({ objects: model.foreignKeys['compliance_assessments'] })}
field="compliance_assessments"
cacheLock={cacheLocks['compliance_assessments']}
bind:cachedValue={formDataCache['compliance_assessments']}
label={m.complianceAssessment()}
/>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as m from '$paraglide/messages.js';
import { getOptions } from '$lib/utils/crud';
import TextArea from '../TextArea.svelte';
import Checkbox from '$lib/components/Forms/Checkbox.svelte';

export let form: SuperValidated<any>;
export let model: ModelInfo;
Expand Down Expand Up @@ -66,3 +67,4 @@
field="qualifications"
label={m.qualifications()}
/>
<Checkbox {form} field="is_selected" label={m.isSelected()} />
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script lang="ts">
import type { SuperValidated } from 'sveltekit-superforms';
import type { ModelInfo, CacheLock } from '$lib/utils/types';
import TextField from '$lib/components/Forms/TextField.svelte';
import Checkbox from '$lib/components/Forms/Checkbox.svelte';
import AutocompleteSelect from '$lib/components/Forms/AutocompleteSelect.svelte';
import Select from '$lib/components/Forms/Select.svelte';
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/components/Modals/CreateModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
const modalStore: ModalStore = getModalStore();

export let form: SuperValidated<AnyZodObject>;
export let customNameDescription = false;
export let model: ModelInfo;
export let duplicate = false;
export let invalidateAll = true; // set to false to keep form data using muliple forms on a page
Expand Down Expand Up @@ -47,6 +48,7 @@
</div>
<ModelForm
{form}
{customNameDescription}
{suggestions}
{parent}
{invalidateAll}
Expand Down
67 changes: 67 additions & 0 deletions frontend/src/lib/components/Modals/UpdateModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
// Props
/** Exposes parent props to this component. */
export let parent: any;

// Stores
import type { ModelInfo } from '$lib/utils/types';
import type { ModalStore } from '@skeletonlabs/skeleton';
import { getModalStore } from '@skeletonlabs/skeleton';

const modalStore: ModalStore = getModalStore();

export let form: SuperValidated<AnyZodObject>;
export let model: ModelInfo;
export let invalidateAll = true; // set to false to keep form data using muliple forms on a page
export let formAction = '?/update';
export let context = 'default';
export let object: Record<string, any> = {};
let closeModal = true;
export let suggestions: { [key: string]: any } = {};
export let selectOptions: Record<string, any> = {};
export let foreignKeys: Record<string, any> = {};

// Base Classes
const cBase = 'card p-4 w-modal shadow-xl space-y-4';
const cHeader = 'text-2xl font-bold';
export let debug = false;

import ModelForm from '$lib/components/Forms/ModelForm.svelte';
import type { SuperValidated } from 'sveltekit-superforms';
import type { AnyZodObject } from 'zod';
</script>

{#if $modalStore[0]}
<div class="modal-example-form {cBase}">
<div class="flex items-center justify-between">
<header class={cHeader} data-testid="modal-title">
{$modalStore[0].title ?? '(title missing)'}
</header>
<div
role="button"
tabindex="0"
class="flex items-center hover:text-primary-500 cursor-pointer"
on:click={parent.onClose}
on:keydown={parent.onClose}
>
<i class="fa-solid fa-xmark" />
</div>
</div>
<ModelForm
customNameDescription
{form}
{object}
{suggestions}
{parent}
action={formAction}
{invalidateAll}
{model}
{closeModal}
{context}
caching={true}
{selectOptions}
{foreignKeys}
{debug}
/>
</div>
{/if}
9 changes: 6 additions & 3 deletions frontend/src/lib/components/ModelTable/ModelTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,12 @@
{#if pagination && rowsPerPage}
<RowsPerPage {handler} />
{/if}
{#if canCreateObject}
<slot name="addButton" />
{/if}
<div class="flex space-x-2 items-center">
<slot name="optButton" />
{#if canCreateObject}
<slot name="addButton" />
{/if}
</div>
</header>
<!-- Table -->
<table
Expand Down
11 changes: 7 additions & 4 deletions frontend/src/lib/utils/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ export const URL_MODEL_MAP: ModelMap = {
{ field: 'parent_assets', urlModel: 'assets' },
{ field: 'owner', urlModel: 'users' },
{ field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO&content_type=GL' },
{ field: 'filtering_labels', urlModel: 'filtering-labels' }
{ field: 'filtering_labels', urlModel: 'filtering-labels' },
{ field: 'ebios_rm_studies', urlModel: 'ebios-rm', endpointUrl: 'ebios-rm/studies' }
],
selectFields: [{ field: 'type' }],
filters: [
Expand Down Expand Up @@ -609,8 +610,10 @@ export const URL_MODEL_MAP: ModelMap = {
{ field: 'assets', urlModel: 'assets' },
{ field: 'authors', urlModel: 'users', urlParams: 'is_third_party=false' },
{ field: 'reviewers', urlModel: 'users', urlParams: 'is_third_party=false' },
{ field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO' }
]
{ field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO' },
{ field: 'compliance_assessments', urlModel: 'compliance-assessments' }
],
reverseForeignKeyFields: [{ field: 'ebios_rm_studies', urlModel: 'assets' }]
},
'feared-events': {
endpointUrl: 'ebios-rm/feared-events',
Expand All @@ -621,7 +624,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' },
{ field: 'assets', urlModel: 'assets', urlParams: 'ebios_rm_studies=' },
{ field: 'qualifications', urlModel: 'qualifications' }
],
selectFields: [{ field: 'gravity', valueType: 'number', detail: true }]
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/utils/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const loadDetail = async ({ event, model, id }) => {
if (info.foreignKeyFields) {
for (const keyField of info.foreignKeyFields) {
const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : '';
const url = `${BASE_API_URL}/${keyField.urlModel}/${queryParams}`;
const url = `${BASE_API_URL}/${keyField.endpointUrl || keyField.urlModel}/${queryParams}`;
const response = await event.fetch(url);
if (response.ok) {
foreignKeys[keyField.field] = await response.json().then((data) => data.results);
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/lib/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ export const AssetSchema = z.object({
.optional(),
reference_link: z.string().url().optional().or(z.literal('')),
owner: z.string().uuid().optional().array().optional(),
filtering_labels: z.string().optional().array().optional()
filtering_labels: z.string().optional().array().optional(),
ebios_rm_studies: z.string().uuid().optional().array().optional()
});

export const FilteringLabelSchema = z.object({
Expand Down Expand Up @@ -400,7 +401,8 @@ export const ebiosRMSchema = z.object({
reviewers: z.array(z.string().optional()).optional(),
observation: z.string().optional().nullable(),
assets: z.string().uuid().optional().array().optional(),
folder: z.string()
folder: z.string(),
compliance_assessments: z.string().uuid().optional().array().optional()
});

export const fearedEventsSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ export const load: LayoutServerLoad = async (event) => {
for (const keyField of foreignKeyFields) {
const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : '';
const keyModel = getModelInfo(keyField.urlModel);
const url = keyModel.endpointUrl
let url = keyModel.endpointUrl
? `${BASE_API_URL}/${keyModel.endpointUrl}/${queryParams}`
: `${BASE_API_URL}/${keyModel.urlModel}/${queryParams}`;
if (keyModel.urlModel === 'assets' && event.params.model === 'feared-events') {
url = `${BASE_API_URL}/${keyModel.urlModel}/${queryParams}${object.ebios_rm_study}`;
}
const response = await event.fetch(url);
if (response.ok) {
foreignKeys[keyField.field] = await response.json().then((data) => data.results);
Expand Down
Loading
Loading