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

Job Summary - Group Build-Info Related Modules into the Same Section #1229

Merged
merged 16 commits into from
Aug 18, 2024
95 changes: 69 additions & 26 deletions artifactory/commands/commandssummaries/buildinfosummary.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,12 @@ func (bis *BuildInfoSummary) buildInfoTable(builds []*buildInfo.BuildInfo) strin

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### Modules Published As Part of This Build\n")
var shouldGenerate bool
for _, build := range builds {
for _, module := range build.Modules {
if len(module.Artifacts) == 0 {
continue
}

switch module.Type {
case buildInfo.Docker, buildInfo.Maven, buildInfo.Npm, buildInfo.Go, buildInfo.Generic, buildInfo.Terraform:
markdownBuilder.WriteString(bis.generateModuleMarkdown(module))
shouldGenerate = true
default:
// Skip unsupported module types.
continue
}
if modulesMarkdown := bis.generateModulesMarkdown(build.Modules...); modulesMarkdown != "" {
markdownBuilder.WriteString(modulesMarkdown)
shouldGenerate = true
}
}

Expand All @@ -85,19 +75,36 @@ func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) str
return markdownBuilder.String()
}

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"
func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module) string {
var modulesMarkdown strings.Builder
parentToModulesMap := groupModulesByParent(modules)
if len(parentToModulesMap) == 0 {
return ""
}
// Format the time in a more human-readable format and save it in a variable
return buildInfoTime.Format(timeFormat)

for parentModuleID, parentModules := range parentToModulesMap {
modulesMarkdown.WriteString(fmt.Sprintf("#### %s\n<pre>", parentModuleID))
isMultiModule := len(parentModules) > 1

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
}
artifactsTree := bis.createArtifactsTree(module)
if isMultiModule {
// Collapse the module tree if there are multiple modules
modulesMarkdown.WriteString(fmt.Sprintf("<details><summary>%s</summary>\n%s</details>", module.Id, artifactsTree))
} else {
modulesMarkdown.WriteString(artifactsTree)
}
}
modulesMarkdown.WriteString("</pre>\n")
}
return modulesMarkdown.String()
}

func (bis *BuildInfoSummary) generateModuleMarkdown(module buildInfo.Module) string {
var moduleMarkdown strings.Builder
moduleMarkdown.WriteString(fmt.Sprintf("\n #### %s \n", module.Id))
func (bis *BuildInfoSummary) createArtifactsTree(module buildInfo.Module) string {
artifactsTree := utils.NewFileTree()
for _, artifact := range module.Artifacts {
artifactUrlInArtifactory := bis.generateArtifactUrl(artifact)
Expand All @@ -108,8 +115,7 @@ func (bis *BuildInfoSummary) generateModuleMarkdown(module buildInfo.Module) str
artifactTreePath := path.Join(artifact.OriginalDeploymentRepo, artifact.Path)
artifactsTree.AddFile(artifactTreePath, artifactUrlInArtifactory)
}
moduleMarkdown.WriteString("\n\n <pre>" + artifactsTree.String() + "</pre>")
return moduleMarkdown.String()
return artifactsTree.String()
}

func (bis *BuildInfoSummary) generateArtifactUrl(artifact buildInfo.Artifact) string {
Expand All @@ -118,3 +124,40 @@ func (bis *BuildInfoSummary) generateArtifactUrl(artifact buildInfo.Artifact) st
}
return generateArtifactUrl(bis.platformUrl, path.Join(artifact.OriginalDeploymentRepo, artifact.Path), bis.majorVersion)
}

