Skip to content

Commit

Permalink
Added option to merge and unmerge manual and automatic results (#53)
Browse files Browse the repository at this point in the history
* Added option to merge and unmerge manual and automatic results

* Correctly filtering statuses on click

* Make ids and names unique per ToE
  • Loading branch information
oxisto authored Oct 24, 2023
1 parent ab5f30b commit 450e090
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 59 deletions.
144 changes: 102 additions & 42 deletions src/lib/components/ComplianceChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,26 @@
import { goto } from '$app/navigation';
import type { ComplianceStatus } from '$lib/api/evaluation';
import type { TargetOfEvaluation } from '$lib/api/orchestrator';
import { Chart, type ChartConfiguration } from 'chart.js/auto';
import { Chart, type ChartConfiguration, type ChartData } from 'chart.js/auto';
import { onMount } from 'svelte';
let canvas: HTMLCanvasElement;
let chart: Chart<'doughnut', { status: string[]; num: number }[]>;
export let compliance: Map<string, ComplianceStatus>;
export let toe: TargetOfEvaluation;
const data = {
labels: [
'Non Compliant',
'Manually set to Non Compliant',
'Compliant',
'Manually set to Compliant',
'Waiting for Data'
],
datasets: [
{
label: toe.catalogId,
data: [
Array.from(compliance.values()).filter(
(value) => value == 'EVALUATION_STATUS_NOT_COMPLIANT'
).length,
Array.from(compliance.values()).filter(
(value) => value == 'EVALUATION_STATUS_NOT_COMPLIANT_MANUALLY'
).length,
Array.from(compliance.values()).filter((value) => value == 'EVALUATION_STATUS_COMPLIANT')
.length,
Array.from(compliance.values()).filter(
(value) => value == 'EVALUATION_STATUS_COMPLIANT_MANUALLY'
).length,
Array.from(compliance.values()).filter((value) => value == 'EVALUATION_STATUS_PENDING')
.length
],
backgroundColor: ['#991b1b', '#991b1b', '#166534', '#166534', '#d4d4d4'],
hoverOffset: 4
}
]
};
let merge = true;
const config: ChartConfiguration = {
$: data = buildData(merge);
$: updateChart(data);
let config: ChartConfiguration<'doughnut', { status: string[]; num: number }[]> = {
type: 'doughnut',
data: data,
data: buildData(merge),
options: {
parsing: {
key: 'num'
},
animation: false,
plugins: {
tooltip: {
Expand All @@ -71,26 +48,109 @@
};
onMount(() => {
let chart = new Chart(canvas, config);
chart = new Chart(canvas, config);
canvas.onclick = (evt) => {
const res = chart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, true);
console.log(res);
if (res.length === 0) {
return;
} else {
goto(
`/cloud/${toe.cloudServiceId}/compliance/${toe.catalogId}/?status=${data.labels[
res[0].index
].replace(/\s/g, '')}`
);
const data = chart.data.datasets[0].data[res[0].index];
const params = new URLSearchParams();
for (const s of data.status) {
params.append('status', s);
}
goto(`/cloud/${toe.cloudServiceId}/compliance/${toe.catalogId}?${params.toString()}`);
}
};
});
function updateChart(data: ChartData<'doughnut', { status: string[]; num: number }[]>) {
if (chart) {
chart.data = data;
chart.update();
}
}
function buildData(merge: boolean): ChartData<'doughnut', { status: string[]; num: number }[]> {
if (merge) {
return {
labels: ['Non Compliant', 'Compliant', 'Waiting for Data'],
datasets: [
{
label: toe.catalogId,
data: [
filter([
'EVALUATION_STATUS_NOT_COMPLIANT',
'EVALUATION_STATUS_NOT_COMPLIANT_MANUALLY'
]),
filter(['EVALUATION_STATUS_COMPLIANT', 'EVALUATION_STATUS_COMPLIANT_MANUALLY']),
filter(['EVALUATION_STATUS_PENDING'])
],
backgroundColor: ['#991b1b', '#166534', '#d4d4d4'],
hoverOffset: 4
}
]
};
} else {
return {
labels: [
'Non Compliant',
'Manually set to Non Compliant',
'Compliant',
'Manually set to Compliant',
'Waiting for Data'
],
datasets: [
{
label: toe.catalogId,
data: [
filter(['EVALUATION_STATUS_NOT_COMPLIANT']),
filter(['EVALUATION_STATUS_NOT_COMPLIANT_MANUALLY']),
filter(['EVALUATION_STATUS_COMPLIANT']),
filter(['EVALUATION_STATUS_COMPLIANT_MANUALLY']),
filter(['EVALUATION_STATUS_PENDING'])
],
backgroundColor: ['#991b1b', 'rgb(185 28 28)', '#166534', 'rgb(21 128 61)', '#d4d4d4'],
hoverOffset: 4
}
]
};
}
}
function filter(status: string[]) {
return {
status: status,
num: Array.from(compliance.values()).filter((value) => status.includes(value)).length
};
}
</script>

<div class="py-3">
<div class="relative flex items-start mb-2">
<div class="flex h-6 items-center">
<input
id="merge-{toe.catalogId}"
aria-describedby="merge-description-{toe.catalogId}"
name="merge-{toe.catalogId}"
type="checkbox"
class="h-4 w-4 rounded border-gray-300 text-clouditor focus:ring-clouditor"
bind:checked={merge}
/>
</div>
<div class="ml-3 text-sm leading-6">
<label for="merge-{toe.catalogId}" class="font-medium text-gray-900"
>Merge manual results</label
>
<span id="merge-description-{toe.catalogId}" class="text-gray-500">
<span class="sr-only">Merge manual results </span>
with automatic results.
</span>
</div>
</div>
<canvas id="chart" bind:this={canvas} class="h-72 w-72 ml-auto mr-auto" />
</div>
8 changes: 5 additions & 3 deletions src/lib/components/ComplianceStatusSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
const publishingOptions = [
{
name: 'Compliant',
description: 'This job posting can be viewed by anyone who has the link.',
description:
'This evaluation result will be considered as compliant. It will be marked as manually evaluated.',
bgColor: 'bg-green-800',
hoverColor: 'hover:bg-green-900 focus:ring-green-800',
divideColor: 'divide-green-900',
status: 'EVALUATION_STATUS_COMPLIANT_MANUALLY'
},
{
name: 'Non-Compliant',
description: 'This job posting will no longer be publicly accessible.',
description:
'This evaluation result will be considered as compliant. It will be marked as manually evaluated.',
bgColor: 'bg-red-800',
hoverColor: 'hover:bg-red-900 focus:ring-red-900',
divideColor: 'divide-red-900',
Expand All @@ -36,7 +38,7 @@
status == 'EVALUATION_STATUS_COMPLIANT_MANUALLY' ? publishingOptions[0] : publishingOptions[1];
</script>

<Listbox bind:value={status}>
<Listbox bind:value={status} class="mt-2">
<ListboxLabel class="sr-only">Change published status</ListboxLabel>
<div class="relative">
<div class="inline-flex divide-x {selected.divideColor} rounded-md shadow-sm">
Expand Down
16 changes: 3 additions & 13 deletions src/routes/(app)/cloud/[id]/compliance/[catalogId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,18 @@
$: tree = buildTree(data.evaluations, data.filterStatus);
const queryParamToStatus: Map<string, string> = new Map([
['Compliant', 'EVALUATION_STATUS_COMPLIANT'],
['NonCompliant', 'EVALUATION_STATUS_NOT_COMPLIANT'],
['ManuallysettoCompliant', 'EVALUATION_STATUS_COMPLIANT_MANUALLY'],
['WaitingforData', 'EVALUATION_STATUS_PENDING']
]);
/**
* This function builds a tree-like structure out of the evaluation results,
* with the first level comprising of the top level controls. The second level
* consists of their sub controls.
*
* @param results
*/
function buildTree(
results: EvaluationResult[],
status: string | null
): Map<string, TreeItemData> {
function buildTree(results: EvaluationResult[], status: string[]): Map<string, TreeItemData> {
const tree = new Map<string, TreeItemData>();
for (const result of results) {
if (status !== null && result.status !== queryParamToStatus.get(status)) {
if (status !== null && !status.includes(result.status)) {
continue;
}
Expand All @@ -56,7 +46,7 @@
continue;
}
if (status !== null && result.status == queryParamToStatus.get(status)) {
if (status !== null && status.includes(result.status)) {
parent.children.push(result);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const load = (async ({ fetch, params, url }) => {
const controls = new Map(
(await listControls(params.catalogId, undefined, fetch)).map((c) => [c.id, c])
);
const filterStatus = url.searchParams.get('status');
const filterStatus = url.searchParams.getAll('status');

return {
evaluations,
Expand Down

0 comments on commit 450e090

Please sign in to comment.