Repository: safedep/vet
Pull request #281 (2024-11-18 10:10:50)

feat: Enhance markdown summary reporter to use collapsable sections


Patch:
diff --git a/pkg/reporter/markdown/markdown.go b/pkg/reporter/markdown/markdown.go
index 45b0610..aaf6282 100644
--- a/pkg/reporter/markdown/markdown.go
+++ b/pkg/reporter/markdown/markdown.go
@@ -10,32 +10,69 @@ type MarkdownBuilder struct {
 	content bytes.Buffer
 }
 
+type MarkdownCollapsibleSection struct {
+	title   string
+	builder *MarkdownBuilder
+
+	// Only github for now
+	flavor string
+}
+
+func (s *MarkdownCollapsibleSection) Builder() *MarkdownBuilder {
+	return s.builder
+}
+
 func NewMarkdownBuilder() *MarkdownBuilder {
 	return &MarkdownBuilder{}
 }
 
+// AddHeader adds a header to the markdown document.
 func (mb *MarkdownBuilder) AddHeader(level int, text string) {
 	mb.content.WriteString(fmt.Sprintf("%s %s\n", strings.Repeat("#", level), text))
 }
 
+// AddParagraph adds a paragraph to the markdown document.
 func (mb *MarkdownBuilder) AddParagraph(text string) {
 	mb.content.WriteString(fmt.Sprintf("%s\n\n", text))
 }
 
+// AddBulletPoint adds a bullet point to the markdown document.
 func (mb *MarkdownBuilder) AddBulletPoint(text string) {
 	mb.content.WriteString(fmt.Sprintf("- %s\n", text))
 }
 
+// AddNumberedPoint adds a numbered point to the markdown document.
 func (mb *MarkdownBuilder) AddNumberedPoint(number int, text string) {
 	mb.content.WriteString(fmt.Sprintf("%d. %s\n", number, text))
 }
 
+// AddCodeSnippet adds a code snippet to the markdown document.
 func (mb *MarkdownBuilder) AddCodeSnippet(code, language string) {
 	mb.content.WriteString("```" + language + "\n")
 	mb.content.WriteString(code + "\n")
 	mb.content.WriteString("```\n")
 }
 
+// StartCollapsibleSection starts a collapsible section in the markdown document.
+func (mb *MarkdownBuilder) StartCollapsibleSection(title string) *MarkdownCollapsibleSection {
+	return &MarkdownCollapsibleSection{
+		title:   title,
+		flavor:  "github",
+		builder: NewMarkdownBuilder(),
+	}
+}
+
+// AddCollapsibleSection adds a collapsible section to the markdown document.
+func (mb *MarkdownBuilder) AddCollapsibleSection(section *MarkdownCollapsibleSection) {
+	if section.flavor != "github" {
+		return
+	}
+
+	mb.content.WriteString(fmt.Sprintf("<details>\n<summary>%s</summary>\n\n", section.title))
+	mb.content.WriteString(section.builder.Build())
+	mb.content.WriteString("</details>\n\n")
+}
+
 func (mb *MarkdownBuilder) Build() string {
 	return mb.content.String()
 }
diff --git a/pkg/reporter/markdown_summary.go b/pkg/reporter/markdown_summary.go
index af3d5a4..a3a1872 100644
--- a/pkg/reporter/markdown_summary.go
+++ b/pkg/reporter/markdown_summary.go
@@ -278,7 +278,8 @@ func (r *markdownSummaryReporter) addChangedPackageSection(builder *markdown.Mar
 		return nil
 	}
 
