// Released under the MIT/X11 license // http://www.opensource.org/licenses/mit-license.php "use strict"; var bugURL = "https://bugzilla.redhat.com/show_bug.cgi?id="; var BTSPrefNS = "bugzilla-triage.setting."; // Shared contstants var NumberOfFrames = 7; // constants var SalmonPink = new Color(255, 224, 176); // RGB 255, 224, 176; HSL 36, 2, // 85 var ReporterColor = new Color(255, 255, 166); // RGB 255, 255, 166; HSL 60, 2, // 83 // global variables var config = {}; var constantData = {}; // This should be probably eliminated ASAP or // or done by other means. TODO /** * central handler processing messages from the main script. */ onMessage = function onMessage(msg) { console.log("onMessage - incoming : msg.cmd = " + msg.cmd); switch (msg.cmd) { case "ReloadThePage": document.location.reload(true); break; case "queryLocal": queryInNewTab(msg.data, getComponent(), getProduct()); break; case "CreateButtons": constantData = msg.data.constData; config = msg.data.config; generateButtons(msg.data.instPkgs, msg.data.kNodes); break; case "Error": alert("Error " + msg.data); break; case "Unhandled": break; default: if (RHOnMessageHandler) { RHOnMessageHandler(msg); } else { console.error("Error: unknown RPC call " + msg.toSource()); } } }; /** * @param cmd Object with all commands to be executed * * PROBLEM: according to https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\ * /Statements/for...in there is no guaranteed order of execution of * commands (i.e., key, commentObj[key] pairs) in for..in cycle. * According to https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\ * /Operators/Special_Operators/delete_Operator#Cross-browser_issues it seems that * everywhere except of Internet Explorer this should work well, but waiting * impatiently when this bite us. */ function executeCommand(cmdObj) { for (var key in cmdObj) { centralCommandDispatch(key, cmdObj[key]); } } /** * Actual execution function * * @param cmdLabel String with the name of the command to be executed * @param cmdParams Object with the appropriate parameters for the command */ function centralCommandDispatch (cmdLabel, cmdParams) { console.log("centralCommandDispatch : cmdLabel = " + cmdLabel); switch (cmdLabel) { case "name": case "position": break; case "resolution": case "product": case "component": case "version": case "priority": selectOption(cmdLabel, cmdParams); break; case "status": selectOption("bug_status", cmdParams); break; case "platform": selectOption("rep_platform", cmdParams); break; case "os": selectOption("op_sys", cmdParams); break; case "severity": selectOption("bug_severity", cmdParams); break; case "target": selectOption("target_milestone", cmdParams); break; case "addKeyword": addStuffToTextBox("keywords",cmdParams); break; case "removeKeyword": removeStuffFromTextBox("keywords", cmdParams); break; case "addWhiteboard": addStuffToTextBox("status_whiteboard",cmdParams); break; case "removeWhiteboard": removeStuffFromTextBox("status_whiteboard",cmdParams); break; case "assignee": changeAssignee(cmdParams); break; case "qacontact": clickMouse("bz_qa_contact_edit_action"); document.getElementById("qa_contact").value = cmdParams; break; case "url": clickMouse("bz_url_edit_action"); document.getElementById("bug_file_loc").value = cmdParams; break; // TODO dependson/blocked doesn't work. Find out why. case "addDependsOn": clickMouse("dependson_edit_action"); addStuffToTextBox("dependson", cmdParams); break; case "removeDependsOn": clickMouse("dependson_edit_action"); removeStuffFromTextBox("dependson", cmdParams); break; case "addBlocks": clickMouse("blocked_edit_action"); addStuffToTextBox("blocked", cmdParams); break; case "removeBlocks": clickMouse("blocked_edit_action"); removeStuffFromTextBox("blocked", cmdParams); break; case "comment": addStuffToTextBox("comment", cmdParams); break; case "commentIdx": throw "There should be no commentIdx here at all."; break; case "setNeedinfo": // cmdParams are actually ignored for now; we may in future // distinguish different actors to be target of needinfo setNeedinfoReporter(); break; case "addCC": addToCCList(cmdParams); break; case "queryStringOurBugzilla": queryForSelection(); break; // TODO flags, see also case "commit": if (cmdParams) { // Directly commit the form document.forms.namedItem("changeform").submit(); } break; default: if (RHcentralCommandDispatch) { RHcentralCommandDispatch(cmdLabel, cmdParams); } else { console.error("Unknown command:\n" + cmdLabel + "\nparameters:\n" + cmdParams); } break; } } /** * remove elements from the page based on their IDs * * @param doc Document object * @param target String/Array with ID(s) * @param remove Boolean indicating whether the node should be * actually removed or just hidden. * @return none * TODO remove parameter could be replaced by function which would * do actual activity. */ function killNodes(doc, target, remove) { var targetArr = target instanceof Array ? target : target.trim().split(/[,\s]+/); targetArr.forEach(function(x) { if (remove) { var targetNode = doc.getElementById(x); targetNode.parentNode.removeChild(targetNode); } else { x.style.display = "none"; } }); } /** * Change assignee of the bug * * @param newAssignee String with the email address of new assigneeAElement * or 'default' if the component's default assignee should be used. * Value null clears "Reset Assignee to default for component" checkbox * @return none */ function changeAssignee (newAssignee) { var defAssigneeButton = null; // Previous assignee should know what's going on in his bug addToCCList(getOwner()); // Optional value null if (newAssignee === null) { document.getElementById("set_default_assignee").removeAttribute( "checked"); return ; } if (getDefaultAssignee) { if (newAssignee === "default") { var defAss = getDefaultAssignee(); if (defAss) { newAssignee = defAss; } else { return ; } } } if (newAssignee) { clickMouse("bz_assignee_edit_action"); document.getElementById("assigned_to").value = newAssignee; document.getElementById("set_default_assignee").checked = false; defAssigneeButton = document.getElementById("defassignee_btn"); if (defAssigneeButton) { defAssigneeButton.style.display = "none"; } } } /** * Adds new option to the 'comment_action' scroll down box * * @param pkg String package name * @param cmd String with the name of the command * If the 'comment_action' scroll down box doesn't exist, this * function will set up new one. */ function addToCommentsDropdown (cmdObj) { var select = document.getElementById("comment_action"); if (!select) { config.commandsList = []; document.getElementById("comments").innerHTML += "
" + " " + " " + "
"; select = document.getElementById("comment_action"); select.addEventListener("change", function (evt) { var value = select.options[select.selectedIndex].value; executeCommand(config.commandsList[value]); }, false); } var opt = document.createElement("option"); var objIdx = config.commandsList.length + 1; opt.value = objIdx; config.commandsList[objIdx] = cmdObj; opt.textContent = cmdObj.name; select.appendChild(opt); } /** * Generic function to add new button to the page. Actually copies new button * from the old one (in order to have the same look-and-feel, etc. * * @param location Object around which the new button will be added * @param after Boolean before or after location ? * @param pkg String which package to take the command from * @param id String which command to take * @return none */ function createNewButton (location, after, cmdObj) { try { var newId = cmdObj.name.toLowerCase().replace(/[^a-z0-9]+/,"","g") + "_btn"; } catch (e) { console.error("createNewButton : e = " + e + "\ncreateNewButton : cmdObj.toSource() = " + cmdObj.toSource()); } // protection against double-firings if (document.getElementById(newId)) { console.log("Element with id " + newId + " already exists!"); return ; } var newButton = document.createElement("input"); newButton.setAttribute("id", newId); newButton.setAttribute("type", "button"); newButton.value = cmdObj.name; newButton.addEventListener("click", function(evt) { executeCommand(cmdObj); }, false); var originalLocation = document.getElementById(location); try { if (after) { originalLocation.parentNode.insertBefore(newButton, originalLocation.nextSibling); originalLocation.parentNode.insertBefore(document .createTextNode("\u00A0"), newButton); } else { originalLocation.parentNode.insertBefore(newButton, originalLocation); originalLocation.parentNode.insertBefore(document .createTextNode("\u00A0"), originalLocation); } } catch (e) { if (e instanceof TypeError) { console.error("cannot find originalLocation element with id " + location); } else { throw e; } } } /** * Generate button based on */ function generateButtons (pkgs, kNodes) { var topRowPosition = "topRowPositionID"; var bottomRowPosition = "commit"; // ========================================================= if (kNodes && window.location.hostname in kNodes) { var killConf = kNodes[window.location.hostname]; killNodes(document, killConf[0], killConf[1]); } // create anchor for the top toolbar var commentBox = document.getElementById("comment"); var brElement = document.createElement("br"); brElement.setAttribute("id",topRowPosition); commentBox.parentNode.normalize(); commentBox.parentNode.insertBefore(brElement, commentBox); for (var pkg in pkgs) { for (var cmdIdx in pkgs[pkg]) { var cmdObj = pkgs[pkg][cmdIdx]; if (cmdObj.position !== undefined) { switch (cmdObj.position) { case "topRow": createNewButton(topRowPosition, false, cmdObj); break; case "bottomRow": createNewButton(bottomRowPosition, false, cmdObj); break; case "dropDown": addToCommentsDropdown(cmdObj); break; default: // [+-]ID var firstChr = cmdObj.position.charAt(0); var newId = cmdObj.position.substr(1); createNewButton(newId, firstChr === "+", cmdObj); break; } } else { console.error("generateButtons : rejected cmdObj = " + cmdObj.toSource()); } } } // TODO This is weird in this place, but that's the place where all constantData etc. // are finally defined and available. if (RHBZinit) { RHBZinit(); } } function setConfigurationButton () { var additionalButtons = document.querySelector("#bugzilla-body *.related_actions"); var configurationButtonUI = document.createElement("li"); configurationButtonUI.innerHTML = "\u00A0-\u00A0" + "Triage configuration"; additionalButtons.appendChild(configurationButtonUI); document.getElementById("configurationButton").addEventListener( "click", function(evt) { postMessage(new Message("ChangeJSONURL", null)); evt.stopPropagation(); evt.preventDefault(); }, false); } /** * Get the current title of the bug * * @return string */ function getSummary() { return document.getElementById("short_desc_nonedit_display").textContent; } /** * Get the current email of the reporter of the bug. * * @return string */ function getReporter () { var reporterElement = getOptionTableCell("bz_show_bug_column_2", "Reported"); // RH Bugzilla after upgrade to 3.6.2 moved the information to other column if (!reporterElement) { reporterElement = getOptionTableCell("bz_show_bug_column_1", "Reported", true); } // Maemo calls the label "Reporter" and it doesn't have ids on table columns ... TODO(maemo) return parseMailto(reporterElement); } function getComponent() { var elem = document.getElementById("component"); if (elem) { return elem.value; } return null; } function getProduct() { var elem = document.getElementById("product"); if (elem) { return elem.value; } return null; } function commentsWalker (fce) { var comments = document.getElementById("comments"). getElementsByClassName("bz_comment"); Array.forEach(comments, function(item) { fce(item); }); } /** * Set background color of all comments made by reporter in ReporterColor color * */ function checkComments () { var reporterRE = new RegExp(getReporter()); commentsWalker(function(x) { var email = parseMailto(x.getElementsByClassName("vcard")[0] .getElementsByTagName("a")[0]); if (reporterRE.test(email)) { x.style.backgroundColor = ReporterColor.toString(); } }); } function collectComments () { var outStr = ""; commentsWalker(function(x) { outStr += x.getElementsByTagName("pre")[0].textContent + "\n"; }); return outStr.trim(); } /** * Select option with given value on the