您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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或关注我们的公众号极客氢云获取最新地址