Visualizing Branch Dependencies with Google Sheets and the GitHub API

Naomi Anna Jacobs
Kantata Product Development
7 min readApr 11, 2018

--

Visualizing branch dependencies—by Rob Levin

Here at Mavenlink, my team keeps many small branches off of master for our feature development. Our goal is to ship small features as frequently as possible, and so we often branch new feature branches off of other feature branches that are currently in development. This leads to having multiple branch chains off of master, which can be very time-consuming to keep track of and understand. For example, the branch cool-new-feature might be branched off of master, and then cool-new-feature-followup is branched off of cool-new-feature This means we have a chain that looks like: master <- cool-new-feature <- cool-new-feature-followup, and the pull request for cool-new-feature should be merged into master before cool-new-feature-followup is merged into master. We use GitHub to group our team’s pull requests together, and we want a way to visually see what pull requests we have open, what needs to be worked on, and what branches are highest priority.

TL;DR: If you want an easy visualization of your pull requests in a Google Sheet, go to the repo. Otherwise, read on to see how we did it!

Before this saga, we used GitHub’s own milestone view to see our Pull Requests, but it has drawbacks:

The main problem is that I can’t see which branches are off of master, and which branches are off of each other. This makes it difficult to easily identify the high-priority branches (the ones closest to master). So, my team started using a Google Drawing to manually keep track of our PR tree.

Every time we merged or created a branch, we had to update the spreadsheet with the branch name, title, and link. It took a ton of time to maintain, so I wanted to automate the process. I knew GitHub’s GraphQL API and corresponding explorer were powerful and flexible, and I knew we could write a script to render stuff into a Google Spreadsheet. I didn’t want to create yet another document I’d have to keep track of (I already have too many pinned tabs), so I wanted to target a spreadsheet that the team already used frequently. Because we are a pair programming shop and want to rotate our pairs frequently, we keep track of our pairs and daily priorities in a shared spreadsheet for our standup, which was a perfect candidate.

Another engineer on the team was also excited to automate the process, so we spent a Friday morning building it. It was fun and surprisingly easy to get it working. If you want to jump straight to the code, the repo is here. The explanation is below (note: the code is modified from the final version, so not all variables are defined):

Enter Google Apps Scripts! I had never worked with them before. Google Apps Scripts is an environment where you can write Javascript files to interact with other Google products (like Sheets, Docs, etc.). Some methods are different than what you’re used to in a normal browser: making network calls is synchronous and you have to use special Google-provided methods for lots of things that browsers give you by default. (For more information, read the docs.) A script bound to a Google Spreadsheet can modify the cells in the sheet using Javascript. We planned to render our Pull Request tree to a tab in our pairing schedule.

Next up was the GitHub API , which exposes everything we need. The GraphQL query below fetches all of the information necessary to get all of our pull requests:

This grabs the last 100 open PRs on our milestone, and their titles, urls, branch names (headRefName), and parent PR’s branch name (baseRefName). Assuming all of the PRs in our tree are on our milestone, that’s all we need to construct our tree. Because GitHub’s API returns an array of PRs, we will need to reconstruct the tree out of parent branch references, so if any of our milestone’s PR’s parents are not also in the milestone, our tree construction will break.

We fetch the PRs and iterate through them to build a tree with the root node being the master branch. Here’s the node class we made:

Fun fact: Google Scripts doesn’t support all ES6 features yet (including class), so it was back to good old ES5 prototypal inheritance here.

In order to render our tree, we start at master and iterate recursively through the PRs, rendering each one to the spreadsheet with the Google Spreadsheets API. We figure out which cell to target for rendering by saying “increment my column by one from my parent, and increment my row by (my older sibling’s row + the number of leaf nodes my older sibling has)”. Then, if a parent PR has multiple children, we merge it with the correct number of cells below so it spans across all of its descendants:

We render the branch by calling displayNode(sheet, 0, 0, masterNode) where sheet is the Google Spreadsheet (accessed earlier, not shown), and masterNode is a PullRequest with references to all its children PRs.

Seen below is the result. That gets us a lot of the way to where we want to be: column A is master. The next column to the right lists all of the branches that are pointing to master, and so on, so that each branch will be merged into the cell to its left. Branches further to the left are higher priority. Branches like Rpm per page feature master that have two children span multiple rows, which means it’s easy to see that Rpm — Per Page — focus styles is branched off of Rpm per page feature master.

Works, but not pretty or easy to read :(

We’re getting there, but this view is hard to read and I want 🎨PRETTY COLORS🎨 in my spreadsheets, so let’s color code each PR’s cell based on its status. What is its status? Great question! This will vary based on any team’s processes, but we wanted our statuses to indicate when action was possible for any given PR. Thus:

  • Green (ready to ship) if it passed our CI and has been reviewed and QA’d
  • Red (there’s a problem, needs attention) if we have failing specs, requested changes from a review, a rejected story from QA, or it doesn’t have the correct labels
  • Yellow (it’s out of date, pull it through!) if it’s been open longer than 5 days — this was a choice we made to encourage our team to pull through open PRs faster and focus on tight iteration cycles
  • Pink if it has the Blocked label
  • Purple if it has the WIP label
  • Gray (no immediate action necessary) if none of the above apply

Calculating the status meant asking for more data from GitHub for each PR — we needed to know its test status and its labels. Because we integrate GitHub with our CI, every time we push to GitHub we run our tests, and the test result is associated with the last pushed commit. Thus, the test status of the PR as a whole is the status of the last commit on the PR. The final query looked like this:

Finally, we set the background color of the cell based on its status, add a legend, and voilà!

Color-coded Pull Request Visualization — the final view

We can update the view at any time by clicking the Mavenlink menu item and selecting Branch Strategy:

Easy refreshing!

At this point, everything was working, but was in a single giant file, which was difficult to navigate and understand, plus, nothing was testable! While Google Scripts does have some testing available, the tooling isn’t easy to use and is difficult for scripts that are bound to a single spreadsheet, as this one is. Additionally, there’s no importing or module system in Google Scripts, so splitting up the code into testable/comprehensible chunks is tricky. Oh, CommonJS and Webpack, how I miss thee!

Finally, we knew other teams would want to use this visualization for their own milestones, but we didn’t have a great way of sharing this other than copying and pasting everything.

Enter GitHub and eval to the rescue! (yes, really). I pulled all the code out into a GitHub repo and extracted the milestone-specific information into variables that could be passed into generic functions. Then, all someone else has to do to use it is copy the following snippet into their own script attached to a Google Sheet and fill in the relevant variables:

Now, we’re treating GitHub as a CDN hosting the data fetching and rendering code. We use eval to fetch the raw JavaScript file. Each file is wrapped in an IIFE (which is, not coincidentally, also how Webpack compiles your bundles), which we eval. Then you add the correct triggers to the spreadsheet, and you’re done!

Now, every time we do standup in the morning, we can flip to the branch tab, hit refresh, and immediately see an up-to-date dashboard of everything our team is working on.

Again, you can find all of the code here. Feel free to use this for your own team or fork it and add your own conditions for PR state. Add your feedback in the comments below. Additionally, if you want to see the cool stuff my team made with the branch strategies above, check out Mavenlink’s Resource Management! Thanks for reading!

--

--