Scripted Fields2
August 27, 2020

How to prioritise issues in Jira using WSJF as Scripted Fields

MB
Mo Backer 7 minute read

One area of agile project management that is proving difficult, even in Jira, is issue prioritisation. How do you appropriately set issue priorities and keep them up to date in Jira when everything is always changing?

In this post we’ll look at a Scaled Agile Framework (SAFe) method for prioritising issues, called WSJF, and then we’ll dive into how to implement this dynamically in Jira using ScriptRunner’s Scripted Fields feature. A code snippet is included to help you get started.

A holistic and agile approach to prioritising issues in Jira: the Weighted Shortest Job First (WSJF) method

In agile teams that manage backlogs and releases continuously, it’s important to decide on a job sequence that maximises output value. Weighted Shortest Job First (WSJF) is designed to prioritise the backlog jobs that provide the most value in the shortest time. This is done by determining:

  1. Cost of delay
  2. Job duration (or size in some cases)

These can then be divided together so that:

  • WSJF ranking = Cost of delay ÷ Job duration

Let’s think about this intuitively. If you have a job which really needs to be done, then the cost of delay is high, so WSJF ranks this highly in prioritisation. If that job is also a short one and can be done quickly, WSJF pushes it up further still in the priority ranking.

How to calculate the cost of delay

If we list every factor that’s important in deciding an issue priority, we will probably come up with the following:

  1. Value to the customer - how much will customers benefit from an issue being progressed? For example, a missing product feature may be consistently requested by customers.
  2. Enablement value - is an issue blocking progress towards other value-add features or technology? An example could be a bug preventing users achieving a task. 
  3. Time criticality - does an issue need a quick resolution? An example might be an outage that requires urgent resolution

If an issue is delayed, these factors are put at risk. In other words, there is a cost to each of the above. For each issue, you can assign the factors a weighting that then makes calculating the cost of delay relatively straightforward:

  • Cost of delay = Value to the customer + Enablement value + Time criticality
How to determine the Job duration

If you feel comfortable predicting the duration of issues, you can use this as the denominator in your WSJF calculation. Perhaps you have a conversion rate between story points and hours which you can use. But it is fair to say that a lot of organisations find it hard to predict exactly how long jobs will take as there are many factors at play. 

In this case, we can use a proxy that is much easier to estimate: the size of the issue. In most cases, as long as the team makeup is consistent, issue size is a good enough proxy for duration. The larger the issue size, the longer it will take to complete. What you use here is up to you and what you are most confident about. The key is to get a relative weighting between issues.

Following the WSJF method, your backlog becomes a continuous, dynamic and prioritised list of jobs adding incremental value, in the shortest time. Of course, the world is not a perfect equation and there’ll be times when an intervention is necessary. For example, a security vulnerability might crop up meaning that the typical backlog prioritisation is put to one side until a fix is deployed (even in this case you could argue that WSJF would rank these jobs highly because of their high cost of delay). But for normal day-to-day backlog prioritisation, WSJF provides a clear, logical and justifiable method for prioritising jobs.

Using ScriptRunner to prioritise issues

As we alluded to earlier, Jira projects, epics and issues are constantly being updated by team members. This is the very reason why Jira is such a great tool, but it also means the backlog is constantly in a state of flux. Discrete prioritisation sessions only keep you up to date for so long. There needs to be a way to continuously update the priority ranking as and when things change elsewhere in your instance. Scripted Fields lets you do exactly this.

What is the Scripted Fields feature?

Scripted Fields lets you show dynamically calculated information on the issue view. For example, let’s say you want to dynamically show the latest USD to GBP exchange rate on an issue. Using Scripted Fields, you can create a field on the issue view that is calculated with a script that pulls the rate from a public API. Every time you open the issue, the latest rate is pulled and shown right there. 

