Skip to content

Commit

Permalink
Improve typing
Browse files Browse the repository at this point in the history
  • Loading branch information
nas-tabchiche committed Apr 26, 2024
1 parent 0433be5 commit cdd344d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 31 deletions.
2 changes: 1 addition & 1 deletion frontend/src/lib/components/Chart/DonutChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { languageTag } from '$paraglide/runtime';
// export let name: string;
export let s_label: string;
export let s_label = '';
export let width = 'w-auto';
export let height = 'h-full';
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/lib/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ export interface RiskMatrix {
json_definition: string; // stringified
}

export interface Project {
id: string;
folder: Record<string, any>;
lc_status: string;
created_at: string;
updated_at: string;
is_published: boolean;
name: string;
description?: string;
internal_reference?: string;
compliance_assessments: Record<string, any>[];
}

export type RiskScenario = z.infer<typeof RiskScenarioSchema>;

interface LibraryObject {
Expand Down
80 changes: 59 additions & 21 deletions frontend/src/routes/(app)/analytics/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,35 @@ import { composerSchema } from '$lib/utils/schemas';
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import type { PageServerLoad } from './$types';
import type { Project } from '$lib/utils/types';

const REQUIREMENT_ASSESSMENT_STATUS = [
'compliant',
'partially_compliant',
'in_progress',
'non_compliant',
'not_applicable',
'to_do'
] as const;

interface DonutItem {
name: string;
localName?: string;
value: number;
itemStyle: Record<string, unknown>;
}

interface RequirementAssessmentDonutItem extends Omit<DonutItem, 'name'> {
name: (typeof REQUIREMENT_ASSESSMENT_STATUS)[number];
percentage: string;
}

interface ProjectAnalytics extends Project {
overallCompliance: {
values: RequirementAssessmentDonutItem[];
total: number;
};
}

export const load: PageServerLoad = async ({ locals, fetch }) => {
const req_applied_control_status = await fetch(`${BASE_API_URL}/applied-controls/per_status/`);
Expand All @@ -26,17 +55,21 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
const req_get_counters = await fetch(`${BASE_API_URL}/get_counters/`);
const counters = await req_get_counters.json();

const usedRiskMatrices = await fetch(`${BASE_API_URL}/risk-matrices/used/`)
.then((res) => res.json())
.then((res) => res.results);
const usedFrameworks = await fetch(`${BASE_API_URL}/frameworks/used/`)
.then((res) => res.json())
.then((res) => res.results);

const usedRiskMatrices: { id: string; name: string; risk_assessments_count: number }[] =
await fetch(`${BASE_API_URL}/risk-matrices/used/`)
.then((res) => res.json())
.then((res) => res.results);
const usedFrameworks: { id: string; name: string; compliance_assessments_count: number }[] =
await fetch(`${BASE_API_URL}/frameworks/used/`)
.then((res) => res.json())
.then((res) => res.results);
const req_get_risks_count_per_level = await fetch(
`${BASE_API_URL}/risk-scenarios/count_per_level/`
);
const risks_count_per_level = await req_get_risks_count_per_level.json();
const risks_count_per_level: {
current: Record<string, any>[];
residual: Record<string, any>[];
} = await req_get_risks_count_per_level.json().then((res) => res.results);

const req_get_measures_to_review = await fetch(`${BASE_API_URL}/applied-controls/to_review/`);
const measures_to_review = await req_get_measures_to_review.json();
Expand All @@ -47,19 +80,19 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
const req_risk_assessments = await fetch(`${BASE_API_URL}/risk-assessments/`);
const risk_assessments = await req_risk_assessments.json();

const projects = await fetch(`${BASE_API_URL}/projects/`)
const projects: ProjectAnalytics[] = await fetch(`${BASE_API_URL}/projects/`)
.then((res) => res.json())
.then((projects) => {
if (projects && Array.isArray(projects.results)) {
// Process each project to fetch its compliance assessments
const projectPromises = projects.results.map((project) => {
const projectPromises = projects.results.map(async (project: Record<string, any>) => {
return fetch(`${BASE_API_URL}/compliance-assessments/?project=${project.id}`)
.then((res) => res.json())
.then((compliance_assessments) => {
.then(async (compliance_assessments) => {
if (compliance_assessments && Array.isArray(compliance_assessments.results)) {
// Fetch donut data for each compliance assessment
const donutDataPromises = compliance_assessments.results.map(
(compliance_assessment) => {
async (compliance_assessment: Record<string, any>) => {
return fetch(
`${BASE_API_URL}/compliance-assessments/${compliance_assessment.id}/donut_data/`
)
Expand Down Expand Up @@ -88,24 +121,29 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
throw new Error('Projects results not found or not an array');
}
})
.catch((error) => console.error('Error:', error));
.catch((error) => {
console.error('Failed to load projects:', error);
return []; // Ensure always returning an array of Record<string, any>
});

if (projects) {
projects.forEach((project) => {
// Initialize an object to hold the aggregated donut data
const aggregatedDonutData = {
const aggregatedDonutData: {
values: RequirementAssessmentDonutItem[];
total: number;
} = {
values: [],
total: 0
};

// Iterate through each compliance assessment of the project
project.compliance_assessments.forEach((compliance_assessment) => {
project.compliance_assessments.forEach((compliance_assessment: Record<string, any>) => {
// Process the donut data of each assessment
compliance_assessment.donut.values.forEach((donutItem) => {
compliance_assessment.donut.values.forEach((donutItem: RequirementAssessmentDonutItem) => {
// Find the corresponding item in the aggregated data
const aggregatedItem = aggregatedDonutData.values.find(
(item) => item.name === donutItem.name
);
const aggregatedItem: RequirementAssessmentDonutItem | undefined =
aggregatedDonutData.values.find((item) => item.name === donutItem.name);

if (aggregatedItem) {
// If the item already exists, increment its value
Expand All @@ -123,7 +161,7 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
// Calculate and store the percentage for each item
aggregatedDonutData.values = aggregatedDonutData.values.map((item) => ({
...item,
percentage: totalValue > 0 ? parseFloat((item.value / totalValue) * 100).toFixed(1) : 0
percentage: totalValue > 0 ? ((item.value / totalValue) * 100).toFixed(1) : '0'
}));

// Assign the aggregated donut data to the project
Expand All @@ -140,7 +178,7 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
riskAssessmentsPerStatus,
complianceAssessmentsPerStatus,
riskScenariosPerStatus,
risks_level: risks_count_per_level.results,
risks_count_per_level,
measures_to_review: measures_to_review.results,
acceptances_to_review: acceptances_to_review.results,
risk_assessments: risk_assessments.results,
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/routes/(app)/analytics/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import { Tab, TabGroup, tableSourceMapper } from '@skeletonlabs/skeleton';
import ComposerSelect from './ComposerSelect.svelte';
import CounterCard from './CounterCard.svelte';
import type { PageData } from './$types';
interface Counters {
domains: number;
Expand All @@ -22,12 +23,11 @@
policies: number;
}
export let data;
export let data: PageData;
let counters: Counters = data.get_counters;
const counters: Counters = data.get_counters;
let risk_level = data.risks_level;
let risk_assessments = data.risk_assessments;
const risk_assessments = data.risk_assessments;
const cur_rsk_label = m.currentRisk();
const rsd_rsk_label = m.residualRisk();
Expand Down Expand Up @@ -249,17 +249,17 @@

<DonutChart
s_label={cur_rsk_label}
values={risk_level.current}
colors={risk_level.current.map((object) => object.color)}
values={data.risks_count_per_level.current}
colors={data.risks_count_per_level.current.map((object) => object.color)}
/>
</div>
<div class="h-96 flex-1">
<span class="text-sm font-semibold">{m.residualRiskLevelPerScenario()}</span>

<DonutChart
s_label={rsd_rsk_label}
values={risk_level.residual}
colors={risk_level.residual.map((object) => object.color)}
values={data.risks_count_per_level.residual}
colors={data.risks_count_per_level.residual.map((object) => object.color)}
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/routes/(app)/analytics/ComposerSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import * as m from '$paraglide/messages';
import { zod } from 'sveltekit-superforms/adapters';
export let composerForm: SuperValidated<AnyZodObject>;
export let composerForm: SuperValidated<Record<string, any>>;
let options: { label: string; value: string }[];
Expand Down

0 comments on commit cdd344d

Please sign in to comment.