Last active
May 21, 2025 11:16
-
-
Save joaomcarlos/60a32a465f4330bc4c5a47ade9d25a56 to your computer and use it in GitHub Desktop.
Jira to GitLab issue + merge request command generator button
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 Jira to GitLab Command Copier | |
// @namespace https://github.com/joaomcarlos | |
// @version 1.13 | |
// @description Adds a "Glab" button on Jira issues to copy a command for creating a GitLab issue and merge request with the correct issue URL, title, description, and label (bug or feat). | |
// @match https://gad-claranet.atlassian.net/* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
"use strict"; | |
console.log("[Glab Copier] Script loaded"); | |
// Creates the command button with styling. | |
function createButton() { | |
const btn = document.createElement('button'); | |
btn.textContent = 'Glab'; | |
btn.style.margin = '5px'; | |
btn.style.padding = '5px 10px'; | |
btn.style.fontSize = '0.9rem'; | |
btn.style.backgroundColor = '#0052CC'; | |
btn.style.color = 'white'; | |
btn.style.border = 'none'; | |
btn.style.borderRadius = '3px'; | |
btn.style.cursor = 'pointer'; | |
btn.id = "glab-mr-copy-btn"; | |
console.log("[Glab Copier] Button created:", btn); | |
return btn; | |
} | |
// Copies the given text to the clipboard. | |
function copyTextToClipboard(text) { | |
console.log("[Glab Copier] Attempting to copy text:", text); | |
if (navigator.clipboard && navigator.clipboard.writeText) { | |
navigator.clipboard.writeText(text) | |
.then(() => alert('Command copied to clipboard!')) | |
.catch(err => alert('Failed to copy text: ' + err)); | |
} else { | |
const textArea = document.createElement("textarea"); | |
textArea.value = text; | |
textArea.style.position = "fixed"; // Prevent scrolling. | |
document.body.appendChild(textArea); | |
textArea.focus(); | |
textArea.select(); | |
try { | |
const successful = document.execCommand('copy'); | |
if (successful) { | |
alert('Command copied to clipboard!'); | |
} else { | |
alert('Copying failed. Please copy manually.'); | |
} | |
} catch (err) { | |
alert('Copying failed: ' + err); | |
} | |
document.body.removeChild(textArea); | |
} | |
} | |
// Helper to get the issue description using multiple selectors. | |
function getIssueDescription() { | |
let description = ""; | |
const selectors = [ | |
'div[data-testid="issue.views.issue-base.foundation.description.rich-text-renderer"]', | |
'.jira-issue__description', | |
'.ak-renderer-document' | |
]; | |
for (let sel of selectors) { | |
const elem = document.querySelector(sel); | |
if (elem && elem.textContent.trim()) { | |
description = elem.textContent.trim(); | |
console.log("[Glab Copier] Issue description found using selector:", sel, description); | |
break; | |
} | |
} | |
if (!description) { | |
console.warn("[Glab Copier] Issue description element not found. Leaving description empty."); | |
} | |
return description; | |
} | |
// Helper to get the issue type by checking the issue type button's image alt text | |
function getIssueType() { | |
// First try to find the issue type button with the image | |
const issueTypeButton = document.querySelector('[data-testid="issue.views.issue-base.foundation.change-issue-type.button"]'); | |
if (issueTypeButton) { | |
// Look for the image inside the button with alt text | |
const issueTypeImg = issueTypeButton.querySelector('img[alt]'); | |
if (issueTypeImg && issueTypeImg.alt) { | |
const issueType = issueTypeImg.alt.toLowerCase(); | |
console.log(`[Glab Copier] Found issue type: ${issueType}`); | |
return issueType; | |
} | |
} | |
// Fallback to old method if the button method fails | |
const selectors = [ | |
'span[data-testid="issue.views.issue-base.foundation.issuetype.issuetype"]', | |
'span[aria-label="Issue type"]' | |
]; | |
for (let sel of selectors) { | |
const elem = document.querySelector(sel); | |
if (elem && elem.textContent.trim()) { | |
const issueType = elem.textContent.trim().toLowerCase(); | |
console.log("[Glab Copier] Issue type found using fallback selector:", sel, issueType); | |
return issueType; | |
} | |
} | |
console.warn("[Glab Copier] Issue type element not found, defaulting to 'bug'"); | |
return 'bug'; // Default to 'bug' if we can't determine the type | |
} | |
// Build the command string using the issue title, description, type (label), and URL. | |
function buildCommand() { | |
console.log("[Glab Copier] Building command…"); | |
// Get the issue title. | |
const titleElem = document.querySelector('h1[data-testid="issue.views.issue-base.foundation.summary.heading"]'); | |
if (!titleElem) { | |
console.error("[Glab Copier] Issue title element not found!"); | |
alert("Could not find issue title!"); | |
return ''; | |
} | |
const issueTitle = titleElem.textContent.trim(); | |
console.log("[Glab Copier] Issue title:", issueTitle); | |
// Get the issue description using our helper. | |
const issueDescription = getIssueDescription(); | |
// Determine the label based on the issue type. | |
let label = "feat"; // default label | |
const issueType = getIssueType(); | |
if (issueType === "bug") { | |
label = "bug"; | |
} | |
console.log("[Glab Copier] Label set to:", label); | |
// Determine the issue key. | |
let issueKey = ""; | |
try { | |
const urlParams = new URL(window.location.href).searchParams; | |
if (urlParams.has("selectedIssue")) { | |
issueKey = urlParams.get("selectedIssue").trim(); | |
console.log("[Glab Copier] Issue key from URL parameter:", issueKey); | |
} | |
} catch (err) { | |
console.warn("[Glab Copier] Error reading URL parameters:", err); | |
} | |
if (!issueKey) { | |
const keyElem = document.querySelector('span[data-testid="issue.views.issue-base.foundation.issue-key.issue-key"]'); | |
if (keyElem) { | |
issueKey = keyElem.textContent.trim(); | |
console.log("[Glab Copier] Issue key from DOM element:", issueKey); | |
} | |
} | |
if (!issueKey) { | |
console.warn("[Glab Copier] Issue key not found, falling back to current location"); | |
} | |
// Construct the issue URL. | |
let issueUrl = ""; | |
if (issueKey) { | |
issueUrl = window.location.origin + "/browse/" + issueKey; | |
} else { | |
issueUrl = window.location.href.split('?')[0]; | |
} | |
// Build the final command. | |
const command = `glab-mr new -t "${issueTitle}" -d "${issueDescription}" -l ${label} -j ${issueUrl}`; | |
console.log("[Glab Copier] Final command:", command); | |
return command; | |
} | |
// Insert the button next to the issue title. | |
function insertButton() { | |
const titleElem = document.querySelector('h1[data-testid="issue.views.issue-base.foundation.summary.heading"]'); | |
if (!titleElem) { | |
console.warn("[Glab Copier] Cannot insert button: Issue title element missing."); | |
return; | |
} | |
const parent = titleElem.parentNode; | |
// If button exists and is in the correct container, no need to insert. | |
const existingBtn = document.getElementById("glab-mr-copy-btn"); | |
if (existingBtn && parent.contains(existingBtn)) { | |
console.log("[Glab Copier] Button already present in the current ticket."); | |
return; | |
} | |
const btn = createButton(); | |
btn.addEventListener('click', function(e) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
const cmd = buildCommand(); | |
if (cmd) { | |
copyTextToClipboard(cmd); | |
} | |
}); | |
parent.insertBefore(btn, titleElem.nextSibling); | |
console.log("[Glab Copier] Button inserted successfully."); | |
} | |
// Use an interval to check periodically if the button exists. | |
setInterval(() => { | |
// Check if the ticket title exists. | |
const titleElem = document.querySelector('h1[data-testid="issue.views.issue-base.foundation.summary.heading"]'); | |
if (titleElem) { | |
// Check if the button is already present in the title's container. | |
const parent = titleElem.parentNode; | |
const btn = document.getElementById("glab-mr-copy-btn"); | |
if (!btn || !parent.contains(btn)) { | |
console.log("[Glab Copier] Button missing, re-inserting..."); | |
insertButton(); | |
} | |
} | |
}, 500); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment