<!-- 1.0@bugzilla.org --> [%# The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Bug Tracking System. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): Chris Lahey <clahey@ximian.com> [javascript fixes] # Christian Reis <kiko@async.com.br> [javascript rewrite] # Gervase Markham <gerv@gerv.net> #%] [% INCLUDE global/header title = "Search for bugs" extra = " onLoad=\"selectProduct(document.forms['queryform']);\"" %] [%# Note: use Template comments and not JS ones here, to avoid bloating what we actually send to the browser %] <script language="JavaScript" type="text/javascript"> <!-- var first_load = true; [%# is this the first time we load the page? %] var last_sel = new Array(); [%# caches last selection %] var cpts = new Array(); var vers = new Array(); [% IF Param('usetargetmilestone') %] var tms = new Array(); [% END %] [%# Create three arrays of components, versions and target milestones, indexed # numerically according to the product they refer to. #%] [% n = 0 %] [% FOREACH p = product %] cpts[[% n %]] = [ [%- FOREACH item = componentsbyproduct.$p %]'[% item FILTER js %]', [%- END -%]]; vers[[% n %]] = [ [%- FOREACH item = versionsbyproduct.$p -%]'[% item FILTER js %]', [%- END -%]]; [% IF Param('usetargetmilestone') %] tms[[% n %]] = [ [%- FOREACH item = milestonesbyproduct.$p %]'[% item FILTER js %]', [%- END -%]]; [% END %] [% n = n+1 %] [% END %] [%# updateSelect(array, sel, target, merging) # # Adds to the target select object all elements in array that # correspond to the elements selected in source. # - array should be a array of arrays, indexed by number. the # array should contain the elements that correspond to that # product. # - sel is a list of selected items, either whole or a diff # depending on merging. # - target should be the target select object. # - merging (boolean) determines if we are mergine in a diff or # substituting the whole selection. a diff is used to optimize adding # selections. # # Example (compsel is a select form control) # # var components = Array(); # components[1] = [ 'ComponentA', 'ComponentB' ]; # components[2] = [ 'ComponentC', 'ComponentD' ]; # source = [ 2 ]; # updateSelect(components, source, compsel, 0, 0); # # would clear compsel and add 'ComponentC' and 'ComponentD' to it. # %] function updateSelect(array, sel, target, merging) { var i, item; [%# If we have no versions/components/milestones %] if (array.length < 1) { target.options.length = 0; return false; } if (merging) { [%# array merging/sorting in the case of multiple selections %] [%# merge in the current options with the first selection %] item = merge_arrays(array[sel[0]], target.options, 1); [%# merge the rest of the selection with the results %] for (i = 1 ; i < sel.length ; i++) { item = merge_arrays(array[sel[i]], item, 0); } } else if ( sel.length > 1 ) { [%# here we micro-optimize for two arrays to avoid merging with a null array %] item = merge_arrays(array[sel[0]],array[sel[1]], 0); [%# merge the arrays. not very good for multiple selections. %] for (i = 2; i < sel.length; i++) { item = merge_arrays(item, array[sel[i]], 0); } } else { [%# single item in selection, just get me the list %] item = array[sel[0]]; } [%# clear select %] target.options.length = 0; [%# load elements of list into select %] for (i = 0; i < item.length; i++) { target.options[i] = new Option(item[i], item[i]); } return true; } [%# Returns elements in a that are not in b. # NOT A REAL DIFF: does not check the reverse. # - a,b: arrays of values to be compare. %] function fake_diff_array(a, b) { var newsel = new Array(); var found = false; [%# do a boring array diff to see who's new %] for (var ia in a) { for (var ib in b) { if (a[ia] == b[ib]) { found = true; } } if (!found) { newsel[newsel.length] = a[ia]; } found = false; } return newsel; } [%# takes two arrays and sorts them by string, returning a new, sorted # array. the merge removes dupes, too. # - a, b: arrays to be merge. # - b_is_select: if true, then b is actually an optionitem and as # such we need to use item.value on it. %] function merge_arrays(a, b, b_is_select) { var pos_a = 0; var pos_b = 0; var ret = new Array(); var bitem, aitem; [%# iterate through both arrays and add the larger item to the return list. remove dupes, too. Use toLowerCase to provide case-insensitivity. %] while ((pos_a < a.length) && (pos_b < b.length)) { if (b_is_select) { bitem = b[pos_b].value; } else { bitem = b[pos_b]; } aitem = a[pos_a]; [%# smaller item in list a %] if (aitem.toLowerCase() < bitem.toLowerCase()) { ret[ret.length] = aitem; pos_a++; } else { [%# smaller item in list b %] if (aitem.toLowerCase() > bitem.toLowerCase()) { ret[ret.length] = bitem; pos_b++; } else { [%# list contents are equal, inc both counters. %] ret[ret.length] = aitem; pos_a++; pos_b++; } } } [%# catch leftovers here. these sections are ugly code-copying. %] if (pos_a < a.length) { for (; pos_a < a.length ; pos_a++) { ret[ret.length] = a[pos_a]; } } if (pos_b < b.length) { for (; pos_b < b.length; pos_b++) { if (b_is_select) { bitem = b[pos_b].value; } else { bitem = b[pos_b]; } ret[ret.length] = bitem; } } return ret; } [%# Returns an array of indexes or values from a select form control. # - control: select control from which to find selections # - findall: boolean, store all options when true or just the selected # indexes # - want_values: boolean; we store values when true and indexes when # false %] function getSelection(control, findall, want_values) { var ret = new Array(); if ((!findall) && (control.selectedIndex == -1)) { return ret; } for (var i=0; i<control.length; i++) { if (findall || control.options[i].selected) { ret[ret.length] = want_values ? control.options[i].value : i; } } return ret; } [%# Selects items in control that have index defined in sel # - control: SELECT control to be restored # - selnames: array of indexes in select form control %] function restoreSelection(control, selnames) { [%# right. this sucks. but I see no way to avoid going through the # list and comparing to the contents of the control. %] for (var j=0; j < selnames.length; j++) { for (var i=0; i < control.options.length; i++) { if (control.options[i].value == selnames[j]) { control.options[i].selected = true; } } } } [%# selectProduct reads the selection from f.product and updates # f.version, component and target_milestone accordingly. # - f: a form containing product, component, varsion and # target_milestone select boxes. # globals (3vil!): # - cpts, vers, tms: array of arrays, indexed by product name. the # subarrays contain a list of names to be fed to the respective # selectboxes. For bugzilla, these are generated with perl code # at page start. # - first_load: boolean, specifying if it is the first time we load # the query page. # - last_sel: saves our last selection list so we know what has # changed, and optimize for additions. %] function selectProduct(f) { [%# this is to avoid handling events that occur before the form itself is ready, which could happen in buggy browsers. %] if ((!f) || (!f.product)) { return; } [%# if this is the first load and nothing is selected, no need to merge and sort all components; perl gives it to us sorted. %] if ((first_load) && (f.product.selectedIndex == -1)) { first_load = false; return; } [%# turn first_load off. this is tricky, since it seems to be redundant with the above clause. It's not: if when we first load the page there is _one_ element selected, it won't fall into that clause, and first_load will remain 1. Then, if we unselect that item, selectProduct will be called but the clause will be valid (since selectedIndex == -1), and we will return - incorrectly - without merge/sorting. %] first_load = false; [%# - sel keeps the array of products we are selected. - merging says if it is a full list or just a list of products that were added to the current selection. %] var merging = false; var sel = Array(); [%# if nothing selected, pick all %] var findall = f.product.selectedIndex == -1; sel = getSelection(f.product, findall, false); if (!findall) { [%# save sel for the next invocation of selectProduct() %] var tmp = sel; [%# this is an optimization: if we have just added products to an existing selection, no need to clear the form controls and add everybody again; just merge the new ones with the existing options. %] if ((last_sel.length > 0) && (last_sel.length < sel.length)) { sel = fake_diff_array(sel, last_sel); merging = true; } last_sel = tmp; } [%# save original options selected %] var saved_cpts = getSelection(f.component, false, true); var saved_vers = getSelection(f.version, false, true); [% IF Param('usetargetmilestone') %] var saved_tms = getSelection(f.target_milestone, false, true); [% END %] [%# do the actual fill/update, reselect originally selected options %] updateSelect(cpts, sel, f.component, merging); restoreSelection(f.component, saved_cpts); updateSelect(vers, sel, f.version, merging); restoreSelection(f.version, saved_vers); [% IF Param('usetargetmilestone') %] updateSelect(tms, sel, f.target_milestone, merging); restoreSelection(f.target_milestone, saved_tms); [% END %] } // --> </script> [% query_variants = [ { value => "allwordssubstr", description => "contains all of the words/strings" }, { value => "anywordssubstr", description => "contains any of the words/strings" }, { value => "substring", description => "contains the string" }, { value => "casesubstring", description => "contains the string (exact case)" }, { value => "allwords", description => "contains all of the words" }, { value => "anywords", description => "contains any of the words" }, { value => "regexp", description => "matches the regexp" }, { value => "notregexp", description => "doesn’t match the regexp" } ] %] <form method="get" action="buglist.cgi" name="queryform"> [%# *** Summary *** %] <table> <tr> <th align="right">Summary:</th> <td> <select name="short_desc_type"> [% FOREACH qv = query_variants %] <option value="[% qv.value %]" [% " selected" IF default.short_desc_type.0 == qv.value %]>[% qv.description %]</option> [% END %] </select> </td> <td> <input name="short_desc" size="40" value="[% default.short_desc.0 FILTER html %]" /> </td> <td> <input type="submit" value="Search" /> </td> </tr> [%# *** Product Component Version Target *** %] <tr> <td colspan="4"> <table> <tr valign="bottom"> <th align="left">Product:</th> <th align="left"><a href="describecomponents.cgi">Component</a>:</th> <th align="left">Version:</th> [% IF (Param("usetargetmilestone")) %] <th align="left">Target:</th> [% END %] </tr> <tr valign="top"> [%# Can't use the select block here because of onChange and the fact that 'component' is a toolkit reserved word - we use 'component_' instead. %] <td align="left"> <select name="product" multiple size="5" onChange="selectProduct(this.form);"> [% FOREACH p = product %] <option value="[% p FILTER html %]" [% " selected" IF lsearch(default.product, p) != -1 %]> [% p FILTER html %]</option> [% END %] </select> </td> <td align="left"> <select name="component" multiple size="5"> [% FOREACH c = component_ %] <option value="[% c FILTER html %]" [% " selected" IF lsearch(default.component, c) != -1 %]> [% c FILTER html %]</option> [% END %] </select> </td> [% PROCESS select sel = { name => 'version', size => 5 } %] [% IF Param('usetargetmilestone') && target_milestone.size > 0 %] [% PROCESS select sel = { name => 'target_milestone', size => 5 } %] [% END %] </tr> </table> </td> </tr> [%# *** Comment URL Whiteboard Keywords *** %] [% FOREACH field = [ { name => "long_desc", description => "A comment" }, { name => "bug_file_loc", description => "The URL" }, { name => "status_whiteboard", description => "Whiteboard" } ] %] [% UNLESS field.name == 'status_whiteboard' AND NOT Param('usestatuswhiteboard') %] <tr> <th align="right">[% field.description %]:</th> <td> <select name="[% field.name %]_type"> [% FOREACH qv = query_variants %] [% type = "${field.name}_type" %] <option value="[% qv.value %]" [% " selected" IF default.$type.0 == qv.value %]>[% qv.description %]</option> [% END %] </select> </td> <td><input name="[% field.name %]" size="40" value=" [% default.${field.name}.0 FILTER html %]" /> </td> <td></td> </tr> [% END %] [% END %] [% IF have_keywords %] <tr> <th align="right"><a href="describekeywords.cgi">Keywords</a>:</th> <td> <select name="keywords_type"> [% FOREACH qv = [ { name => "anywords", description => "contains any of the keywords" }, { name => "allwords", description => "contains all of the keywords" }, { name => "nowords", description => "contains none of the keywords" } ] %] <option value="[% qv.name %]" [% " selected" IF default.keywords_type.0 == qv.name %]> [% qv.description %]</option> [% END %] </select> </td> <td> <input name="keywords" size="40" value="[% default.keywords.0 FILTER html %]" /> </td> </tr> [% END %] </table> <hr> [%# *** Status Resolution Severity Priority Hardware OS *** %] <table> <tr> <th align="left"><a href="queryhelp.cgi#status">Status</a>:</th> <th align="left"><a href="queryhelp.cgi#resolution">Resolution</a>:</th> <th align="left"><a href="queryhelp.cgi#severity">Severity</a>:</th> <th align="left"><a href="queryhelp.cgi#priority">Priority</a>:</th> <th align="left"><a href="queryhelp.cgi#platform">Hardware</a>:</th> <th align="left"><a href="queryhelp.cgi#opsys">OS</a>:</th> </tr> <tr valign="top"> [% PROCESS select sel = { name => 'bug_status', size => 7 } %] [% PROCESS select sel = { name => 'resolution', size => 7 } %] [% PROCESS select sel = { name => 'bug_severity', size => 7 } %] [% PROCESS select sel = { name => 'priority', size => 7 } %] [% PROCESS select sel = { name => 'rep_platform', size => 7 } %] [% PROCESS select sel = { name => 'op_sys', size => 7 } %] </tr> </table> <p> [%# *** Email Numbering Votes *** %] <table> <tr> <td> <fieldset> <legend> <strong> <a href="queryhelp.cgi#peopleinvolved">Email</a> and Numbering </strong> </legend> <table> <tr> [% FOREACH n = [1, 2] %] <td> <table cellspacing="0" cellpadding="0"> <tr> <td> Any of: </td> </tr> <tr> <td> <input type="checkbox" name="emailassigned_to[% n %]" id="emailassigned_to[% n %]" value="1" [% " checked" IF default.emailassigned_to.$n %] /> <label for="emailassigned_to[% n %]"> bug owner </label> </td> </tr> <tr> <td> <input type="checkbox" name="emailreporter[% n %]" id="emailreporter[% n %]" value="1" [% " checked" IF default.emailreporter.$n %] /> <label for="emailreporter[% n %]"> reporter </label> </td> </tr> [% IF Param('useqacontact') %] <tr> <td> <input type="checkbox" name="emailqa_contact[% n %]" id="emailqa_contact[% n %]" value="1" [% " checked" IF default.emailqa_contact.$n %] /> <label for="emailqa_contact[% n %]"> QA contact </label> </td> </tr> [% END %] <tr> <td> <input type="checkbox" name="emailcc[% n %]" id="emailcc[% n %]" value="1" [% " checked" IF default.emailcc.$n %] /> <label for="emailcc[% n %]"> CC list member </label> </td> </tr> <tr> <td> <input type="checkbox" name="emaillongdesc[% n %]" id="emaillongdesc[% n %]" value="1" [% " checked" IF default.emaillongdesc.$n %] /> <label for="emaillongdesc[% n %]"> commenter </label> </td> </tr> <tr> <td> <select name="emailtype[% n %]"> [% FOREACH qv = [ { name => "substring", description => "contains" }, { name => "exact", description => "is" }, { name => "regexp", description => "matches regexp" }, { name => "notregexp", description => "doesn’t match regexp" } ] %] <option value="[% qv.name %]" [% " selected" IF default.emailtype.$n == qv.name %]>[% qv.description %]</option> [% END %] </select> </td> </tr> <tr> <td> <input name="email[% n %]" size="20" value="[% default.email.$n FILTER html %]" /> </td> </tr> </table> </td> [% END %] </tr> </table> <hr> <table> <tr> <td> <select name="bugidtype"> <option value="include"[% " selected" IF default.bugidtype.0 == "include" %]>Only include</option> <option value="exclude"[% " selected" IF default.bugidtype.0 == "exclude" %]>Exclude</option> </select> bugs numbered: </td> <td> <input type="text" name="bug_id" value="[% default.bug_id.0 FILTER html %]" size="20" /> </td> </tr> <tr> <td></td> <td>(comma-separated list)</td> </tr> <tr> <td align="right"> Only bugs with at least: </td> <td> <input name="votes" size="3" value="[% default.votes.0 FILTER html %]" /> votes </td> </tr> </table> </fieldset> </td> [%# *** Bug Changes *** %] <td valign="top"> <fieldset> <legend><strong>Bug Changes</strong></legend> <dl> <dt>Only bugs changed in the last </dt> <dd><input name=changedin size=3 value="[% default.changedin.0 FILTER html %]" /> days</dd> </dl> <dl> <dt>Only bugs where any of the fields</dt> <dd> <select name="chfield" multiple size="4"> [% FOREACH field = chfield %] <option value="[% field FILTER html %]" [% " selected" IF lsearch(default.chfield, field) != -1 %]> [% field FILTER html %]</option> [% END %] </select> </dd> <dt>were changed between</dt> <dd> <input name="chfieldfrom" size="10" value="[% default.chfieldfrom.0 FILTER html %]" /> and <input name="chfieldto" size="10" value="[% default.chfieldto.0 FILTER html %]" /> <br />(YYYY-MM-DD) </dd> <dt>to this value: (optional)</dt> <dd> <input name="chfieldvalue" size="20" value="[% default.chfieldvalue.0 FILTER html %]" /> </dd> </dl> </fieldset> </td> </tr> [%# *** Action Selection *** %] <tr> <td colspan="2"> [% IF NOT userid %] <input type="hidden" name="cmdtype" value="doit" /> [% ELSE %] <br /> <input type="radio" name="cmdtype" value="doit" checked /> Run this query <br /> [% IF namedqueries.size > 0 %] <p> <table cellspacing="0" cellpadding="0"> <tr> <td> <input type="radio" name="cmdtype" value="editnamed" /> Load my remembered query: </td> <td rowspan="3"> <select name="namedcmd"> [% FOREACH query = namedqueries %] <option value="[% query FILTER html %]">[% query FILTER html %]</option> [% END %] </select> </td> </tr> <tr> <td> <input type="radio" name="cmdtype" value="runnamed" /> Run my remembered query: </td> </tr> <tr> <td> <input type="radio" name="cmdtype" value="forgetnamed" /> Forget my remembered query: </td> </tr> </table> </p> [% END %] <input type="radio" name="cmdtype" value="asdefault" /> Remember this as my default query <br /> <input type="radio" name="cmdtype" value="asnamed" /> Remember this query, and name it: <input type="text" name="newqueryname"> <br /> <input type="checkbox" name="tofooter" value="1" /> and put it in my page footer <br /> [% END %] <br /> <div> Sort results by: <select name="order"> [% FOREACH order = orders %] <option value="[% order FILTER html %]" [% " selected" IF default.order.0 == order %]>[% order FILTER html %]</option> [% END %] </select> <input type="submit" value="Search" /> [% IF userdefaultquery %] <p> <a href="query.cgi?nukedefaultquery=1"> Set my default query back to the system default</a> </p> [% END %] </div> </td> </tr> </table> [%# *** Boolean Charts *** %] <hr> [% types = [ { name => "noop", description => "---" }, { name => "equals", description => "is equal to" }, { name => "notequals", description => "is not equal to" }, { name => "substring", description => "contains the string" }, { name => "casesubstring", description => "contains the string (exact case)" }, { name => "notsubstring", description => "does not contain the string" }, { name => "allwordssubstr", description => "contains all of the strings" }, { name => "anywordssubstr", description => "contains any of the strings" }, { name => "regexp", description => "contains regexp" }, { name => "notregexp", description => "does not contain regexp" }, { name => "lessthan", description => "is less than" }, { name => "greaterthan", description => "is greater than" }, { name => "anywords", description => "contains any of the words" }, { name => "allwords", description => "contains all of the words" }, { name => "nowords", description => "contains none of the words" }, { name => "changedbefore", description => "changed before" }, { name => "changedafter", description => "changed after" }, { name => "changedfrom", description => "changed from" }, { name => "changedto", description => "changed to" }, { name => "changedby", description => "changed by" } ] %] <p> <strong> <a name="chart" href="queryhelp.cgi#advancedquerying"> Advanced Querying Using Boolean Charts</a>: </strong> </p> [%# Whoever wrote the original version of boolean charts had a seriously twisted mind %] [% jsmagic = "onclick=\"document.forms[0].action='query.cgi#chart'; document.forms[0].method='POST'; return 1;\"" %] [% FOREACH chart = default.charts %] [% chartnum = loop.count - 1 %] <table> [% FOREACH row = chart %] [% rownum = loop.count - 1 %] <tr> [% FOREACH col = row %] [% colnum = loop.count - 1 %] <td> <select name="[% "field${chartnum}-${rownum}-${colnum}" %]"> [% FOREACH field = fields %] <option value="[% field.name %]" [%- " selected" IF field.name == col.field %]>[% field.description %]</option> [% END %] </select> <select name="[% "type${chartnum}-${rownum}-${colnum}" %]"> [% FOREACH type = types %] <option value="[% type.name %]" [%- " selected" IF type.name == col.type %]>[% type.description %]</option> [% END %] </select> <input name="[% "value${chartnum}-${rownum}-${colnum}" %]" value="[% col.value FILTER html %]" /> </td> [% IF NOT col == row.last %] <td align="center"> Or </td> [% ELSE %] <td> [% newor = colnum + 1 %] <input type="submit" value="Or" name="cmd-add[% "${chartnum}-${rownum}-${newor}" %]" [% $jsmagic %] /> </td> [% END %] [% END %] </tr> [% IF NOT row == chart.last %] <tr> <td>And</td> </tr> [% ELSE %] <tr> <td> [% newand = rownum + 1; newchart = chartnum + 1 %] <input type="submit" value="And" name="cmd-add[% "${chartnum}-${newand}-0" %]"[% $jsmagic %] /> <input type="submit" value="Add another boolean chart" name="cmd-add[% newchart %]-0-0" [% $jsmagic %] /> </td> </tr> [% END %] [% END %] </table> <hr> [% END %] <p>Give me a <a href="queryhelp.cgi">clue</a> about how to use this form.</p> </FORM> [% INCLUDE global/footer %] [%############################################################################%] [%# Block for SELECT fields #%] [%############################################################################%] [% BLOCK select %] <td align="left"> <select name="[% sel.name %]" multiple size="[% sel.size %]"> [% FOREACH name = ${sel.name} %] <option value="[% name FILTER html %]" [% " selected" IF lsearch(default.${sel.name}, name) != -1 %]> [% name FILTER html %]</option> [% END %] </select> </td> [% END %]