Skip to content

Commit 3328c73

Browse files
committed
deactivate signal in the context of imports
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent f716f9a commit 3328c73

3 files changed

Lines changed: 72 additions & 3 deletions

File tree

product_portfolio/importers.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from django.db import transaction
2121
from django.db.models import ObjectDoesNotExist
2222
from django.db.models import Q
23+
from django.db.models.signals import post_delete
24+
from django.db.models.signals import post_save
2325
from django.utils.translation import gettext_lazy as _
2426

2527
from license_expression import Licensing
@@ -40,6 +42,8 @@
4042
from dje.models import Dataspace
4143
from dje.utils import get_help_text
4244
from dje.utils import is_uuid4
45+
from policy.signals import evaluate_product_rules_on_productpackage_change
46+
from policy.tasks import evaluate_product_rules_task
4347
from product_portfolio.forms import ProductComponentLicenseExpressionFormMixin
4448
from product_portfolio.models import CodebaseResource
4549
from product_portfolio.models import CodebaseResourceUsage
@@ -50,6 +54,8 @@
5054
from product_portfolio.models import ProductPackage
5155
from product_portfolio.models import ProductRelationStatus
5256
from product_portfolio.models import ScanCodeProject
57+
from vulnerabilities.triage.signals import reevaluate_on_product_package_change
58+
from vulnerabilities.triage.tasks import reevaluate_product_triage_rulesets_task
5359

5460
logger = logging.getLogger(__name__)
5561

@@ -62,6 +68,35 @@ def log_elapsed(label):
6268
logger.info(f"{label}: {time.perf_counter() - start:.1f}s")
6369

6470

71+
@contextmanager
72+
def paused_product_package_reevaluation():
73+
"""
74+
Pause the policy and triage re-evaluation signals triggered by ProductPackage changes,
75+
for the duration of a bulk import. Call `reevaluate_products()` once the import completes
76+
to evaluate each affected product exactly once, instead of once per imported row.
77+
"""
78+
receivers = [
79+
evaluate_product_rules_on_productpackage_change,
80+
reevaluate_on_product_package_change,
81+
]
82+
for receiver in receivers:
83+
post_save.disconnect(receiver, sender=ProductPackage)
84+
post_delete.disconnect(receiver, sender=ProductPackage)
85+
try:
86+
yield
87+
finally:
88+
for receiver in receivers:
89+
post_save.connect(receiver, sender=ProductPackage)
90+
post_delete.connect(receiver, sender=ProductPackage)
91+
92+
93+
def reevaluate_products(products):
94+
"""Queue the policy and triage re-evaluation once for each of the given products."""
95+
for product in products:
96+
evaluate_product_rules_task.delay(product_uuid=product.uuid)
97+
reevaluate_product_triage_rulesets_task.delay(product_uuid=product.uuid)
98+
99+
65100
class CleanProductMixin(ComponentRelatedFieldImportMixin):
66101
def clean_product(self):
67102
queryset = Product.objects.get_queryset(self.user)
@@ -230,6 +265,14 @@ def clean_package(self):
230265
class ProductPackageImporter(BaseImporter):
231266
model_form = ProductPackageImportForm
232267

268+
def save_all(self):
269+
with paused_product_package_reevaluation():
270+
super().save_all()
271+
272+
touched_product_packages = self.results["added"] + self.results["modified"]
273+
products = {product_package.product for product_package in touched_product_packages}
274+
reevaluate_products(products)
275+
233276

234277
class CodebaseResourceImportForm(CleanProductMixin, BaseImportModelForm):
235278
product = forms.CharField(
@@ -434,7 +477,9 @@ def save(self):
434477
self.create_scancode_project()
435478
self.load_data_from_file()
436479
self.validate_headers()
437-
self.import_packages()
480+
with paused_product_package_reevaluation():
481+
self.import_packages()
482+
reevaluate_products([self.product])
438483
if self.create_codebase_resources:
439484
self.import_codebase_resources()
440485
self.update_scancode_project()
@@ -720,7 +765,9 @@ def save(self):
720765
self.dependencies = scancodeio.fetch_project_dependencies(self.project_uuid)
721766

722767
with log_elapsed("import_packages"):
723-
self.import_packages()
768+
with paused_product_package_reevaluation():
769+
self.import_packages()
770+
reevaluate_products([self.product])
724771

725772
if self.create_dependencies:
726773
with log_elapsed("import_dependencies"):

vulnerabilities/triage/signals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def reevaluate_on_analysis_change(sender, instance, **kwargs):
4949
"""Re-evaluate triage on a product when an vulnerability analysis is updated."""
5050
signal = kwargs.get("signal")
5151
if signal == post_save and instance.applied_by_preset_id:
52-
# When the analysis is created by the triage engine itself, the evaluation is skipped.
52+
# When the analysis is created by the triage engine itself, the evaluation is skipped.
5353
return
5454

5555
# When a user explicitly deletes their analysis, skip preset application to avoid

vulnerabilities/triage/tasks.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,36 @@
88

99
import logging
1010

11+
from django.apps import apps
12+
1113
from django_rq import job
1214

15+
from dje.models import get_unsecured_manager
1316
from vulnerabilities.triage.engine import evaluate_ruleset
17+
from vulnerabilities.triage.engine import reevaluate_product_rulesets
1418
from vulnerabilities.triage.models import ProductTriageRuleset
1519

1620
logger = logging.getLogger(__name__)
1721

1822

23+
@job
24+
def reevaluate_product_triage_rulesets_task(product_uuid):
25+
"""Re-evaluate all enabled triage rulesets assigned to the given product."""
26+
Product = apps.get_model("product_portfolio", "product")
27+
28+
try:
29+
product = get_unsecured_manager(Product).get(uuid=product_uuid)
30+
except Product.DoesNotExist:
31+
logger.error(
32+
f"reevaluate_product_triage_rulesets_task: product {product_uuid} not found,"
33+
" skipping."
34+
)
35+
return
36+
37+
logger.info(f"Evaluating triage rulesets for product {product}")
38+
reevaluate_product_rulesets(product)
39+
40+
1941
@job
2042
def evaluate_all_products_vulnerability_triage_task():
2143
"""Evaluate all enabled triage rulesets against their assigned products."""

0 commit comments

Comments
 (0)