Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support severity threshold #155

Merged
merged 11 commits into from
May 8, 2024
22 changes: 19 additions & 3 deletions internal/presenters/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,16 @@ func RenderTip(str string) string {
return fmt.Sprintf("\n💡 Tip\n\n%s", str)
}

func RenderSummary(summary *json_schemas.TestSummary, orgName string, testPath string) (string, error) {
func reverseSlice(original []string) []string {
reversed := make([]string, 0, len(original))
for i := len(original) - 1; i >= 0; i-- {
reversed = append(reversed, original[i])
}

return reversed
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Nice optimisation


func RenderSummary(summary *json_schemas.TestSummary, orgName string, testPath string, severityMinLevel string) (string, error) {
var buff bytes.Buffer
var summaryTemplate = template.Must(template.New("summary").Parse(`Test Summary

Expand All @@ -120,11 +129,18 @@ func RenderSummary(summary *json_schemas.TestSummary, orgName string, testPath s
openIssueLabelledCount := ""
ignoredIssueLabelledCount := ""

slices.Reverse(summary.SeverityOrderAsc)
minLevelPointer := slices.Index(summary.SeverityOrderAsc, severityMinLevel)
reversedSlice := reverseSlice(summary.SeverityOrderAsc)
PeterSchafer marked this conversation as resolved.
Show resolved Hide resolved

for _, severity := range summary.SeverityOrderAsc {
for _, severity := range reversedSlice {
for _, result := range summary.Results {
satisfyMinLevel := slices.Index(summary.SeverityOrderAsc, result.Severity) >= minLevelPointer
if result.Severity == severity {
if !satisfyMinLevel {
openIssueLabelledCount += renderInSeverityColor(severity, fmt.Sprintf(" %d %s ", 0, strings.ToUpper(severity)))
ignoredIssueLabelledCount += renderInSeverityColor(severity, fmt.Sprintf(" %d %s ", 0, strings.ToUpper(severity)))
continue
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Design decision will be needed on whether we want to show 0 LOW when in fact there were Low Issues but they have been filtered out by --severity-threshold setting.

}
totalIssueCount += result.Total
openIssueCount += result.Open
ignoredIssueCount += result.Ignored
Expand Down
45 changes: 34 additions & 11 deletions internal/presenters/presenter_sarif_results_pretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ type FindingProperty struct {
}

type Presenter struct {
ShowIgnored bool
ShowOpen bool
Input sarif.SarifDocument
OrgName string
TestPath string
ShowIgnored bool
ShowOpen bool
Input sarif.SarifDocument
OrgName string
TestPath string
SeverityMinLevel string
}

type PresenterOption func(*Presenter)
Expand Down Expand Up @@ -57,13 +58,20 @@ func WithTestPath(testPath string) PresenterOption {
}
}

func WithSeverityThershold(severityMinLevel string) PresenterOption {
return func(p *Presenter) {
p.SeverityMinLevel = severityMinLevel
}
}

func SarifTestResults(sarifDocument sarif.SarifDocument, options ...PresenterOption) *Presenter {
p := &Presenter{
ShowIgnored: false,
ShowOpen: true,
Input: sarifDocument,
OrgName: "",
TestPath: "",
ShowIgnored: false,
ShowOpen: true,
Input: sarifDocument,
OrgName: "",
TestPath: "",
SeverityMinLevel: "low",
}

for _, option := range options {
Expand All @@ -73,16 +81,31 @@ func SarifTestResults(sarifDocument sarif.SarifDocument, options ...PresenterOpt
return p
}

func FilterFindingsBySeverity(findings []Finding, minLevel string, severityOrder []string) []Finding {
var filteredFindings []Finding
minLevelPointer := slices.Index(severityOrder, minLevel)
for _, finding := range findings {
level := slices.Index(severityOrder, finding.Severity)
if level >= minLevelPointer {
filteredFindings = append(filteredFindings, finding)
}
}
return filteredFindings
}

func (p *Presenter) Render() (string, error) {
summaryData := sarif_utils.CreateCodeSummary(&p.Input)
findings :=
SortFindings(convertSarifToFindingsList(p.Input), summaryData.SeverityOrderAsc)
summaryOutput, err := RenderSummary(summaryData, p.OrgName, p.TestPath)
summaryOutput, err := RenderSummary(summaryData, p.OrgName, p.TestPath, p.SeverityMinLevel)

if err != nil {
return "", err
}

// Filter findings based on severity
findings = FilterFindingsBySeverity(findings, p.SeverityMinLevel, summaryData.SeverityOrderAsc)

str := strings.Join([]string{
"",
renderBold(fmt.Sprintf("Testing %s ...", p.TestPath)),
Expand Down
7 changes: 4 additions & 3 deletions pkg/configuration/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const (
FF_CODE_CONSISTENT_IGNORES string = "internal_snyk_code_ignores_enabled"

// flags
FLAG_EXPERIMENTAL string = "experimental"
FLAG_INCLUDE_IGNORES string = "include-ignores"
FLAG_ONLY_IGNORES string = "only-ignores"
FLAG_EXPERIMENTAL string = "experimental"
FLAG_INCLUDE_IGNORES string = "include-ignores"
FLAG_ONLY_IGNORES string = "only-ignores"
FLAG_SEVERITY_THRESHOLD string = "severity-threshold"
)
6 changes: 4 additions & 2 deletions pkg/local_workflows/output_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func InitOutputWorkflow(engine workflow.Engine) error {
outputConfig.String(OUTPUT_CONFIG_KEY_SARIF_FILE, "", "Write sarif output to file")
outputConfig.Bool(configuration.FLAG_INCLUDE_IGNORES, false, "Include ignored findings in the output")
outputConfig.Bool(configuration.FLAG_ONLY_IGNORES, false, "Hide open issues in the output")
outputConfig.String(configuration.FLAG_SEVERITY_THRESHOLD, "low", "Severity threshold for findings to be included in the output")

entry, err := engine.Register(WORKFLOWID_OUTPUT_WORKFLOW, workflow.ConfigurationOptionsFromFlagset(outputConfig), outputWorkflowEntryPointImpl)
entry.SetVisibility(false)
Expand Down Expand Up @@ -118,7 +119,7 @@ func handleContentTypeJson(config configuration.Configuration, input []workflow.
// yes: use presenter
// no: print json to cmd
if showToHuman && input[i].GetContentType() == content_type.SARIF_JSON {
humanReadanbleSarifOutput(config, input, i, outputDestination, debugLogger, singleData)
humanReadableSarifOutput(config, input, i, outputDestination, debugLogger, singleData)
} else {
// if json data is processed but non of the json related output configuration is specified, default printJsonToCmd is enabled
if !printJsonToCmd && !writeToFile {
Expand Down Expand Up @@ -151,7 +152,7 @@ func jsonWriteToFile(debugLogger *zerolog.Logger, input []workflow.Data, i int,
return nil
}

func humanReadanbleSarifOutput(config configuration.Configuration, input []workflow.Data, i int, outputDestination iUtils.OutputDestination, debugLogger *zerolog.Logger, singleData []byte) {
func humanReadableSarifOutput(config configuration.Configuration, input []workflow.Data, i int, outputDestination iUtils.OutputDestination, debugLogger *zerolog.Logger, singleData []byte) {
includeOpenFindings := !config.GetBool(configuration.FLAG_ONLY_IGNORES)
includeIgnoredFindings := config.GetBool(configuration.FLAG_INCLUDE_IGNORES) || config.GetBool(configuration.FLAG_ONLY_IGNORES)

Expand All @@ -167,6 +168,7 @@ func humanReadanbleSarifOutput(config configuration.Configuration, input []workf
presenters.WithTestPath(input[i].GetContentLocation()),
presenters.WithIgnored(includeIgnoredFindings),
presenters.WithOpen(includeOpenFindings),
presenters.WithSeverityThershold(config.GetString(configuration.FLAG_SEVERITY_THRESHOLD)),
)

humanReadableResult, err := p.Render()
Expand Down
29 changes: 28 additions & 1 deletion pkg/local_workflows/output_workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func Test_Output_outputWorkflowEntryPoint(t *testing.T) {
// mock assertions
outputDestination.EXPECT().Println(gomock.Any()).Do(func(str string) {
assert.Contains(t, str, "Total issues: 5")
assert.Contains(t, str, "✗ [MEDIUM]")
assert.NotContains(t, str, "Ignored rule")
}).Times(1)

Expand Down Expand Up @@ -274,9 +275,34 @@ func Test_Output_outputWorkflowEntryPoint(t *testing.T) {
assert.Equal(t, []workflow.Data{}, output)
})

t.Run("should print human readable output for sarif data only showing ignored data", func(t *testing.T) {
t.Run("should print human readable output excluding medium severity issues", func(t *testing.T) {
input := getSarifInput()
rawSarif, err := json.Marshal(input)
assert.Nil(t, err)

workflowIdentifier := workflow.NewTypeIdentifier(WORKFLOWID_OUTPUT_WORKFLOW, "output")
sarifData := workflow.NewData(workflowIdentifier, content_type.SARIF_JSON, rawSarif)
sarifData.SetContentLocation("/mypath")

// mock assertions
outputDestination.EXPECT().Println(gomock.Any()).Do(func(str string) {
assert.Contains(t, str, "Open issues: 2")
assert.Contains(t, str, "0 MEDIUM")
assert.NotContains(t, str, "✗ [MEDIUM]")
}).Times(1)

config.Set(configuration.FLAG_SEVERITY_THRESHOLD, "high")

// execute
output, err := outputWorkflowEntryPoint(invocationContextMock, []workflow.Data{sarifData}, outputDestination)

// assert
assert.Nil(t, err)
assert.Equal(t, []workflow.Data{}, output)
})

t.Run("should print human readable output for sarif data only showing ignored data", func(t *testing.T) {
input := getSarifInput()
rawSarif, err := json.Marshal(input)
assert.Nil(t, err)

Expand All @@ -292,6 +318,7 @@ func Test_Output_outputWorkflowEntryPoint(t *testing.T) {
}).Times(1)

config.Set(configuration.FLAG_ONLY_IGNORES, true)
config.Set(configuration.FLAG_SEVERITY_THRESHOLD, "low")

// execute
output, err := outputWorkflowEntryPoint(invocationContextMock, []workflow.Data{sarifData}, outputDestination)
Expand Down