Skip to content

Instantly share code, notes, and snippets.

@dennishall
Last active August 4, 2021 19:09
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 dennishall/6cb8487f6ee8a3705ecd94139cd97b45 to your computer and use it in GitHub Desktop.
Save dennishall/6cb8487f6ee8a3705ecd94139cd97b45 to your computer and use it in GitHub Desktop.
UserScript (Tampermonkey) for JIRA - Adds buttons to copy branch name, pull request title to clipboard
// ==UserScript==
// @name Get branch name, get PR title, copy to clipboard
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description For JIRA. Adds buttons to copy branch name and pull request title to clipboard, adds console helpers.
// @author Dennis Hall
// @match https://*.atlassian.net/*
// @grant none
// ==/UserScript==
(function () {
// Your initials. Change this to your initials.
const initials = 'DH';
// Only show buttons on these pages:
// * Backlog and Kanban view: `/secure/RapidBoard`
// * Detail view: `/browse`
if (!/^\/secure\/RapidBoard|\/browse/.test(location.pathname)) {
return;
}
/**
* Gets the Title, JIRA "Number" (ID, such as NOW-1000), and title in
* @return {{}|{title: String, jiraNumber: String, dashesTitle: String}}
*/
function getData () {
const issueLink = (
// backlog view, detail view
document.querySelector('[data-test-id*="current-issue"] a')
// kanban view
|| document.querySelector('.ghx-selected a')
);
if (!issueLink) {
alert('No active issue, please select an issue first.');
return;
}
const jiraNumber = issueLink.dataset.tooltip || issueLink.innerText;
const title = (
// kanban view with ticket details in a modal
document.querySelector('[data-test-id*="summary.heading"]')
// kanban view
|| document.querySelector('.ghx-selected .ghx-summary')
// backlog view, detail view
|| Array.from(document.querySelectorAll('h1')).pop()
).innerText;
return {
jiraNumber,
title,
};
}
/**
* Copies text to the clipboard
* @param text
*/
function copy(text) {
navigator.clipboard.writeText(text).then(
function() {
console.log('success, copied text.');
},
function(err) {
console.error('Could not copy text: ', err);
}
);
}
/**
* Momentarily shows "✔ Copied" as the button text, to inform the user of successful copy to clipboard
* @param e
*/
function flashCopiedMessage(e) {
if (e) {
const prevText = this.innerText;
this.innerHTML = '✔ Copied';
setTimeout(() => {
this.innerText = prevText;
}, 1500);
}
}
/**
* Copies the Branch Name to clipboard
* @param {Event|undefined} e
* @return {String} the branch name
*/
function getBranchName (e) {
const { jiraNumber, title } = getData();
const dashesTitle = (title || '').replace(/\W+/g, '-').replace(/-+/g, '-');
const branchName = `${jiraNumber}--${dashesTitle}--${initials}`;
if (!jiraNumber || !title) return;
copy(branchName);
console.log(`copied branch name to clipboard:\n${branchName}`);
flashCopiedMessage.bind(this)(e);
return branchName;
}
/**
* Copies the Pull Request Title to clipboard
* @param {Event|undefined} e
* @return {String} the pull request title
*/
function getPullRequestTitle (e) {
const { jiraNumber, title } = getData();
const fullTitle = `${jiraNumber} - ${title}`;
if (!jiraNumber || !title) return;
copy(fullTitle);
console.log(`copied pull request title to clipboard:\n${fullTitle}`);
flashCopiedMessage.bind(this)(e);
return fullTitle;
}
// make these as global variables for anyone who prefers the console. in the console,
// you can just start to type `branchName` or `pullRequestTitle`, it'll autocomplete
// like any other window property or global var, then hit enter to copy to clipboard
Object.defineProperty(window, 'branchName', { get: getBranchName });
Object.defineProperty(window, 'pullRequestTitle', { get: getPullRequestTitle });
// render the ui panel and buttons
const div = document.createElement('div');
div.setAttribute('style', `
position: absolute;
z-index: 99999;
bottom: 0;
left: 0;
padding: 15px 10px 25px;
background: white;
width: 200px;
border-top: 1px solid black;
border-right: 1px solid black;
`);
div.innerHTML = `
<style>
.my-button {
background: #6f42c1;
color: white;
border: 0;
border-radius: 5px;
padding: 10px;
display: block;
width: 200px;
margin-top: 10px;
font-size: 14px;
cursor: pointer;
}
.my-close-button {
position: absolute;
top: -13px;
left: 208px;
cursor: pointer;
background: white;
border-radius: 12px;
}
</style>
<button id='getBranchName' class='my-button'>
Branch Name
</button>
<button id='getPullRequestTitle' class='my-button'>
Pull Request Title
</button>
<svg class="my-close-button" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.5 16.084l-1.403 1.416-4.09-4.096-4.102 4.096-1.405-1.405 4.093-4.092-4.093-4.098 1.405-1.405 4.088 4.089 4.091-4.089 1.416 1.403-4.092 4.087 4.092 4.094z"/>
</svg>
`;
document.body.append(div);
// bind events
const buttons = div.getElementsByTagName('button');
buttons[0].onclick = getBranchName;
buttons[1].onclick = getPullRequestTitle;
div.querySelector('svg').onclick = function () {
this.parentNode.remove();
};
})();
@dennishall
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment