Skip to content

Commit

Permalink
more UI tweaks and fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
adonovan committed Mar 31, 2021
1 parent 6346da2 commit 7f8b1bf
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 53 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ at `localhost:18080`.
This tool is a rewrite from scratch of a tool I wrote while at Google
for exploring the dependency graphs used by the Blaze build system.
The basic design could be easily be generalized to support any kind of
dependency graph, independent of language or domain.
dependency graph, independent of language or domain, or turned into a
secure multi-user web service that operates on graph data uploaded from
the client program that generates it.

You can probably tell that web UIs are not my expertise.
PRs that provide cosmetic improvements are most welcome!
42 changes: 28 additions & 14 deletions code.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ var broken = null // array of 2-arrays of int, the node ids of broken edges.

function onLoad() {
// Grab data from server: package graph, "directory" tree, broken edges.
// TODO(adonovan): opt: put JSON data in the /index.html page?
// There's no need for a second HTTP request.
jQuery.ajax({url: "/data", success: onData})
}

Expand Down Expand Up @@ -76,36 +74,44 @@ function onData(data) {
function selectPkg(json) {
if (json.Package < 0) {
// Non-package "directory" node: clear the fields.
$('#json').text("")
$('#pkgname').text("none")
$('#doc').text("")
$('#imports').text("")
$('#imports').html("")
$('#path').text("")
return
}

// A package node was selected.
var pkg = packages[json.Package]

// Show JSON, for debugging.
$('#json').html("<code>" + JSON.stringify(json) + "</code>")

// Show selected package.
$('#pkgname').text(pkg.PkgPath)

// Set link to package documentation.
$('#doc').html("<a target='_blank' href='https://pkg.go.dev/" + pkg.PkgPath + "'>doc</a>")

// TODO(adonovan): display imports as a set of links,
// with as ImportPath text and "select dir tree node" as action.
$('#imports').text(json.Imports.join(" "))
// Set link to Go package documentation.
$('#doc').html("<a title='doc' target='_blank' href='https://pkg.go.dev/" + pkg.PkgPath + "'><img src='https://pkg.go.dev/favicon.ico' width='16' height='16'/></a>")

// Show imports in a drop-down menu.
// Selecting an import acts like clicking on that package in the tree.
var imports = $('#imports')
imports.html("")
var option = document.createElement("option")
option.textContent = "..."
option.value = "-1"
imports.append(option)
for (var i in json.Imports) {
var imp = json.Imports[i]
option = document.createElement("option")
option.textContent = packages[imp].PkgPath
option.value = "" + imp // package index, used by onSelectImport
imports.append(option)
}

// Show "break edges" buttons.
var html = ""
var path = [].concat(json.Path).reverse() // from root to selected package
for (var i in path) {
var p = packages[path[i]]
if (i >= 0) {
if (i > 0) {
html += "<button type='button' onclick='breakedge(" + path[i-1] + ", " + path[i] + ", false)'>break</button> "
+ "<button type='button' onclick='breakedge(" + path[i-1] + ", " + path[i] + ", true)'>break all</button> "
+ "⟶ "
Expand All @@ -124,3 +130,11 @@ function unbreak(i, j) {
// Must reload the page since the graph has changed.
document.location = "/unbreak?from=" + i + "&to=" + j
}

// onSelectImport is called by the imports dropdown.
function onSelectImport(sel) {
if (sel.value >= 0) {
// Simulate a click on a tree node corresponding to the selected import.
$('#tree').jstree('select_node', 'node' + sel.value);
}
}
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/adonovan/spaghetti
go 1.16

require (
github.com/google/renameio v0.1.0 // indirect
golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399 // indirect
golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399
golang.org/x/tools/gopls v0.6.9 // indirect
)
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
github.com/jba/templatecheck v0.5.0/go.mod h1:/1k7EajoSErFI9GLHAsiIJEaNLt3ALKNw2TV7z2SYv4=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
Expand All @@ -26,7 +25,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
Expand All @@ -40,7 +38,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -49,7 +46,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399 h1:O5bm8buX/OaamnfcBrkjn0SPUIU30jFmaS8lP+ikkxs=
golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
Expand Down
51 changes: 26 additions & 25 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Spaghetti</title>
<title>Spaghetti: dependency analysis for Go packages</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Lato" />
<link rel="stylesheet" type="text/css" href="/style.css" />
Expand All @@ -10,61 +10,62 @@
<script src="/code.js"></script>
</head>
<body onload='onLoad()'>
<h1>Spaghetti</h1>
<h1>Spaghetti: dependency analysis for Go packages</h1>
<p>
This tool displays the complete dependencies of these initial packages:
</p>
<pre id='initial'>...</pre>
<p>
Click on a package in the tree view to display information about
it, including a path by which it is reached from one of the
initial packages. Use the <i>break</i> button to remove an
edge from the graph, so that you can assess what edges need to be
broken to remove an unwanted dependency.
Click on a package in the tree view to display information about it,
including a path by which it is reached from one of the initial packages.

Use the <i>break</i> button to remove an edge from the graph, so
that you can assess what edges need to be broken to eliminate an
unwanted dependency.
</p>

<h2>Packages</h2>
<p>
<form style='display: inline;'>
<span class="tooltip"><span class="tooltiptext">
This tree shows all dependencies of the initial packages,
grouped hierarchically by import path and containing
module. <br/>
module. <br/><br/>

Each package has a numeric weight, computed using network
flow: this is the size of the graph rooted at the node,
divided by the node's in-degree. <br/>
divided by the node's in-degree. <br/><br/>

Click a package to show more information aboutit.</span></span>
<form style='display: inline;'>
Click a package to show more information about it.
</span></span>
<label for='search'>Filter:
<input id='search' type='text' name='filter' size='50'/></label>
<input id='search' type='text' name='filter' size='70'/></label>
<button onclick='this.form.search.value=""'>Clear</button>
</form>
</p>
<div style='overflow: auto; width: 65%; height: 20em; border: thin solid grey; display: inline-block'>
<div style='height: 20em;' id='tree'>tree</div>
</div>

<h2>Selected package: <span id='pkgname'>none</span></h2>
<div id='json' style='display: none; overflow: scroll'>...</div> <!-- debugging -->
<p>
[<span id='doc'>...</span>]<br/>
</p>
<h2>Selected package: <span id='pkgname'>none</span>&nbsp;&nbsp;&nbsp;<span id='doc'></span></h2>
<p>
<span class="tooltip"><span class="tooltiptext">This list shows the packages
directly imported by the selected package</span></span>
<label for='imports'>Imports:</label>
<code id='imports'>...</code>
<label for='imports'>Imports: </label>
<select id='imports' onchange='onSelectImport(this)'></select>
</p>
<p>
<span class="tooltip"><span class="tooltiptext">This section
displays an arbitrary path from one of the initial packages to
the selected package. Click the <i>break</i> button so see how
your dependencies would change if you were to remove a single
edge. Click <i>break all</i> to remove all inbound edges to a
<span class="tooltip"><span class="tooltiptext">
This section displays an arbitrary path from one of the
initial packages to the selected package. Click
the <i>break</i> button so see how your dependencies would
change if you were to remove a single edge.<br/><br/>

Click <i>break all</i> to remove all inbound edges to a
package, removing it from the graph. This may be useful for
removing distracting packages that you don't plan to
eliminate.
eliminate.<br/><br/>

The bold nodes are <i>dominators</i>: nodes that are found on
every path to the selected node. One way to break a dependency
on a package is to break all dependencies on any of its dominators.
Expand Down
8 changes: 4 additions & 4 deletions spaghetti.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func main() {
http.Handle("/", http.FileServer(http.FS(content)))

const addr = "localhost:18080"
log.Printf("Listening on %s...", addr)
log.Printf("listening on http://%s", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -256,7 +256,7 @@ func onData(w http.ResponseWriter, req *http.Request) {
// Any additional fields will be accessible
// in the jstree node's .original field.
Package int // -1 for non-package nodes
Imports []string
Imports []int
Dominators []int // path through dom tree, from package to root inclusive
Path []int // path through package graph, from package to root inclusive
}
Expand Down Expand Up @@ -306,9 +306,9 @@ func onData(w http.ResponseWriter, req *http.Request) {
// item.State = { 'opened' : true, 'selected' : true }

item.Package = e.node.index
item.Imports = []string{} // avoid JSON null
item.Imports = []int{} // avoid JSON null
for _, imp := range e.node.imports {
item.Imports = append(item.Imports, imp.Package.ID)
item.Imports = append(item.Imports, imp.index)
}
for n := e.node; n != nil; n = n.Idom() {
item.Dominators = append(item.Dominators, n.index)
Expand Down
6 changes: 3 additions & 3 deletions style.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
body { margin: 1em; font-family: "Minion Pro", serif; }
body { margin: 1em; font-family: "Minion Pro", serif; max-width: 1024px; }
h1, h2, h3, h4, .jstree-default { font-family: Lato; }
code, pre { font-family: Consolas, monospace; }
code.dom { font-weight: bold; }
button { font-size: 50%; }
pre#imports { font-size: 50%; max-height: 3em; overflow: scroll; }
button { font-size: 90%; }
pre#initial { margin-left: 1em; }

/* the ⓘ symbol */
.tooltip {
Expand Down

0 comments on commit 7f8b1bf

Please sign in to comment.