Skip to content

Commit

Permalink
Add a new affected_packages_count column #95
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <tdruez@nexb.com>
  • Loading branch information
tdruez committed Aug 27, 2024
1 parent a0b674b commit dcb5bee
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 21 deletions.
7 changes: 4 additions & 3 deletions component_catalog/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ class VulnerabilityFilterSet(DataspacedFilterSet):
"lowest_score",
"priority",
"vulnerability_id",
"affected_products_count",
"affected_packages_count",
"fixed_packages_length",
"created_date",
Expand All @@ -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:
Expand Down
35 changes: 26 additions & 9 deletions component_catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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),
)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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"))
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@
{% endif %}
{% endif %}
</td>
<td>
{% if vulnerability.affected_products_count %}
<a href="{% url 'product_portfolio:product_list' %}?affected_by={{ vulnerability.vulnerability_id }}">
{{ vulnerability.affected_products_count }}
</a>
{% else %}
0
{% endif %}
</td>
<td>
{% if vulnerability.affected_packages_count %}
<a href="{% url 'component_catalog:package_list' %}?affected_by={{ vulnerability.vulnerability_id }}">
Expand Down
18 changes: 9 additions & 9 deletions component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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",
)
Expand Down
4 changes: 4 additions & 0 deletions product_portfolio/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit dcb5bee

Please sign in to comment.