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

Command Summary - New UI #1245

Merged
merged 79 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
fb2adf3
Implement both extended and basic test
EyalDelarea Aug 21, 2024
5acc691
Fix docker multi-arch view
EyalDelarea Aug 21, 2024
a3efa83
full to extended
EyalDelarea Aug 21, 2024
46c7f0b
Nest modules inside build info section
EyalDelarea Aug 21, 2024
348cfd1
fix tests
EyalDelarea Aug 21, 2024
5a3c100
fix table
EyalDelarea Aug 22, 2024
e3fb69c
Change bool location
EyalDelarea Aug 22, 2024
866e1ee
Merge branch 'subscription-based-summary' into new_summary_ui
EyalDelarea Aug 22, 2024
36647ac
Fix tables
EyalDelarea Aug 22, 2024
0e1a410
Move to static variables
EyalDelarea Aug 22, 2024
d8e49c4
Fix unsued variables
EyalDelarea Aug 22, 2024
0e2aa2e
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into su…
EyalDelarea Aug 22, 2024
975f485
Fix tests
EyalDelarea Aug 22, 2024
189ce13
Fix tests
EyalDelarea Aug 22, 2024
060fe79
Fix static check
EyalDelarea Aug 22, 2024
02df2bc
Fix static check
EyalDelarea Aug 22, 2024
4e1151e
Fix static check
EyalDelarea Aug 22, 2024
5ee94d8
Fix static check
EyalDelarea Aug 22, 2024
85f9bbc
pull dev
EyalDelarea Aug 25, 2024
57af0a1
Update upgrade message
EyalDelarea Aug 25, 2024
9b129f9
Rename
EyalDelarea Aug 25, 2024
b0f4cd4
Fix tests with new link
EyalDelarea Aug 25, 2024
9a93bfe
pull
EyalDelarea Aug 25, 2024
89ec77f
Module id title
EyalDelarea Aug 25, 2024
c4b1118
Working table length
EyalDelarea Aug 25, 2024
fffd482
Build info + maven module
EyalDelarea Aug 25, 2024
e2c1e38
Fix except docker multi view
EyalDelarea Aug 25, 2024
791bbe6
Add correct width
EyalDelarea Aug 25, 2024
9100e8f
Comment out the build info summary tests until fix comparing issues
EyalDelarea Aug 25, 2024
2390106
Merge branch 'dev' into subscription-based-summary
EyalDelarea Aug 26, 2024
f66cb37
fetch scan results
EyalDelarea Aug 26, 2024
0b55fbc
UI changes and interface changes
EyalDelarea Aug 26, 2024
6f5fe4b
remove unused
EyalDelarea Aug 26, 2024
57910ef
CR
EyalDelarea Aug 26, 2024
5856719
CR
EyalDelarea Aug 26, 2024
bb81e3f
Rename env var
EyalDelarea Aug 26, 2024
eae9fed
Pull basic / extended summary
EyalDelarea Aug 26, 2024
e4a0a44
Add to interface
EyalDelarea Aug 26, 2024
e4b8652
Remove unused
EyalDelarea Aug 26, 2024
261b761
Pull dev
EyalDelarea Aug 26, 2024
985b9d8
Disable tests
EyalDelarea Aug 26, 2024
00ebd90
Refactor
EyalDelarea Aug 26, 2024
6a50061
Update icons and headers
EyalDelarea Aug 26, 2024
201c49b
Start handling tests
EyalDelarea Aug 26, 2024
1162e9c
Fix tests
EyalDelarea Aug 27, 2024
763395b
Fix single docker image test
EyalDelarea Aug 27, 2024
bcff291
Fix tests
EyalDelarea Aug 27, 2024
ea46269
Fix tests
EyalDelarea Aug 27, 2024
e815dfd
Limit test to 900 chars to avoid formatting problems
EyalDelarea Aug 27, 2024
8a038ba
add comments
EyalDelarea Aug 27, 2024
62402bb
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into ne…
EyalDelarea Aug 27, 2024
ac54cf0
dont test title
EyalDelarea Aug 27, 2024
8d7e428
dont test title
EyalDelarea Aug 27, 2024
cff212a
fix tree test
EyalDelarea Aug 27, 2024
d3ac166
Fix static check
EyalDelarea Aug 27, 2024
f84a2b7
Update header
EyalDelarea Aug 27, 2024
dd56730
Refactor
EyalDelarea Aug 27, 2024
7411d65
Ignore non indxed folders
EyalDelarea Aug 27, 2024
0d079dc
Fix test
EyalDelarea Aug 27, 2024
32775bd
Remove err
EyalDelarea Aug 27, 2024
570fbfe
Update interface
EyalDelarea Aug 27, 2024
b91768b
Fix sha
EyalDelarea Aug 27, 2024
87cb917
Fix marhsal
EyalDelarea Aug 28, 2024
87f6489
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into ne…
EyalDelarea Aug 28, 2024
140d385
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into ne…
EyalDelarea Aug 28, 2024
1dafdc0
extract docker image tag
EyalDelarea Aug 28, 2024
4cafa4a
Fix docker, need refactor
EyalDelarea Aug 28, 2024
9206fbb
Refactor
EyalDelarea Aug 28, 2024
e11fbfe
Fix tests
EyalDelarea Aug 28, 2024
959dcd8
Fix basic table build
EyalDelarea Aug 28, 2024
7cbc893
Revert manual
EyalDelarea Aug 28, 2024
4df5171
Refactor
EyalDelarea Aug 29, 2024
46f82e1
Add link
EyalDelarea Aug 29, 2024
af3104a
Add comments
EyalDelarea Aug 29, 2024
fab2227
Fix basic links
EyalDelarea Aug 29, 2024
1f14b0d
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into ne…
EyalDelarea Aug 29, 2024
1beff48
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into ne…
EyalDelarea Aug 29, 2024
3d7e1ce
CR
EyalDelarea Aug 29, 2024
7b0f89d
Fix no modules at all
EyalDelarea Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 141 additions & 46 deletions artifactory/utils/commandsummary/buildinfosummary.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package commandsummary
import (
"fmt"
buildInfo "github.com/jfrog/build-info-go/entities"
"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container"

"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container"
"path"
"strings"
"time"
)

