Jira Issue Linking Model

Jira issue linking model
Reading Time: 5 minutes

Recently I have been building a system for visualizing the relationships and dependencies between issues contained in two separate Jira projects. The data model employs links between entities which are entered using Jira's issue linking UI within the issue view. However, when extracting the data, I became confused about Jira's API providing issue link information and I couldn't find satisfactory information in the reference documentation (REST API documentation and guides in DAC).

After a little research, it seems other developers have also been confused by issue link semantics so I decided to research the topic and create this post in order to reduce further confusion.

Let's start with an explanation of a simplified version of the model I've been building. There are two projects, THING and FEAT. The issues in FEAT represent features, whilst the issues in THING represent things that we want to build. The features represented by the FEAT issues may or may not be implemented. Links from THING issues to FEAT issues are used to represent the dependencies of the things we want to build. Here's a fictitious object model (similar to the one I was building) illustrating this:

The conceptual model being built.
Example model to illustrating Jira issue links

The projects have the following issue link configuration (obtainable by calling GET /rest/api/2/issueLinkType):

"issueLinkTypes": [{
  "id": "10000",
  "name": "Blocks",
  "inward": "is blocked by",
  "outward": "blocks",
  "self": "..."
}, {
  "id": "10007",
  "name": "Depends",
  "inward": "is depended on by",
  "outward": "depends on",
  "self": "..."
}]

Since Jira is being used to capture data and a number of people will be entering information, it's possible some people will enter dependencies as Depends links, whilst others will enter them as Blocker links. However, we'd like the model to treat Depends and Blocker links identically.

After creating the four issues represented in the diagram above, the following links were also entered:

  • On the THING-1: Car issue screen, is blocked by FEAT-1: Engine
  • On the THING-1: Car issue screen, depends on FEAT-2: Wheel
  • On the FEAT-3: Chassis issue screen, blocks THING-1: Car
  • On the FEAT-4: Seat issue screen, is depended on by THING-1: Car

After entering these links, Jira displays the following in the THING-1: Car issue screen:

Jira issue view screen showing links to other issues

The link information presented by Jira is intuitive and matches my mental model corresponding to the way I entered the link information, noting the different screens the data was entered in and the different link options used.

When the details of THING-1: Car is retrieved from the REST API using GET /rest/api/2/issue/THING-1, the issueLinks field contains the following (where some fields are omitted to keep it brief):

issueLinks: [{
  type: {
    name: "Blocks",
    inward: "is blocked by",
    outward: "blocks"
  },
  inwardIssue: {
    key: "FEAT-1"
  }
}, {
  type: {
    name: "Blocks",
    inward: "is blocked by",
    outward: "blocks"
  },
  inwardIssue: {
    key: "FEAT-3"
  }
}, {
  type: {
    name: "Depends",
    inward: "is dependend on by",
    outward: "depends on"
  },
  outwardIssue: {
    key: "FEAT-2"
  }
}, {
  type: {
    name: "Depends",
    inward: "is dependend on by",
    outward: "depends on"
  },
  outwardIssue: {
    key: "FEAT-4"
  }
}]

When I saw this at first I was surprised to see some of the issue link data captured within inwardIssue fields and other issue link data captured within outwardIssue fields. I was expecting only to see outwardIssue fields, matching the model illustrated by the first diagram. However, the existence of inwardIssue fields caused me to interpret it as the following object model:

The initial conceptual model based on  Jira issue link data provided by the Jira API.

I was assuming the inwardIssue and outwardIssue fields indicated the directionality of the links in my logical model, but that's not the case. I then started thinking the fields might indicate how the links were added using the Jira UI which is why my scenario included entering links in both THING and FEAT issue screens. Taking a closer look at the definitions of the issueLinkTypes, I then realised the inward and outward fields of the Blocks issue link type didn't match my mental model. For example, the Jira UI indicates THING-1: Car is blocked by FEAT-1: Engine, but the issue link data represents the link as an inward link on THING-1: Car, or in more visual terms, FEAT-1: EngineTHING-1: Car. Whilst Jira issue links are bidirectional, the meaning of each direction is limited to the labels provided in the Jira issue link types configuration. The purpose of the inwardIssue and outwardIssue fields is to identify the appropriate label to present to users, which changes depending on which end of a link it is presented in. For example, when the link data returned by the API contains an inwardIssue field, display the inward label.

A conceptual model of Jira issue links.

This is why in the THING-1 issue screen, it presents the link to FEAT-1 under the label is blocked by which makes sense. However, the labels of the Blocks link type make less sense and is the root cause of why I was surprised to see inwardIssue fields at THING-1. It would make more sense if the labels of the Blocks issue link types were swapped.

"issueLinkTypes": [{
  "id": "10000",
  "name": "Blocks",
  "inward": "is blocked by",     < "blocks" makes more sense
  "outward": "blocks",           < "is blocked by" makes more sense
  "self": "..."
}, {
  "id": "10007",
  "name": "Depends",
  "inward": "is depended on by", < makes sense
  "outward": "depends on",       < makes sense
  "self": "..."
}]

In order to map the Jira issue link data to the model I was building, I had to hard code the issue link configuration with an additional field indicating whether to invert the directionality of the links.

issueLinkDirections: [{
  id: "10000",
  name: "Blocks",
  invert: true
}, {
  id: "10007",
  name: "Depends",
  invert: false
}]

This approach is fine when building a system against well known project configurations, but not for a generic product since there is nothing stopping a Jira administrator from updating the issue link configuration. For example, an administrator could swap the inward and outward descriptions of the Blocks link type. If a generic app needed to be built, it would have to introduce extra project configuration in order to be able to determine the meaning of the issue link directions.

The key lesson I learnt from this investigation is that whilst Jira issue links are bidirectional, the semantics of each direction is only interpretable at the user interface level and not at the API level. For this reason, you should not apply any meaning to field names suggesting directionality such as inward, outward, inwardIssue and outwardIssue. The purpose of these fields is to facilitate link presentation in user interfaces. The API could alternatively have employed field names such as directionA, directionB, directionAIssue and directionBIssue.

Up until now, the Jira REST API had documented the inwardIssue and outwardIssue fields as follows:

  • inwardIssue: The issue the link joins to.
  • outwardIssue: The issue the link originates from.

As explained above, these descriptions are somewhat misleading so I've updated their descriptions as follows:

  • inwardIssue: Provides details about the linked issue. If presenting this link in a user interface, use the inward field of the issue link type to label the link.
  • outwardIssue: Provides details about the linked issue. If presenting this link in a user interface, use the outward field of the issue link type to label the link.

On its own, the above API documentation does not adequately explain the semantics so I have also created a guide to improve the discoverability of key information about Jira's issue linking model.

Hopefully, this post provides clarity about Jira issue links and in particular, how to interpret issue link data returned by the Jira API.