Skip to content

Commit

Permalink
Add a Vulnerabilities tab in the Product details view #95 (#173)
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <tdruez@nexb.com>
  • Loading branch information
tdruez committed Sep 2, 2024
1 parent 08f9367 commit 45cc6ba
Show file tree
Hide file tree
Showing 25 changed files with 413 additions and 154 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ Release notes
"Add Package".
https://github.com/aboutcode-org/dejacode/issues/163

- Add a Vulnerabilities tab in the Product details view.
https://github.com/aboutcode-org/dejacode/issues/95

### Version 5.1.0

- Upgrade Python version to 3.12 and Django to 5.0.x
Expand Down
2 changes: 1 addition & 1 deletion component_catalog/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters["max_score"].extra["widget"] = DropDownRightWidget()
self.filters["max_score"].extra["widget"] = DropDownRightWidget(anchor=self.anchor)

def filter_by_score_range(self, queryset, name, value):
if value in vulnerability_score_ranges:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{% block javascripts %}
{{ block.super }}
<script src="{% static 'awesomplete/awesomplete-1.1.5.min.js' %}" integrity="sha384-p5NIw+GEWbrK/9dC3Vuxh36c2HL0ETAXQ81nk8gl1B7FHZmXehonZWs/HBqunmCI" crossorigin="anonymous"></script>
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-oTqsk3bKZbt7gvIiSLAxv/f/1zUBuzofCLQcVe+9J9s7zHhGnWfebQu3miHGT1Vc" crossorigin="anonymous"></script>
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-sb1eCgSzQ43/Yt/kNTeuZ9XmmY0rfloyqPka6VPMR6ZWJsK0pTfsAnTHY7XRZUgd" crossorigin="anonymous"></script>
<script src="{% static 'json-viewer/jquery.json-viewer-1.4.0.js' %}" integrity="sha384-mCd7P/7rxz1zpQAb195/BFZG4pDkLO6GdkRi772EZRiLTGdfnlhC74NrrwtSHvBI" crossorigin="anonymous"></script>
{% include 'includes/dependencies-json-viewer.js.html' %}
{% if open_add_to_package_modal %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ul class="list-unstyled">
<ul class="list-unstyled mb-0">
{% for alias in aliases %}
<li>
{% if alias|slice:":3" == "CVE" %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@
{% trans 'Affected by' %}
</span>
</th>
<th>
<span class="help_text" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Summary of the vulnerability.">
{% trans 'Summary' %}
</span>
</th>
<th style="width: 210px;">
<span class="help_text" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="A list of aliases for this vulnerability.">
{% trans 'Aliases' %}
</span>
</th>
<th style="width: 90px;">
<span class="help_text" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Severity score range.">
{% trans 'Score' %}
</span>
</th>
<th>
<span class="help_text" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Summary of the vulnerability.">
{% trans 'Summary' %}
</span>
</th>
<th style="min-width: 320px;">
<span class="help_text" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="The identifiers of Package Versions that have been reported to fix a specific vulnerability and collected in VulnerableCodeDB.">
{% trans 'Fixed packages' %}
Expand All @@ -28,16 +33,37 @@
{% for vulnerability in values.vulnerabilities %}
<tr>
<td>
<a href="{{ values.vulnerablecode_url }}vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">
{{ vulnerability.vulnerability_id }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
<strong>
<a href="{{ values.vulnerablecode_url }}vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">
{{ vulnerability.vulnerability_id }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
</strong>
</td>
<td>
{{ vulnerability.summary }}
{% include 'component_catalog/includes/vulnerability_aliases.html' with aliases=vulnerability.aliases only %}
</td>
<td>
{% include 'component_catalog/includes/vulnerability_aliases.html' with aliases=vulnerability.aliases only %}
{% if vulnerability.min_score %}
{{ vulnerability.min_score }} -
{% endif %}
{% if vulnerability.max_score %}
<strong>
{{ vulnerability.max_score }}
</strong>
{% endif %}
</td>
<td>
{% if vulnerability.summary %}
{% if vulnerability.summary|length > 120 %}
<details>
<summary>{{ vulnerability.summary|slice:":120" }}...</summary>
{{ vulnerability.summary|slice:"120:" }}
</details>
{% else %}
{{ vulnerability.summary }}
{% endif %}
{% endif %}
</td>
<td>
{% if vulnerability.fixed_packages_html %}
Expand Down
31 changes: 31 additions & 0 deletions dejacode/static/css/dejacode_bootstrap.css
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,37 @@ table.vulnerabilities-table .column-summary {
max-width: 300px;
}

/* -- Vulnerability tab -- */
#tab_vulnerabilities .column-vulnerability_id {
width: 210px;
}
#tab_vulnerabilities .column-aliases {
width: 210px;
}
#tab_vulnerabilities .column-max_score {
width: 105px;
}
#tab_vulnerabilities .column-column-affected_packages {
width: 320px;
}

