Skip to content

Commit

Permalink
Merge pull request #757 from intuitem/dashboard_improvement
Browse files Browse the repository at this point in the history
Experimental version of a new homepage for Analytics
  • Loading branch information
ab-smith authored Aug 30, 2024
2 parents f43d6df + 3f0770f commit a535419
Show file tree
Hide file tree
Showing 11 changed files with 702 additions and 17 deletions.
142 changes: 136 additions & 6 deletions backend/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,11 @@ def risks_per_project_groups(user: User):


def get_counters(user: User):
print()
controls_count = len(
RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), user, AppliedControl
)[0]
)
return {
"domains": len(
RoleAssignment.get_accessible_object_ids(
Expand All @@ -758,11 +762,7 @@ def get_counters(user: User):
Folder.get_root_folder(), user, Project
)[0]
),
"applied_controls": len(
RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), user, AppliedControl
)[0]
),
"applied_controls": controls_count,
"risk_assessments": len(
RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), user, RiskAssessment
Expand All @@ -781,6 +781,136 @@ def get_counters(user: User):
}


def build_audits_tree_metrics(user):
(object_ids, _, _) = RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), user, Folder
)
viewable_domains = Folder.objects.filter(id__in=object_ids)

tree = list()
domain_prj_children = list()
for domain in viewable_domains.exclude(name="Global"):
block_domain = {"name": domain.name, "children": []}
domain_prj_children = []
for project in Project.objects.filter(folder=domain):
block_prj = {"name": project.name, "domain": domain.name, "children": []}
children = []
for audit in ComplianceAssessment.objects.filter(project=project):
cnt_reqs = RequirementAssessment.objects.filter(
compliance_assessment=audit
).count()
cnt_res = {}
for result in RequirementAssessment.Result.choices:
cnt_res[result[0]] = (
RequirementAssessment.objects.filter(
compliance_assessment=audit
)
.filter(result=result[0])
.count()
)
print(cnt_res)
blk_audit = {
"name": audit.name,
"children": [
{
"name": "compliant",
"value": cnt_res["compliant"],
},
{
"name": "not assessed",
"value": cnt_res["not_assessed"],
},
{
"name": "Not Applicable",
"value": cnt_res["not_applicable"],
},
{
"name": "partial",
"value": cnt_res["partially_compliant"],
},
{
"name": "Non compliant",
"value": cnt_res["non_compliant"],
},
],
}
children.append(blk_audit)
block_prj["children"] = children
domain_prj_children.append(block_prj)
block_domain["children"] = domain_prj_children
tree.append(block_domain)
return tree


def csf_functions(user):
(object_ids, _, _) = RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), user, AppliedControl
)
viewable_controls = AppliedControl.objects.filter(id__in=object_ids)
cnt = dict()
for choice in ReferenceControl.CSF_FUNCTION:
cnt[choice[0]] = viewable_controls.filter(csf_function=choice[0]).count()
undefined = viewable_controls.filter(csf_function__isnull=True).count()
data = [
{"name": "Govern", "value": cnt["govern"]},
{"name": "Identify", "value": cnt["identify"]},
{"name": "Protect", "value": cnt["protect"]},
{"name": "Detect", "value": cnt["detect"]},
{"name": "Respond", "value": cnt["respond"]},
{"name": "Recover", "value": cnt["recover"]},
]
if undefined > 0:
data.append({"name": "(undefined)", "value": undefined})

return data


def get_metrics(user: User):
def viewable_items(model):
(object_ids, _, _) = RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), user, model
)
return model.objects.filter(id__in=object_ids)

viewable_controls = viewable_items(AppliedControl)
controls_count = viewable_controls.count()
data = {
"controls": {
"total": controls_count,
"to_do": viewable_controls.filter(status="to_do").count(),
"in_progress": viewable_controls.filter(status="in_progress").count(),
"on_hold": viewable_controls.filter(status="on_hold").count(),
"active": viewable_controls.filter(status="active").count(),
"deprecated": viewable_controls.filter(status="deprecated").count(),
},
"risk": {
"assessments": viewable_items(RiskAssessment).count(),
"scenarios": viewable_items(RiskScenario).count(),
"threats": viewable_items(Threat)
.filter(risk_scenarios__isnull=False)
.distinct()
.count(),
"acceptances": viewable_items(RiskAcceptance).count(),
},
"compliance": {
"audits": viewable_items(ComplianceAssessment).count(),
"active_audits": viewable_items(ComplianceAssessment)
.filter(status__in=["in_progress", "in_review", "done"])
.count(),
"evidences": viewable_items(Evidence).count(),
"compliant_items": viewable_items(RequirementAssessment)
.filter(result="compliant")
.count(),
"non_compliant_items": viewable_items(RequirementAssessment)
.filter(result="non_compliant")
.count(),
},
"audits_tree": build_audits_tree_metrics(user),
"csf_functions": csf_functions(user),
}
return data


