Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,34 @@

{% block content %}
<div class="d-flex align-items-baseline gap-3 mb-3">
<h1 class="h3 mb-0">{% trans "Compliance Control Center" %}</h1>
<span class="text-body-secondary">{{ total_products }} {% trans "active product" %}{{ total_products|pluralize }}</span>
<h1 class="h3 mb-0">
{% trans "Compliance Control Center" %}
</h1>
<span class="text-body-secondary">
{{ total_products }} {% trans "active product" %}{{ total_products|pluralize }}
</span>
<div class="dropdown ms-auto">
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-download me-1"></i>{% trans "Export" %}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="?export=csv">
<i class="fas fa-download me-1"></i>{% trans "CSV" %}
</a>
</li>
<li>
<a class="dropdown-item" href="?export=xlsx">
<i class="fas fa-download me-1"></i>{% trans "XLSX" %}
</a>
</li>
<li>
<a class="dropdown-item" href="?export=json">
<i class="fas fa-download me-1"></i>{% trans "JSON" %}
</a>
</li>
</ul>
</div>
</div>

<div class="row g-3 mb-3">
Expand Down
89 changes: 89 additions & 0 deletions product_portfolio/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from openpyxl.styles import Font
from openpyxl.styles import NamedStyle
from openpyxl.styles import Side
from openpyxl.utils import get_column_letter

from component_catalog.forms import ComponentAjaxForm
from component_catalog.license_expression_dje import build_licensing
Expand Down Expand Up @@ -2828,6 +2829,21 @@ class ComplianceDashboardView(LoginRequiredMixin, DataspacedFilterView):
model = Product
filterset_class = ProductFilterSet
paginate_by = settings.DEJACODE_PAGINATE_BY.get("compliance", 50)
export_filename = "compliance_dashboard"
export_fields = {
"name": "Product",
"version": "Version",
"package_count": "Packages",
"license_error_count": "License errors",
"license_warning_count": "License warnings",
"max_risk_level": "Max risk level",
"risk_threshold": "Risk threshold",
"critical_count": "Critical",
"high_count": "High",
"medium_count": "Medium",
"low_count": "Low",
"vulnerability_count": "Total vulnerabilities",
}

def get_queryset(self):
base_qs = Product.objects.get_queryset(
Expand Down Expand Up @@ -2905,3 +2921,76 @@ def get_context_data(self, **kwargs):
)

return context

def get(self, request, *args, **kwargs):
export_format = request.GET.get("export")
if export_format in ("csv", "xlsx", "json"):
return self.export(export_format)
return super().get(request, *args, **kwargs)

def export(self, export_format):
if export_format == "csv":
return self.export_csv()
if export_format == "xlsx":
return self.export_xlsx()
return self.export_json()

def get_export_headers(self):
return list(self.export_fields.values())

def get_export_fields(self):
return list(self.export_fields.keys())

def get_export_rows(self):
return self.get_queryset().values_list(*self.get_export_fields())

def export_csv(self):
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="{self.export_filename}.csv"'

writer = csv.writer(response)
writer.writerow(self.get_export_headers())
writer.writerows(self.get_export_rows())
return response

def export_xlsx(self):
workbook = Workbook()
worksheet = workbook.active
worksheet.title = "Compliance Dashboard"

headers = self.get_export_headers()
worksheet.append(headers)

# Header styling
header_style = NamedStyle(name="header")
header_style.font = Font(bold=True)
header_style.border = Border(bottom=Side(border_style="thin"))
header_style.alignment = Alignment(horizontal="center", vertical="center")
for cell in worksheet[1]:
cell.style = header_style

worksheet.freeze_panes = "A2"

for row in self.get_export_rows():
worksheet.append(row)

# Auto-width columns
for col_index, header in enumerate(headers, 1):
column_letter = get_column_letter(col_index)
worksheet.column_dimensions[column_letter].width = max(len(header) + 4, 12)

response = HttpResponse(
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
response["Content-Disposition"] = f'attachment; filename="{self.export_filename}.xlsx"'
workbook.save(response)
return response

def export_json(self):
data = list(self.get_queryset().values(*self.get_export_fields()))
response = HttpResponse(
json.dumps(data, indent=2, default=str),
content_type="application/json",
)
response["Content-Disposition"] = f'attachment; filename="{self.export_filename}.json"'
return response
Loading