From dcb5beeb7c602c1d62bc2b467df9416c1ee0be75 Mon Sep 17 00:00:00 2001 From: tdruez Date: Tue, 27 Aug 2024 15:19:41 +0400 Subject: [PATCH] Add a new affected_packages_count column #95 Signed-off-by: tdruez --- component_catalog/filters.py | 7 ++-- component_catalog/models.py | 35 ++++++++++++++----- .../vulnerability_list_table.html | 9 +++++ component_catalog/views.py | 18 +++++----- product_portfolio/filters.py | 4 +++ 5 files changed, 52 insertions(+), 21 deletions(-) rename component_catalog/templates/component_catalog/{includes => tables}/vulnerability_list_table.html (87%) diff --git a/component_catalog/filters.py b/component_catalog/filters.py index c1a51c70..cfcfb6dc 100644 --- a/component_catalog/filters.py +++ b/component_catalog/filters.py @@ -305,6 +305,7 @@ class VulnerabilityFilterSet(DataspacedFilterSet): "lowest_score", "priority", "vulnerability_id", + "affected_products_count", "affected_packages_count", "fixed_packages_length", "created_date", @@ -314,9 +315,9 @@ class VulnerabilityFilterSet(DataspacedFilterSet): ) highest_score = django_filters.ChoiceFilter( choices=SCORE_CHOICES, - method='filter_by_score_range', - label='Score Range', - help_text='Select a score range to filter.' + method="filter_by_score_range", + label="Score Range", + help_text="Select a score range to filter.", ) class Meta: diff --git a/component_catalog/models.py b/component_catalog/models.py index b35228b0..e82e633b 100644 --- a/component_catalog/models.py +++ b/component_catalog/models.py @@ -19,6 +19,7 @@ from django.core.validators import EMPTY_VALUES from django.db import models from django.db.models import CharField +from django.db.models import Count from django.db.models import Exists from django.db.models import OuterRef from django.db.models.functions import Concat @@ -134,7 +135,7 @@ class VulnerabilityQuerySetMixin: def with_vulnerability_count(self): """Annotate the QuerySet with the vulnerability_count.""" return self.annotate( - vulnerability_count=models.Count("affected_by_vulnerabilities", distinct=True) + vulnerability_count=Count("affected_by_vulnerabilities", distinct=True) ) def vulnerable(self): @@ -1748,7 +1749,7 @@ def declared_dependencies_count(self, product): dependencies are always scoped to a Product. """ return self.annotate( - declared_dependencies_count=models.Count( + declared_dependencies_count=Count( "declared_dependencies", filter=models.Q(declared_dependencies__product=product), ) @@ -2562,6 +2563,22 @@ def __str__(self): return f"<{self.component}>: {self.package}" +class VulnerabilityQuerySet(DataspacedQuerySet): + def with_affected_products_count(self): + """Annotate the QuerySet with the affected_products_count.""" + return self.annotate( + affected_products_count=Count( + "affected_packages__productpackages__product", distinct=True + ), + ) + + def with_affected_packages_count(self): + """Annotate the QuerySet with the affected_packages_count.""" + return self.annotate( + affected_packages_count=Count("affected_packages", distinct=True), + ) + + class Vulnerability(HistoryDateFieldsMixin, DataspacedModel): """ A software vulnerability with a unique identifier and alternate aliases. @@ -2622,9 +2639,9 @@ class Vulnerability(HistoryDateFieldsMixin, DataspacedModel): # The second set of fields are in the context of handling vulnerabilities in DejaCode class Priority(models.IntegerChoices): - HIGH = 1, 'High' - MEDIUM = 2, 'Medium' - LOW = 3, 'Low' + HIGH = 1, "High" + MEDIUM = 2, "Medium" + LOW = 3, "Low" priority = models.IntegerField( choices=Priority.choices, @@ -2633,6 +2650,8 @@ class Priority(models.IntegerChoices): help_text=_("Priority level"), ) + objects = DataspacedManager.from_queryset(VulnerabilityQuerySet)() + class Meta: verbose_name_plural = "Vulnerabilities" unique_together = (("dataspace", "vulnerability_id"), ("dataspace", "uuid")) @@ -2672,7 +2691,7 @@ def add_affected_components(self, components): @staticmethod def range_to_values(self, range_str): try: - min_str, max_str = score.split('-') + min_str, max_str = range_str.split("-") return float(min_str.strip()), float(max_str.strip()) except Exception: return @@ -2686,9 +2705,7 @@ def create_from_data(cls, dataspace, data, validate=False, affecting=None): # data["highest_score"] = max_score severities = [ - score - for reference in data.get("references") - for score in reference.get("scores", []) + score for reference in data.get("references") for score in reference.get("scores", []) ] scores = cls.get_severity_scores(severities) if scores: diff --git a/component_catalog/templates/component_catalog/includes/vulnerability_list_table.html b/component_catalog/templates/component_catalog/tables/vulnerability_list_table.html similarity index 87% rename from component_catalog/templates/component_catalog/includes/vulnerability_list_table.html rename to component_catalog/templates/component_catalog/tables/vulnerability_list_table.html index d7254f80..66ac6c62 100644 --- a/component_catalog/templates/component_catalog/includes/vulnerability_list_table.html +++ b/component_catalog/templates/component_catalog/tables/vulnerability_list_table.html @@ -46,6 +46,15 @@ {% endif %} {% endif %} + + {% if vulnerability.affected_products_count %} + + {{ vulnerability.affected_products_count }} + + {% else %} + 0 + {% endif %} + {% if vulnerability.affected_packages_count %} diff --git a/component_catalog/views.py b/component_catalog/views.py index 1bc26909..518b5f17 100644 --- a/component_catalog/views.py +++ b/component_catalog/views.py @@ -2487,16 +2487,18 @@ class VulnerabilityListView( model = Vulnerability filterset_class = VulnerabilityFilterSet template_name = "component_catalog/vulnerability_list.html" - template_list_table = "component_catalog/includes/vulnerability_list_table.html" + template_list_table = "component_catalog/tables/vulnerability_list_table.html" table_headers = ( Header("vulnerability_id", _("Vulnerability")), Header("aliases", _("Aliases")), - Header("highest_score", _("Score"), help_text="Severity score range", filter="highest_score"), + Header( + "highest_score", _("Score"), help_text="Severity score range", filter="highest_score" + ), # Header("priority", _("Priority"), filter="priority"), Header("summary", _("Summary")), - Header("affected_packages_count", "Affected packages", help_text="TODO"), - Header("fixed_packages_length", "Fixed by packages", help_text="TODO"), - # Header("affected_product_count", "Affected products"), + Header("affected_products_count", "Affected products", help_text="Affected products"), + Header("affected_packages_count", "Affected packages", help_text="Affected packages"), + Header("fixed_packages_length", "Fixed by", help_text="Fixed by packages"), ) def get_queryset(self): @@ -2513,11 +2515,9 @@ def get_queryset(self): # "last_modified_date", # "dataspace", # ) - .annotate( - affected_packages_count=Count("affected_packages"), - ) + .with_affected_products_count() + .with_affected_packages_count() .order_by( - "priority", "-highest_score", "-lowest_score", ) diff --git a/product_portfolio/filters.py b/product_portfolio/filters.py index 6bc89b39..b84f6236 100644 --- a/product_portfolio/filters.py +++ b/product_portfolio/filters.py @@ -102,6 +102,10 @@ class ProductFilterSet(DataspacedFilterSet): search_placeholder="Search keywords", ), ) + affected_by = django_filters.CharFilter( + field_name="packages__affected_by_vulnerabilities__vulnerability_id", + label=_("Affected by"), + ) class Meta: model = Product