Building our Kanban Board

Building a JIRA Board Widget, Pt 3

Welcome back! If you've checked out the projects section lately, you may have noticed something new. Go ahead, check it out. I'll wait here.

That's right, a real life Kanban board! This board has been created using nothing but front-end JavaScript, thanks to our friends at MongoDB. We're using Stitch to populate our board via issues we've uploaded to our MongoDB Atlas database. If you'll recall, we use Tableau prior to this: the end goal is to create a workflow where:

  • Tableau is functioning as a GUI for filtering issues
  • MongoDB stores the output
  • Changes in data are automatically synced into Tableau and populated into MongoDB
  • Displaying the output on our board

Today we'll have covered items 1, 2, and 4.

More to Merge

While creating this board and completely dismantling all Atlassian design principals, it dawned on me: how are we going to let people specify their own images and colors for issue types? Since we're going through all the effort of creating this board, chances are we want it to match a certain visual style.

We're going to need Tableau for this.

First things first, let's create a new CSV to hold this data. we're going to create an empty sheet to list all of our Issue Types:

issuetype issuetype_image color
Bug
Content
Data
Epic
Idea
Integration
Story
Task
Sub-Task

Yeah, there are a lot of custom issue types in our project. Don't judge me.

For each issue type, I want to specify two things: the color to be associated with each issue type, as well as an image to be associated with each issue type.

Whoa, why are we doing this again?

Alright, alright, fine. Maybe you caught be trying to rush through a post. I should know better.

We're doing this because we want to recreate issuetype icons to bend to our will. For example, in our JIRA instance, our "data" issuetype has this icon:

JIRA data issue type

This fix would allow us to create a more relevant icon, such as the following:

For somebody who's supposed to be technical, you sure sacrifice a lot of time and effort for dumb design discussions

Shut up. Fill out your CSV with values such as these:

issuetype issuetype_image color
Bug https://hackers.nyc3.digitaloceanspaces.com/bug.png #c36666
Content https://hackers.nyc3.digitaloceanspaces.com/content.png #9e85bb
Data https://hackers.nyc3.digitaloceanspaces.com/data.png #987ead
Epic https://hackers.nyc3.digitaloceanspaces.com/epic.png #685d7b
Idea https://hackers.nyc3.digitaloceanspaces.com/idea.png #eabb7c
Integration https://hackers.nyc3.digitaloceanspaces.com/integration.png #f28b8a
Story https://hackers.nyc3.digitaloceanspaces.com/story.png #b6d49c
Task https://hackers.nyc3.digitaloceanspaces.com/task.png #92BFE5
Sub-Task https://hackers.nyc3.digitaloceanspaces.com/subtask.png #c36666

                                                               

We're linking to images in a CDN and providing colors as hex values; this way, we can programmatically add these styles to whatever we're building.

Now we're ready for Tableau. We're going to add a second inner merge to our data, so now we're filling in the blanks for both epics as well as issues.

Tableau Merge Inner
Visually executing merges is pretty cool.

Export that sheet with with our new values and import that bad boy into MongoDB.

Query Que Sí

Ready to build? Awesome. We're going make the board by creating a 4-column flexbox. If you actually care about styles, I've made the .less publicly available here: https://hackers.nyc3.digitaloceanspaces.com/kanban.less

Here's the real code:

function populateCards(cards, status) {
    for (var i = 0; i < cards.length; i++) {
      $('#' + status + ' .cards').append('<div class="card"> \n' +
        '<h5>' + cards[i].summary + '</h5> \n' +
        '<p>' + cards[i].description + '</p> \n' +
        '<div style="background-color:' + cards[i].issuetype_color + ';" class="issuetype ' + cards[i].issuetype + '"><img src="' + cards[i].issuetype_url + '"></div> \n' +
        '<div class="info"> \n' +
          '<div class="left"> \n' +
            '<div class="avatar"><img src="https://www.gravatar.com/avatar/9eb3868db428fb602e03b3059608199b?s=250&d=mm&r=x"></div> \n' +
            '<div class="priority ' + cards[i].priority + '"><i class="fas fa-arrow-up"></i></div> \n' +
          '</div> \n' +
          '<div style="background-color:' + cards[i].epic_color + ';" class="epic ' + cards[i].epic_name + '"><span>' + cards[i].epic_name + '</span></div> \n' +
        '</div> \n' +
      '</div>');
    }
}

const clientPromise = stitch.StitchClientFactory.create('hackerjira-bzmfe');

clientPromise.then(client => {
    const db = client.service('mongodb', 'mongodb-atlas').db('HackersBlog');
    client.login().then(() =>
      db.collection('jira').find({status: 'Backlog', issuetype: { $in: ['Task', 'Story', 'Integrations', 'Bug']}}).limit(6).sort({ updated: -1 }).execute()
    ).then(docs => {
      populateCards(docs, 'backlog')
    }).then(() =>
      db.collection('jira').find({status: 'To Do', issuetype: { $in: ['Task', 'Story', 'Integrations', 'Bug']}}).limit(6).sort({ updated: -1 }).execute()
    ).then(docs => {
      populateCards(docs, 'todo')
    }).then(() =>
      db.collection('jira').find({status: 'In Progress'}).limit(6).sort({ updated: -1 }).execute()
    ).then(docs => {
      populateCards(docs, 'progress')
    }).then(() =>
      db.collection('jira').find({status: 'Done', issuetype: { $in: ['Task', 'Story', 'Integrations', 'Bug']}}).limit(6).sort({ updated: -1 }).execute()
    ).then(docs => {
      populateCards(docs, 'done')
    }).catch(err => {
      console.error(err)
    });
  });

We're making 4 queries: one per column of the Kanban board. Let's look at the first query to see what's happening:

db.collection('jira').find({
    // get issues which are in the backlog
    status: 'Backlog',
    // also, limit results so that they are within these 4 issue types:
    issuetype: { 
        $in: ['Task', 'Story', 'Integrations', 'Bug']
      }
})
// limit of max 6 issues
.limit(6)
// sort by most recently updated (desc)
.sort({ updated: -1 })
// run query
.execute()

Super simple stuff. Because we're running 4 queries, it also allows us to run different queries per column. For example, we have a lot of issues in our backlog, so it makes sense to add filters to, say, only return items of high priority. Our board only wants to fit 6 issues per column, otherwise things start getting a little dirty.

That's pretty much it

I'm sorry, I wish this were more difficult. There's only one part left: automating all of this stuff. Coming soon. Kind of. Probably not though. Coming at some point.

Author image
New York City Website
Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.

Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.