@@ -11,6 +11,7 @@ import (
11
11
12
12
"github.com/snyk/go-application-framework/pkg/apiclients/testapi"
13
13
"github.com/snyk/go-application-framework/pkg/configuration"
14
+ "github.com/snyk/go-application-framework/pkg/local_workflows/json_schemas"
14
15
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
15
16
)
16
17
@@ -247,9 +248,12 @@ func isOpenFinding() func(obj any) bool {
247
248
if ! ok {
248
249
return false
249
250
}
250
- // A finding is considered "open" if it has no suppression information.
251
- // A rejected suppression is not represented as a status, but by the absence of a suppression object.
252
- return finding .Attributes .Suppression == nil
251
+ // Treat findings as open unless they are explicitly ignored.
252
+ // Pending ignore approvals and other statuses remain visible as open issues.
253
+ if finding .Attributes == nil || finding .Attributes .Suppression == nil {
254
+ return true
255
+ }
256
+ return finding .Attributes .Suppression .Status != testapi .SuppressionStatusIgnored
253
257
}
254
258
}
255
259
@@ -275,14 +279,50 @@ func isIgnoredFinding() func(obj any) bool {
275
279
}
276
280
}
277
281
282
+ // isLicenseFinding returns true if the finding is a license finding.
283
+ func isLicenseFinding (finding testapi.FindingData ) bool {
284
+ if finding .Attributes != nil {
285
+ for _ , problem := range finding .Attributes .Problems {
286
+ disc , err := problem .Discriminator ()
287
+ if err == nil && disc == string (testapi .SnykLicense ) {
288
+ return true
289
+ }
290
+ }
291
+ }
292
+ return false
293
+ }
294
+
295
+ // isLicenseFindingFilter returns a filter function that checks if a finding is a license finding.
296
+ func isLicenseFindingFilter () func (obj any ) bool {
297
+ return func (obj any ) bool {
298
+ finding , ok := obj .(testapi.FindingData )
299
+ if ! ok {
300
+ return false
301
+ }
302
+ return isLicenseFinding (finding )
303
+ }
304
+ }
305
+
306
+ // isNotLicenseFindingFilter returns a function that checks if a finding is not a license finding.
307
+ func isNotLicenseFindingFilter () func (obj any ) bool {
308
+ return func (obj any ) bool {
309
+ finding , ok := obj .(testapi.FindingData )
310
+ if ! ok {
311
+ return true
312
+ }
313
+ isLicense := isLicenseFinding (finding )
314
+ return ! isLicense
315
+ }
316
+ }
317
+
278
318
// hasSuppression checks if a finding has any suppression.
279
319
func hasSuppression (finding testapi.FindingData ) bool {
280
- if finding .Attributes .Suppression == nil {
320
+ if finding .Attributes == nil || finding . Attributes .Suppression == nil {
281
321
return false
282
322
}
283
323
284
- // If a suppression object exists, the finding is considered suppressed (either ignored or pending ).
285
- return true
324
+ // Treat as suppressed unless the suppression status is "other" (treating as rejected ).
325
+ return finding . Attributes . Suppression . Status != testapi . SuppressionStatusOther
286
326
}
287
327
288
328
// getCliTemplateFuncMap returns the template function map for CLI rendering.
@@ -299,7 +339,8 @@ func getCliTemplateFuncMap(tmpl *template.Template) template.FuncMap {
299
339
fnMap ["divider" ] = RenderDivider
300
340
fnMap ["title" ] = RenderTitle
301
341
fnMap ["renderToString" ] = renderTemplateToString (tmpl )
302
- fnMap ["isLicenseFinding" ] = isLicenseFinding
342
+ fnMap ["isLicenseFindingFilter" ] = isLicenseFindingFilter
343
+ fnMap ["isNotLicenseFindingFilter" ] = isNotLicenseFindingFilter
303
344
fnMap ["isOpenFinding" ] = isOpenFinding
304
345
fnMap ["isPendingFinding" ] = isPendingFinding
305
346
fnMap ["isIgnoredFinding" ] = isIgnoredFinding
@@ -366,24 +407,109 @@ func getDefaultTemplateFuncMap(config configuration.Configuration, ri runtimeinf
366
407
defaultMap ["formatDatetime" ] = formatDatetime
367
408
defaultMap ["getSourceLocation" ] = getSourceLocation
368
409
defaultMap ["getFindingId" ] = getFindingID
369
- defaultMap ["hasPrefix" ] = strings .HasPrefix
370
410
defaultMap ["isLicenseFinding" ] = isLicenseFinding
411
+ defaultMap ["hasPrefix" ] = strings .HasPrefix
371
412
defaultMap ["constructDisplayPath" ] = constructDisplayPath (config )
413
+ defaultMap ["filterByIssueType" ] = filterByIssueType
414
+ defaultMap ["getSummaryResultsByIssueType" ] = getSummaryResultsByIssueType
415
+ defaultMap ["getIssueCountsTotal" ] = getIssueCountsTotal
416
+ defaultMap ["getIssueCountsOpen" ] = getIssueCountsOpen
417
+ defaultMap ["getIssueCountsIgnored" ] = getIssueCountsIgnored
372
418
373
419
return defaultMap
374
420
}
375
421
376
- // isLicenseFinding returns true if the finding is a license finding.
377
- func isLicenseFinding (finding testapi.FindingData ) bool {
378
- if finding .Attributes != nil {
379
- for _ , problem := range finding .Attributes .Problems {
380
- disc , err := problem .Discriminator ()
381
- if err == nil && disc == string (testapi .SnykLicense ) {
382
- return true
383
- }
422
+ func getIssueCountsTotal (results []json_schemas.TestSummaryResult ) (total int ) {
423
+ for _ , res := range results {
424
+ total += res .Total
425
+ }
426
+ return total
427
+ }
428
+
429
+ func getIssueCountsOpen (results []json_schemas.TestSummaryResult ) (open int ) {
430
+ for _ , res := range results {
431
+ open += res .Open
432
+ }
433
+ return open
434
+ }
435
+
436
+ func getIssueCountsIgnored (results []json_schemas.TestSummaryResult ) (ignored int ) {
437
+ for _ , res := range results {
438
+ ignored += res .Ignored
439
+ }
440
+ return ignored
441
+ }
442
+
443
+ // filterByIssueType filters a list of finding summary results by issue type.
444
+ func filterByIssueType (issueType string , summary * json_schemas.TestSummary ) []json_schemas.TestSummaryResult {
445
+ if summary .Type == issueType {
446
+ return summary .Results
447
+ }
448
+ return []json_schemas.TestSummaryResult {}
449
+ }
450
+
451
+ // getSummaryResultsByIssueType computes summary results for a specific issue type from findings.
452
+ // issueType can be "vulnerability" or "license".
453
+ func getSummaryResultsByIssueType (issueType string , findings []testapi.FindingData , orderAsc []string ) []json_schemas.TestSummaryResult {
454
+ if len (findings ) == 0 {
455
+ return []json_schemas.TestSummaryResult {}
456
+ }
457
+
458
+ // Prepare counters by severity
459
+ totalBySeverity := map [string ]int {}
460
+ openBySeverity := map [string ]int {}
461
+ ignoredBySeverity := map [string ]int {}
462
+
463
+ for _ , f := range findings {
464
+ // Determine category membership
465
+ isLicense := isLicenseFinding (f )
466
+ if issueType == "license" && ! isLicense {
467
+ continue
468
+ }
469
+ if issueType == "vulnerability" && isLicense {
470
+ continue
471
+ }
472
+
473
+ severity := getFieldValueFrom (f , "Attributes.Rating.Severity" )
474
+ if severity == "" {
475
+ // Skip if we cannot determine severity
476
+ continue
477
+ }
478
+
479
+ totalBySeverity [severity ]++
480
+
481
+ // Determine suppression state: only explicit "ignored" should reduce open counts.
482
+ isIgnored := false
483
+ isOpen := true
484
+ if f .Attributes != nil && f .Attributes .Suppression != nil {
485
+ isIgnored = f .Attributes .Suppression .Status == testapi .SuppressionStatusIgnored
486
+ isOpen = ! isIgnored
487
+ }
488
+
489
+ if isOpen {
490
+ openBySeverity [severity ]++
491
+ }
492
+ if isIgnored {
493
+ ignoredBySeverity [severity ]++
384
494
}
385
495
}
386
- return false
496
+
497
+ // Build results in the provided order, but only include severities that appeared
498
+ results := make ([]json_schemas.TestSummaryResult , 0 , len (totalBySeverity ))
499
+ for _ , sev := range orderAsc {
500
+ total := totalBySeverity [sev ]
501
+ if total == 0 {
502
+ continue
503
+ }
504
+ results = append (results , json_schemas.TestSummaryResult {
505
+ Severity : sev ,
506
+ Total : total ,
507
+ Open : openBySeverity [sev ],
508
+ Ignored : ignoredBySeverity [sev ],
509
+ })
510
+ }
511
+
512
+ return results
387
513
}
388
514
389
515
// reverse reverses the order of elements in a slice.
0 commit comments