Given what Scripted Fields is capable of, you can see how it is the ideal solution for WSJF prioritisation. There are three main steps to getting this setup on your instance:

  1. Define your WSJF output format - what do you want to see when you click on an issue? You can go about this in a couple of ways:
    • Rank - you may want to rank every single issue: 1, 2, 3 etc. The advantage of this method is that it is decisive in setting the priority. On the other hand, this decisiveness might be too crude if some issues are extremely close in their WSJF ranking
    • Categorisation - the alternative option is to create categories, for example from “Extremely Low” to “Urgent”, each of which corresponds to a WSJF range defined by you. This helps you to keep that human element, as all issues within a category need to ultimately be prioritised over one another by you. The difficulty with this method is knowing where to draw the boundaries for the categories; it might not be immediately obvious
  2. Setup your Field in Scripted Fieldsnow that you know your output format, you’ll know what option you’ll need to pick in Scripted Fields. If you’ve gone for the rank option, you’ll need the numeric field, if you’ve gone with the categories, you’ll need single line text

  3. Write your script - You know what you’re aiming for, so it’s time to implement the logic. Below is an example code snippet for ScriptRunner for Jira Cloud

// ScriptRunner for Jira Cloud example 

// To retrieve a custom fields ID we have to hit the field REST API endpoint
// This will return a Map of field names to field ID's
def customFields = get("/rest/api/2/field")
    .asObject(List)
    .body

// We've defined a closure here so we can pass in a custom field name and have it return its ID
// We use the ID to assign the fields value to a variable that we can then pass into our calculatePrioritizationCategory method
def getCustomFieldId = { name -> customFields.find { it.name == name }.id }

// Define a method to calculate the cost of delay (cod) using the wsjf formula and determine its category
// If a parameter hasn't got a value default it to 1
def calculatePrioritizationCategory(busValue = 1, timeCritical = 1, opportunityEnablement = 1, jobDuration = 1) {
    // Calculate the cod based off the parameters passed in
    def cod = (busValue + timeCritical + opportunityEnablement) / jobDuration
    // If the cod is weighted at less than or equal to 2.5 class it as low
    if(cod <= 2.5) {
        return 'Low'
    }
    // If the cod is weighted at greater than 2.5 and less than or equal to 5 class it as normal
    else if(cod <= 5) {
        return 'Normal'
    }
    // If the cod is weighted at greater than 5 and less than or equal to 7.5 class it as high
    else if(cod <= 7.5) {
        return 'High'
    }
    // If the cod is weighted at greater than 7.5 and less than or equal to 10 class it as urgent
    else if(cod <= 10) {
        return 'Urgent'
    }
}

// Pull the value from the issues business value custom field.
def busValue = issue.fields[getCustomFieldId('Business Value')].value

// Pull the value from the issues time criticality custom field
def timeCritical = issue.fields[getCustomFieldId('Time Criticality')].value

// Pull the value from the issues opportunity enablement custom field
def opportunityEnablement = issue.fields[getCustomFieldId('Opportunity Enablement')].value

// Pull the value from the issues job duration custom field
def jobDuration = issue.fields[getCustomFieldId('Job Duration')].value

// Call our calculation method passing in the values we've retrieved from our custom fields
return calculatePrioritizationCategory(busValue, timeCritical, opportunityEnablement, jobDuration)

In this example, we've followed the categorisation method for the WSJF result. We've defined the category boundaries in our code. We've then extracted the value from the custom fields, calculated the WSJF rating and then categorised them according to this definition. The result will then show in the issue view in Jira.

Keep your backlog under control with ScriptRunner for Jira Cloud

In our previous blog post we discussed how to forecast and plan sprints. Prioritising issues is a key way of helping that agile process. It is critical to staying efficient and delivering the most effective results. But it’s a difficult task, especially with long, dynamic backlogs. Luckily the WSJF method, implemented using ScriptRunner for Jira’s Scripted Fields, lets any Product Manager or Jira Admin make sense of their backlog and make sure they’re focusing on the right actions at the right time.

Try ScriptRunner for Jira for FREE