// // This is the main JS file for QuickSearch. // // Derived from: // // * C. Begle's SimpleSearch tool: // http://www.mozilla.org/quality/help/simplesearch.html // http://www.mozilla.org/quality/help/bugreport.js // // * Jesse Ruderman's bugzilla search page: // http://www.cs.hmc.edu/~jruderma/s/bugz.html // // Created by // Andreas Franke <afranke@mathweb.org> // // Contributors: // Stephen Lee <slee@uk.bnsmc.com> // Use no_result variable to avoid problems with "undefined" on some browsers var no_result="---"; // do_unshift(l, s) is equivalent to l.unshift(s), but some browsers do not // support the built-in function. function do_unshift(l, s) { l.length = l.length + 1; for (var i=l.length-1; i>0; i--) { l[i] = l[i-1]; } l[0] = s; return l.length; } // do_shift(l) is equivalent to l.shift(s), but some browsers do not // support the built-in function. function do_shift(l) { var l0=l[0]; for (var i=0; i<l.length-1; i++) { l[i] = l[i+1]; } l.length = l.length - 1; return l0; } function go_to (url) { if ( typeof sidebar != "undefined" && sidebar == 1 ) { load_relative_url(url); } else { document.location.href = url; } } function map(l, f) { var l1 = new Array(); for (var i=0; i<l.length; i++) { l1[i] = f(l[i]); } return l1; } function isPrefix(s1, s2) { return (s1.length <= s2.length) && (s1 == s2.substring(0,s1.length)) } function member(s, l) { for (var i=0; i<l.length; i++) { if (l[i] == s) return true; } return false; } function add(s, l) { if (! member(s, l)) { do_unshift(l,s); } } function addAll(l1, l2) { for (var i=0; i<l1.length; i++) { add(l1[i],l2); } } function isSubset (l1, l2) { return (l1.length == 0) || (member(l1[0],l2) && subset(l1.slice(1),l2)); } // fields var f1 = new Array(); var f2 = new Array(); function add_mapping(from,to) { f1[f1.length] = from; f2[f2.length] = to; } // Status, Resolution, Platform, OS, Priority, Severity add_mapping("status", "bug_status"); add_mapping("resolution", "resolution"); // no change add_mapping("platform", "rep_platform"); add_mapping("os", "op_sys"); add_mapping("opsys", "op_sys"); add_mapping("priority", "priority"); // no change add_mapping("pri", "priority"); add_mapping("severity", "bug_severity"); add_mapping("sev", "bug_severity"); // People: AssignedTo, Reporter, QA Contact, CC, Added comment (?) add_mapping("owner", "assigned_to"); add_mapping("assignee", "assigned_to"); add_mapping("assignedto", "assigned_to"); add_mapping("reporter", "reporter"); // no change add_mapping("rep", "reporter"); add_mapping("qa", "qa_contact"); add_mapping("qacontact", "qa_contact"); add_mapping("cc", "cc"); // no change // Product, Version, Component, Target Milestone add_mapping("product", "product"); // no change add_mapping("prod", "product"); add_mapping("version", "version"); // no change add_mapping("ver", "version"); add_mapping("component", "component"); // no change add_mapping("comp", "component"); add_mapping("milestone", "target_milestone"); add_mapping("target", "target_milestone"); add_mapping("targetmilestone", "target_milestone"); // Summary, Description, URL, Status whiteboard, Keywords add_mapping("summary", "short_desc"); add_mapping("shortdesc", "short_desc"); add_mapping("desc", "longdesc"); add_mapping("description", "longdesc"); //add_mapping("comment", "longdesc"); // ??? // reserve "comment" for "added comment" email search? add_mapping("longdesc", "longdesc"); add_mapping("url", "bug_file_loc"); add_mapping("whiteboard", "status_whiteboard"); add_mapping("statuswhiteboard", "status_whiteboard"); add_mapping("sw", "status_whiteboard"); add_mapping("keywords", "keywords"); // no change add_mapping("kw", "keywords"); // Attachments add_mapping("attachment", "attachments.description"); add_mapping("attachmentdesc", "attachments.description"); add_mapping("attachdesc", "attachments.description"); add_mapping("attachmentdata", "attachments.thedata"); add_mapping("attachdata", "attachments.thedata"); add_mapping("attachmentmimetype", "attachments.mimetype"); add_mapping("attachmimetype", "attachments.mimetype"); // disabled because of bug 30823: // "BugsThisDependsOn" --> "dependson" // "OtherBugsDependingOnThis"--> "blocked" //add_mapping("dependson", "dependson"); //add_mapping("blocked", "blocked"); // Substring search doesn't make much sense for the following fields: // "Attachment is patch" --> "attachments.ispatch" // "Last changed date" --> "delta_ts" // "Days since bug changed" --> "(to_days(now()) - to_days(bugs.delta_ts))" //"groupset" //"everconfirmed" //"bug","bugid","bugno" --> "bug_id" // "votes" --> "votes" // "votes>5", "votes>=5", "votes=>5" works now, see below // "votes:5" is interpreted as "votes>=5" function findIndex(array,value) { for (var i=0; i<array.length; i++) if (array[i] == value) return i; return -1; } function mapField(fieldname) { var i = findIndex(f1,fieldname); if (i >= 0) return f2[i]; return no_result; } // `keywords' is defined externally function is_keyword(s) { return member(s, keywords); } // `platforms' is defined externally function is_platform(str) { return member (str.toLowerCase(),platforms); } // `severities' is defined externally function is_severity(str) { return member(str.toLowerCase(),severities); } // `product_exceptions' is defined externally function match_product(str) { var s = str.toLowerCase(); return (s.length > 2) && (! member(s,product_exceptions)); } // `component_exceptions are defined externally function match_component(str) { var s = str.toLowerCase(); return (s.length > 2) && (! member(s,component_exceptions)); } var status_and_resolution = ""; // for pretty debug output only; these vars var charts = ""; // always hold the data from the last query // derived from http://www.mozilla.org/quality/help/bugreport.js function make_chart(expr, field, type, value) { charts += "<tr>" + "<td><tt>" + expr + "</tt></td>" + "<td><tt>" + field + "</tt></td>" + "<td><tt>" + type + "</tt></td>" + "<td><tt>" + value + "</tt></td>" + "</tr>"; return "&field" + expr + "=" + field + "&type" + expr + "=" + type + "&value" + expr + "=" + escape(value).replace(/[+]/g,"%2B"); } // returns true if at least one of comparelist had the prefix, false otherwise function addPrefixMatches(prefix, comparelist, resultlist) { var foundMatch = false; for (var i=0; i<comparelist.length; i++) { if (isPrefix(prefix,comparelist[i])) { foundMatch = true; add(comparelist[i],resultlist); } } return foundMatch; } function prefixesNotFoundError(prefixes,statusValues,resolutionValues) { var txt; if (prefixes.length == 1) { txt = "is not a prefix "; } else { txt = "are not prefixes "; } alert(prefixes + "\n" + txt + "of one of these status or resolution values:\n" + statusValues + "\n" + resolutionValues + "\n"); } function make_query_URL(url, input, searchLong) { status_and_resolution = ""; charts = ""; // declare all variables used in this function var searchURL = url; // bugzilla + "buglist.cgi" (or "query.cgi") var abort = false; // global flag, checked upon return var i,j,k,l; // index counters used in 'for' loops var parts,input2; // escape "quoted" parts of input var word; // array of words // (space-separated parts of input2) var alternative; // array of parts of an element of 'word' // (separated by '|', sometimes by comma) var comma_separated_words; // array of parts of an element of 'alternative' var w; // current element of one of these arrays: // word, alternative, comma_separated_words var w0; // first element of 'word' var prefixes; // comma-separated parts of w0 // (prefixes of status/resolution values) var expr; // used for 'priority' support var n,separator; // used for 'votes' support var colon_separated_parts, fields,values,field; // used for generic fields:values notation var chart,and,or; // counters used in add_chart var negation; // boolean flag used in add_chart // `statuses_open' and `statuses_resolved' are defined externally var statusOpen = statuses_open; var statusResolved = statuses_resolved; var statusAll = statusOpen.concat(statusResolved); // `resolutions' is defined externally var bug_status = statusOpen.slice().reverse(); //reverse is just cosmetic var resolution = new Array(); // escape everything between quotes: "foo bar" --> "foo%20bar" parts = input.split('"'); if ((parts.length % 2) != 1) { alert('Unterminated quote'); abort = true; return no_result; } for (i=1; i<parts.length; i+=2) { parts[i] = escape(parts[i]); } input2 = parts.join('"'); // abort if there are still brackets if (input2.match(/[(]|[\)]/)) { alert('Brackets (...) are not supported.\n' + 'Use quotes "..." for values that contain special characters.'); abort = true; return no_result; } // translate " AND "," OR "," NOT " to space,comma,dash input2 = input2.replace(/[\s]+AND[\s]+/g," "); input2 = input2.replace(/[\s]+OR[\s]+/g,"|"); input2 = input2.replace(/[\s]+NOT[\s]+/g," -"); // now split into words at space positions word = input2.split(/[\s]+/); // determine bug_status and resolution // the first word may contain relevant info // This function matches the given prefixes against the given statuses and // resolutions. Matched statuses are added to bug_status, matched // resolutions are added to resolution. Returns true iff some matches // were found for at least one of the given prefixes. function matchPrefixes(prefixes,statuses,resolutions) { var failedPrefixes = new Array(); var foundMatch = false; for (var j=0; j<prefixes.length; j++) { var ok1 = addPrefixMatches(prefixes[j],statuses,bug_status); var ok2 = addPrefixMatches(prefixes[j],resolutions,resolution); if ((! ok1) && (! ok2)) { add(prefixes[j],failedPrefixes); } else { foundMatch = true; } } //report an error if some (but not all) prefixes didn't match anything if (foundMatch && (failedPrefixes.length > 0)) { prefixesNotFoundError(failedPrefixes,statuses,resolutions); abort = true; } return foundMatch; } if (word[0] == "ALL") { // special case: search for bugs regardless of status addAll(statusResolved,bug_status); do_shift(word); } else if (word[0] == "OPEN") { // special case: search for open bugs only do_shift(word); } else if (word[0].match("^[+][A-Z]+(,[A-Z]+)*$")) { // e.g. +DUP,FIX w0 = do_shift(word); prefixes = w0.substring(1).split(","); if (! matchPrefixes(prefixes,statusResolved,resolutions)) { do_unshift(word,w0); } } else if (word[0].match("^[A-Z]+(,[A-Z]+)*$")) { // e.g. NEW,ASSI,REOP,FIX bug_status = new Array(); // reset w0 = do_shift(word); prefixes = w0.split(","); if (! matchPrefixes(prefixes,statusAll,resolutions)) { do_unshift(word,w0); bug_status = statusOpen.reverse(); //reset to default bug_status } } else { // default case: // search for unresolved bugs only // uncomment this to include duplicate bugs in the search // add("DUPLICATE",resolution); } if (resolution.length > 0) { resolution = resolution.reverse(); do_unshift(resolution,"---"); addAll(statusResolved,bug_status); } bug_status = bug_status.reverse(); bug_status = map(bug_status,escape); searchURL += "?bug_status=" + bug_status.join("&bug_status="); status_and_resolution += 'Status: <tt>'+bug_status+'</tt>'; if (resolution.length > 0) { resolution = map(resolution,escape); searchURL += "&resolution=" + resolution.join("&resolution="); status_and_resolution += '<br>'+'Resolution: <tt>'+resolution+'</tt>'; } // end of bug_status & resolution stuff chart = 0; and = 0; or = 0; negation = false; function negate_comparison_type(type) { switch(type) { case "substring": return "notsubstring"; case "anywords": return "nowords"; case "regexp": return "notregexp"; default: // e.g. "greaterthan" alert("Can't negate comparison type: `" + type + "'"); abort = true; return "dummy"; } } function add_chart(field,type,value) { // undo escaping for value: '"foo%20bar"' --> 'foo bar' var parts = value.split('"'); if ((parts.length % 2) != 1) { alert('Internal error: unescaping failure'); abort = true; } for (var i=1; i<parts.length; i+=2) { parts[i] = unescape(parts[i]); } var value2 = parts.join(''); // negate type if negation is set var type2 = type; if (negation) { type2 = negate_comparison_type(type2); } searchURL += make_chart(chart+"-"+and+"-"+or,field,type2,value2); or++; if (negation) { and++; or=0; } } for (i=0; i<word.length; i++, chart++) { w = word[i]; negation = false; if (w.charAt(0) == "-") { negation = true; w = w.substring(1); } switch (w.charAt(0)) { case "+": alternative = w.substring(1).split(/[|,]/); for (j=0; j<alternative.length; j++) add_chart("short_desc","substring",alternative[j]); break; case "#": alternative = w.substring(1).replace(/[|,]/g," "); add_chart("short_desc","anywords",alternative); if (searchLong) add_chart("longdesc","anywords",alternative); break; case ":": alternative = w.substring(1).split(","); for (j=0; j<alternative.length; j++) { add_chart("product","substring",alternative[j]); add_chart("component","substring",alternative[j]); } break; case "@": alternative = w.substring(1).split(","); for (j=0; j<alternative.length; j++) add_chart("assigned_to","substring",alternative[j]); break; case "[": add_chart("short_desc","substring",w); add_chart("status_whiteboard","substring",w); break; case "!": add_chart("keywords","anywords",w.substring(1)); break; default: alternative=w.split("|"); for (j=0; j<alternative.length; j++) { w=alternative[j]; // votes:xx ("at least xx votes") if (w.match("^votes[:][0-9]+$")) { n = w.split(/[:]/)[1]; add_chart("votes","greaterthan",String(n-1)); continue; } // generic field1,field2,field3:value1,value2 notation if (w.match("^[^:]+[:][^:\/][^:]*$")) { colon_separated_parts = w.split(":"); fields = colon_separated_parts[0].split(/[,]+/); values = colon_separated_parts[1].split(/[,]+/); for (k=0; k<fields.length; k++) { field = mapField(fields[k]); if (field == no_result) { alert("`"+fields[k]+"'"+ " is not a valid field name."); abort = true; return no_result; } else { for (l=0; l<values.length; l++) { add_chart(field,"substring",values[l]); } } } continue; } comma_separated_words=w.split(/[,]+/); for (k=0; k<comma_separated_words.length; k++) { w=comma_separated_words[k]; // platform if (is_platform(w)) { add_chart("rep_platform","substring",w); continue; } // priority if (w.match("^[pP][1-5](,[pP]?[1-5])*$")) { expr = "["+w.replace(/[p,]/g,"")+"]"; add_chart("priority","regexp",expr); continue; } if (w.match("^[pP][1-5]-[1-5]$")) { expr = "["+w.substring(1)+"]"; add_chart("priority","regexp",expr); continue; } // severity if (is_severity(w)) { add_chart("bug_severity","substring",w); continue; } // votes>xx if (w.match("^votes>[0-9]+$")) { n = w.split(">")[1]; add_chart("votes","greaterthan",n); continue; } // votes>=xx, votes=>xx if (w.match("^votes(>=|=>)[0-9]+$")) { separator = w.match("^votes(>=|=>)[0-9]+$")[1]; n = w.split(separator)[1]; add_chart("votes","greaterthan",String(n-1)); continue; } // really default case if (match_product(w)) { add_chart("product","substring",w); } if (match_component(w)) { add_chart("component","substring",w); } if (is_keyword(w)) { add_chart("keywords","substring",w); if (w.length > 2) { add_chart("short_desc","substring",w); add_chart("status_whiteboard","substring",w); } } else { add_chart("short_desc","substring",w); add_chart("status_whiteboard","substring",w); } if (searchLong) add_chart("longdesc","substring",w); // URL field (for IP addrs, host.names, scheme://urls) if (w.match(/[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) || w.match(/^[A-Za-z]+([.][A-Za-z]+)+/) || w.match(/[:][\/][\/]/) || w.match(/localhost/) || w.match(/mailto[:]?/) // || w.match(/[A-Za-z]+[:][0-9]+/) //host:port ) add_chart("bug_file_loc","substring",w); } } } and = 0; or = 0; } //searchURL += "&cmdtype=doit"; if (abort == false) { return searchURL; } else { return no_result; } } function unique_id () { return (new Date()).getTime(); } function ShowURL(mode,input) { var searchURL = make_query_URL(bugzilla+"buglist.cgi", input, false); if (searchURL != no_result) { var pieces = searchURL.replace(/[\?]/g,"\n?").replace(/[\&]/g,"\n&"); if (mode == "alert") { alert(pieces); } else { var table = "<table border=1>" + "<thead>" + "<tr>" + "<th>Chart-And-Or</th>" + "<th>Field</th>" + "<th>Type</th>" + "<th>Value</th>" + "</tr>" + "</thead>" + "<tbody>" + charts + "</tbody>" + "</table>"; var html = '<html>' + '<head>' + '<title>' + input + '</title>' + '</head>' + '<body>' + '<a href="' + searchURL + '">' + 'Submit Query' + '</a>' + '<p>' + status_and_resolution + '<p>' + table + '<pre>' + pieces.replace(/[\n]/g,"<br>") + '</pre>' + '</body>' + '</html>'; var w = window.open("","preview_"+unique_id()); w.document.write(html); w.document.close(); } } } // // new interface: // searchLong is a boolean now (not a checkbox/radiobutton) // function Search(url, input, searchLong) { var inputstring = new String(input); var word = inputstring.split(/[\s]+/); // Check for empty input if ( word.length == 1 && word[0] == "" ) return; // Check for potential Bugzilla-busting intensive queries if ((searchLong!=false) && word.length > 4) { var message = "Searching Descriptions for more than four words " + "will take a very long time indeed. Please choose " + "no more than four keywords for your query."; alert(message); return; } var searchURL = make_query_URL(url, inputstring, searchLong); if (searchURL != no_result) { go_to(searchURL); //window.open(searchURL, "other" ); } else { return; } } // // original interface, untested // //function SearchForBugs (input, searchRadio) { // if (searchRadio[0].checked) { // return Search(bugzilla + "buglist.cgi", input, false); // } else { // return Search(bugzilla + "buglist.cgi", input, true); // } //} // derived from http://www.cs.hmc.edu/~jruderma/s/bugz.html // QuickSearch combines lookup-by-bug-number and search // in a single textbox. // // type nothing: // --> go to bugzilla front page // type a number: // --> go to that bug number // type several numbers, separated by commas: // --> go to a buglist of just those bug numbers // type anything else: // --> search summary, product, component, keywords, status whiteboard // (and URL if it's an IP address, a host.name, or an absolute://URL) function QuickSearch (input) { //remove leading and trailing whitespace input = input.replace(/^[\s]+/,"").replace(/[\s]+$/,""); if (input == "") { //once this _is_ on http://bugzilla.mozilla.org, it should just return; go_to(bugzilla); } else if (input.match(/^[0-9, ]*$/)) { if (input.indexOf(",") == -1) { // only _one_ bug number --> show_bug go_to(bugzilla+"show_bug.cgi?id="+escape(input)); } else { // comma-separated bug numbers --> buglist go_to(bugzilla+"buglist.cgi?bug_id="+escape(input) + "&bugidtype=include&order=bugs.bug_id"); } } else { Search(bugzilla+"buglist.cgi",input,false); } return; } function LoadQuery(input) { //remove leading and trailing whitespace input = input.replace(/^[\s]+/,"").replace(/[\s]+$/,""); Search(bugzilla+"query.cgi",input,false); return; }