aboutsummaryrefslogblamecommitdiffstats
path: root/bugzillaBugTriage.js
blob: 210d41c29848840b58623902fe692231568a8115 (plain) (tree)
1
2
3
4
5
6
7
8

                                                     
 

                                        
                                   
                                   
 




                                           
                              
                                                         
                                      
                                                             

                                                                



                                                                 


                                                                 

                                                                        


                   



                                                                                


                                      



                                           



                                       
        

                                    
        


                                     

                                  


                              


                                                                                  



                          
                            

                   

                              
                                            



                                                       
                                                                                          





                                                     
   
 
                             



                                
 
                                                                


                                                                                         
                       



                                                                           
                                              
                            

                                         

                                                                                              
                                   
         
                                    
                                                  




                          





                                                                 
                               

















                                                                   
                                


                                           


























                                                                                     
             







                                                                  


   
                                      
  

                                     
  
   



                                                           
 
                        

   
                                                                               
  
   
                                              
                                 


                                                            
                                                           


         


                                                                          
  


                                                      

               
   
                                            
                            
        
                                                                        
                                          



                                               

                                                                    






                                                                           

                                                             


                                                                
                                           
                                                     


                                                   

          







                                                                  
 

   


































                                                                                         
                                                                 









                                                    
                                                              
                                   
                                        

                                         
                                                                            

                                                                                          
                                                            


















                                                                                   
                                                                



                                                        
                                                       




                                      



                                                  
            
                                                                         




                                                                         







                                                               
                                                        


                                     












































































                                                                                         



























                                                                                              
                               


   

                                                                             

                                                 






                                                         


   








                                                                      
                                                                









                                            
                                                          
                     
                                    
                                                                                        










                                                                            
































































































































































                                                                                                

                                                     

                                                                         
               






























































































































                                                                                              

                                                                           
                                                               





                                                         

                                                             

                                                           




                                                        
 
                                   
                                          





                                                                                          

                                                                     


                                                                                         
                                   


                                       



                                                                                        
         
  
 

















































                                                                                            
                               
                                  
  
 

                   
                                                
       
                                        
// Released under the MIT/X11 license
// http://www.opensource.org/licenses/mit-license.php

jetpack.future.import("pageMods");
jetpack.future.import("storage.simple");
jetpack.future.import("selection");
jetpack.future.import("clipboard");

var RHColor = "#9E292B";
var FedoraColor = "#002867";
var RawhideColor = "#007700"; // or "green"
var RHITColor = "#660066";
var SalmonPink = "#FFE0B0";
var ReporterColor = "#FFFFA6";
var XMLRPCurl = "https://bugzilla.redhat.com/xmlrpc.cgi";
var myConfig = jetpack.storage.simple;
var badMIMEArray = ["application/octet-stream","text/x-log"];

//==============================================================
// CONFIGURE: The easiest method how to set up the configuration
// value is to uncomment the following line with proper URL as
// the second parameter. Then reload the bug page and comment out
// again.
// myConfig.JSONURL = "URL-somewhere-with-your-JSON";
var jsonDataURL = myConfig.JSONURL ? myConfig.JSONURL : 
    "http://mcepl.fedorapeople.org/scripts/BugZappers_data.json";
var PCIIDsURL = "http://mcepl.fedorapeople.org/scripts/drm_pciids.json";
//var debug = GM_getValue("debug",false);
var reqCounter = 0;
var msgStrs = {};

var CommentRe = new RegExp("^\\s*#");
var BlankLineRe = new RegExp("^\\s*$");
var ChipsetRE = new RegExp("^\\(--\\) ([A-Za-z]+)\\([0-9]?\\): Chipset: (.*)$");
var ATIgetIDRE = new RegExp("^.*\\(ChipID = 0x([0-9a-fA-F]+)\\).*$");

