Skip to content

Instantly share code, notes, and snippets.

@joaomcarlos
Last active May 21, 2025 11:16
Show Gist options
  • Save joaomcarlos/60a32a465f4330bc4c5a47ade9d25a56 to your computer and use it in GitHub Desktop.
Save joaomcarlos/60a32a465f4330bc4c5a47ade9d25a56 to your computer and use it in GitHub Desktop.
Jira to GitLab issue + merge request command generator button
// ==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