NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Velvica Jira Improvements // @namespace https://jira.citilink.ru/ // @version 0.3.2 // @description makes Jira better! // @author Velvica // @match https://jira.citilink.ru/* // @match https://github.com/rentsoft/* // @run-at document-end // @grant none // @license MIT // ==/UserScript== if (window.location.hostname.includes('jira')) { const WATCHED_IDS = ['commentLevel-multi-select', 'assignee-single-select', 'customfield_15705-form']; const VELVICA_USER_SELECTOR_NAME = '_velvica_user_selector'; const userMapping = [ { velvicaName: 'abo', fullName: 'Александр Богданов', jiraName: 'albogdanov' }, // { velvicaName: 'van', fullName: 'Вадим Андриенко', jiraName: 'andrienko' }, // { velvicaName: 'mzu', fullName: 'Маргарита Зуйкова', jiraName: 'zuykova.m' }, { velvicaName: 'mno', fullName: 'Марк Норенберг', jiraName: 'norenberg' }, { velvicaName: 'nku', fullName: 'Никита Куличков', jiraName: 'kulichkovn' }, { velvicaName: 'rsv', fullName: 'Рустам Шаджалилов', jiraName: 'rustam.sh' }, ]; (function() { 'use strict'; const jiraNode = document.getElementById('jira'); const observerConfig = { childList: true, subtree: true }; const callback = function(mutationsList, observer) { for(const mutation of mutationsList) { if (mutation.type === 'childList') { const nodes = Array.from(mutation.addedNodes); if (nodes.some(node => WATCHED_IDS.includes(node.id))) { // Jira is laggy. Wait just in case. setTimeout(() => hookVelvicaUsers(), 300); } } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); observer.observe(jiraNode, observerConfig); function hookVelvicaUsers() { const velvicaUserSelector = document.createElement('select'); const usersField = document.getElementById('customfield_15705'); if (usersField === null) { return; } if (Array.from(usersField.parentNode.childNodes).some(node => node.name === VELVICA_USER_SELECTOR_NAME)) { // Do not duplicate. return true; } velvicaUserSelector.onchange = function () { const foundUser = userMapping.find(user => user.velvicaName === this.value); if (foundUser === null) { return true; } const { jiraName } = foundUser; const formerUsers = usersField.value.split(',') .map(userName => userName.trim()) .filter(userName => userName.length > 0); const onlyUnique = (value, index, self) => self.indexOf(value) === index; usersField.value = [...formerUsers, jiraName].filter(onlyUnique).join(', '); velvicaUserSelector.selectedIndex = 0; }; velvicaUserSelector.name = VELVICA_USER_SELECTOR_NAME; velvicaUserSelector.style.display = 'block'; velvicaUserSelector.style.fontSize = '13px'; velvicaUserSelector.style.fontFamily = 'monospace'; velvicaUserSelector.style.padding = '5px 10px'; velvicaUserSelector.style.backgroundImage = 'none'; velvicaUserSelector.style.backgroundColor = '#ebecf0'; velvicaUserSelector.style.border = 'none'; velvicaUserSelector.style.borderRadius = '3px'; velvicaUserSelector.style.marginBottom = '5px'; const firstOption = document.createElement('option'); firstOption.text = 'Добавить ответственного'; velvicaUserSelector.add(firstOption); for (const user of userMapping) { const option = document.createElement('option'); option.value = user.velvicaName; option.text = `${user.velvicaName} | ${user.fullName}`; velvicaUserSelector.add(option); } usersField.insertAdjacentElement('beforebegin', velvicaUserSelector); } })(); } else if (window.location.hostname.includes('github')) { // Must be idempotent. const apply = () => { const JIRA_LINK = 'https://jira.citilink.ru/browse/VEL-$1'; const MERGE_WARNING_LABELS = ['not yet', 'not-yet', 'dont-merge']; // PR title const title = document.querySelector('.js-issue-title'); if (title && !title.innerHTML.includes('<a')) { title.innerHTML = title.innerHTML.replaceAll( /VEL-([0-9]{5,6})/g, '<a target="_blank" title="Открыть задачу в Jira" href="' + JIRA_LINK + '">VEL-$1</a>' ); } // PR labels const labels = [...document.querySelectorAll('.js-issue-labels a')].map(label => label.getAttribute('data-name')); const hasNoMergeLabels = labels.some(label => MERGE_WARNING_LABELS.includes(label.toLowerCase())); const mergeBox = document.querySelector('.js-merge-box'); const addedWarning = () => mergeBox ? mergeBox.querySelector('.velvica-warning') : null; if (!addedWarning() && hasNoMergeLabels) { const retries = 10; for (let i = 0; i < retries; i++) { const timeout = setTimeout(() => { if (mergeBox && !addedWarning()) { mergeBox.innerHTML = '<p class="velvica-warning"><strong>⚠️ Этот PR не нужно мержить.</strong> Обратите внимание на метки.</p>' + mergeBox.innerHTML; clearTimeout(timeout); } }, 1000 * i); } } else if (addedWarning() && !hasNoMergeLabels) { // If the label has been removed. document.querySelector('.velvica-warning').remove(); } }; apply(); setInterval(apply, 2500); }