// groupModulesByParent groups modules that share the same parent ID into a map where the key is the parent ID and the value is a slice of those modules.
func groupModulesByParent(modules []buildInfo.Module) map[string][]buildInfo.Module {
parentToModulesMap := make(map[string][]buildInfo.Module, len(modules))
omerzi marked this conversation as resolved.
Show resolved Hide resolved
for _, module := range modules {
if len(module.Artifacts) == 0 || !isSupportedModuleType(module.Type) {
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 == "" {
parentID = module.Id
}
parentToModulesMap[parentID] = append(parentToModulesMap[parentID], module)
}
return parentToModulesMap
}

func isSupportedModuleType(moduleType buildInfo.ModuleType) bool {
switch moduleType {
case buildInfo.Docker, buildInfo.Maven, buildInfo.Npm, buildInfo.Go, buildInfo.Generic, buildInfo.Terraform:
return true
default:
return false
}
}

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 and save it in a variable
return buildInfoTime.Format(timeFormat)
}
149 changes: 137 additions & 12 deletions artifactory/commands/commandssummaries/buildinfosummary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ func TestBuildInfoModules(t *testing.T) {
BuildUrl: "http://myJFrogPlatform/builds/buildName/123",
Modules: []buildinfo.Module{
{
Id: "gradle",
// Validate that ignored types don't show.
Type: buildinfo.Gradle,
Artifacts: []buildinfo.Artifact{
{
Name: "gradleArtifact",
Path: "dir/gradleArtifact",
OriginalDeploymentRepo: "gradle-local",
},
},
},
{
Id: "maven",
Type: buildinfo.Maven,
Artifacts: []buildinfo.Artifact{{
Name: "artifact1",
Expand All @@ -46,29 +59,24 @@ func TestBuildInfoModules(t *testing.T) {
},
},
{
Id: "generic",
Type: buildinfo.Generic,
Artifacts: []buildinfo.Artifact{{
Name: "artifact2",
Path: "path/to/artifact2",
OriginalDeploymentRepo: "generic-local",
}},
},
{
// Validate that ignored types don't show.
Type: buildinfo.Gradle,
Artifacts: []buildinfo.Artifact{
{
Name: "gradleArtifact",
Path: "dir/gradleArtifact",
OriginalDeploymentRepo: "gradle-local",
},
},
},
},
},
}

assert.Equal(t, getTestDataFile(t, "modules.md"), gh.buildInfoModules(builds))
result := gh.buildInfoModules(builds)
// Validate that the markdown contains the expected "generic" repo content as well as the "maven" repo content.
assert.Contains(t, result, getTestDataFile(t, "generic.md"))
assert.Contains(t, result, getTestDataFile(t, "maven.md"))
// The build-info also contains a "gradle" module, but it should not be included in the markdown.
assert.NotContains(t, result, "gradle")
}

