Skip to content

Instantly share code, notes, and snippets.

@cartok
Last active April 5, 2022 07:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cartok/b9c11b3aba71e1aea1beaff4c5f7db19 to your computer and use it in GitHub Desktop.
Save cartok/b9c11b3aba71e1aea1beaff4c5f7db19 to your computer and use it in GitHub Desktop.
// ==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