Merge pull request #475 from jola5/master

Support a vertical graph layout in addition to the existing horizontal
This commit is contained in:
Andrey Smirnov
2017-01-20 23:41:56 +03:00
committed by GitHub
6 changed files with 69 additions and 12 deletions
+1
View File
@@ -24,3 +24,4 @@ List of contributors, in chronological order:
* Geoffrey Thomas (https://github.com/geofft) * Geoffrey Thomas (https://github.com/geofft)
* Oliver Sauder (https://github.com/sliverc) * Oliver Sauder (https://github.com/sliverc)
* Harald Sitter (https://github.com/apachelogger) * Harald Sitter (https://github.com/apachelogger)
* Johannes Layher (https://github.com/jola5)
+3 -2
View File
@@ -11,7 +11,7 @@ import (
"os/exec" "os/exec"
) )
// GET /api/graph.:ext // GET /api/graph.:ext?layout=[vertical|horizontal(default)]
func apiGraph(c *gin.Context) { func apiGraph(c *gin.Context) {
var ( var (
err error err error
@@ -19,6 +19,7 @@ func apiGraph(c *gin.Context) {
) )
ext := c.Params.ByName("ext") ext := c.Params.ByName("ext")
layout := c.Request.URL.Query().Get("layout")
factory := context.CollectionFactory() factory := context.CollectionFactory()
@@ -31,7 +32,7 @@ func apiGraph(c *gin.Context) {
factory.PublishedRepoCollection().RLock() factory.PublishedRepoCollection().RLock()
defer factory.PublishedRepoCollection().RUnlock() defer factory.PublishedRepoCollection().RUnlock()
graph, err := deb.BuildGraph(factory) graph, err := deb.BuildGraph(factory, layout)
if err != nil { if err != nil {
c.JSON(500, err) c.JSON(500, err)
return return
+5 -1
View File
@@ -21,8 +21,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
layout := context.Flags().Lookup("layout").Value.String()
fmt.Printf("Generating graph...\n") fmt.Printf("Generating graph...\n")
graph, err := deb.BuildGraph(context.CollectionFactory()) graph, err := deb.BuildGraph(context.CollectionFactory(), layout)
if err != nil { if err != nil {
return err return err
} }
@@ -108,6 +111,7 @@ Example:
cmd.Flag.String("format", "png", "render graph to specified format (png, svg, pdf, etc.)") 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("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 return cmd
} }
+26 -9
View File
@@ -7,13 +7,28 @@ import (
) )
// BuildGraph generates graph contents from aptly object database // 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 var err error
graph := gographviz.NewEscape() graph := gographviz.NewEscape()
graph.SetDir(true) graph.SetDir(true)
graph.SetName("aptly") 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{} existingNodes := map[string]bool{}
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error { err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error {
@@ -26,9 +41,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "darkgoldenrod1", "fillcolor": "darkgoldenrod1",
"label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}", "label": fmt.Sprintf("%sMirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d%s", labelStart, repo.Name, repo.ArchiveRoot,
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "), repo.Distribution, strings.Join(repo.Components, ", "),
strings.Join(repo.Architectures, ", "), repo.NumPackages()), strings.Join(repo.Architectures, ", "), repo.NumPackages(), labelEnd),
}) })
existingNodes[repo.UUID] = true existingNodes[repo.UUID] = true
return nil return nil
@@ -48,8 +63,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "mediumseagreen", "fillcolor": "mediumseagreen",
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}", "label": fmt.Sprintf("%sRepo %s|comment: %s|pkgs: %d%s", labelStart,
repo.Name, repo.Comment, repo.NumPackages()), repo.Name, repo.Comment, repo.NumPackages(), labelEnd),
}) })
existingNodes[repo.UUID] = true existingNodes[repo.UUID] = true
return nil return nil
@@ -79,7 +94,8 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "cadetblue1", "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" { if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
@@ -102,8 +118,9 @@ func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, err
"shape": "Mrecord", "shape": "Mrecord",
"style": "filled", "style": "filled",
"fillcolor": "darkolivegreen1", "fillcolor": "darkolivegreen1",
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, "label": fmt.Sprintf("%sPublished %s/%s|comp: %s|arch: %s%s", labelStart,
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")), repo.Prefix, repo.Distribution, strings.Join(repo.Components(), " "),
strings.Join(repo.Architectures, ", "), labelEnd),
}) })
for _, uuid := range repo.Sources { for _, uuid := range repo.Sources {
+8
View File
@@ -270,6 +270,14 @@ class BaseTest(object):
if a != b: if a != b:
self.verify_match(a, b, match_prepare=pprint.pformat) 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): def check_in(self, item, l):
if not item in l: if not item in l:
raise Exception("item %r not in %r", item, l) raise Exception("item %r not in %r", item, l)
+26
View File
@@ -1,4 +1,5 @@
from api_lib import APITest from api_lib import APITest
import xml.etree.ElementTree as ET
class GraphAPITest(APITest): class GraphAPITest(APITest):
@@ -19,3 +20,28 @@ class GraphAPITest(APITest):
resp = self.get("/api/graph.dot") resp = self.get("/api/graph.dot")
self.check_equal(resp.headers["Content-Type"], "text/plain; charset=utf-8") self.check_equal(resp.headers["Content-Type"], "text/plain; charset=utf-8")
self.check_equal(resp.content[:13], 'digraph aptly') 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)