From 00f867102ff321c4b418c72f8d870fd8ae0b4d5e Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Sun, 22 Dec 2024 11:37:22 +0100 Subject: [PATCH 1/4] starting point --- .../src/routes/(app)/(internal)/experimental/+page.svelte | 6 ++++++ .../(app)/(internal)/insights/controls-impact/+page.svelte | 0 2 files changed, 6 insertions(+) create mode 100644 frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte diff --git a/frontend/src/routes/(app)/(internal)/experimental/+page.svelte b/frontend/src/routes/(app)/(internal)/experimental/+page.svelte index 7964bcdf6..751686494 100644 --- a/frontend/src/routes/(app)/(internal)/experimental/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/experimental/+page.svelte @@ -27,4 +27,10 @@ link="assets/graph" tags={['analysis', 'assets']} /> +
diff --git a/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte new file mode 100644 index 000000000..e69de29bb From 1b7288fef1f5625edcf800bb2ee08de96a5526d2 Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Sun, 22 Dec 2024 17:48:23 +0100 Subject: [PATCH 2/4] wip --- backend/core/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/core/views.py b/backend/core/views.py index 915e0c054..092155b6b 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -1267,6 +1267,12 @@ def ids(self, request): return Response(my_map) + @action(detail=False, name="Generate data for applied controls impact graph") + def impact_graph(self, request): + (viewable_controls_ids, _, _) = RoleAssignment.get_accessible_object_ids( + Folder.get_root_folder(), request.user, AppliedControl + ) + class PolicyViewSet(AppliedControlViewSet): model = Policy From 02ce510641f00fa3795b4f4cf84d351b3440ed80 Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Mon, 23 Dec 2024 14:24:08 +0100 Subject: [PATCH 3/4] Impact graph for applied controls --- backend/core/views.py | 83 +++++++++++++++++++ .../components/DataViz/GraphExplorer.svelte | 3 +- .../insights/controls-impact/+page.server.ts | 13 +++ .../insights/controls-impact/+page.svelte | 12 +++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts diff --git a/backend/core/views.py b/backend/core/views.py index 092155b6b..86e4f3fc9 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -1272,6 +1272,89 @@ def impact_graph(self, request): (viewable_controls_ids, _, _) = RoleAssignment.get_accessible_object_ids( Folder.get_root_folder(), request.user, AppliedControl ) + csf_functions_map = dict() + categories = [{"name": "--"}] + for i, option in enumerate(ReferenceControl.CSF_FUNCTION, 1): + csf_functions_map[option[0]] = i + categories.append({"name": option[1]}) + categories.append({"name": "requirements"}) # 7 + categories.append({"name": "scenarios"}) # 9 + categories.append({"name": "audits"}) # 8 + categories.append({"name": "risk assessments"}) + + nodes = list() + links = list() + indexes = dict() + idx_cnt = 0 + for ac in AppliedControl.objects.filter(id__in=viewable_controls_ids): + nodes.append( + { + "name": ac.name, + "value": ac.name, + "category": csf_functions_map.get(ac.csf_function, 0), + } + ) + indexes[ac.id] = idx_cnt + idx_cnt += 1 + # attached requirement_assessments + for req in RequirementAssessment.objects.filter(applied_controls__id=ac.id): + nodes.append( + { + "name": req.requirement.ref_id, + "value": req.requirement.description, + "category": 7, + "symbol": "triangle", + } + ) + indexes[req.id] = ( + idx_cnt # not good - even if the probability of collision is low + ) + idx_cnt += 1 + + audit = req.compliance_assessment + if indexes.get(audit.id) is None: + nodes.append( + { + "name": audit.name, + "value": audit.framework.name, + "category": 9, + "symbol": "rect", + } + ) + indexes[audit.id] = idx_cnt + idx_cnt += 1 + links.append({"source": indexes[audit.id], "target": indexes[req.id]}) + + links.append({"source": indexes[ac.id], "target": indexes[req.id]}) + for sc in RiskScenario.objects.filter(applied_controls__id=ac.id): + nodes.append( + { + "name": sc.ref_id, + "value": sc.name, + "category": 8, + "symbol": "diamond", + } + ) + indexes[sc.id] = idx_cnt + idx_cnt += 1 + + ra = sc.risk_assessment + if indexes.get(ra.id) is None: + nodes.append( + { + "name": ra.name, + "value": ra.name, + "category": 10, + "symbol": "rect", + } + ) + indexes[ra.id] = idx_cnt + idx_cnt += 1 + links.append({"source": indexes[ra.id], "target": indexes[sc.id]}) + + links.append({"source": indexes[ac.id], "target": indexes[sc.id]}) + + return Response({"nodes": nodes, "categories": categories, "links": links}) class PolicyViewSet(AppliedControlViewSet): diff --git a/frontend/src/lib/components/DataViz/GraphExplorer.svelte b/frontend/src/lib/components/DataViz/GraphExplorer.svelte index cfd9d8d59..16139da92 100644 --- a/frontend/src/lib/components/DataViz/GraphExplorer.svelte +++ b/frontend/src/lib/components/DataViz/GraphExplorer.svelte @@ -8,6 +8,7 @@ export let classesContainer = ''; export let title = ''; export let layout = 'force'; + export let initLayout = 'circular'; export let edgeLength = 50; export let name = 'graph'; @@ -81,7 +82,7 @@ gravity: 0.05, layoutAnimation: true, friction: 0.1, - initLayout: 'circular' + initLayout: initLayout }, labelLayout: { hideOverlap: true diff --git a/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts new file mode 100644 index 000000000..ac8154c51 --- /dev/null +++ b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts @@ -0,0 +1,13 @@ + +import { BASE_API_URL } from '$lib/utils/constants'; + +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ params, fetch }) => { + const endpoint = `${BASE_API_URL}/applied-controls/impact_graph/`; + + const res = await fetch(endpoint); + const data = await res.json(); + + return { data }; +}; diff --git a/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte index e69de29bb..f5cd108ef 100644 --- a/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.svelte @@ -0,0 +1,12 @@ + + +
+
+ +
+
From 18b082ffb7bab4e068b32118dc9cc9b156afba1f Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Mon, 23 Dec 2024 14:26:51 +0100 Subject: [PATCH 4/4] formatter --- .../(internal)/insights/controls-impact/+page.server.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts index ac8154c51..8aa903d5f 100644 --- a/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/insights/controls-impact/+page.server.ts @@ -1,13 +1,12 @@ - import { BASE_API_URL } from '$lib/utils/constants'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ params, fetch }) => { - const endpoint = `${BASE_API_URL}/applied-controls/impact_graph/`; + const endpoint = `${BASE_API_URL}/applied-controls/impact_graph/`; - const res = await fetch(endpoint); - const data = await res.json(); + const res = await fetch(endpoint); + const data = await res.json(); - return { data }; + return { data }; };