Last active
April 5, 2022 07:18
-
-
Save cartok/b9c11b3aba71e1aea1beaff4c5f7db19 to your computer and use it in GitHub Desktop.
This file contains 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 Epic Filter | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description Helper functions to only see one epic at a time on the board | |
// @author cartok | |
// @match https://*.atlassian.net/*/boards/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=atlassian.net | |
// @grant none | |
// @license MIT | |
// ==/UserScript== | |
// TODOs: | |
// * feature: Improve UX | |
// * feature: Add error handling for DOM queries in case the DOM changed | |
(function() { | |
clearInterval(window.jiraEpicToolbarInstallInterval) | |
window.jiraEpicToolbarInstallInterval = setInterval(() => { | |
if(!isDOMReady()) { | |
return | |
} | |
clearInterval(window.jiraEpicToolbarInstallInterval) | |
installEpicToolbar() | |
clearInterval(window.jiraEpicToolbarUpdateInterval) | |
window.jiraEpicToolbarUpdateInterval = setInterval(() => { | |
installEpicToolbar() | |
}, 3000) | |
}, 300) | |
function isDOMReady () { | |
const container = document.getElementById('ghx-rabid') | |
if(!container) return false | |
const toolbar = container.querySelector('#ghx-operations') | |
if(!toolbar) return false | |
return true | |
} | |
function installEpicToolbar () { | |
const toolbar = document.getElementById('epic-ghx-operations') | |
if (toolbar) { | |
toolbar.remove() | |
} | |
createEpicToolbar() | |
} | |
function createEpicToolbar () { | |
const container = document.getElementById('ghx-rabid') | |
const toolbar = container.querySelector('#ghx-operations') | |
const range = document.createRange() | |
// * create styles and container div | |
const epicToolbar = range.createContextualFragment(` | |
<style> | |
#epic-ghx-operations { | |
display: flex; | |
flex-direction: row; | |
gap: 0.5rem; | |
margin-bottom: 2rem; | |
} | |
.epic-ghx-button { | |
border: none; | |
padding: 0.5rem; | |
font-weight: 500; | |
background-color: rgba(9, 30, 66, 0.04); | |
color: rgb(66, 82, 110); | |
} | |
.epic-ghx-button-reset { | |
background-color: #0052cc; | |
color: #fff; | |
} | |
</style> | |
<div id="epic-ghx-operations" /> | |
`) | |
const epicToolbarContainer = epicToolbar.getElementById('epic-ghx-operations') | |
// * get all information required for button rendering | |
const visibleEpics = [ | |
...new Set( | |
getTasks() | |
.map(i => getTaskEpic(i)) | |
.filter(Boolean) | |
) | |
] | |
// * add epic buttons | |
visibleEpics.forEach(epic => { | |
const buttonFragment = range.createContextualFragment(` | |
<button class="epic-ghx-button">${epic}</button> | |
`) | |
buttonFragment.querySelector('.epic-ghx-button').addEventListener('click', () => setEpicFilter(epic)) | |
epicToolbarContainer.appendChild(buttonFragment) | |
}) | |
// * add reset button | |
const resetButtonFragment = range.createContextualFragment(` | |
<button class="epic-ghx-button epic-ghx-button-reset">Reset</button> | |
`) | |
resetButtonFragment.querySelector('.epic-ghx-button').addEventListener('click', resetEpicFilter) | |
epicToolbarContainer.appendChild(resetButtonFragment) | |
container.insertBefore(epicToolbar, toolbar.nextSibling) | |
} | |
function setEpicFilter (filteredEpic) { | |
resetEpicFilter() | |
const tasks = getTasks() | |
const taskKeyAndEpicMap = tasks.reduce((acc, i) => { | |
const key = getTaskKey(i) | |
const epic = getTaskEpic(i) | |
acc[key] = epic | |
return acc | |
}, {}) | |
// * filter out all tasks that aren't epic! | |
// * filter out all tasks from epics that are not enabled | |
tasks | |
.filter(i => { | |
const key = getTaskKey(i) | |
const parentKey = i.querySelector('.ghx-parent-stub .ghx-key')?.textContent | |
const epic = taskKeyAndEpicMap[key] | |
const parentEpic = taskKeyAndEpicMap[parentKey] | |
return (epic && epic !== filteredEpic) || (!epic && parentEpic !== filteredEpic) | |
}) | |
.forEach(i => { | |
i.style.display = 'none' | |
}) | |
} | |
function getTaskKey (task) { | |
return task.querySelector('a.ghx-key').href.replace(/.*\/(.*)$/, '$1') | |
} | |
function getTaskEpic (task) { | |
return task.querySelector('.ghx-highlighted-field')?.textContent | |
} | |
function resetEpicFilter () { | |
getTasks().forEach(i => { | |
i.style.display = null | |
}) | |
} | |
function getTasks () { | |
return [...document.querySelectorAll('.ghx-wrap-issue > *')] | |
.filter(i => !i.classList.contains('ghx-show-old')) | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment