Jira assignee

Set Jira assignee

// ==UserScript==
// @name         Jira assignee
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Set Jira assignee
// @match        https://hp-jira.external.hp.com/secure/RapidBoard.jspa*view=planning*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jira.com
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const injectCSS = css => {
        let el = document.createElement('style');
        el.type = 'text/css';
        el.innerText = css;
        document.head.appendChild(el);
        return el;
    };

    injectCSS(`.button {margin: 0 5px 5px 5px;appearance: none;border: 2px solid rgba(27, 31, 35, .15);border-radius: 6px;box-shadow: rgba(27, 31, 35, .1) 0 1px 0;box-sizing: border-box;color: #fff;cursor: pointer;display: inline-block;font-family: -apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";font-size: 13px;font-weight: 600;line-height: 10px;padding: 5px 6px;position: relative;text-align: center;text-decoration: none;user-select: none;-webkit-user-select: none;touch-action: manipulation;vertical-align: middle;white-space: nowrap;}
        .button-green {background-color: #2ea44f;}
        .button-pink {background-color: #EA4C89;}
        #assignee-list-config li {list-style-type: none}
        #assignee-list-config {position: fixed;z-index: 10000000;top: 0px;right: 60px;width: 360px;height: 482px; padding: 30px; background: -webkit-gradient(linear, 0 0, 0 100%, from(#fcfcfc), to(#f2f2f7)) !important}
        #assignee-list-config button {margin: 0 26px;text-align: center;white-space: nowrap;background-color: #f9f9f9 !important;border: 1px solid #ccc !important;box-shadow: inset 0 10px 5px white !important;border-radius: 3px !important;padding: 3px 3px !important; width: 100px}
        #assignee-list-config * {color: black;text-align: left;line-height: normal;font-size: 15px;min-height: 12px;}
        #assignee-list-config textarea { width: 300px;height: 400px;margin: 10px 0;}
        .last-assigned { background: lightblue; }
    `);

    // this config is from backlog's quicker filters in header
    let assignes;
    let assignedTasks = {};

    Promise.all([
        GM.getValue("assignee-config", '{}'),
    ]).then(function(values) {
        let assignesText = values[0];
        assignes = JSON.parse(assignesText);

        GM_registerMenuCommand('Assignees config', function(){
            $("body").append(`<div id="assignee-list-config">
                <li>
                    <span>Please input assignees config: <br>e.g <b>{"email": "display name"}</b></span>
                    <textarea></textarea>
                </li>
                <li>
                    <button id="assignee-list-config-ok">OK</button>
                    <button id="assignee-list-config-cancel">Cancel</button>
                </li>
            </div>`);

            $("#assignee-list-config textarea").val(JSON.stringify(assignes, null, 4));

            $("#assignee-list-config textarea").change(function(){
                $("#assignee-list-config textarea").css("border-color", "");
            })

            $("#assignee-list-config-ok").click(function(){
                try {
                    let newAssignes = JSON.parse($("#assignee-list-config textarea").val());
                    let newAssignesText = JSON.stringify(newAssignes);

                    if (newAssignesText !== assignesText) {
                        assignes = newAssignes;
                        assignesText = newAssignesText;
                        GM.setValue("assignee-config", newAssignesText);

                        // remove old assignee list and add new one
                        $(".assignee-list").remove();
                        addAssignee();
                    }

                    // remove config panel
                    $("#assignee-list-config").remove();
                } catch (e) {
                    $("#assignee-list-config textarea").css("border-color", "red");
                }
            })

            $("#assignee-list-config-cancel").click(function(){
                $("#assignee-list-config").remove();
            })
        });
    })

    function queryUserInfoFromCache(email) {
        let userInfo = localStorage.getItem("assignee_"+email);
        if (!userInfo) {
            fetch('https://hp-jira.external.hp.com/rest/api/2/user/search?username='+email, {
                method: 'GET',
                headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
            }).then(response => {
                return response.text();
            }).then(body => {
                if (body.length) {
                    let data = JSON.parse(body)[0]
                    console.log("-----------------", "email set in localStorage", email, data)

                    localStorage.setItem("assignee_"+email, JSON.stringify(data))

                    userInfo = data;
                }
            }).catch(err => console.error(err));
        }

        return JSON.parse(userInfo)
    }

    function assigneeEqual(email, text) {
        let userInfo = queryUserInfoFromCache(email);
        if (userInfo && userInfo.displayName === text) {
            return true
        }

        if (assignes[email]) {
            let emailToName = email.replace("@hp.com", "").replaceAll(".", " ").replace(/[0-9]/g, '');
            text = text.toLowerCase();

            return text.contains(assignes[email].toLowerCase()) || text.contains(emailToName)
        }

        return false;
    }

    function sleep(ms = 0) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function addAssignee() {
        while (true) {
            let planned = $(".ghx-sprint-planned");
            if (! planned.length) {
                console.log("-----------------class ghx-sprint-planned not found!");
                await sleep(300)
                continue
            }
            let tasks = planned.find(".js-issue-list").children();
            if (! tasks.length) {
                console.log("-----------------sprint tasks not found!");
                return
            }

            if (_.isEmpty(assignes)) {
                await sleep(300)
                console.log("-----------------assignes not found!");
                return
            }
            // add story point hint
            $(".ghx-stat-total").each(function (index, value) {
                var estimate = $(this).find("aui-badge").text();

                if (value && $(this).parents(".ghx-backlog-container").find(".estimate-count").attr("points") != estimate) {
                    $(this).parents(".ghx-backlog-container").find(".estimate-count").remove();
                    $(this).parents(".ghx-backlog-container").find(".ghx-issue-count").after('<div class="estimate-count" style="font-weight: bold; display: inline; margin-left: 10px;" points="'+estimate+'">Total points: ' + estimate +'</div>');
                }
            })

            let lastAssignIssue = localStorage.getItem("last_assign_issue");

            // add assignee
            tasks.each(function(index, element) {
                let issueId = $(this).attr("data-issue-key");
                if (issueId !== undefined) {
                    if (lastAssignIssue === issueId) {
                        $(this).addClass("last-assigned");
                    }

                    if ($(this).find(".assignee-list").length) {
                        return;
                    }

                    // cut the shadow, and expose this tool to click
                    $(this).find('.m-sortable-trigger').css("height", $(this).find(".ghx-issue-content").height());

                    let assignedTitle = "";

                    let assignedNode = $(this).find('.ghx-estimate');
                    if (assignedNode.length === 1) {
                        assignedTitle = assignedNode.children().first().attr("title").replace("Assignee: ", "");
                    }

                    $(this).append('<div class="assignee-list"></div>');

                    for (const email in assignes) {
                        let btnClass = "button-green";

                        // if has assigned from this tool, or assignee already existed
                        if (
                            assignedTasks[issueId] == email ||
                            assignedTasks[issueId] == undefined && assigneeEqual(email, assignedTitle) && (assignedTasks[issueId] = email)
                        ) {
                            btnClass = "button-pink";
                        }

                        $(this).children(".assignee-list").append('<button class="button '+btnClass+' set-assignee" email="'+email+'">'+assignes[email]+'</button>');
                    }
                    $(this).css("margin-bottom", "7px");
                }
            })

            $(".assignee-list").unbind('click').click(function(e) {
                e.stopPropagation();
            });

            $(".set-assignee").unbind('click').click(function(e) {
                if ($(this).hasClass("button-pink")) {
                    return;
                }

                let issueId = $(this).parent().parent().attr("data-issue-key");
                localStorage.setItem("last_assign_issue", issueId);

                $(this).parents(".ghx-issues").find(".last-assigned").removeClass("last-assigned");
                $(this).parent().parent().addClass("last-assigned");

                // remove color from others
                $(this).parent().find(".button-pink").removeClass("button-pink").addClass("button-green");

                let changingBorderColor = setInterval(function flashText(target) {
                        target.toggleClass("button-green").toggleClass("button-pink");
                }, 160, $(this));

                let responseCode = 204;
                let email = $(this).attr("email");

                fetch('https://hp-jira.external.hp.com/rest/api/2/issue/'+issueId, {
                    method: 'PUT',
                    body: `{"fields": {"assignee": {"name": "`+email+`"}}}`,
                    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
                }).then(response => {
                    responseCode = response.status;

                    if (responseCode !== 204) {
                        $(this).addClass("button-green").removeClass("button-pink");
                        alert(`change assignee failed, Response status: ${response.status}`);

                    } else {
                        $(this).removeClass("button-green").addClass("button-pink");

                        assignedTasks[issueId] = email;
                    }

                    clearInterval(changingBorderColor);

                    return response.text();
                }).then(body => {
                    if (responseCode !== 204) {
                        console.log(`---------------------------------issue: ${issueId}, change assignee failed, status code ${responseCode}, response message ${body}`)
                    }
                }).catch(err => console.error(err));
            });

            await sleep(1000)
        }
    }

    addAssignee();

    let avatarColorDict = {
        "C": "#f691b2",
        "L": "#8eb021",
        "Y": "#654982",
    }

    function getLetterCol(firstLetter) {
        if (firstLetter.length > 1) {
            firstLetter = firstLetter[0];
        }

        firstLetter = firstLetter.toUpperCase();
        return avatarColorDict[firstLetter] || "#8eb021";
    }

    // modify workload info
    $(document).on('DOMNodeInserted','#assigned-work-dialog',function() {
        // has not modified and display layer is show
        if (! $('#assigned-work-dialog').hasClass("fixed-by-script") && $("#aui-dialog-close").length) {
            // find out all assignee list boxes
            let sprintName = $("#assigned-work-dialog-title").text().replace("Workload by assignee - ", "");
            let assigneeList;
            $('span[data-fieldname="sprintName"]').each(function() {
                if ($(this).text() === sprintName) {
                    assigneeList = $(this).parents(".ghx-backlog-container").find(".assignee-list");
                }
            })

            if (assigneeList === undefined || ! assigneeList.length) {
                console.log("assigneeList not found, exit")
                return;
            }

            // calculate workload
            let workloadList = {};

            assigneeList.each(function(index, element) {
                let identifier = "Unassigned"

                if ($(element).find(".button-pink").length) {
                    identifier = $(element).find(".button-pink").attr("email");
                }

                let point = parseFloat($(element).siblings(".ghx-issue-content").find(".ghx-estimate aui-badge").text());

                if (!workloadList.hasOwnProperty(identifier)) {
                    workloadList[identifier] = {"point": point, "issues": 1};
                } else {
                    workloadList[identifier].point += point;
                    workloadList[identifier].issues += 1;
                }
            })

            $.each(workloadList, function(email, info) {
                let assigneeRowExist = false

                $("#assigned-work-dialog").find("tbody tr").each(function(index, tr) {
                    let firstTd = $(tr).children().first();
                    let assignee = firstTd.text();

                    // assignee node has two format
                    // <td><span class="ghx-no-avatar">Unassigned</span></td>
                    // <td><img class="ghx-avatar-img" alt="" loading="lazy">assignee name</td>
                    if (assignee !== "Unassigned") {
                        assignee = firstTd.clone().children().remove().end().text();
                    }

                    if (email === "Unassigned" || assigneeEqual(email, assignee)) {
                        assigneeRowExist = true;

                        $('#assigned-work-dialog').addClass("fixed-by-script");

                        let issuesInTable = parseInt($(tr).children().eq(1).text());
                        if (issuesInTable !== info.issues) {
                            $(tr).children().eq(1).text(info.issues);
                            $(tr).children().eq(2).text(info.point);

                            console.log(`change Workload for user: ${email}, issues: ${info.issues}, point: ${info.point}`);
                        }
                        return false;
                    }
                })

                if (!assigneeRowExist) {
                    let userInfo = queryUserInfoFromCache(email);

                    if (!userInfo) {
                        return;
                    }

                    let firstLetter = userInfo.displayName[0];
                    let color = getLetterCol(firstLetter);
                    let image = `<span class="ghx-avatar-img ghx-auto-avatar" style="background-color: ${color}; ">${firstLetter}</span>`;

                    let avatar = userInfo.avatarUrls["48x48"];
                    if (avatar !== "https://hp-jira.external.hp.com/secure/useravatar?avatarId=10122") {
                        image = `<img src="${avatar}" class="ghx-avatar-img" alt="" loading="lazy">`;
                    }


                    $("#assigned-work-dialog").find("tbody").append(`<tr>
                        <td>${image}${userInfo.displayName}</td>
                        <td class="ghx-right">${info.issues}</td>
                        <td class="ghx-right">${info.point}</td>
                    </tr>`);
                }
            })
        }
    })
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址