Skip to content

Commit b60b3f5

Browse files
committed
Refactor to get_epss_history and update tests
Signed-off-by: Sampurna Pyne <sampurnapyne1710@gmail.com>
1 parent 8115908 commit b60b3f5

4 files changed

Lines changed: 148 additions & 104 deletions

File tree

vulnerabilities/templates/advisory_detail.html

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
<div class="tabs is-boxed" id="tabs">
2727
<ul>
28-
<li class="{% if not is_epss_tab_active %}is-active{% endif %}" data-tab="essentials">
28+
<li class="{% if not 'epss_page' in request.GET %}is-active{% endif %}" data-tab="essentials">
2929
<a>
3030
<span>Essentials</span>
3131
</a>
@@ -55,7 +55,7 @@
5555
</li>
5656
{% endif %}
5757

58-
<li class="{% if is_epss_tab_active %}is-active{% endif %}" data-tab="epss">
58+
<li class="{% if 'epss_page' in request.GET %}is-active{% endif %}" data-tab="epss">
5959
<a>
6060
<span>
6161
EPSS
@@ -80,7 +80,7 @@
8080
</a>
8181
</li>
8282
{% endif %}
83-
83+
8484
<li data-tab="patch-url">
8585
<a>
8686
<span>
@@ -90,17 +90,18 @@
9090
</span>
9191
</a>
9292
</li>
93-
<!-- <li data-tab="history">
93+
94+
{% comment %} <li data-tab="history">
9495
<a>
9596
<span>
9697
History ({{ history|length }})
9798
</span>
9899
</a>
99-
</li> -->
100+
</li> {% endcomment %}
100101
</ul>
101102
</div>
102103
<div id="tab-content">
103-
<div class="tab-div {% if not is_epss_tab_active %}is-active{% endif %}" data-content="essentials">
104+
<div class="tab-div {% if not 'epss_page' in request.GET %}is-active{% endif %}" data-content="essentials">
104105
<div class="tab-nested-div">
105106
<table class="table vcio-table width-100-pct mt-2">
106107
<tbody>
@@ -458,7 +459,7 @@
458459
{% endfor %}
459460
</div>
460461

461-
<div class="tab-div content {% if is_epss_tab_active %}is-active{% endif %}" data-content="epss">
462+
<div class="tab-div content {% if 'epss_page' in request.GET %}is-active{% endif %}" data-content="epss">
462463
{% if epss_data %}
463464
<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-1">
464465
Exploit Prediction Scoring System (EPSS)
@@ -533,14 +534,14 @@
533534
</button>
534535
</div>
535536

536-
<div id="epss-chart-wrap" style="display:none;">
537+
<div id="epss-chart-container" style="display:none;">
537538
<div class="has-text-weight-bold tab-nested-div ml-1 mb-3">
538539
30-Day Trend
539540
</div>
540541
<div id="epss-chart" style="width:100%; height:260px;"></div>
541542
</div>
542543

543-
<div id="epss-history-table-wrap" class="mt-5" {% if not is_epss_tab_active %}style="display:none;"{% endif %}>
544+
<div id="epss-history-table-container" class="mt-5" {% if not 'epss_page' in request.GET %}style="display:none;"{% endif %}>
544545
<div class="has-text-weight-bold tab-nested-div ml-1 mb-3">
545546
EPSS History Table
546547
</div>
@@ -563,16 +564,16 @@
563564
</tbody>
564565
</table>
565566

566-
{% if epss_page_obj and epss_page_obj.has_other_pages %}
567+
{% if epss_pagination_obj and epss_pagination_obj.has_other_pages %}
567568
<nav class="pagination is-small is-centered mt-4" role="navigation" aria-label="pagination">
568-
{% if epss_page_obj.has_previous %}
569-
<a class="pagination-previous" href="?epss_page={{ epss_page_obj.previous_page_number }}">Newer</a>
569+
{% if epss_pagination_obj.has_previous %}
570+
<a class="pagination-previous" href="?epss_page={{ epss_pagination_obj.previous_page_number }}">Newer</a>
570571
{% else %}
571572
<a class="pagination-previous" disabled>Newer</a>
572573
{% endif %}
573574

574-
{% if epss_page_obj.has_next %}
575-
<a class="pagination-next" href="?epss_page={{ epss_page_obj.next_page_number }}">Older</a>
575+
{% if epss_pagination_obj.has_next %}
576+
<a class="pagination-next" href="?epss_page={{ epss_pagination_obj.next_page_number }}">Older</a>
576577
{% else %}
577578
<a class="pagination-next" disabled>Older</a>
578579
{% endif %}
@@ -746,7 +747,7 @@
746747
{% endif %}
747748
</div>
748749

