From 00482134f781894283a6637acfe885ec485d324b Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 14:09:52 +0200 Subject: [PATCH 01/42] Implement filtering and add some test filters --- .../components/Filters/SelectFilter.svelte | 30 ++++++++ .../components/ModelTable/ModelTable.svelte | 50 ++++++++++++++ frontend/src/lib/utils/table.ts | 69 +++++++++++++++++-- 3 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 frontend/src/lib/components/Filters/SelectFilter.svelte diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte new file mode 100644 index 000000000..88aa4a3a1 --- /dev/null +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -0,0 +1,30 @@ + + + + + diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 5465945c6..8940433ff 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -95,6 +95,7 @@ $: canCreateObject = Object.hasOwn(user.permissions, `add_${model?.name}`); import { URL_MODEL_MAP } from '$lib/utils/crud'; + import { listViewFields } from '$lib/utils/table'; // Reactive $: classesBase = `${$$props.class || 'bg-white'}`; @@ -117,6 +118,46 @@ const handler = new DataHandler(data, { rowsPerPage: pagination ? numberRowsPerPage : undefined }); + const allRows = handler.getAllRows(); + const tableURLModel = source.meta?.urlmodel ?? URLModel; + const filters = listViewFields[tableURLModel].filters ?? {}; + const filteredFields = Object.keys(filters); + const filterValues: {[key: string]: any} = {}; + const filterProps: { + [key: string]: {[key: string]: any} + } = {}; + + function defaultFilterProps(rows,field: string) { + const options = [ + ...new Set( + rows.map(row => filters[field].parser(row.meta)) + ) + ].sort(); + return { options }; + } + + function defaultFilterFunction(columnValue: any, value: any): boolean { + return value ? columnValue === value : true; + } + + $: { + for (const field of filteredFields) { + handler.filter(filterValues[field], field, filters[field].filter ?? defaultFilterFunction); + } + }; + + let allowOptionsUpdate = true; + allRows.subscribe(rows => { + if (!allowOptionsUpdate) + return; + + for (const key of filteredFields) { + filterProps[key] = defaultFilterProps(rows,key); + } + if (rows.length > 0) + allowOptionsUpdate = false; + }); + const rows = handler.getRows(); onMount(() => { @@ -143,6 +184,15 @@
+ {#each filteredFields as field} + + {/each} + {#if search} {/if} diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 68ef374f8..673dc18a0 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -1,4 +1,55 @@ -export const listViewFields = { +import SelectFilter from "$lib/components/Filters/SelectFilter.svelte"; +import type { ComponentType } from 'svelte'; +import { LOCALE_DISPLAY_MAP } from "./constants"; + +type JSONObject = {[key: string]: JSONObject} | JSONObject[] | string | number | boolean | null; + +interface ListViewFilterConfig { + component: ComponentType; + parser?: (row: JSONObject) => any; // Imperfect typescript type for the row argument. + filter?: (columnValue: any, value: any) => boolean; + filterProps?: (rows: any[],field: string) => {[key: string]: any}; + extraProps?: {[key: string]: any}; +} + +interface ListViewFieldsConfig { + [key: string]: { + head: string[]; + body: string[]; + meta?: string[]; + breadcrumb_link_disabled?: boolean; + filters?: { + [key: string]: ListViewFilterConfig + }; + } +} + +const PROJECT_FILTER: ListViewFilterConfig = { + component: SelectFilter, + parser: row => row.project.str, // Imperfect typescript type for the row argument. + extraProps: { + defaultOptionName: "Select project..." + } +}; + +const FRAMEWORK_FILTER: ListViewFilterConfig = { + component: SelectFilter, + parser: row => row.framework.str, // Imperfect typescript type for the row argument. + extraProps: { + defaultOptionName: "Select framework..." + } +}; + +const LANGUAGE_FILTER: ListViewFilterConfig = { + component: SelectFilter, + parser: row => row.locale, + extraProps: { + defaultOptionName: "Select language...", + OptionLabels: LOCALE_DISPLAY_MAP + } +}; + +export const listViewFields: ListViewFieldsConfig = { folders: { head: ['name', 'description', 'parentDomain'], body: ['name', 'description', 'parent_folder'] @@ -14,7 +65,10 @@ export const listViewFields = { }, 'risk-assessments': { head: ['name', 'riskMatrix', 'description', 'riskScenarios', 'project'], - body: ['name', 'risk_matrix', 'description', 'risk_scenarios_count', 'project'] + body: ['name', 'risk_matrix', 'description', 'risk_scenarios_count', 'project'], + filters: { + project: PROJECT_FILTER + } }, threats: { head: ['ref', 'name', 'description', 'provider', 'domain'], @@ -77,7 +131,11 @@ export const listViewFields = { }, 'compliance-assessments': { head: ['name', 'framework', 'description', 'project'], - body: ['name', 'framework', 'description', 'project'] + body: ['name', 'framework', 'description', 'project'], + filters: { + project: PROJECT_FILTER, + framework: FRAMEWORK_FILTER + } }, 'requirement-assessments': { head: ['name', 'description', 'complianceAssessment'], @@ -99,7 +157,10 @@ export const listViewFields = { }, 'stored-libraries': { head: ['ref', 'name', 'description', 'language', 'overview'], - body: ['ref_id', 'name', 'description', 'locale', 'overview'] + body: ['ref_id', 'name', 'description', 'locale', 'overview'], + filters: { + locale: LANGUAGE_FILTER + } }, 'loaded-libraries': { head: ['ref', 'name', 'description', 'language', 'overview'], From bb0aabda8b61ad8d72012fc62040e43de9c517f9 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 16:01:50 +0200 Subject: [PATCH 02/42] Make filtering possible of all the object fields including the one not included in the modeltable Formatter --- .../components/Filters/SelectFilter.svelte | 12 +++---- .../components/ModelTable/ModelTable.svelte | 36 +++++++++---------- frontend/src/lib/utils/table.ts | 29 +++++++-------- 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 88aa4a3a1..1104035d7 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -1,9 +1,9 @@ @@ -14,11 +14,7 @@ name={field} --> - {#each options as option} {#if option} diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 8940433ff..215326b1a 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -122,45 +122,45 @@ const tableURLModel = source.meta?.urlmodel ?? URLModel; const filters = listViewFields[tableURLModel].filters ?? {}; const filteredFields = Object.keys(filters); - const filterValues: {[key: string]: any} = {}; + const filterValues: { [key: string]: any } = {}; const filterProps: { - [key: string]: {[key: string]: any} + [key: string]: { [key: string]: any }; } = {}; - function defaultFilterProps(rows,field: string) { - const options = [ - ...new Set( - rows.map(row => filters[field].parser(row.meta)) - ) - ].sort(); + function defaultFilterProps(rows, field: string) { + const getColumn = filters[field].getColumn ?? ((row) => row[field]); + const options = [...new Set(rows.map(getColumn))].sort(); return { options }; } function defaultFilterFunction(columnValue: any, value: any): boolean { - return value ? columnValue === value : true; - } + return value ? columnValue === value : true; + } $: { for (const field of filteredFields) { - handler.filter(filterValues[field], field, filters[field].filter ?? defaultFilterFunction); + handler.filter( + filterValues[field], + filters[field].getColumn ?? field, + filters[field].filter ?? defaultFilterFunction + ); } - }; + } let allowOptionsUpdate = true; - allRows.subscribe(rows => { - if (!allowOptionsUpdate) - return; + allRows.subscribe((rows) => { + if (!allowOptionsUpdate) return; for (const key of filteredFields) { - filterProps[key] = defaultFilterProps(rows,key); + filterProps[key] = (filters[key].filterProps ?? defaultFilterProps)(rows, key); } - if (rows.length > 0) - allowOptionsUpdate = false; + if (rows.length > 0) allowOptionsUpdate = false; }); const rows = handler.getRows(); onMount(() => { + console.log(source); if (orderBy) { orderBy.direction === 'asc' ? handler.sortAsc(orderBy.identifier) diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 673dc18a0..d1da4fc95 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -1,15 +1,16 @@ -import SelectFilter from "$lib/components/Filters/SelectFilter.svelte"; +import SelectFilter from '$lib/components/Filters/SelectFilter.svelte'; import type { ComponentType } from 'svelte'; -import { LOCALE_DISPLAY_MAP } from "./constants"; +import { LOCALE_DISPLAY_MAP } from './constants'; +import type { Row } from '@vincjo/datatables'; -type JSONObject = {[key: string]: JSONObject} | JSONObject[] | string | number | boolean | null; +type JSONObject = { [key: string]: JSONObject } | JSONObject[] | string | number | boolean | null; interface ListViewFilterConfig { component: ComponentType; - parser?: (row: JSONObject) => any; // Imperfect typescript type for the row argument. filter?: (columnValue: any, value: any) => boolean; - filterProps?: (rows: any[],field: string) => {[key: string]: any}; - extraProps?: {[key: string]: any}; + getColumn?: (row: Row) => Row[keyof Row]; + filterProps?: (rows: any[], field: string) => { [key: string]: any }; + extraProps?: { [key: string]: any }; } interface ListViewFieldsConfig { @@ -19,32 +20,32 @@ interface ListViewFieldsConfig { meta?: string[]; breadcrumb_link_disabled?: boolean; filters?: { - [key: string]: ListViewFilterConfig + [key: string]: ListViewFilterConfig; }; - } + }; } const PROJECT_FILTER: ListViewFilterConfig = { component: SelectFilter, - parser: row => row.project.str, // Imperfect typescript type for the row argument. + getColumn: (row) => row.project.str, extraProps: { - defaultOptionName: "Select project..." + defaultOptionName: 'Select project...' } }; const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, - parser: row => row.framework.str, // Imperfect typescript type for the row argument. + getColumn: (row) => row.framework.str, extraProps: { - defaultOptionName: "Select framework..." + defaultOptionName: 'Select framework...' } }; const LANGUAGE_FILTER: ListViewFilterConfig = { component: SelectFilter, - parser: row => row.locale, + getColumn: (row) => row.locale, extraProps: { - defaultOptionName: "Select language...", + defaultOptionName: 'Select language...', OptionLabels: LOCALE_DISPLAY_MAP } }; From b9549ad7bfc20d9649a3ac0d1d71ae6c95716e58 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 16:30:04 +0200 Subject: [PATCH 03/42] Add checkbox filter type --- .../components/Filters/CheckboxFilter.svelte | 11 ++++++++++ frontend/src/lib/utils/table.ts | 20 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/components/Filters/CheckboxFilter.svelte diff --git a/frontend/src/lib/components/Filters/CheckboxFilter.svelte b/frontend/src/lib/components/Filters/CheckboxFilter.svelte new file mode 100644 index 000000000..13b1e2910 --- /dev/null +++ b/frontend/src/lib/components/Filters/CheckboxFilter.svelte @@ -0,0 +1,11 @@ + + +
+ {title} + + +
diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index d1da4fc95..aa9920367 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -1,4 +1,5 @@ import SelectFilter from '$lib/components/Filters/SelectFilter.svelte'; +import CheckboxFilter from '$lib/components/Filters/CheckboxFilter.svelte'; import type { ComponentType } from 'svelte'; import { LOCALE_DISPLAY_MAP } from './constants'; import type { Row } from '@vincjo/datatables'; @@ -50,6 +51,22 @@ const LANGUAGE_FILTER: ListViewFilterConfig = { } }; +const HAS_RISK_MATRIX_FILTER: ListViewFilterConfig = { + component: CheckboxFilter, + getColumn: row => { + return !row.meta.overview.some( + line => line.startsWith("risk_matrix") + ); // It would be better to directly have a boolean given by the library data which is set to True when the library has a risk matrix or false otherwise. + }, + filterProps: (rows: any[],field: string) => new Object(), + filter: (builtin: boolean, value: boolean): boolean => { + return value ? !builtin : true; + }, + extraProps: { + title: "Only display matrix libraries" + } +}; + export const listViewFields: ListViewFieldsConfig = { folders: { head: ['name', 'description', 'parentDomain'], @@ -160,7 +177,8 @@ export const listViewFields: ListViewFieldsConfig = { head: ['ref', 'name', 'description', 'language', 'overview'], body: ['ref_id', 'name', 'description', 'locale', 'overview'], filters: { - locale: LANGUAGE_FILTER + locale: LANGUAGE_FILTER, + has_risk_matrix: HAS_RISK_MATRIX_FILTER } }, 'loaded-libraries': { From 5a8229070dafc802c17f9f2abda6f5202a621773 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 17:31:40 +0200 Subject: [PATCH 04/42] Add domain filtering for all objects with a domain --- backend/core/serializers.py | 2 + .../components/ModelTable/ModelTable.svelte | 1 - frontend/src/lib/utils/table.ts | 88 +++++++++++++++---- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 094a107e7..0f096c219 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -131,6 +131,7 @@ class Meta: class RiskAssessmentReadSerializer(AssessmentReadSerializer): + project = FieldsRelatedField(["folder"]) risk_scenarios = FieldsRelatedField(many=True) risk_scenarios_count = serializers.IntegerField(source="risk_scenarios.count") risk_matrix = FieldsRelatedField() @@ -467,6 +468,7 @@ class Meta: class ComplianceAssessmentReadSerializer(AssessmentReadSerializer): + project = FieldsRelatedField(["folder"]) framework = FieldsRelatedField( ["id", "min_score", "max_score", "implementation_groups_definition"] ) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 215326b1a..f231e0246 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -160,7 +160,6 @@ const rows = handler.getRows(); onMount(() => { - console.log(source); if (orderBy) { orderBy.direction === 'asc' ? handler.sortAsc(orderBy.identifier) diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index aa9920367..e5975b4ea 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -26,11 +26,34 @@ interface ListViewFieldsConfig { }; } +const DOMAIN_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: (row) => row.folder.str, + extraProps: { + defaultOptionName: 'Select domain...' + } +}; + +const DOMAIN_FILTER_FROM_PROJECT: ListViewFilterConfig = { + ...DOMAIN_FILTER, + getColumn: (row) => row.project.folder.str, +}; + +const DOMAIN_FILTER_FROM_META: ListViewFilterConfig = { + ...DOMAIN_FILTER, + getColumn: (row) => row.meta.folder.str, +}; + +const DOMAIN_FILTER_FROM_META_PROJECT: ListViewFilterConfig = { + ...DOMAIN_FILTER, + getColumn: (row) => row.meta.project.folder.str, +}; + const PROJECT_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.project.str, extraProps: { - defaultOptionName: 'Select project...' + defaultOptionName: 'Select project...' // Make translations } }; @@ -38,7 +61,7 @@ const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.framework.str, extraProps: { - defaultOptionName: 'Select framework...' + defaultOptionName: 'Select framework...' // Make translations } }; @@ -46,7 +69,7 @@ const LANGUAGE_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.locale, extraProps: { - defaultOptionName: 'Select language...', + defaultOptionName: 'Select language...', // Make translations OptionLabels: LOCALE_DISPLAY_MAP } }; @@ -63,7 +86,7 @@ const HAS_RISK_MATRIX_FILTER: ListViewFilterConfig = { return value ? !builtin : true; }, extraProps: { - title: "Only display matrix libraries" + title: "Only display matrix libraries" // Make translations } }; @@ -74,24 +97,34 @@ export const listViewFields: ListViewFieldsConfig = { }, projects: { head: ['name', 'description', 'domain'], - body: ['name', 'description', 'folder'] + body: ['name', 'description', 'folder'], + filters: { + domain: DOMAIN_FILTER + } }, 'risk-matrices': { head: ['name', 'description', 'provider', 'domain'], body: ['name', 'description', 'provider', 'folder'], - meta: ['id', 'urn'] + meta: ['id', 'urn'], + filters: { + domain: DOMAIN_FILTER + } }, 'risk-assessments': { head: ['name', 'riskMatrix', 'description', 'riskScenarios', 'project'], body: ['name', 'risk_matrix', 'description', 'risk_scenarios_count', 'project'], filters: { + domain: DOMAIN_FILTER_FROM_PROJECT, project: PROJECT_FILTER } }, threats: { head: ['ref', 'name', 'description', 'provider', 'domain'], body: ['ref_id', 'name', 'description', 'provider', 'folder'], - meta: ['id', 'urn'] + meta: ['id', 'urn'], + filters: { + domain: DOMAIN_FILTER + } }, 'risk-scenarios': { head: ['name', 'threats', 'riskAssessment', 'appliedControls', 'currentLevel', 'residualLevel'], @@ -102,28 +135,46 @@ export const listViewFields: ListViewFieldsConfig = { 'applied_controls', 'current_level', 'residual_level' - ] + ], + filters: { + domain: DOMAIN_FILTER_FROM_META_PROJECT + } }, 'risk-acceptances': { head: ['name', 'description', 'riskScenarios'], - body: ['name', 'description', 'risk_scenarios'] + body: ['name', 'description', 'risk_scenarios'], + filters: { + domain: DOMAIN_FILTER_FROM_META + } }, 'applied-controls': { head: ['name', 'description', 'category', 'eta', 'domain', 'referenceControl'], - body: ['name', 'description', 'category', 'eta', 'folder', 'reference_control'] + body: ['name', 'description', 'category', 'eta', 'folder', 'reference_control'], + filters: { + domain: DOMAIN_FILTER + } }, policies: { head: ['name', 'description', 'eta', 'domain', 'referenceControl'], - body: ['name', 'description', 'eta', 'folder', 'reference_control'] + body: ['name', 'description', 'eta', 'folder', 'reference_control'], + filters: { + domain: DOMAIN_FILTER + } }, 'reference-controls': { head: ['ref', 'name', 'description', 'category', 'provider', 'domain'], body: ['ref_id', 'name', 'description', 'category', 'provider', 'folder'], - meta: ['id', 'urn'] + meta: ['id', 'urn'], + filters: { + domain: DOMAIN_FILTER + } }, assets: { head: ['name', 'description', 'businessValue', 'domain'], - body: ['name', 'description', 'business_value', 'folder'] + body: ['name', 'description', 'business_value', 'folder'], + filters: { + domain: DOMAIN_FILTER + } }, users: { head: ['email', 'firstName', 'lastName'], @@ -145,12 +196,16 @@ export const listViewFields: ListViewFieldsConfig = { frameworks: { head: ['name', 'description', 'provider', 'complianceAssessments', 'domain'], body: ['name', 'description', 'provider', 'compliance_assessments', 'folder'], - meta: ['id', 'urn'] + meta: ['id', 'urn'], + filters: { + domain: DOMAIN_FILTER + } }, 'compliance-assessments': { head: ['name', 'framework', 'description', 'project'], body: ['name', 'framework', 'description', 'project'], filters: { + domain: DOMAIN_FILTER_FROM_PROJECT, project: PROJECT_FILTER, framework: FRAMEWORK_FILTER } @@ -162,7 +217,10 @@ export const listViewFields: ListViewFieldsConfig = { }, evidences: { head: ['name', 'file', 'description'], - body: ['name', 'attachment', 'description'] + body: ['name', 'attachment', 'description'], + filters: { + domain: DOMAIN_FILTER_FROM_META + } }, requirements: { head: ['ref', 'name', 'description', 'framework'], From c2e8801f3084384f8258b43849ee66ac8dfca307 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 17:40:17 +0200 Subject: [PATCH 05/42] Add project filtering for all objects with a project Add semicolon --- frontend/src/lib/utils/table.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index e5975b4ea..55eb31ca5 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -57,6 +57,11 @@ const PROJECT_FILTER: ListViewFilterConfig = { } }; +const PROJECT_FILTER_FROM_META: ListViewFilterConfig = { + ...PROJECT_FILTER, + getColumn: (row) => row.meta.project.str +}; + const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.framework.str, @@ -137,7 +142,8 @@ export const listViewFields: ListViewFieldsConfig = { 'residual_level' ], filters: { - domain: DOMAIN_FILTER_FROM_META_PROJECT + domain: DOMAIN_FILTER_FROM_META_PROJECT, + project: PROJECT_FILTER_FROM_META } }, 'risk-acceptances': { From 6a423ecac7af4c3136aeda34508790aa5c0dc665 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 17:55:39 +0200 Subject: [PATCH 06/42] Add status filtering for all objects with a status-like object --- .../components/Filters/SelectFilter.svelte | 28 ++++++++------- frontend/src/lib/utils/table.ts | 36 ++++++++++++++++--- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 1104035d7..3916e2a97 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -2,8 +2,10 @@ export let options: any[]; export let value: string | undefined; export let defaultOptionName: string = '--'; - export let OptionLabels: { [key: string]: string } = Object.fromEntries( - options.map((option) => [option, option.toString()]) + export let optionLabels: { [key: string]: string } = Object.fromEntries( + options + .filter(option => Boolean(option)) + .map((option) => [option, option.toString()]) ); @@ -14,13 +16,15 @@ name={field} --> - +{#if Object.keys(optionLabels).length > 0} + +{/if} diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 55eb31ca5..18ffa9cf1 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -62,6 +62,30 @@ const PROJECT_FILTER_FROM_META: ListViewFilterConfig = { getColumn: (row) => row.meta.project.str }; +const STATUS_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: (row) => row.meta.status, + extraProps: { + defaultOptionName: 'Select status...' + } +}; + +const TREATMENT_FILTER: ListViewFilterConfig = { // I could make a function just make the code less repeatitive and long for nothing + component: SelectFilter, + getColumn: (row) => row.meta.treatment, + extraProps: { + defaultOptionName: 'Select treatment...' + } +}; + +const STATE_FILTER: ListViewFilterConfig = { // I could make a function just make the code less repeatitive and long for nothing + component: SelectFilter, + getColumn: (row) => row.meta.state, + extraProps: { + defaultOptionName: 'Select state...' + } +}; + const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.framework.str, @@ -120,7 +144,8 @@ export const listViewFields: ListViewFieldsConfig = { body: ['name', 'risk_matrix', 'description', 'risk_scenarios_count', 'project'], filters: { domain: DOMAIN_FILTER_FROM_PROJECT, - project: PROJECT_FILTER + project: PROJECT_FILTER, + status: STATUS_FILTER } }, threats: { @@ -143,21 +168,24 @@ export const listViewFields: ListViewFieldsConfig = { ], filters: { domain: DOMAIN_FILTER_FROM_META_PROJECT, - project: PROJECT_FILTER_FROM_META + project: PROJECT_FILTER_FROM_META, + treatment: TREATMENT_FILTER } }, 'risk-acceptances': { head: ['name', 'description', 'riskScenarios'], body: ['name', 'description', 'risk_scenarios'], filters: { - domain: DOMAIN_FILTER_FROM_META + domain: DOMAIN_FILTER_FROM_META, + state: STATE_FILTER } }, 'applied-controls': { head: ['name', 'description', 'category', 'eta', 'domain', 'referenceControl'], body: ['name', 'description', 'category', 'eta', 'folder', 'reference_control'], filters: { - domain: DOMAIN_FILTER + domain: DOMAIN_FILTER, + status: STATUS_FILTER } }, policies: { From 8c06e190cf12c413e9017534fdfde2e68c75ab37 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 18:04:29 +0200 Subject: [PATCH 07/42] Add approver filtering for risk scenarios --- backend/core/serializers.py | 1 + frontend/src/lib/utils/table.ts | 38 ++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 0f096c219..f7f5af325 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -101,6 +101,7 @@ class RiskAcceptanceReadSerializer(BaseModelSerializer): folder = FieldsRelatedField() approver = FieldsRelatedField() risk_scenarios = FieldsRelatedField(many=True) + approver = FieldsRelatedField(["first_name","last_name"]); state = serializers.CharField(source="get_state_display") diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 18ffa9cf1..e12667130 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -28,7 +28,7 @@ interface ListViewFieldsConfig { const DOMAIN_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: (row) => row.folder.str, + getColumn: row => row.folder.str, extraProps: { defaultOptionName: 'Select domain...' } @@ -36,22 +36,22 @@ const DOMAIN_FILTER: ListViewFilterConfig = { const DOMAIN_FILTER_FROM_PROJECT: ListViewFilterConfig = { ...DOMAIN_FILTER, - getColumn: (row) => row.project.folder.str, + getColumn: row => row.project.folder.str, }; const DOMAIN_FILTER_FROM_META: ListViewFilterConfig = { ...DOMAIN_FILTER, - getColumn: (row) => row.meta.folder.str, + getColumn: row => row.meta.folder.str, }; const DOMAIN_FILTER_FROM_META_PROJECT: ListViewFilterConfig = { ...DOMAIN_FILTER, - getColumn: (row) => row.meta.project.folder.str, + getColumn: row => row.meta.project.folder.str, }; const PROJECT_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: (row) => row.project.str, + getColumn: row => row.project.str, extraProps: { defaultOptionName: 'Select project...' // Make translations } @@ -59,12 +59,12 @@ const PROJECT_FILTER: ListViewFilterConfig = { const PROJECT_FILTER_FROM_META: ListViewFilterConfig = { ...PROJECT_FILTER, - getColumn: (row) => row.meta.project.str + getColumn: row => row.meta.project.str }; const STATUS_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: (row) => row.meta.status, + getColumn: row => row.meta.status, extraProps: { defaultOptionName: 'Select status...' } @@ -72,7 +72,7 @@ const STATUS_FILTER: ListViewFilterConfig = { const TREATMENT_FILTER: ListViewFilterConfig = { // I could make a function just make the code less repeatitive and long for nothing component: SelectFilter, - getColumn: (row) => row.meta.treatment, + getColumn: row => row.meta.treatment, extraProps: { defaultOptionName: 'Select treatment...' } @@ -80,15 +80,28 @@ const TREATMENT_FILTER: ListViewFilterConfig = { // I could make a function just const STATE_FILTER: ListViewFilterConfig = { // I could make a function just make the code less repeatitive and long for nothing component: SelectFilter, - getColumn: (row) => row.meta.state, + getColumn: row => row.meta.state, extraProps: { defaultOptionName: 'Select state...' } }; +const APPROVER_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: row => { + if (row.first_name && row.last_name) { + return `${row.first_name} ${row.last_name}`; + } + return row.meta.approver.str; // This display the email in the approver filter, is this a problem because of email leak risks ? + }, + extraProps: { + defaultOptionName: 'Select approver...' + } +} + const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: (row) => row.framework.str, + getColumn: row => row.framework.str, extraProps: { defaultOptionName: 'Select framework...' // Make translations } @@ -96,7 +109,7 @@ const FRAMEWORK_FILTER: ListViewFilterConfig = { const LANGUAGE_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: (row) => row.locale, + getColumn: row => row.locale, extraProps: { defaultOptionName: 'Select language...', // Make translations OptionLabels: LOCALE_DISPLAY_MAP @@ -177,7 +190,8 @@ export const listViewFields: ListViewFieldsConfig = { body: ['name', 'description', 'risk_scenarios'], filters: { domain: DOMAIN_FILTER_FROM_META, - state: STATE_FILTER + state: STATE_FILTER, + approver: APPROVER_FILTER } }, 'applied-controls': { From bb12b31ad8dfe6b4c5433ee22d380a11f90e2668 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 18:08:43 +0200 Subject: [PATCH 08/42] Add risk assessment filtering for risk scenarios --- frontend/src/lib/utils/table.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index e12667130..d961442b3 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -99,6 +99,14 @@ const APPROVER_FILTER: ListViewFilterConfig = { } } +const RISK_ASSESSMENT_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: row => row.meta.risk_assessment.str, + extraProps: { + defaultOptionName: 'Select risk assessment...' + } +}; + const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: row => row.framework.str, @@ -182,7 +190,8 @@ export const listViewFields: ListViewFieldsConfig = { filters: { domain: DOMAIN_FILTER_FROM_META_PROJECT, project: PROJECT_FILTER_FROM_META, - treatment: TREATMENT_FILTER + treatment: TREATMENT_FILTER, + risk_assessment: RISK_ASSESSMENT_FILTER } }, 'risk-acceptances': { From 91ee5250ca48f955d13a0703b92f13e527a824af Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 26 Jun 2024 18:33:03 +0200 Subject: [PATCH 09/42] Add threat and asset filters for risk scenarios --- frontend/src/lib/utils/table.ts | 53 ++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index d961442b3..f497e9aca 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -107,6 +107,55 @@ const RISK_ASSESSMENT_FILTER: ListViewFilterConfig = { } }; +const THREAT_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: row => row.meta.threats, + filter: (rowThreatName, threatName) => { + if (!threatName) + return true; + return rowThreatName === threatName; + }, + filterProps: (rows, _) => { + const threatSet = new Set(); + for (const row of rows) { + for (const threat of row.meta.threats) { + threatSet.add(threat.str); + } + } + const options = [...threatSet].sort(); + return { options }; + }, + extraProps: { + defaultOptionName: 'Select threat...' + } +}; + +const ASSET_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: row => { + console.log(row); + return row.meta.assets; + }, + filter: (rowAssetName, assetName) => { + if (!assetName) + return true; + return rowAssetName === assetName; + }, + filterProps: (rows, _) => { + const assetSet = new Set(); + for (const row of rows) { + for (const asset of row.meta.assets) { + assetSet.add(asset.str); + } + } + const options = [...assetSet].sort(); + return { options }; + }, + extraProps: { + defaultOptionName: 'Select asset...' + } +}; + const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: row => row.framework.str, @@ -191,7 +240,9 @@ export const listViewFields: ListViewFieldsConfig = { domain: DOMAIN_FILTER_FROM_META_PROJECT, project: PROJECT_FILTER_FROM_META, treatment: TREATMENT_FILTER, - risk_assessment: RISK_ASSESSMENT_FILTER + risk_assessment: RISK_ASSESSMENT_FILTER, + threats: THREAT_FILTER, + assets: ASSET_FILTER } }, 'risk-acceptances': { From 403c66cd921d82047a6ad2a0f3dd96e144f7103a Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Fri, 28 Jun 2024 10:24:32 +0200 Subject: [PATCH 10/42] Add Filter button Remove useless commentary --- .../components/Filters/SelectFilter.svelte | 23 +++++---------- .../components/ModelTable/ModelTable.svelte | 29 +++++++++++++------ frontend/src/lib/utils/table.ts | 8 ++--- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 3916e2a97..12671f61d 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -1,28 +1,19 @@ - - -{#if Object.keys(optionLabels).length > 0} - {#each options as option} {#if option} + {@const label = optionLabels[option] ?? option} {/if} {/each} diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index f231e0246..f514ae0bd 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -126,6 +126,7 @@ const filterProps: { [key: string]: { [key: string]: any }; } = {}; + let displayFilters = false; function defaultFilterProps(rows, field: string) { const getColumn = filters[field].getColumn ?? ((row) => row[field]); @@ -183,15 +184,11 @@
- {#each filteredFields as field} - - {/each} - + {#if filteredFields.length > 0} + + {/if} {#if search} {/if} @@ -202,6 +199,20 @@ {/if}
+ {#if displayFilters} +
+ {#if filteredFields.length > 0} + {#each filteredFields as field} + + {/each} + {/if} +
+ {/if} row.locale, extraProps: { defaultOptionName: 'Select language...', // Make translations - OptionLabels: LOCALE_DISPLAY_MAP + optionLabels: LOCALE_DISPLAY_MAP } }; -const HAS_RISK_MATRIX_FILTER: ListViewFilterConfig = { +/* const HAS_RISK_MATRIX_FILTER: ListViewFilterConfig = { component: CheckboxFilter, getColumn: row => { return !row.meta.overview.some( @@ -187,7 +187,7 @@ const HAS_RISK_MATRIX_FILTER: ListViewFilterConfig = { extraProps: { title: "Only display matrix libraries" // Make translations } -}; +}; */ export const listViewFields: ListViewFieldsConfig = { folders: { @@ -344,7 +344,7 @@ export const listViewFields: ListViewFieldsConfig = { body: ['ref_id', 'name', 'description', 'locale', 'overview'], filters: { locale: LANGUAGE_FILTER, - has_risk_matrix: HAS_RISK_MATRIX_FILTER + // has_risk_matrix: HAS_RISK_MATRIX_FILTER } }, 'loaded-libraries': { From be6d1a4811ec43dfcf66b9cb0f94471c5a721534 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Fri, 28 Jun 2024 14:56:39 +0200 Subject: [PATCH 11/42] chore: run format --- backend/core/serializers.py | 3 +- .../components/Filters/CheckboxFilter.svelte | 8 ++-- .../components/Filters/SelectFilter.svelte | 2 +- .../components/ModelTable/ModelTable.svelte | 27 ++++++----- frontend/src/lib/utils/table.ts | 46 +++++++++---------- 5 files changed, 45 insertions(+), 41 deletions(-) diff --git a/backend/core/serializers.py b/backend/core/serializers.py index f7f5af325..50a810f52 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -101,8 +101,7 @@ class RiskAcceptanceReadSerializer(BaseModelSerializer): folder = FieldsRelatedField() approver = FieldsRelatedField() risk_scenarios = FieldsRelatedField(many=True) - approver = FieldsRelatedField(["first_name","last_name"]); - + approver = FieldsRelatedField(["first_name", "last_name"]) state = serializers.CharField(source="get_state_display") class Meta: diff --git a/frontend/src/lib/components/Filters/CheckboxFilter.svelte b/frontend/src/lib/components/Filters/CheckboxFilter.svelte index 13b1e2910..d5d335ce0 100644 --- a/frontend/src/lib/components/Filters/CheckboxFilter.svelte +++ b/frontend/src/lib/components/Filters/CheckboxFilter.svelte @@ -1,11 +1,11 @@
- {title} - - + {title} + +
diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 12671f61d..1c9201743 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -1,7 +1,7 @@ diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index f514ae0bd..9348023c7 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -185,8 +185,13 @@
{#if filteredFields.length > 0} - {/if} {#if search} @@ -200,16 +205,16 @@ {/if}
{#if displayFilters} -
+
{#if filteredFields.length > 0} - {#each filteredFields as field} - - {/each} + {#each filteredFields as field} + + {/each} {/if}
{/if} diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 6ffda0d8f..573704717 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -28,7 +28,7 @@ interface ListViewFieldsConfig { const DOMAIN_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.folder.str, + getColumn: (row) => row.folder.str, extraProps: { defaultOptionName: 'Select domain...' } @@ -36,22 +36,22 @@ const DOMAIN_FILTER: ListViewFilterConfig = { const DOMAIN_FILTER_FROM_PROJECT: ListViewFilterConfig = { ...DOMAIN_FILTER, - getColumn: row => row.project.folder.str, + getColumn: (row) => row.project.folder.str }; const DOMAIN_FILTER_FROM_META: ListViewFilterConfig = { ...DOMAIN_FILTER, - getColumn: row => row.meta.folder.str, + getColumn: (row) => row.meta.folder.str }; const DOMAIN_FILTER_FROM_META_PROJECT: ListViewFilterConfig = { ...DOMAIN_FILTER, - getColumn: row => row.meta.project.folder.str, + getColumn: (row) => row.meta.project.folder.str }; const PROJECT_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.project.str, + getColumn: (row) => row.project.str, extraProps: { defaultOptionName: 'Select project...' // Make translations } @@ -59,28 +59,30 @@ const PROJECT_FILTER: ListViewFilterConfig = { const PROJECT_FILTER_FROM_META: ListViewFilterConfig = { ...PROJECT_FILTER, - getColumn: row => row.meta.project.str + getColumn: (row) => row.meta.project.str }; const STATUS_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.meta.status, + getColumn: (row) => row.meta.status, extraProps: { defaultOptionName: 'Select status...' } }; -const TREATMENT_FILTER: ListViewFilterConfig = { // I could make a function just make the code less repeatitive and long for nothing +const TREATMENT_FILTER: ListViewFilterConfig = { + // I could make a function just make the code less repeatitive and long for nothing component: SelectFilter, - getColumn: row => row.meta.treatment, + getColumn: (row) => row.meta.treatment, extraProps: { defaultOptionName: 'Select treatment...' } }; -const STATE_FILTER: ListViewFilterConfig = { // I could make a function just make the code less repeatitive and long for nothing +const STATE_FILTER: ListViewFilterConfig = { + // I could make a function just make the code less repeatitive and long for nothing component: SelectFilter, - getColumn: row => row.meta.state, + getColumn: (row) => row.meta.state, extraProps: { defaultOptionName: 'Select state...' } @@ -88,7 +90,7 @@ const STATE_FILTER: ListViewFilterConfig = { // I could make a function just mak const APPROVER_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => { + getColumn: (row) => { if (row.first_name && row.last_name) { return `${row.first_name} ${row.last_name}`; } @@ -97,11 +99,11 @@ const APPROVER_FILTER: ListViewFilterConfig = { extraProps: { defaultOptionName: 'Select approver...' } -} +}; const RISK_ASSESSMENT_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.meta.risk_assessment.str, + getColumn: (row) => row.meta.risk_assessment.str, extraProps: { defaultOptionName: 'Select risk assessment...' } @@ -109,10 +111,9 @@ const RISK_ASSESSMENT_FILTER: ListViewFilterConfig = { const THREAT_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.meta.threats, + getColumn: (row) => row.meta.threats, filter: (rowThreatName, threatName) => { - if (!threatName) - return true; + if (!threatName) return true; return rowThreatName === threatName; }, filterProps: (rows, _) => { @@ -132,13 +133,12 @@ const THREAT_FILTER: ListViewFilterConfig = { const ASSET_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => { + getColumn: (row) => { console.log(row); return row.meta.assets; }, filter: (rowAssetName, assetName) => { - if (!assetName) - return true; + if (!assetName) return true; return rowAssetName === assetName; }, filterProps: (rows, _) => { @@ -158,7 +158,7 @@ const ASSET_FILTER: ListViewFilterConfig = { const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.framework.str, + getColumn: (row) => row.framework.str, extraProps: { defaultOptionName: 'Select framework...' // Make translations } @@ -166,7 +166,7 @@ const FRAMEWORK_FILTER: ListViewFilterConfig = { const LANGUAGE_FILTER: ListViewFilterConfig = { component: SelectFilter, - getColumn: row => row.locale, + getColumn: (row) => row.locale, extraProps: { defaultOptionName: 'Select language...', // Make translations optionLabels: LOCALE_DISPLAY_MAP @@ -343,7 +343,7 @@ export const listViewFields: ListViewFieldsConfig = { head: ['ref', 'name', 'description', 'language', 'overview'], body: ['ref_id', 'name', 'description', 'locale', 'overview'], filters: { - locale: LANGUAGE_FILTER, + locale: LANGUAGE_FILTER // has_risk_matrix: HAS_RISK_MATRIX_FILTER } }, From 26e5460c2211e97e0f06c9d23ff98cbb6c037847 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Fri, 28 Jun 2024 15:14:34 +0200 Subject: [PATCH 12/42] Hide the filter button for empty datatables --- frontend/src/lib/components/ModelTable/ModelTable.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 9348023c7..21558e183 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -148,8 +148,10 @@ } } + let numberOfRows = 0; let allowOptionsUpdate = true; allRows.subscribe((rows) => { + numberOfRows = rows.length; if (!allowOptionsUpdate) return; for (const key of filteredFields) { @@ -184,7 +186,7 @@
- {#if filteredFields.length > 0} + {#if filteredFields.length > 0 && numberOfRows > 0} +
+
+ {#if filteredFields.length > 0} + {#each filteredFields as field} + {field} + + {/each} + {/if} +
+
{/if} {#if search} @@ -207,20 +230,6 @@ {/if}
- {#if displayFilters} -
- {#if filteredFields.length > 0} - {#each filteredFields as field} - - {/each} - {/if} -
- {/if}
Date: Tue, 2 Jul 2024 13:13:52 +0200 Subject: [PATCH 17/42] Fix filter button dissapearing when no row pass the filter --- frontend/src/lib/components/ModelTable/ModelTable.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 549efd162..542831e2a 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -149,10 +149,8 @@ } } - let numberOfRows = 0; let allowOptionsUpdate = true; allRows.subscribe((rows) => { - numberOfRows = rows.length; if (!allowOptionsUpdate) return; for (const key of filteredFields) { @@ -197,7 +195,7 @@
- {#if filteredFields.length > 0 && numberOfRows > 0} + {#if filteredFields.length > 0} -
-
+
+
{#if filteredFields.length > 0} {#each filteredFields as field} - {field} - +
+ +
{/each} {/if}
From daf7374dcc2d9b782d6421f747872d4a5361dcfb Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Tue, 2 Jul 2024 13:33:41 +0200 Subject: [PATCH 19/42] fix: empty rows --- frontend/src/lib/components/ModelTable/ModelTable.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 9a61b9b34..972ddcb04 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -149,8 +149,10 @@ } } + let numberOfRows = 0; let allowOptionsUpdate = true; allRows.subscribe((rows) => { + numberOfRows = rows.length; if (!allowOptionsUpdate) return; for (const key of filteredFields) { @@ -204,7 +206,7 @@
- {#if filteredFields.length > 0} + {#if numberOfRows > 0} {#each filteredFields as field}
{/each} + {:else} +

Nothing to filter for now

{/if}
From e16c377a4e3c71aaceaef65ffd2ff2ce3b43e66f Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Tue, 2 Jul 2024 13:38:27 +0200 Subject: [PATCH 20/42] fix: no entries with filters --- .../components/ModelTable/ModelTable.svelte | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 972ddcb04..a0afe6e8d 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -206,20 +206,16 @@
- {#if numberOfRows > 0} - {#each filteredFields as field} -
- -
- {/each} - {:else} -

Nothing to filter for now

- {/if} + {#each filteredFields as field} +
+ +
+ {/each}
{/if} From 051f3b04a565e676c06243a1631a9a7580940440 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Tue, 2 Jul 2024 13:53:05 +0200 Subject: [PATCH 21/42] Hide the filter button only when there is no row from the start --- frontend/src/lib/components/ModelTable/ModelTable.svelte | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index a0afe6e8d..e2b45507f 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -119,6 +119,7 @@ const handler = new DataHandler(data, { rowsPerPage: pagination ? numberRowsPerPage : undefined }); + $: hasRows = data.length > 0; const allRows = handler.getAllRows(); const tableURLModel = source.meta?.urlmodel ?? URLModel; const filters = fromListView ? listViewFields[tableURLModel].filters ?? {} : {}; @@ -149,12 +150,9 @@ } } - let numberOfRows = 0; let allowOptionsUpdate = true; allRows.subscribe((rows) => { - numberOfRows = rows.length; if (!allowOptionsUpdate) return; - for (const key of filteredFields) { filterProps[key] = (filters[key].filterProps ?? defaultFilterProps)(rows, key); } @@ -197,7 +195,7 @@
- {#if filteredFields.length > 0} + {#if filteredFields.length > 0 && hasRows} -
+
{#each filteredFields as field}
From c21eb1a4376116ea101771237fed2e9fc22d21bd Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 3 Jul 2024 11:00:29 +0200 Subject: [PATCH 23/42] Add typesearch to the SelectFilter component --- frontend/src/app.postcss | 6 + .../components/Filters/SelectFilter.svelte | 109 +++++++++++++++--- .../components/ModelTable/ModelTable.svelte | 5 +- 3 files changed, 99 insertions(+), 21 deletions(-) diff --git a/frontend/src/app.postcss b/frontend/src/app.postcss index a8bf3e1cc..e12ea162b 100644 --- a/frontend/src/app.postcss +++ b/frontend/src/app.postcss @@ -11,3 +11,9 @@ body { .capitalize-first:first-letter { @apply capitalize; } + +@layer base { + [data-popup] { + transition: none; + } +} diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 299e3ec64..272ea34e7 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -1,27 +1,98 @@ -{#if options.length > 0} - - {inputFocused = true;}} + on:blur={(event) => { // Add FocusEvent typing + if (event?.relatedTarget?.tagName !== "BUTTON") { + inputFocused = false; + } + }} + /> + {#if inputFocused} +
+ {#if matchingOptionsIndices.length == 0} + No result found + {/if} + {#each matchingOptionsIndices as [optionIndex, matchIndex]} + {@const option = options[optionIndex]} + {@const splittedOption = [ + option.substring(0,matchIndex), + option.substring(matchIndex,matchIndex+searchText.length), + option.substring(matchIndex+searchText.length) + ]} + + {/each} +
{/if} - {/each} - + {:else} + { + value = ""; + }}/> + {/if} +
+{:else} + + {#if options.length > 0} + + {/if} {/if} diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index b4a73026f..d27002637 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -188,7 +188,8 @@ const popupFilter: PopupSettings = { event: 'click', target: 'popupFilter', - placement: 'bottom-start' + placement: 'bottom-start', + closeQuery: 'a[href]' }; @@ -405,4 +406,4 @@ {/if} -
+
\ No newline at end of file From 6c07ee4062fdfe936b3e24dd65b9e947ad742ec3 Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 3 Jul 2024 12:21:52 +0200 Subject: [PATCH 24/42] Add scrolling for SelectFilter Remove useless comment --- .../components/Filters/SelectFilter.svelte | 14 +++++++----- frontend/src/lib/utils/table.ts | 22 +++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 272ea34e7..901211949 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -25,9 +25,13 @@ [optionIndex, option.toLowerCase().indexOf(searchText)] : [null,-1] ).filter(([_, matchIndex]) => matchIndex >= 0); - - + /* if (options.some(x => x === "Domain 1")) { // Code to test the scroll + for (let i=4;i<50;i++) { + options.push(`Dom4in ${i}`); + } + } */ + { searchText = ""; @@ -43,7 +47,7 @@ {inputFocused = true;}} @@ -54,7 +58,7 @@ }} /> {#if inputFocused} -
+
{#if matchingOptionsIndices.length == 0} No result found {/if} @@ -67,7 +71,7 @@ ]} diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 573704717..b899d8610 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -30,7 +30,7 @@ const DOMAIN_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.folder.str, extraProps: { - defaultOptionName: 'Select domain...' + defaultOptionName: 'Select domain' // ...' } }; @@ -53,7 +53,7 @@ const PROJECT_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.project.str, extraProps: { - defaultOptionName: 'Select project...' // Make translations + defaultOptionName: 'Select project' // ...' // Make translations } }; @@ -66,7 +66,7 @@ const STATUS_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.meta.status, extraProps: { - defaultOptionName: 'Select status...' + defaultOptionName: 'Select status' // ...' } }; @@ -75,7 +75,7 @@ const TREATMENT_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.meta.treatment, extraProps: { - defaultOptionName: 'Select treatment...' + defaultOptionName: 'Select treatment' // ...' } }; @@ -84,7 +84,7 @@ const STATE_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.meta.state, extraProps: { - defaultOptionName: 'Select state...' + defaultOptionName: 'Select state' // ...' } }; @@ -97,7 +97,7 @@ const APPROVER_FILTER: ListViewFilterConfig = { return row.meta.approver.str; // This display the email in the approver filter, is this a problem because of email leak risks ? }, extraProps: { - defaultOptionName: 'Select approver...' + defaultOptionName: 'Select approver' // ...' } }; @@ -105,7 +105,7 @@ const RISK_ASSESSMENT_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.meta.risk_assessment.str, extraProps: { - defaultOptionName: 'Select risk assessment...' + defaultOptionName: 'Select risk assessment' // ...' } }; @@ -127,7 +127,7 @@ const THREAT_FILTER: ListViewFilterConfig = { return { options }; }, extraProps: { - defaultOptionName: 'Select threat...' + defaultOptionName: 'Select threat' // ...' } }; @@ -152,7 +152,7 @@ const ASSET_FILTER: ListViewFilterConfig = { return { options }; }, extraProps: { - defaultOptionName: 'Select asset...' + defaultOptionName: 'Select asset' // ...' } }; @@ -160,7 +160,7 @@ const FRAMEWORK_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.framework.str, extraProps: { - defaultOptionName: 'Select framework...' // Make translations + defaultOptionName: 'Select framework' // ...' // Make translations } }; @@ -168,7 +168,7 @@ const LANGUAGE_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.locale, extraProps: { - defaultOptionName: 'Select language...', // Make translations + defaultOptionName: 'Select language', // ...' // Make translations optionLabels: LOCALE_DISPLAY_MAP } }; From f78b770e46e10c016d824a462cd36b08b723757a Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Wed, 3 Jul 2024 14:39:45 +0200 Subject: [PATCH 25/42] Set translation usage for SelectFilter --- frontend/messages/en.json | 3 ++- frontend/src/lib/components/Filters/SelectFilter.svelte | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 7a2bd052d..6dbd2f709 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -617,5 +617,6 @@ "ssoSettings": "SSO settings", "ssoSettingsDescription": "Configure your Single Sign-On settings here.", "sso": "SSO", - "isSso": "Is SSO" + "isSso": "Is SSO", + "noResultFound": "No result found" } diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index 901211949..da76a8dd9 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -1,4 +1,6 @@ - { - searchText = ""; - inputFocused = false; -}}/> + { + searchText = ''; + inputFocused = false; + }} +/> {#if !hasOptionLabels} @@ -50,32 +51,41 @@
{#if !filterApplied} {inputFocused = true;}} - on:blur={(event) => { // Add FocusEvent typing - if (event?.relatedTarget?.tagName !== "BUTTON") { + on:focus={() => { + inputFocused = true; + }} + on:blur={(event) => { + // Add FocusEvent typing + if (event?.relatedTarget?.tagName !== 'BUTTON') { inputFocused = false; } }} /> {#if inputFocused} -
+
{#if matchingOptionsIndices.length == 0} - {m.noResultFound()} + {m.noResultFound()} {/if} {#each matchingOptionsIndices as [optionIndex, matchIndex]} {@const option = options[optionIndex]} {@const splittedOption = [ - option.substring(0,matchIndex), - option.substring(matchIndex,matchIndex+searchText.length), - option.substring(matchIndex+searchText.length) + option.substring(0, matchIndex), + option.substring(matchIndex, matchIndex + searchText.length), + option.substring(matchIndex + searchText.length) ]}
{/if} {:else} - { - value = ""; - }}/> + { + value = ''; + }} + /> {/if}
{:else} {#if options.length > 0} - {#each options as option} {#if option} diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index f87385552..6a42bde20 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -197,7 +197,8 @@
{#if filteredFields.length > 0 && hasRows}
@@ -405,4 +406,4 @@ {/if} -
\ No newline at end of file +
diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 79e693a1c..67db57b9c 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -3,7 +3,7 @@ import CheckboxFilter from '$lib/components/Filters/CheckboxFilter.svelte'; import type { ComponentType } from 'svelte'; import { LOCALE_DISPLAY_MAP } from './constants'; import type { Row } from '@vincjo/datatables'; -import * as m from '$paraglide/messages' +import * as m from '$paraglide/messages'; type JSONObject = { [key: string]: JSONObject } | JSONObject[] | string | number | boolean | null; From 06de3be3592224de3f26f6171f09a57f494f2253 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 3 Jul 2024 16:54:39 +0200 Subject: [PATCH 36/42] chore: run format --- frontend/src/lib/components/Filters/SelectFilter.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/Filters/SelectFilter.svelte b/frontend/src/lib/components/Filters/SelectFilter.svelte index f69679317..8eadbdaf5 100644 --- a/frontend/src/lib/components/Filters/SelectFilter.svelte +++ b/frontend/src/lib/components/Filters/SelectFilter.svelte @@ -67,7 +67,9 @@ }} /> {#if inputFocused} -
+
{#if matchingOptionsIndices.length == 0} {m.noResultFound()} {value = option;}} + on:click|stopPropagation={() => { + value = option; + }} class="block text-center bg-white py-1 px-2 w-full hover:text-primary-500 hover:font-semibold animation duration-100 overflow-ellipsis overflow-hidden" > {splittedOption[0]}{splittedOption[1]}{splittedOption[2]} From 7effbb41b12371b3b628299a73cc96af8fef5898 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 3 Jul 2024 17:12:38 +0200 Subject: [PATCH 37/42] ci: fix api tests --- backend/app_tests/api/test_api_risk_scenarios.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/app_tests/api/test_api_risk_scenarios.py b/backend/app_tests/api/test_api_risk_scenarios.py index a402c41be..b848508e5 100644 --- a/backend/app_tests/api/test_api_risk_scenarios.py +++ b/backend/app_tests/api/test_api_risk_scenarios.py @@ -197,6 +197,7 @@ def test_get_risk_scenarios(self, test): "treatment": RISK_SCENARIO_TREATMENT_STATUS[1], "risk_assessment": { "id": str(risk_assessment.id), + "name": str(risk_assessment.name), "str": str(risk_assessment), }, "threats": [{"id": str(threat.id), "str": str(threat)}], @@ -256,6 +257,7 @@ def test_create_risk_scenarios(self, test): "risk_assessment": { "id": str(risk_assessment.id), "str": str(risk_assessment), + "name": str(risk_assessment.name), }, "threats": [{"id": str(threat.id), "str": threat.name}], "risk_matrix": { @@ -339,6 +341,7 @@ def test_update_risk_scenarios(self, test): "risk_assessment": { "id": str(risk_assessment.id), "str": str(risk_assessment), + "name": str(risk_assessment.name), }, "threats": [{"id": str(threat.id), "str": threat.name}], "risk_matrix": { From bb2ccad37b64e4bdce1625b1b5c49edac4cb044a Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Thu, 4 Jul 2024 11:42:06 +0200 Subject: [PATCH 38/42] Add status filter for the project list view --- frontend/src/lib/utils/table.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/utils/table.ts b/frontend/src/lib/utils/table.ts index 67db57b9c..4feff61ac 100644 --- a/frontend/src/lib/utils/table.ts +++ b/frontend/src/lib/utils/table.ts @@ -27,6 +27,14 @@ interface ListViewFieldsConfig { }; } +const PROJECT_STATUS_FILTER: ListViewFilterConfig = { + component: SelectFilter, + getColumn: (row) => row.meta.lc_status, + extraProps: { + defaultOptionName: "status" + } +}; + const DOMAIN_FILTER: ListViewFilterConfig = { component: SelectFilter, getColumn: (row) => row.folder.str, @@ -199,7 +207,8 @@ export const listViewFields: ListViewFieldsConfig = { head: ['name', 'description', 'domain'], body: ['name', 'description', 'folder'], filters: { - domain: DOMAIN_FILTER + domain: DOMAIN_FILTER, + lc_status: PROJECT_STATUS_FILTER } }, 'risk-matrices': { From c989c24360cbe39b777fe20e989c3df45aa872be Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Thu, 4 Jul 2024 12:11:28 +0200 Subject: [PATCH 39/42] Add filters to nested tables --- frontend/src/lib/components/ModelTable/ModelTable.svelte | 9 ++++++--- frontend/src/routes/(app)/[model=urlmodel]/+page.svelte | 2 +- frontend/src/routes/(app)/libraries/+page.svelte | 2 -- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index 6a42bde20..bd5d54626 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -88,7 +88,6 @@ export let deleteForm: SuperValidated | undefined = undefined; export let URLModel: urlModel | undefined = undefined; export let detailQueryParameter: string | undefined; - export let fromListView: boolean = false; detailQueryParameter = detailQueryParameter ? `?${detailQueryParameter}` : ''; const user = $page.data.user; @@ -115,6 +114,10 @@ $: data = source.body.map((item: Record, index: number) => { return { ...item, meta: source.meta ? { ...source.meta[index] } : undefined }; }); + const columnFields = new Set( + source.body.length === 0 ? [] + : Object.keys(source.body[0]) + ); const handler = new DataHandler(data, { rowsPerPage: pagination ? numberRowsPerPage : undefined @@ -122,8 +125,8 @@ $: hasRows = data.length > 0; const allRows = handler.getAllRows(); const tableURLModel = source.meta?.urlmodel ?? URLModel; - const filters = fromListView ? listViewFields[tableURLModel].filters ?? {} : {}; - const filteredFields = Object.keys(filters); + const filters = listViewFields[tableURLModel].filters ?? {}; + const filteredFields = Object.keys(filters).filter(key => columnFields.has(key)); const filterValues: { [key: string]: any } = {}; const filterProps: { [key: string]: { [key: string]: any }; diff --git a/frontend/src/routes/(app)/[model=urlmodel]/+page.svelte b/frontend/src/routes/(app)/[model=urlmodel]/+page.svelte index 3912c0358..6575db23e 100644 --- a/frontend/src/routes/(app)/[model=urlmodel]/+page.svelte +++ b/frontend/src/routes/(app)/[model=urlmodel]/+page.svelte @@ -47,7 +47,7 @@ {#if data.table}
{#key URLModel} - +
{#if !['risk-matrices', 'frameworks', 'user-groups', 'role-assignments'].includes(URLModel)}