/*jslint 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 */ /*global jetpack */ // Released under the MIT/X11 license // http://www.opensource.org/licenses/mit-license.php "use strict"; jetpack.future.import("pageMods"); jetpack.future.import("storage.simple"); jetpack.future.import("selection"); jetpack.future.import("clipboard"); var TriagedDistro = 13; var NumberOfFrames = 7; var XMLRPCurl = "https://bugzilla.redhat.com/xmlrpc.cgi"; var bugURL = "https://bugzilla.redhat.com/show_bug.cgi?id="; var myStorage = jetpack.storage.simple; var badMIMEArray = [ "application/octet-stream", "text/x-log", "undefined" ]; // ============================================================== // TODO https://wiki.mozilla.org/Labs/Jetpack/JEP/24 var manifest = { settings : [ { name : "BZpassword", type : "password", label : "Bugzilla password" }, { name : "JSONURL", type : "text", label : "Configuration file URL", "default" : "http://mcepl.fedorapeople.org/scripts/BugZappers_data.json" } ] }; jetpack.future.import("storage.settings"); // if (!jetpack.storage.settings.BZpassword) { // jetpack.settings.open(); // } var jsonDataURL = myStorage.JSONURL ? myStorage.JSONURL : "http://mcepl.fedorapeople.org/scripts/BugZappers_data.json"; var PCIIDsURL = "http://mcepl.fedorapeople.org/scripts/drm_pciids.json"; var abrtQueryURL = "https://bugzilla.redhat.com/buglist.cgi?" + "cmdtype=dorem&remaction=run&namedcmd=all%20NEW%20abrt%20crashes&sharer_id=74116"; var reqCounter = 0; var msgStrs = {}; var CommentRe = new RegExp("^\\s*#"); var BlankLineRe = new RegExp("^\\s*$"); // nová řádka // [ 65.631] (--) intel(0): Chipset: "845G" var ChipsetRE = new RegExp("^\\s*\\[?[ 0-9.]*\\]?\\s*\\(--\\) ([A-Za-z]+)\\([0-9]?\\): Chipset: (.*)$"); var ATIgetIDRE = new RegExp("^.*\\(ChipID = 0x([0-9a-fA-F]+)\\).*$"); var AbrtRE = new RegExp("^\\s*\\[abrt\\]"); var signalHandlerRE = new RegExp("^\\s*#[0-9]*\\s*"); var frameNoRE = new RegExp("^\\s*#([0-9]*)\\s"); // Initialize data from remote URL var XMLHttpRequestDone = false; var hashBugzillaName = []; var hashBugzillaWholeURL = []; var defAssigneeList = []; var suspiciousComponents = []; var signatureFedoraString = ""; // TODO we should have an array SpecialFlags instead of multiple Boolean // variables var parseAbrtBacktraces = false; var queryButtonAvailable = false; var upstreamButtonAvailable = false; var logSubmits = false; var chipIDsGroupings = []; var AddrArray = []; var PCI_ID_Array = []; var topRow = {}; var bottomRow = {}; // ======== load external library =============================== // var XMLRPCMessage = {}; // var req = new XMLHttpRequest(); // req.open("GET","http://mcepl.fedorapeople.org/scripts/xmlrpc.js",true); // req.onreadystatechange = function (aEvt) { // if (req.readyState == 4) { // if (req.status == 200) { // var thisDoc = jetpack.tabs.focused.contentDocument; // var script = thisDoc.createElement("script"); // script.setAttribute("type","text/javascript"); // script.innerHTML = req.responseText; // thisDoc.getElementsByTagName("head")[0].appendChild(script); // XMLRPCMessage = // jetpack.tabs.focused.contentWindow.wrappedJSObject.XMLRPCMessage; // console.log("XMLHTTPRequest should be loaded."); // console.log(XMLRPCMessage); // } // } // }; // console.log("Now we are calling XMLHTTPRequest to load xmlrpc.js"); // req.send(""); /* * * xmlrpc.js beta version 1 Tool for creating XML-RPC formatted requests in * JavaScript * * Copyright 2001 Scott Andrew LePera scott@scottandrew.com * http://www.scottandrew.com/xml-rpc * * License: You are granted the right to use and/or redistribute this code only * if this license and the copyright notice are included and you accept that no * warranty of any kind is made or implied by the author. * */ function XMLRPCMessage(methodname) { this.method = methodname || "system.listMethods"; this.params = []; return this; } XMLRPCMessage.prototype.setMethod = function(methodName) { if (!methodName) return; this.method = methodName; }; XMLRPCMessage.prototype.addParameter = function(data) { if (arguments.length == 0) return; this.params[this.params.length] = data; }; XMLRPCMessage.prototype.xml = function() { var method = this.method; // assemble the XML message header var xml = ""; xml += "\n"; xml += "\n"; xml += "" + method + "\n"; xml += "\n"; // do individual parameters for ( var i = 0; i < this.params.length; i++) { var data = this.params[i]; xml += "\n"; xml += "" + this.getParamXML(this.dataTypeOf(data), data) + "\n"; xml += "\n"; } xml += "\n"; xml += ""; return xml; // for now }; XMLRPCMessage.prototype.dataTypeOf = function(o) { // identifies the data type var type = typeof (o); type = type.toLowerCase(); switch (type) { case "number": if (Math.round(o) == o) type = "i4"; else type = "double"; break; case "object": var con = o.constructor; if (con == Date) type = "date"; else if (con == Array) type = "array"; else type = "struct"; break; } return type; }; XMLRPCMessage.prototype.doValueXML = function(type, data) { var xml = "<" + type + ">" + data + ""; return xml; }; XMLRPCMessage.prototype.doBooleanXML = function(data) { var value = (data == true) ? 1 : 0; var xml = "" + value + ""; return xml; }; XMLRPCMessage.prototype.doDateXML = function(data) { var leadingZero = function (n) { // pads a single number with a leading zero. Heh. if (n.length == 1) n = "0" + n; return n; }; var dateToISO8601 = function(date) { // wow I hate working with the Date object var year = new String(date.getYear()); var month = this.leadingZero(new String(date.getMonth())); var day = this.leadingZero(new String(date.getDate())); var time = this.leadingZero(new String(date.getHours())) + ":" + this.leadingZero(new String(date.getMinutes())) + ":" + this.leadingZero(new String(date.getSeconds())); var converted = year + month + day + "T" + time; return converted; }; var xml = ""; xml += dateToISO8601(data); xml += ""; return xml; }; XMLRPCMessage.prototype.doArrayXML = function(data) { var xml = "\n"; for ( var i = 0; i < data.length; i++) { xml += "" + this.getParamXML(this.dataTypeOf(data[i]), data[i]) + "\n"; } xml += "\n"; return xml; }; XMLRPCMessage.prototype.doStructXML = function(data) { var xml = "\n"; for ( var i in data) { xml += "\n"; xml += "" + i + "\n"; xml += "" + this.getParamXML(this.dataTypeOf(data[i]), data[i]) + "\n"; xml += "\n"; } xml += "\n"; return xml; }; XMLRPCMessage.prototype.getParamXML = function(type, data) { var xml; switch (type) { case "date": xml = this.doDateXML(data); break; case "array": xml = this.doArrayXML(data); break; case "struct": xml = this.doStructXML(data); break; case "boolean": xml = this.doBooleanXML(data); break; default: xml = this.doValueXML(type, data); break; } return xml; }; // ============================================================== var hlpr = function () { }; hlpr.valToArray = function valToArray(val) { let arr = []; if (typeof val == "string") { arr = [val]; } else if (val instanceof Array) { arr = val; } return arr; }; /** * Merges two comma separated string as a list and returns new string * * @param str String with one values * @param value String with other values * @return String with merged lists */ hlpr.addCSVValue = function addCSVValue(str, value) { let parts = (str.trim().length > 0 ? str.split(",") : []); // FIXME isn't this wrong? In if conditions it treats value as a // string, but then uses hlpr.valToArray on it ??? if (parts.indexOf(value) < 0) { parts.concat(hlpr.valToArray(value)); } return parts.join(","); }; /** * Treats comma separated string as a list and removes one item from it * * @param str String treated as a list * @param value String with the value to be removed from str * @return String with the resulting list comma separated */ hlpr.removeCSVValue = function removeCSVValue(str, value) { str = str.trim(); // we want it trimmed without any questions anyway let parts = str ? str.split(",") : []; parts = parts.filter(function(e,i,a) {return (e != value)}); return parts.join(","); }; /** * Check whether an item is member of the list. Idea is just to make long if * commands slightly more readable. * * @param mbr string to be searched in the list * @param list list * @return position of the string in the list, or -1 if none found. */ hlpr.isInList = function(mbr, list) { return (list.indexOf(mbr) !== -1); }; hlpr.createBlankPage = function (ttl, bodyBuildCB) { var title = ttl || "Yet another untitled page"; var that = this; var logTab = jetpack.tabs.open("about:blank"); jetpack.tabs.onReady(function() { var otherDoc = logTab.contentDocument; otherDoc.title = title; otherDoc.body.innerHTML = "

