├── assets └── demo.gif ├── README.md └── mr-2-clickup.js /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-SLAYER/MR2ClickUp/main/assets/demo.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MRs 2 Clickup (Tampermonkey Script) 2 | 3 | ![picture](assets/demo.gif?raw=true) 4 | 5 | ## 🔧 Installation 6 | 7 | 1. **Install Tampermonkey**: 8 | 9 | If you haven't already, you need to install the Tampermonkey browser extension. You can find it for various browsers: 10 | - [Tampermonkey for Chrome](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) 11 | - [Tampermonkey for Firefox](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/) 12 | - [Tampermonkey for Edge](https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd) 13 | - [Tampermonkey for Opera/OperaGX](https://addons.opera.com/en-gb/extensions/details/tampermonkey-beta/) 14 | 15 | 2. **Install the Script**: 16 | 17 | - [Click Here and Press Install](mr-2-clickup.js?raw=True). 18 | 19 | - Click **Install** in the Tampermonkey prompt. 20 | 21 | 3. **Enable the Script**: 22 | Enable the script by clicking the switch next to the script name in the Tampermonkey Dashboard. 23 | 24 | 4. **Get Your Gemini API Key**: To use the script and fetch merge request data, you’ll need to get your **Gemini API Key**. Follow these steps to obtain it: 25 | 26 | 1. Visit the [Gemini API page](https://aistudio.google.com/app/apikey). 27 | 28 | 2. Sign in to your account. 29 | 30 | 3. Navigate to **API Keys** section. 31 | 32 | 4. Click **Create New Key** and save the API key for later use. 33 | 34 | 35 | 5. **Done!** You’re all set! Fill in the task with any additional info and hit submit. Easy peasy! -------------------------------------------------------------------------------- /mr-2-clickup.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Clickup Gen from Gitlab 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0 5 | // @description Generate ClickUp Tasks from git commits 6 | // @author X-SLAYER: (https://github.com/X-SLAYER) 7 | // @match https://gitlab.proxym-group.net/*merge_requests* 8 | // @icon https://gitlab.proxym-group.net/assets/favicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png 9 | // @grant GM_setClipboard 10 | // @grant GM_xmlhttpRequest 11 | // @grant GM_getValue 12 | // @grant GM_setValue 13 | // ==/UserScript== 14 | 15 | (function () { 16 | 'use strict'; 17 | 18 | let GEMINI_API_KEY = null; 19 | const MODEL_ID = 'gemini-1.5-flash'; 20 | let API_URL = ''; 21 | 22 | 23 | const systemPrompt = `I want you to act as a ClickUp task generator that converts raw text inputs into formatted task titles and descriptions. I will provide a brief description (e.g., 'login timeout bug' or 'dark mode toggle for mobile app'), and you will respond *only* with: 24 | 1. **Title**: A concise task name starting with a relevant tag like [UAT], [BUG-FIXING], [FEATURE], or [ENHANCEMENT]. 25 | 2. **Description**: A bullet-point list of 2-4 actionable steps or requirements (e.g., 'Fix session expiration logic', 'Add UI toggle in settings screen'). 26 | 27 | **Rules**: 28 | - Use tags contextually (e.g., [BUG-FIXING] for errors, [UAT] for testing tasks). 29 | - Prioritize clarity over technical jargon unless specified. 30 | - Include specific modules (e.g., 'checkout API', 'user profile page') if mentioned. 31 | 32 | **Examples**: 33 | - Input: "Slow response on checkout page" 34 | Output: 35 | [BUG-FIXING] Optimize Checkout Page API Response Time 36 | • Identify bottlenecks in /checkout API database queries. 37 | • Implement caching for product inventory data. 38 | • Target: Reduce latency from 3s to <1s. 39 | 40 | - Input: "Add Arabic language support" 41 | Output: 42 | [FEATURE] Implement Arabic Language Localisation 43 | • Integrate Arabic translations for all UI strings. 44 | • Ensure RTL (right-to-left) layout compatibility. 45 | • Test date/number formatting in invoices. 46 | `; 47 | 48 | async function getOrPromptForAPIKey() { 49 | GEMINI_API_KEY = await GM_getValue("GEMINI_API_KEY"); 50 | if (!GEMINI_API_KEY) { 51 | const userKey = prompt("🔐 Please enter your Gemini API Key:\nVisit: https://aistudio.google.com/app/apikey"); 52 | if (userKey) { 53 | await GM_setValue("GEMINI_API_KEY", userKey.trim()); 54 | GEMINI_API_KEY = userKey.trim(); 55 | alert("✅ API Key saved successfully!"); 56 | } else { 57 | alert("❌ Invalid API Key. Please try again."); 58 | } 59 | } 60 | } 61 | 62 | function addCheckboxes() { 63 | document.querySelectorAll('ul.controls').forEach((ul) => { 64 | const parentLi = ul.closest('li.merge-request'); 65 | if (parentLi && !parentLi.classList.contains('checkbox-added')) { 66 | const checkbox = document.createElement('input'); 67 | checkbox.type = 'checkbox'; 68 | checkbox.className = 'mr-selector-checkbox'; 69 | checkbox.style.marginRight = '10px'; 70 | 71 | const container = document.createElement('div'); 72 | container.style.display = 'flex'; 73 | container.style.alignItems = 'center'; 74 | container.appendChild(checkbox); 75 | parentLi.prepend(container); 76 | 77 | parentLi.classList.add('checkbox-added'); 78 | } 79 | }); 80 | } 81 | 82 | function addCopyButton() { 83 | if (document.getElementById('copy-selected-mrs-btn')) return; 84 | 85 | const btn = document.createElement('button'); 86 | btn.id = 'copy-selected-mrs-btn'; 87 | btn.innerText = 'Generate Clickup Tasks ✨'; 88 | btn.style.position = 'fixed'; 89 | btn.style.top = '10px'; 90 | btn.style.right = '10px'; 91 | btn.style.zIndex = '9999'; 92 | btn.style.padding = '12px 20px'; 93 | btn.style.background = 'linear-gradient(135deg, #F736AA, #F78847)'; 94 | btn.style.color = '#fff'; 95 | btn.style.border = 'none'; 96 | btn.style.borderRadius = '5px'; 97 | btn.style.cursor = 'pointer'; 98 | btn.style.fontWeight = 'bold'; 99 | btn.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.1)'; // Optional nice touch 100 | 101 | btn.addEventListener('click', async () => { 102 | 103 | await getOrPromptForAPIKey(); 104 | if (!GEMINI_API_KEY) return; 105 | API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:generateContent?key=${GEMINI_API_KEY}`; 106 | 107 | const selected = document.querySelectorAll('.mr-selector-checkbox:checked'); 108 | const texts = []; 109 | selected.forEach(checkbox => { 110 | const li = checkbox.closest('li.merge-request'); 111 | const anchor = li.querySelector('.merge-request-title-text a'); 112 | if (anchor) { 113 | texts.push(anchor.innerText.trim()); 114 | } 115 | }); 116 | 117 | if (!texts.length) { 118 | alert('Please select at least one merge request.'); 119 | return; 120 | } 121 | 122 | const originalText = btn.innerText; 123 | btn.innerText = '⏳ Generating...'; 124 | btn.disabled = true; 125 | 126 | const finalText = texts.join('\n'); 127 | GM_setClipboard(finalText); 128 | 129 | const inputText = texts.join('\n'); 130 | const payload = { 131 | contents: [{ 132 | role: "user", 133 | parts: [{ text: inputText }] 134 | }], 135 | systemInstruction: { 136 | parts: [{ text: systemPrompt }] 137 | }, 138 | generationConfig: { 139 | responseMimeType: "text/plain" 140 | } 141 | }; 142 | 143 | try { 144 | const resultText = await sendToGemini(payload); 145 | GM_setClipboard(resultText); 146 | alert('📋 Tasks copied to clipboard'); 147 | } catch (err) { 148 | alert('❌ Failed to generate tasks: ' + err.message); 149 | } finally { 150 | btn.innerText = originalText; 151 | btn.disabled = false; 152 | } 153 | }); 154 | 155 | document.body.appendChild(btn); 156 | } 157 | 158 | function sendToGemini(payload) { 159 | return new Promise((resolve, reject) => { 160 | GM_xmlhttpRequest({ 161 | method: 'POST', 162 | url: API_URL, 163 | headers: { 164 | 'Content-Type': 'application/json', 165 | }, 166 | data: JSON.stringify(payload), 167 | onload: function (res) { 168 | try { 169 | const json = JSON.parse(res.responseText); 170 | const text = json?.candidates?.[0]?.content?.parts?.[0]?.text; 171 | if (!text) throw new Error('No content returned from Gemini.'); 172 | resolve(text); 173 | } catch (e) { 174 | console.error('❌ Gemini response error:', res.responseText); 175 | reject(new Error('Invalid response from Gemini. ' + e.message)); 176 | } 177 | }, 178 | onerror: function (err) { 179 | reject(new Error('Network error: ' + err.message)); 180 | } 181 | }); 182 | }); 183 | } 184 | 185 | 186 | 187 | function init() { 188 | addCheckboxes(); 189 | addCopyButton(); 190 | } 191 | 192 | 193 | init(); 194 | setInterval(init, 2000); 195 | })(); 196 | --------------------------------------------------------------------------------