Last active
January 31, 2024 20:56
-
-
Save ericboehs/cd4c39beeb338e4582977657e7dde62e to your computer and use it in GitHub Desktop.
GitHub PR Status Markdowner
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 GitHub PR Status Markdowner | |
// @namespace http://tampermonkey.net/ | |
// @version 1.0 | |
// @description Copy MD of GitHub PR Review Statuses | |
// @author Eric Boehs | |
// @match https://github.com/pulls* | |
// @grant GM.setClipboard | |
// @run-at document-ready | |
// @inject-into content | |
// ==/UserScript== | |
(function() { | |
const statuses = [ | |
{ text: "Haven't even looked at it", emoji: ':question_block:' }, | |
{ text: 'Awaiting my review', emoji: ':waiting:' }, | |
{ text: "Needs review from author's team mate", emoji: ':one-team:' }, | |
{ text: 'CI Failing', emoji: ':red-x:' }, | |
{ text: 'Needs me to work on it', emoji: ':sad_robot:' }, | |
{ text: 'Code conflict', emoji: ':spiderman-pointing:' } | |
] | |
function createStatusDropdown(issueRow, issueId) { | |
let select = document.createElement('select') | |
select.id = 'status-dropdown-' + issueId | |
select.onchange = function() { saveStatus(issueId, this.value) } | |
// Set the maximum width of the select element | |
select.style.maxWidth = '100px' | |
statuses.forEach(status => { | |
let option = document.createElement('option') | |
option.value = status.emoji // Use emoji as the value | |
option.textContent = status.text // Use text for display | |
select.appendChild(option) | |
}) | |
// Locate the target element where the dropdown should be inserted | |
let targetDiv = issueRow.querySelector('.flex-shrink-0.col-4.col-md-3.pt-2.text-right.pr-3.no-wrap.d-flex.hide-sm') | |
if (targetDiv) { | |
// Check if targetDiv has any child nodes | |
if (targetDiv.firstElementChild) { | |
targetDiv.insertBefore(select, targetDiv.firstElementChild) | |
} else { | |
// If no child nodes, appendChild works just like insertBefore | |
targetDiv.appendChild(select) | |
} | |
} else { | |
console.error('Target div for dropdown not found in issue row') | |
} | |
initializeStatus(issueRow, issueId, select) | |
} | |
function saveStatus(issueId, status) { | |
localStorage.setItem('issueStatus-' + issueId, status) | |
} | |
function getStatus(issueId) { | |
return localStorage.getItem('issueStatus-' + issueId) || ':question_block:' | |
} | |
function initializeStatus(issueRow, issueId, select) { | |
select.value = getStatus(issueId) | |
} | |
function addStatusDropdowns() { | |
const issueRows = document.querySelectorAll('.js-issue-row') | |
issueRows.forEach(issueRow => { | |
let issueId = issueRow.dataset.id | |
createStatusDropdown(issueRow, issueId) | |
}) | |
} | |
addStatusDropdowns() | |
function convertToMarkdown(){ | |
const issueToolbar = document.querySelector('#js-issues-toolbar') | |
if (!issueToolbar) { | |
console.log('Element #js-issues-toolbar not found') | |
return | |
} | |
const prElements = issueToolbar.querySelectorAll('.js-issue-row') | |
const markdownLines = Array.from(prElements).map(pr => { | |
let repoNameElement = pr.querySelector('a[data-hovercard-type="repository"]') | |
let prLinkElement = pr.querySelector('a[data-hovercard-type="pull_request"]') | |
let openedByElement = pr.querySelector('.opened-by') | |
let issueId = pr.dataset.id // Assuming this is the unique identifier for the issue | |
let repoName = repoNameElement ? repoNameElement.textContent.trim() : '' | |
if (repoName) { | |
repoName = repoName.replace('department-of-veterans-affairs/', '') | |
} | |
let prLink = prLinkElement ? prLinkElement.getAttribute('href') : '' | |
let prId = openedByElement ? openedByElement.textContent.match(/#\d+/) : '' | |
let prTitle = prLinkElement ? prLinkElement.textContent.trim() : '' | |
let statusEmoji = getStatus(issueId) // Retrieve the status emoji using the getStatus function | |
return `* ${statusEmoji} [${prTitle} ${prId} (${repoName})](https://github.com${prLink})` | |
}) | |
return markdownLines | |
} | |
function copyMdToClipboard() { | |
// Count the number of PRs | |
const prCount = document.querySelectorAll('.js-issue-row').length | |
// Prepend message | |
const userLogin = document.querySelector('[name=user-login]').content | |
const prUrl = document.URL.replace(/%40me/, userLogin) | |
const prependMessage = `[Current PR Statuses](${prUrl}) for Backend (${prCount} PRs in the queue):\n\n` | |
// Generate markdown for PRs | |
const prMarkdown = convertToMarkdown().join('\n') | |
// Generate key for statuses | |
const statusKey = generateStatusKey() | |
// Combine all parts | |
GM.setClipboard(prependMessage + prMarkdown + '\n\n' + statusKey) | |
var copyButton = document.getElementById("copyStatusToClipboard") | |
// Replace copy button with 'copied' version | |
copyButton.outerHTML = copiedButtonHtml | |
setTimeout(function() { | |
// Set the button back to the original 'copy' state after 5000ms (5 seconds) | |
document.getElementById("copyStatusToClipboard").outerHTML = copyButtonHtml | |
// Re-attach the event listener to the new copy button | |
document.getElementById("copyStatusToClipboard").addEventListener("click", copyMdToClipboard) | |
}, 5000)} | |
function isStatusUsed(statusEmoji) { | |
const issueRows = document.querySelectorAll('.js-issue-row') | |
return Array.from(issueRows).some(issueRow => { | |
let issueId = issueRow.dataset.id // unique identifier for the issue | |
return getStatus(issueId) === statusEmoji | |
}) | |
} | |
function generateStatusKey() { | |
let statusKey = "Status Key:\n" | |
statuses.forEach(status => { | |
if (isStatusUsed(status.emoji)) { | |
statusKey += `${status.emoji} - ${status.text}\n` | |
} | |
}) | |
return statusKey | |
} | |
var copyButtonHtml = '<a class="p-0 ml-3 btn-link" id="copyStatusToClipboard" style="align-items: center"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" height="16" style="position: relative; top: 4px"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" /></svg> Copy Status</a>' | |
var copiedButtonHtml = '<a class="p-0 ml-3 btn-link" id="copyStatusToClipboard" style="color: green; align-items: center"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="green" height="16" style="position: relative; top: 4px"><path stroke-linecap="round" stroke-linejoin="round" d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75" /></svg> Copied!</a>' | |
document.querySelectorAll('[data-ga-click="Pull Requests, Table state, Closed"]')[1].insertAdjacentHTML('afterend', copyButtonHtml) | |
document.getElementById("copyStatusToClipboard").addEventListener("click", copyMdToClipboard) | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This Safari Userscript adds status drop downs to github.com/pulls. You can then copy a status report in Markdown format for sharing in Slack.
It should work in Tampermonkey or other user script extensions in Chrome/FF but hasn't been tested.
Screenshot.2024-01-31.at.14.37.58.mp4