/*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; // ============================================================== // 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" }, { name : "enabledPacks", type : "text", label : "comment packs which should be enabled", "default" : "" } ] }; jetpack.future.import("storage.settings"); // TODO we should have an array SpecialFlags instead of multiple Boolean // variables var logSubmits = false; var chipIDsGroupings = []; var AddrArray = []; var topRow = {}; var bottomRow = {}; /* * * 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() { let method = this.method; // assemble the XML message header let xml = ""; xml += "\n"; xml += "\n"; xml += "" + method + "\n"; xml += "\n"; // do individual parameters for ( let i = 0; i < this.params.length; i++) { let 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 let type = typeof (o); type = type.toLowerCase(); switch (type) { case "number": if (Math.round(o) == o) type = "i4"; else type = "double"; break; case "object": let 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) { let xml = "<" + type + ">" + data + ""; return xml; }; XMLRPCMessage.prototype.doBooleanXML = function(data) { let value = (data == true) ? 1 : 0; let xml = "" + value + ""; return xml; }; XMLRPCMessage.prototype.doDateXML = function(data) { let leadingZero = function (n) { // pads a single number with a leading zero. Heh. if (n.length == 1) n = "0" + n; return n; }; let dateToISO8601 = function(date) { // wow I hate working with the Date object let year = new String(date.getYear()); let month = this.leadingZero(new String(date.getMonth())); let day = this.leadingZero(new String(date.getDate())); let time = this.leadingZero(new String(date.getHours())) + ":" + this.leadingZero(new String(date.getMinutes())) + ":" + this.leadingZero(new String(date.getSeconds())); let converted = year + month + day + "T" + time; return converted; }; let xml = ""; xml += dateToISO8601(data); xml += ""; return xml; }; XMLRPCMessage.prototype.doArrayXML = function(data) { let xml = "\n"; for ( let 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) { let xml = "\n"; for ( let 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) { let 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; }; // ============================================================== let hlpr = function () { }; /** * Function for the management of the prototypal inheritace * David Flanagan, Javascript: The Definitve Guide, * IV. edition, O'Reilly, 2006, p. 168 * * @param superobject * @return new object, it needs new prototype.constructor * *
 * function Father(x) {
 *    this.family = x;
 * }
 *
 * function Son(x,w) {
 *    Father.call(this,x);
 *    this.wife = w;
 * }
 * Son.prototype = hlpr.heir(Father);
 * Son.prototype.constructor = Son;
 * 
*/ hlpr.heir = function(p) { function f() {}; f.prototype = p.prototype; return new f(); } /** * 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) { if (!list) { return false; } return (list.indexOf(mbr) !== -1); }; /** * Make sure value returned is Array * * @param Array/String * @return Array * * If something else than Array or String is passed to the function * the result will be untouched actual argument of the call. */ hlpr.valToArray = function valToArray(val) { return (val instanceof Array) ? val : [val]; }; /** * Merges two comma separated string as a list and returns new string * * @param str String with old values * @param value String/Array with other values * @return String with merged lists */ hlpr.addCSVValue = function addCSVValue(str, value) { console.log("addCSVValue: str = " + str + ", value = " + value); console.log("addCSVValue: typeof str = " + typeof str + ", typeof value = " + typeof value); let parts = (str.trim().length > 0 ? str.split(",") : []); console.log("parts before = " + parts.toSource()); if (!hlpr.isInList(value,parts)) { let newValue = hlpr.valToArray(value); console.log("newValue = " + newValue); parts = parts.concat(newValue); } console.log("parts on leaving = " + parts.toSource()); 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(); let parts = str ? str.split(",") : []; let valueArr = value instanceof Array ? value : value.split(","); parts = parts.filter(function(e,i,a) { return (hlpr.isInList(e,valueArr)); }); return parts.join(","); }; /** * Load text from URL * * @param URL String * @param cb_function Function to be called when the download happens with * the downloaded body of the HTTP message as the only parameter * @param what optional Object argument representing this for this call * @return none */ hlpr.loadText = function(URL, cb_function, what) { if (what === undefined) { // missing optional argument what = this; } let 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(""); }; /** * Load JSON from URL * * @param URL String * @param cb_function Function to be called when the download happens with * the downloaded JSON as the only parameter * @param what optional Object argument representing this for this call * @return none */ hlpr.loadJSON = function(URL, cb_function, what) { if (what === undefined) { // missing optional argument what = this; } console.log("URL = " + URL); hlpr.loadText(URL, function(text) { let data = JSON.parse(text); cb_function.call(what, data); }, what); }; // ============================================================================ // 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() { let rH = Number(this.r.toFixed()).toString(16); let gH = Number(this.g.toFixed()).toString(16); let 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() { let r = this.r / 255; let g = this.g / 255; let b = this.b / 255; let max = Math.max(r, g, b), min = Math.min(r, g, b); let h, s, l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { let 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; } let r, g, b; if (s === 0) { r = g = b = l; // achromatic } else { let q = l < 0.5 ? l * (1 + s) : l + s - l * s; let 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() { let r = this.r / 255; let g = this.g / 255; let b = this.b / 255; let max = Math.max(r, g, b), min = Math.min(r, g, b); let h, s, v = max; let 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) { let r, g, b; let i = Math.floor(h * 6); let f = h * 6 - i; let p = v * (1 - s); let q = v * (1 - f * s); let 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() { let hslArray = this.hsl(); let h = Number(hslArray[0]); let s = Number(hslArray[1]) * this.Desaturated; let l = this.Luminosity; let desA = this.hslToRgb(h, s, l); return new Color(desA[0], desA[1], desA[2]); }; // ==================================================================================== // BZPage's methods function BZPage(doc) { // 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 this.EmptyLogsColor = new Color(0, 255, 0); this.FullLogsColor = new Color(0, 40, 103); this.RE = { Comment: new RegExp("^\\s*#"), // unsused BlankLine: new RegExp("^\\s*$"), // unused // nová řádka // [ 65.631] (--) intel(0): Chipset: "845G" Chipset: new RegExp("^\\s*\\[?[ 0-9.]*\\]?\\s*\\(--\\) "+ "([A-Za-z]+)\\([0-9]?\\): Chipset: (.*)$"), ATIgetID: new RegExp("^.*\\(ChipID = 0x([0-9a-fA-F]+)\\).*$"), Abrt: new RegExp("^\\s*\\[abrt\\]"), signalHandler: new RegExp("^\\s*#[0-9]*\\s*"), frameNo: new RegExp("^\\s*#([0-9]*)\\s") }; // initialize dynamic properties this.doc = doc; this.packages = this.getInstalledPackages(); if ("commentStrings" in config.gJSONData) { this.commentStrings = config.gJSONData.commentStrings; } if ("constantData" in config.gJSONData) { this.constantData = config.gJSONData.constantData; } if ("CCmaintainer" in config.gJSONData.constantData) { this.defBugzillaMaintainerArr = config.gJSONData.constantData.CCmaintainer; } if ("submitsLogging" in config.gJSONData.configData && config.gJSONData.configData.submitsLogging) { this.setUpLoggingButtons(); } 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; this.CCList = this.getCCList(); this.generateButtons(); } /** * */ BZPage.prototype.getInstalledPackages = function() { let installedPackages = {}; if (config.gJSONData && ("commentPackages" in config.gJSONData)) { let enabledPackages = jetpack.storage.settings.enabledPacks.split(/[, ]/); for each (let pkg in enabledPackages) { if (pkg in config.gJSONData.commentPackages) { installedPackages[pkg] = config.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 (cmdLabel, cmdParams) { console.log("BZPage centralCommandDispatch\n"+ "cmdLabel = " + cmdLabel + ", cmdParams = " + cmdParams); switch (cmdLabel) { // FIXME we don't have guaranteed order of execution, but // for example resolution can be executed only after status; // we may need to eliminate these two commands and replace them // with "closed", "assigned", etc. with parameter resolution? // or only "closed" and other commands which require resolution? 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": console.log("commentIdx: idx = " + cmdParams); let commentText = this.commentStrings[cmdParams]; console.log("comment = " + commentText); 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; // 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. */ BZPage.prototype.executeCommand = function (cmd) { console.log("executeCommand / cmd = " + cmd); let [pkg, id] = cmd.split("//"); let commentObj = this.packages[pkg][id]; console.log("commentObj:\n" + commentObj.toSource()+ "\n----------------------------------"); for (let key in commentObj) { console.log("key = " + key + "\ncommentObj = " + commentObj.toSource()); this.centralCommandDispatch(key,commentObj[key]); } }; /** * Add XGL to the CC list * * @param evt Event which made this function active * @return none */ BZPage.prototype.changeAssignee = function(newAssignee) { let defAssigneeButton = null; this.addToCCList(this.owner); if (newAssignee === null) { this.doc.getElementById("set_default_assignee").removeAttribute( "checked"); return ; } if (this.getDefaultAssignee) { if (newAssignee == "default") { let defAss = this.getDefaultAssignee(); console.log("defAss = " + defAss); 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; if (defAssigneeButton = this.doc .getElementById("setDefaultAssignee_btn")) { 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(pkg, cmd) { let select = this.doc.getElementById("comment_action"); if (!select) { 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) { let sel = this.doc.getElementById(id); sel.value = label; let 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(targetID) { let 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; } let 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 Object chosen element */ BZPage.prototype.filterByRegexp = function(list, chosingMark) { let 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; } else { return ""; } }; /** * 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(id, stuff) { let textBox = this.doc.getElementById(id); console.log("textBox.id = " + id); console.log("textBox.stuff = " + stuff); console.log("textBox.tagname = " + textBox.tagName.toLowerCase()); if (textBox.tagName.toLowerCase() === "textarea") { stuff = textBox.value ? "\n\n" + stuff : stuff; textBox.value += stuff; } else { textBox.value = hlpr.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(id, stuff) { let changedElement = this.getElementById(id); changedElement.value = hlpr.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(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)); }; /** * */ 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"; } }; /** * 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() { this.clickMouse("needinfo"); this.selectOption("needinfo_role", "reporter"); }; /** * */ BZPage.prototype.getOwner = function() { let priorityParent = this.doc.querySelector("label[for~='target_milestone']") .parentNode.parentNode.parentNode; let assigneeAElement = priorityParent.querySelector("tr:nth-of-type(1) a.email"); let assgineeHref = decodeURI(assigneeAElement.getAttribute("href")); console.log("assignee href = " + assgineeHref); let email = assgineeHref.split(":")[1]; console.log("assignee's email = " + email); return email; }; /** * Get login of the currently logged-in user. * * @return String with the login name of the currently logged-in user */ BZPage.prototype.getLogin = function () { let lastLIElement = this.doc.querySelector("#header ul.links li:last-of-type"); let loginArr = lastLIElement.textContent.split("\n"); let loginStr = loginArr[loginArr.length - 1].trim(); console.log("login = " + loginStr); return loginStr; }; /** * 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(component) { console.log("getDefaultBugzillaMaintainer / component = " + component); let address = this.filterByRegexp(this.defBugzillaMaintainerArr, component); console.log("getDefaultBugzillaMaintainer / address = " + address); return address; } /** * 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() { let outAtts = []; let atts = this.doc.getElementById("attachment_table") .getElementsByTagName("tr"); for ( let i = 1, ii = atts.length - 1; i < ii; i++) { outAtts.push(this.parseAttachmentLine(atts[i])); } return outAtts; }; /** * 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() { if (jetpack.storage.settings.BZpassword) { return jetpack.storage.settings.BZpassword; } else { let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); let password = { value : "" }; // default the password to pass let check = { value : true }; // default the checkbox to true let 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) { let passwordText = password.value; jetpack.storage.settings.BZpassword = passwordText; jetpack.storage.simple.sync(); return passwordText; } } return null; }; /** * */ BZPage.prototype.setUpLoggingButtons = function() { console.log("LOgging setUpLoggingButtons"); // For adding additional buttons to the top toolbar let additionalButtons = this.doc.querySelector("#bugzilla-body *.related_actions"); let that = this; // logging all submits for timesheet this.doc.forms.namedItem("changeform").addEventListener("submit", function(evt) { if (that.addLogRecord() === null) { // FIXME doesn't work ... still submitting' evt.stopPropagation(); evt.preventDefault(); } }, false); let generateTimeSheetUI = this.doc.createElement("li"); generateTimeSheetUI.innerHTML = "\u00A0-\u00A0" + "Generate timesheet"; additionalButtons.appendChild(generateTimeSheetUI); this.doc.getElementById("generateTSButton").addEventListener( "click", function(evt) { that.createBlankPage.call(that, "TimeSheet", that.generateTimeSheet); evt.stopPropagation(); evt.preventDefault(); }, false); let clearLogsUI = this.doc.createElement("li"); clearLogsUI.innerHTML = "\u00A0-\u00A0" + "Clear logs"; additionalButtons.appendChild(clearLogsUI); let 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"; } }; /** * 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(who) { console.log("who = " + who); if (!who) { return ; } if (who == "self") { this.doc.getElementById("addselfcc").checked = true; } else { this.clickMouse("cc_edit_area_showhide"); if (!hlpr.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() { let CCListSelect = this.doc.getElementById("cc"); outCCList = []; if (CCListSelect) { outCCList = Array.map(CCListSelect.options, function(item) { return item.value; }); } return outCCList; }; // ==================================================================================== // MozillaBugzilla object MozillaBugzilla = function (doc) { BZPage.call(this, doc) }; MozillaBugzilla.prototype = hlpr.heir(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 // END OF CONSTANTS // Prepare for query buttons // FIXME getting null for commentArea sometimes let commentArea = doc.getElementById("comment_status_commit"); let brElementPlacer = commentArea.getElementsByTagName("br")[0]; brElementPlacer.setAttribute("id","brElementPlacer_location"); brElementPlacer.parentNode.insertBefore(doc.createElement("br"), brElementPlacer); // inheritance ... call superobject's constructor BZPage.call(this,doc); let that = this; this.reqCounter = 0; this.signaturesCounter = 0; this.chipMagicInterestingLine = ""; this.login = this.getLogin(); this.password = this.getPassword(); let bugNoTitle = this.doc.querySelector("#title > p").textContent.trim(); this.bugNo = new RegExp("[0-9]+").exec(bugNoTitle)[0]; let ITbutton = this.doc.getElementById("cf_issuetracker"); this.its = ITbutton ? ITbutton.value.trim() : ""; // set default assignee on change of the component this.doc.getElementById("component").addEventListener("change", function() { that.component = that.getOptionValue("component"); that.changeAssignee("default"); }, false); // getBadAttachments this.XorgLogAttList = []; this.XorgLogAttListIndex = 0; this.attachments = this.getAttachments(); this.markBadAttachments(); this.setDefaultAssignee(); // Dig out backtrace this.btSnippet = ""; let parseAbrtBacktraces = config.gJSONData.configData.parseAbrtBacktraces; console.log("parseAbrtBacktraces = " + parseAbrtBacktraces); if (parseAbrtBacktraces && this.RE.Abrt.test(this.title)) { this.pasteBacktraceInComments(); } // Take care of signature for Fedora bugzappers if (config.gJSONData.configData.signature.length > 0) { let signatureFedoraString = config.gJSONData.configData.signature; this.doc.forms.namedItem("changeform").addEventListener("submit", function() { if (this.signaturesCounter < 1) { that.addStuffToTextBox("comment", signatureFedoraString); this.signaturesCounter += 1; } }, false); } this.setBranding(); this.checkComments(); // offline-capable submit this.doc.forms.namedItem("changeform").addEventListener('submit', function(evt) { that.submitCallback.call(that, evt); }, false); // TODO Get compiz bugs as well if ((config.gJSONData.configData.PCIIDsURL && (config.PCI_ID_Array.length > 0)) && this.maintCCAddr === "xgl-maint@redhat.com") { // Add find chip magic button let whiteboard_string = this.doc.getElementById("status_whiteboard").value; if (!/card_/.test(whiteboard_string)) { this.fillInChipMagic(); } } if (logSubmits) { this.setUpLoggingButtons(); } } // END OF RHBugzillaPage CONSTRUCTOR RHBugzillaPage.prototype = hlpr.heir(BZPage); RHBugzillaPage.prototype.constructor = RHBugzillaPage; /** * Find default assignee based on the current component * * @return String what would be a default assignee if * we haven't set it up. */ RHBugzillaPage.prototype.getDefaultAssignee = function() { return this.filterByRegexp(this.constantData.defaultAssignee, this.component).toLowerCase(); } /** * Set default assignee * * @return none * sets this.defaultAssignee property according to defaultAssignee list */ RHBugzillaPage.prototype.setDefaultAssignee = function() { this.defaultAssignee = this.getDefaultAssignee(); let defAss = this.defaultAssignee; console.log("defAss = " + defAss); // Add setting default assignee if ((defAss.length > 0) && (defAss !== this.getOwner())) { this.constantData.defaultAssigneeTrigger = true; this.createNewButton("bz_assignee_edit_container",true,"rh-common","setDefaultAssignee"); } }; /** * Auxiliary function to computer more complicated resolution */ RHBugzillaPage.prototype.closeSomeRelease = function() { // for RAWHIDE close as RAWHIDE, // if active selection -> CURRENTRELEASE // and put the release version to // "Fixed in Version" textbox // otherwise -> NEXTRELEASE let verNo = this.getVersion(); this.selectOption("bug_status", "CLOSED"); let text = ""; let resolution = ""; if (jetpack.selection.text) { text = jetpack.select.text.trim(); } if (text.length > 0) { resolution = "CURRENTRELEASE"; this.doc.getElementById("cf_fixed_in").value = text; } else if (verNo === 999) { resolution = "RAWHIDE"; } else { resolution = "NEXTRELEASE"; } this.centralCommandDispatch("resolution", resolution); }; /** * Additional commands specific for this subclass, overriding superclass one. */ RHBugzillaPage.prototype.centralCommandDispatch = function(cmdLabel, cmdParams) { console.log("RHBugzillaPage centralCommandDispatch\n"+ "cmdLabel = " + cmdLabel + ", cmdParams = " + cmdParams); switch (cmdLabel) { // Set up our own commands case "closeUpstream": this.addClosingUpstream(); break; case "computeResolution": this.closeSomeRelease(); break; case "queryStringOurBugzilla": this.queryForSelection(); break; case "queryUpstreamBugzilla": this.queryUpstream(); break; case "sendBugUpstream": this.sendBugUpstream(); break; case "markTriaged": this.markBugTriaged(); break; case "chipMagic": let splitArr = cmdParams.split("\t"); this.fillInWhiteBoard(splitArr[0], splitArr[1]); break; // If we don't have it here, call superclass method default: BZPage.prototype.centralCommandDispatch.call(this, cmdLabel, cmdParams); break; } }; /* 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) { let 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) { let 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) { let serForm = this .serializeForm(jetpack.tabs.focused.contentWindow.document.forms .namedItem("changeform")); // console.log("serForm:\n" + serForm.toSource()); } else { let 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 let bugID = formData.bugNo; let 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. */ /** * */ RHBugzillaPage.prototype.createBlankPage = function (ttl, bodyBuildCB) { let title = ttl || "Yet another untitled page"; let that = this; let logTab = jetpack.tabs.open("about:blank"); jetpack.tabs.onReady(function() { let otherDoc = logTab.contentDocument; otherDoc.title = title; otherDoc.body.innerHTML = "

" + title + "

"; bodyBuildCB.call(that, otherDoc.body); logTab.focus(); }); }; RHBugzillaPage.prototype.ProfessionalProducts = [ "Red Hat Enterprise Linux", "Red Hat Enterprise MRG" ]; /** * */ RHBugzillaPage.prototype.pasteBacktraceInComments = function() { // FIXME This paragraph looks suspicous ... what is it? // Does it belong to this function? let notedLabel = this.doc.querySelector("label[for='newcc']"); while (notedLabel.firstChild) { let node = notedLabel.removeChild(notedLabel.firstChild); notedLabel.parentNode.insertBefore(node, notedLabel); } notedLabel.parentNode.removeChild(notedLabel); let abrtQueryURL = "https://bugzilla.redhat.com/buglist.cgi?" + "cmdtype=dorem&remaction=run&namedcmd=all%20NEW%20abrt%20crashes&sharer_id=74116"; let mainTitle = this.doc .getElementsByClassName("bz_alias_short_desc_container")[0]; let 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.addStuffToTextBox('status_whiteboard', 'btparsed'); } if (!(this.isTriaged() || this.idContainsWord("status_whiteboard", 'btparsed'))) { let 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) { let btRaw = hlpr.loadText(attURL, function(ret) { this.btSnippet = this.parseBacktrace(ret); if (this.btSnippet) { this.addStuffToTextBox("comment", this.btSnippet); this.addStuffToTextBox("status_whiteboard", "btparsed"); } }, this); } }, this); } }; /** * */ RHBugzillaPage.prototype.markBadAttachments = function() { let badMIMEArray = [ "application/octet-stream", "text/x-log", "undefined" ]; let badAttachments = this.attachments.filter(function(att, idx, arr) { return (hlpr.isInList(att[2], badMIMEArray)); }); if (badAttachments.length > 0) { let 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); } }; /** * Is this bug a RHEL bug? * * @return Boolean true if it is a RHEL bug */ RHBugzillaPage.prototype.isEnterprise = function() { let prod = this.product; console.log("Testing whether the bug with product = " + prod + " is an enterprise bug."); let 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() { let brandColor = {}; let 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 let titleElem = this.doc.getElementsByTagName("title")[0]; titleElem.textContent = titleElem.textContent.slice(4); let bodyTitleParent = this.doc.getElementById("summary_alias_container").parentNode; let 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 = this.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)) { let 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 let compElems; let suspiciousComponents = config.gJSONData.configData.suspiciousComponents; 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) { let that = this; function groupIDs(manStr, cardStrID) { let 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) { let soughtID = (manufacturerNo + "," + cardNo).toUpperCase(); let outList = config.PCI_ID_Array[soughtID]; if (outList) { return outList; } else { return ""; } } ; let outStr = ""; let cardIDStr = ""; let cardIDArr = []; chipSwitchboard: if (driverStr === "RADEON") { let cardID = iLine.replace(this.RE.ATIgetID, "$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.addStuffToTextBox("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 () { let XorgLogURL = ""; let XorgLogAttID = ""; let XorgLogFound = false; let attURL = "", interestingLine = ""; let 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; let req = new XMLHttpRequest(); req.open("GET",attURL,true); req.onreadystatechange = function (aEvt) { if (req.readyState == 4) { if (req.status == 200) { let ret = req.responseText; let interestingLineArr = ret.split("\n"). filter(function (v,i,a) { return this.RE.Chipset.test(v); }); console.log("interestingLineArr = " + interestingLineArr.toSource()); if (interestingLineArr.length >0) { interestingArray = this.RE.Chipset.exec(interestingLineArr[0]); interestingLine = interestingArray[2]. replace(/[\s"]+/g," ").trim(); // Persuade createNewButton to have mercy and to actually add // non-default button this.constantData.chipMagicTrigger = true; this.chipMagicInterestingLine = interestingLine+"\t"+interestingArray[1] .toUpperCase(); this.createNewButton("status_whiteboard", true, "rh-xorg", "chipMagic"); } } 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; } let 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()); let 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() { let 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() { let text = jetpack.selection.text; if (!text) { text = jetpack.clipboard.get(); } if (text) { let text = encodeURIComponent(text.trim()); let queryUpstreamBugsURLArray = this.constantData.queryUpstreamBug; let url = this.filterByRegexp(queryUpstreamBugsURLArray, this.component); jetpack.tabs.open(url + text); } } /** * */ RHBugzillaPage.prototype.sendBugUpstream = function() { let url = this.filterByRegexp(newUpstreamBugsURLArray, this .getOptionValue("component")); let ret = jetpack.tabs.open(url); let that = this; jetpack.tabs.onReady(function() { let otherDoc = ret.contentDocument; let otherElems = otherDoc.forms.namedItem("Create").elements; otherElems.namedItem("short_desc").value = that.doc .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) { let MIMEtype = ""; let size = 0; // Skip over obsolete attachments if (inElem.getElementsByClassName("bz_obsolete").length > 0) { return ( []); } // getting name of the attachment let attName = inElem.getElementsByTagName("b")[0].textContent.trim(); let aHrefsArr = inElem.getElementsByTagName("a"); let aHref = Array.filter(aHrefsArr, function(x) { return x.textContent.trim() == "Details"; })[0]; let id = parseInt(aHref.getAttribute("href").replace( /^.*attachment.cgi\?id=/, ""), 10); // getting MIME type and size let 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 ]; }; /** * 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 */ RHBugzillaPage.prototype.fixElement = function(elem, beforeText, accKey, afterText) { elem.setAttribute("accesskey", accKey.toLowerCase()); elem.innerHTML = beforeText + "" + accKey + "" + afterText; return elem; }; /** * 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) { let bugzillaID = ""; if (this.constantData.bugzillalabelNames[URLhostname]) { bugzillaID = this.constantData.bugzillalabelNames[URLhostname]; } else { bugzillaID = ""; } return bugzillaID; }; /** * Generate URL of the bug on remote bugzilla * * @param selectValue Number which is index of the bugzilla * in this.constantData.bugzillaIDURLs * @param bugID Number which is bug ID * @return string with the URL */ RHBugzillaPage.prototype.getWholeURL = function(selectValue, bugID) { let returnURL = ""; if (this.constantData.bugzillaIDURLs[selectValue]) { returnURL = this.constantData.bugzillaIDURLs[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.fixingMIMECallBack = 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; } let 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()); let req = new XMLHttpRequest(); let 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.fixingMIMECallBack(); } else { console.error("Fixing MIME type attachment failed!"); } } }; req.send(msg.xml()); this.reqCounter++; }; /** * 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; } let that = this; let elem = this.doc.createElement("a"); elem.setAttribute("href", ""); elem.setAttribute("accesskey", "f"); elem.innerHTML = "Fix all"; elem.addEventListener("click", function() { Array.forEach(list, function(x) { this.fixAttachById(x[1]); }, that); }, 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) { let that = this; let elemS = row[4].getElementsByTagName("td"); let 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() { let refs = this.doc.getElementById("external_bugs_table") .getElementsByTagName("tr"); // that's a bad id, if there is a one. :) let inputBox = this.doc.getElementById("inputbox"); let externalBugID = 0; let wholeURL = ""; // Fix missing ID on the external_id SELECT this.doc.getElementsByName("external_id")[0].setAttribute("id", "external_id"); if (inputBox.value.match(/^http.*/)) { let helpAElem = this.doc.createElement("a"); wholeURL = inputBox.value; helpAElem.setAttribute("href", wholeURL); let paramsArr = helpAElem.search.replace(/^\?/, '').split('&'); // get ID# let 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 let bugzillaName = this.getBugzillaName(helpAElem.hostname); this.selectOption("external_id", bugzillaName); } else if (!isNaN(inputBox.value)) { externalBugID = parseInt(inputBox.value, 10); let 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? } // FIXME THis is not good, we don't have a feedback for other commands, // not to be run, if this fails. // It is not good to close bug as UPSTREAM, if there is no reference // to the upstream bug. if ((externalBugID > 0) || (refs.length > 2)) { let msgStr = this.commentStrings["sentUpstreamString"]; msgStr = msgStr.replace("§§§", wholeURL); this.centralCommandDispatch("comment",msgStr); this.centralCommandDispatch("status", "CLOSED"); this.centralCommandDispatch("resolution", "UPSTREAM"); } else { console.log("No external bug specified among the External References!"); } }; RHBugzillaPage.prototype.markBugTriaged = function() { // 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 0) && (i < ii)) { outStr += curLine + '\n'; numStr = this.RE.frameNo.exec(curLine); if (numStr) { lineCounter = parseInt(numStr[1], 10); } i++; curLine = splitArray[i]; } } return outStr; }; // FIXME we should store whole URL, not just ID (to cooperate with other bugzillas) // General rule: there is nothing special about bugzilla.redhat.com, so for example // constantData.bugzillalabelNames should be made non-RH-centric // and getBugzillaName should work everywhere same (even on bugzilla.mozilla.org, // if that makes sense?) RHBugzillaPage.prototype.addLogRecord = function() { let rec = {}; rec.date = new Date(); rec.bugId = this.bugNo; rec.title = this.title; let comment = jetpack.tabs.focused.contentWindow.prompt( "Enter comments for this comment").trim(); if (comment.length > 0) { rec.comment = comment; let recKey = this.getISODate(rec.date) + "+" + rec.bugId; let 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) { let that = this; // sort the records into temporary array let tmpArr = []; for ( let i in records) { if (records.hasOwnProperty(i)) { tmpArr.push( [ i, records[i] ]); } } tmpArr.sort(function(a, b) { return a[0] > b[0] ? 1 : -1; }); let currentDay = ""; // now print the array tmpArr .forEach(function(rec) { let x = rec[1]; let 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) { let doc = body.ownerDocument; this.timeSheetRecordsPrinter(body, myStorage.logs); }; // ///////////////////////////////////////////////////////////////////////////// let callback = function(doc) { if (config.gJSONData.configData.objectStyle = "RH") { let curPage = new RHBugzillaPage(doc); } else if (config.gJSONData.configData.objectStyle = "MoFo") { let curPage = new MozillaBugzilla(doc); } }; let config = {}; config.matches = [ "https://bugzilla.redhat.com/show_bug.cgi", "https://bugzilla.mozilla.org/show_bug.cgi" ]; hlpr.loadJSON(jetpack.storage.settings.JSONURL, function(parsedData) { console.log("jsonDataURL = " + jetpack.storage.settings.JSONURL); config.gJSONData = parsedData; // Get card translation table let keys = ""; for (let key in config.gJSONData) { keys += key + " "; } // console.log("configData = " + config.gJSONData.toSource()); console.log("keys = " + keys); if ("PCIIDsURL" in config.gJSONData.configData) { hlpr.loadJSON(config.gJSONData.configData.PCIIDsURL, function(response) { config.PCI_ID_Array = response; }); } }, this); jetpack.pageMods.add(callback, config);