Closed Bug 634531 Opened 14 years ago Closed 14 years ago

add product/component watching extension to bmo

Categories

(bugzilla.mozilla.org :: General, enhancement)

enhancement
Not set
normal

Tracking

()

RESOLVED FIXED

People

(Reporter: glob, Assigned: glob)

References

Details

Attachments

(1 file, 3 obsolete files)

until bug 76794 has been resolved, we can implement very simple product and/or component watching functionality as an extension.
Attached patch component watching extension, v1 (obsolete) — Splinter Review
Assignee: nobody → bjones
Attachment #512719 - Flags: review?(dkl)
Comment on attachment 512719 [details] [diff] [review] component watching extension, v1 >+# The Original Code is the BMO Watching Ex Extension "Ex Extension" ? >+# The Initial Developer of the Original Code is Byron Jones "the Mozilla Foundation" >+sub end { >+ my ($self, $args) = @_; >+ use Data::Dumper; >+ die Dumper($args); >+} This looks debuggish. Probably shouldn't be here. :)
Attached patch component watching extension, v2 (obsolete) — Splinter Review
addresses issues from reed's drive-by
Attachment #512719 - Attachment is obsolete: true
Attachment #512719 - Flags: review?(dkl)
Comment on attachment 512780 [details] [diff] [review] component watching extension, v2 >=== added file 'extensions/ComponentWatching/Extension.pm' >+# >+# preferences >+# >+ >+sub user_preferences { >+ my ($self, $args) = @_; >+ my $tab = $args->{'current_tab'}; >+ return unless $tab eq 'componentwatch_tab'; nit-pick: s/componentwatch_tab/component_watch/ +# we have to monkeypatch $user->wants_bug_mail +our $_wants_bug_mail; +BEGIN { + no strict 'refs'; + no warnings 'redefine'; + $_wants_bug_mail = *Bugzilla::User::wants_bug_mail; + *Bugzilla::User::wants_bug_mail = *Bugzilla::Extension::ComponentWatching::componentWatch_wants_bug_mail; +} + +sub componentWatch_wants_bug_mail { + my ($self, $bug_id, $relationship, $fieldDiffs, $comments, $dependencyText, + $changer, $bug_is_new) = @_; + + if (+$relationship == REL_COMPONENT_WATCHER) { + return 1; + } else { + return &$_wants_bug_mail(@_); + } +} Would it be possible to just add some hooks in Bugzilla::User::wants_bug_mail and Bugzilla::User::wants_mail to accomplish this? Seems it could be useful for others to have that ability. We can oush the hooks upstream once they are working. >--- extensions/ComponentWatching/template/en/default/account/prefs/componentwatch_tab.html.tmpl 1970-01-01 00:00:00 +0000 >+++ extensions/ComponentWatching/template/en/default/account/prefs/componentwatch_tab.html.tmpl 2011-02-16 08:03:59 +0000 Nit: s/componentwatch_tab/component_watch/ >--- extensions/ComponentWatching/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl 1970-01-01 00:00:00 +0000 >+++ extensions/ComponentWatching/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl 2011-02-16 08:04:24 +0000 >+[% tabs = tabs.import([{ >+ name => "componentwatch_tab", >+ label => "Component/Product Watching", >+ link => "userprefs.cgi?tab=componentwatch_tab", >+ saveable => 1 >+ }]) %] Same name change here. The extension is effectively about component watching and selecting a Product/__Any__ means watch *all* components. So using the word Product everywhere is not really necessary. Looks good so far.
Attachment #512780 - Flags: review-
Attached patch component watching extension, v3 (obsolete) — Splinter Review
addresses review points
Attachment #512780 - Attachment is obsolete: true
Attachment #513034 - Flags: review?(dkl)
Blocks: bmo-upgrade
Status: NEW → ASSIGNED
dkl: can i get a review on this please?
Comment on attachment 513034 [details] [diff] [review] component watching extension, v3 >=== modified file 'Bugzilla/BugMail.pm' >--- Bugzilla/BugMail.pm 2011-02-15 21:30:47 +0000 >+++ Bugzilla/BugMail.pm 2011-02-17 05:09:27 +0000 >@@ -400,6 +400,19 @@ > $rels_which_want{$relationship} = > $recipients{$user_id}->{$relationship}; > } >+ # Upstreaming: TODO >+ Bugzilla::Hook::process('bugmail_user_wants', { >+ relationship_mail => \%rels_which_want, >+ user => $user, >+ bug_id => $id, >+ relationship => $relationship, >+ field_diffs => $diffs, >+ comments => $comments, >+ dependency_text => $deptext, >+ changer => $changer, >+ bug_is_new => !$start >+ }); >+ Refresh my memory. Would this be better suited in the end of Bugzilla::User::wants_bug_mail instead? Could be called user_wants_bug_mail then. Suppose technically works in either place but maybe would cleaner. >=== added file 'extensions/ComponentWatching/Extension.pm' >+use Bugzilla::Util qw(diff_arrays html_quote); >+use Bugzilla::Status qw(is_open_state); Doesn't seem like any of these are used anywhere. >+sub user_preferences { >+ my ($self, $args) = @_; >+ my $tab = $args->{'current_tab'}; >+ return unless $tab eq 'component_watch'; >+ >+ my $save = $args->{'save_changes'}; >+ my $handled = $args->{'handled'}; >+ my $user = Bugzilla->user; >+ >+ if ($save) { >+ my ($sth, $sthAdd, $sthDel); >+ >+ if (Bugzilla->input_params->{'add'}) { >+ # add watch >+ >+ my $productName = Bugzilla->input_params->{'add_product'}; >+ my $componentName = Bugzilla->input_params->{'add_component'}; >+ >+ # load product and verify access >+ my $product = Bugzilla::Product->new({ name => $productName }); >+ unless ($product && $user->can_access_product($product)) { >+ ThrowUserError('product_access_denied', { product => $productName }); >+ } >+ >+ my $component; >+ if ($componentName) { >+ >+ # watching a specific component >+ $component = Bugzilla::Component->new({ name => $componentName, product => $product }); >+ unless ($component) { >+ ThrowUserError('product_access_denied', { product => $productName }); >+ } >+ _addComponentWatch($user, $component); >+ >+ } else { >+ # watching a product >+ _addProductWatch($user, $product); >+ } >+ >+ } else { >+ # remove watch(s) >+ >+ foreach my $name (keys %{Bugzilla->input_params}) { >+ if ($name =~ /^del_(\d+)$/) { >+ _deleteProductWatch($user, $1); >+ } elsif ($name =~ /^del_(\d+)_(\d+)$/) { >+ _deleteComponentWatch($user, $1, $2); >+ } >+ } >+ } >+ } >+ >+ $args->{'vars'}->{'watches'} = _getWatches($user); >+ >+ $$handled = 1; >+} >+ >+# >+# bugmail >+# >+ >+sub bugmail_recipients { >+ my ($self, $args) = @_; >+ my $bug = $args->{'bug'}; >+ my $recipients = $args->{'recipients'}; >+ my $diffs = $args->{'diffs'}; >+ >+ my ($oldProductId, $newProductId) = ($bug->product_id, $bug->product_id); >+ my ($oldComponentId, $newComponentId) = ($bug->component_id, $bug->component_id); >+ >+ # notify when the product/component is switch from one being watched >+ if (@$diffs) { >+ # we need the product to process the component, so scan for that first >+ my $product; >+ foreach my $ra (@$diffs) { >+ my (undef, undef, undef, undef, $old, $new, undef, $field) = @$ra; >+ if ($field eq 'product') { >+ $product = Bugzilla::Product->new({ name => $old }); >+ $oldProductId = $product->id; >+ } >+ } >+ if (!$product) { >+ $product = Bugzilla::Product->new($oldProductId); >+ } >+ foreach my $ra (@$diffs) { >+ my (undef, undef, undef, undef, $old, $new, undef, $field) = @$ra; >+ if ($field eq 'component') { >+ my $component = Bugzilla::Component->new({ name => $old, product => $product }); >+ $oldComponentId = $component->id; >+ } >+ } >+ } >+ >+ my $dbh = Bugzilla->dbh; >+ my $sth = $dbh->prepare(" >+ SELECT user_id >+ FROM component_watch >+ WHERE ((product_id = ? OR product_id = ?) AND component_id IS NULL) >+ OR (component_id = ? OR component_id = ?) >+ "); >+ $sth->execute($oldProductId, $newProductId, $oldComponentId, $newComponentId); >+ while (my ($uid) = $sth->fetchrow_array) { >+ if (!exists $recipients->{$uid}) { >+ $recipients->{$uid}->{+REL_COMPONENT_WATCHER} = 1; >+ } >+ } >+} >+ >+sub bugmail_user_wants { >+ my ($self, $args) = @_; >+ my $relationship = $args->{'relationship'}; >+ >+ if (+$relationship == REL_COMPONENT_WATCHER) { >+ $args->{'relationship_mail'}{$relationship} = 1; >+ } >+} >+ >+sub bugmail_relationships { >+ my ($self, $args) = @_; >+ my $relationships = $args->{relationships}; >+ >+ $relationships->{+REL_COMPONENT_WATCHER} = 'Component-Watcher'; >+} >+ >+# >+# db >+# >+ >+sub _getWatches { >+ my ($user) = @_; >+ my $dbh = Bugzilla->dbh; >+ >+ my $sth = $dbh->prepare(" >+ SELECT product_id, component_id >+ FROM component_watch >+ WHERE user_id = ? >+ "); >+ $sth->execute($user->id); >+ my @watches; >+ while (my ($productId, $componentId) = $sth->fetchrow_array) { >+ my $product = Bugzilla::Product->new($productId); >+ next unless $product && $user->can_access_product($product); >+ >+ my %watch = ( product => $product ); >+ if ($componentId) { >+ my $component = Bugzilla::Component->new($componentId); >+ next unless $component; >+ $watch{'component'} = $component; >+ } >+ >+ push @watches, \%watch; >+ } >+ >+ @watches = sort { >+ $a->{'product'}->name cmp $b->{'product'}->name >+ || $a->{'component'}->name cmp $b->{'component'}->name >+ } @watches; >+ >+ return \@watches; >+} >+ >+sub _addProductWatch { >+ my ($user, $product) = @_; >+ my $dbh = Bugzilla->dbh; >+ >+ my $sth = $dbh->prepare(" >+ SELECT 1 >+ FROM component_watch >+ WHERE user_id = ? AND product_id = ? AND component_id IS NULL >+ "); >+ $sth->execute($user->id, $product->id); >+ return if $sth->fetchrow_array; >+ >+ $sth = $dbh->prepare(" >+ DELETE FROM component_watch >+ WHERE user_id = ? AND product_id = ? >+ "); >+ $sth->execute($user->id, $product->id); >+ >+ $sth = $dbh->prepare(" >+ INSERT INTO component_watch(user_id, product_id) >+ VALUES (?, ?) >+ "); >+ $sth->execute($user->id, $product->id); >+} >+ >+sub _addComponentWatch { >+ my ($user, $component) = @_; >+ my $dbh = Bugzilla->dbh; >+ >+ my $sth = $dbh->prepare(" >+ SELECT 1 >+ FROM component_watch >+ WHERE user_id = ? >+ AND (component_id = ? OR (product_id = ? AND component_id IS NULL)) >+ "); >+ $sth->execute($user->id, $component->id, $component->product_id); >+ return if $sth->fetchrow_array; >+ >+ $sth = $dbh->prepare(" >+ INSERT INTO component_watch(user_id, product_id, component_id) >+ VALUES (?, ?, ?) >+ "); >+ $sth->execute($user->id, $component->product_id, $component->id); >+} >+ >+sub _deleteProductWatch { >+ my ($user, $productId) = @_; >+ my $dbh = Bugzilla->dbh; >+ >+ my $sth = $dbh->prepare(" >+ DELETE FROM component_watch >+ WHERE user_id = ? AND product_id = ? AND component_id IS NULL >+ "); >+ $sth->execute($user->id, $productId); >+} >+ >+sub _deleteComponentWatch { >+ my ($user, $productId, $componentId) = @_; >+ my $dbh = Bugzilla->dbh; >+ >+ my $sth = $dbh->prepare(" >+ DELETE FROM component_watch >+ WHERE user_id = ? AND product_id = ? AND component_id = ? >+ "); >+ $sth->execute($user->id, $productId, $componentId); >+} >+ >+__PACKAGE__->NAME; >=== added file 'extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl' >+[% FOREACH prod = Bugzilla.user.get_selectable_products %] Nit: Just use user.get_selectable_products >+ cpts['[% n %]'] = [ >+ [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ]; >+ [% n = n+1 %] >+[% END %] >+</script> >+<script type="text/javascript" >+ src="[% 'js/productform.js' FILTER mtime FILTER html %]"> Nit: indent src= >+ <select name="add_product" id="product" onChange="onSelectProduct()"> >+ [% FOREACH product IN Bugzilla.user.get_selectable_products %] >+ <option>[% product.name FILTER html %]</option> >+ [% END %] Same nit: user.get_selectable_products >+ <select name="add_component" id="component"> >+ <option value="">__Any__</option> >+ [% FOREACH product IN Bugzilla.user.get_selectable_products %] >+ [% FOREACH component IN product.components %] >+ <option>[% component.name FILTER html %]</option> >+ [% END %] And here. Maybe do [% SET selectable_products = user.get_selectable_products %] at the top of them template and use that? >+ <td>&nbsp;</td> >+ <td><input type="submit" name="add" value="Add"></td> Needs id="commit" or similar for later automated testing. >+[% FOREACH watch IN watches %] >+ [% IF (watch.component) %] >+ <tr> >+ <td><input type="checkbox" name="del_[% watch.product.id %]_[% watch.component.id %]" value="1"></td> >+ <td>[% watch.component.product.name FILTER html %]</td> >+ <td>[% watch.component.name FILTER html %]</td> >+ </tr> >+ [% ELSE %] >+ <tr> >+ <td><input type="checkbox" name="del_[% watch.product.id %]" value="1"></td> >+ <td>[% watch.product.name FILTER html %]</td> >+ <td>__Any__</td> >+ </tr> >+ [% END %] Nit: place <tr> and </tr> outside of the [% IF ELSE END %] Another comment I had that may be misleading is that in the email headers, I get the following output: X-Bugzilla-Reason: Component-Watcher X-Bugzilla-Watch-Reason: None It may make more sense for it to be: X-Bugzilla-Watch-Reason: Component-Watcher instead but looking at the code in BugMail.pm, it may not be that simple to implement. Otherwise looks good. Minor changes. Dave
Attachment #513034 - Flags: review?(dkl) → review-
thanks dave. i can't recall exactly why the hook ended up in BugMail instead of User; i suspect it organically ended up there as i worked through the issues. it is cleaner in User, so i've moved it there as per your suggestion. this revision fixes that, and your other review points.
Attachment #513034 - Attachment is obsolete: true
Attachment #521107 - Flags: review?(dkl)
Comment on attachment 521107 [details] [diff] [review] component watching extension, v4 >- <td><input type="submit" name="add" value="Add"></td> >+ <td><input type="submit" id="commit" name="add" value="Add"></td> Nit: Change to id="add" instead of id="commit" and this is good to check in. r=dkl
Attachment #521107 - Flags: review?(dkl) → review+
bzr threw an error while committing, however it looks ok: $ bzr commit --fixes mozilla:634531 -m 'Bug 634531: Add component watching extension' Committing to: bzr+ssh://bjones%40mozilla.com@bzr.mozilla.org/bmo/4.0/ modified Bugzilla/BugMail.pm modified Bugzilla/User.pm modified extensions/ComponentWatching/Extension.pm modified extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl bzr: ERROR: Server sent an unexpected error: ('error', 'math range error') $ bzr commit --fixes mozilla:634531 -m 'Bug 634531: Add component watching extension' bzr: ERROR: Bound branch BzrBranch7(file:///cygdrive/c/dev/bugzilla/repo/bmo/4.0/) is out of date with master branch RemoteBranch(bzr+ssh://bjones%40mozilla.com@bzr.mozilla.org/bmo/4.0/). To commit to master branch, run update and then commit. You can also pass --local to commit to continue working disconnected. $ bzr up All changes applied successfully. Updated to revision 7577 of branch bzr+ssh://bjones%40mozilla.com@bzr.mozilla.org/bmo/4.0 byron@Glob-Laptop ~/bugzilla/repo/bmo/4.0 $ bzr st byron@Glob-Laptop ~/bugzilla/repo/bmo/4.0 $
Status: ASSIGNED → RESOLVED
Closed: 14 years ago
Resolution: --- → FIXED
Component: Bugzilla: Other b.m.o Issues → General
Product: mozilla.org → bugzilla.mozilla.org
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: