NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Jira sort issues in epic by status // @namespace https://openuserjs.org/users/floodmeadows // @description Adds buttons to sort issues by status. // @copyright 2023, floodmeadows (https://openuserjs.org/users/floodmeadows) // @license MIT // @version 0.1 // @include https://jira.*.uk/browse/* // @updateURL https://openuserjs.org/meta/floodmeadows/Jira_sort_issues_in_epic_by_status.meta.js // @downloadURL https://openuserjs.org/install/floodmeadows/Jira_sort_issues_in_epic_by_status.user.js // @grant none // ==/UserScript== /* jshint esversion: 6 */ //--- Customise this to your Jira project -----// const debug = false; const delayOffset = 150 var delay = 0 var itemsLeftToChange = 0 const sortedStatusNames = new Array( "Backlog", "To be Prioritised", "To Do", "Ready for Analysis", "In Analysis", "Ready for Refinement", "Ready for Dev", "In Progress", "In Dev", "In PR", "Ready for Test", "In Test", "In Review", "Ready for Release", "Done", "Won't Fix" ); //---------------------------------------------// const currentUrl = new URL(document.URL); const jiraBaseUrl = currentUrl.protocol + '//' + currentUrl.host; const issueKey = document.getElementById("key-val").childNodes[0].nodeValue; const issueName = document.getElementById("summary-val").childNodes[0].nodeValue; const issueUrl = `${jiraBaseUrl}/browse/${issueKey}`; (function() { 'use strict'; addButtonSortByStatusAscending(); addButtonSortByStatusDecending(); addProgressLabel(); addProgressIndicator(); })(); function addButtonSortByStatusAscending() { const buttonLabel = "Sort by status ('Done' at top)" const targetElement = document.getElementById('greenhopper-epics-issue-web-panel_heading') const eventHandler = function() { sortIssuesByStatus(true) } const buttonCssClasses = "aui-button toolbar-trigger issueaction-workflow-transition" addButton(buttonLabel, targetElement, null, eventHandler, buttonCssClasses) } function addButtonSortByStatusDecending() { const buttonLabel = "Sort by status ('Done' at bottom)" const targetElement = document.getElementById('greenhopper-epics-issue-web-panel_heading') const eventHandler = function() { sortIssuesByStatus(false) } const buttonCssClasses = "aui-button toolbar-trigger issueaction-workflow-transition" addButton(buttonLabel, targetElement, null, eventHandler, buttonCssClasses) } function addProgressLabel() { const label = " Progress: " const targetElement = document.getElementById('greenhopper-epics-issue-web-panel_heading') const id = "progress-label" const cssClasses = "" addSpan(label, targetElement, null, null, id, cssClasses) } function addProgressIndicator() { const label = "" const targetElement = document.getElementById('greenhopper-epics-issue-web-panel_heading') const id = "progress-indicator" const cssClasses = "" addSpan(label, targetElement, null, null, id, cssClasses) } function addButton(text, parentElement, beforeElement, clickEventFunctionName, cssClass) { if(debug) console.log("addButton called") const btn = document.createElement("a") const textNode = document.createTextNode(text) btn.appendChild(textNode) btn.setAttribute("href","#") btn.addEventListener("click", clickEventFunctionName) btn.setAttribute("class", cssClass) parentElement.insertBefore(btn, beforeElement) } function addSpan(text, parentElement, beforeElement, clickEventFunctionName, id, cssClasses) { if(debug) console.log("addSpan called") const s = document.createElement("span") const textNode = document.createTextNode(text) s.appendChild(textNode) s.addEventListener("click", clickEventFunctionName) s.setAttribute("id", id) s.setAttribute("class", cssClasses) parentElement.insertBefore(s, beforeElement) } function sortIssuesByStatus(isAscending) { var rowsInEpicTable = document.querySelectorAll('table#ghx-issues-in-epic-table tr') if(!isAscending) sortedStatusNames.reverse() sortedStatusNames.forEach(function(statusName) { if(debug) console.log('Looking for ' + statusName) for(let i=0; i< rowsInEpicTable.length; i++) { var statusValueForRow = document.querySelector(`table#ghx-issues-in-epic-table tr:nth-child(${i+1}) td:nth-child(5) span`).childNodes[0].textContent if(statusValueForRow == statusName) { if(debug) console.log(Date.now() + ' Found ' + statusName) if(debug) console.log(Date.now() + ' Delay is ' + delay) let issueKey = document.querySelector(`table#ghx-issues-in-epic-table tr:nth-child(${i+1}) td:nth-child(2) a`).childNodes[0].textContent if(debug) console.log(Date.now() + ' setting clickOnRowToShowActionsMenu() to fire in ' + delay + ' milliseconds...') rankRowItem(i, issueKey) } } }) } function rankRowItem(tableRow, issueKey) { updateProgressIndicator(1) window.setTimeout(function() { clickOnRowToShowActionsMenu(tableRow) }, delay) // can't pass params directly when calling functions using setTimeout :( delay += delayOffset if(debug) console.log(Date.now() + ' Delay is ' + delay) if(debug) console.log(Date.now() + ' setting clickOnRankToTopMenuItem() to fire in ' + delay + ' milliseconds...') window.setTimeout(function() { clickOnMenuItem(issueKey, "rank-top-operation") }, delay) delay += delayOffset } function clickOnRowToShowActionsMenu(rowIndex) { if(debug) console.log(`${Date.now()} clicking on row ${rowIndex} in table`) if(!debug) document.getElementsByClassName('issuerow')[rowIndex].children[6].children[0].children[0].click() } function clickOnMenuItem(issueKey, operation) { if(debug) console.log(`${Date.now()} clicking on ${issueKey} to rank to top`) if(!debug) { document.querySelector(`a[data-issuekey="${issueKey}"].issueaction-greenhopper-${operation}`).click() updateProgressIndicator(-1) } } function updateProgressIndicator(changeValue) { var currentText = document.getElementById('progress-indicator').childNodes[0].textContent var updatedText = "" itemsLeftToChange += changeValue updatedText = itemsLeftToChange if(debug) console.log('Updating progress indicator to ' + updatedText) document.getElementById('progress-indicator').childNodes[0].textContent = updatedText if(itemsLeftToChange == 0) { updatedText = "All done!" document.getElementById('progress-indicator').childNodes[0].textContent = updatedText if(!debug) window.location.assign(currentUrl); } }