diff options
Diffstat (limited to 'extensions/GuidedBugEntry')
30 files changed, 2018 insertions, 0 deletions
diff --git a/extensions/GuidedBugEntry/Config.pm b/extensions/GuidedBugEntry/Config.pm new file mode 100644 index 000000000..e4bc9c70b --- /dev/null +++ b/extensions/GuidedBugEntry/Config.pm @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::GuidedBugEntry; +use strict; + +use constant NAME => 'GuidedBugEntry'; + +use constant REQUIRED_MODULES => [ +]; + +use constant OPTIONAL_MODULES => [ +]; + +__PACKAGE__->NAME; diff --git a/extensions/GuidedBugEntry/Extension.pm b/extensions/GuidedBugEntry/Extension.pm new file mode 100644 index 000000000..5665e18ae --- /dev/null +++ b/extensions/GuidedBugEntry/Extension.pm @@ -0,0 +1,116 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::GuidedBugEntry; +use strict; +use base qw(Bugzilla::Extension); + +use Bugzilla::Token; +use Bugzilla::Error; +use Bugzilla::Status; +use Bugzilla::Util 'url_quote'; +use Bugzilla::UserAgent; + +our $VERSION = '1'; + +sub enter_bug_start { + my ($self, $args) = @_; + my $vars = $args->{vars}; + my $template = Bugzilla->template; + my $cgi = Bugzilla->cgi; + my $user = Bugzilla->user; + + # hack for skipping old guided code when enabled + $vars->{'disable_guided'} = 1; + + # force guided format for new users + my $format = $cgi->param('format') || ''; + if ( + $format eq 'guided' || + ( + $format eq '' && + !$user->in_group('canconfirm') + ) + ) { + # skip the first step if a product is provided + if ($cgi->param('product')) { + print $cgi->redirect('enter_bug.cgi?format=guided#h=dupes' . + '|' . url_quote($cgi->param('product')) . + '|' . url_quote($cgi->param('component') || '') + ); + exit; + } + + $self->_init_vars($vars); + print $cgi->header(); + $template->process('guided/guided.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + exit; + } + + # we use the __default__ format to bypass the guided entry + # it isn't understood upstream, so remove it once a product + # has been selected. + if ( + ($cgi->param('format') && $cgi->param('format') eq "__default__") + && ($cgi->param('product') && $cgi->param('product') ne '') + ) { + $cgi->delete('format'); + } +} + +sub _init_vars { + my ($self, $vars) = @_; + my $user = Bugzilla->user; + + my @enterable_products = @{$user->get_enterable_products}; + ThrowUserError('no_products') unless scalar(@enterable_products); + + my @classifications = ({object => undef, products => \@enterable_products}); + + my $class; + foreach my $product (@enterable_products) { + $class->{$product->classification_id}->{'object'} ||= + new Bugzilla::Classification($product->classification_id); + push(@{$class->{$product->classification_id}->{'products'}}, $product); + } + @classifications = + sort { + $a->{'object'}->sortkey <=> $b->{'object'}->sortkey + || lc($a->{'object'}->name) cmp lc($b->{'object'}->name) + } (values %$class); + $vars->{'classifications'} = \@classifications; + + my @open_states = BUG_STATE_OPEN(); + $vars->{'open_states'} = \@open_states; + + $vars->{'token'} = issue_session_token('create_bug'); + + $vars->{'platform'} = detect_platform(); + $vars->{'op_sys'} = detect_op_sys(); + + eval 'use Bugzilla::Extension::BMO::Data'; + $vars->{'BMO'} = $@ ? 0 : 1; +} + +sub page_before_template { + my ($self, $args) = @_; + my $page = $args->{'page_id'}; + my $vars = $args->{'vars'}; + + return unless $page eq 'guided_products.js'; + + # import product -> security group mappings from the BMO ext + + our %product_sec_groups; + eval q#use Bugzilla::Extension::BMO::Data '%product_sec_groups'#; + return if $@; + + $vars->{'products'} = \%product_sec_groups; +} + +__PACKAGE__->NAME; diff --git a/extensions/GuidedBugEntry/template/en/default/bug/create/comment-guided.txt.tmpl b/extensions/GuidedBugEntry/template/en/default/bug/create/comment-guided.txt.tmpl new file mode 100644 index 000000000..6b0de9466 --- /dev/null +++ b/extensions/GuidedBugEntry/template/en/default/bug/create/comment-guided.txt.tmpl @@ -0,0 +1,25 @@ +[% USE Bugzilla %] +[% cgi = Bugzilla.cgi %] +User Agent: [% cgi.param('user_agent') %] +[% IF cgi.param('build_id') %] +Build ID: [% cgi.param('build_id') %][% END %] + +[% IF cgi.param('bug_steps') %] +Steps to reproduce: + +[%+ cgi.param('bug_steps') %] +[% END %] + +[% IF cgi.param('actual') %] + +Actual results: + +[%+ cgi.param('actual') %] +[% END %] + +[% IF cgi.param('expected') %] + +Expected results: + +[%+ cgi.param('expected') %] +[% END %] diff --git a/extensions/GuidedBugEntry/template/en/default/guided/guided.html.tmpl b/extensions/GuidedBugEntry/template/en/default/guided/guided.html.tmpl new file mode 100644 index 000000000..ee5bea892 --- /dev/null +++ b/extensions/GuidedBugEntry/template/en/default/guided/guided.html.tmpl @@ -0,0 +1,549 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + +[% PROCESS global/variables.none.tmpl %] + +[% js_urls = [ 'extensions/GuidedBugEntry/web/js/products.js', + 'extensions/GuidedBugEntry/web/js/guided.js', + 'js/field.js', 'js/TUI.js', 'js/bug.js' ] %] +[% js_urls.push('extensions/BMO/web/js/prod_comp_search.js') IF BMO %] + +[% yui_modules = [ 'history', 'datatable', 'container' ] %] +[% yui_modules.push('autocomplete') IF BMO %] + +[% IF BMO %] + [% onload = "YAHOO.util.Dom.get('prod_comp_search').focus();" %] +[% END %] + +[% PROCESS global/header.html.tmpl + title = "Enter A Bug" + javascript_urls = js_urls + style_urls = [ 'extensions/GuidedBugEntry/web/style/guided.css', + 'js/yui/assets/skins/sam/container.css' ] + yui = yui_modules +%] + +<iframe id="yui-history-iframe" src="extensions/GuidedBugEntry/web/yui-history-iframe.txt"></iframe> +<input id="yui-history-field" type="hidden"> + +<noscript> +You require JavaScript to use this [% terms.bug %] entry form.<br><br> +Please use the <a href="enter_bug.cgi?format=__default__">advanced [% terms.bug %] entry form</a>. +</noscript> + +<div id="loading" class="hidden"> +Please wait... +</div> +<script type="text/javascript"> +YAHOO.util.Dom.removeClass('loading', 'hidden'); +</script> + +<div id="steps"> +[% INCLUDE product_step %] +[% INCLUDE otherProducts_step %] +[% INCLUDE dupes_step %] +[% INCLUDE bugForm_step %] +</div> + +<div id="advanced"> + <a id="advanced_img" href="enter_bug.cgi?format=__default__"><img + src="extensions/GuidedBugEntry/web/images/advanced.png" width="16" height="16" border="0"></a> + <a id="advanced_link" href="enter_bug.cgi?format=__default__">Switch to the advanced [% terms.bug %] entry form</a> +</div> + +<script type="text/javascript"> +YAHOO.util.Dom.addClass('loading', 'hidden'); +guided.init(); +guided.detectedPlatform = '[% platform FILTER js %]'; +guided.detectedOpSys = '[% op_sys FILTER js %]'; +guided.currentUser = '[% user.login FILTER js %]'; +guided.openStates = [ +[% FOREACH state = open_states %] + '[% state FILTER js%]' + [%- "," UNLESS loop.last %] +[% END %] +]; +dupes.setLabels( + { + id: "[% field_descs.bug_id FILTER js %]", + summary: "[% field_descs.short_desc FILTER js %]", + component: "[% field_descs.component FILTER js %]", + status: "[% field_descs.bug_status FILTER js %]", + } +); +</script> +<script type="text/javascript" src="page.cgi?id=guided_products.js"></script> +[% PROCESS global/footer.html.tmpl %] + +[%############################################################################%] +[%# page title #%] +[%############################################################################%] + +[% BLOCK page_title %] + <div id="page_title"> + <h2>Enter A [% terms.Bug %]</h2> + <h3>Step [% step_number FILTER html %] of 3</h3> + </div> +[% END %] + +[%############################################################################%] +[%# product step #%] +[%############################################################################%] + +[% BLOCK product_step %] +<div id="product_step" class="step hidden"> + +[% INCLUDE page_title + step_number = "1" +%] + +[% INCLUDE exits + show = "all" +%] + +<table id="products"> +[% INCLUDE 'guided/products.html.tmpl' %] +[% INCLUDE product_block + name="Other Products" + icon="other.png" + desc="Other Mozilla products which aren't listed here" + onclick="guided.setStep('otherProducts')" +%] +</table> + +</div> +[% END %] + +[% BLOCK product_block %] + [% IF !caption %] + [% caption = name %] + [% END %] + [% IF !desc %] + [% FOREACH c = classifications %] + [% FOREACH p = c.products %] + [% IF p.name == name %] + [% desc = p.description %] + [% LAST %] + [% END %] + [% END %] + [% END %] + [% END %] + <tr> + <td class="product_img"> + <a href="javascript:void(0)" + [% IF onclick %] + onclick="[% onclick FILTER html %]" + [% ELSE %] + onclick="product.select('[% name FILTER js %]')" + [% END %] + ><img src="extensions/GuidedBugEntry/web/images/products/[% icon FILTER uri %]" width="64" height="64" + ></a> + </td> + <td> + <h2> + <a href="javascript:void(0)" + [% IF onclick %] + onclick="[% onclick FILTER html %]" + [% ELSE %] + onclick="product.select('[% name FILTER js %]')" + [% END %] + >[% caption FILTER html %]</a> + </h2> + <p> + [% desc FILTER html_light %] + </p> + </td> + </tr> +[% END %] + +[%############################################################################%] +[%# other products step #%] +[%############################################################################%] + +[% BLOCK otherProducts_step %] +<div id="otherProducts_step" class="step hidden"> + +[% INCLUDE page_title + step_number = "1" +%] + +[% INCLUDE exits + show = "all" +%] + +<table id="other_products"> +[% FOREACH c = classifications %] + [% IF c.object %] + <tr class="classification"> + <th align="right" valign="top"> + [% c.object.name FILTER html %]: + </th> + <td> + [% c.object.description FILTER html_light %] + </td> + </tr> + [% END %] + [% FOREACH p = c.products %] + <tr> + <th align="right" valign="top"> + <a href="javascript:void(0)" onclick="product.select('[% p.name FILTER js %]')"> + [% p.name FILTER html FILTER no_break %]</a>: + </th> + + <td valign="top">[% p.description FILTER html_light %]</td> + </tr> + [% END %] + <tr> + <td> </td> + </tr> +[% END %] +</table> + +</div> +[% END %] + +[%############################################################################%] +[%# exits (support/input) #%] +[%############################################################################%] + +[% BLOCK exits %] +<table class="exits"> + <tr> + <td> + <div class="exit_img"> + <a href="http://www.mozilla.org/support/" + ><img src="extensions/GuidedBugEntry/web/images/support.png" width="32" height="32" + ></a> + </div> + </td> + <td width="100%"> + <h2> + <a href="http://www.mozilla.org/support/">I need technical support</a> + </h2> + For technical support or help getting your site to work with Mozilla. + </td> + </tr> + <tr> + <td> + <div class="exit_img"> + <a href="http://input.mozilla.org/idea/" + ><img src="extensions/GuidedBugEntry/web/images/idea.png" width="32" height="32" + ></a> + </div> + </td> + <td width="100%"> + <h2> + <a href="http://input.mozilla.org/idea/">I have an idea for firefox</a> + </h2> + For offering us ideas on how to enhance Firefox. + </td> + </tr> + <tr> + <td> + <div class="exit_img"> + <a href="http://input.mozilla.org/feedback/" + ><img src="extensions/GuidedBugEntry/web/images/input.png" width="32" height="32" + ></a> + </div> + </td> + <td width="100%"> + • <a href="http://input.mozilla.org/feedback/">Provide other feedback about Firefox</a><br> + • <a href="http://input.mozilla.org/feedback/#sad">Report an issue with a web site that I use</a><br> + • <a href="enter_bug.cgi?format=guided&product=Core">Report an issue with Firefox on a site that I've developed</a><br> + </td> + </tr> +</table> +<h3> + None of the above; my [% terms.bug %] is in: +</h3> + +[% IF BMO %] + <div id="prod_comp_search_main"> + <div id="prod_comp_search_autocomplete"> + <div id="prod_comp_search_label"> + Type to find product and component by name or description: + <img id="prod_comp_throbber" src="extensions/GuidedBugEntry/web/images/throbber.gif" + class="hidden" width="16" height="11"> + </div> + <input id="prod_comp_search" type="text" size="60"> + <div id="prod_comp_search_autocomplete_container"></div> + </div> + </div> + <script type="text/javascript"> + if (typeof(YAHOO.bugzilla.prodCompSearch) !== 'undefined' && YAHOO.bugzilla.prodCompSearch != null) + YAHOO.bugzilla.prodCompSearch.init('prod_comp_search', 'prod_comp_search_autocomplete_container', 'guided'); + </script> + + <h3> + Or select a product: + </h3> +[% END %] + +[% END %] + +[% BLOCK exit_block %] + <tr> + <td> + <div class="exit_img"> + <a href="[% href FILTER none %]" + ><img src="extensions/GuidedBugEntry/web/images/[% icon FILTER uri %]" width="32" height="32" + ></a> + </div> + </td> + <td width="100%"> + <h2> + <a href="[% href FILTER none %]">[% name FILTER html %]</a> + </h2> + [% desc FILTER html %] + </td> + </tr> +[% END %] + +[%############################################################################%] +[%# duplicates step #%] +[%############################################################################%] + +[% BLOCK dupes_step %] +<div id="dupes_step" class="step hidden"> + +[% INCLUDE page_title + step_number = "2" +%] + +<p> +Product: <b><span id="dupes_product_name">?</span></b>: +(<a href="javascript:void(0)" onclick="guided.setStep('product')">Change</a>) +</p> + +<table border="0" cellpadding="5" cellspacing="0" id="product_support" class="hidden"> +<tr> +<td> + <img src="extensions/GuidedBugEntry/web/images/message.png" width="24" height="24"> +</td> +<td id="product_support_message"> </td> +</table> + +<div id="dupe_form"> + <p> + Please summarise your issue or request in one sentence: + </p> + <input id="dupes_summary" value="Short summary of issue" spellcheck="true" placeholder="Short summary of issue"> + <button id="dupes_search">Find similar issues</button> + <button id="dupes_continue_button_top" onclick="guided.setStep('bugForm')">My issue is not listed</button> +</div> + +<div id="dupes_list"></div> +<div id="dupes_continue"> +<button id="dupes_continue_button_bottom" onclick="guided.setStep('bugForm')">My issue is not listed</button> +</div> + +</div> +[% END %] + +[%############################################################################%] +[%# bug form step #%] +[%############################################################################%] + +[% BLOCK bugForm_step %] +<div id="bugForm_step" class="step hidden"> + +[% INCLUDE page_title + step_number = "3" +%] + +<form method="post" action="post_bug.cgi" enctype="multipart/form-data" onsubmit="return bugForm.validate()"> +<input type="hidden" name="token" value="[% token FILTER html %]"> +<input type="hidden" name="product" id="product" value=""> +<input type="hidden" name="component" id="component" value=""> +<input type="hidden" name="bug_severity" value="normal"> +<input type="hidden" name="rep_platform" id="rep_platform" value="All"> +<input type="hidden" name="priority" value="--"> +<input type="hidden" name="op_sys" id="op_sys" value="All"> +<input type="hidden" name="version" id="version" value=""> +<input type="hidden" name="comment" id="comment" value=""> +<input type="hidden" name="format" value="guided"> +<input type="hidden" name="user_agent" id="user_agent" value=""> +<input type="hidden" name="build_id" id="build_id" value=""> + +<ul> +<li>Please fill out this form clearly, precisely and in as much detail as you can manage.</li> +<li>Please report only a single problem at a time.</li> +<li><a href="https://developer.mozilla.org/en/Bug_writing_guidelines" target="_blank">These guidelines</a> +explain how to write effective [% terms.bug %] reports.</li> +</ul> + +<table id="bugForm" cellspacing="0"> + +<tr class="odd"> + <td class="label">Summary:</td> + <td width="100%" colspan="2"> + <input name="short_desc" id="short_desc" class="textInput" spellcheck="true"> + </td> + <td valign="top"> + [% PROCESS help id="summary_help" %] + <div id="summary_help" class="hidden help"> + A sentence which summarises the problem. Please be descriptive and use lots of keywords.<br> + <br> + <span class="help-bad">Bad example</span>: mail crashed<br> + <span class="help-good">Good example</span>: crash if I close the mail window while checking for new POP mail + </div> + </td> +</tr> + +<tr class="even"> + <td class="label">Product:</td> + <td id="productTD"> + <span id="product_label"></span> + (<a href="javascript:void(0)" onclick="guided.setStep('product')">Change</a>) + </td> + <td id="versionTD" class="hidden"> + <span class="label">Version: + <select id="version_select" onchange="bugForm.onVersionChange(this.value)"> + </select> + </td> + <td valign="top"> + [% PROCESS help id="product_help" %] + <div id="product_help" class="hidden help"> + The Product and Version you are reporting the issue with. + </div> +</tr> + +<tr class="odd" id="componentTR"> + <td valign="top"> + <div class="label"> + Component: + </div> + (<a id="list_comp" href="describecomponents.cgi" target="_blank" + title="Show a list of all components and descriptions (in a new window)." + >List</a>) + </td> + <td valign="top" colspan="2"> + <select id="component_select" onchange="bugForm.onComponentChange(this.value)" class="mandatory"> + </select> + <div id="component_description"></div> + </td> + <td valign="top"> + [% PROCESS help id="component_help" %] + <div id="component_help" class="hidden help"> + The area where the problem occurs.<br> + <br> + If you are unsure which component to use, select a 'General' component. + </div> +</tr> + +<tr class="even"> + <td class="label" colspan="3">What did you do?</td> + <td valign="top"> + [% PROCESS help id="steps_help" %] + <div id="steps_help" class="hidden help"> + Please be as specific as possible about what what you did + to cause the problem. Providing step-by-step instructions + would be ideal.<br> + <br> + Include any relevant URLs and special setup steps.<br> + <br> + <span class="help-bad">Bad example</span>: Mozilla crashed. You suck!<br> + <span class="help-good">Good example</span>: After a crash which happened + when I was sorting in the Bookmark Manager, all of my top-level bookmark + folders beginning with the letters Q to Z are no longer present. + </div> + </td> +</tr> +<tr class="even"> + <td colspan="3"><textarea id="bug_steps" name="bug_steps" rows="5"></textarea></td> + <td> </td> +</tr> + +<tr class="odd"> + <td class="label" colspan="3">What happened?</td> + <td valign="top"> + [% PROCESS help id="actual_help" %] + <div id="actual_help" class="hidden help"> + What happened after you performed the steps above? + </div> +</tr> +<tr class="odd"> + <td colspan="3"><textarea id="actual" name="actual" rows="5"></textarea></td> + <td> </td> +</tr> + +<tr class="even"> + <td class="label" colspan="3">What should have happened?</td> + <td valign="top"> + [% PROCESS help id="expected_help" %] + <div id="expected_help" class="hidden help"> + What should the software have done instead? + </div> +</tr> +<tr class="even"> + <td colspan="3"><textarea id="expected" name="expected" rows="5"></textarea></td> + <td> </td> +</tr> + +<tr class="odd"> + <td class="label">Attach a file:</td> + <td colspan="2"> + <input type="file" name="data" id="data" size="50" onchange="bugForm.onFileChange()"> + <input type="hidden" name="contenttypemethod" value="autodetect"> + <button id="reset_data" onclick="return bugForm.onFileClear()" disabled>Clear</button> + </td> + <td valign="top"> + [% PROCESS help id="file_help" %] + <div id="file_help" class="hidden help"> + If a file helps explain the issue better, such as a screenshot, please + attach one here. + </div> + </td> +</tr> +<tr class="odd"> + <td class="label">File Description:</td> + <td colspan="2"><input type="text" name="description" id="data_description" class="textInput" disabled></td> + <td> </td> +</tr> + +<tr class="even"> + <td class="label">Security:</td> + <td colspan="2"> + <table border="0" cellpadding="0" cellspacing="0"> + <tr> + <td> + <input type="checkbox" name="groups" value="core-security" id="groups"> + </td> + <td> + <label for="groups">Many users could be harmed by this security problem: + it should be kept hidden from the public until it is resolved.</label> + </td> + </tr> + </table> + </td> + <td> </td> +</tr> + +<tr class="odd"> + <td> </td> + <td colspan="2" id="submitTD"> + <input type="submit" id="submit" value="Submit [% terms.Bug %]"> + </td> + <td> </td> +</tr> + +</table> + +</form> + +</div> +[% END %] + +[%############################################################################%] +[%# help block #%] +[%############################################################################%] + +[% BLOCK help %] +<img src="extensions/GuidedBugEntry/web/images/help.png" width="16" height="16" class="help_image" + helpid="[% id FILTER html %]" onMouseOver="bugForm.showHelp(this)" onMouseOut="bugForm.hideHelp(this)" + > +[% END %] diff --git a/extensions/GuidedBugEntry/template/en/default/guided/products.html.tmpl b/extensions/GuidedBugEntry/template/en/default/guided/products.html.tmpl new file mode 100644 index 000000000..22c93a354 --- /dev/null +++ b/extensions/GuidedBugEntry/template/en/default/guided/products.html.tmpl @@ -0,0 +1,44 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% INCLUDE product_block + name="Firefox" + icon="firefox.png" +%] +[% INCLUDE product_block + name="Firefox for Android" + icon="firefox.png" +%] +[% INCLUDE product_block + name="Thunderbird" + icon="thunderbird.png" +%] +[% INCLUDE product_block + name="Mozilla Services" + icon="dino.png" +%] +[% INCLUDE product_block + name="SeaMonkey" + icon="seamonkey.png" +%] +[% INCLUDE product_block + name="Mozilla Localizations" + icon="dino.png" +%] +[% INCLUDE product_block + name="Mozilla Labs" + icon="labs.png" +%] +[% INCLUDE product_block + name="Calendar" + icon="sunbird.png" +%] +[% INCLUDE product_block + name="Core" + icon="core.png" +%] diff --git a/extensions/GuidedBugEntry/template/en/default/pages/guided_products.js.tmpl b/extensions/GuidedBugEntry/template/en/default/pages/guided_products.js.tmpl new file mode 100644 index 000000000..231681085 --- /dev/null +++ b/extensions/GuidedBugEntry/template/en/default/pages/guided_products.js.tmpl @@ -0,0 +1,18 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[%# this file allows us to pull in data defined in the BMO ext %] + +[% IF products %] + [% FOREACH product = products %] + if (!products['[% product.key FILTER js %]']) + products['[% product.key FILTER js %]'] = {}; + products['[% product.key FILTER js %]'].secgroup = '[% product.value FILTER js %]'; + [% END %] +[% END %] + diff --git a/extensions/GuidedBugEntry/web/images/advanced.png b/extensions/GuidedBugEntry/web/images/advanced.png Binary files differnew file mode 100644 index 000000000..71a3fcb78 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/advanced.png diff --git a/extensions/GuidedBugEntry/web/images/help.png b/extensions/GuidedBugEntry/web/images/help.png Binary files differnew file mode 100644 index 000000000..5c870176d --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/help.png diff --git a/extensions/GuidedBugEntry/web/images/idea.png b/extensions/GuidedBugEntry/web/images/idea.png Binary files differnew file mode 100644 index 000000000..0a0ce6c79 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/idea.png diff --git a/extensions/GuidedBugEntry/web/images/input.png b/extensions/GuidedBugEntry/web/images/input.png Binary files differnew file mode 100644 index 000000000..34c10e989 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/input.png diff --git a/extensions/GuidedBugEntry/web/images/message.png b/extensions/GuidedBugEntry/web/images/message.png Binary files differnew file mode 100644 index 000000000..55b6add19 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/message.png diff --git a/extensions/GuidedBugEntry/web/images/products/camino.png b/extensions/GuidedBugEntry/web/images/products/camino.png Binary files differnew file mode 100644 index 000000000..c833b4d04 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/camino.png diff --git a/extensions/GuidedBugEntry/web/images/products/core.png b/extensions/GuidedBugEntry/web/images/products/core.png Binary files differnew file mode 100644 index 000000000..b9c5053f6 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/core.png diff --git a/extensions/GuidedBugEntry/web/images/products/dino.png b/extensions/GuidedBugEntry/web/images/products/dino.png Binary files differnew file mode 100644 index 000000000..9e0470a07 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/dino.png diff --git a/extensions/GuidedBugEntry/web/images/products/fennec.png b/extensions/GuidedBugEntry/web/images/products/fennec.png Binary files differnew file mode 100644 index 000000000..ebad7e358 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/fennec.png diff --git a/extensions/GuidedBugEntry/web/images/products/firefox.png b/extensions/GuidedBugEntry/web/images/products/firefox.png Binary files differnew file mode 100644 index 000000000..582a6952a --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/firefox.png diff --git a/extensions/GuidedBugEntry/web/images/products/labs.png b/extensions/GuidedBugEntry/web/images/products/labs.png Binary files differnew file mode 100644 index 000000000..346e0ef06 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/labs.png diff --git a/extensions/GuidedBugEntry/web/images/products/mozilla.png b/extensions/GuidedBugEntry/web/images/products/mozilla.png Binary files differnew file mode 100644 index 000000000..e506328bc --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/mozilla.png diff --git a/extensions/GuidedBugEntry/web/images/products/other.png b/extensions/GuidedBugEntry/web/images/products/other.png Binary files differnew file mode 100644 index 000000000..e436c22ae --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/other.png diff --git a/extensions/GuidedBugEntry/web/images/products/seamonkey.png b/extensions/GuidedBugEntry/web/images/products/seamonkey.png Binary files differnew file mode 100644 index 000000000..fcb261ae1 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/seamonkey.png diff --git a/extensions/GuidedBugEntry/web/images/products/sunbird.png b/extensions/GuidedBugEntry/web/images/products/sunbird.png Binary files differnew file mode 100644 index 000000000..6b15c257d --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/sunbird.png diff --git a/extensions/GuidedBugEntry/web/images/products/thunderbird.png b/extensions/GuidedBugEntry/web/images/products/thunderbird.png Binary files differnew file mode 100644 index 000000000..f3523183a --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/products/thunderbird.png diff --git a/extensions/GuidedBugEntry/web/images/sumo.png b/extensions/GuidedBugEntry/web/images/sumo.png Binary files differnew file mode 100644 index 000000000..d5773647c --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/sumo.png diff --git a/extensions/GuidedBugEntry/web/images/support.png b/extensions/GuidedBugEntry/web/images/support.png Binary files differnew file mode 100644 index 000000000..2320ea74a --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/support.png diff --git a/extensions/GuidedBugEntry/web/images/throbber.gif b/extensions/GuidedBugEntry/web/images/throbber.gif Binary files differnew file mode 100644 index 000000000..bc4fa6561 --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/throbber.gif diff --git a/extensions/GuidedBugEntry/web/images/warning.png b/extensions/GuidedBugEntry/web/images/warning.png Binary files differnew file mode 100644 index 000000000..86bed170d --- /dev/null +++ b/extensions/GuidedBugEntry/web/images/warning.png diff --git a/extensions/GuidedBugEntry/web/js/guided.js b/extensions/GuidedBugEntry/web/js/guided.js new file mode 100644 index 000000000..e98237fab --- /dev/null +++ b/extensions/GuidedBugEntry/web/js/guided.js @@ -0,0 +1,904 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +// global + +var Dom = YAHOO.util.Dom; +var Event = YAHOO.util.Event; +var History = YAHOO.util.History; + +var guided = { + _currentStep: '', + detectedPlatform: '', + detectedOpSys: '', + currentUser: '', + openStates: [], + + setStep: function(newStep, noSetHistory) { + // initialise new step + switch(newStep) { + case 'product': + product.onShow(); + break; + case 'otherProducts': + otherProducts.onShow(); + break; + case 'dupes': + dupes.onShow(); + break; + case 'bugForm': + bugForm.onShow(); + break; + default: + guided.setStep('product'); + return; + } + + // change visibility of _step div + if (this._currentStep) + Dom.addClass(this._currentStep + '_step', 'hidden'); + this._currentStep = newStep; + Dom.removeClass(this._currentStep + '_step', 'hidden'); + + // scroll to top of page to mimic real navigation + scroll(0,0); + + // update history + if (History && !noSetHistory) { + History.navigate('h', newStep + '|' + product.getName() + + (product.getPreselectedComponent() ? '|' + product.getPreselectedComponent() : '') + ); + } + }, + + init: function() { + // init history manager + try { + History.register('h', History.getBookmarkedState('h') || 'product', + this._onStateChange); + History.initialize("yui-history-field", "yui-history-iframe"); + History.onReady(function () { + guided._onStateChange(History.getCurrentState('h'), true); + }); + } catch(err) { + History = false; + } + + // init steps + product.onInit(); + dupes.onInit(); + bugForm.onInit(); + }, + + _onStateChange: function(state, noSetHistory) { + state = state.split('|'); + product.setName(state[1] || ''); + product.setPreselectedComponent(state[2] || ''); + guided.setStep(state[0], noSetHistory); + }, + + setAdvancedLink: function() { + href = 'enter_bug.cgi?format=__default__' + + '&product=' + encodeURIComponent(product.getName()) + + '&short_desc=' + encodeURIComponent(dupes.getSummary()); + Dom.get('advanced_img').href = href; + Dom.get('advanced_link').href = href; + } +}; + +// product step + +var product = { + details: false, + _counter: 0, + _loaded: '', + _preselectedComponent: '', + + onInit: function() { }, + + onShow: function() { + Dom.removeClass('advanced', 'hidden'); + }, + + select: function(productName) { + // called when a product is selected + this.setName(productName); + dupes.reset(); + guided.setStep('dupes'); + }, + + getName: function() { + return Dom.get('product').value; + }, + + getPreselectedComponent: function() { + return this._preselectedComponent; + }, + + setPreselectedComponent: function(value) { + this._preselectedComponent = value; + }, + + _getNameAndRelated: function() { + var result = []; + + var name = this.getName(); + result.push(name); + + if (products[name] && products[name].related) { + for (var i = 0, n = products[name].related.length; i < n; i++) { + result.push(products[name].related[i]); + } + } + + return result; + }, + + setName: function(productName) { + if (productName == this.getName() && this.details) + return; + + // display the product name + Dom.get('product').value = productName; + Dom.get('product_label').innerHTML = YAHOO.lang.escapeHTML(productName); + Dom.get('dupes_product_name').innerHTML = YAHOO.lang.escapeHTML(productName); + Dom.get('list_comp').href = 'describecomponents.cgi?product=' + encodeURIComponent(productName); + guided.setAdvancedLink(); + + if (productName == '') { + Dom.addClass("product_support", "hidden"); + return; + } + + // use the correct security group + if (products[productName] && products[productName].secgroup) { + Dom.get('groups').value = products[productName].secgroup; + } else { + Dom.get('groups').value = products['_default'].secgroup; + } + + // use the correct platform & op_sys + if (products[productName] && products[productName].detectPlatform) { + Dom.get('rep_platform').value = guided.detectedPlatform; + Dom.get('op_sys').value = guided.detectedOpSys; + } else { + Dom.get('rep_platform').value = 'All'; + Dom.get('op_sys').value = 'All'; + } + + // show support message + if (products[productName] && products[productName].support) { + Dom.get("product_support_message").innerHTML = products[productName].support; + Dom.removeClass("product_support", "hidden"); + } else { + Dom.addClass("product_support", "hidden"); + } + + // show/hide component selection row + if (products[productName] && products[productName].noComponentSelection) { + if (!Dom.hasClass('componentTR', 'hidden')) { + Dom.addClass('componentTR', 'hidden'); + bugForm.toggleOddEven(); + } + } else { + if (Dom.hasClass('componentTR', 'hidden')) { + Dom.removeClass('componentTR', 'hidden'); + bugForm.toggleOddEven(); + } + } + + if (this._loaded == productName) + return; + + // grab the product information + this.details = false; + this._loaded = productName; + YAHOO.util.Connect.setDefaultPostHeader('application/json; charset=UTF-8'); + YAHOO.util.Connect.asyncRequest( + 'POST', + 'jsonrpc.cgi', + { + success: function(res) { + try { + data = YAHOO.lang.JSON.parse(res.responseText); + if (data.error) + throw(data.error.message); + product.details = data.result.products[0]; + bugForm.onProductUpdated(); + } catch (err) { + product.details = false; + bugForm.onProductUpdated(); + if (err) { + alert('Failed to retreive components for product "' + + productName + '":' + "\n\n" + err); + if (console) + console.error(err); + } + } + }, + failure: function(res) { + this._loaded = ''; + product.details = false; + bugForm.onProductUpdated(); + if (res.responseText) { + alert('Failed to retreive components for product "' + + productName + '":' + "\n\n" + res.responseText); + if (console) + console.error(res); + } + } + }, + YAHOO.lang.JSON.stringify({ + version: "1.1", + method: "Product.get", + id: ++this._counter, + params: { + names: [productName], + exclude_fields: ['internals', 'milestones'] + } + } + ) + ); + } +}; + +// other products step + +var otherProducts = { + onInit: function() { }, + + onShow: function() { + Dom.removeClass('advanced', 'hidden'); + } +}; + +// duplicates step + +var dupes = { + _counter: 0, + _dataTable: null, + _dataTableColumns: null, + _elSummary: null, + _elSearch: null, + _elList: null, + _currentSearchQuery: '', + + onInit: function() { + this._elSummary = Dom.get('dupes_summary'); + this._elSearch = Dom.get('dupes_search'); + this._elList = Dom.get('dupes_list'); + + Event.onBlur(this._elSummary, this._onSummaryBlur); + Event.addListener(this._elSummary, 'input', this._onSummaryBlur); + Event.addListener(this._elSummary, 'keydown', this._onSummaryKeyDown); + Event.addListener(this._elSummary, 'keyup', this._onSummaryKeyUp); + Event.addListener(this._elSearch, 'click', this._doSearch); + }, + + setLabels: function(labels) { + this._dataTableColumns = [ + { key: "id", label: labels.id, formatter: this._formatId }, + { key: "summary", label: labels.summary, formatter: "text" }, + { key: "component", label: labels.component, formatter: "text" }, + { key: "status", label: labels.status, formatter: this._formatStatus }, + { key: "update_token", label: '', formatter: this._formatCc } + ]; + }, + + _initDataTable: function() { + var dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi"); + dataSource.connTimeout = 15000; + dataSource.connMethodPost = true; + dataSource.connXhrMode = "cancelStaleRequests"; + dataSource.maxCacheEntries = 3; + dataSource.responseSchema = { + resultsList : "result.bugs", + metaFields : { error: "error", jsonRpcId: "id" } + }; + // DataSource can't understand a JSON-RPC error response, so + // we have to modify the result data if we get one. + dataSource.doBeforeParseData = + function(oRequest, oFullResponse, oCallback) { + if (oFullResponse.error) { + oFullResponse.result = {}; + oFullResponse.result.bugs = []; + if (console) + console.error("JSON-RPC error:", oFullResponse.error); + } + return oFullResponse; + }; + dataSource.subscribe('dataErrorEvent', + function() { + dupes._currentSearchQuery = ''; + } + ); + + this._dataTable = new YAHOO.widget.DataTable( + 'dupes_list', + this._dataTableColumns, + dataSource, + { + initialLoad: false, + MSG_EMPTY: 'No similar issues found.', + MSG_ERROR: 'An error occurred while searching for similar issues,' + + ' please try again.' + } + ); + }, + + _formatId: function(el, oRecord, oColumn, oData) { + el.innerHTML = '<a href="show_bug.cgi?id=' + oData + + '" target="_blank">' + oData + '</a>'; + }, + + _formatStatus: function(el, oRecord, oColumn, oData) { + var resolution = oRecord.getData('resolution'); + var bug_status = display_value('bug_status', oData); + if (resolution) { + el.innerHTML = bug_status + ' ' + + display_value('resolution', resolution); + } else { + el.innerHTML = bug_status; + } + }, + + _formatCc: function(el, oRecord, oColumn, oData) { + var cc = oRecord.getData('cc'); + var isCCed = false; + for (var i = 0, n = cc.length; i < n; i++) { + if (cc[i] == guided.currentUser) { + isCCed = true; + break; + } + } + dupes._buildCcHTML(el, oRecord.getData('id'), oRecord.getData('status'), + isCCed); + }, + + _buildCcHTML: function(el, id, bugStatus, isCCed) { + while (el.childNodes.length > 0) + el.removeChild(el.firstChild); + + var isOpen = false; + for (var i = 0, n = guided.openStates.length; i < n; i++) { + if (guided.openStates[i] == bugStatus) { + isOpen = true; + break; + } + } + + if (!isOpen && !isCCed) { + // you can't cc yourself to a closed bug here + return; + } + + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + if (isCCed) { + button.innerHTML = 'Stop following'; + button.onclick = function() { + dupes.updateFollowing(el, id, bugStatus, button, false); return false; + }; + } else { + button.innerHTML = 'Follow bug'; + button.onclick = function() { + dupes.updateFollowing(el, id, bugStatus, button, true); return false; + }; + } + el.appendChild(button); + }, + + updateFollowing: function(el, bugID, bugStatus, button, follow) { + button.disabled = true; + button.innerHTML = 'Updating...'; + + var ccObject; + if (follow) { + ccObject = { add: [ guided.currentUser ] }; + } else { + ccObject = { remove: [ guided.currentUser ] }; + } + + YAHOO.util.Connect.setDefaultPostHeader('application/json; charset=UTF-8'); + YAHOO.util.Connect.asyncRequest( + 'POST', + 'jsonrpc.cgi', + { + success: function(res) { + data = YAHOO.lang.JSON.parse(res.responseText); + if (data.error) + throw(data.error.message); + dupes._buildCcHTML(el, bugID, bugStatus, follow); + }, + failure: function(res) { + dupes._buildCcHTML(el, bugID, bugStatus, !follow); + if (res.responseText) + alert("Update failed:\n\n" + res.responseText); + } + }, + YAHOO.lang.JSON.stringify({ + version: "1.1", + method: "Bug.update", + id: ++this._counter, + params: { + ids: [ bugID ], + cc : ccObject + } + }) + ); + }, + + reset: function() { + this._elSummary.value = ''; + Dom.addClass(this._elList, 'hidden'); + Dom.addClass('dupes_continue', 'hidden'); + this._elList.innerHTML = ''; + this._showProductSupport(); + this._currentSearchQuery = ''; + }, + + _showProductSupport: function() { + var elSupport = Dom.get('product_support_' + + product.getName().replace(' ', '_').toLowerCase()); + var supportElements = Dom.getElementsByClassName('product_support'); + for (var i = 0, n = supportElements.length; i < n; i++) { + if (supportElements[i] == elSupport) { + Dom.removeClass(elSupport, 'hidden'); + } else { + Dom.addClass(supportElements[i], 'hidden'); + } + } + }, + + onShow: function() { + this._showProductSupport(); + this._onSummaryBlur(); + + // hide the advanced form and top continue button entry until + // a search has happened + Dom.addClass('advanced', 'hidden'); + Dom.addClass('dupes_continue_button_top', 'hidden'); + + if (!this._elSearch.disabled && this.getSummary().length >= 4) { + // do an immediate search after a page refresh if there's a query + this._doSearch(); + + } else { + // prepare for a search + this.reset(); + } + }, + + _onSummaryBlur: function() { + dupes._elSearch.disabled = dupes._elSummary.value == ''; + guided.setAdvancedLink(); + }, + + _onSummaryKeyDown: function(e) { + // map <enter> to doSearch() + if (e && (e.keyCode == 13)) { + dupes._doSearch(); + Event.stopPropagation(e); + } + }, + + _onSummaryKeyUp: function(e) { + // disable search button until there's a query + dupes._elSearch.disabled = YAHOO.lang.trim(dupes._elSummary.value) == ''; + }, + + _doSearch: function() { + if (dupes.getSummary().length < 4) { + alert('The summary must be at least 4 characters long.'); + return; + } + dupes._elSummary.blur(); + + // don't query if we already have the results (or they are pending) + if (dupes._currentSearchQuery == dupes.getSummary()) + return; + dupes._currentSearchQuery = dupes.getSummary(); + + // initialise the datatable as late as possible + dupes._initDataTable(); + + try { + // run the search + Dom.removeClass(dupes._elList, 'hidden'); + + dupes._dataTable.showTableMessage( + 'Searching for similar issues... ' + + '<img src="extensions/GuidedBugEntry/web/images/throbber.gif"' + + ' width="16" height="11">', + YAHOO.widget.DataTable.CLASS_LOADING + ); + var json_object = { + version: "1.1", + method: "Bug.possible_duplicates", + id: ++dupes._counter, + params: { + product: product._getNameAndRelated(), + summary: dupes.getSummary(), + limit: 12, + include_fields: [ "id", "summary", "status", "resolution", + "update_token", "cc", "component" ] + } + }; + + dupes._dataTable.getDataSource().sendRequest( + YAHOO.lang.JSON.stringify(json_object), + { + success: dupes._onDupeResults, + failure: dupes._onDupeResults, + scope: dupes._dataTable, + argument: dupes._dataTable.getState() + } + ); + + Dom.get('dupes_continue_button_top').disabled = true; + Dom.get('dupes_continue_button_bottom').disabled = true; + Dom.removeClass('dupes_continue', 'hidden'); + } catch(err) { + if (console) + console.error(err.message); + } + }, + + _onDupeResults: function(sRequest, oResponse, oPayload) { + Dom.removeClass('advanced', 'hidden'); + Dom.removeClass('dupes_continue_button_top', 'hidden'); + Dom.get('dupes_continue_button_top').disabled = false; + Dom.get('dupes_continue_button_bottom').disabled = false; + dupes._dataTable.onDataReturnInitializeTable(sRequest, oResponse, + oPayload); + }, + + getSummary: function() { + var summary = YAHOO.lang.trim(this._elSummary.value); + // work around chrome bug + if (summary == dupes._elSummary.getAttribute('placeholder')) { + return ''; + } else { + return summary; + } + } +}; + +// bug form step + +var bugForm = { + _visibleHelpPanel: null, + _mandatoryFields: [], + + onInit: function() { + Dom.get('user_agent').value = navigator.userAgent; + if (navigator.buildID && navigator.buildID != navigator.userAgent) { + Dom.get('build_id').value = navigator.buildID; + } + Event.addListener(Dom.get('short_desc'), 'blur', function() { + Dom.get('dupes_summary').value = Dom.get('short_desc').value; + guided.setAdvancedLink(); + }); + }, + + onShow: function() { + Dom.removeClass('advanced', 'hidden'); + // default the summary to the dupes query + Dom.get('short_desc').value = dupes.getSummary(); + this.resetSubmitButton(); + if (Dom.get('component_select').length == 0) + this.onProductUpdated(); + this.onFileChange(); + for (var i = 0, n = this._mandatoryFields.length; i < n; i++) { + Dom.removeClass(this._mandatoryFields[i], 'missing'); + } + }, + + resetSubmitButton: function() { + Dom.get('submit').disabled = false; + Dom.get('submit').value = 'Submit Bug'; + }, + + onProductUpdated: function() { + var productName = product.getName(); + + // init + var elComponents = Dom.get('component_select'); + Dom.addClass('component_description', 'hidden'); + elComponents.options.length = 0; + + var elVersions = Dom.get('version_select'); + elVersions.length = 0; + + // product not loaded yet, bail out + if (!product.details) { + Dom.addClass('versionTH', 'hidden'); + Dom.addClass('versionTD', 'hidden'); + Dom.get('productTD').colSpan = 2; + Dom.get('submit').disabled = true; + return; + } + Dom.get('submit').disabled = false; + + // build components + + var elComponent = Dom.get('component'); + if (products[productName] && products[productName].noComponentSelection) { + + elComponent.value = products[productName].defaultComponent; + bugForm._mandatoryFields = [ 'short_desc', 'version_select' ]; + + } else { + + bugForm._mandatoryFields = [ 'short_desc', 'component_select', 'version_select' ]; + + // check for the default component + var defaultRegex; + if (product.getPreselectedComponent()) { + defaultRegex = new RegExp('^' + quoteMeta(product.getPreselectedComponent()) + '$', 'i') + } else if(products[productName] && products[productName].defaultComponent) { + defaultRegex = new RegExp('^' + quoteMeta(products[productName].defaultComponent) + '$', 'i') + } else { + defaultRegex = new RegExp('General', 'i'); + } + + var preselectedComponent = false; + for (var i = 0, n = product.details.components.length; i < n; i++) { + var component = product.details.components[i]; + if (component.is_active == '1') { + if (defaultRegex.test(component.name)) { + preselectedComponent = component.name; + break; + } + } + } + + // if there isn't a default component, default to blank + if (!preselectedComponent) { + elComponents.options[elComponents.options.length] = new Option('', ''); + } + + // build component select + for (var i = 0, n = product.details.components.length; i < n; i++) { + var component = product.details.components[i]; + if (component.is_active == '1') { + elComponents.options[elComponents.options.length] = + new Option(component.name, component.name); + } + } + + var validComponent = false; + for (var i = 0, n = elComponents.options.length; i < n && !validComponent; i++) { + if (elComponents.options[i].value == elComponent.value) + validComponent = true; + } + if (!validComponent) + elComponent.value = ''; + if (elComponent.value == '' && preselectedComponent) + elComponent.value = preselectedComponent; + if (elComponent.value != '') { + elComponents.value = elComponent.value; + this.onComponentChange(elComponent.value); + } + + } + + // build versions + var defaultVersion = ''; + var currentVersion = Dom.get('version').value; + for (var i = 0, n = product.details.versions.length; i < n; i++) { + var version = product.details.versions[i]; + if (version.is_active == '1') { + elVersions.options[elVersions.options.length] = + new Option(version.name, version.name); + if (currentVersion == version.name) + defaultVersion = version.name; + } + } + + if (!defaultVersion) { + // try to detect version on a per-product basis + if (products[productName] && products[productName].version) { + var detectedVersion = products[productName].version(); + var options = elVersions.options; + for (var i = 0, n = options.length; i < n; i++) { + if (options[i].value == detectedVersion) { + defaultVersion = detectedVersion; + break; + } + } + } + } + if (!defaultVersion) { + // load last selected version + defaultVersion = YAHOO.util.Cookie.get('VERSION-' + productName); + } + + if (elVersions.length > 1) { + // more than one version, show select + Dom.get('productTD').colSpan = 1; + Dom.removeClass('versionTH', 'hidden'); + Dom.removeClass('versionTD', 'hidden'); + + } else { + // if there's only one version, we don't need to ask the user + Dom.addClass('versionTH', 'hidden'); + Dom.addClass('versionTD', 'hidden'); + Dom.get('productTD').colSpan = 2; + defaultVersion = elVersions.options[0].value; + } + + if (defaultVersion) { + elVersions.value = defaultVersion; + + } else { + // no default version, select an empty value to force a decision + var opt = new Option('', ''); + try { + // standards + elVersions.add(opt, elVersions.options[0]); + } catch(ex) { + // ie only + elVersions.add(opt, 0); + } + elVersions.value = ''; + } + bugForm.onVersionChange(elVersions.value); + }, + + onComponentChange: function(componentName) { + // show the component description + Dom.get('component').value = componentName; + var elComponentDesc = Dom.get('component_description'); + elComponentDesc.innerHTML = ''; + for (var i = 0, n = product.details.components.length; i < n; i++) { + var component = product.details.components[i]; + if (component.name == componentName) { + elComponentDesc.innerHTML = component.description; + break; + } + } + Dom.removeClass(elComponentDesc, 'hidden'); + }, + + onVersionChange: function(version) { + Dom.get('version').value = version; + }, + + onFileChange: function() { + // toggle ui enabled when a file is uploaded or cleared + var elFile = Dom.get('data'); + var elReset = Dom.get('reset_data'); + var elDescription = Dom.get('data_description'); + var filename = bugForm._getFilename(); + if (filename) { + elReset.disabled = false; + elDescription.value = filename; + elDescription.disabled = false; + } else { + elReset.disabled = true; + elDescription.value = ''; + elDescription.disabled = true; + } + }, + + onFileClear: function() { + Dom.get('data').value = ''; + this.onFileChange(); + return false; + }, + + toggleOddEven: function() { + var rows = Dom.get('bugForm').getElementsByTagName('TR'); + var doToggle = false; + for (var i = 0, n = rows.length; i < n; i++) { + if (doToggle) { + rows[i].className = rows[i].className == 'odd' ? 'even' : 'odd'; + } else { + doToggle = rows[i].id == 'componentTR'; + } + } + }, + + _getFilename: function() { + var filename = Dom.get('data').value; + if (!filename) + return ''; + filename = filename.replace(/^.+[\\\/]/, ''); + return filename; + }, + + _mandatoryMissing: function() { + var result = new Array(); + for (var i = 0, n = this._mandatoryFields.length; i < n; i++ ) { + id = this._mandatoryFields[i]; + el = Dom.get(id); + + if (el.type.toString() == "checkbox") { + value = el.checked; + } else { + value = el.value.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + el.value = value; + } + + if (value == '') { + Dom.addClass(id, 'missing'); + result.push(id); + } else { + Dom.removeClass(id, 'missing'); + } + } + return result; + }, + + validate: function() { + + // check mandatory fields + + var missing = bugForm._mandatoryMissing(); + if (missing.length) { + var message = 'The following field' + + (missing.length == 1 ? ' is' : 's are') + ' required:\n\n'; + for (var i = 0, n = missing.length; i < n; i++ ) { + var id = missing[i]; + if (id == 'short_desc') message += ' Summary\n'; + if (id == 'component_select') message += ' Component\n'; + if (id == 'version_select') message += ' Version\n'; + } + alert(message); + return false; + } + + if (Dom.get('data').value && !Dom.get('data_description').value) + Dom.get('data_description').value = bugForm._getFilename(); + + Dom.get('submit').disabled = true; + Dom.get('submit').value = 'Submitting Bug...'; + + return true; + }, + + _initHelp: function(el) { + var help_id = el.getAttribute('helpid'); + if (!el.panel) { + if (!el.id) + el.id = help_id + '_parent'; + el.panel = new YAHOO.widget.Panel( + help_id, + { + width: "320px", + visible: false, + close: false, + context: [el.id, 'tl', 'tr', null, [5, 0]] + } + ); + el.panel.render(); + Dom.removeClass(help_id, 'hidden'); + } + }, + + showHelp: function(el) { + this._initHelp(el); + if (this._visibleHelpPanel) + this._visibleHelpPanel.hide(); + el.panel.show(); + this._visibleHelpPanel = el.panel; + }, + + hideHelp: function(el) { + if (!el.panel) + return; + if (this._visibleHelpPanel) + this._visibleHelpPanel.hide(); + el.panel.hide(); + this._visibleHelpPanel = null; + } +} + +function quoteMeta(value) { + return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} diff --git a/extensions/GuidedBugEntry/web/js/products.js b/extensions/GuidedBugEntry/web/js/products.js new file mode 100644 index 000000000..9f9b62a14 --- /dev/null +++ b/extensions/GuidedBugEntry/web/js/products.js @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +/* Product-specifc configuration for guided bug entry + * + * related: array of product names which will also be searched for duplicates + * version: function which returns a version (eg. detected from UserAgent) + * support: string which is displayed at the top of the duplicates page + * secgroup: the group to place confidential bugs into + * defaultComponent: the default compoent to select. Defaults to 'General' + * noComponentSelection: when true, the default component will always be + * used. Defaults to 'false'; + * detectPlatform: when true the platform and op_sys will be set from the + * browser's user agent. when false, these will be set to All + */ + +var products = { + + "Firefox": { + related: [ "Core", "Toolkit" ], + version: function() { + var re = /Firefox\/(\d+)\.(\d+)/i; + var match = re.exec(navigator.userAgent); + if (match) { + var maj = match[1]; + var min = match[2]; + if (maj * 1 >= 5) { + return maj + " Branch"; + } else { + return maj + "." + min + " Branch"; + } + } else { + return false; + } + }, + defaultComponent: "Untriaged", + noComponentSelection: true, + detectPlatform: true, + support: + 'If you are new to Firefox or Bugzilla, please consider checking ' + + '<a href="http://support.mozilla.com/">' + + '<img src="extensions/GuidedBugEntry/web/images/sumo.png" width="16" height="16" align="absmiddle">' + + ' <b>Firefox Help</b></a> instead of creating a bug.' + }, + + "Fennec": { + related: [ "Firefox for Android", "Core", "Toolkit" ], + detectPlatform: true, + support: + 'If you are new to Firefox or Bugzilla, please consider checking ' + + '<a href="http://support.mozilla.com/">' + + '<img src="extensions/GuidedBugEntry/web/images/sumo.png" width="16" height="16" align="absmiddle">' + + ' <b>Firefox Help</b></a> instead of creating a bug.' + }, + + "Firefox for Android": { + related: [ "Fennec", "Core", "Toolkit" ], + detectPlatform: true, + support: + 'If you are new to Firefox or Bugzilla, please consider checking ' + + '<a href="http://support.mozilla.com/">' + + '<img src="extensions/GuidedBugEntry/web/images/sumo.png" width="16" height="16" align="absmiddle">' + + ' <b>Firefox Help</b></a> instead of creating a bug.' + }, + + "SeaMonkey": { + related: [ "Core", "Toolkit" ], + detectPlatform: true, + version: function() { + var re = /SeaMonkey\/(\d+)\.(\d+)/i; + var match = re.exec(navigator.userAgent); + if (match) { + var maj = match[1]; + var min = match[2]; + return "SeaMonkey " + maj + "." + min + " Branch"; + } else { + return false; + } + } + }, + + "Camino": { + related: [ "Core", "Toolkit" ], + detectPlatform: true + }, + + "Core": { + detectPlatform: true + }, + + "Thunderbird": { + related: [ "Core", "Toolkit", "MailNews Core" ], + detectPlatform: true + }, + + "Penelope": { + related: [ "Core", "Toolkit", "MailNews Core" ] + }, + + "Bugzilla": { + support: + 'Please use <a href="http://landfill.bugzilla.org/">Bugzilla Landfill</a> to file "test bugs".' + }, + + "bugzilla.mozilla.org": { + related: [ "Bugzilla" ], + support: + 'Please use <a href="http://landfill.bugzilla.org/">Bugzilla Landfill</a> to file "test bugs".' + } +} diff --git a/extensions/GuidedBugEntry/web/style/guided.css b/extensions/GuidedBugEntry/web/style/guided.css new file mode 100644 index 000000000..55550933f --- /dev/null +++ b/extensions/GuidedBugEntry/web/style/guided.css @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +/* global */ + +#page_title { +} + +#page_title h2 { + margin-bottom: 0px; +} + +#page_title h3 { + margin-top: 0px; +} + +.hidden { + display: none; +} + +#yui-history-iframe { + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 1px; + visibility: hidden; +} + +.step { + margin-left: 20px; + margin-bottom: 25px; +} + +#steps a img { + border: none; +} + +#advanced { + margin-top: 50px; +} + +#advanced img { + vertical-align: middle; +} + +#advanced a { + cursor: pointer; +} + +/* remove the shaded background from data_table header + it looks out of place */ +.yui-skin-sam .yui-dt th { + background: #f0f0f0; +} + +/* products and other_products step */ + +.exits { + width: 600px; + margin-bottom: 10px; + border: 1px solid #aaa; + border-radius: 5px; +} + +.exits td { + padding: 5px; +} + +.exits h2 { + margin: 0px; + font-size: 90%; +} + +.exit_img { + width: 64px; + text-align: right; +} + +#prod_comp_search_main { + width: 400px; +} + +#prod_comp_search_label { + margin-bottom: 1px; +} + +#prod_comp_search_main li.yui-ac-highlight a { + text-decoration: none; + color: #FFFFFF; + display: block; +} + +#products { + width: 600px; +} + +#products td { + padding: 5px; + padding-bottom: 10px; +} + +#products h2 { + margin-bottom: 0px; +} + +#products p { + margin-top: 0px; +} + +.product_img { + width: 64px; +} + +#other_products .classification { + font-weight: bold; +} + +#other_products .classification th { + font-size: large; +} + +/* duplicates step */ + +#dupes_summary { + width: 500px; +} + +#dupes_list { + margin-top: 1em; + margin-bottom: 1em; +} + +#product_support { + border: 1px solid #dddddd; +} + +/* bug form step */ + +#bugForm { + width: 600px; + border: 4px solid #e0e0e0; + -moz-border-radius: 5px; + border-radius: 5px; +} + +#bugForm th, #bugForm td { + padding: 5px; +} + +#bugForm .even th, #bugForm .even td { + background: #e0e0e0; +} + +#bugForm .label { + text-align: left; + font-weight: bold; + white-space: nowrap +} + +#bugzilla-body #bugForm th { + vertical-align: middle; +} + +#bugForm .textInput { + width: 450px; +} + +#bugForm textarea { + font-family: Verdana, sans-serif; + font-size: small; + width: 590px; +} + +#bugForm .mandatory_mark { + color: red; + font-size: 80%; +} + +#bugForm .mandatory { +} + +#bugForm .textInput[disabled] { + background: transparent; + border: 1px solid #dddddd; +} + +#versionTD { + text-align: right; + white-space: nowrap +} + +#component_select { + width: 450px; +} + +#component_description { + padding: 5px; +} + +#bugForm .missing { + border: 1px solid red; + box-shadow: 0px 0px 4px #ff0000; + -webkit-box-shadow: 0px 0px 4px #ff0000; + -moz-box-shadow: 0px 0px 4px #ff0000; +} + +#submitTD { + text-align: right; +} + +.help { + position: absolute; + background: #ffffff; + padding: 2px; + cursor: default; +} + +.help-bad { + color: #990000; +} + +.help-good { + color: #009900; +} diff --git a/extensions/GuidedBugEntry/web/yui-history-iframe.txt b/extensions/GuidedBugEntry/web/yui-history-iframe.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/extensions/GuidedBugEntry/web/yui-history-iframe.txt |