const (
timeFormat = "Jan 2, 2006 , 15:04:05"
basicSummaryUpgradeNotice = "\n<p> <a href=\"%s\">⏫ Enable the linkage to Artifactory</a> </p>\n"
basicSummaryUpgradeNotice = "<a href=\"%s\">🐸 Enable the linkage to Artifactory</a>\n\n"
modulesTitle = "📦 Artifacts published to Artifactory by this workflow"
minTableColumnLength = 400
markdownSpaceFiller = "&nbsp;"
NonScannedResult = "non-scanned"
)

type BuildInfoSummary struct {
Expand All @@ -24,6 +25,10 @@ func NewBuildInfoSummary() (*CommandSummary, error) {
return New(&BuildInfoSummary{}, "build-info")
}

func (bis *BuildInfoSummary) GetSummaryTitle() string {
return "🛠️️ Published JFrog Build Info"
}

func (bis *BuildInfoSummary) GenerateMarkdownFromFiles(dataFilePaths []string) (finalMarkdown string, err error) {
// Aggregate all the build info files into a slice
var builds []*buildInfo.BuildInfo
Expand All @@ -34,34 +39,39 @@ func (bis *BuildInfoSummary) GenerateMarkdownFromFiles(dataFilePaths []string) (
}
builds = append(builds, &publishBuildInfo)
}

if len(builds) > 0 {
finalMarkdown = bis.buildInfoTable(builds) + bis.buildInfoModules(builds)
if len(builds) == 0 {
return "", nil
}
// Creates the build info table
buildInfoTableMarkdown := bis.buildInfoTable(builds)
// Creates the published modules
publishedModulesMarkdown := bis.buildInfoModules(builds)
if publishedModulesMarkdown != "" {
publishedModulesMarkdown = WrapCollapsableMarkdown(modulesTitle, publishedModulesMarkdown, 2)
}
finalMarkdown = buildInfoTableMarkdown + publishedModulesMarkdown

// Wrap the content under a collapsible section
finalMarkdown = WrapCollapsableMarkdown(bis.GetSummaryTitle(), finalMarkdown, 3)
return
}

func (bis *BuildInfoSummary) buildInfoTable(builds []*buildInfo.BuildInfo) string {
// Generate a string that represents a Markdown table
var tableBuilder strings.Builder
tableBuilder.WriteString("\n\n### Published Build Infos\n\n")
tableBuilder.WriteString("\n\n| Build Info | Time Stamp |\n")
tableBuilder.WriteString("|---------|------------| \n")
// Write table header
tableBuilder.WriteString(getBuildInfoTableHeader())
// Add rows
for _, build := range builds {
buildTime := parseBuildTime(build.Started)
if StaticMarkdownConfig.IsExtendedSummary() {
tableBuilder.WriteString(fmt.Sprintf("| [%s](%s) | %s |\n", build.Name+" "+build.Number, build.BuildUrl, buildTime))
} else {
tableBuilder.WriteString(fmt.Sprintf("| %s | %s |\n", build.Name+" "+build.Number, buildTime))
}
appendBuildRow(&tableBuilder, build)
}
// Add a new line after the table
tableBuilder.WriteString("\n\n")
return tableBuilder.String()
}

func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) string {
var markdownBuilder strings.Builder
markdownBuilder.WriteString("\n\n### Modules Published As Part of This Build\n\n")
markdownBuilder.WriteString("\n\n<h3>Published Modules</h3>\n\n")
var shouldGenerate bool
for _, build := range builds {
if modulesMarkdown := bis.generateModulesMarkdown(build.Modules...); modulesMarkdown != "" {
Expand All @@ -78,30 +88,47 @@ func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) str
}

func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module) string {
var modulesMarkdown strings.Builder
var parentModulesMarkdown strings.Builder
// Modules could have nested modules inside them
// Group them by their parent ID to allow tracing
// If a module has no parent, it is considered a parent module itself
parentToModulesMap := groupModulesByParent(modules)
if len(parentToModulesMap) == 0 {
return ""
}

for parentModuleID, parentModules := range parentToModulesMap {
modulesMarkdown.WriteString(fmt.Sprintf("#### %s\n<pre>", parentModuleID))
parentModulesMarkdown.WriteString(generateModuleHeader(parentModuleID))
parentModulesMarkdown.WriteString(generateModuleTableHeader())
isMultiModule := len(parentModules) > 1
nestedModuleMarkdownTree := bis.generateNestedModuleMarkdownTree(parentModules, parentModuleID, isMultiModule)
scanResult := getScanResults(extractDockerImageTag(parentModules))
parentModulesMarkdown.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult))
}
return parentModulesMarkdown.String()
}