// Validate that if no supported module with artifacts was found, we avoid generating the markdown.
Expand All @@ -82,6 +90,7 @@ func TestBuildInfoModulesEmpty(t *testing.T) {
BuildUrl: "http://myJFrogPlatform/builds/buildName/123",
Modules: []buildinfo.Module{
{
Id: "maven",
Type: buildinfo.Maven,
Artifacts: []buildinfo.Artifact{},
Dependencies: []buildinfo.Dependency{{
Expand All @@ -90,6 +99,7 @@ func TestBuildInfoModulesEmpty(t *testing.T) {
},
},
{
Id: "gradle",
Type: buildinfo.Gradle,
Artifacts: []buildinfo.Artifact{
{
Expand All @@ -106,6 +116,121 @@ func TestBuildInfoModulesEmpty(t *testing.T) {
assert.Empty(t, gh.buildInfoModules(builds))
}

func TestBuildInfoModulesWithGrouping(t *testing.T) {
gh := &BuildInfoSummary{platformUrl: platformUrl, majorVersion: 7}
var builds = []*buildinfo.BuildInfo{
{
Name: "dockerx",
Number: "1",
Started: "2024-08-12T11:11:50.198+0300",
Modules: []buildinfo.Module{
{
Properties: map[string]string{
"docker.image.tag": "ecosysjfrog.jfrog.io/docker-local/multiarch-image:1",
},
Type: "docker",
Id: "multiarch-image:1",
Artifacts: []buildinfo.Artifact{
{
Type: "json",
Checksum: buildinfo.Checksum{
Sha1: "faf9824aca9d192e16c2f8a6670b149392465ce7",
Sha256: "2217c766cddcd2d24994caaf7713db556a0fa8de108a946ebe5b0369f784a59a",
Md5: "ba0519ebb6feef0edefa03a7afb05406",
},
Name: "list.manifest.json",
Path: "multiarch-image/1/list.manifest.json",
OriginalDeploymentRepo: "docker-local",
},
},
},
{
Type: "docker",
Parent: "multiarch-image:1",
Id: "linux/amd64/multiarch-image:1",
Artifacts: []buildinfo.Artifact{
{
Checksum: buildinfo.Checksum{
Sha1: "32c1416f8430fbbabd82cb014c5e09c5fe702404",
Sha256: "aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e",
Md5: "f568bfb1c9576a1f06235ebe0389d2d8",
},
Name: "sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e",
Path: "multiarch-image/sha256:552ccb2628970ef526f13151a0269258589fc8b5701519a9c255c4dd224b9a21/sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e",
OriginalDeploymentRepo: "docker-local",
},
},
},
{
Type: "docker",
Parent: "multiarch-image:1",
Id: "linux/arm64/multiarch-image:1",
Artifacts: []buildinfo.Artifact{
{
Checksum: buildinfo.Checksum{
Sha1: "82b6d4ae1f673c609469a0a84170390ecdff5a38",
Sha256: "1f17f9d95f85ba55773db30ac8e6fae894831be87f5c28f2b58d17f04ef65e93",
Md5: "d178dd8c1e1fded51ade114136ebdaf2",
},
Name: "sha256__1f17f9d95f85ba55773db30ac8e6fae894831be87f5c28f2b58d17f04ef65e93",
Path: "multiarch-image/sha256:bee6dc0408dfd20c01e12e644d8bc1d60ff100a8c180d6c7e85d374c13ae4f92/sha256__1f17f9d95f85ba55773db30ac8e6fae894831be87f5c28f2b58d17f04ef65e93",
OriginalDeploymentRepo: "docker-local",
},
},
},
{
Type: "docker",
Parent: "multiarch-image:1",
Id: "linux/arm/multiarch-image:1",
Artifacts: []buildinfo.Artifact{
{
Checksum: buildinfo.Checksum{
Sha1: "63d3ac90f9cd322b76543d7bf96eeb92417faf41",
Sha256: "33b5b5485e88e63d3630e5dcb008f98f102b0f980a9daa31bd976efdec7a8e4c",
Md5: "99bbb1e1035aea4d9150e4348f24e107",
},
Name: "sha256__33b5b5485e88e63d3630e5dcb008f98f102b0f980a9daa31bd976efdec7a8e4c",
Path: "multiarch-image/sha256:686085b9972e0f7a432b934574e3dca27b4fa0a3d10d0ae7099010160db6d338/sha256__33b5b5485e88e63d3630e5dcb008f98f102b0f980a9daa31bd976efdec7a8e4c",
OriginalDeploymentRepo: "docker-local",
},
{
Checksum: buildinfo.Checksum{
Sha1: "9dceac352f990a3149ff97ab605c3c8833409abf",
Sha256: "5480d2ca1740c20ce17652e01ed2265cdc914458acd41256a2b1ccff28f2762c",
Md5: "d6a694604c7e58b2c788dec5656a1add",
},
Name: "sha256__5480d2ca1740c20ce17652e01ed2265cdc914458acd41256a2b1ccff28f2762c",
Path: "multiarch-image/sha256:686085b9972e0f7a432b934574e3dca27b4fa0a3d10d0ae7099010160db6d338/sha256__5480d2ca1740c20ce17652e01ed2265cdc914458acd41256a2b1ccff28f2762c",
OriginalDeploymentRepo: "docker-local",
},
},
},
{
Type: "docker",
Parent: "image:2",
Id: "image:2",
Artifacts: []buildinfo.Artifact{
{
Checksum: buildinfo.Checksum{
Sha1: "32c1416f8430fbbabd82cb014c5e09c5fe702404",
Sha256: "aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e",
Md5: "f568bfb1c9576a1f06235ebe0389d2d8",
},
Name: "sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e",
Path: "image2/sha256:552ccb2628970ef526f13151a0269258589fc8b5701519a9c255c4dd224b9a21/sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e",
OriginalDeploymentRepo: "docker-local",
},
},
},
},
},
}

result := gh.buildInfoModules(builds)
assert.Contains(t, result, getTestDataFile(t, "image2.md"))
assert.Contains(t, result, getTestDataFile(t, "multiarch-image1.md"))
}

func getTestDataFile(t *testing.T, fileName string) string {
modulesPath := filepath.Join(".", "testdata", fileName)
content, err := os.ReadFile(modulesPath)
Expand Down
7 changes: 7 additions & 0 deletions artifactory/commands/commandssummaries/testdata/generic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#### generic
<pre>📦 generic-local
└── 📁 path
└── 📁 to
└── <a href=https://myplatform.com/ui/repos/tree/General/generic-local/path/to/artifact2?clearFilter=true target="_blank">artifact2</a>

</pre>
7 changes: 7 additions & 0 deletions artifactory/commands/commandssummaries/testdata/image2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#### image:2
<pre>📦 docker-local
└── 📁 image2
└── 📁 sha256:552ccb2628970ef526f13151a0269258589fc8b5701519a9c255c4dd224b9a21
└── <a href=https://myplatform.com/ui/repos/tree/General/docker-local/image2/sha256:552ccb2628970ef526f13151a0269258589fc8b5701519a9c255c4dd224b9a21/sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e?clearFilter=true target="_blank">sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e</a>

</pre>
7 changes: 7 additions & 0 deletions artifactory/commands/commandssummaries/testdata/maven.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#### maven
<pre>📦 libs-release
└── 📁 path
└── 📁 to
└── <a href=https://myplatform.com/ui/repos/tree/General/libs-release/path/to/artifact1?clearFilter=true target="_blank">artifact1</a>

</pre>
23 changes: 0 additions & 23 deletions artifactory/commands/commandssummaries/testdata/modules.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#### multiarch-image:1
<pre><details><summary>linux/amd64/multiarch-image:1</summary>
📦 docker-local
└── 📁 multiarch-image
└── 📁 sha256:552ccb2628970ef526f13151a0269258589fc8b5701519a9c255c4dd224b9a21
└── <a href=https://myplatform.com/ui/repos/tree/General/docker-local/multiarch-image/sha256:552ccb2628970ef526f13151a0269258589fc8b5701519a9c255c4dd224b9a21/sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e?clearFilter=true target="_blank">sha256__aee9d258e62f0666e3286acca21be37d2e39f69f8dde74454b9f3cd8ef437e4e</a>

</details><details><summary>linux/arm64/multiarch-image:1</summary>
📦 docker-local
└── 📁 multiarch-image
└── 📁 sha256:bee6dc0408dfd20c01e12e644d8bc1d60ff100a8c180d6c7e85d374c13ae4f92
└── <a href=https://myplatform.com/ui/repos/tree/General/docker-local/multiarch-image/sha256:bee6dc0408dfd20c01e12e644d8bc1d60ff100a8c180d6c7e85d374c13ae4f92/sha256__1f17f9d95f85ba55773db30ac8e6fae894831be87f5c28f2b58d17f04ef65e93?clearFilter=true target="_blank">sha256__1f17f9d95f85ba55773db30ac8e6fae894831be87f5c28f2b58d17f04ef65e93</a>

</details><details><summary>linux/arm/multiarch-image:1</summary>
📦 docker-local
└── 📁 multiarch-image
└── 📁 sha256:686085b9972e0f7a432b934574e3dca27b4fa0a3d10d0ae7099010160db6d338
├── <a href=https://myplatform.com/ui/repos/tree/General/docker-local/multiarch-image/sha256:686085b9972e0f7a432b934574e3dca27b4fa0a3d10d0ae7099010160db6d338/sha256__33b5b5485e88e63d3630e5dcb008f98f102b0f980a9daa31bd976efdec7a8e4c?clearFilter=true target="_blank">sha256__33b5b5485e88e63d3630e5dcb008f98f102b0f980a9daa31bd976efdec7a8e4c</a>
└── <a href=https://myplatform.com/ui/repos/tree/General/docker-local/multiarch-image/sha256:686085b9972e0f7a432b934574e3dca27b4fa0a3d10d0ae7099010160db6d338/sha256__5480d2ca1740c20ce17652e01ed2265cdc914458acd41256a2b1ccff28f2762c?clearFilter=true target="_blank">sha256__5480d2ca1740c20ce17652e01ed2265cdc914458acd41256a2b1ccff28f2762c</a>

</details></pre>
Loading
Loading