-	builder.AddHeader(2, "New Packages")
+	section := builder.StartCollapsibleSection("Changed Packages")
+	section.Builder().AddHeader(2, "Changed Packages")
 
 	for _, pkg := range internalModel.packages {
 		pkgModel := pkg.GetPackage()
@@ -292,17 +293,22 @@ func (r *markdownSummaryReporter) addChangedPackageSection(builder *markdown.Mar
 			statusEmoji = markdown.EmojiWarning
 		}
 
-		builder.AddBulletPoint(fmt.Sprintf("%s [`%s`] `%s@%s`",
+		section.Builder().AddBulletPoint(fmt.Sprintf("%s [`%s`] `%s@%s`",
 			statusEmoji,
 			pkgModel.GetEcosystem(), pkgModel.GetName(), pkgModel.GetVersion()))
 	}
 
+	builder.AddCollapsibleSection(section)
 	return nil
 }
 
 func (r *markdownSummaryReporter) addViolationSection(builder *markdown.MarkdownBuilder,
 	internalModel *vetResultInternalModel) error {
-	isHeaderAdded := false
+
+	section := builder.StartCollapsibleSection("Policy Violations")
+	section.Builder().AddHeader(2, "Packages Violating Policy")
+
+	hasViolations := false
 
 	for _, pkg := range internalModel.packages {
 		packageViolations := pkg.GetViolations()
@@ -317,18 +323,13 @@ func (r *markdownSummaryReporter) addViolationSection(builder *markdown.Markdown
 			continue
 		}
 
-		// We use this weird logic to ensure we don't end up adding
-		// section header if there are no package with violations
-		if !isHeaderAdded {
-			builder.AddHeader(2, "Packages Violating Policy")
-			isHeaderAdded = true
-		}
+		hasViolations = true
 
 		externalReferenceEmojiUrl := fmt.Sprintf("[%s](%s)",
 			markdown.EmojiLink,
 			r.getPackageExternalReferenceUrl(pkgModel))
 
-		builder.AddHeader(3, fmt.Sprintf("[%s] `%s@%s` %s",
+		section.Builder().AddHeader(3, fmt.Sprintf("[%s] `%s@%s` %s",
 			pkgModel.GetEcosystem(), pkgModel.GetName(), pkgModel.GetVersion(),
 			externalReferenceEmojiUrl))
 
@@ -343,12 +344,12 @@ func (r *markdownSummaryReporter) addViolationSection(builder *markdown.Markdown
 					}
 				*/
 
-				builder.AddBulletPoint(fmt.Sprintf(":arrow_right: Found in manifest `%s`", path))
+				section.Builder().AddBulletPoint(fmt.Sprintf(":arrow_right: Found in manifest `%s`", path))
 			}
 		}
 
 		for _, v := range packageViolations {
-			builder.AddBulletPoint(fmt.Sprintf(":warning: %s", v.GetFilter().GetSummary()))
+			section.Builder().AddBulletPoint(fmt.Sprintf(":warning: %s", v.GetFilter().GetSummary()))
 		}
 
 		advices := pkg.GetAdvices()
@@ -358,10 +359,14 @@ func (r *markdownSummaryReporter) addViolationSection(builder *markdown.Markdown
 				continue
 			}
 
-			builder.AddBulletPoint(fmt.Sprintf(":zap: %s", advSummary))
+			section.Builder().AddBulletPoint(fmt.Sprintf(":zap: %s", advSummary))
 		}
 	}
 
+	if hasViolations {
+		builder.AddCollapsibleSection(section)
+	}
+
 	return nil
 }
 


Test patch:
diff --git a/pkg/reporter/markdown/markdown_test.go b/pkg/reporter/markdown/markdown_test.go
index 811f8f9..d109cc0 100644
--- a/pkg/reporter/markdown/markdown_test.go
+++ b/pkg/reporter/markdown/markdown_test.go
@@ -41,6 +41,15 @@ func TestMarkdownBuilder(t *testing.T) {
 			},
 			"- AAAA\n- BBBB",
 		},
+		{
+			"Has collapsible section",
+			func(builder *MarkdownBuilder) {
+				section := builder.StartCollapsibleSection("Title")
+				section.Builder().AddParagraph("AAAABBBB")
+				builder.AddCollapsibleSection(section)
+			},
+			"<details>\n<summary>Title</summary>\n\nAAAABBBB\n\n</details>",
+		},
 	}
 
 	for _, test := range cases {