if !StaticMarkdownConfig.IsExtendedSummary() {
// Adds a teaser message to upgrade to extend summary
modulesMarkdown.WriteString(fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetPlatformUrl()))
}
for _, module := range parentModules {
if isMultiModule && parentModuleID == module.Id {
// Skip the parent module if there are multiple modules, as it will be displayed as a header
continue
}
modulesMarkdown.WriteString(bis.generateModuleArtifactsTree(&module, isMultiModule))
func (bis *BuildInfoSummary) generateNestedModuleMarkdownTree(parentModules []buildInfo.Module, parentModuleID string, isMultiModule bool) string {
var nestedModuleMarkdownTree strings.Builder
if len(parentModules) == 0 {
return ""
}
if !StaticMarkdownConfig.IsExtendedSummary() {
nestedModuleMarkdownTree.WriteString("|")
nestedModuleMarkdownTree.WriteString(fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetExtendedSummaryLangPage()))
nestedModuleMarkdownTree.WriteString("<pre>")
} else {
nestedModuleMarkdownTree.WriteString("|<pre>")
}

for _, module := range parentModules {
if isMultiModule && parentModuleID == module.Id {
continue
}
modulesMarkdown.WriteString("</pre>\n")
nestedModuleMarkdownTree.WriteString(bis.generateModuleArtifactsTree(&module, isMultiModule))
}
return modulesMarkdown.String()
nestedModuleMarkdownTree.WriteString(appendSpacesToTableColumn(""))
nestedModuleMarkdownTree.WriteString("</pre>")
return nestedModuleMarkdownTree.String()
}

