Last active
August 10, 2022 07:16
-
-
Save Dainius14/0de0e65b12e41c9936da8a586d0c2dd6 to your computer and use it in GitHub Desktop.
Jira Board Additional Info
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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