" + title + "

"; bodyBuildCB.call(that, otherDoc.body); logTab.focus(); }); }; //Get JSON configuration data hlpr.loadText = function(URL, cb_function, what) { if (what === undefined) { // missing optional argument what = this; } var req = new XMLHttpRequest(); req.open("GET", URL, true); req.onreadystatechange = function(aEvt) { if (req.readyState == 4) { if (req.status == 200) { cb_function.call(what, req.responseText); } else { throw "Getting " + URL + "failed!"; } } }; req.send(""); }; /** * Converts attributes value of the given list of elements to the Javascript * list. * * @param list * array of elements * @return array of values * @depreceated FIXME never used */ hlpr.valuesToList = function(list) { var outL = []; list.forEach(function(e, i, a) { if (e.hasAttribute("value")) { outL.push(e.getAttribute("value").trim()); } }); return outL; }; // Initialization hlpr.loadJSON = function(URL, cb_function, what) { if (what === undefined) { // missing optional argument what = this; } hlpr.loadText(URL, function(text) { var data = JSON.parse(text); cb_function.call(what, data); }, what); }; hlpr.loadJSON(jsonDataURL, function(response) { msgStrs = response.strings; signatureFedoraString = response.signature; suspiciousComponents = response.suspiciousComponents; hashBugzillaName = response.bugzillalabelNames; hashBugzillaWholeURL = response.bugzillaIDURLs; // [{'regexp to match component':'email address of an universal // maintainer'}, ...] AddrArray = response.CCmaintainer; defAssigneeList = response.defaultAssignee; queryButtonAvailable = response.queryButton; upstreamButtonAvailable = response.upstreamButton; parseAbrtBacktraces = response.parseAbrtBacktraces; logSubmits = response.submitsLogging; newUpstreamBugsURLArray = response.newUpstreamBug; queryUpstreamBugsURLArray = response.queryUpstreamBug; chipIDsGroupings = response.chipIDsGroupings; topRow = response.topRow; bottomRow = response.bottomRow; }); // Get card translation table hlpr.loadJSON(PCIIDsURL, function(response) { PCI_ID_Array = response; }); // ============================================================================ // Color management methods // originally from // http://www.mjijackson.com/2008/02\ // /rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript function Color(r, g, b) { this.Luminosity = 0.85; this.Desaturated = 0.4; if (r instanceof Array) { this.r = r[0]; this.g = r[1]; this.b = r[2]; } else { this.r = r; this.g = g; this.b = b; } } Color.prototype.update = function(r, g, b) { this.r = r; this.g = g; this.b = b; }; Color.prototype.hs = function(nStr) { if (Number(nStr) === 0) { return "00"; } else if (nStr.length < 2) { return "0" + nStr; } else { return nStr; } }; Color.prototype.toString = function() { var rH = Number(this.r.toFixed()).toString(16); var gH = Number(this.g.toFixed()).toString(16); var bH = Number(this.b.toFixed()).toString(16); return "#" + this.hs(rH) + this.hs(gH) + this.hs(bH); }; /** * Converts an RGB color value to HSL. Conversion formula adapted from * http://en.wikipedia.org/wiki/HSL_color_space. Assumes r, g, and b are * contained in the set [0, 255] and returns h, s, and l in the set [0, 1].4343 * * @param Number r The red color value * @param Number g The green color value * @param Number b The blue color value * @return Array The HSL representation */ Color.prototype.hsl = function() { var r = this.r / 255; var g = this.g / 255; var b = this.b / 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [ h, s, l ]; }; /** * Converts an HSL color value to RGB. Conversion formula adapted from * http://en.wikipedia.org/wiki/HSL_color_space. Assumes h, s, and l are * contained in the set [0, 1] and returns r, g, and b in the set [0, 255]. * * @param Number h The hue * @param Number s The saturation * @param Number l The lightness * @return Array The RGB representation */ Color.prototype.hslToRgb = function(h, s, l) { function hue2rgb(p, q, t) { if (t < 0) { t += 1; } if (t > 1) { t -= 1; } if (t < 1 / 6) { return p + (q - p) * 6 * t; } if (t < 1 / 2) { return q; } if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } return p; } var r, g, b; if (s === 0) { r = g = b = l; // achromatic } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return [ r * 255, g * 255, b * 255 ]; }; /** * Converts an RGB color value to HSV. Conversion formula adapted from * http://en.wikipedia.org/wiki/HSV_color_space. Assumes r, g, and b are * contained in the set [0, 255] and returns h, s, and v in the set [0, 1]. * * @param Number r The red color value * @param Number g The green color value * @param Number b The blue color value * @return Array The HSV representation */ Color.prototype.hsv = function() { var r = this.r / 255; var g = this.g / 255; var b = this.b / 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, v = max; var d = max - min; s = max === 0 ? 0 : d / max; if (max === min) { h = 0; // achromatic } else { switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [ h, s, v ]; }; /** * Converts an HSV color value to RGB. Conversion formula adapted from * http://en.wikipedia.org/wiki/HSV_color_space. Assumes h, s, and v are * contained in the set [0, 1] and returns r, g, and b in the set [0, 255]. * * @param Number h The hue * @param Number s The saturation * @param Number v The value * @return Array The RGB representation */ Color.prototype.hsvToRgb = function(h, s, v) { var r, g, b; var i = Math.floor(h * 6); var f = h * 6 - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; case 5: r = v; g = p; b = q; break; } return [ r * 255, g * 255, b * 255 ]; }; /** * Provide */ Color.prototype.lightColor = function() { var hslArray = this.hsl(); var h = Number(hslArray[0]); var s = Number(hslArray[1]) * this.Desaturated; var l = this.Luminosity; var desA = this.hslToRgb(h, s, l); return new Color(desA[0], desA[1], desA[2]); }; // ==================================================================================== // BZPage's methods function BZPage(doc) { // this.initCommentsDropdown(); } /** * */ BZPage.prototype.getInstalledPackages = function() { let installedPackages = {}; if (gJSONData && ("commentPackages" in gJSONData)) { let enabledPackages = jetpack.storage.settings.enabledPacks.split(/[, ]/); for each (let pkg in enabledPackages) { if (pkg in gJSONData.commentPackages) { installedPackages[pkg] = gJSONData.commentPackages[pkg]; } } } return installedPackages; }; /** * */ BZPage.prototype.initCommentsDropdown = function() { let that = this; this.doc.getElementById("comments").innerHTML += "
" + " " + " element with given id. * * Also execute change HTMLEvent, so that the form behaves accordingly. * * @param id * @param label * @return none * * FIXME bugzilla-comments version has this signature: * selectOption = function selectOption(select, value) { let doc = select[0].ownerDocument; select.val(value); */ BZPage.prototype.selectOption = function(id, label) { var sel = this.doc.getElementById(id); var options = Array.filter(sel.getElementsByTagName("option"), function(x) { return x.textContent.trim() == label; }); theOption = options.length ? options[0] : []; if (theOption) { theOption.selected = true; var intEvent = this.doc.createEvent("HTMLEvents"); intEvent.initEvent("change", true, true); theOption.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(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); }; /** * format date to be in ISO format (just day part) * * @param date * @return string with the formatted date */ BZPage.prototype.getISODate = function (dateStr) { function pad(n) { return n < 10 ? '0' + n : n; } var date = new Date(dateStr); return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()); }; /** * select element of the array where regexp in the first element matches second * parameter of this function * * @param list Array with regexps and return values * @param chosingMark String by which the element of array is to be matched * @return String chosen element */ BZPage.prototype.filterByRegexp = function(list, chosingMark) { var chosenPair = []; if (list.length > 0) { chosenPair = list.filter(function(pair) { return new RegExp(pair.regexp, "i").test(chosingMark); }); } if (chosenPair.length > 0) { return chosenPair[0].addr.trim(); } else { return ""; } }; /** * Add text to the text box (comment box or status whiteboard) * * @param id String with the id of the element * @param string2BAdded String to be added to the comment box * * @return none */ BZPage.prototype.addTextToTextBox = function(id, string2BAdded) { var textBox = this.doc.getElementById(id); var separator = ", "; if (textBox.tagName.toLowerCase() === "textarea") { separator = "\n\n"; } else { // don't add string if it is already there if (textBox.value.indexOf(string2BAdded) != -1) { return; } } // don't remove the current content of the comment box, // just behave accordingly if (textBox.value.length > 0) { textBox.value = textBox.value.trim() + separator; } textBox.value = textBox.value + string2BAdded; }; /** * Add new keyword among the keywords. * * @param str String with the new keyword * @return none * * Checks for the existing keywords. */ BZPage.prototype.addKeyword = function(str) { this.addTextToTextBox("keywords", str); }; /** * 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(id, str) { try { var 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; } console.log("id = " + id + ", kwd = " + kwd.trim()); return (kwd.trim().indexOf(str) != -1); }; /** * Check for the presence of a keyword * * @param str String with the keyword * @return Boolean */ BZPage.prototype.hasKeyword = function(str) { return (this.idContainsWord('keywords', str)); }; /** * Set additional keyword if it isn't there * * @param str String with the keyword * @return none */ BZPage.prototype.setKeyword = function(str) { this.addTextToTextBox('keywords', str); }; BZPage.prototype.getOptionValue = function(id) { // Some special bugs don't have version for example try { return this.doc.getElementById(id).value; } catch (e) { console.error("Failed to find element with id = " + id); return "#NA"; } }; /** * 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 originalLocation Object after which the new button will be added * @param newId String with the id of the new button; has to be unique in whole * page * @param newLabel String with the label which will be shown to user * @param commentString String with comment to be added to the comment box * @param nState String with the new state bug should switch to (see * generalPurposeCureForAllDisease function for details) * @param secPar String with second parameter for generalPurposeForAllDisease * @param doSubmit Boolean optional whether the button should submit whole * page (default true) * @return none */ BZPage.prototype.addNewButton = function(originalLocation, newId, newLabel, commentString, nState, secPar, doSubmit, after) { var that = this; var commStr = ""; if (doSubmit === undefined) { // missing optional argument doSubmit = false; } if (after === undefined) { // missing optional argument after = false; } if (msgStrs[commentString]) { commStr = msgStrs[commentString]; } var newButton = this.doc.createElement("input"); newButton.setAttribute("id", newId); if (doSubmit) { newButton.setAttribute("type", "submit"); } else { newButton.setAttribute("type", "button"); } newButton.value = newLabel; newButton.addEventListener("click", function(evt) { that.generalPurposeCureForAllDisease(commStr, nState, secPar); }, false); 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); } }; /** * Get login of the currently logged-in user. * * @return String with the login name of the currently logged-in user */ BZPage.prototype.getLogin = function () { var lastLIElement = this.doc.querySelector("#header ul.links li:last-of-type"); console.log("Testing element:\n"+ lastLIElement); console.log("Testing element.textContent:\n"+ lastLIElement.textContent); var loginArr = lastLIElement.textContent.split("\n"); console.log("loginArr = " + loginArr.toSource()); var loginStr = loginArr[loginArr.length - 1].trim(); console.log("loginStr = " + loginStr); 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 * @todo TODO myStorage is a global object ... should be probably a parameter * of BZPage instance. * - more importantly, we should use jetpack.storage.settings */ BZPage.prototype.getPassword = function() { if (myStorage.BZpassword) { return myStorage.BZpassword; } else { var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); var password = { value : "" }; // default the password to pass var check = { value : true }; // default the checkbox to true var result = prompts.promptPassword(null, "Title", "Enter password:", password, null, check); // result is true if OK was pressed, false if cancel was pressed. // password.value is // set if OK was pressed. The checkbox is not displayed. if (result) { this.password = password.value; myStorage.BZpassword = this.password; jetpack.storage.simple.sync(); return password.value; } } return null; } // ==================================================================================== // MozillaBugzilla object MozillaBugzilla = function () { }; MozillaBugzilla.prototype = new BZPage(); MozillaBugzilla.prototype.constructor = MozillaBugzilla; // ==================================================================================== // RHBugzillaPage object RHBugzillaPage = function(doc) { // For identification of graphics card const manuChipStrs = [ [ "ATI Radeon", "ATI", "1002" ], [ "ATI Mobility Radeon", "ATI", "1002" ], [ "Intel Corporation", "INTEL", "8086" ], [ "NVIDIA", "NV", "10de" ] ]; // http://en.wikipedia.org/wiki/HSL_color_space // when only the value of S is changed // stupido!!! the string is value in hex for each color this.RHColor = new Color(158, 41, 43); // RGB 158, 41, 43; HSL 359, 1, 39 this.FedoraColor = new Color(0, 40, 103); // RGB 0, 40, 103; HSL 359, 1, 39 this.RawhideColor = new Color(0, 119, 0); // or "green", or RGB 0, 119, 0, or // HSL // 120, 0, 23 this.RHITColor = new Color(102, 0, 102); // RGB 102, 0, 102; HSL 300, 0, 20 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 this.EmptyLogsColor = new Color(0, 255, 0); this.FullLogsColor = this.FedoraColor; // END OF CONSTANTS this.doc = doc; var that = this; this.originalButton = this.doc.getElementById("commit"); this.login = this.getLogin(); this.password = this.getPassword(); var bugNoTitle = this.doc.querySelector("#title > p").textContent.trim(); this.bugNo = new RegExp("[0-9]+").exec(bugNoTitle)[0]; this.reporter = this.getReporter(); this.product = this.getOptionValue("product"); this.component = this.getOptionValue("component"); this.version = this.getVersion(); this.title = this.doc.getElementById("short_desc_nonedit_display").textContent; var ITbutton = this.doc.getElementById("cf_issuetracker"); this.its = ITbutton ? ITbutton.value.trim() : ""; this.CCList = Array.map(this.doc.getElementById("cc"), function(item) { return item.value; }); // TODO be careful about this, seems breaking for non-RH BugZappers, // but I cannot see why this.owner = this.doc.getElementById("bz_assignee_edit_container") .getElementsByClassName("fn")[0].textContent; this.defaultAssignee = this.filterByRegexp(defAssigneeList, this.component) .toLowerCase(); this.maintCCAddr = this.filterByRegexp(AddrArray, this.component).toLowerCase(); this.XorgLogAttList = []; this.XorgLogAttListIndex = 0; this.attachments = []; this.reqCounter = 0; var atts = this.doc.getElementById("attachment_table") .getElementsByTagName("tr"); for ( var i = 1, ii = atts.length - 1; i < ii; i++) { this.attachments.push(this.parseAttachmentLine(atts[i])); } var badAttachments = this.attachments.filter(function(att, idx, arr) { return (hlpr.isInList(att[2], badMIMEArray)); }); if (badAttachments.length > 0) { var titleElement = this.doc .getElementsByClassName("bz_alias_short_desc_container")[0]; titleElement.style.backgroundColor = "olive"; titleElement.appendChild(this.createFixAllButton(badAttachments)); badAttachments.forEach(function(x, i, a) { this.addTextLink(x); }, this); } // Dig out backtrace this.btSnippet = ""; console.log("parseAbrtBacktraces = " + parseAbrtBacktraces); if (parseAbrtBacktraces && AbrtRE.test(this.title)) { var notedLabel = this.doc.querySelector("label[for='newcc']"); while (notedLabel.firstChild) { var node = notedLabel.removeChild(notedLabel.firstChild); notedLabel.parentNode.insertBefore(node, notedLabel); } notedLabel.parentNode.removeChild(notedLabel); var mainTitle = this.doc .getElementsByClassName("bz_alias_short_desc_container")[0]; var abrtButton = this.doc.createElement("a"); abrtButton.setAttribute("accesskey", "a"); abrtButton.setAttribute("href", abrtQueryURL); abrtButton.textContent = "Abrt bugs"; mainTitle.appendChild(abrtButton); if (this.idContainsWord("cf_devel_whiteboard", 'btparsed')) { this.addTextToTextBox('status_whiteboard', 'btparsed'); } if (!(this.isTriaged() || this.idContainsWord("status_whiteboard", 'btparsed'))) { var btAttachments = this.attachments .filter(function(att, idx, arr) { return (/backtrace/.test(att[0])); }); // TODO we need to go through all backtrace attachments, but // just the first one will do for now, we would need to do async // parsing btAttachments.forEach(function(x) { attURL = "https://bugzilla.redhat.com/attachment.cgi?id=" + x[1]; console.log("attURL = " + attURL); console.log("btSnippet = " + this.btSnippet); if (!this.btSnippet) { var btRaw = hlpr.loadText(attURL, function(ret) { this.btSnippet = this.parseBacktrace(ret); if (this.btSnippet) { this.addTextToTextBox("comment", this.btSnippet); this.addTextToTextBox("status_whiteboard", "btparsed"); } }, this); } }, this); } } // Take care of signature for Fedora bugzappers if (signatureFedoraString.length > 0) { this.doc.forms.namedItem("changeform").addEventListener("submit", function() { that.addTextToTextBox("comment", signatureFedoraString); }, false); } this.setBranding(); this.checkComments(); this.buildButtons(topRow, bottomRow); // UI for the customization JSON URL var additionalButtons = this.doc.getElementById("bugzilla-body") .getElementsByClassName("related_actions")[0]; var customJSONURLUI = this.doc.createElement("li"); customJSONURLUI.innerHTML = "\u00A0-\u00A0" + "BugZap config"; additionalButtons.appendChild(customJSONURLUI); this.doc.getElementById("customJSONbutton").addEventListener( "click", function() { var newURL = jetpack.tabs.focused.contentWindow .prompt("URL for your JSON customization file"); if (newURL) { myStorage.JSONURL = newURL; jetpack.storage.simple.sync(); jetpack.tabs.focused.contentWindow.location.reload(); } }, false); // set default assignee on change of the component this.doc.getElementById("component").addEventListener( "change", function() { that.component = that.getOptionValue("component"); that .changeOwner(that.filterByRegexp(defAssigneeList, that.component).toLowerCase()); }, false); // offline-capable submit this.doc.forms.namedItem("changeform").addEventListener('submit', function(evt) { that.submitCallback.call(that, evt); }, false); // logging all submits for timesheet console.log("logSubmits = " + logSubmits); if (logSubmits) { this.doc.forms.namedItem("changeform").addEventListener("submit", function(evt) { if (that.addLogRecord() === null) { // FIXME doesn't work ... still submitting' evt.stopPropagation(); evt.preventDefault(); } }, false); var generateTimeSheetUI = this.doc.createElement("li"); generateTimeSheetUI.innerHTML = "\u00A0-\u00A0" + "Generate timesheet"; additionalButtons.appendChild(generateTimeSheetUI); this.doc.getElementById("generateTSButton").addEventListener( "click", function(evt) { hlpr.createBlankPage.call(that, "TimeSheet", that.generateTimeSheet); evt.stopPropagation(); evt.preventDefault(); }, false); var clearLogsUI = this.doc.createElement("li"); clearLogsUI.innerHTML = "\u00A0-\u00A0" + "Clear logs"; additionalButtons.appendChild(clearLogsUI); var clearLogAElem = this.doc.getElementById("clearLogs"); clearLogAElem.addEventListener("click", function() { myStorage.logs = {}; jetpack.storage.simple.sync(); this.style.color = that.EmptyLogsColor; clearLogAElem.style.fontWeight = "normal"; console.log("mystorage.logs wiped out!"); }, false); if (!myStorage.logs) { console.log("No myStorage.logs defined!"); myStorage.logs = {}; } if (myStorage.logs) { clearLogAElem.style.color = this.FullLogsColor; clearLogAElem.style.fontWeight = "bolder"; } else { clearLogAElem.style.color = this.EmptyLogsColor; clearLogAElem.style.fontWeight = "normal"; } } } // END OF RHBugzillaPage CONSTRUCTOR RHBugzillaPage.prototype = new BZPage(); RHBugzillaPage.prototype.constructor = RHBugzillaPage; /* Offline supporting functions */ /** * * @todo FIXME this probably makes a closure and a memory leak name='changeform' * investigate * https://developer.mozilla.org/en/How_to_Turn_Off_Form_Autocompletion * *
* * Reading * http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13 * random notes: - 17.13.3 provides all steps necessary - enctype != * application/x-www-form-urlencoded => SHOULD fails (no further questions * needed) - http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1. is * nice explanation (albeit quite dated) - on multiple values * http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.6.1 - * příliš jednoduché * http://www.innovation.ch/java/HTTPClient/emulating_forms.html - */ RHBugzillaPage.prototype.serializeForm = function(form) { var serialForm = { dataOut : "", name : form.name, method : form.method, acceptCharset : form.acceptCharset, action : form.action, // TODO shouldn't we get a non-relative URL? enctype : form.enctype, cookie : this.doc.cookie, autocomplete : form.getAttribute("autocomplete"), bugNo : this.bugNo }; function genURIElement(sName, sValue) { return encodeURIComponent(sName) + "=" + encodeURIComponent(sValue); } /** * @param o * control to be serialized * @return String with the serialized control */ function serializeControl(element) { var val = element.value; // console.log("val.toSource() = " + val.toSource()); /* * on HTMLSelectElement we have an attribute 'type' of type DOMString, * readonly The type of this form control. This is the string * "select-multiple" when the multiple attribute is true and the string * "select-one" when false. */ if ((val == null) || (val == undefined) || (val == "")) { return; } else if (val instanceof Array) { return val.map(function(x) { return genURIElement(element.name, x.value); }).join("&"); } else if (val instanceof String) { return genURIElement(element.name, val); } else { // assume HTMLCollection return Array.map(val, function(x) { return genURIElement(element.name, x.value); }).join("&"); } } serialForm.dataOut = Array .filter( form.elements, function(el) { return !el.disabled && el.name && // FIXME shouldn't I just add && el.value here? (el.checked || /select|textarea/i.test(el.nodeName) || /text|hidden|password|search/i .test(el.type)); }).map(serializeControl).join("&"); return serialForm; }; RHBugzillaPage.prototype.submitCallback = function(evt) { console.log("Submit Callback!"); if (jetpack.__parent__.navigator.onLine) { var serForm = this .serializeForm(jetpack.tabs.focused.contentWindow.document.forms .namedItem("changeform")); console.log("serForm:\n" + serForm.toSource()); } else { var serForm = this .serializeForm(jetpack.tabs.focused.contentWindow.document.forms .namedItem("changeform")); myStorage.forms[this.bugNo] = serForm; evt.stopPropagation(); evt.preventDefault(); } }; /** * * * Yes, this is correct, this is NOT method of RHBugzillaPage! */ function onlineCallback() { function deserializeAndSend(formData) { // FIXME notImplemented // is it enough to just // run XMLHttpRequest? Probably yes, this is just a form // and this is just a HTTP request // it is probably better to get already processed // application/x-www-form-urlencoded // see http://htmlhelp.com/reference/html40/forms/form.html for details // and also https://developer.mozilla.org/en/AJAX/Getting_Started // what's? // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\ // /Global_Functions/encodeURI & co. // this seems to be also interesting // https://developer.mozilla.org/en/Code_snippets/Post_data_to_window console.error("Sending bugs not implemented yet!"); return ""; // FIXME check other HTTP headers to be set var bugID = formData.bugNo; var req = new XMLHttpRequest(); req.open("POST", formData.action, true); // FIXME co očekávám za odpověď? req.overrideMimeType("text/xml"); // * Accept-Encoding // * Accept-Language // * Accept (MIME types) req.setRequestHeader("Connection", "keep-alive"); req.setRequestHeader("Keep-Alive", 300); req.setRequestHeader("Content-Type", formData.enctype); req.setRequestHeader("Referer", bugURL + bugID); req.setRequestHeader("Accept-Charset", formData.acceptCharset); req.setRequestHeader("Cookie", formData.cookie); req.onreadystatechange = function(aEvt) { if (req.readyState == 4) { if (req.status == 200) { console.log("Sent form for bug " + bugID); delete myStorage.forms[bugID]; } else { console.error("Sending form for bug " + bugID + "failed!"); } } }; req.send(formData.data); } if (myStorage.forms.length > 0) { myStorage.forms.forEach(function(x) { deserializeAndSend(x); }); } } /* Bugzilla functions. */ /** * Get the current email of the reporter of the bug. * * @return string */ RHBugzillaPage.prototype.getReporter = function() { return this.doc .querySelector("#bz_show_bug_column_2 > table .vcard:first-of-type > a").textContent; }; /** * Get the current version of the Fedora release ... even if changed meanwhile * by bug triager. * * @return string (integer for released Fedora, float for RHEL, rawhide) */ RHBugzillaPage.prototype.getVersion = function() { var verStr = this.getOptionValue("version").toLowerCase(); var verNo = 0; if (/rawhide/.test(verStr)) { verNo = 999; } else { verNo = Number(verStr); } return verNo; }; RHBugzillaPage.prototype.commentsWalker = function(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 * */ RHBugzillaPage.prototype.checkComments = function() { var that = this; this.commentsWalker(function(x) { var email = x.getElementsByClassName("vcard")[0] .getElementsByTagName("a")[0].textContent; if (new RegExp(that.reporter).test(email)) { x.style.backgroundColor = that.ReporterColor.toString(); } }); }; RHBugzillaPage.prototype.collectComments = function() { var outStr = ""; this.commentsWalker(function(x) { outStr += x.getElementsByTagName("pre")[0].textContent + "\n"; }); return outStr.trim(); }; RHBugzillaPage.prototype.ProfessionalProducts = [ "Red Hat Enterprise Linux", "Red Hat Enterprise MRG" ]; /** * Is this bug a RHEL bug? * * @return Boolean true if it is a RHEL bug */ RHBugzillaPage.prototype.isEnterprise = function() { var prod = this.product; console.log("Testing whether the bug with product = " + prod + " is an enterprise bug."); var result = this.ProfessionalProducts.some(function(elem,idx,arr) { return new RegExp(elem).test(prod); }); console.log("result = " + result); return result; }; /** * Find out whether the bug is needed an attention of bugZappers * * @return Boolean whether the bug has been triaged or not */ RHBugzillaPage.prototype.isTriaged = function() { // First excceptions if (this.version > 7 && this.version < 12) { return this.doc.getElementById("bug_status").value.toUpperCase() !== "NEW"; } else { // and then the rule return this.hasKeyword("Triaged"); } }; /** * Set branding colours to easily distinguish between Fedora and RHEL bugs * * @param brand * string with product of the current bug * @param version * string with the version of the bug * @param its * string with the IsueTracker numbers * @return none * */ RHBugzillaPage.prototype.setBranding = function() { var brandColor = {}; var TriagedColor = {}; if (this.isEnterprise()) { console.log("This is an enterprise bug."); if (this.its && (this.its.length > 0)) { brandColor = this.RHITColor; } else { brandColor = this.RHColor; } } else if (new RegExp("Fedora").test(this.product)) { console.log("This is NOT an enterprise bug."); if (this.version == 999) { brandColor = this.RawhideColor; } else { brandColor = this.FedoraColor; } } // Comment each of the following lines to get only partial branding this.doc.getElementsByTagName("body")[0].style.background = brandColor .toString() + " none"; this.doc.getElementById("titles").style.background = brandColor.toString() + " none"; // Remove "Bug" from the title of the bug page, so we have more space with // plenty of tabs var titleElem = this.doc.getElementsByTagName("title")[0]; titleElem.textContent = titleElem.textContent.slice(4); var bodyTitleParent = this.doc.getElementById("summary_alias_container").parentNode; var bodyTitleElem = bodyTitleParent.getElementsByTagName("b")[0]; bodyTitleElem.textContent = bodyTitleElem.textContent.slice(4); // Make background-color of the body of bug salmon pink // for security bugs. if (this.hasKeyword("Security")) { this.doc.getElementById("bugzilla-body").style.background = SalmonPink .toString() + ' none'; } // Make it visible whether the bug has been triaged if (this.isTriaged()) { this.doc.getElementById("bz_field_status").style.background = brandColor .lightColor().toString() + " none"; } // we should make visible whether maintCCAddr is in CCList if (hlpr.isInList(this.maintCCAddr, this.CCList)) { var ccEditBoxElem = this.doc.getElementById("cc_edit_area_showhide"); // ccEditBoxElem.textContent = "*"+ccEditBoxElem.textContent; ccEditBoxElem.style.color = "navy"; ccEditBoxElem.style.fontWeight = "bolder"; ccEditBoxElem.style.textDecoration = "underline"; } // mark suspicious components var compElems; if (suspiciousComponents && hlpr.isInList(this.component, suspiciousComponents) && (compElems = this.doc .getElementById("bz_component_edit_container"))) { compElems.style.background = "red none"; } }; /** * Given line to be parsed, find out which chipset it is and fill in the * whiteboard * * @param iLine String with the whole unparsed "interesting line" * @param driverStr String with the driver name * @return None */ RHBugzillaPage.prototype.fillInWhiteBoard = function(iLine, driverStr) { var that = this; function groupIDs(manStr, cardStrID) { var outStr = that.filterByRegexp(chipIDsGroupings, manStr + "," + cardStrID); if (outStr.length === 0) { outStr = "UNGROUPED_" + manStr + "/" + cardStrID; } return outStr; } ; /** * Given PCI IDs for manufacturer and card ID return chipset string * * @param manufacturerNo String with manufacturer PCI ID * @param cardNo String with card PCI ID * @return Array with chip string and optinoal variants */ function checkChipStringFromID(manufacturerNo, cardNo) { var soughtID = (manufacturerNo + "," + cardNo).toUpperCase(); var outList = PCI_ID_Array[soughtID]; if (outList) { return outList; } else { return ""; } } ; var outStr = ""; var cardIDStr = ""; var cardIDArr = []; chipSwitchboard: if (driverStr === "RADEON") { var cardID = iLine.replace(ATIgetIDRE, "$1"); cardIDArr = checkChipStringFromID("1002", cardID); if (cardIDArr.length > 0) { cardIDStr = cardIDArr[0]; if (cardIDArr[1]) { optionStr = cardIDArr[1]; outStr = groupIDs(driverStr, cardIDStr) + "/" + optionStr; } else { outStr = groupIDs(driverStr, cardIDStr); optionStr = ""; } } else { outStr = "**** FULLSTRING: " + iLine; } } else { // Intel Corporation, NVIDIA cardIDArr = manuChipStrs.filter(function(el, ind, arr) { return new RegExp(el[0], "i").test(iLine); }); if (cardIDArr && (cardIDArr.length > 0)) { cardIDArr = cardIDArr[0]; } else { outStr = iLine; break chipSwitchboard; } // cardIDArr [0] = RE, [1] = ("RADEON","INTEL","NOUVEAU"), [2] = manu // PCIID iLine = iLine.replace(new RegExp(cardIDArr[0], "i")).trim(); // nVidia developers opted-out from grouping if (driverStr === "INTEL") { outStr = groupIDs(cardIDArr[1], iLine); } else { outStr = iLine; } } this.addTextToTextBox("status_whiteboard", ("card_" + outStr).trim()); this.doc.getElementById("chipmagic").style.display = "none"; }; /** * Get attached Xorg.0.log, parse it and find the value of chip. Does not fill * the whiteboard itself, just adds button to do so,paramList so that slow * XMLHttpRequest is done in advance. * * @return None */ RHBugzillaPage.prototype.fillInChipMagic = function () { var XorgLogURL = ""; var XorgLogAttID = ""; var XorgLogFound = false; var attURL = "", interestingLine = ""; var interestingArray = []; // Find out Xorg.0.log attachment URL this.XorgLogAttList = this.attachments.filter(function (value, index, array) { // Xorg.0.log must be text, otherwise we cannot parse it return (/[xX].*log/.test(value[0]) && /text/.test(value[2])); }); if (this.XorgLogAttList.length === 0) { return; } XorgLogAttID = this.XorgLogAttList[this.XorgLogAttListIndex][1]; attURL = "https://bugzilla.redhat.com/attachment.cgi?id="+XorgLogAttID; that = this; var req = new XMLHttpRequest(); req.open("GET",attURL,true); req.onreadystatechange = function (aEvt) { if (req.readyState == 4) { if (req.status == 200) { var ret = req.responseText; var interestingLineArr = ret.split("\n"). filter(function (v,i,a) { return ChipsetRE.test(v); }); console.log("interestingLineArr = " + interestingLineArr.toSource()); if (interestingLineArr.length >0) { interestingArray = ChipsetRE.exec(interestingLineArr[0]); interestingLine = interestingArray[2]. replace(/[\s"]+/g," ").trim(); var whiteboardInput = that.dok. getElementById("status_whiteboard"); that.addNewButton(whiteboardInput,"chipmagic","Fill In", "","CHIPMAGIC", interestingLine+"\t"+interestingArray[1].toUpperCase(), false,true); } } else { throw "Getting attachment " + attURL + "failed!"; } } }; req.send(""); this.XorgLogAttListIndex++; }; /** * 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 * @return None * */ RHBugzillaPage.prototype.queryInNewTab = function(text, component, product) { // Optional parameter if (product === undefined) { product = this.product; } var url = "https://bugzilla.redhat.com/buglist.cgi?query_format=advanced"; if (product) { url += "&product=" + product.trim(); } if (component) { url += "&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; url += searchText; jetpack.tabs.open(url); // Don't do it ... b.m.o is apparently not powerful enough to sustain // the weight // of the search if (false) { url = "https://bugzilla.mozilla.org/buglist.cgi?query_format=advanced" + "field0-0-0=product;type0-0-0=regexp;" + "value0-0-0=thunderbird|firefox|xulrunner" + searchText.replace("&", ";"); jetpack.tabs.open(url); } } }; /** * Get the text to search for and prepare other things for the real executive * function this.queryInNewTab, and run it. */ RHBugzillaPage.prototype.queryForSelection = function() { var text = jetpack.selection.text; if (!text) { text = jetpack.clipboard.get(); } if (text) { this.queryInNewTab(text, this.component); } }; /** * Search simple query in the upstream bugzilla appropriate for the component. */ RHBugzillaPage.prototype.queryUpstream = function() { var text = jetpack.selection.text; if (!text) { text = jetpack.clipboard.get(); } if (text) { var text = encodeURIComponent(text.trim()); var url = this.filterByRegexp(queryUpstreamBugsURLArray, this.component); jetpack.tabs.open(url + text); } } /** * */ RHBugzillaPage.prototype.sendBugUpstream = function() { var url = this.filterByRegexp(newUpstreamBugsURLArray, this .getOptionValue("component")); var ret = jetpack.tabs.open(url); var that = this; jetpack.tabs.onReady(function() { var otherDoc = ret.contentDocument; var otherElems = otherDoc.forms.namedItem("Create").elements; otherElems.namedItem("short_desc").value = that.dok .getElementById("short_desc_nonedit_display").textContent .trim(); otherElems.namedItem("comment").value = that.collectComments(); ret.focused(); }); }; /** * 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 */ RHBugzillaPage.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 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 ]; }; /** * Check for the presence of a keyword * * @param str String with the keyword * @return Boolean * */ RHBugzillaPage.prototype.hasKeyword = function(str) { var kwd = this.doc.getElementById('keywords').value.trim(); return (new RegExp(str).test(kwd)); }; /** * Add accesskey to the particular element * * @param rootElement Element to which the new text object will be attached * @param beforeText Text before the accesskey character * @param accKey what will be the accesskey itself * @param afterText text after the accesskey character * @return modified element with the fixed accesskey FIXME isn't this closure * and possible memleak? */ RHBugzillaPage.prototype.fixElement = function(elem, beforeText, accKey, afterText) { elem.setAttribute("accesskey", accKey.toLowerCase()); elem.innerHTML = beforeText + "" + accKey + "" + afterText; return elem; }; /** * Add XGL to the CC list * * @param evt Event which made this function active * @return none */ RHBugzillaPage.prototype.changeOwner = function(newAssignee) { /** * Take care that when changing assignment of the bug, current owner is * added to CC list. Switch off setting to the default assignee */ var defAssigneeButton; if (!hlpr.isInList(newAssignee, this.CCList)) { this.doc.getElementById("newcc").textContent = newAssignee; } if (newAssignee) { this.clickMouse("bz_assignee_edit_action"); this.doc.getElementById("assigned_to").value = newAssignee; this.doc.getElementById("set_default_assignee").checked = false; if (defAssigneeButton = this.doc .getElementById("setdefaultassigneebutton")) { defAssigneeButton.style.display = "none"; } } }; /** * Set the bug to NEEDINFO state * * Working function. * @return none */ RHBugzillaPage.prototype.setNeedinfoReporter = function() { this.clickMouse("needinfo"); this.selectOption("needinfo_role", "reporter"); }; /** * Return string with the ID for the external_id SELECT for external bugzilla * * @param URLhostname String hostname of the external bugzilla * @return String with the string for the external_id SELECT */ RHBugzillaPage.prototype.getBugzillaName = function(URLhostname) { var bugzillaID = ""; if (hashBugzillaName[URLhostname]) { bugzillaID = hashBugzillaName[URLhostname]; } else { bugzillaID = ""; } return bugzillaID; }; /** * Generate URL of the bug on remote bugzilla * * @param selectValue Number which is index of the bugzilla * in hashBugzillaWholeURL * @param bugID Number which is bug ID * @return string with the URL */ RHBugzillaPage.prototype.getWholeURL = function(selectValue, bugID) { var returnURL = ""; if (hashBugzillaWholeURL[selectValue]) { returnURL = hashBugzillaWholeURL[selectValue] + bugID; } else { returnURL = ""; } return returnURL; }; /** * Callback function for the XMLRPC request * * @param ret Object with xmlhttprequest response with attributes: * + status -- int return code * + statusText * + responseHeaders * + responseText */ RHBugzillaPage.prototype.callBack = function(data, textStatus) { if (--this.reqCounter <= 0) { setTimeout(this.doc.location.reload, 1000); } }; /** * The worker function -- call XMLRPC to fix MIME type of the particular * attachment * * @param id Integer with the attachment id to be fixed * @param type String with the new MIME type, optional defaults to "text/plain" * @param email Boolean whether email should be sent to appropriate person; * option, defaults to false * * updateAttachMimeType($data_ref, $username, $password) * * Update the attachment mime type of an attachment. The first argument is a * data hash containing information on the new MIME type and the attachment id * that you want to act on. * * $data_ref = { "attach_id" => "", # Attachment ID to perform * MIME type change on. "mime_type" => "", # Legal MIME * type value that you want to change the attachment to. "nomail" => 0, # * OPTIONAL Flag that is either 1 or 0 if you want email to be sent or not for * this change }; * */ RHBugzillaPage.prototype.fixAttachById = function(id, type, email) { if (type === undefined) { type = "text/plain"; } if (email === undefined) { email = false; } var msg = new XMLRPCMessage("bugzilla.updateAttachMimeType"); console.log("XML-RPC before:\n"+msg.xml()) msg.addParameter( { 'attach_id' : id, 'mime_type' : type, 'nomail' : !email }); console.log("XML-RPC message:\n"+msg.xml()); msg.addParameter(this.login); console.log("XML-RPC message:\n"+msg.xml()); msg.addParameter(this.password); console.log("XML-RPC message:\n"+msg.xml()); var req = new XMLHttpRequest(); var that = this; req.open("POST", XMLRPCurl, true); req.overrideMimeType("text/xml"); req.setRequestHeader("Content-type", "text/xml"); req.onreadystatechange = function(aEvt) { if (req.readyState == 4) { if (req.status == 200) { console.log("Fixing attachment MIME type success!"); that.callBack(); } else { console.error("Fixing MIME type attachment failed!"); } } }; req.send(msg.xml()); this.reqCounter++; }; // FIXME possibly eliminate this function altogether and // make it inline? RHBugzillaPage.prototype.fixAllAttachments = function(list) { Array.forEach(list, function(x) { this.fixAttachById(x[1]); }, this); }; /** * Create a button for fixing all bad attachments. * * @param list Array of all bad attachmentss * @return button fixing all bad Attachments */ RHBugzillaPage.prototype.createFixAllButton = function(list) { if (!XMLRPCMessage) { return; } var that = this; var elem = this.doc.createElement("a"); elem.setAttribute("href", ""); elem.setAttribute("accesskey", "f"); elem.innerHTML = "Fix all"; elem.addEventListener("click", function() { that.fixAllAttachments(list); }, false); return elem; }; /** * Add a link to the bad attachment for fixing it. * * @param * DOM jQuery element with a bad attachment * @return none */ RHBugzillaPage.prototype.addTextLink = function(row) { var that = this; var elemS = row[4].getElementsByTagName("td"); var elem = elemS[elemS.length - 1]; elem.innerHTML += "
Text"; elem.addEventListener("click", function(x) { that.fixAttachById(row[1], "text/plain"); }, false); }; /** * Add information about the upstream bug upstream, and closing it. * * @param evt Event which called this handler * @return none */ RHBugzillaPage.prototype.addClosingUpstream = function() { var refs = this.doc.getElementById("external_bugs_table") .getElementsByTagName("tr"); // that's a bad id, if there is a one. :) var inputBox = this.doc.getElementById("inputbox"); var externalBugID = 0; var wholeURL = ""; // Fix missing ID on the external_id SELECT this.doc.getElementsByName("external_id")[0].setAttribute("id", "external_id"); if (inputBox.value.match(/^http.*/)) { var helpAElem = this.doc.createElement("a"); wholeURL = inputBox.value; helpAElem.setAttribute("href", wholeURL); var paramsArr = helpAElem.search.replace(/^\?/, '').split('&'); // get ID# var params = {}, s = []; paramsArr.forEach(function(par, idx, arr) { s = par.split('='); params[s[0]] = s[1]; }); if (params.id) { externalBugID = parseInt(params.id, 10); inputBox.value = externalBugID; } // get host and bugzillaName var bugzillaName = this.getBugzillaName(helpAElem.hostname); this.selectOption("external_id", bugzillaName); } else if (!isNaN(inputBox.value)) { externalBugID = parseInt(inputBox.value, 10); var bugzillaID = this.doc.getElementById("external_id").value; wholeURL = this.getWholeURL(bugzillaID, externalBugID); } else { // no inputBox.value -- maybe there is an external bug from // the previous commit? } // It is not good to close bug as UPSTREAM, if there is no reference // to the upstream bug. if ((externalBugID > 0) || (refs.length > 2)) { this.addTextToTextBox("comment", msgStrs.sentUpstreamString.replace( "§§§", wholeURL)); this.selectOption("bug_status", "CLOSED"); this.selectOption("resolution", "UPSTREAM"); } else { console.log("No external bug specified among the External References!"); } }; /** * Insert a row of buttons before the marked element * * @param anchor Element before which the row of buttons will be inserted * @param array Array of data for buttons to be generated * @return none */ RHBugzillaPage.prototype.generateToolBar = function(anchor, array) { for ( var i = 0; i < array.length; i++) { var butt = array[i]; this.addNewButton(anchor, butt.idx, butt.msg, butt.string, butt.state, butt.parameter, butt.submit); } }; RHBugzillaPage.prototype.setBugAssigned = function(ver) { // Now we lie completely, we just set keyword Triaged, // this is not just plain ASSIGNED, but // modified according to // https://fedoraproject.org/wiki/BugZappers/Meetings/Minutes-2009-Oct-27 // and // http://meetbot.fedoraproject.org/fedora-meeting/2009-11-24\ // /fedora-meeting.2009-11-24-15.11.log.html // and // http://meetbot.fedoraproject.org/fedora-meeting/2009-11-24\ // /fedora-meeting.2009-11-24-15.11.log.html // for F13 and later, ASSIGNED is "add Triaged keyword" (as well) // for CURRENTRELEASE // and put the release version to // "Fixed in Version" textbox // otherwise -> NEXTRELEASE this.selectOption("bug_status", "CLOSED"); var text = ""; if (jetpack.selection.text) { text = jetpack.select.text.trim(); } if (text.length > 0) { this.selectOption("resolution", "CURRENTRELEASE"); this.doc.getElementById("cf_fixed_in").value = text; } else if (verNo === 999) { this.selectOption("resolution", "RAWHIDE"); } else { this.selectOption("resolution", "NEXTRELEASE"); } } /** * Generalized function for all actions * * @param addString String to be added as new comment * @param nextState String signifying next state of the bug (whatever is in Bugzilla + * "NEEDINFO" meaning NEEDINFO(Reporter)) * @param secondParameter String with label on the subbutton for reason of * closing the bug * @return none */ RHBugzillaPage.prototype.generalPurposeCureForAllDisease = function(addString, nextState, secondParameter) { var verNo = this.getVersion(); if (addString.length > 0) { this.addTextToTextBox("comment", addString); } if (nextState === "CLOSED") { if (secondParameter === "UPSTREAM") { this.addClosingUpstream(); } else if (secondParameter === "SOMERELEASE") { this.closeSomeRelease(verNo); } else if (secondParameter.length > 0) { this.selectOption("bug_status", nextState); this.selectOption("resolution", secondParameter); return 0; } else { throw "Missing resolution for CLOSED status."; } } // Now closing bugs is done, what about the rest? if (nextState === "NEEDINFO") { this.setNeedinfoReporter(); } else if (nextState === "ADDKEYWORD") { if (secondParameter.length === 0) { throw "Keyword has to be defined"; } this.addKeyword(secondParameter); } else if (nextState === "ASSIGNED") { this.setBugAssigned(verNo); } else if (nextState === "QUERYSEL") { this.queryForSelection(); } else if (nextState === "QUERYUP") { this.queryUpstream(); } else if (nextState === "SENDUPSTREAM") { this.sendBugUpstream(); } else if (nextState === "SETDEFASS") { if (secondParameter.length > 0) { this.changeOwner(secondParameter); } } else if (nextState === "CHIPMAGIC") { var splitArr = secondParameter.split("\t"); this.fillInWhiteBoard(splitArr[0], splitArr[1]); } else if (nextState.length > 0) { this.selectOption("bug_status", nextState); } if (secondParameter === "ADDSELFCC") { this.doc.getElementById("addselfcc").checked = true; } else if (secondParameter === "NODEFAULTASSIGNEE") { this.doc.getElementById("set_default_assignee").removeAttribute( "checked"); } }; /** * */ RHBugzillaPage.prototype.parseBacktrace = function(ret) { var splitArray = ret.split("\n"); var i = 0, ii = splitArray.length; var outStr = "", curLine = "", numStr = ""; var lineCounter = 0, endLineNo = 0; while (i < ii) { if (signalHandlerRE.test(splitArray[i])) { break; } i++; } if (i < ii) { lineCounter = parseInt(frameNoRE.exec(splitArray[i])[1], 10); endLineNo = lineCounter + NumberOfFrames; curLine = splitArray[i]; while ((lineCounter < endLineNo) && (curLine.trim().length > 0) && (i < ii)) { outStr += curLine + '\n'; numStr = frameNoRE.exec(curLine); if (numStr) { lineCounter = parseInt(numStr[1], 10); } i++; curLine = splitArray[i]; } } return outStr; }; /** * Main executable functioning actually building all buttons on the page -- * separated into function, so that it could be called from onload method of the * XMLHttpRequest. * * @param jsonList Array created from JSON * @return none */ RHBugzillaPage.prototype.buildButtons = function(above, below) { // Generate a list of elements in the page this.addNewButton(this.doc.getElementById("commit_middle"), "changeOwnerbtn", "Mark Triaged", "", "ASSIGNED", "NODEFAULTASSIGNEE"); // THE MAIN BUTTON ROWS var commentBox = this.doc.getElementById("comment"); var brElement = this.doc.createElement("br"); commentBox.parentNode.normalize(); commentBox.parentNode.insertBefore(brElement, commentBox); // this.generateToolBar(commentBox.previousSibling,above); this.generateToolBar(brElement, above); this.generateToolBar(this.originalButton, below); var commentArea = this.doc.getElementById("comment_status_commit"); var brElementPlacer = commentArea.getElementsByTagName("br")[0]; if (queryButtonAvailable || upstreamButtonAvailable) { brElementPlacer.parentNode.insertBefore(this.doc.createElement("br"), brElementPlacer); } if (queryButtonAvailable) { // Add query search button this.addNewButton(brElementPlacer, "newqueryintab", "Query for string", "", "QUERYSEL", "", false); } if (queryUpstreamBugsURLArray) { console.log("Adding upstream query search box"); if (this.filterByRegexp(queryUpstreamBugsURLArray, this.component)) { this.addNewButton(brElementPlacer, "upstreamqueryintab", "Query upstream", "", "QUERYUP", "", false); } } // Button for upstreaming if (upstreamButtonAvailable) { this.addNewButton(brElementPlacer, "sendupstream", "Send upstream", "", "SENDUPSTREAM", "", false); } // var brElement2BMoved = this.doc.querySelector("#comment_status_commit // br:last-of-type"); // var brWhereMove = this.doc.getElementsByClassName("status")[0]; // brWhereMove.parentNode.insertBefore(brElement2BMoved.cloneNode(true), // brWhereMove); // brElement2BMoved.parentNode.removeChild(brElement2BMoved); // TODO Get compiz bugs as well if ((chipIDsGroupings.length > 0) && this.maintCCAddr === "xgl-maint@redhat.com") { // Add find chip magic button var whiteboard_string = this.doc.getElementById("status_whiteboard").value; if (!/card_/.test(whiteboard_string)) { this.fillInChipMagic(); } } // Add setting default assignee if ((this.defaultAssignee.length > 0) && (this.defaultAssignee !== this.owner)) { this.addNewButton( this.doc.getElementById("bz_assignee_edit_container"), "setdefaultassigneebutton", "Def. Assignee", "", "SETDEFASS", this.defaultAssignee, false, true); } }; RHBugzillaPage.prototype.addLogRecord = function() { var rec = {}; rec.date = new Date(); rec.bugId = this.bugNo; rec.title = this.title; var comment = jetpack.tabs.focused.contentWindow.prompt( "Enter comments for this comment").trim(); if (comment.length > 0) { rec.comment = comment; var recKey = this.getISODate(rec.date) + "+" + rec.bugId; var clearLogAElem = this.doc.getElementById("clearLogs"); clearLogAElem.style.color = this.FullLogsColor; clearLogAElem.style.fontWeight = "bolder"; if (myStorage.logs[recKey]) { myStorage.logs[recKey].comment += "
\n" + comment; } else { myStorage.logs[recKey] = rec; } jetpack.storage.simple.sync(); } else if (comment === null) { return null; } }; RHBugzillaPage.prototype.timeSheetRecordsPrinter = function(body, records) { var that = this; // sort the records into temporary array var tmpArr = []; for ( var i in records) { if (records.hasOwnProperty(i)) { tmpArr.push( [ i, records[i] ]); } } tmpArr.sort(function(a, b) { return a[0] > b[0] ? 1 : -1; }); var currentDay = ""; // now print the array tmpArr .forEach(function(rec) { var x = rec[1]; var dayStr = that.getISODate(x.date); if (dayStr != currentDay) { currentDay = dayStr; body.innerHTML += "

" + currentDay + "

"; } body.innerHTML += "

Bug " + x.title + "" + " \n
" + x.comment + "

"; }); }; RHBugzillaPage.prototype.generateTimeSheet = function(body) { var doc = body.ownerDocument; this.timeSheetRecordsPrinter(body, myStorage.logs); }; // ///////////////////////////////////////////////////////////////////////////// var callback = function(doc) { var curPage = new RHBugzillaPage(doc); }; var options = {}; options.matches = [ "https://bugzilla.redhat.com/show_bug.cgi" ]; jetpack.pageMods.add(callback, options);