IgorVodka / Velvica Jira Improvements

// ==UserScript==
// @name         Velvica Jira Improvements
// @namespace    https://jira.citilink.ru/
// @version      0.1.5
// @description  makes Jira better!
// @author       Velvica
// @match        https://jira.citilink.ru/*
// @grant        none
// @license      MIT
// ==/UserScript==

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: 'aza', fullName: 'Алексей Захаров', jiraName: 'alekseyz' },
    { velvicaName: 'dsi', fullName: 'Дмитрий Сизоненко', jiraName: 'sizonenko' },
    { velvicaName: 'dtk', fullName: 'Дмитрий Ткачук', jiraName: 'tkachuk.d' },
    { velvicaName: 'igv', fullName: 'Игорь Водка', jiraName: 'vodka.i' },
    { velvicaName: 'iik', fullName: 'Ильшат Иксанов', jiraName: 'iksanov.i' },
    { velvicaName: 'kno', fullName: 'Кирилл Новиков', jiraName: 'novikov.k' },
    { velvicaName: 'kti', fullName: 'Кирилл Тихонов', jiraName: 'tikhonov.k' },
    { velvicaName: 'mzu', fullName: 'Маргарита Зуйкова', jiraName: 'zuykova.m' },
    { velvicaName: 'miv', fullName: 'Мария Иваненко', jiraName: 'ivanenko.m' },
    { velvicaName: 'mno', fullName: 'Марк Норенберг', jiraName: 'norenberg' },
    { velvicaName: 'mgr', fullName: 'Михаил Греков', jiraName: 'grekov.m' },
    { velvicaName: 'nku', fullName: 'Никита Куличков', jiraName: 'kulichkovn' },
    { velvicaName: 'nmi', fullName: 'Наталья Мистюкова', jiraName: 'mistyukova' },
    { velvicaName: 'rsv', fullName: 'Рустам Шаджалилов', jiraName: 'rustam.sh' },
    { velvicaName: 'tne', fullName: 'Татьяна Невская', jiraName: 'nevskaya.t' },
    { velvicaName: 'van', fullName: 'Вадим Андриенко', jiraName: 'andrienko' },
];

(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);
    }
})();