|
150 | 150 | from vulnerabilities.models import Vulnerability |
151 | 151 | from vulnerabilities.models import VulnerabilityAnalysis |
152 | 152 | from vulnerabilities.models import get_risk_level |
| 153 | +from vulnerabilities.triage.models import TriageAction |
| 154 | +from vulnerabilities.triage.models import TriageRecord |
| 155 | +from vulnerabilities.triage.rules import RULE_REGISTRY as TRIAGE_RULE_REGISTRY |
153 | 156 |
|
154 | 157 |
|
155 | 158 | class BaseProductViewMixin: |
@@ -295,6 +298,7 @@ class ProductDetailsView( |
295 | 298 | ], |
296 | 299 | }, |
297 | 300 | "vulnerabilities": {}, |
| 301 | + "triage": {}, |
298 | 302 | "codebase": { |
299 | 303 | "fields": [ |
300 | 304 | "path", |
@@ -666,6 +670,34 @@ def tab_vulnerabilities(self): |
666 | 670 | "fields": [(None, tab_context, None, template)], |
667 | 671 | } |
668 | 672 |
|
| 673 | + def tab_triage(self): |
| 674 | + product = self.object |
| 675 | + |
| 676 | + triage_count = ( |
| 677 | + TriageRecord.objects.filter(product_package__product=product).primary_actions().count() |
| 678 | + ) |
| 679 | + |
| 680 | + badge_class = "bg-primary-subtle text-primary-emphasis" |
| 681 | + if triage_count > 0: |
| 682 | + badge_class = "bg-warning-subtle text-warning-emphasis" |
| 683 | + |
| 684 | + label = f'Triage <span class="badge {badge_class}">{triage_count}</span>' |
| 685 | + |
| 686 | + tab_view_url = product.get_url("tab_triage") |
| 687 | + if full_query_string := self.request.META["QUERY_STRING"]: |
| 688 | + tab_view_url += f"?{full_query_string}" |
| 689 | + |
| 690 | + template = "tabs/tab_async_loader.html" |
| 691 | + tab_context = { |
| 692 | + "tab_view_url": tab_view_url, |
| 693 | + "tab_object_name": "triage actions", |
| 694 | + } |
| 695 | + |
| 696 | + return { |
| 697 | + "label": mark_safe(label), |
| 698 | + "fields": [(None, tab_context, None, template)], |
| 699 | + } |
| 700 | + |
669 | 701 | def tab_codebase(self): |
670 | 702 | codebaseresources_count = self.object.codebaseresources.count() |
671 | 703 | if not codebaseresources_count: |
@@ -1328,6 +1360,65 @@ def get_context_data(self, **kwargs): |
1328 | 1360 | return context_data |
1329 | 1361 |
|
1330 | 1362 |
|
| 1363 | +class ProductTabTriageView( |
| 1364 | + LoginRequiredMixin, |
| 1365 | + BaseProductViewMixin, |
| 1366 | + PaginationMixin, |
| 1367 | + TabContentView, |
| 1368 | +): |
| 1369 | + template_name = "product_portfolio/tabs/tab_triage.html" |
| 1370 | + paginate_by = 50 |
| 1371 | + query_dict_page_param = "triage-page" |
| 1372 | + tab_id = "triage" |
| 1373 | + |
| 1374 | + def get_context_data(self, **kwargs): |
| 1375 | + product = self.object |
| 1376 | + |
| 1377 | + action_labels = dict(TriageAction.choices) |
| 1378 | + rule_labels = { |
| 1379 | + rule_type: handler.label for rule_type, handler in TRIAGE_RULE_REGISTRY.items() |
| 1380 | + } |
| 1381 | + |
| 1382 | + triage_qs = ( |
| 1383 | + TriageRecord.objects.filter(product_package__product=product) |
| 1384 | + .primary_actions() |
| 1385 | + .select_related( |
| 1386 | + "product_package__package", |
| 1387 | + "ruleset", |
| 1388 | + ) |
| 1389 | + ) |
| 1390 | + total_count = triage_qs.count() |
| 1391 | + |
| 1392 | + paginator = Paginator(triage_qs, self.paginate_by) |
| 1393 | + page_number = self.request.GET.get(self.query_dict_page_param) |
| 1394 | + page_obj = paginator.get_page(page_number) |
| 1395 | + |
| 1396 | + for record in page_obj.object_list: |
| 1397 | + record.action_label = action_labels.get(record.action, record.action) |
| 1398 | + record.rule_labels = [ |
| 1399 | + rule_labels.get(rule_type, rule_type) for rule_type in record.matched_rules |
| 1400 | + ] |
| 1401 | + |
| 1402 | + context_data = super().get_context_data(**kwargs) |
| 1403 | + context_data.update( |
| 1404 | + { |
| 1405 | + "page_obj": page_obj, |
| 1406 | + "total_count": total_count, |
| 1407 | + } |
| 1408 | + ) |
| 1409 | + |
| 1410 | + if page_obj: |
| 1411 | + previous_url, next_url = self.get_previous_next(page_obj) |
| 1412 | + context_data.update( |
| 1413 | + { |
| 1414 | + "previous_url": (previous_url or "") + f"#{self.tab_id}", |
| 1415 | + "next_url": (next_url or "") + f"#{self.tab_id}", |
| 1416 | + } |
| 1417 | + ) |
| 1418 | + |
| 1419 | + return context_data |
| 1420 | + |
| 1421 | + |
1331 | 1422 | class ProductTabActivityView( |
1332 | 1423 | LoginRequiredMixin, |
1333 | 1424 | BaseProductViewMixin, |
|
0 commit comments