jdoklovic.jpgThis is a guest blog post submitted by Jonathan Doklovic. Jonathan is Founder and CEO of Systems Bliss Inc., an Atlassian Partner. Jonathan has been using Atlassian tools and writing plugins for them for over 5 years and has over 25 years of development experience.
His goal is to blissfully skip through a field filled with fluffy bunnies while automated systems are doing his bidding.

Visualising Bamboo Dependencies with GraphViz

Bamboo has a feature that allows you to link builds together by defining parent child relationships between them. While this is a very useful feature during build time, Bamboo doesn’t ship with anyway to view the dependency graph which can make it difficult to manage large complex build structures.

To solve this, Sysbliss created the Bamboo Project Graph Plugin which provides a visual representation of the dependency graph for a given project right within the Bamboo interface.

Project Graph Features

  • Visually see project dependency tree
  • Includes cross-project dependencies
  • Nodes display project and build name
  • Includes project name on image (for printing)
  • Anonymous user access
  • Graph is clickable to other graphs

Accessing The Project Graph

The project graph is accessible by all users (including anonymous) by simply clicking through to a Project or Build screen within Bamboo.

The plugin adds a new top-level tab to access the graph.

bamboo-tabs.png

This is accomplished within the plugin by using the xwork plugin component to define an action and adding a web-item component to create the tab for the action.

<package name="graphvizDependencies" extends="buildView" namespace="/build/graphviz">
<action name="viewGraphvizDependencies" class="com.sysbliss.bamboo.plugins.graphviz.action.ViewDependencyGraph" method="default">
<result name="success" type="freemarker">/templates/graphvizViewAction/graphvizResults.ftl</result>
<result name="error" type="freemarker">/error.ftl</result>
</action>
...
<web-item key="graphvizDependencies:${buildKey}" name="graphvizDependencies" section="build.subMenu/build" weight="130">
<label key="Project Graph"/>
<link linkId="graphvizDependencies:${buildKey}">/build/graphviz/viewGraphvizDependencies.action?buildKey=${buildKey}</link>
</web-item>

note that the section attribute on the above web-item tag tells Bamboo where to put the tab

Configuring GraphViz

To generate the actual graph image, we use the wildly popular GraphViz tools.

GraphViz is a set of command-line tools that accept a dot language file, generate a graph layout, and write the final graph out in various formats. With this in mind, the first thing we need to do is allow the Bamboo Administrator to configure where GraphViz is installed so that Bamboo can make use of the tools.

To accomplish this, we need to add another xwork action and a web-item, but this time within the Bamboo administration screens.

<package name="graphvizDependenciesAdmin" extends="admin" namespace="/admin/graphviz">
<action name="configureGraphviz" class="com.sysbliss.bamboo.plugins.graphviz.action.ConfigureGraphvizAction" method="default">
<result name="input" type="freemarker">/templates/graphvizAdminAction/configureGraphviz.ftl</result>
<result name="success" type="freemarker">/templates/graphvizAdminAction/configureGraphviz.ftl</result>
<result name="error" type="freemarker">/error.ftl</result>
</action>
</package>
<web-item key="graphvizConfigureDot" name="graphvizConfigureDot" section="system.admin/system" weight="130">
<label key="Graphviz Configuration"/>
<link linkId="graphvizConfigureDot">/admin/graphviz/configureGraphviz.action</link>
</web-item>

You’ll notice in the package tag we’re extending the built-in “admin” package which provides all the security rules for accessing the admin screens. We’re also using the section attribute of the web-item tag to instruct Bamboo to place our link within the left nav of the admin screen.

Our action and it’s corresponding template simply provide a field for the Bamboo Administrator to type in the path to the dot executable provided by GraphViz which then gets saved as a system variable.

Generating The Graph

Now that we have GraphViz configured in Bamboo, we need our ViewDependencyGraph action to generate our graph using GraphViz and display it in the template. To generate the graph, we’re going to need to create the dot language file and pass it to the dot executable with some basic parameters.

To create the dot language file, we chose to use a modified sub-set of the classes provided by the LinguineMaps project.

Linguine Maps is a full featured library that can visualize many text formats (DTD, ORM, schema) using the GraphViz tools. For our purposes, we make use of a small sub-set of the library that contains objects for programatically creating nodes and edges as well as providing utilities for calling GraphViz dot.

To create our object graph, the ViewDependencyGraph action makes use of Bamboo’s PlanDependencyManager to loop through the dependency tree for the given project and create the Node and Edge objects. To save some space, I’m not going to list the entire source, but here’s and example of how to build a simple graph:

