Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental Insights #887

Merged
merged 20 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions backend/core/migrations/0030_appliedcontrol_start_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 5.1 on 2024-10-04 08:48

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("core", "0029_alter_appliedcontrol_link_alter_evidence_link"),
]

operations = [
migrations.AddField(
model_name="appliedcontrol",
name="start_date",
field=models.DateField(
blank=True,
help_text="Start date (useful for timeline)",
null=True,
verbose_name="Start date",
),
),
]
7 changes: 7 additions & 0 deletions backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _
from structlog import get_logger
from django.utils.timezone import now

from iam.models import Folder, FolderMixin, PublishInRootFolderMixin
from library.helpers import (
Expand Down Expand Up @@ -1309,6 +1310,12 @@ class Status(models.TextChoices):
verbose_name=_("Owner"),
related_name="applied_controls",
)
start_date = models.DateField(
blank=True,
null=True,
help_text=_("Start date (useful for timeline)"),
verbose_name=_("Start date"),
)
eta = models.DateField(
blank=True,
null=True,
Expand Down
131 changes: 131 additions & 0 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import uuid
import zipfile
from datetime import date, datetime, timedelta
import time
import pytz
from typing import Any, Tuple
from uuid import UUID
from itertools import cycle

import django_filters as df
from ciso_assistant.settings import BUILD, VERSION, EMAIL_HOST, EMAIL_HOST_RESCUE
Expand Down Expand Up @@ -637,6 +640,18 @@ def duplicate(self, request, pk):
return Response({"results": "risk assessment duplicated"})


def convert_date_to_timestamp(date):
"""
Converts a date object (datetime.date) to a Linux timestamp.
It creates a datetime object for the date at midnight and makes it timezone-aware.
"""
if date:
date_as_datetime = datetime.combine(date, datetime.min.time())
aware_datetime = pytz.UTC.localize(date_as_datetime)
return int(time.mktime(aware_datetime.timetuple())) * 1000
return None


class AppliedControlViewSet(BaseModelViewSet):
"""
API endpoint that allows applied controls to be viewed or edited.
Expand Down Expand Up @@ -783,6 +798,122 @@ def export_csv(self, request):
writer.writerow(row)
return response

@action(detail=False, methods=["get"])
def get_controls_info(self, request):
nodes = list()
links = list()
for ac in AppliedControl.objects.all():
related_items_count = 0
for ca in ComplianceAssessment.objects.filter(
requirement_assessments__applied_controls=ac
).distinct():
audit_coverage = (
RequirementAssessment.objects.filter(compliance_assessment=ca)
.filter(applied_controls=ac)
.count()
)
related_items_count += audit_coverage
links.append(
{
"source": ca.id,
"target": ac.id,
"coverage": audit_coverage,
}
)
for ra in RiskAssessment.objects.filter(
risk_scenarios__applied_controls=ac
).distinct():
risk_coverage = (
RiskScenario.objects.filter(risk_assessment=ra)
.filter(applied_controls=ac)
.count()
)
related_items_count += risk_coverage
links.append(
{
"source": ra.id,
"target": ac.id,
"coverage": risk_coverage,
}
)
nodes.append(
{
"id": ac.id,
"label": ac.name,
"shape": "hexagon",
"counter": related_items_count,
"color": "#47e845",
}
)
for audit in ComplianceAssessment.objects.all():
nodes.append(
{
"id": audit.id,
"label": audit.name,
"shape": "circle",
"color": "#5D4595",
}
)
for ra in RiskAssessment.objects.all():
nodes.append(
{
"id": ra.id,
"label": ra.name,
"shape": "square",
"color": "#E6499F",
}
)
return Response(
{
"nodes": nodes,
"links": links,
}
)

@action(detail=False, methods=["get"])
def get_timeline_info(self, request):
entries = []
COLORS_PALETTE = [
"#F72585",
"#7209B7",
"#3A0CA3",
"#4361EE",
"#4CC9F0",
"#A698DC",
]
colorMap = {}
(viewable_controls_ids, _, _) = RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), request.user, AppliedControl
)

applied_controls = AppliedControl.objects.filter(
id__in=viewable_controls_ids
).select_related("folder")

for ac in applied_controls:
if ac.eta:
endDate = convert_date_to_timestamp(ac.eta)
startDate = (
convert_date_to_timestamp(ac.start_date)
if ac.start_date
else endDate
)
entries.append(
{
"startDate": startDate,
"endDate": endDate,
"name": ac.name,
"description": ac.description
if ac.description
else "(no description)",
"domain": ac.folder.name,
}
)
color_cycle = cycle(COLORS_PALETTE)
for domain in Folder.objects.all():
colorMap[domain.name] = next(color_cycle)
return Response({"entries": entries, "colorMap": colorMap})


class PolicyViewSet(AppliedControlViewSet):
model = Policy
Expand Down
13 changes: 12 additions & 1 deletion backend/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ drf-spectacular = "0.27.2"
django-rest-knox = "5.0.1"
django-allauth = { version = "64.2.1", extras = ["socialaccount", "saml"] }
python-magic = "0.4.27"
pytz = "2024.2"

[tool.poetry.group.dev.dependencies]
pytest-django = "4.8.0"
Expand Down
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ pre-commit==3.8.0
django-allauth[saml]==64.2.1
django-allauth==64.2.1
python-magic==0.4.27
pytz==2024.2
5 changes: 5 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,11 @@
"theFollowingControlsWillBeAddedColon": "The following controls will be added:",
"ShowAllNodesMessage": "Show all",
"ShowOnlyAssessable": "Only assessable",
"experimental": "Experimental",
"timeline": "Timeline",
"graph": "Graph",
"startDate": "Start date",
"startDateHelpText": "Start date (useful for timeline)",
"backupLoadingError": "An error occurred while loading the backup.",
"backupGreaterVersionError": "Can't load the backup, the version of the backup is higher than the current version of your application.",
"backupLowerVersionError": "An error occurred, the backup version may be too old, if so it must be updated before retrying.",
Expand Down
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
"@fortawesome/fontawesome-free": "^6.6.0",
"@inlang/paraglide-js-adapter-vite": "^1.2.40",
"@inlang/paraglide-sveltekit": "0.11.0",
"@unovis/svelte": "1.4.3-beta.0",
"@unovis/ts": "1.4.3-beta.0",
"dotenv": "^16.4.5",
"echarts": "^5.5.1",
"svelte-multiselect": "^10.2.0",
Expand Down
7 changes: 4 additions & 3 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const config: PlaywrightTestConfig = {
? 'echo "The docker compose frontend server didn\'t start correctly"'
: 'pnpm run preview',
port: process.env.COMPOSE_TEST ? 3000 : 4173,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI
},
testDir: 'tests',
Expand All @@ -15,10 +16,10 @@ const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 1,
workers: process.env.CI ? 1 : 1,
globalTimeout: 60 * 60 * 1000,
timeout: 50 * 1000,
globalTimeout: 120 * 60 * 1000,
timeout: 100 * 1000,
expect: {
timeout: 10 * 1000
timeout: 20 * 1000
},
reporter: [
[process.env.CI ? 'github' : 'list'],
Expand Down
Loading
Loading