def risk_status(user: User, risk_assessment_list):
risk_color_map = get_risk_color_map(user)
names = list()
Expand Down
1 change: 1 addition & 0 deletions backend/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
path("license/", license, name="license"),
path("evidences/<uuid:pk>/upload/", UploadAttachmentView.as_view(), name="upload"),
path("get_counters/", get_counters_view, name="get_counters_view"),
path("get_metrics/", get_metrics_view, name="get_metrics_view"),
path("agg_data/", get_agg_data, name="get_agg_data"),
path("composer_data/", get_composer_data, name="get_composer_data"),
path("i18n/", include("django.conf.urls.i18n")),
Expand Down
9 changes: 9 additions & 0 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,15 @@ def get_counters_view(request):
return Response({"results": get_counters(request.user)})


@api_view(["GET"])
@permission_classes([permissions.IsAuthenticated])
def get_metrics_view(request):
"""
API endpoint that returns the counters
"""
return Response({"results": get_metrics(request.user)})


# TODO: Add all the proper docstrings for the following list of functions


Expand Down
62 changes: 62 additions & 0 deletions frontend/src/lib/components/Chart/HalfDonutChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script lang="ts">
import { onMount } from 'svelte';
// export let name: string;
export let s_label = '';
export let width = 'w-auto';
export let height = 'h-full';
export let classesContainer = '';
export let title = '';
export let name: string;
interface riskChartData {
name: string;
value: number;
color: string;
}
export let values: riskChartData[]; // Set the types for these variables later on
export let colors: string[] = [];
const chart_id = `${name}_div`;
onMount(async () => {
const echarts = await import('echarts');
let chart = echarts.init(document.getElementById(chart_id), null, { renderer: 'svg' });
// specify chart configuration item and data
var option = {
title: {
subtext: title
},
tooltip: {
trigger: 'item'
},
series: [
{
name: 'risk level',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '70%'],
// adjust the start and end angle
startAngle: 180,
endAngle: 360,
minShowLabelAngle: 1,
data: values,
color: colors
}
]
};
// console.debug(option);
// use configuration item and data specified to show chart
chart.setOption(option);
window.addEventListener('resize', function () {
chart.resize();
});
});
</script>

<div id={chart_id} class="{width} {height} {classesContainer}" />
49 changes: 49 additions & 0 deletions frontend/src/lib/components/Chart/NightingaleChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import { onMount } from 'svelte';
export let width = 'w-auto';
export let height = 'h-full';
export let classesContainer = '';
export let title = '';
export let name = '';
interface ndChartData {
name: string;
value: number;
}
export let values: ndChartData[]; // Set the types for these variables later on
const chart_id = `${name}_div`;
onMount(async () => {
const echarts = await import('echarts');
let chart = echarts.init(document.getElementById(chart_id), null, { renderer: 'svg' });
// specify chart configuration item and data
var option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
series: [
{
name: 'Control Function',
type: 'pie',
radius: [20, 100],
roseType: 'area',
itemStyle: {
borderRadius: 5
},
data: values
}
]
}; // console.debug(option);
// use configuration item and data specified to show chart
chart.setOption(option);
window.addEventListener('resize', function () {
chart.resize();
});
});
</script>

<div id={chart_id} class="{width} {height} {classesContainer}" />
74 changes: 74 additions & 0 deletions frontend/src/lib/components/Chart/SankeyChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script lang="ts">
import { onMount } from 'svelte';
export let width = 'w-auto';
export let height = 'h-full';
export let classesContainer = '';
export let title = '';
export let name = '';
interface sankeyData {
source: string;
target: string;
value: number;
}
export let values: sankeyData[]; // Set the types for these variables later on
const chart_id = `${name}_div`;
onMount(async () => {
const echarts = await import('echarts');
let chart = echarts.init(document.getElementById(chart_id), null, { renderer: 'svg' });
// specify chart configuration item and data
var option = {
title: {
subtext: title
},
series: {
type: 'sankey',
layout: 'none',
orient: 'horizontal',
emphasis: {
focus: 'adjacency'
},
data: [
{
name: 'Controls function'
},
{
name: 'Govern'
},
{
name: 'Identify'
},
{
name: 'Protect'
},
{
name: 'Detect'
},
{
name: 'Respond'
},
{
name: 'Recover'
},
{
name: '--'
}
],
links: values
}
};
// console.debug(option);
// use configuration item and data specified to show chart
chart.setOption(option);
window.addEventListener('resize', function () {
chart.resize();
});
});
</script>

<div id={chart_id} class="{width} {height} {classesContainer}" />
Loading

0 comments on commit a535419

Please sign in to comment.