diff --git a/alpha/declcfg/write.go b/alpha/declcfg/write.go index b721d3145..4021c8a32 100644 --- a/alpha/declcfg/write.go +++ b/alpha/declcfg/write.go @@ -19,12 +19,7 @@ import ( // output is sorted lexicographically by package name, and then by channel name // if provided, minEdgeName will be used as the lower bound for edges in the output graph // -// NB: Output has wrapper comments stating the skipRange edge caveat in HTML comment format, which cannot be parsed by mermaid renderers. -// -// This is deliberate, and intended as an explicit acknowledgement of the limitations, instead of requiring the user to notice the missing edges upon inspection. -// // Example output: -// // graph LR // // %% package "neuvector-certified-operator-rhmp" @@ -40,7 +35,6 @@ import ( // end // // end -// func WriteMermaidChannels(cfg DeclarativeConfig, out io.Writer, minEdgeName string) error { pkgs := map[string]*strings.Builder{} @@ -59,6 +53,15 @@ func WriteMermaidChannels(cfg DeclarativeConfig, out io.Writer, minEdgeName stri } } + // build increasing-version-ordered bundle names, so we can meaningfully iterate over a range + orderedBundles := []string{} + for n, _ := range versionMap { + orderedBundles = append(orderedBundles, n) + } + sort.Slice(orderedBundles, func(i, j int) bool { + return versionMap[orderedBundles[i]].LT(versionMap[orderedBundles[j]]) + }) + for _, c := range cfg.Channels { filteredChannel := filterChannel(&c, versionMap, minEdgeName) if filteredChannel != nil { @@ -88,13 +91,24 @@ func WriteMermaidChannels(cfg DeclarativeConfig, out io.Writer, minEdgeName stri pkgBuilder.WriteString(fmt.Sprintf(" %s[%q]-- %s --> %s[%q]\n", entryId, ce.Name, "skips", skipsId, s)) } } + if len(ce.SkipRange) > 0 { + skipRange, err := semver.ParseRange(ce.SkipRange) + if err != nil { + return err + } + for _, bundleName := range orderedBundles { + if skipRange(versionMap[bundleName]) { + skipRangeId := fmt.Sprintf("%s-%s", channelID, bundleName) + pkgBuilder.WriteString(fmt.Sprintf(" %s[%q]-- \"%s(%s)\" --> %s[%q]\n", entryId, ce.Name, "skipRange", ce.SkipRange, skipRangeId, bundleName)) + } + } + } } } pkgBuilder.WriteString(" end\n") } } - out.Write([]byte("\n")) out.Write([]byte("graph LR\n")) pkgNames := []string{} for pname, _ := range pkgs { @@ -109,7 +123,6 @@ func WriteMermaidChannels(cfg DeclarativeConfig, out io.Writer, minEdgeName stri out.Write([]byte(pkgs[pkgName].String())) out.Write([]byte(" end\n")) } - out.Write([]byte("\n")) return nil } @@ -121,6 +134,20 @@ func filterChannel(c *Channel, versionMap map[string]semver.Version, minEdgeName if minEdgeName == "" { return c } + + // short-circuit where the minEdgeName is not in this channel + edgeNames := make(map[string]struct{}) + + for _, ce := range c.Entries { + if _, ok := edgeNames[ce.Name]; !ok { + edgeNames[ce.Name] = struct{}{} + } + } + + if _, ok := edgeNames[minEdgeName]; !ok { + return nil + } + // convert the edge name to the version so we don't have to duplicate the lookup minVersion := versionMap[minEdgeName] @@ -132,8 +159,9 @@ func filterChannel(c *Channel, versionMap map[string]semver.Version, minEdgeName out.Entries = append(out.Entries, filteredCe) continue } - // if len(ce.SkipRange) > 0 { - // } + if len(ce.SkipRange) > 0 { + filteredCe.SkipRange = ce.SkipRange + } if len(ce.Replaces) > 0 { if versionMap[ce.Replaces].GTE(minVersion) { filteredCe.Replaces = ce.Replaces diff --git a/alpha/declcfg/write_test.go b/alpha/declcfg/write_test.go index 96eb47095..3e2f43fe4 100644 --- a/alpha/declcfg/write_test.go +++ b/alpha/declcfg/write_test.go @@ -472,16 +472,17 @@ func removeJSONWhitespace(cfg *DeclarativeConfig) { func TestWriteMermaidChannels(t *testing.T) { type spec struct { - name string - cfg DeclarativeConfig - expected string + name string + cfg DeclarativeConfig + startEdge string + expected string } specs := []spec{ { - name: "Success", - cfg: buildValidDeclarativeConfig(true), - expected: ` -graph LR + name: "SuccessNoFilters", + cfg: buildValidDeclarativeConfig(true), + startEdge: "", + expected: `graph LR %% package "anakin" subgraph "anakin" %% channel "dark" @@ -509,15 +510,33 @@ graph LR boba-fett-mando-boba-fett.v2.0.0["boba-fett.v2.0.0"]-- replaces --> boba-fett-mando-boba-fett.v1.0.0["boba-fett.v1.0.0"] end end - +`, + }, + { + name: "SuccessMinEdgeFilter", + cfg: buildValidDeclarativeConfig(true), + startEdge: "anakin.v0.1.0", + expected: `graph LR + %% package "anakin" + subgraph "anakin" + %% channel "dark" + subgraph anakin-dark["dark"] + anakin-dark-anakin.v0.1.0["anakin.v0.1.0"] + anakin-dark-anakin.v0.1.1["anakin.v0.1.1"] + anakin-dark-anakin.v0.1.1["anakin.v0.1.1"]-- skips --> anakin-dark-anakin.v0.1.0["anakin.v0.1.0"] + end + %% channel "light" + subgraph anakin-light["light"] + anakin-light-anakin.v0.1.0["anakin.v0.1.0"] + end + end `, }, } - startVersion := "" for _, s := range specs { t.Run(s.name, func(t *testing.T) { var buf bytes.Buffer - err := WriteMermaidChannels(s.cfg, &buf, startVersion) + err := WriteMermaidChannels(s.cfg, &buf, s.startEdge) require.NoError(t, err) require.Equal(t, s.expected, buf.String()) })