Skip to content

Instantly share code, notes, and snippets.

@Dainius14
Last active August 10, 2022 07:16
Show Gist options
  • Save Dainius14/0de0e65b12e41c9936da8a586d0c2dd6 to your computer and use it in GitHub Desktop.
Save Dainius14/0de0e65b12e41c9936da8a586d0c2dd6 to your computer and use it in GitHub Desktop.
Jira Board Additional Info
// ==UserScript==
// @name Jira Board Additional Info
// @description Adds additional data next to each issue in single line in Jira backlog.
// @namespace https://saldainius.lt/
// @version 1.2
// @author Dainius
// @downloadURL https://gist.githubusercontent.com/Dainius14/0de0e65b12e41c9936da8a586d0c2dd6/raw/jira-board-additional-info.user.js
// @updateURL https://gist.githubusercontent.com/Dainius14/0de0e65b12e41c9936da8a586d0c2dd6/raw/jira-board-additional-info.user.js
// @website https://gist.github.com/Dainius14/0de0e65b12e41c9936da8a586d0c2dd6/
// @match https://jira.cid-dev.net/secure/RapidBoard.jspa*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
// Observe changes of body element
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
// Work only in Backlog page
if (document.querySelector('#ghx-header .subnavigator-title')?.textContent !== 'Backlog') {
continue;
}
if (mutation.type === 'childList') {
// Get only not marked as processed
const issueElements = [...document.querySelectorAll('.js-issue:not(.processed-by-saldainius)')];
// Immediatelly mark as processed so no stuff gets duplicated
for (const issueEl of issueElements) {
issueEl.classList.add('processed-by-saldainius');
}
processAllIssueElements(issueElements);
}
}
});
observer.observe(document.querySelector('body'), {attributes: false, childList: true, subtree: true});
/**
* @param {Element[]} issueElements
*/
function processAllIssueElements(issueElements) {
// Batch elements so not all requests are fired immediatelly
let batchId = 0;
while (issueElements.length > 0) {
const issuesBatch = issueElements.splice(0, 50);
setTimeout(() => {
for (const issueEl of issuesBatch) {
processIssueElement(issueEl);
}
}, batchId * 600);
batchId++;
}
}
/**
* @param {Element} issueEl
*/
function processIssueElement(issueEl) {
const issueId = issueEl.attributes['data-issue-id'].value;
getIssueDetails(issueId, ([statusEl, labels]) => {
const issueContentRowEl = issueEl.querySelector('.ghx-issue-content .ghx-row');
const issueContentEndEl = issueContentRowEl.querySelector('.ghx-end');
for (const label of labels) {
const labelEl = document.createElement('span');
labelEl.setAttribute('data-tooltip', `<span class="jira-issue-status-tooltip-title">${label}</span>`);
labelEl.classList.add('jira-issue-status-lozenge');
labelEl.classList.add('aui-label');
labelEl.classList.add('ghx-label');
labelEl.classList.add('ghx-label-single');
labelEl.style.fontStyle = 'italic';
labelEl.style.marginLeft = '10px';
labelEl.textContent = label;
issueContentRowEl.insertBefore(labelEl, issueContentEndEl);
}
statusEl.style.marginRight = '15px';
statusEl.style.marginLeft = '10px';
issueContentRowEl.insertBefore(statusEl, issueContentEndEl);
});
}
/**
* @param {string} issueId
* @param {issueDetailsCallback} callback
* @callback issueDetailsCallback
* @param {tuple} responseCode
* @typedef {Element} statusEl
* @typedef {string[]} labels
* @typedef {[statusEl, labels]} tuple
*/
function getIssueDetails(issueId, callback) {
GM_xmlhttpRequest({
method: 'GET',
url: `/rest/greenhopper/1.0/xboard/issue/details.json?rapidViewId=10426&issueIdOrKey=${issueId}`,
onload: response => {
const responseJson = JSON.parse(response.responseText);
const detailsTab = responseJson.tabs.defaultTabs.find(x => x.tabId === 'DETAILS');
const detailsSection = detailsTab.sections.find(x => x.providerKey === 'com.pyxis.greenhopper.jira:details-module');
const detailsEl = document.createElement('div');
detailsEl.innerHTML = detailsSection.html;
const statusEl = detailsEl.querySelector('#status-val').children[0];
const labels = [...detailsEl.querySelectorAll('.labels')].map(x => x.textContent.trim()).filter(x => x !== 'None');
callback([statusEl, labels]);
},
})
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment