Better living through user scripts

The best feature of the web is the fact that once a page is served to your client, it’s yours.  There’s no such thing as a closed-source web page, only one that you haven’t looked at.  Better yet, any reasonably modern browser can inject your custom CSS and javascript into a web page after it’s been downloaded, to extend or enhance it any way you see fit.  As a celebration of the open web, here are three client-side additions to JIRA’s Greenhopper that I wrote to make my daily life better.

Don’t Resolve Tasks

Our Greenhopper workflow deals mostly with Stories and Tasks.  Both of those are stored as JIRA issues, so they can both be either Closed or Resolved.  The way we do things, it doesn’t make sense to Resolve a task, only ever Close it.  This short bit of CSS will remove the option to Resolve a task when it gets dragged into the Done column:

.aui-popup .gh-aui ul li
.aui-popup .gh-aui ul :first-child

Highlight Tasks By User

Greenhopper sets different colors on different issue types.  This is nice, but I realized that what I really want is to be able to quickly distinguish tasks by who’s working on them, rather than what they are.  The following javascript will run through the Task Board and assign classes to each subtask based on who’s assigned to it (and run again whenever the page updates from dragging an issues):

function addClasses() {
    var subtasks = document.getElementsByClassName("gh-issue");

    for (var i = 0; i < subtasks.length; i++) {
        var issue_body = subtasks[i].children[0].children[1];
        var assignee = null;
        for (var j = 0; j < issue_body.children.length; j++) {
            if (issue_body.children[j].getAttribute('data-fieldid') == 'assignee') {
                assignee = issue_body.children[j].innerText.replace(' ', '').replace('\n', '').toLowerCase();

        if (assignee != null && subtasks[i].children[0].className.indexOf(assignee) < 0) {
            subtasks[i].children[0].className += " " + assignee;

document.addEventListener("DOMNodeInserted", addClasses);

Once that’s in place, I can add a little CSS to style different users.  For example, this turns all issues assigned to me green and bold: {
  background-color:#384 !important;

Colored Columns

Our Greenhopper task board has five columns at the moment, and there’s enough issues in it that when I scroll down past the column headers I can lose track of which issues are in which column based solely on their position.  This script sets the background color of each column section in the task board to a different color to make it easier:

function colorColumns() {
    var colors = ["#e0e0d7", "#cca36e", "#614a48", "#f5e1a4", "#99948b" ]
    var cols = document.getElementsByClassName("gh-step-col")
    for (var i = 0; i < cols.length; i++) {
document.addEventListener("DOMNodeInserted", colorColumns);

All the code here can be installed in your very own browser either by default (Chrome handles .user.js files as extensions) or with browser add-ons like Stylish and Greasemonkey

Related posts:

One thought on “Better living through user scripts”

  1. Hi Sean,

    We (Atlassian) have a thing called speakeasy extensions that allow you to easily override client side resources. Check out the docs here:

    We use it a lot internally to experiment with new features and ideas.

    You could also write your own full-blown JIRA plugin to easily inject these scripts into the page. The process is a little more involved. I’m happy to help if you have any questions.


Comments are closed.