Skip to content

Commit bce3f73

Browse files
committed
display policy violation in views
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 403838b commit bce3f73

7 files changed

Lines changed: 115 additions & 7 deletions

File tree

policy/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from dje.models import DataspacedModel
2121
from dje.models import DataspacedQuerySet
2222
from dje.models import colored_icon_mixin_factory
23+
from policy.rules import RULE_REGISTRY
2324

2425
ColoredIconMixin = colored_icon_mixin_factory(
2526
verbose_name="usage policy",
@@ -286,6 +287,16 @@ class Meta:
286287
def __str__(self):
287288
return self.name
288289

290+
@property
291+
def rule_label(self):
292+
handler = RULE_REGISTRY.get(self.rule_type)
293+
return handler.label if handler else self.rule_type
294+
295+
@property
296+
def rule_description(self):
297+
handler = RULE_REGISTRY.get(self.rule_type)
298+
return handler.description if handler else ""
299+
289300

290301
class AbstractPolicyViolation(models.Model):
291302
"""Shared fields for all concrete policy violation models. No DB table."""

product_portfolio/admin.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ class ProductAdmin(
384384
ProductPackageInline,
385385
]
386386
form = ProductAdminForm
387-
actions = []
387+
actions = ["evaluate_policy_rules"]
388388
actions_to_remove = ["copy_to", "compare_with", "delete_selected"]
389389
navigation_buttons = True
390390
activity_log = False
@@ -395,6 +395,16 @@ class ProductAdmin(
395395
awesomplete_data = {"primary_language": PROGRAMMING_LANGUAGES}
396396
readonly_fields = DataspacedAdmin.readonly_fields + ("get_feature_datalist",)
397397

398+
def evaluate_policy_rules(self, request, queryset):
399+
from policy.tasks import evaluate_product_rules_task
400+
401+
count = queryset.count()
402+
for product in queryset:
403+
evaluate_product_rules_task.delay(product_uuid=product.uuid)
404+
self.message_user(request, f"Policy rules evaluation enqueued for {count} product(s).")
405+
406+
evaluate_policy_rules.short_description = _("Evaluate policy rules")
407+
398408
def get_feature_datalist(self, obj):
399409
if obj.pk:
400410
return obj.get_feature_datalist()

product_portfolio/models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from django.db.models import Max
2525
from django.db.models import OuterRef
2626
from django.db.models import Q
27+
from django.db.models import Subquery
2728
from django.db.models import Value
2829
from django.db.models import When
2930
from django.db.models.functions import Coalesce
@@ -244,6 +245,15 @@ def with_has_vulnerable_packages(self):
244245
has_vulnerable_packages=Exists(vulnerable_productpackage_qs),
245246
)
246247

248+
def with_policy_violation_count(self):
249+
subquery = ProductPolicyViolation.objects.filter(
250+
product=OuterRef("pk"),
251+
resolved=False,
252+
).values("product").annotate(violation_count=models.Count("id")).values("violation_count")
253+
return self.annotate(
254+
policy_violation_count=Subquery(subquery, output_field=models.IntegerField()),
255+
)
256+
247257

248258
class ProductSecuredManager(DataspacedManager):
249259
"""
@@ -426,6 +436,10 @@ def save(self, *args, **kwargs):
426436
if self.has_changed("configuration_status_id"):
427437
self.actions_on_status_change()
428438

439+
from policy.tasks import evaluate_product_rules_task
440+
441+
evaluate_product_rules_task.delay(product_uuid=self.uuid)
442+
429443
def get_attribution_url(self):
430444
return self.get_url("attribution")
431445

product_portfolio/templates/product_portfolio/compliance/compliance_dashboard.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ <h1 class="h3 mb-0">
130130
<th class="fw-medium text-end">{% trans "License compliance" %}</th>
131131
<th class="fw-medium text-end">{% trans "Security compliance" %}</th>
132132
<th class="fw-medium text-end">{% trans "Vulnerabilities" %}</th>
133+
<th class="fw-medium text-end">{% trans "Policy violations" %}</th>
133134
</tr>
134135
</thead>
135136
<tbody>
@@ -196,11 +197,18 @@ <h1 class="h3 mb-0">
196197
<span class="text-body-tertiary small">{% trans "None" %}</span>
197198
{% endif %}
198199
</td>
200+
<td class="text-end">
201+
{% if product.policy_violation_count %}
202+
<span class="badge bg-danger-subtle text-danger-emphasis">{{ product.policy_violation_count }}</span>
203+
{% else %}
204+
<span class="text-body-tertiary small">{% trans "None" %}</span>
205+
{% endif %}
206+
</td>
199207
</tr>
200208
{% endwith %}
201209
{% empty %}
202210
<tr>
203-
<td colspan="5" class="text-center text-body-tertiary py-4">
211+
<td colspan="6" class="text-center text-body-tertiary py-4">
204212
{% trans "No active products" %}
205213
</td>
206214
</tr>

product_portfolio/templates/product_portfolio/compliance/compliance_panels.html

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,42 @@ <h3 class="fs-6 fw-medium mb-0">{% trans "Security compliance" %}</h3>
214214
{% endif %}
215215
</div>
216216
</div>
217-
</div>
217+
</div>
218+
219+
{% if policy_violations %}
220+
<div class="row g-4 mb-4">
221+
<div class="col-12">
222+
<div class="border rounded-3 p-3">
223+
<div class="d-flex justify-content-between align-items-center mb-3">
224+
<h3 class="fs-6 fw-medium mb-0">{% trans "Policy violations" %}</h3>
225+
<span class="badge bg-danger-subtle text-danger-emphasis">
226+
{{ policy_violation_count }} {% trans "active" %}
227+
</span>
228+
</div>
229+
<table class="table table-sm mb-0">
230+
<thead>
231+
<tr>
232+
<th class="fw-medium">{% trans "Rule" %}</th>
233+
<th class="fw-medium">{% trans "Description" %}</th>
234+
<th class="fw-medium">{% trans "Objects in violation" %}</th>
235+
<th class="fw-medium">{% trans "Detected" %}</th>
236+
</tr>
237+
</thead>
238+
<tbody>
239+
{% for violation in policy_violations %}
240+
<tr>
241+
<td>
242+
<div>{{ violation.policy_rule.name }}</div>
243+
<div class="text-body-secondary small">{{ violation.policy_rule.rule_label }}</div>
244+
</td>
245+
<td class="text-body-secondary small">{{ violation.policy_rule.rule_description }}</td>
246+
<td>{{ violation.violation_count }}</td>
247+
<td class="text-body-tertiary small">{{ violation.detected_date|date:"N j, Y" }}</td>
248+
</tr>
249+
{% endfor %}
250+
</tbody>
251+
</table>
252+
</div>
253+
</div>
254+
</div>
255+
{% endif %}

product_portfolio/templates/product_portfolio/compliance/metric_cards.html

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% load i18n humanize %}
22
<div class="row g-3 mb-4">
3-
<div class="col-6 col-md-3">
3+
<div class="col-6 col-md">
44
<div class="bg-body-secondary rounded-3 p-3">
55
<div class="small text-body-secondary mb-1">{% trans "Total packages" %}</div>
66
<div class="fs-4 fw-medium lh-sm">{{ total_packages|intcomma }}</div>
@@ -15,7 +15,7 @@
1515
</div>
1616
</div>
1717
</div>
18-
<div class="col-6 col-md-3">
18+
<div class="col-6 col-md">
1919
<div class="bg-body-secondary rounded-3 p-3">
2020
<div class="small text-body-secondary mb-1">{% trans "License compliance" %}</div>
2121
<div class="fs-4 fw-medium lh-sm {% if license_compliance_pct == 100 %}text-success{% elif license_compliance_pct >= 90 %}text-warning-orange{% else %}text-danger{% endif %}">
@@ -32,7 +32,7 @@
3232
</div>
3333
</div>
3434
</div>
35-
<div class="col-6 col-md-3">
35+
<div class="col-6 col-md">
3636
<div class="bg-body-secondary rounded-3 p-3">
3737
<div class="small text-body-secondary mb-1">{% trans "License coverage" %}</div>
3838
<div class="fs-4 fw-medium lh-sm {% if license_coverage_pct == 100 %}text-success{% elif license_coverage_pct >= 90 %}text-warning-orange{% else %}text-danger{% endif %}">
@@ -49,7 +49,7 @@
4949
</div>
5050
</div>
5151
</div>
52-
<div class="col-6 col-md-3">
52+
<div class="col-6 col-md">
5353
<div class="bg-body-secondary rounded-3 p-3">
5454
<div class="small text-body-secondary mb-1">{% trans "Vulnerabilities" %}</div>
5555
{% if vulnerability_count == 0 %}
@@ -75,4 +75,18 @@
7575
{% endif %}
7676
</div>
7777
</div>
78+
<div class="col-6 col-md">
79+
<div class="bg-body-secondary rounded-3 p-3">
80+
<div class="small text-body-secondary mb-1">{% trans "Policy violations" %}</div>
81+
{% if policy_violation_count == 0 %}
82+
<div class="fs-4 fw-medium lh-sm text-success">0</div>
83+
<div class="text-body-tertiary fs-xs mt-1">{% trans "No active violations" %}</div>
84+
{% else %}
85+
<div class="fs-4 fw-medium lh-sm text-danger">{{ policy_violation_count }}</div>
86+
<div class="text-body-tertiary fs-xs mt-1">
87+
{{ policy_violation_count }} {% trans "rule" %}{{ policy_violation_count|pluralize }} {% trans "triggered" %}
88+
</div>
89+
{% endif %}
90+
</div>
91+
</div>
7892
</div>

product_portfolio/views.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2772,6 +2772,7 @@ def get_context_data(self, **kwargs):
27722772
**self.get_package_compliance_context(productpackages),
27732773
**self.get_license_compliance_context(licenses),
27742774
**self.get_security_compliance_context(product),
2775+
**self.get_policy_compliance_context(product),
27752776
}
27762777
)
27772778

@@ -2842,6 +2843,17 @@ def get_license_compliance_context(licenses, distribution_limit=10):
28422843
"remaining_license_count": max(0, len(license_distribution) - distribution_limit),
28432844
}
28442845

2846+
@staticmethod
2847+
@staticmethod
2848+
def get_policy_compliance_context(product):
2849+
policy_violations = (
2850+
product.policy_violations.filter(resolved=False).select_related("policy_rule")
2851+
)
2852+
return {
2853+
"policy_violations": policy_violations,
2854+
"policy_violation_count": policy_violations.count(),
2855+
}
2856+
28452857
@staticmethod
28462858
def get_security_compliance_context(product, display_limit=10):
28472859
risk_threshold = product.get_vulnerabilities_risk_threshold()
@@ -3057,6 +3069,7 @@ def get_queryset(self):
30573069
.with_compliance_data()
30583070
.with_max_risk_level()
30593071
.with_has_vulnerable_packages()
3072+
.with_policy_violation_count()
30603073
)
30613074

30623075
def get_context_data(self, **kwargs):

0 commit comments

Comments
 (0)