Skip to content

Commit b4f510e

Browse files
authored
CM-27046 - Improve UX of SCA table with results by grouping and sorting data (#180)
1 parent eb488c4 commit b4f510e

File tree

1 file changed

+60
-14
lines changed

1 file changed

+60
-14
lines changed

cycode/cli/printers/tables/sca_table_printer.py

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import click
55

66
from cycode.cli.consts import LICENSE_COMPLIANCE_POLICY_ID, PACKAGE_VULNERABILITY_POLICY_ID
7-
from cycode.cli.models import Detection
7+
from cycode.cli.models import Detection, Severity
88
from cycode.cli.printers.tables.table import Table
99
from cycode.cli.printers.tables.table_models import ColumnInfoBuilder, ColumnWidths
1010
from cycode.cli.printers.tables.table_printer_base import TablePrinterBase
@@ -19,21 +19,21 @@
1919
# Building must have strict order. Represents the order of the columns in the table (from left to right)
2020
SEVERITY_COLUMN = column_builder.build(name='Severity')
2121
REPOSITORY_COLUMN = column_builder.build(name='Repository')
22-
23-
FILE_PATH_COLUMN = column_builder.build(name='File Path')
22+
CODE_PROJECT_COLUMN = column_builder.build(name='Code Project') # File path to manifest file
2423
ECOSYSTEM_COLUMN = column_builder.build(name='Ecosystem')
25-
DEPENDENCY_NAME_COLUMN = column_builder.build(name='Dependency Name')
26-
DIRECT_DEPENDENCY_COLUMN = column_builder.build(name='Direct Dependency')
27-
DEVELOPMENT_DEPENDENCY_COLUMN = column_builder.build(name='Development Dependency')
28-
DEPENDENCY_PATHS_COLUMN = column_builder.build(name='Dependency Paths')
29-
24+
PACKAGE_COLUMN = column_builder.build(name='Package')
3025
CVE_COLUMNS = column_builder.build(name='CVE')
26+
DEPENDENCY_PATHS_COLUMN = column_builder.build(name='Dependency Paths')
3127
UPGRADE_COLUMN = column_builder.build(name='Upgrade')
3228
LICENSE_COLUMN = column_builder.build(name='License')
29+
DIRECT_DEPENDENCY_COLUMN = column_builder.build(name='Direct Dependency')
30+
DEVELOPMENT_DEPENDENCY_COLUMN = column_builder.build(name='Development Dependency')
31+
3332

3433
COLUMN_WIDTHS_CONFIG: ColumnWidths = {
3534
REPOSITORY_COLUMN: 2,
36-
FILE_PATH_COLUMN: 3,
35+
CODE_PROJECT_COLUMN: 2,
36+
PACKAGE_COLUMN: 3,
3737
CVE_COLUMNS: 5,
3838
UPGRADE_COLUMN: 3,
3939
LICENSE_COLUMN: 2,
@@ -47,7 +47,7 @@ def _print_results(self, local_scan_results: List['LocalScanResult']) -> None:
4747
table = self._get_table(policy_id)
4848
table.set_cols_width(COLUMN_WIDTHS_CONFIG)
4949

50-
for detection in detections:
50+
for detection in self._sort_and_group_detections(detections):
5151
self._enrich_table_with_values(table, detection)
5252

5353
self._print_summary_issues(len(detections), self._get_title(policy_id))
@@ -64,6 +64,52 @@ def _get_title(policy_id: str) -> str:
6464

6565
return 'Unknown'
6666

67+
@staticmethod
68+
def __group_by(detections: List[Detection], details_field_name: str) -> Dict[str, List[Detection]]:
69+
grouped = defaultdict(list)
70+
for detection in detections:
71+
grouped[detection.detection_details.get(details_field_name)].append(detection)
72+
return grouped
73+
74+
@staticmethod
75+
def __severity_sort_key(detection: Detection) -> int:
76+
severity = detection.detection_details.get('advisory_severity')
77+
return Severity.try_get_value(severity)
78+
79+
def _sort_detections_by_severity(self, detections: List[Detection]) -> List[Detection]:
80+
return sorted(detections, key=self.__severity_sort_key, reverse=True)
81+
82+
@staticmethod
83+
def __package_sort_key(detection: Detection) -> int:
84+
return detection.detection_details.get('package_name')
85+
86+
def _sort_detections_by_package(self, detections: List[Detection]) -> List[Detection]:
87+
return sorted(detections, key=self.__package_sort_key)
88+
89+
def _sort_and_group_detections(self, detections: List[Detection]) -> List[Detection]:
90+
"""Sort detections by severity and group by repository, code project and package name.
91+
92+
Note:
93+
Code Project is path to manifest file.
94+
95+
Grouping by code projects also groups by ecosystem.
96+
Because manifest files are unique per ecosystem.
97+
"""
98+
result = []
99+
100+
# we sort detections by package name to make persist output order
101+
sorted_detections = self._sort_detections_by_package(detections)
102+
103+
grouped_by_repository = self.__group_by(sorted_detections, 'repository_name')
104+
for repository_group in grouped_by_repository.values():
105+
grouped_by_code_project = self.__group_by(repository_group, 'file_name')
106+
for code_project_group in grouped_by_code_project.values():
107+
grouped_by_package = self.__group_by(code_project_group, 'package_name')
108+
for package_group in grouped_by_package.values():
109+
result.extend(self._sort_detections_by_severity(package_group))
110+
111+
return result
112+
67113
def _get_table(self, policy_id: str) -> Table:
68114
table = Table()
69115

@@ -77,9 +123,9 @@ def _get_table(self, policy_id: str) -> Table:
77123
if self._is_git_repository():
78124
table.add(REPOSITORY_COLUMN)
79125

80-
table.add(FILE_PATH_COLUMN)
126+
table.add(CODE_PROJECT_COLUMN)
81127
table.add(ECOSYSTEM_COLUMN)
82-
table.add(DEPENDENCY_NAME_COLUMN)
128+
table.add(PACKAGE_COLUMN)
83129
table.add(DIRECT_DEPENDENCY_COLUMN)
84130
table.add(DEVELOPMENT_DEPENDENCY_COLUMN)
85131
table.add(DEPENDENCY_PATHS_COLUMN)
@@ -93,9 +139,9 @@ def _enrich_table_with_values(table: Table, detection: Detection) -> None:
93139
table.set(SEVERITY_COLUMN, detection_details.get('advisory_severity'))
94140
table.set(REPOSITORY_COLUMN, detection_details.get('repository_name'))
95141

96-
table.set(FILE_PATH_COLUMN, detection_details.get('file_name'))
142+
table.set(CODE_PROJECT_COLUMN, detection_details.get('file_name'))
97143
table.set(ECOSYSTEM_COLUMN, detection_details.get('ecosystem'))
98-
table.set(DEPENDENCY_NAME_COLUMN, detection_details.get('package_name'))
144+
table.set(PACKAGE_COLUMN, detection_details.get('package_name'))
99145
table.set(DIRECT_DEPENDENCY_COLUMN, detection_details.get('is_direct_dependency_str'))
100146
table.set(DEVELOPMENT_DEPENDENCY_COLUMN, detection_details.get('is_dev_dependency_str'))
101147

0 commit comments

Comments
 (0)