// For identification of graphics card
var manuChipStrs = [
    ["ATI Radeon", "ATI", "1002"],
    ["ATI Mobility Radeon", "ATI", "1002"],
    ["Intel Corporation", "INTEL", "8086"],
    ["NVIDIA", "NV", "10de"]
];
var backTranslateManufacturerPCIID = [{
        regexp: "ATI Technologies Inc",
        addr: "1002"
    }, {
        regexp: "Intel Corporation",
        addr: "8086"
    }, {
        regexp: "nVidia Corporation",
        addr: "10de"
}];
// Initialize data from remote URL
var XMLHTTPRequestDone = false;
var hashBugzillaName = [];
var hashBugzillaWholeURL = [];
var defAssigneeList = [];
var signatureFedoraString = "";
// TODO we should have an array SpecialFlags instead of multiple Boolean variables
var queryButtonAvailable = false;
var chipIDsGroupings = [];
var AddrArray = [];
var PCI_ID_Array = [];
var XorgLogAttList = [];
var XorgLogAttListIndex = 0;
var topRow = {};
var bottomRow = {};

// Get JSON configuration data
$.getJSON(jsonDataURL, function (response) {
	msgStrs = response.strings;
	signatureFedoraString = response.signature;
	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;
	chipIDsGroupings = response.chipIDsGroupings;
    topRow = response.topRow;
    bottomRow = response.bottomRow;
});

// Get card translation table
$.getJSON(PCIIDsURL,
    function (response) {
        PCI_ID_Array = response;
});

//==============================================================

/**
 * 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
 */
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 $.trim(chosenPair[0].addr);
	} else {
		return "";
	}
}

/**
 * Converts attributes value of the given list of elements to the
 *  Javascript list.
 *  @param list array of elements
 *  @return array of values
 */
valuesToList = function(list) {
	var outL = [];

	list.forEach(function (e, i, a) {
	    if (e.hasAttribute("value")) {
			outL.push(e.getAttribute("value").trim());
	    }
	});
	return outL;
}

/**
 * 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.
 */
isInList = function(mbr, list) {
	 return (list.indexOf(mbr) !== -1);
}

/**
 * This function creates a new anchor element and uses location properties (inherent)
 * to get the desired URL data. Some String operations are used (to normalize results
 * across browsers).
 * originally from http://snipplr.com/view.php?codeview&id=12659
 *
 * @param url String with URL
 * @return object with parameters set
 *
 */