/* -- Dependency tab -- */
#tab_dependencies .column-for_package {
width: 250px;
}
#tab_dependencies .column-resolved_to_package {
width: 250px;
}
#tab_dependencies .column-is_runtime {
width: 100px;
}
#tab_dependencies .column-column-is_optional {
width: 100px;
}
#tab_dependencies .column-column-is_resolved {
width: 88px;
}

/* -- Package Details -- */
textarea.licenseexpressionwidget {
height: 62px;
Expand Down
6 changes: 5 additions & 1 deletion dje/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ def is_active(self):
)

def get_query_no_sort(self):
return remove_field_from_query_dict(self.data, "sort")
sort_field_name = "sort"
if self.form_prefix:
sort_field_name = f"{self.form_prefix}-{sort_field_name}"
return remove_field_from_query_dict(self.data, sort_field_name)

def get_filter_breadcrumb(self, field_name, data_field_name, value):
return {
Expand Down Expand Up @@ -86,6 +89,7 @@ def __init__(self, *args, **kwargs):

self.dynamic_qs = kwargs.pop("dynamic_qs", True)
self.parent_qs_cache = {}
self.anchor = kwargs.pop("anchor", None)

super().__init__(*args, **kwargs)

Expand Down
2 changes: 1 addition & 1 deletion dje/templates/global_search.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ <h4>

{% block javascripts %}
{{ block.super }}
<script src="{% static 'js/scroll_to.js' %}" integrity="sha384-4gCm4SPCq9O/y66P13LOCfjqfgMtrLt+7DzjdZfHi00UBDkeqn7oAf9Ywdi1HeXN" crossorigin="anonymous"></script>
<script src="{% static 'js/scroll_to.js' %}" integrity="sha384-5b5N+CiBiLgPGLMj4/gsf7MiPclQO0wtbtg1aUq+wogLo8D59FAoRjL/TfuKZf/t" crossorigin="anonymous"></script>

{% if include_purldb %}
<script>
Expand Down
6 changes: 4 additions & 2 deletions dje/templates/includes/object_list_table_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
{% if header.sort %}
{% with query_no_sort=filter.get_query_no_sort %}
{% if filter.form.sort.data and header.field_name in filter.form.sort.data.0 %}
<a href="?{% if query_no_sort %}{{ query_no_sort }}&{% endif %}sort={% if '-' not in filter.form.sort.data.0 %}-{% endif %}{{ header.field_name }}" class="sort active" aria-label="Sort"><i class="fas fa-sort-{% if '-' not in filter.form.sort.data.0 %}up{% else %}down{% endif %}"></i></a>
{# The sort for this field is currently active #}
<a href="?{% if query_no_sort %}{{ query_no_sort }}&{% endif %}{% if tab_id %}{{ tab_id }}-{% endif %}sort={% if '-' not in filter.form.sort.data.0 %}-{% endif %}{{ header.field_name }}{% if tab_id %}#{{ tab_id }}{% endif %}" class="sort active" aria-label="Sort"><i class="fas fa-sort-{% if '-' not in filter.form.sort.data.0 %}up{% else %}down{% endif %}"></i></a>
{% else %}
<a href="?{% if query_no_sort %}{{ query_no_sort }}&{% endif %}sort={{ header.field_name }}" class="sort" aria-label="Sort"><i class="fas fa-sort"></i></a>
{# The sort for this field is NOT active #}
<a href="?{% if query_no_sort %}{{ query_no_sort }}&{% endif %}{% if tab_id %}{{ tab_id }}-{% endif %}sort={{ header.field_name }}{% if tab_id %}#{{ tab_id }}{% endif %}" class="sort" aria-label="Sort"><i class="fas fa-sort"></i></a>
{% endif %}
{% endwith %}
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion dje/templates/notifications/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,5 @@ <h1 class="header-title">

{% block javascripts %}
{{ block.super }}
<script src="{% static 'js/scroll_to.js' %}" integrity="sha384-4gCm4SPCq9O/y66P13LOCfjqfgMtrLt+7DzjdZfHi00UBDkeqn7oAf9Ywdi1HeXN" crossorigin="anonymous"></script>
<script src="{% static 'js/scroll_to.js' %}" integrity="sha384-5b5N+CiBiLgPGLMj4/gsf7MiPclQO0wtbtg1aUq+wogLo8D59FAoRjL/TfuKZf/t" crossorigin="anonymous"></script>
{% endblock %}
4 changes: 2 additions & 2 deletions dje/templates/object_details_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ <h1 class="header-title text-break">
<nav role="navigation">
<ul class="nav nav-tabs container px-3" id="details_tab" role="tablist">
{% for tab_name, tab_context in tabsets.items %}
<li class="nav-item" role="presentation">
<button class="nav-link{% if forloop.first %} active{% endif %}" id="tab_{{ tab_name|slugify }}-tab" data-bs-toggle="tab" data-bs-target="#tab_{{ tab_name|slugify }}" type="button" role="tab" aria-controls="tab_{{ tab_name|slugify }}" aria-selected="{% if forloop.first %}true{% else %}false{% endif %}">
<li class="nav-item" role="presentation"{% if tab_context.tooltip %} data-bs-toggle="tooltip" title="{{ tab_context.tooltip }}"{% endif %}>
<button class="nav-link{% if forloop.first %} active{% endif %}" id="tab_{{ tab_name|slugify }}-tab" data-bs-toggle="tab" data-bs-target="#tab_{{ tab_name|slugify }}" type="button" role="tab" aria-controls="tab_{{ tab_name|slugify }}" aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"{% if tab_context.disabled %} disabled="disabled"{% endif %}>
{% if tab_context.label %}
{{ tab_context.label }}
{% else %}
Expand Down
2 changes: 1 addition & 1 deletion dje/templates/object_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ <h1 class="header-title">
{% block javascripts %}
{{ block.super }}
{% if form.fields.license_expression %}
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-oTqsk3bKZbt7gvIiSLAxv/f/1zUBuzofCLQcVe+9J9s7zHhGnWfebQu3miHGT1Vc" crossorigin="anonymous"></script>
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-sb1eCgSzQ43/Yt/kNTeuZ9XmmY0rfloyqPka6VPMR6ZWJsK0pTfsAnTHY7XRZUgd" crossorigin="anonymous"></script>
{% endif %}
{{ licenses_data_for_builder|json_script:"licenses_data_for_builder" }}
{{ form.identifier_fields|json_script:"identifier_fields" }}
Expand Down
30 changes: 30 additions & 0 deletions dje/templates/tabs/pagination.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% load humanize %}
<div class="row align-items-end">
<div class="col mb-3">
<ul class="nav nav-pills">
<li class="nav-item">
<form id="tab-{{ tab_id }}-search-form" class="mt-md-0 me-sm-2">
<input style="width: 250px;" type="text" class="form-control form-control-sm" id="tab-{{ tab_id }}-search-input" name="{{ tab_id }}-q" placeholder="Search {{ tab_id }}" aria-label="Search" autocomplete="off" value="{{ search_query|escape }}">
</form>
</li>
<li class="nav-item">
<div class="h6 mt-2 mb-0 smaller">
{% if page_obj.paginator.count != total_count %}
{{ page_obj.paginator.count|intcomma }} of
<a href="#" hx-get="{{ request.path }}?all=true#{{ tab_id }}" hx-target="{{ tab_id_html }}">
{{ total_count }} results
</a>
{% else %}
{{ page_obj.paginator.count|intcomma }} results
{% endif %}
</div>
</li>
</ul>
<div class="mt-1">
{% include 'includes/filters_breadcrumbs.html' with filterset=filterset fragment=tab_id only %}
</div>
</div>
<div class="col-auto">
{% include 'pagination/object_list_pagination.html' with hx_target=tab_id_html %}
</div>
</div>
1 change: 1 addition & 0 deletions dje/tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def test_permissions_get_all_tabsets(self):
"notice",
"license",
"owner",
"vulnerabilities",
"dependencies",
"activity",
"imports",
Expand Down
5 changes: 3 additions & 2 deletions dje/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,11 @@ def get_context_data(self, **kwargs):
class TableHeaderMixin:
table_headers = ()
model = None
filterset = None
table_model = None

def get_table_headers(self):
opts = self.model._meta
model = self.table_model or self.model
opts = model._meta

sort_fields = []
if self.filterset and "sort" in self.filterset.filters:
Expand Down
2 changes: 0 additions & 2 deletions product_portfolio/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ def filter_object_type(queryset, name, value):
return queryset.none()

def __init__(self, *args, **kwargs):
self.anchor = kwargs.pop("anchor", None)

super().__init__(*args, **kwargs)

self.filters["review_status"].extra["to_field_name"] = "label"
Expand Down
5 changes: 5 additions & 0 deletions product_portfolio/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from component_catalog.models import KeywordsMixin
from component_catalog.models import LicenseExpressionMixin
from component_catalog.models import Package
from component_catalog.models import Vulnerability
from component_catalog.models import component_mixin_factory
from component_catalog.vulnerabilities import fetch_for_queryset
from dje import tasks
Expand Down Expand Up @@ -495,6 +496,10 @@ def fetch_vulnerabilities(self):
"""Fetch and update the vulnerabilties of all the Package of this Product."""
return fetch_for_queryset(self.all_packages, self.dataspace)

def get_vulnerability_qs(self):
"""Return a QuerySet of all Vulnerability instances related to this product"""
return Vulnerability.objects.filter(affected_packages__in=self.packages.all())


class ProductRelationStatus(BaseStatusMixin, DataspacedModel):
class Meta(BaseStatusMixin.Meta):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ <h1 class="header-title">
{{ formset.media }}
<link href="{% static 'awesomplete/awesomplete-1.1.5.css' %}" type="text/css" media="all" rel="stylesheet">
<script src="{% static 'awesomplete/awesomplete-1.1.5.min.js' %}" integrity="sha384-p5NIw+GEWbrK/9dC3Vuxh36c2HL0ETAXQ81nk8gl1B7FHZmXehonZWs/HBqunmCI" crossorigin="anonymous"></script>
<script src="{% static 'js/widget_autocomplete.js' %}" integrity="sha384-Xwh0IgdZ3xDFYieC0irLFU0+f9PHFFfA5k/MHGMBcwVDgovhbrJhXEbXX7s5/Ylz" crossorigin="anonymous"></script>
<script src="{% static 'js/widget_autocomplete.js' %}" integrity="sha384-ksQnUTczA9QhT+bgsL38TcPgxAQFj6tyqqdmYm1tmfemEgq/u0QqXshxPiefnTIz" crossorigin="anonymous"></script>

{% include 'includes/messages_alert.html' %}

Expand Down Expand Up @@ -167,7 +167,7 @@ <h5 class="modal-title">Create new Component</h5>
{% endblock %}

{% block javascripts %}
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-oTqsk3bKZbt7gvIiSLAxv/f/1zUBuzofCLQcVe+9J9s7zHhGnWfebQu3miHGT1Vc" crossorigin="anonymous"></script>
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-sb1eCgSzQ43/Yt/kNTeuZ9XmmY0rfloyqPka6VPMR6ZWJsK0pTfsAnTHY7XRZUgd" crossorigin="anonymous"></script>
<script src="{% static 'js/jquery.dirtyforms.2.0.0.min.js' %}" integrity="sha384-xesGfeB9VUH4sEN2ROWGaWMcYi5B/NjoBb5XK6cvcuUyL6f+GI2B7kcCzbqsJcwc" crossorigin="anonymous"></script>
{% if component_add_form %}
<script src="{% static 'js/csrf_header.js' %}" integrity="sha384-H61e46QMjASwnZFb/rwCl9PANtdqt1dbKU8gnGOh9lIGQEoi1B6qkWROHnrktD3R" crossorigin="anonymous"></script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@

{% if has_edit_productpackage or has_edit_productcomponent or has_add_productcomponent %}
<script src="{% static 'awesomplete/awesomplete-1.1.5.min.js' %}" integrity="sha384-p5NIw+GEWbrK/9dC3Vuxh36c2HL0ETAXQ81nk8gl1B7FHZmXehonZWs/HBqunmCI" crossorigin="anonymous"></script>
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-oTqsk3bKZbt7gvIiSLAxv/f/1zUBuzofCLQcVe+9J9s7zHhGnWfebQu3miHGT1Vc" crossorigin="anonymous"></script>
<script src="{% static 'js/license_expression_builder.js' %}" integrity="sha384-sb1eCgSzQ43/Yt/kNTeuZ9XmmY0rfloyqPka6VPMR6ZWJsK0pTfsAnTHY7XRZUgd" crossorigin="anonymous"></script>
<script>
$(document).ready(function () {
let edit_modal = $('#edit-productrelation-modal');
Expand Down
Loading

0 comments on commit 45cc6ba

Please sign in to comment.