Skip to content

Commit 77d6bdd

Browse files
findings list: support ordering by more fields (#13300)
* findings list: support ordering by more fields * remove duplicate sorting, fix sla sorting * fix bulk edit tag field
1 parent 8e400a2 commit 77d6bdd

3 files changed

Lines changed: 49 additions & 21 deletions

File tree

dojo/filters.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,7 +1624,9 @@ class ApiFindingFilter(DojoFilter):
16241624
("is_mitigated", "is_mitigated"),
16251625
("numerical_severity", "numerical_severity"),
16261626
("out_of_scope", "out_of_scope"),
1627+
("planned_remediation_date", "planned_remediation_date"),
16271628
("severity", "severity"),
1629+
("sla_expiration_date", "sla_expiration_date"),
16281630
("reviewers", "reviewers"),
16291631
("static_finding", "static_finding"),
16301632
("test__engagement__product__name", "test__engagement__product__name"),
@@ -1782,10 +1784,12 @@ class FindingFilterHelper(FilterSet):
17821784
("risk_acceptance__created__date",
17831785
"risk_acceptance__created__date"),
17841786
("last_reviewed", "last_reviewed"),
1787+
("planned_remediation_date", "planned_remediation_date"),
17851788
("title", "title"),
17861789
("test__engagement__product__name",
17871790
"test__engagement__product__name"),
17881791
("service", "service"),
1792+
("sla_age_days", "sla_age_days"),
17891793
("epss_score", "epss_score"),
17901794
("epss_percentile", "epss_percentile"),
17911795
("known_exploited", "known_exploited"),
@@ -1805,6 +1809,8 @@ class FindingFilterHelper(FilterSet):
18051809
"known_exploited": "Known Exploited",
18061810
"ransomware_used": "Ransomware Used",
18071811
"kev_date": "Date added to KEV",
1812+
"sla_age_days": "SLA age (days)",
1813+
"planned_remediation_date": "Planned Remediation",
18081814
},
18091815
)
18101816