function parseURL(url) {
    var a =  $('a',this.doc).get(0);
    a.href = url;
    return {
        source: url,
        protocol: a.protocol.replace(':',''),
        host: a.hostname,
        port: a.port,
        query: a.search,
        params: (function(){
            var ret = {},
                seg = a.search.replace(/^\?/,'').split('&'),
                len = seg.length, i = 0, s;
            for (;i<len;i++) {
                if (!seg[i]) { continue; }
                s = seg[i].split('=');
                ret[s[0]] = s[1];
            }
            return ret;
        })(),
        file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
        hash: a.hash.replace('#',''),
        path: a.pathname.replace(/^([^\/])/,'/$1'),
        relative: (a.href.match(/tp:\/\/[^\/]+(.+)/) || [,''])[1],
        segments: a.pathname.replace(/^\//,'').split('/')
    };
}

/**
 * Check for the presence of a keyword
 *
 * @param str string with the keyword
 * @return Boolean
 *
 */
bzPage.prototype.hasKeyword = function (str) {
	 var kwd = $.trim($('#keywords', this.doc).text());
	 return (new RegExp(str).test(kwd));
};

/* Bugzilla functions.*/

/**
 * Set background color of all comments made by reporter in ReporterColor color
 *
 */
bzPage.prototype.checkComments = function () {
    var reporter = this.reporter;
    $("#comments .bz_comment", this.doc).each(function (i) {
        var email = $(".vcard a", this).text();
        if (new RegExp(reporter).test(email)) {
            $(this).css("background-color", ReporterColor);
        }
    });
};

/**
 * 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
 *
 */
bzPage.prototype.setBranding = function () {
	var brandColor = "";
	
	if (new RegExp("Red Hat Enterprise Linux").test(this.product)) {
		if (this.its.length > 0) {
			brandColor = RHITColor;
		} else {
			brandColor = RHColor;
		}
	} else if (new RegExp("Fedora").test(this.product)) {
		if (new RegExp("rawhide", "i").test(this.version)) {
			brandColor = RawhideColor;
		} else {
			brandColor = FedoraColor;
		}
	}

	// Comment each of the following lines to get only partial branding
	$("body", this.doc).css("background", brandColor);
	$("#titles", this.doc).css("background", brandColor);

	 // Make background-color of the body of bug salmon pink
    // for security bugs.
	 if (this.hasKeyword("Security")) {
		  $("#bugzilla-body", this.doc).css({
		    'background-image' : 'none',
		    'background-color' : SalmonPink
		  });
	 }

	// we should make visible whether maintCCAddr is in CCList
	if (isInList(this.maintCCAddr, this.CCList)) {
		$("#cc_edit_area_showhide", this.doc).
		    css({ "color": "navy",
		        "font-weight": "bolder",
		        "text-decoration": "underline"});
	}
};

/**
 */
bzPage.prototype.groupIDs = function (manStr,cardStrID) {
    var outStr = 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
 */
bzPage.prototype.checkChipStringFromID = function (manufacturerNo,cardNo) {
    console.log("This is the card ID: " + cardNo + " manufactured by " + manufacturerNo);
    var soughtID = (manufacturerNo+","+cardNo).toUpperCase();
    var outList = PCI_ID_Array[soughtID];
    console.log("nalezeno = " + outList.toSource());
    if (outList) {
        return outList;
    } else {
        return "";
    }
};

/**
 * 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
 */
bzPage.prototype.fillInWhiteBoard = function (iLine, driverStr) {
    var outStr = "";
    var cardIDStr = "";
    var cardIDArr = [];
    
    console.log("driverStr = " + driverStr);
    console.log("iLine: " + iLine);

    chipSwitchboard:
    if (driverStr == "RADEON") {
        var cardID = iLine.replace(ATIgetIDRE,"$1");
        cardIDArr = this.checkChipStringFromID("1002",cardID);
        if (cardIDArr.length > 0) {
            cardIDStr = cardIDArr[0];	
            if (cardIDArr[1]) {
                optionStr = cardIDArr[1];
                outStr = this.groupIDs(driverStr,cardIDStr)+"/" + optionStr;
                console.log("cardIDArr = " + cardIDArr.toSource() + ", outStr = "+outStr);
            } else {
                outStr = this.groupIDs(driverStr,cardIDStr);
                optionStr = "";
            }
            console.log("found IDs: " + cardIDStr + "," + optionStr);
        } else {
            outStr = "**** FULLSTRING: " + iLine;
        }
    } else {
    // Intel Corporation, NVIDIA
        cardIDArr = manuChipStrs.filter(function (el, ind, arr) {
            return RegExp(el[0],"i").test(iLine);
        });
        console.log("cardIDArr = " + cardIDArr.toSource());
        if (cardIDArr && (cardIDArr.length > 0)) {
            cardIDArr = cardIDArr[0];
        } else {
            outStr = iLine;
            break chipSwitchboard;
        }
        // cardIDArr [0] = RE, [1] = ("RADEON","INTEL","NOUVEAU"), [2] = manu PCIID
        iLine = $.trim(iLine.replace(RegExp(cardIDArr[0],"i")));
        // FIXME is this necessary? Let's try without it
        // outStr = iLine.replace(/^\W*(\w*).*$/,"$1");
        // nVidia developers opted-out from grouping
        if (driverStr == "INTEL") {
            outStr = this.groupIDs(cardIDArr[1],iLine);
        } else {
            outStr = iLine;
        }
    }
    console.log("result = " + outStr);
    var whiteboardInput = $("#status_whiteboard");
    var attachedText = $.trim("card_"+outStr);
    if (whiteboardInput.text().length == 0) {
        whiteboardInput.text(attachedText);
    } else {
        whiteboardInput.text(whiteboardInput.text()+", " + attachedText);
    }
};

// 
bzPage.prototype.fillInAddButton = function (interestLine,driverString) {
    var newButt = this.originalButton.clone(true);
	var whiteboardInput = $("#status_whiteboard");

    newButt.attr("id","chipmagic");
    newButt.attr("value","Fill In");
    console.log("fillInAddButton newButt = " + newButt.html());
    newButt.get(0).setAttribute("type","button");
    newButt.click(function (evt) {
	    fillInWhiteBoard(interestLine,driverString);
    });
    whiteboardInput.append(newButt);
    whiteboardInput.before("\u00A0");
};

/**
 * Recursive function to run Get attached Xorg.0.log, parse it and find the value of chip
 * @return None
 *
 */
bzPage.prototype.fillInChipMagicProcessAtts = function (ret) {    
    if (ret) {
        if (ret.status != 200) {
            alert([ret.status,ret.statusText,ret.responseHeaders,
                ret.responseText]);
            throw "XMLHTTPRequest got return code " + ret.status;
        }
        console.log('fetched ' + ret.finalUrl);
        var interestingLineArr = ret.responseText.split("\n").filter(function (v,i,a) {
            return ChipsetRE.test(v);
        });
        console.log("interestingLineArr = " + interestingLineArr.toSource());
        if (interestingLineArr.length >0) {
            // Process and exit
            // .replace(ChipsetRE,"$1\t$2").split("\t")
            var interestingArray = ChipsetRE.exec(interestingLineArr[0]);
            console.log("interesting array = " + interestingArray.toSource());
            interestingLine = trim(interestingArray[2].replace(/[\s"]+/g," "));
            console.log("interesting line = " + interestingLine);
            fillInAddButton(interestingLine,interestingArray[1].toUpperCase());
            console.log("XMLHTTPRequest done!");
            return;
        }
    }

    if (XorgLogAttList[XorgLogAttListIndex]) {
        var XorgLogAttID = XorgLogAttList[XorgLogAttListIndex][1];
        var attURL = "https://bugzilla.redhat.com/attachment.cgi?id="+XorgLogAttID;
        GM_xmlhttpRequest({
            method: 'GET',
            url: attURL,
            headers: {
	            'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey getXorgLog',
	            'Accept': 'text/plain',
	            'Content-type': 'text/xml'
            },
            onload:fillInChipMagicProcessAtts
        });
        XorgLogAttListIndex++;
    } else {
        console.log("No more Xorg.0.log attachments!");
    }
};

/**
 * 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,
 * so that slow XMLHTTPRequest is done in advance.
 *
 * @return None
 */
bzPage.prototype.fillInChipMagic = function () {
    var XorgLogURL = "";
    var XorgLogAttID = "";
    var XorgLogFound = false;

    // Find out Xorg.0.log attachment URL
    XorgLogAttList = this.attachments.filter(function (value, index, array) {
        // Xorg.0.log must be text, otherwise we cannot parse it
        return (RegExp("[xX].*log").test(value[0]) && /text/.test(value[2]));
    });
    console.log("XorgLogAttList = " + XorgLogAttList.toSource());
    if (XorgLogAttList.length == 0) {
        console.log("No Xorg.0.log attachments found.")
        return;
    }
    fillInChipMagicProcessAtts();    
};

/**
 * 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
 *
 * old
 * long_desc=Xpress%20200&bug_status=NEW&bug_status=ASSIGNED
 * new
 * long_desc_type=substring&long_desc=Xpress%20200&bug_status=NEW&bug_status=ASSIGNED
 */
bzPage.prototype.queryInNewTab = function(text,component,product) {
	// Optional parameter
	console.log("text = " + text + ", component = " + component + ", product " + product);
	if (product == null) {
		product = "Fedora";
	}
	var url = "https://bugzilla.redhat.com/buglist.cgi?query_format=advanced";
	if (product) {
		url += "&product="+product;
	}
	if (component) {
		url += "&component="+component;
	}
	if (text) {
		url += "&long_desc_type=substring&long_desc="+ text.replace(" ","%20");
	}
	console.log("queryInNewTab: url = " + url);
	jetpack.tabs.open(url);
}

/**
 * Get the text to search for and prepare other things for the real executive
 * function this.queryInNewTab, and run it.
 */
bzPage.prototype.queryForSelection = function() {
	var text = $.trim(jetpack.selection.text);
	if (text.length < 1) {
		text = jetpack.clipboard.get();
	};
	if (text.length > 0) {
		this.queryInNewTab(text, this.component);
	}
}

/**
 * Parse the row with the attachment
 *
 * @param <tr> DOM element to be parsed
 * @return array with string name of the attachment,
					 integer its id number,
					 string of MIME type,
					 integer of size in kilobytes,
					 and the whole element itself
 */
bzPage.prototype.parseAttachmentLine = function (index,inElem) {
	var MIMEtype = String();
	var size = Number();
	inElem.normalize();

    // Skip over obsolete attachments
    if ($(".bz_obsolete",inElem).length>0) {
        return;
    }

	// getting name of the attachment
	var attName = $.trim($("b:first", inElem).text());
	// getting id
	var aHrefs = $("a", inElem);
	var id = parseInt(aHrefs.attr("href").replace(/^.*attachment.cgi\?id=/, ""),10);

	//getting MIME type and size
	var stringArray = $(".bz_attach_extra_info >*:first",inElem).text().
	    replace(/[\n ()]+/g," ").trim().split(", ");
	size = parseInt(stringArray[0],10);
	MIMEtype = stringArray[1];

	return([attName,id,MIMEtype,size,inElem]);
};

/**
 * Select option with given label on the <SELECT> element with given id.
 *
 * Also execute change HTMLEvent, so that the form behaves accordingly.
 *
 * @param id
 * @param label
 * @return none
 */
bzPage.prototype.selectOption = function(id,label) {
//	var theOption = $("#"+id+" option[value='"+label+"']",this.doc);
//    theOption.select();
    var selectElement = $(this.doc).get(0).getElementById(id);
	var values = selectElement.options;
	for (var i = 0; i < values.length; i++) {
		values[i].normalize();
		if (values[i].text.search(label) != -1) {
			values[i].selected = true;
			var intEvent = $(this.doc).get(0).createEvent("HTMLEvents");
			intEvent.initEvent("change", true, true);
			selectElement.dispatchEvent(intEvent);
			break;
		}
	}
}

/**
 * Check for the presence of a keyword
 *
 * @param str string with the keyword
 * @return Boolean
 *
 */
bzPage.prototype.hasKeyword = function(str) {
	 var kwd = $.trim($('#keywords',this.doc).text());
	 return (RegExp(str).test(kwd));
}

/**
 * Add XGL  to the CC list
 *
 * @param evt event which made this function active
 * @return none
 */
bzPage.prototype.changeOwnerHandler = function(evt) {
	/** Take care that when changing assignment of the bug,
	 * current owner is added to CC list.
	 * Switch off setting to the default assignee
	 */
	if (!isInList(this.maintCCAddr, CCList)) {
	    $("#newcc",this.doc).text(this.maintCCAddr);
	}
	$("#set_default_assignee",this.doc).removeAttr("checked");
	this.selectOption("bug_status", "ASSIGNED");
}

/**
 * Set the bug to NEEDINFO state
 *
 * Working function.
 * @return none
 */
bzPage.prototype.setNeedinfoReporter = function() {
	$("#needinfo",this.doc).click();
	this.selectOption("needinfo_role", "reporter");
}

/**
 * Add text to the comment.
 * @param string2BAdded string to be added to the comment box
 *
 * @return none
 */
bzPage.prototype.addTextToComment = function(string2BAdded) {
	var commentTextarea = $("#comment",this.doc);

	// don't remove the current content of the comment box,
	// just behave accordingly
	if (commentTextarea.text().length > 0) {
		commentTextarea.text(commentTextarea.text() + "\n\n");
	}
	commentTextarea.text(commentTextarea.text() + string2BAdded);
}

/**
 * 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
 */
bzPage.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
 */
bzPage.prototype.getWholeURL = function(selectValue,bugID) {
	var returnURL = "";
	if (hashBugzillaWholeURL[selectValue]) {
		returnURL = hashBugzillaWholeURL[selectValue]+bugID;
	} else {
		returnURL = "";
	}
	return returnURL;
}

/**
 * Add information about the upstream bug upstream, and closing it.
 * @param evt event which called this handler
 *
 * @return none
 */
bzPage.prototype.addClosingUpstream = function() {
	var refs = $("#external_bugs_table tr",this.doc);
	// that's a bad id, if there is a one. :)
	var inputBox = $("#inputbox",this.doc);
	var externalBugID = 0;
	var wholeURL = "";

	// Fix missing ID on the external_id SELECT
	$("select[name='external_id']:first",this.doc).attr("id","external_id");

	if (inputBox.text().match(/^http.*/)) {
		var IBURLArr = parseURL(inputBox.text());
		console.log("IBURLArr = " + IBURLArr.toSource());
		externalBugID = parseInt(IBURLArr.params["id"]);
		inputBox.text(externalBugID);
		var bugzillaName = getBugzillaName(IBURLArr.host);
		this.selectOption("external_id", bugzillaName);
		console.log("externalBugID = " + externalBugID);
	} else if (!isNaN(inputBox.text())) {
		externalBugID = parseInt(inputBox.text());
		var bugzillaID = $("#external_id").text();
		wholeURL = 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 ((refs.length > 2) || (externalBugID > 0)) {
		this.addTextToComment(msgStrs['sentUpstreamString'].replace("§§§",wholeURL));
		this.selectOption("bug_status", "CLOSED");
		this.selectOption("resolution", "UPSTREAM");
	} else {
		alert("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
 */
bzPage.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']);
	}
}

/**
 * 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
 */
bzPage.prototype.generalPurposeCureForAllDisease = function
    (addString,nextState,secondParameter) {
		if (addString.length >0) {
 			this.addTextToComment(addString);
 		}

 		if (nextState == "CLOSED") {
 			if (secondParameter == "UPSTREAM") {
 				this.addClosingUpstream();
 			} 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") {
 			if (!isInList(this.maintCCAddr, this.CCList)) {
 			    $("#newcc",this.doc).text(this.maintCCAddr);
 			}
 			this.selectOption("bug_status", nextState);
 		} else if (nextState == "QUERYSEL") {
 			this.queryForSelection();
 		} else if (nextState == "SETDEFASS") {
			if (secondParameter.length > 0) {
			    console.log("default assignee = " + secondParameter);
			    //$("#bz_assignee_edit_action",this.doc).click();
				//$("#assigned_to", this.doc).text(secondParameter);
			}
 		} else if (nextState.length >0) {
 			this.selectOption("bug_status", nextState);
 		}

 		if (secondParameter == "ADDSELFCC") {
 			$("#addselfcc", this.doc).attr("checked","checked");
 		} else if (secondParameter == "NODEFAULTASSIGNEE") {
 			$("#set_default_assignee", this.doc).removeAttr("checked");
 		}
}

/**
 * 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 with the button to be copied from
 * @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 bool optional whether the button should submit whole page
 *				 (default true)
 *
 * @return none
 */
bzPage.prototype.addNewButton = function (originalLocation,newId,newLabel,
        commentString,nState,secPar,doSubmit) {
    var that = this;
	var commStr = "";
	if (doSubmit === null) { // missing optional argument
		doSubmit = true;
	}
	if (msgStrs[commentString]) {
		commStr = msgStrs[commentString];
	}
	var newButton = this.originalButton.clone(true).attr({
            "id":newId,
            "value":newLabel
    	}).click(function (evt) {
		    that.generalPurposeCureForAllDisease(commStr,nState, secPar);
	    });
	$(originalLocation, this.doc).before(newButton).before("\u00A0");
	if (!doSubmit) {
		newButton.get(0).setAttribute("type","button");
	}
}

/**
 * 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) {
	var kwd = $('#keywords',this.doc);
	if (kwd.text().length == 0) {
		kwd.text(str);
	}else{
		kwd.text(kwd.text() + ", " + str);
	}
}

/**
 * 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
 */
bzPage.prototype.buildButtons = function (above,below) {
	//Generate a list of <input> elements in the page
	var IBLast = $("#commit_top", this.doc);
	this.addNewButton(IBLast,"changeOwnerbtn","reASSIGN",
			"","ASSIGNED","NODEFAULTASSIGNEE");

	// THE MAIN BUTTON ROWS
	var commentBox = $("#comment", this.doc);
	commentBox.before("<br>");
	this.generateToolBar(commentBox.prev(),above);
	this.generateToolBar(this.originalButton,below);

	if (queryButtonAvailable) {
		// Add query search button
		// Apparently there is a bug in jQuery, we have to use plain DOM
		//newPosition = $("#newcommentprivacy ~ br", this.doc);
		newPosition = $(this.doc.get(0).querySelector("#newcommentprivacy ~ br"));
		newPosition.css("border","solid blue");
	    this.addNewButton(newPosition,"newqueryintab","Query for string",
            "","QUERYSEL","",false);
	    }
	console.log("chipIDsGroupings = " + chipIDsGroupings.length);
	if ((chipIDsGroupings.length >0) && this.maintCCAddr == "xgl-maint@redhat.com") {
	    // Add find chip magic button
		if (!$("#status_whiteboard:contains('card_')", this.doc)) {
            this.fillInChipMagic();
		}
	}
	// Add setting default assignee
	if ((this.defaultAssignee.length > 0) && (this.defaultAssignee != this.owner)) {
	    this.addNewButton($("#bz_assignee_edit_container", this.doc),
	        "setdefaultassigneebutton","Def. Assignee",
            "","SETDEFASS",this.defaultAssignee,false);
	}
};

///////////////////////////////////////////////////////////////////////////////
function bzPage(doc) {
    this.doc = $(doc);
    this.originalButton = $("#commit", this.doc);
    var bugNoTitle = $.trim($("#title > p:first", this.doc).text());
    this.bugNo = new RegExp("[0-9]+").exec(bugNoTitle)[0]; 

    this.reporter = $("#bz_show_bug_column_2 > table .vcard:first > a",
        this.doc).attr("title");
    this.product   = $("#product option:selected:first", this.doc).text();
    this.component = $("#component option:selected:first", this.doc).text();
    this.version   = $("#version option:selected:first", this.doc).text();
    this.its       = $.trim($("#cf_issuetracker", this.doc).text());
    this.CCList    = $("#cc", this.doc).attr("value");
    this.defaultAssignee = filterByRegexp(defAssigneeList, this.component).toLowerCase();
    this.maintCCAddr = filterByRegexp(AddrArray,this.component).toLowerCase();
    
    this.attachments = $("#attachment_table tr",
        this.doc).slice(1,-1).map(this.parseAttachmentLine);
        
    var badAttachments = this.attachments.filter(function (a) {
        return (badMIMEArray.indexOf(a[2]) != -1);
    });

    if (badAttachments.length>0) {
    	console.log("we have " + badAttachments.length + " bad attachments.");
    	var titleElement = $(".bz_alias_short_desc_container:first").css("background-color",
    	    "olive").append($(this.createFixAllButton(badAttachments)));
    	badAttachments.forEach(function (x) {
            this.addTextLink(x);    	
    	});
    }

    // Take care of signature for Fedora bugzappers
    if (signatureFedoraString.length > 0) {
        // (or a form named "changeform")
        $("form:nth-child(2)", this.doc).submit(function () {
            var cmntText = $("#comment", this.doc);
            if ((signatureFedoraString.length > 0) &&
                    ($.trim(cmntText.text()).length > 0)) {
                cmntText.text($.trim(cmntText.text()) + signatureFedoraString);
            }
        });
	}

    this.setBranding();
    this.checkComments();
	this.buildButtons(topRow,bottomRow);
}

var callback = function (doc) {
    var curPage = new bzPage(doc);
};

var options = {};
options.matches = [
     "https://bugzilla.redhat.com/show_bug.cgi",
     ];
jetpack.pageMods.add(callback, options);