749-
<!-- <div class="tab-div content" data-content="history">
750+
{% comment %} <div class="tab-div content" data-content="history">
750751
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
751752
<thead>
752753
<tr>
@@ -790,16 +791,19 @@
790791
</tr>
791792
{% endfor %}
792793
</table>
793-
</div> -->
794+
</div> {% endcomment %}
794795
</div>
795796
</div>
796797
</section>
797798
{% endif %}
798799

799-
<script src="{% static 'js/main.js' %}" crossorigin="anonymous"></script>
800+
<script src="{% static 'js/main.js' %}"></script>
800801

801-
<link rel="stylesheet" href="{% static 'css/billboard-3.18.0.min.css' %}">
802-
<script src="{% static 'js/billboard-3.18.0.pkgd.min.js' %}"></script>
802+
{% if 'epss_page' in request.GET or severities %}
803+
<link rel="stylesheet" href="{% static 'css/billboard.css' %}">
804+
<script src="{% static 'js/d3.js' %}"></script>
805+
<script src="{% static 'js/billboard.js' %}"></script>
806+
{% endif %}
803807

804808
{{ epss_history_data|json_script:"epss-history-data" }}
805809

vulnerabilities/tests/test_epss_history.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
110
import json
11+
import time
12+
import pytest
213
from pathlib import Path
314

415
from django.test import TestCase
516
from django.urls import reverse
617

7-
from vulnerabilities.models import AdvisoryAlias
18+
from vulnerabilities.models import EPSS
819
from vulnerabilities.models import AdvisoryReference
9-
from vulnerabilities.models import AdvisorySeverity
1020
from vulnerabilities.models import AdvisoryV2
21+
from vulnerabilities.pipelines.v2_importers.epss_importer_v2 import EPSSImporterPipeline
1122
from vulnerabilities.tests.util_tests import check_results_against_json
1223

1324

@@ -27,25 +38,30 @@ def setUp(self):
2738
advisory_id="CVE-2022-25204",
2839
url="https://epss.cyentia.com/epss_scores-current.csv.gz",
2940
is_latest=is_latest,
30-
datasource_id="test_epss",
31-
pipeline_id="test_epss",
41+
datasource_id=EPSS.identifier,
42+
pipeline_id=EPSSImporterPipeline.pipeline_id,
3243
unique_content_id=f"hash_{i}",
3344
)
3445

3546
advisory.references.add(ref_obj)
3647

37-
severity = data["severities"][0]
38-
advisory.severities.create(
39-
scoring_system=severity["system"],
40-
value=severity["value"],
41-
scoring_elements=severity["scoring_elements"],
42-
published_at=severity["published_at"],
43-
)
48+
for severity in data["severities"]:
49+
advisory.severities.create(
50+
scoring_system=severity["system"],
51+
value=severity["value"],
52+
scoring_elements=severity["scoring_elements"],
53+
published_at=severity["published_at"],
54+
)
4455

4556
if i == 0:
4657
self.advisory = advisory
4758

59+
@pytest.mark.ignore_template_errors()
4860
def test_epss_history_context(self):
61+
session = self.client.session
62+
session["altcha_verified_at"] = time.time()
63+
session.save()
64+
4965
response = self.client.get(reverse("advisory_details", kwargs={"avid": self.advisory.avid}))
5066
self.assertEqual(response.status_code, 200)
5167

@@ -56,3 +72,18 @@ def test_epss_history_context(self):
5672
]
5773

5874
check_results_against_json(history_data_for_json, expected_file)
75+
76+
def test_get_epss_history(self):
77+
from vulnerabilities.views import get_epss_history
78+
79+
epss_history_data, epss_pagination_obj = get_epss_history(self.advisory, 1)
80+
81+
expected_file = Path(__file__).parent / "test_data" / "epss" / "epss_history_expected.json"
82+
history_data_for_json = [
83+
{**record, "published_at": record["published_at"].isoformat()}
84+
for record in epss_history_data
85+
]
86+
87+
check_results_against_json(history_data_for_json, expected_file)
88+
self.assertEqual(epss_pagination_obj.number, 1)
89+
self.assertTrue(epss_pagination_obj.has_next())

vulnerabilities/views.py

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
import datetime
1011
import json
1112
import logging
1213
import time
@@ -40,7 +41,6 @@
4041
from django.urls import reverse_lazy
4142
from django.views import View
4243
from django.views import generic
43-
from django.views.decorators.http import require_safe
4444
from django.views.generic.detail import DetailView
4545
from django.views.generic.edit import FormMixin
4646
from django.views.generic.edit import FormView
@@ -589,6 +589,61 @@ def get_context_data(self, **kwargs):
589589
return context
590590