dojo/finding/views.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from django.core import serializers
1515
from django.core.exceptions import PermissionDenied, ValidationError
1616
from django.db import models
17-
from django.db.models import QuerySet
18-
from django.db.models.functions import Length
17+
from django.db.models import F, QuerySet
18+
from django.db.models.functions import Coalesce, ExtractDay, Length, TruncDate
1919
from django.db.models.query import Prefetch
2020
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse, StreamingHttpResponse
2121
from django.shortcuts import get_object_or_404, render
@@ -274,7 +274,13 @@ def filter_findings_by_form(self, request: HttpRequest, findings: QuerySet[Findi
274274
)
275275

276276
def get_filtered_findings(self):
277-
findings = get_authorized_findings(Permissions.Finding_View).order_by(self.get_order_by())
277+
findings = get_authorized_findings(Permissions.Finding_View)
278+
# Annotate computed SLA age in days: sla_expiration_date - (sla_start_date or date)
279+
findings = findings.annotate(
280+
sla_age_days=ExtractDay(
281+
F("sla_expiration_date") - Coalesce(F("sla_start_date"), TruncDate("created")),
282+
),
283+
).order_by(self.get_order_by())
278284
findings = self.filter_findings_by_object(findings)
279285
return self.filter_findings_by_filter_name(findings)
280286

dojo/templates/dojo/findings_list_snippet.html

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ <h3 class="has-filters">
5757
<div class="clearfix">{% include "dojo/paging_snippet.html" with page=findings page_size=True %}</div>
5858
{% if not product_tab or product_tab and product_tab.product|has_object_permission:"Finding_Edit" %}
5959
<div class="dropdown hidden" style="padding-bottom: 5px;" id="bulk_edit_menu">
60+
{{ bulk_edit_form.media.css }}
61+
{{ bulk_edit_form.media.js }}
6062
{% if not product_tab or product_tab and product_tab.product|has_object_permission:"Finding_Edit" %}
6163
<button class="btn btn-info btn-sm btn-primary dropdown-toggle"
6264
type="button"
@@ -242,18 +244,8 @@ <h3 class="has-filters">
242244
<br />
243245
{% endif %}
244246
<label style="display: block">{% trans "Notes" %}</label>
245-
{% comment %}
246-
Quick hack to make bulk edit work without refactoring the whole bulk edit form
247-
{{ bulk_edit_form.media.css }}
248-
{{ bulk_edit_form.media.js }}
249-
{% endcomment %}
250247
{{ bulk_edit_form.notes }}
251248
<label style="display: block">{% trans "Tags" %}</label>
252-
{% comment %}
253-
Quick hack to make bulk edit work without refactoring the whole bulk edit form
254-
{{ bulk_edit_form.media.css }}
255-
{{ bulk_edit_form.media.js }}
256-
{% endcomment %}
257249
{{ bulk_edit_form.tags }}
258250
{% if bulk_edit_form.disclaimer %}
259251
<div style="background-color:#DADCE2; border:1px #003333; padding:.3em; margin:.1em; ">
@@ -318,14 +310,14 @@ <h3 class="has-filters">
318310
{% endif %}
319311
<th></th>
320312
<th class="nowrap centered severity-sort" scope="col">
321-
{% trans "Severity" %}
313+
{% dojo_sort request 'Severity' 'numerical_severity' %}
322314
</th>
323315
<th class="nowrap" scope="col">
324316
{% comment %} The display field is translated in the function. No need to translate here as well{% endcomment %}
325317
{% dojo_sort request 'Name' 'title' %}
326318
</th>
327319
<th scope="col">
328-
{% trans "CWE" %}
320+
{% dojo_sort request 'CWE' 'cwe' %}
329321
</th>
330322
<th scope="col">
331323
{% trans "Vulnerability Id" %}
@@ -355,11 +347,11 @@ <h3 class="has-filters">
355347
{% endif %}
356348
</th>
357349
<th class="nowrap" scope="col">
358-
{% trans "Age" %}
350+
{% dojo_sort request 'Age' 'date' %}
359351
</th>
360352
{% if system_settings.enable_finding_sla %}
361353
<th scope="col">
362-
{% trans "SLA" %}
354+
{% dojo_sort request 'SLA' 'sla_age_days' %}
363355
</th>
364356
{% endif %}
365357
<th scope="col">
@@ -396,10 +388,10 @@ <h3 class="has-filters">
396388
</th>
397389
{% endif %}
398390
<th scope="col">
399-
{% trans "Service" %}
391+
{% dojo_sort request 'Service' 'service' %}
400392
</th>
401-
<th scope="col">
402-
{% trans "Planned Remediation" %}
393+
<th scope="col">
394+
{% dojo_sort request 'Planned Remediation' 'planned_remediation_date' %}
403395
</th>
404396
{% if filter_name != 'Closed' %}
405397
<th scope="col">
@@ -800,7 +792,7 @@ <h3 class="has-filters">
800792
return data;
801793
}},
802794
{ "data": "finding_age" },
803-
{% if system_settings.enable_finding_sla %}
795+
{% if system_settings.enable_finding_sla %}
804796
{ "data": "finding_sla", "type": "num", render: function (data, type, row, meta) {
805797
if(type === 'sort') {
806798
var api = new $.fn.dataTable.Api(meta.settings);
@@ -867,6 +859,25 @@ <h3 class="has-filters">
867859
var fileDated = 'Findings_List_' + date;
868860
var columns = datatables_columns;
869861

862+
// Determine columns that should be server-sorted via dojo_sort (disable client sorting for these)
863+
var serverSortDataKeys = new Set([
864+
'severity',
865+
'finding',
866+
'cwe',
867+
'found_date',
868+
'finding_age',
869+
'finding_sla',
870+
'product',
871+
'service',
872+
'planned_remediation_date'
873+
]);
874+
var serverSortTargets = [];
875+
for (var i = 0; i < columns.length; i++) {
876+
if (serverSortDataKeys.has(columns[i]["data"])) {
877+
serverSortTargets.push(i);
878+
}
879+
}
880+
870881
// Filter the list of items to display based on what is shown.
871882
var disallowed_entries = new Set(["checkbox", "action"]);
872883
var data_column_list = [];
@@ -899,8 +910,13 @@ <h3 class="has-filters">
899910
},
900911
colReorder: true,
901912
"columns": columns,
913+
ordering: true,
902914
order: [],
903915
columnDefs: [
916+
{
917+
orderable: false,
918+
targets: serverSortTargets
919+
},
904920
{
905921
"orderable": false,
906922
"targets": [0, 1]

0 commit comments

Comments
 (0)