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
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):
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.
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
- Purple if it has the
- 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à!
We can update the view at any time by clicking the
Mavenlink menu item and selecting
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. 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!