diff options
Diffstat (limited to 'data/bzpage.js')
-rw-r--r-- | data/bzpage.js | 1087 |
1 files changed, 1087 insertions, 0 deletions
diff --git a/data/bzpage.js b/data/bzpage.js new file mode 100644 index 0000000..cb539dc --- /dev/null +++ b/data/bzpage.js @@ -0,0 +1,1087 @@ +/*jslint forin: true, rhino: true, onevar: false, browser: true, evil: true, laxbreak: true, undef: true, nomen: true, eqeqeq: true, bitwise: true, maxerr: 1000, immed: false, white: false, plusplus: false, regexp: false, undef: false, strict: true */ +// Released under the MIT/X11 license +// http://www.opensource.org/licenses/mit-license.php +"use strict"; +var util = require("util"); +var passUtils = require("passwords"); +var apiUtils = require("api-utils"); +var selfMod = require("self"); +var clip = require("clipboard"); +var preferences = require("preferences-service"); +var selection = require("selection"); +var prompts = require("prompts"); +var Request = require("request").Request; +var tabs = require("tabs"); +var Color = require("color").Color; + +var bugURL = "https://bugzilla.redhat.com/show_bug.cgi?id="; + +// Shared contstants +var NumberOfFrames = 7; +exports.NumberOfFrames = NumberOfFrames; +var BTSPrefNS = "bugzilla-triage.setting."; +exports.BTSPrefNS = BTSPrefNS; +var BTSPassRealm = "BTSXMLRPCPass"; + +// ============================================ + +var NotLoggedinException = function NotLoggedinException (message) { + this.message = message; + this.name = "NotLoggedinException"; +}; + +NotLoggedinException.prototype.toString = function () { + return this.name + ': "' + this.message + '"'; +}; +exports.NotLoggedinException = NotLoggedinException; + +// ==================================================================================== +// BZPage's methods +var BZPage = function BZPage(win, config) { + // constants + this.SalmonPink = new Color(255, 224, 176); // RGB 255, 224, 176; HSL 36, 2, + // 85 + this.ReporterColor = new Color(255, 255, 166); // RGB 255, 255, 166; HSL 60, 2, + // 83 + + // initialize dynamic properties + var that = this; + this.win = win; + this.doc = win.document; + this.hostname = this.win.location.hostname; + + // First, preflight check ... if we are not logged in, there + // is nothing we can do. + var logoutLink = Array.some(this.doc.links, function (x) { + return x.search === "?logout=1" ; + }); + if (!logoutLink) { + throw new NotLoggedinException("Not logged in"); + } + + // So, now we know we are logged in, so we can get to + // the real work. + this.packages = this.getInstalledPackages(config); + + if ("commentStrings" in config.gJSONData) { + this.commentStrings = config.gJSONData.commentStrings; + } + + this.constantData = {}; + if ("constantData" in config.gJSONData) { + this.constantData = config.gJSONData.constantData; + this.constantData.queryUpstreamBug = JSON.parse( + selfMod.data.load("queryUpstreamBug.json")); + this.constantData.XMLRPCData = JSON.parse( + selfMod.data.load("XMLRPCdata.json")); + } + + if ("CCmaintainer" in this.constantData) { + this.defBugzillaMaintainerArr = this.constantData.CCmaintainer; + } + + if ("suspiciousComponents" in config.gJSONData.configData) { + this.suspiciousComponents = config.gJSONData.configData.suspiciousComponents; + } + + if ("XorgLogAnalysis" in config.gJSONData.configData) { + this.xorglogAnalysis = config.gJSONData.configData.XorgLogAnalysis; + } + + if ("submitsLogging" in config.gJSONData.configData && + config.gJSONData.configData.submitsLogging) { + this.log = config.logger; + this.setUpLogging(); + } + + if ("killNodes" in config.gJSONData.configData && + this.hostname in config.gJSONData.configData.killNodes) { + var killConf = config.gJSONData.configData.killNodes[this.hostname]; + util.killNodes(this.doc, killConf[0], killConf[1]); + } + + this.setConfigurationButton(); + this.submitHandlerInstalled = false; + + this.login = this.getLogin(); + // XML-RPC password + if (this.hostname in this.constantData.XMLRPCData) { + this.password = this.getPassword(this.login); + } + + this.bugNo = util.getBugNo(this.doc.location.toString()); + + // deal with aliases + if (isNaN(parseInt(this.bugNo, 10)) && this.password) { + this.bugNo = this.getRealBugNo(); + } + + this.title = this.doc.getElementById("short_desc_nonedit_display").textContent; + this.CCList = this.getCCList(); + + // Prepare for query buttons + // element ID brElementPlace_location is later used in JSON files + // Stay with this add_comment element even if RH BZ upgrades, this seems + // to be generally much more stable (even with other bugzillas, e.g. b.gnome.org) + // then some getElementById. + var commentArea = this.doc.getElementsByName("add_comment")[0].parentNode; + if (commentArea) { + var brElementPlacer = commentArea.getElementsByTagName("br"); + brElementPlacer = brElementPlacer[0]; + if (brElementPlacer) { + brElementPlacer.setAttribute("id","brElementPlacer_location"); + brElementPlacer.parentNode.insertBefore(this.doc.createElement("br"), + brElementPlacer); + } + } + + this.checkComments(); + this.generateButtons(); +}; + +/** + * In case URL contains alias, not the real bug number, get the real bug no + * from the XML representation. Sets correct value to this.bugNo. + */ +BZPage.prototype.getRealBugNo = function () { + console.log("We have to deal with bug aliased as " + this.bugNo); + var that = this; + // https://bugzilla.redhat.com/show_bug.cgi?ctype=xml&id=serialWacom + Request({ + url: this.win.location.href+"&ctype=xml", + onComplete: function(response) { + if (response.status === 200) { + var xmlRepr = response.xml; + var bugID = parseInt(xmlRepr.getElementsByTagName("bug_id")[0].textContent, 10); + if (isNaN(bugID)) { + throw new Error("Cannot get bug no. even from XML representation!"); + } + that.bugNo = bugID; + console.log("The real bug no. is " + bugID); + } + } + }).get(); +}; + +/** + * + */ +BZPage.prototype.getInstalledPackages = function getInstalledPackages(cfg) { + var installedPackages = {}; + var enabledPackages = []; + + // Collect enabled packages per hostname (plus default ones) + if (cfg.gJSONData && ("commentPackages" in cfg.gJSONData)) { + if ("enabledPackages" in cfg.gJSONData.configData) { + var epObject = cfg.gJSONData.configData.enabledPackages; + if (this.hostname in epObject) { + enabledPackages = enabledPackages.concat(epObject[this.hostname].split(/[,\s]+/)); + } + if ("any" in epObject) { + enabledPackages = enabledPackages.concat(epObject.any.split(/[,\s]+/)); + } + } + + if ((enabledPackages.length === 1) && (enabledPackages[0] === "all")) { + enabledPackages = []; + for (var key in cfg.gJSONData.commentPackages) { + enabledPackages.push(key); + } + } + + enabledPackages.forEach(function (pkg, idx, arr) { + if (pkg in cfg.gJSONData.commentPackages) { + installedPackages[pkg] = cfg.gJSONData.commentPackages[pkg]; + } + }); + } + return installedPackages; +}; + +/** + * 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 + */ +BZPage.prototype.centralCommandDispatch = function centralCommandDispatch (cmdLabel, cmdParams) { + switch (cmdLabel) { + case "resolution": + case "product": + case "component": + case "version": + case "priority": + this.selectOption(cmdLabel, cmdParams); + break; + case "status": + this.selectOption("bug_status", cmdParams); + break; + case "platform": + this.selectOption("rep_platform", cmdParams); + break; + case "os": + this.selectOption("op_sys", cmdParams); + break; + case "severity": + this.selectOption("bug_severity", cmdParams); + break; + case "target": + this.selectOption("target_milestone", cmdParams); + break; + case "addKeyword": + this.addStuffToTextBox("keywords",cmdParams); + break; + case "removeKeyword": + this.removeStuffFromTextBox("keywords", cmdParams); + break; + case "addWhiteboard": + this.addStuffToTextBox("status_whiteboard",cmdParams); + break; + case "removeWhiteboard": + this.removeStuffFromTextBox("status_whiteboard",cmdParams); + break; + case "assignee": + this.changeAssignee(cmdParams); + break; + case "qacontact": + this.clickMouse("bz_qa_contact_edit_action"); + this.doc.getElementById("qa_contact").value = cmdParams; + break; + case "url": + this.clickMouse("bz_url_edit_action"); + this.doc.getElementById("bug_file_loc").value = cmdParams; + break; + // TODO dependson/blocked doesn't work. Find out why. + case "addDependsOn": + this.clickMouse("dependson_edit_action"); + this.addStuffToTextBox("dependson", cmdParams); + break; + case "removeDependsOn": + this.clickMouse("dependson_edit_action"); + this.removeStuffFromTextBox("dependson", cmdParams); + break; + case "addBlocks": + this.clickMouse("blocked_edit_action"); + this.addStuffToTextBox("blocked", cmdParams); + break; + case "removeBlocks": + this.clickMouse("blocked_edit_action"); + this.removeStuffFromTextBox("blocked", cmdParams); + break; + case "comment": + this.addStuffToTextBox("comment", cmdParams); + break; + case "commentIdx": + var commentText = this.commentStrings[cmdParams]; + this.addStuffToTextBox("comment", commentText); + break; + case "setNeedinfo": + // cmdParams are actually ignored for now; we may in future + // distinguish different actors to be target of needinfo + this.setNeedinfoReporter(); + break; + case "addCC": + this.addToCCList(cmdParams); + break; + case "queryStringOurBugzilla": + this.queryForSelection(); + break; + // TODO flags, see also + + case "commit": + if (cmdParams) { + // Directly commit the form + this.doc.forms.namedItem("changeform").submit(); + } + break; + } +}; + +/** + * Take the ID of the package/id combination, and execute it + * + * @param String combined package + "//" + id combination + * Fetches the command object from this.installedPackages and then + * goes through all commands contained in it, and calls + * this.centralCommandDispatch to execute them. + * + * 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. + */ +BZPage.prototype.executeCommand = function executeCommand (cmd) { + var cmdArr = cmd.split("//"); + var commentObj = this.packages[cmdArr[0]][cmdArr[1]]; + + for (var key in commentObj) { + this.centralCommandDispatch(key,commentObj[key]); + } +}; + +/** + * 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 + */ +BZPage.prototype.changeAssignee = function changeAssignee (newAssignee) { + var defAssigneeButton = null; + // Previous assignee should know what's going on in his bug + this.addToCCList(this.owner); + + // Optional value null + if (newAssignee === null) { + this.doc.getElementById("set_default_assignee").removeAttribute( + "checked"); + return ; + } + + if (this.getDefaultAssignee) { + if (newAssignee === "default") { + var defAss = this.getDefaultAssignee(); + if (defAss) { + newAssignee = defAss; + } else { + return ; + } + } + } + + if (newAssignee) { + this.clickMouse("bz_assignee_edit_action"); + this.doc.getElementById("assigned_to").value = newAssignee; + this.doc.getElementById("set_default_assignee").checked = false; + defAssigneeButton = this.doc.getElementById("setDefaultAssignee_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. + */ +BZPage.prototype.addToCommentsDropdown = function addToCommentsDropdown (pkg, cmd) { + var select = this.doc.getElementById("comment_action"); + if (!select) { + var that = this; + this.doc.getElementById("comments").innerHTML += + "<div id='make_bugzilla_comment_action'>" + + " <label for='comment_action'>Add Comment: </label>" + + " <select id='comment_action'>" + + " <option value=''>-- Select Comment from List --</option>" + + "</div>"; + select = this.doc.getElementById("comment_action"); + select.addEventListener("change", function (evt) { + var value = ""; + var valueElement = that.doc.getElementById("comment_action"); + if (valueElement) { + var selIdx = valueElement.selectedIndex; + value = valueElement.options[selIdx].value; + console.log("value = " + value); + } else { + return; + } + that.executeCommand(value); + }, false); + } + + var opt = this.doc.createElement("option"); + opt.value = pkg + "//" + cmd; + opt.textContent = this.packages[pkg][cmd].name; + select.appendChild(opt); +}; + +/** + * Create a A element leadink nowhere, but with listener running a callback on the click + * + * @param id String with a id to be added to the element + * @param text String with a string to be added as a textContent of the element + * @param parent Node which is a parent of the object + * @param callback Function to be called after clicking on the link + * @param params Array with parameters of the callback + * @param Boolean breakBefore if there should be a <br> element before. + * @return none + */ +BZPage.prototype.createDeadLink = function createDeadLink (id, text, parent, callback, params, before, covered, accesskey) { + var that = this; + params = util.valToArray(params); + var locParent = {}; + + // Yes, I want != here, not !== + if (covered != null) { + locParent = this.doc.createElement(covered); + parent.appendChild(locParent); + } else { + locParent = parent; + } + + var newAElem = this.doc.createElement("a"); + newAElem.setAttribute("id", id); + if (accesskey) { + newAElem.setAttribute("accesskey", accesskey); + } + newAElem.setAttribute("href", ""); + newAElem.textContent = text; + + newAElem.addEventListener("click", function(evt) { + callback.apply(that, params); + evt.stopPropagation(); + evt.preventDefault(); + }, false); + + if ((before === "br") || (before === true)) { + locParent.appendChild(this.doc.createElement("br")); + } else if (before === "dash") { + locParent.appendChild(this.doc.createTextNode("\u00A0-\u00A0")); + } + + locParent.appendChild(newAElem); +}; + + +/** + * 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 + */ +BZPage.prototype.createNewButton = function createNewButton (location, after, pkg, id) { + var that = this; + var cmdObj = this.packages[pkg][id]; + var newId = id + "_btn"; + var label = cmdObj.name; + + // protection against double-firings + if (this.doc.getElementById(newId)) { + console.log("Element with id " + newId + "already exists!"); + return ; + } + + // creation of button might be conditional on existence of data in constantData + if ("ifExist" in cmdObj) { + if (!(cmdObj.ifExist in this.constantData)) { + return ; + } + } + + var newButton = this.doc.createElement("input"); + newButton.setAttribute("id", newId); + newButton.setAttribute("type", "button"); + newButton.value = label; + newButton.addEventListener("click", function(evt) { + that.executeCommand(pkg + "//" + id); + }, false); + + var originalLocation = this.doc.getElementById(location); + + try { + if (after) { + originalLocation.parentNode.insertBefore(newButton, + originalLocation.nextSibling); + originalLocation.parentNode.insertBefore(this.doc + .createTextNode("\u00A0"), newButton); + } else { + originalLocation.parentNode.insertBefore(newButton, originalLocation); + originalLocation.parentNode.insertBefore(this.doc + .createTextNode("\u00A0"), originalLocation); + } + } catch (e) { + if (e instanceof TypeError) { + console.error("cannot find originalLocation element with id " + location); + } else { + throw e; + } + } +}; + +/** + * + */ +BZPage.prototype.generateButtons = function generateButtons () { + var topRowPosition = "topRowPositionID"; + var bottomRowPosition = "commit"; + + // create anchor for the top toolbar + var commentBox = this.doc.getElementById("comment"); + var brElement = this.doc.createElement("br"); + brElement.setAttribute("id",topRowPosition); + commentBox.parentNode.normalize(); + commentBox.parentNode.insertBefore(brElement, commentBox); + + for (var pkg in this.packages) { + for (var cmdIdx in this.packages[pkg]) { + var cmdObj = this.packages[pkg][cmdIdx]; + switch (cmdObj.position) { + case "topRow": + this.createNewButton(topRowPosition, false, pkg, cmdIdx); + break; + case "bottomRow": + this.createNewButton(bottomRowPosition, false, pkg, cmdIdx); + break; + case "dropDown": + this.addToCommentsDropdown(pkg,cmdIdx); + break; + default: // [+-]ID + var firstChr = cmdObj.position.charAt(0); + var newId = cmdObj.position.substr(1); + this.createNewButton(newId, firstChr === "+", pkg, cmdIdx); + break; + } + } + } +}; + +BZPage.prototype.setConfigurationButton = function setConfigurationButton () { + var additionalButtons = this.doc.querySelector("#bugzilla-body *.related_actions"); + var configurationButtonUI = this.doc.createElement("li"); + configurationButtonUI.innerHTML = "\u00A0-\u00A0<a href='' id='configurationButton'>" + + "Triage configuration</a>"; + additionalButtons.appendChild(configurationButtonUI); + this.doc.getElementById("configurationButton").addEventListener( + "click", + function(evt) { + var prfNm = BTSPrefNS+"JSONURL"; + var url = preferences.get(prfNm,""); + + var reply = prompts.prompt("New location of JSON configuration file", url); + if (reply) { + preferences.set(prfNm, reply.trim()); + prompts.alert("For now, you should really restart Firefox!"); + } + + evt.stopPropagation(); + evt.preventDefault(); + }, false); +}; + +/* + * From <a> element diggs out just plain email address + * Note that bugzilla.gnome.org doesn't have mailto: url but + * https://bugzilla.gnome.org/page.cgi?id=describeuser.html&login=mcepl%40redhat.com + * + * @param aElement Element with href attribute or something else + * @return String with the address or null + * + */ +BZPage.prototype.parseMailto = function parseMailto(aElement) { + var emailStr = "", hrefStr = ""; + // use url utils + if (aElement) { + hrefStr = decodeURIComponent(aElement.getAttribute("href")); + emailStr = hrefStr.split(":")[1]; + // + if (emailStr === undefined) { + var params = util.getParamsFromURL("https://" + this.hostname + "/" + hrefStr); + emailStr = decodeURI(params.login); + } + return emailStr; + } + return null; +}; + +/** + * Get the current email of the reporter of the bug. + * + * @return string + */ +BZPage.prototype.getReporter = function getReporter () { + var reporterElement = this.getOptionTableCell("bz_show_bug_column_2", "Reported"); + // RH Bugzilla after upgrade to 3.6.2 moved the information to other column + if (!reporterElement) { + reporterElement = this.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 this.parseMailto(reporterElement); +}; + +BZPage.prototype.getComponent = function getComponent() { + var elem = this.doc.getElementById("component"); + if (elem) { + return elem.value; + } + return null; +}; + + +BZPage.prototype.commentsWalker = function commentsWalker (fce) { + var comments = this.doc.getElementById("comments").getElementsByClassName( + "bz_comment"); + Array.forEach(comments, function(item) { + fce(item); + }, this); +}; + +/** + * Set background color of all comments made by reporter in ReporterColor color + * + */ +BZPage.prototype.checkComments = function checkComments () { + var that = this; + var reporterRE = new RegExp(this.getReporter()); + this.commentsWalker(function(x) { + var email = that.parseMailto(x.getElementsByClassName("vcard")[0] + .getElementsByTagName("a")[0]); + if (reporterRE.test(email)) { + x.style.backgroundColor = that.ReporterColor.toString(); + } + }); +}; + +BZPage.prototype.collectComments = function collectComments () { + var outStr = ""; + this.commentsWalker(function(x) { + outStr += x.getElementsByTagName("pre")[0].textContent + "\n"; + }); + return outStr.trim(); +}; + + +/** + * Select option with given value on the <SELECT> element with given id. + * + * Also execute change HTMLEvent, so that the form behaves accordingly. + * + * @param id + * @param label + * @return none + * + */ +BZPage.prototype.selectOption = function selectOption (id, label, fireEvent) { + if (!fireEvent) { + fireEvent = true; + } + var sel = this.doc.getElementById(id); + sel.value = label; + if (fireEvent) { + var intEvent = this.doc.createEvent("HTMLEvents"); + intEvent.initEvent("change", true, true); + sel.dispatchEvent(intEvent); + } +}; + +BZPage.prototype.selectOptionByLabel = function selectOptionByLabel(id, label, fireEvent) { + if (!fireEvent) { + fireEvent = true; + } + var sel = this.doc.getElementById(id); + var labelRE = new RegExp(label.trim()); + var ourOption = Array.filter(sel.options, function (op) { + return op.textContent.trim() === label; + }, this); + + if (ourOption[0]) { + sel.value = ourOption[0].value; + } + + if (fireEvent) { + var intEvent = this.doc.createEvent("HTMLEvents"); + intEvent.initEvent("change", true, true); + sel.dispatchEvent(intEvent); + } +}; + +/** + * Send mouse click to the specified element + * + * @param String ID of the element to send mouseclick to + * @return None + */ +BZPage.prototype.clickMouse = function clickMouse (targetID) { + var localEvent = this.doc.createEvent("MouseEvents"); + localEvent.initMouseEvent("click", true, true, this.doc.defaultView, 0, 0, + 0, 0, 0, false, false, false, false, 0, null); + this.doc.getElementById(targetID).dispatchEvent(localEvent); +}; + +/** + * Add object to the text box (comment box or status whiteboard) + * + * @param id String with the id of the element + * @param stuff String/Array to be added to the comment box + * + * @return none + */ +BZPage.prototype.addStuffToTextBox = function addStuffToTextBox (id, stuff) { + var textBox = this.doc.getElementById(id); + if (textBox.tagName.toLowerCase() === "textarea") { + stuff = textBox.value ? "\n\n" + stuff : stuff; + textBox.value += stuff; + } else { + textBox.value = util.addCSVValue(textBox.value,stuff); + } +}; + +/** + * Remove a keyword from the element if it is there + * + * @param id String with the id of the element + * @param stuff String/Array with keyword(s) to be removed + */ +BZPage.prototype.removeStuffFromTextBox = function removeStuffFromTextBox (id, stuff) { + var changedElement = this.getElementById(id); + changedElement.value = util.removeCSVValue(changedElement.value,stuff); +}; + +/** + * generalized hasKeyword ... search in the value of the box with given id + * + * @param id String with ID of the element we want to check + * @param str String to be searched for + * @return Boolean found? + */ +BZPage.prototype.idContainsWord = function idContainsWord (id, str) { + var kwd = ""; + try { + kwd = this.doc.getElementById(id).value; + } catch (e) { + // For those who don't have particular element at all or if it is empty + return false; + } + return (util.isInList(str, kwd.trim().split(/[,\s]+/))); +}; + +/** + * Check for the presence of a keyword + * + * @param str String with the keyword + * @return Boolean + */ +BZPage.prototype.hasKeyword = function hasKeyword (str) { + return (this.idContainsWord('keywords', str)); +}; + +/** + +@return Element with the href attribute containng the information + */ +BZPage.prototype.getOptionTableCell = function getOptionTableCell(tableId, label) { + var cleanLabelRE = /^\s*([^.:]*):?\s*$/; + label = label.trim().replace(cleanLabelRE,"$1").toLowerCase(); + + var rows = this.doc.getElementById(tableId).getElementsByTagName("tr"); + var ourLine = Array.filter(rows, function(row) { + var curLabel = row.getElementsByTagName("td")[0].textContent.toLowerCase(); + curLabel = curLabel.replace(cleanLabelRE,"$1"); + return (curLabel === label); // maybe this could be a RE match instead + }); + + if (ourLine.length > 0) { + return ourLine[0].getElementsByTagName("td")[1]. + getElementsByTagName("a")[0]; + } + return null; +}; + +/** + * Set the bug to NEEDINFO state + * + * Working function. + * @return none + * @todo TODO we may extend this to general setNeedinfo function + * with parameter [reporter|assignee|general-email-address] + */ +BZPage.prototype.setNeedinfoReporter = function setNeedinfoReporter () { + this.clickMouse("needinfo"); + this.selectOption("needinfo_role", "reporter"); +}; + +/** + * + */ +BZPage.prototype.getOwner = function getOwner () { + // TODO(maemo) doesn't work on maemo + var assigneeAElement = this.getOptionTableCell("bz_show_bug_column_1","Assigned To"); + return this.parseMailto(assigneeAElement); +}; + +/** + * Return maintainer which is per default by bugzilla + * (which is not necessarily the one who is default maintainer per component) + * + * @return String with the maintainer's email address + */ +BZPage.prototype.getDefaultBugzillaMaintainer = function getDefaultBugzillaMaintainer (component) { + var address = util.filterByRegexp(this.defBugzillaMaintainerArr, component); + return address; +}; + +/** + * Parse the row with the attachment + * + * @param DOM element to be parsed + * @return array with string name of the attachment, integer its id number, + * string of MIME type, integer of size in kilobytes, and the whole + * element itself + */ +BZPage.prototype.parseAttachmentLine = function(inElem) { + var MIMEtype = ""; + var size = 0; + + // Skip over obsolete attachments + if (inElem.getElementsByClassName("bz_obsolete").length > 0) { + return ( []); + } + + // getting name of the attachment + // TODO probably could use url.URL object + var attName = inElem.getElementsByTagName("b")[0].textContent.trim(); + + var aHrefsArr = inElem.getElementsByTagName("a"); + var aHref = Array.filter(aHrefsArr, function(x) { + return x.textContent.trim() === "Details"; + })[0]; + var id = parseInt(aHref.getAttribute("href").replace( + /^.*attachment.cgi\?id=/, ""), 10); + + // getting MIME type and size + var stringArray = inElem.getElementsByClassName("bz_attach_extra_info")[0].textContent + .replace(/[\n ()]+/g, " ").trim().split(", "); + size = parseInt(stringArray[0], 10); + MIMEtype = stringArray[1].split(" ")[0]; + + return [ attName, id, MIMEtype, size, inElem ]; +}; + +/** + * collect the list of attachments in a structured format + * + * @return Array of arrays, one for each attachments; + * each record has string name of the attachment, integer its id number, + * string of MIME type, integer of size in kilobytes, and the whole + * element itself + */ +BZPage.prototype.getAttachments = function getAttachments () { + var outAtts = []; + var atts = this.doc.getElementById("attachment_table") + .getElementsByTagName("tr"); + for ( var i = 1, ii = atts.length - 1; i < ii; i++) { + outAtts.push(this.parseAttachmentLine(atts[i])); + } + return outAtts; +}; + +/** + * Get login of the currently logged-in user. + * + * @return String with the login name of the currently logged-in user + */ +BZPage.prototype.getLogin = function getLogin () { + var lastLIElement = this.doc.querySelector("#header ul.links li:last-of-type"); + var loginArr = lastLIElement.textContent.split("\n"); + var loginStr = loginArr[loginArr.length - 1].trim(); + return loginStr; +}; + +/** + * returns password from the current storage, or if there isn't + * one, then it will ask user for it. + * + * @return String with the password + */ +BZPage.prototype.getPassword = function getPassword (login) { + var passPrompt = "Enter your Bugzilla password for fixing MIME attachment types"; + var switchPrompt = "Do you want to switch off features requiring password completely?"; + var prefName = BTSPrefNS+"withoutPassowrd"; + var domain = this.win.location.protocol + "//" + this.hostname; + + var pass = passUtils.getPassword(login, domain, BTSPassRealm); + // pass === null means no appropriate password in the storage + if (!preferences.get(prefName,false) && (pass === null)) { + var passwordText = prompts.promptPassword(passPrompt); + if (passwordText && passwordText.length > 0) { + passUtils.setLogin(login, passwordText, domain, + BTSPassRealm); + return passwordText; + } else { + var switchOff = prompts.promptYesNoCancel(switchPrompt); + if (switchOff) { + preferences.set(prefName,true); + } + return null; + } + } else { + return pass; + } +}; + +/** + * + */ +BZPage.prototype.setUpLogging = function setUpLogging () { + // Protection against double-call + if (this.doc.getElementById("generateTSButton")) { + return ; + } + + // For adding additional buttons to the top toolbar + var additionalButtons = this.doc.querySelector("#bugzilla-body *.related_actions"); + var that = this; + + // logging all submits for timesheet + if (!this.submitHandlerInstalled) { + this.doc.forms.namedItem("changeform").addEventListener("submit",function (evt) { + var resp = that.log.addLogRecord(that); + if (resp === null) { + evt.stopPropagation(); + evt.preventDefault(); + } + }, false); + this.submitHandlerInstalled = true; + } + + // (id, text, parent, callback, params, before, covered, accesskey) + this.createDeadLink("generateTSButton", "Generate TS", additionalButtons, + function(evt) { + that.log.createBlankPage.call(that.log, "TimeSheet", + that.log.generateTimeSheet); + }, [], "dash", "li"); + + this.createDeadLink("clearLogs", "Clear TS", additionalButtons, + function(evt) { + that.log.clearStore(this); + }, [], "dash", "li"); + + this.createDeadLink("importTSButton", "Import TS", additionalButtons, + function(evt) { + jsonPaths = prompts.promptFileOpenPicker(that.win); + that.log.importOtherStore(jsonPaths, clearLogAElem); + }, [], "dash", "li"); + + var clearLogAElem = this.doc.getElementById("clearLogs"); + if (this.log.isEmpty()) { + clearLogAElem.style.color = this.log.EmptyLogsColor; + clearLogAElem.style.fontWeight = "normal"; + } else { + clearLogAElem.style.color = this.log.FullLogsColor; + clearLogAElem.style.fontWeight = "bolder"; + } +}; + +BZPage.prototype.getSelectionOrClipboard = function getSelectionOrClipboard () { + var text = selection.text; + if (!text) { + text = clip.get(); + } + return text; +}; + +/** + * Opens a new tab with a query for the given text in the selected component + * + * @param text to be searched for + * @param component String with the component name (maybe latter regexp?) + * @param product (optional) string with the product name, if undefined, + * search in all products + * @return None + * + */ +BZPage.prototype.queryInNewTab = function(text, component, product) { + var urlStr = "https://bugzilla.redhat.com/buglist.cgi?query_format=advanced"; + if (product) { + urlStr += "&product=" + product.trim(); + } + if (component) { + if ("equivalentComponents" in this.constantData) { + var equivCompsArr = this.constantData["equivalentComponents"]. + filter(function (REstr) { + return new RegExp(REstr).test(this.getComponent()); + }, this); + if (equivCompsArr.length > 0) { + component = equivCompsArr[0]; + } + urlStr += "&field0-0-0=component&type0-0-0=regexp&value0-0-0=" + + component.trim(); + } else { + urlStr += "&field0-0-0=component&type0-0-0=substring&value0-0-0=" + + component.trim(); + } + } + + // using more complicated query tables here, because they can be more easily + // edited + // for further investigative searches + if (text) { + text = encodeURIComponent(text.trim()); + var searchText = "&field1-0-0=longdesc&type1-0-0=substring&value1-0-0=" + + text + + "&field1-0-1=attach_data.thedata&type1-0-1=substring&value1-0-1=" + + text + + "&field1-0-2=status_whiteboard&type1-0-2=substring&value1-0-2=" + + text; + urlStr += searchText; + tabs.open({ + url: urlStr, + inBackground: true, + onOpen: function (t) { + tabs.activeTab = t; + } + }); + } +}; + +/** + * Get the text to search for and prepare other things for the real executive + * function this.queryInNewTab, and run it. + */ +BZPage.prototype.queryForSelection = function() { + var text = this.getSelectionOrClipboard(); + if (text) { + this.queryInNewTab(text, this.getComponent()); + } +}; + +/** + * adds a person to the CC list, if it isn't already there + * + * @param who String with email address or "self" if the current user + * of the bugzilla should be added + */ +BZPage.prototype.addToCCList = function addToCCList (who) { + if (!who) { + return ; + } + if (who === "self") { + this.doc.getElementById("addselfcc").checked = true; + } else { + this.clickMouse("cc_edit_area_showhide"); + if (!util.isInList(who, this.CCList)) { + this.addStuffToTextBox("newcc",who); + } + } +}; + +/** + * a collect a list of emails on CC list + * + * @return Array with email addresses as Strings. + */ +BZPage.prototype.getCCList = function getCCList () { + var CCListSelect = this.doc.getElementById("cc"); + var outCCList = []; + if (CCListSelect) { + outCCList = Array.map(CCListSelect.options, function(item) { + return item.value; + }); + } + return outCCList; +}; + +// exports.BZPage = apiUtils.publicConstructor(BZPage); +exports.BZPage = BZPage; |