func (bis *BuildInfoSummary) generateModuleArtifactsTree(module *buildInfo.Module, shouldCollapseArtifactsTree bool) string {
Expand Down Expand Up @@ -152,7 +179,6 @@ func groupModulesByParent(modules []buildInfo.Module) map[string][]buildInfo.Mod
if len(module.Artifacts) == 0 || !isSupportedModule(&module) {
continue
}

parentID := module.Parent
// If the module has no parent, that means it is the parent module itself, so we can use its ID as the parent ID.
if parentID == "" {
Expand All @@ -175,18 +201,8 @@ func isSupportedModule(module *buildInfo.Module) bool {
}
}

func parseBuildTime(timestamp string) string {
// Parse the timestamp string into a time.Time object
buildInfoTime, err := time.Parse(buildInfo.TimeFormat, timestamp)
if err != nil {
return "N/A"
}
// Format the time in a more human-readable format
return buildInfoTime.Format(timeFormat)
}

func createDockerMultiArchTitle(module *buildInfo.Module) string {
// Extract the parent image name from the module ID (e.g. my-image:1.0 -> my-image)
// Extract the parent image name from the module ID (e.g., my-image:1.0 -> my-image)
parentImageName := strings.Split(module.Parent, ":")[0]

// Get the relevant SHA256
Expand All @@ -209,3 +225,82 @@ func createDockerMultiArchTitle(module *buildInfo.Module) string {
func createCollapsibleSection(title, content string) string {
return fmt.Sprintf("<details><summary>%s</summary>\n%s</details>", title, content)
}

func appendSpacesToTableColumn(str string) string {
const nbspLength = len(markdownSpaceFiller)
if len(str) < minTableColumnLength {
padding := minTableColumnLength - len(str)
if padding > 0 {
str += strings.Repeat(markdownSpaceFiller, padding/nbspLength)
}
}
return str
}

func appendBuildRow(tableBuilder *strings.Builder, build *buildInfo.BuildInfo) {
buildName := build.Name + " " + build.Number
buildScanResult := getScanResults(buildName)
if StaticMarkdownConfig.IsExtendedSummary() {
tableBuilder.WriteString(fmt.Sprintf("| [%s](%s) %s | %s | %s | \n", buildName, build.BuildUrl, appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities())))
} else {
// Get the URL to the extended summary page
upgradeMessage := fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetExtendedSummaryLangPage())
// Append to build name to fit inside the table
buildName = fmt.Sprintf(" %s %s", upgradeMessage, buildName)
tableBuilder.WriteString(fmt.Sprintf("| %s %s | %s | %s |\n", fitInsideMarkdownTable(buildName), appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities())))
}
}

func getBuildInfoTableHeader() string {
return "\n\n| Build Info | Security Violations | Security Issues |\n|:---------|:------------|:------------|\n"
}

func generateModuleHeader(parentModuleID string) string {
return fmt.Sprintf("\n\n**%s**\n\n", parentModuleID)
}

func generateModuleTableHeader() string {
return "\n\n| Artifacts | Security Violations | Security Issues |\n|:------------|:---------------------|:------------------|\n"
}

func generateTableRow(nestedModuleMarkdownTree string, scanResult ScanResult) string {
return fmt.Sprintf(" %s | %s | %s |\n", fitInsideMarkdownTable(nestedModuleMarkdownTree), appendSpacesToTableColumn(scanResult.GetViolations()), appendSpacesToTableColumn(scanResult.GetVulnerabilities()))
}

// To fit inside the Markdown table, replace new lines with <br>
func fitInsideMarkdownTable(str string) string {
return strings.ReplaceAll(str, "\n", "<br>")
}

func getScanResults(scannedEntity string) (sc ScanResult) {
if sc = StaticMarkdownConfig.scanResultsMapping[fileNameToSha1(scannedEntity)]; sc != nil {
return sc
}
return StaticMarkdownConfig.scanResultsMapping[NonScannedResult]
}

// Extracts the docker image tag from a docker module
// Docker modules have in their first index metadata, which contains the docker image tag
func extractDockerImageTag(modules []buildInfo.Module) string {
if len(modules) == 0 || modules[0].Type != buildInfo.Docker {
return ""
}

const tagKey = "docker.image.tag"
properties := modules[0].Properties
// Handle both cases where the properties are a map[string]interface{} or map[string]string
switch props := properties.(type) {
case map[string]interface{}:
if tag, found := props[tagKey]; found {
if tagStr, ok := tag.(string); ok {
return tagStr
}
}
case map[string]string:
if tag, found := props[tagKey]; found {
return tag
}
}

return ""
}
Loading
Loading