diff --git a/AUTHORS b/AUTHORS index 8301df2c..291fe28a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,3 +24,4 @@ List of contributors, in chronological order: * Geoffrey Thomas (https://github.com/geofft) * Oliver Sauder (https://github.com/sliverc) * Harald Sitter (https://github.com/apachelogger) +* Johannes Layher (https://github.com/jola5) \ No newline at end of file diff --git a/api/graph.go b/api/graph.go index 0d11d706..5c3b8dc6 100644 --- a/api/graph.go +++ b/api/graph.go @@ -11,7 +11,7 @@ import ( "os/exec" ) -// GET /api/graph.:ext +// GET /api/graph.:ext?layout=[vertical|horizontal(default)] func apiGraph(c *gin.Context) { var ( err error @@ -19,6 +19,7 @@ func apiGraph(c *gin.Context) { ) ext := c.Params.ByName("ext") + layout := c.Request.URL.Query().Get("layout") factory := context.CollectionFactory() @@ -31,7 +32,7 @@ func apiGraph(c *gin.Context) { factory.PublishedRepoCollection().RLock() defer factory.PublishedRepoCollection().RUnlock() - graph, err := deb.BuildGraph(factory) + graph, err := deb.BuildGraph(factory, layout) if err != nil { c.JSON(500, err) return diff --git a/cmd/graph.go b/cmd/graph.go index eb35559f..45d836b0 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -21,8 +21,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error { return commander.ErrCommandError } + layout := context.Flags().Lookup("layout").Value.String() + fmt.Printf("Generating graph...\n") - graph, err := deb.BuildGraph(context.CollectionFactory()) + graph, err := deb.BuildGraph(context.CollectionFactory(), layout) + if err != nil { return err } @@ -108,6 +111,7 @@ Example: cmd.Flag.String("format", "png", "render graph to specified format (png, svg, pdf, etc.)") cmd.Flag.String("output", "", "specify output filename, default is to open result in viewer") + cmd.Flag.String("layout", "horizontal", "create a more 'vertical' or a more 'horizontal' graph layout") return cmd } diff --git a/deb/graph.go b/deb/graph.go index 37abd692..088f9953 100644 --- a/deb/graph.go +++ b/deb/graph.go @@ -7,13 +7,28 @@ import ( ) // BuildGraph generates graph contents from aptly object database -func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, error) { +func BuildGraph(collectionFactory *CollectionFactory, layout string) (gographviz.Interface, error) { var err error graph := gographviz.NewEscape() graph.SetDir(true) graph.SetName("aptly") + var labelStart string + var labelEnd string + + switch layout { + case "vertical": + graph.AddAttr("aptly", "rankdir", "LR") + labelStart = "" + labelEnd = "" + case "horizontal": + fallthrough + default: + labelStart = "{" + labelEnd = "}" + } + existingNodes := map[string]bool{} err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error { @@ -26,9 +41,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err "shape": "Mrecord", "style": "filled", "fillcolor": "darkgoldenrod1", - "label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}", - repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "), - strings.Join(repo.Architectures, ", "), repo.NumPackages()), + "label": fmt.Sprintf("%sMirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d%s", labelStart, repo.Name, repo.ArchiveRoot, + repo.Distribution, strings.Join(repo.Components, ", "), + strings.Join(repo.Architectures, ", "), repo.NumPackages(), labelEnd), }) existingNodes[repo.UUID] = true return nil @@ -48,8 +63,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err "shape": "Mrecord", "style": "filled", "fillcolor": "mediumseagreen", - "label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}", - repo.Name, repo.Comment, repo.NumPackages()), + "label": fmt.Sprintf("%sRepo %s|comment: %s|pkgs: %d%s", labelStart, + repo.Name, repo.Comment, repo.NumPackages(), labelEnd), }) existingNodes[repo.UUID] = true return nil @@ -79,7 +94,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err "shape": "Mrecord", "style": "filled", "fillcolor": "cadetblue1", - "label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()), + "label": fmt.Sprintf("%sSnapshot %s|%s|pkgs: %d%s", labelStart, + snapshot.Name, description, snapshot.NumPackages(), labelEnd), }) if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" { @@ -102,8 +118,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err "shape": "Mrecord", "style": "filled", "fillcolor": "darkolivegreen1", - "label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, - strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")), + "label": fmt.Sprintf("%sPublished %s/%s|comp: %s|arch: %s%s", labelStart, + repo.Prefix, repo.Distribution, strings.Join(repo.Components(), " "), + strings.Join(repo.Architectures, ", "), labelEnd), }) for _, uuid := range repo.Sources { diff --git a/system/lib.py b/system/lib.py index d0240f6a..928109f2 100644 --- a/system/lib.py +++ b/system/lib.py @@ -270,6 +270,14 @@ class BaseTest(object): if a != b: self.verify_match(a, b, match_prepare=pprint.pformat) + def check_ge(self, a, b): + if not a >= b: + raise Exception("%s is not greater or equal to %s" % (a, b)) + + def check_gt(self, a, b): + if not a > b: + raise Exception("%s is not greater to %s" % (a, b)) + def check_in(self, item, l): if not item in l: raise Exception("item %r not in %r", item, l) diff --git a/system/t12_api/graph.py b/system/t12_api/graph.py index bc024fc2..bf84ec4c 100644 --- a/system/t12_api/graph.py +++ b/system/t12_api/graph.py @@ -1,4 +1,5 @@ from api_lib import APITest +import xml.etree.ElementTree as ET class GraphAPITest(APITest): @@ -19,3 +20,28 @@ class GraphAPITest(APITest): resp = self.get("/api/graph.dot") self.check_equal(resp.headers["Content-Type"], "text/plain; charset=utf-8") self.check_equal(resp.content[:13], 'digraph aptly') + + # basic test of layout: + # horizontal should be wider than vertical + # vertical should be higher than horizontal + # for this to work we need at couple of repos + tempRepos = [self.random_name() for r in range(3)] + for repo in tempRepos: + self.check_equal(self.post("/api/repos", json={"Name": repo, "Comment": "graph test repo"}).status_code, 201) + + horizontal = self.get("/api/graph.svg?layout=horizontal").content + vertical = self.get("/api/graph.svg?layout=vertical").content + horizontalWidth = int(ET.fromstring(horizontal).get('width').replace("pt","")) + horizontalHeight = int(ET.fromstring(horizontal).get('height').replace("pt","")) + verticalWidth = int(ET.fromstring(vertical).get('width').replace("pt","")) + verticalHeight = int(ET.fromstring(vertical).get('height').replace("pt","")) + + self.check_gt(horizontalWidth, verticalWidth) + self.check_gt(verticalHeight, horizontalHeight) + + # make sure our default layout is horizontal + self.check_equal(horizontal, self.get("/api/graph.svg").content) + + # remove the repos again + for repo in tempRepos: + self.check_equal(self.delete("/api/repos/" + repo, params={"force": "1"}).status_code, 200)