591591

592+
def get_epss_history(advisory, epss_page_param):
593+
"""
594+
Fetch and return EPSS history data for an advisory.
595+
"""
596+
597+
epss_history_data = []
598+
epss_pagination_obj = None
599+
600+
epss_scores_queryset = (
601+
models.AdvisorySeverity.objects.filter(
602+
advisories__advisory_id=advisory.advisory_id,
603+
advisories__datasource_id=EPSS.identifier,
604+
scoring_system=EPSS.identifier,
605+
published_at__isnull=False,
606+
)
607+
.annotate(pub_date=TruncDate("published_at"))
608+
.values("pub_date")
609+
.annotate(
610+
max_score=Max(Cast("value", FloatField())),
611+
max_percentile=Max(Cast("scoring_elements", FloatField())),
612+
)
613+
.order_by("-pub_date")
614+
)
615+
616+
paginator = Paginator(epss_scores_queryset, 30)
617+
epss_page = epss_page_param or 1
618+
epss_pagination_obj = paginator.get_page(epss_page)
619+
620+
if epss_pagination_obj.object_list:
621+
records = list(epss_pagination_obj.object_list)
622+
newest_date = records[0]["pub_date"]
623+
oldest_date = records[-1]["pub_date"]
624+
625+
records_by_date = {
626+
record["pub_date"]: {
627+
"score": record["max_score"],
628+
"percentile": record["max_percentile"],
629+
"published_at": record["pub_date"],
630+
}
631+
for record in records
632+
}
633+
634+
total_days = (newest_date - oldest_date).days
635+
all_dates = [oldest_date + datetime.timedelta(days=i) for i in range(total_days + 1)]
636+
637+
# Handle missing dates
638+
for date in all_dates:
639+
if date in records_by_date:
640+
epss_history_data.append(records_by_date[date])
641+
else:
642+
epss_history_data.append({"published_at": date, "score": None, "percentile": None})
643+
644+
return epss_history_data, epss_pagination_obj
645+
646+
592647
class AdvisoryDetails(VulnerableCodeDetailView):
593648
model = models.AdvisoryV2
594649
template_name = "advisory_detail.html"
@@ -749,47 +804,9 @@ def add_ssvc(ssvc):
749804

750805
context["ssvcs"] = ssvc_entries
751806

752-
# EPSS history
753-
cves = {
754-
alias_obj.alias
755-
for alias_obj in advisory.aliases.all()
756-
if alias_obj.alias.startswith("CVE-")
757-
}
758-
if advisory.advisory_id and advisory.advisory_id.startswith("CVE-"):
759-
cves.add(advisory.advisory_id)
760-
761-
if cves:
762-
epss_scores_queryset = (
763-
models.AdvisorySeverity.objects.filter(
764-
advisories__advisory_id__in=cves,
765-
scoring_system=EPSS.identifier,
766-
published_at__isnull=False,
767-
)
768-
.annotate(pub_date=TruncDate("published_at"))
769-
.values("pub_date")
770-
.annotate(
771-
max_score=Max(Cast("value", FloatField())),
772-
max_percentile=Max(Cast("scoring_elements", FloatField())),
773-
)
774-
.order_by("-pub_date")
775-
)
776-
777-
paginator = Paginator(epss_scores_queryset, 30)
778-
epss_page = self.request.GET.get("epss_page", 1)
779-
epss_page_obj = paginator.get_page(epss_page)
780-
781-
epss_history_data = [
782-
{
783-
"score": record["max_score"],
784-
"percentile": record["max_percentile"],
785-
"published_at": record["pub_date"],
786-
}
787-
for record in epss_page_obj.object_list
788-
]
789-
epss_history_data.reverse()
790-
else:
791-
epss_history_data = []
792-
epss_page_obj = None
807+
epss_history_data, epss_pagination_obj = get_epss_history(
808+
advisory, self.request.GET.get("epss_page")
809+
)
793810

794811
context.update(
795812
{
@@ -803,8 +820,7 @@ def add_ssvc(ssvc):
803820
"status": advisory.get_status_label,
804821
"epss_data": epss_data,
805822
"epss_history_data": epss_history_data,
806-
"epss_page_obj": epss_page_obj,
807-
"is_epss_tab_active": "epss_page" in self.request.GET,
823+
"epss_pagination_obj": epss_pagination_obj,
808824
}
809825
)
810826
return context

0 commit comments

Comments
 (0)