final Graph graph = GraphFactory.newGraph();
graph.getInfo().setCaption("Bamboo Project Graph: " + project.getName());
GraphNode buildNode1 = graph.addNode();
buildNode1.getInfo().setCaption("Project1 - Build1");
buildNode1.getInfo().setAttributes("URL='http://bamboo/project graph url");
GraphNode buildNode2 = graph.addNode();
buildNode2.getInfo().setCaption("Project1 - Build2");
buildNode2.getInfo().setAttributes("URL='http://bamboo/another project graph url");
final GraphEdge ge = graph.addEdge(buildNode1, buildNode2);

Notice I’ve included a URL attribute on each node. This points to our same ViewDependencyGraph action url and passes the proper buildKey for the node. This url is used when GraphViz generates the imagemap for our view.

Now we just need to convert our graph object to a dot language file, run GraphViz and turn it into an image with an imagemap. Luckily Linguine Maps provides the foundation for doing this very simply:

final AdministrationConfiguration adminConfig = getAdministrationConfiguration();
final String path2Dot = adminConfig.getSystemProperty(ConfigureGraphvizAction.PATH_TO_DOT_KEY);
GRAPHtoDOTtoIMAP.transform(graph, dotPath, gifPath, mapPath, path2Dot);

The GRAPHtoDOTtoIMAP transformer in the above code does all the operations for us in one step. We just need to pass it our graph object, the paths to the output files and the path to the dot executable as defined by the Bamboo Administrator.

GRAPHtoDOTtoIMAP is a custom class that was built following the same principles as the converters built into Linguine Maps. When it runs, it saves all of the output files to a temporary directory within the bamboo.home folder. Now for the tricky part: How do we display those temporary files from within our Freemarker template?

Displaying The Graph

Since our output files are saved in a non-web accessible directory, we have to figure out a way to display them in our template. Also, since we may want to use some unique identifier in the file names, it would be very difficult to use the WebResourceManager to figure out which files to include in our template.

To solve this problem, we added a couple of extra methods on our ViewDependencyGraph action to return these files as a stream for us:

public InputStream getImageStream() throws FileNotFoundException
{
final String tempPath = homeLocator.getHomePath() + File.separator + TEMP_PATH;
final String gifPath = tempPath + File.separator + GIF_FILE;
final InputStream is = new FileInputStream(new File(gifPath));
return is;
}
public InputStream getImageMapStream() throws FileNotFoundException
{
final String tempPath = homeLocator.getHomePath() + File.separator + TEMP_PATH;
final String gifPath = tempPath + File.separator + MAP_FILE;
final InputStream is = new FileInputStream(new File(gifPath));
return is;
}

As you can see, these methods are pretty simple. They just load our gif and map files and return the InputStreams. So how do we display them in our template? Luckily the XWork framework Bamboo is based on gives us a solution: The result type of an action can be set as a stream.

<action name="viewGraphvizImage" class="com.sysbliss.bamboo.plugins.graphviz.action.ViewDependencyGraph" method="getGraphImage">
<result name="success" type="stream">
<param name="contentType">image/gif</param>
<param name="inputName">imageStream</param>
<param name="bufferSize">1024</param>
</result>
<result name="error" type="freemarker">/error.ftl</result>
</action>
<action name="viewGraphvizImageMap" class="com.sysbliss.bamboo.plugins.graphviz.action.ViewDependencyGraph" method="getGraphImageMap">
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">imageMapStream</param>
<param name="bufferSize">1024</param>
</result>
<result name="error" type="freemarker">/error.ftl</result>
</action>

And now that we have actions that can return streams, we can use the image action directly in our image tag:

<img src="${req.contextPath}/build/graphviz/viewGraphvizImage.action?buildKey=${build.key}" border="0" ismap="ismap" usemap="#ProjectDepsGraph"/>

So now we have our image, and our image is using our map, but how do we include the external map file? Unfortunately the version of Freemarker included with Bamboo does not provide the @include_page directive, but no worries, we can just use a tiny JQuery snippet to do that for us:

<div id="myMap"></div>
<script type="text/javascript">
jQuery("#myMap").load("${req.contextPath}/build/graphviz/viewGraphvizImageMap.action?buildKey=${build.key}");
</script>

And that’s it. With everything in place, we can now render graphs to the user

graph-image.png

Learn More!

You can download the plugin, check compatibility, and find the documentation from the Atlassian Plugin Exchange.