Skip to content

Commit 25425e1

Browse files
committed
refinements regarding violations resolution
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent b8017ae commit 25425e1

6 files changed

Lines changed: 37 additions & 25 deletions

File tree

policy/engine.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@ def evaluate_rule(rule_type, product, threshold, parameters):
7878
return violation, created, 0
7979

8080
now = timezone.now()
81-
resolved_count = ProductPolicyViolation.objects.filter(**lookup, resolved=False).update(
82-
resolved=True,
83-
resolved_date=now,
84-
last_checked=now,
81+
resolved_count = (
82+
ProductPolicyViolation.objects.filter(**lookup)
83+
.unresolved()
84+
.update(
85+
resolved=True,
86+
resolved_date=now,
87+
last_checked=now,
88+
)
8589
)
8690
return None, False, resolved_count
8791

@@ -96,17 +100,21 @@ def evaluate_rules(product):
96100
"""
97101
new_violations = []
98102
resolved_count = 0
103+
now = timezone.now()
99104

100105
for rule_type in RULE_REGISTRY:
101106
config = get_effective_config(rule_type, product.dataspace)
102107
if not config["is_active"]:
103108
# Explicitly resolve open violations so disabling a rule clears its history
104109
# rather than leaving stale unresolved records.
105-
rows = ProductPolicyViolation.objects.filter(
106-
rule_type=rule_type,
107-
product=product,
108-
resolved=False,
109-
).update(resolved=True, resolved_date=timezone.now())
110+
rows = (
111+
ProductPolicyViolation.objects.filter(
112+
rule_type=rule_type,
113+
product=product,
114+
)
115+
.unresolved()
116+
.update(resolved=True, resolved_date=now, last_checked=now)
117+
)
110118
resolved_count += rows
111119
continue
112120

policy/tasks.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ def evaluate_product_rules_task(product_uuid):
2424
Product = apps.get_model("product_portfolio", "product")
2525

2626
try:
27-
product = get_unsecured_manager(Product).get(uuid=product_uuid)
27+
product = (
28+
get_unsecured_manager(Product)
29+
.select_related("dataspace__configuration")
30+
.get(uuid=product_uuid)
31+
)
2832
except Product.DoesNotExist:
2933
logger.error(f"evaluate_product_rules_task: product {product_uuid} not found, skipping.")
3034
return
@@ -49,7 +53,7 @@ def evaluate_all_products_rules_task(include_locked=False, product_uuids=None):
4953
if product_uuids is not None:
5054
products = products.filter(uuid__in=product_uuids)
5155
if not include_locked:
52-
products = products.exclude(configuration_status__is_locked=True)
56+
products = products.exclude_locked()
5357

5458
count = products.count()
5559
logger.info(f"Starting policy rule evaluation for {count} product(s).")

product_portfolio/admin.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
from dje.permissions import assign_all_object_permissions
4747
from dje.permissions import get_limited_perms_for_model
4848
from dje.utils import is_purl_fragment
49-
from policy.tasks import evaluate_product_rules_task
49+
from policy.tasks import evaluate_all_products_rules_task
5050
from product_portfolio.filters import ComponentCompletenessListFilter
5151
from product_portfolio.forms import ProductAdminForm
5252
from product_portfolio.forms import ProductComponentAdminForm
@@ -397,10 +397,11 @@ class ProductAdmin(
397397
readonly_fields = DataspacedAdmin.readonly_fields + ("get_feature_datalist",)
398398

399399
def evaluate_policy_rules(self, request, queryset):
400-
count = queryset.count()
401-
for product in queryset:
402-
evaluate_product_rules_task.delay(product_uuid=product.uuid)
403-
self.message_user(request, f"Policy rules evaluation enqueued for {count} product(s).")
400+
product_uuids = list(queryset.values_list("uuid", flat=True))
401+
evaluate_all_products_rules_task.delay(product_uuids=product_uuids)
402+
self.message_user(
403+
request, f"Policy rules evaluation enqueued for {len(product_uuids)} product(s)."
404+
)
404405

405406
evaluate_policy_rules.short_description = _("Evaluate policy rules")
406407

product_portfolio/filters.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,8 @@ def filter_policy_violations(self, queryset, name, value):
200200
return queryset
201201
has_violation = ProductPolicyViolation.objects.filter(
202202
product_id=OuterRef("pk"),
203-
resolved=False,
204-
rule_type__in=RULE_REGISTRY,
205-
)
203+
rule_type__in=RULE_REGISTRY.keys(),
204+
).unresolved()
206205
condition = Exists(has_violation)
207206
return queryset.filter(condition if value else ~condition)
208207

product_portfolio/tests/test_admin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ def setUp(self):
554554
self.product1 = Product.objects.create(name="Product1", dataspace=self.dataspace)
555555
self.product2 = Product.objects.create(name="Product2", dataspace=self.dataspace)
556556

557-
@patch("product_portfolio.admin.evaluate_product_rules_task.delay")
557+
@patch("product_portfolio.admin.evaluate_all_products_rules_task.delay")
558558
def test_evaluate_policy_rules_action_queues_task_for_selected_products(self, mock_delay):
559559
self.client.login(username="nexb_user", password="secret")
560560
url = reverse("admin:product_portfolio_product_changelist")
@@ -564,7 +564,7 @@ def test_evaluate_policy_rules_action_queues_task_for_selected_products(self, mo
564564
}
565565
response = self.client.post(url, data, follow=True)
566566
self.assertEqual(200, response.status_code)
567-
self.assertEqual(2, mock_delay.call_count)
568-
called_uuids = {call[1]["product_uuid"] for call in mock_delay.call_args_list}
567+
mock_delay.assert_called_once()
568+
called_uuids = set(mock_delay.call_args[1]["product_uuids"])
569569
self.assertIn(self.product1.uuid, called_uuids)
570570
self.assertIn(self.product2.uuid, called_uuids)

product_portfolio/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2869,9 +2869,9 @@ def get_license_compliance_context(licenses, distribution_limit=10):
28692869
@staticmethod
28702870
def get_policy_compliance_context(product):
28712871
policy_violations = list(
2872-
product.policy_violations.filter(resolved=False, rule_type__in=RULE_REGISTRY).order_by(
2873-
"rule_type"
2874-
)
2872+
product.policy_violations.filter(rule_type__in=RULE_REGISTRY.keys())
2873+
.unresolved()
2874+
.order_by("rule_type")
28752875
)
28762876
violated_rule_types = {violation.rule_type for violation in policy_violations}
28772877

0 commit comments

Comments
 (0)