Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Vulnerabilities tab in the Product details view #95 #173

Merged
merged 9 commits into from
Sep 2, 2024
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