44import  click 
55
66from  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 
88from  cycode .cli .printers .tables .table  import  Table 
99from  cycode .cli .printers .tables .table_models  import  ColumnInfoBuilder , ColumnWidths 
1010from  cycode .cli .printers .tables .table_printer_base  import  TablePrinterBase 
1919# Building must have strict order. Represents the order of the columns in the table (from left to right) 
2020SEVERITY_COLUMN  =  column_builder .build (name = 'Severity' )
2121REPOSITORY_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 
2423ECOSYSTEM_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' )
3025CVE_COLUMNS  =  column_builder .build (name = 'CVE' )
26+ DEPENDENCY_PATHS_COLUMN  =  column_builder .build (name = 'Dependency Paths' )
3127UPGRADE_COLUMN  =  column_builder .build (name = 'Upgrade' )
3228LICENSE_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
3433COLUMN_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