Alica's dev blog
Chrome extension to display Jira issue ID in Azure DevOps

Bitbucket is not my favourite repository service, but when it comes to integration with Jira (which is, from my experience, not favourite issue tracking service among developers), it definitely has some nice features that save you extra clicks.

One of them is that when you mention Jira issue ID in a pull request (or branch name, or commit message), you get a direct link from pull request page to the Jira issue. I find this particularly useful when doing code reviews, as it allows me to quickly find the issue that the given PR is for.

Another use case is browsing the commit history: if your commit messages contain Jira issue ID, you get a link to the Jira issue. This is useful when I am investigating a history of a file and browse through the commits where that file was changed.

One of the projects I worked on used this Bitbucket/Jira combo, but then switched from Bitbucket to Azure DevOps repository service, and kept Jira for issue tracking. After this changes, it was no longer possible to get from repository to Jira issue in one click. Instead, I had to copy the issue ID, open Jira and search for that issue, and I didn’t like it at all.

The administrators told me that they couldn’t find any Azure DevOps add-on for this. So I decided to create my own Chrome extension.

What do we want to achieve

Something like this on the pull request page:

PR page

And something like this on the commits page:

Commits page

Also, we could add links to the commit details page and pushes page.

Main idea of the solution

  1. Identify the pages where we want to display the links.
  2. Find the element on the page that contains Jira issue ID.
  3. Create a link to that Jira issue.
  4. Place that link on the page.

How to do it

Create a boilerplate for the extension

Let’s start from Reading Time extension that is provided as an example by Google Chrome developers. Its structure is similar to what we want to achieve – it finds something on the page, does some processing, and adds element with the result to the page.

First, we will update manifest.json file:

"manifest_version": 3,
  "name": "Azure DevOps links",
  "version": "1.0",
  "description": "Add links to Jira issues to Azure DevOps pages with PRs and commits",
  "content_scripts": [
    {
      "js": ["content.js"],
      "matches": [
        "https://dev.azure.com/organisationName/*"
      ],
      "run_at": "document_idle"
    }
  ]

There are two important things:

  1. We are saying that the script with tour code is called content.js.
  2. We want the script to run only on our organization’s Azure DevOps page https://dev.azure.com/organisationName/*.

Then, we create an empty content.js file next to manifest.json.

Load the extension into Chrome

Follow the instructions on how to load unpacked extension into Chrome.

Every time we make updates to the files, we can simply reload by clicking the button there.

We will compare the current URL with the URLs of the pages where we want to display the links. After a quick look we can see that all four concerned URLs share the same base and only the something part is different: https://dev.azure.com/[organisation name]/[project name]/_git/[repo name]/[something].

The URLs of the pages where we want to display the links are:

  • pull request: [base]/pullrequest/[PR number]
  • commit details: [base]/commit/[commit ID]
  • commits: [base]/commits
  • pushes: [base]/pushes

So we use regular expressions to match those URLs. In my case, I didn’t care about project and repository name, since I wanted to apply it to all repositories:

const azureDevopsUrlPattern = 'https:\/\/dev\.azure\.com\/organisationName\/[^\/]*\/_git\/[^\/]*\/';
const queryParamsPattern = '([\?].*)?(#.*)?'; // let's not forget about query params so that they don't break it

const createAzureUrlPattern = urlKey => new RegExp(`${azureDevopsUrlPattern}${urlKey}${queryParamsPattern}`);

const pullRequestPageRegex = createAzureUrlPattern('pullrequest\/[0-9]+');
const commitListPageRegex = createAzureUrlPattern('commits');
const commitPageRegex = createAzureUrlPattern('commit\/.+');
const pushesPageRexeg = createAzureUrlPattern('pushes');

const currentUrl = window.location.href;

For each page, I browsed the HTML and identified elements that contain the Jira issue ID. For some, a CSS class selector is enough, others we access through their parent elements. See the full code for details.

We match the text content of the element with pattern for issue ID and create a link element:

const issueIdRegex = /ABC-[0-9]{1,6}/g;
const jiraUrl = 'https://organisationName.atlassian.net'

const createIssueLinkElement = issueId => `<a href="${jiraUrl}/browse/${issueId}">${issueId}</a>`;

Then we place it to the right place on the page (see the full code).

Now, if we reload our extension, and open any of the pages and refresh the page, the links are there!

Make it work with SPA

Have you noticed the and refresh the page step? That’s required because of how SPAs work: When clicking and browsing through the SPA, the whole page is not reloaded, only parts of it. That means the code for our extension is not executed until we refresh the page.

Obviously, I was not the first person to have this problem, and found a solution on StackOverflow: Add a MutationObserver that will observe changes in the body and execute our code in such case.

function addLocationObserver(callback) {
    const config = { attributes: false, childList: true, subtree: false }
    const observer = new MutationObserver(callback)
    observer.observe(document.body, config)
}

However, just adding it brings another problem: sometimes it gets called multiple times and then we may end up with 5 links added instead of one.

To solve that, we add a (dumb) check if there already is a Jira URL in the updated element (which means that we won’t get any link added if there is a Jira URL present for a different reason, but I didn’t care about such edge case):

const issueLinkElementExists = parent => parent.innerHTML.includes(jiraUrl);

Limitations

  1. Jira and Azure DevOps URLs are hard-coded – you have to update the extension code manually.
  2. The extension relies on the URL structure and CSS classes, so any change in those can break it.
  3. Some organizations have policies that don’t allow installing custom extensions to Chrome.

Source code

You can view the complete source code on Github.


Last modified on 2024-07-10