launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02997
[Merge] lp:~gmb/launchpad/make-+subscribe-play-nice-bug-735397 into lp:launchpad
Graham Binns has proposed merging lp:~gmb/launchpad/make-+subscribe-play-nice-bug-735397 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #735397 in Launchpad itself: "Update the +subscribe page to handle muted subscriptions properly"
https://bugs.launchpad.net/launchpad/+bug/735397
For more details, see:
https://code.launchpad.net/~gmb/launchpad/make-+subscribe-play-nice-bug-735397/+merge/53839
This branch makes the +subscribe view for a bug, and its associated
overlay on the bug page, play nice with muted subscriptions.
== lib/lp/bugs/browser/bugsubscription.py ==
- I've updated BugSubscriptionSubscribeSelfView so that it handles
muted subscriptions properly and presents options related to unmuting
to the user.
== lib/lp/bugs/browser/tests/test_bugsubscription_views.py ==
- I've added tests to cover the changes I've made to
BugSubscriptionSubscribeSelfView.
== lib/lp/bugs/configure.zcml ==
- I've updated the IBug declaration to include the mute() and unmute()
methods.
== lib/lp/bugs/interfaces/bug.py ==
- I've added two methods, mute() and unmute() to IBug. These are needed
for two reasons:
1. I want to not have to fool around with subscriptions in the JS
unless I need to (see below).
2. We may want to replace the mute/unmute via subscriptions
functionality with something else (like a BugMute table). Having
these two methods means we can do it reasonably simply without
mucking about with View and JS too much.
== lib/lp/bugs/javascript/bug_subscription.js ==
- I've added this file with some JavaScript to add UI polish to the
+subscribe view. This should help make the view a little less
confusing when people hit it whilst they have a mute on a bug.
== lib/lp/bugs/javascript/bugtask_index_portlets.js ==
- I've made quite a lot of changes here, including several
refactorings. The main points are:
- Clicking Mute will now remove you from the subscribers list if
you're there.
- If you're muted and you click Subscribe, you'll see the updated
version of the +subscribe form, with all the JS goodness I've added
in bug_subscription.js.
- Using the Subscribe link to unmute and resubscribe really just
updates your subscription, but it also makes all the UI elements on
the BugTask:+index page play nice.
== lib/lp/bugs/model/bug.py ==
- I've added implementations of IBug.mute() and unmute().
== lib/lp/bugs/templates/bug-subscription.pt ==
- I've updated the page to include the JS calls for
bug_subscription.js.
== lib/lp/bugs/tests/test_bug.py ==
- I've added tests for Bug.mute() and unmute().
--
https://code.launchpad.net/~gmb/launchpad/make-+subscribe-play-nice-bug-735397/+merge/53839
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~gmb/launchpad/make-+subscribe-play-nice-bug-735397 into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
--- lib/lp/bugs/browser/bugsubscription.py 2011-03-10 12:42:35 +0000
+++ lib/lp/bugs/browser/bugsubscription.py 2011-03-17 15:02:39 +0000
@@ -222,9 +222,20 @@
@cachedproperty
def _update_subscription_term(self):
+ if self.user_is_muted:
+ label = "Unmute bug mail from this bug and subscribe me to it"
+ else:
+ label = "Update my current subscription"
return SimpleTerm(
- 'update-subscription', 'update-subscription',
- 'Update my current subscription')
+ 'update-subscription', 'update-subscription', label)
+
+ @cachedproperty
+ def _unsubscribe_current_user_term(self):
+ if self._use_advanced_features and self.user_is_muted:
+ label = "Unmute bug mail from this bug"
+ else:
+ label = 'Unsubscribe me from this bug'
+ return SimpleTerm(self.user, self.user.name, label)
@cachedproperty
def _subscription_field(self):
@@ -233,12 +244,12 @@
for person in self._subscribers_for_current_user:
if person.id == self.user.id:
if (self._use_advanced_features and
- self.user_is_subscribed_directly):
- subscription_terms.append(self._update_subscription_term)
+ (self.user_is_subscribed_directly or
+ self.user_is_muted)):
+ subscription_terms.append(
+ self._update_subscription_term)
subscription_terms.insert(
- 0, SimpleTerm(
- person, person.name,
- 'Unsubscribe me from this bug'))
+ 0, self._unsubscribe_current_user_term)
self_subscribed = True
else:
subscription_terms.append(
@@ -279,6 +290,8 @@
"""See `LaunchpadFormView`."""
super(BugSubscriptionSubscribeSelfView, self).setUpWidgets()
if self._use_advanced_features:
+ self.widgets['bug_notification_level'].widget_class = (
+ 'bug-notification-level-field')
if self._subscriber_count_for_current_user == 0:
# We hide the subscription widget if the user isn't
# subscribed, since we know who the subscriber is and we
@@ -298,14 +311,22 @@
self.widgets['bug_notification_level'].visible = False
@cachedproperty
+ def user_is_muted(self):
+ return self.context.bug.isMuted(self.user)
+
+ @cachedproperty
def user_is_subscribed_directly(self):
"""Is the user subscribed directly to this bug?"""
- return self.context.bug.isSubscribed(self.user)
+ return (
+ self.context.bug.isSubscribed(self.user) and not
+ self.user_is_muted)
@cachedproperty
def user_is_subscribed_to_dupes(self):
"""Is the user subscribed to dupes of this bug?"""
- return self.context.bug.isSubscribedToDupes(self.user)
+ return (
+ self.context.bug.isSubscribedToDupes(self.user) and not
+ self.user_is_muted)
@property
def user_is_subscribed(self):
@@ -347,8 +368,10 @@
bug_notification_level = None
if (subscription_person == self._update_subscription_term.value and
- self.user_is_subscribed):
+ (self.user_is_subscribed or self.user_is_muted)):
self._handleUpdateSubscription(level=bug_notification_level)
+ elif self.user_is_muted and subscription_person == self.user:
+ self._handleUnsubscribeCurrentUser()
elif (not self.user_is_subscribed and
(subscription_person == self.user)):
self._handleSubscribe(bug_notification_level)
=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-03-10 12:42:35 +0000
+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-03-17 15:02:39 +0000
@@ -29,6 +29,9 @@
def setUp(self):
super(BugSubscriptionAdvancedFeaturesTestCase, self).setUp()
+ self.bug = self.factory.makeBug()
+ self.person = self.factory.makePerson()
+ self.team = self.factory.makeTeam()
with feature_flags():
set_feature_flag(u'malone.advanced-subscriptions.enabled', u'on')
@@ -276,6 +279,102 @@
BugNotificationLevel.COMMENTS,
default_notification_level_value)
+ def test_muted_subs_have_unmute_option(self):
+ # If a user has a muted subscription, the
+ # BugSubscriptionSubscribeSelfView's subscription field will
+ # show an "Unmute" option.
+ with person_logged_in(self.person):
+ self.bug.mute(self.person, self.person)
+
+ with feature_flags():
+ with person_logged_in(self.person):
+ subscribe_view = create_initialized_view(
+ self.bug.default_bugtask, name='+subscribe')
+ subscription_widget = (
+ subscribe_view.widgets['subscription'])
+ # The Unmute option is actually treated the same way as
+ # the unsubscribe option.
+ self.assertEqual(
+ "Unmute bug mail from this bug",
+ subscription_widget.vocabulary.getTerm(self.person).title)
+
+ def test_muted_subs_have_unmute_and_update_option(self):
+ # If a user has a muted subscription, the
+ # BugSubscriptionSubscribeSelfView's subscription field will
+ # show an option to unmute the subscription and update it to a
+ # new BugNotificationLevel.
+ with person_logged_in(self.person):
+ self.bug.mute(self.person, self.person)
+
+ with feature_flags():
+ with person_logged_in(self.person):
+ subscribe_view = create_initialized_view(
+ self.bug.default_bugtask, name='+subscribe')
+ subscription_widget = (
+ subscribe_view.widgets['subscription'])
+ update_term = subscription_widget.vocabulary.getTermByToken(
+ 'update-subscription')
+ self.assertEqual(
+ "Unmute bug mail from this bug and subscribe me to it",
+ update_term.title)
+
+ def test_unmute_unmutes(self):
+ # Using the "Unmute bug mail" option when the user has a muted
+ # subscription will remove the muted subscription.
+ with person_logged_in(self.person):
+ self.bug.mute(self.person, self.person)
+
+ with feature_flags():
+ with person_logged_in(self.person):
+ level = BugNotificationLevel.METADATA
+ form_data = {
+ 'field.subscription': self.person.name,
+ # Although this isn't used we must pass it for the
+ # sake of form validation.
+ 'field.bug_notification_level': level.title,
+ 'field.actions.continue': 'Continue',
+ }
+ subscribe_view = create_initialized_view(
+ self.bug.default_bugtask, form=form_data,
+ name='+subscribe')
+ self.assertFalse(self.bug.isMuted(self.person))
+ self.assertFalse(self.bug.isSubscribed(self.person))
+
+ def test_update_when_muted_updates(self):
+ # Using the "Unmute and subscribe me" option when the user has a
+ # muted subscription will update the existing subscription to a
+ # new BugNotificationLevel.
+ with person_logged_in(self.person):
+ muted_subscription = self.bug.mute(self.person, self.person)
+
+ with feature_flags():
+ with person_logged_in(self.person):
+ level = BugNotificationLevel.COMMENTS
+ form_data = {
+ 'field.subscription': 'update-subscription',
+ 'field.bug_notification_level': level.title,
+ 'field.actions.continue': 'Continue',
+ }
+ subscribe_view = create_initialized_view(
+ self.bug.default_bugtask, form=form_data,
+ name='+subscribe')
+ self.assertFalse(self.bug.isMuted(self.person))
+ self.assertTrue(self.bug.isSubscribed(self.person))
+ self.assertEqual(
+ level, muted_subscription.bug_notification_level)
+
+ def test_bug_notification_level_field_has_widget_class(self):
+ # The bug_notification_level widget has a widget_class property
+ # that can be used to manipulate it with JavaScript.
+ with person_logged_in(self.person):
+ with feature_flags():
+ subscribe_view = create_initialized_view(
+ self.bug.default_bugtask, name='+subscribe')
+ widget_class = (
+ subscribe_view.widgets['bug_notification_level'].widget_class)
+ self.assertEqual(
+ 'bug-notification-level-field', widget_class)
+
class BugPortletSubcribersIdsTests(TestCaseWithFactory):
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2011-03-15 04:15:45 +0000
+++ lib/lp/bugs/configure.zcml 2011-03-17 15:02:39 +0000
@@ -782,6 +782,7 @@
linkMessage
markAsDuplicate
markUserAffected
+ mute
newMessage
removeWatch
setPrivate
@@ -791,6 +792,7 @@
unlinkBranch
unlinkCVE
unlinkHWSubmission
+ unmute
unsubscribe
unsubscribeFromDupes
updateHeat"
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2011-03-15 04:15:45 +0000
+++ lib/lp/bugs/interfaces/bug.py 2011-03-17 15:02:39 +0000
@@ -32,6 +32,7 @@
export_write_operation,
exported,
mutator_for,
+ operation_for_version,
operation_parameters,
operation_returns_collection_of,
operation_returns_entry,
@@ -489,6 +490,22 @@
with a BugNotificationLevel of NOTHING.
"""
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=False))
+ @call_with(muted_by=REQUEST_USER)
+ @export_write_operation()
+ @operation_for_version('devel')
+ def mute(person, muted_by):
+ """Add a muted subscription for `person`."""
+
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=False))
+ @call_with(unmuted_by=REQUEST_USER)
+ @export_write_operation()
+ @operation_for_version('devel')
+ def unmute(person, unmuted_by):
+ """Remove a muted subscription for `person`."""
+
def getDirectSubscriptions():
"""A sequence of IBugSubscriptions directly linked to this bug."""
=== added file 'lib/lp/bugs/javascript/bug_subscription.js'
--- lib/lp/bugs/javascript/bug_subscription.js 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/bug_subscription.js 2011-03-17 15:02:39 +0000
@@ -0,0 +1,59 @@
+/* Copyright 2011 Canonical Ltd. This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * A bugtracker form overlay that can create a bugtracker within any page.
+ *
+ * @namespace Y.lp.bugs.bugtracker_overlay
+ * @requires dom, node, io-base, lazr.anim, lazr.formoverlay
+ */
+YUI.add('lp.bugs.bug_subscription', function(Y) {
+var namespace = Y.namespace('lp.bugs.bug_subscription');
+var level_div;
+
+// XXX: gmb 2011-03-17 bug=728457
+// This fix for resizing needs to be incorporated into
+// lazr.effects. When that is done it should be removed from here.
+/*
+ * Make sure that the bug_notification_level div is hidden properly .
+ * @method clean_up_level_div
+ */
+namespace.clean_up_level_div = function() {
+ if (Y.Lang.isValue(level_div)) {
+ level_div.setStyles({
+ height: 0,
+ visibility: 'hidden'
+ });
+ }
+};
+
+namespace.set_up_bug_notification_level_field = function() {
+ level_div = Y.one('.bug-notification-level-field');
+ var subscription_radio_buttons = Y.all(
+ 'input[name=field.subscription]');
+
+ // Only collapse the bug_notification_level field if the buttons are
+ // available to display it again.
+ if (Y.Lang.isValue(level_div) && subscription_radio_buttons.size() > 1) {
+ var slide_in_anim = Y.lazr.effects.slide_in(level_div);
+ slide_in_anim.on('end', namespace.clean_up_level_div);
+ var slide_out_anim = Y.lazr.effects.slide_out(level_div);
+ slide_out_anim.on('end', function() {
+ level_div.setStyles({
+ visibility: 'visible'
+ });
+ });
+ slide_in_anim.run();
+ Y.each(subscription_radio_buttons, function(subscription_button) {
+ subscription_button.on('click', function(e) {
+ if(e.target.get('value') == 'update-subscription') {
+ slide_out_anim.run();
+ } else {
+ slide_in_anim.run();
+ }
+ });
+ });
+ }
+};
+
+}, "0.1", {"requires": ["dom", "node", "io-base", "lazr.anim", "lazr.effects",
+ ]});
=== modified file 'lib/lp/bugs/javascript/bugtask_index_portlets.js'
--- lib/lp/bugs/javascript/bugtask_index_portlets.js 2011-03-14 14:39:33 +0000
+++ lib/lp/bugs/javascript/bugtask_index_portlets.js 2011-03-17 15:02:39 +0000
@@ -247,13 +247,10 @@
}
/*
- * Set up the handlers for the mute / unmute link.
+ * Set up and return a Subscription object for the mute link.
+ * @method get_mute_subscription
*/
-function setup_mute_link_handlers() {
- if (LP.links.me === undefined) {
- return;
- }
-
+function get_mute_subscription() {
setup_client_and_bug();
var mute_link = Y.one('.menu-link-mute_subscription');
var mute_subscription = new Y.lp.bugs.subscriber.Subscription({
@@ -264,41 +261,56 @@
subscriber_ids: subscriber_ids
})
});
+ var parent_node = mute_link.get('parentNode');
+ mute_subscription.set(
+ 'is_subscribed', !parent_node.hasClass('subscribed-false'));
+ mute_subscription.set(
+ 'is_muted', parent_node.hasClass('muted-true'));
mute_subscription.set(
'person', mute_subscription.get('subscriber'));
+ return mute_subscription;
+}
+
+/*
+ * Set up the handlers for the mute / unmute link.
+ */
+function setup_mute_link_handlers() {
+ if (LP.links.me === undefined) {
+ return;
+ }
+
+ var mute_subscription = get_mute_subscription();
+ var mute_link = mute_subscription.get('link');
+ var parent_node = mute_link.get('parentNode');
mute_link.addClass('js-action');
mute_link.on('click', function(e) {
e.halt();
- var parent_node = mute_link.get('parentNode');
- var is_subscribed = !parent_node.hasClass('subscribed-false');
var is_muted = parent_node.hasClass('muted-true');
mute_subscription.enable_spinner('Muting...');
if (! is_muted) {
- mute_subscription.set(
- 'bug_notification_level', "Nothing");
- success_callback = function() {
- parent_node.removeClass('muted-false');
- parent_node.addClass('muted-true');
- mute_subscription.set('is_muted', true);
- update_subscription_after_mute_or_unmute(
- mute_subscription);
- }
- mute_current_user(mute_subscription, success_callback);
+ mute_current_user(mute_subscription);
} else {
- success_callback = function() {
- parent_node.removeClass('muted-true');
- parent_node.addClass('muted-false');
- mute_subscription.set('is_muted', false);
- update_subscription_after_mute_or_unmute(
- mute_subscription);
- }
- unmute_current_user(mute_subscription, success_callback);
+ unmute_current_user(mute_subscription);
}
- mute_subscription.disable_spinner();
});
}
/*
+ * Update the Mute link after the user's subscriptions or mutes have
+ * changed.
+ */
+function update_mute_after_subscription_change(mute_subscription) {
+ var parent_node = mute_subscription.get('link').get('parentNode');
+ if (mute_subscription.get('is_muted')) {
+ parent_node.removeClass('muted-false');
+ parent_node.addClass('muted-true');
+ } else {
+ parent_node.removeClass('muted-true');
+ parent_node.addClass('muted-false');
+ }
+}
+
+/*
* Update the subscription links after the mute button has been clicked.
*
* @param mute_subscription {Object} A Y.lp.bugs.subscriber.Subscription
@@ -593,6 +605,13 @@
centered: true,
visible: false
});
+ // Register a couple of handlers to clean up the overlay when it's
+ // hidden.
+ subscription_overlay.get('form_cancel_button').on(
+ 'click',
+ Y.lp.bugs.bug_subscription.clean_up_level_div);
+ subscription_overlay.on(
+ 'cancel', Y.lp.bugs.bug_subscription.clean_up_level_div);
subscription_overlay.render('#privacy-form-container');
return subscription_overlay;
}
@@ -615,6 +634,7 @@
subscription_overlay.set(
'form_submit_callback', function(form_data) {
handle_advanced_subscription_overlay(subscription, form_data);
+ Y.lp.bugs.bug_subscription.clean_up_level_div();
subscription_overlay.hide();
subscription_overlay.loadFormContentAndRender(
subscription_link_url);
@@ -629,6 +649,13 @@
subscription_overlay.renderUI();
subscription_overlay.bindUI();
subscription.disable_spinner();
+
+ // If the user has a mute on the bug we add some UI polish to
+ // the subscription overlay.
+ var mute_subscription = get_mute_subscription();
+ if(mute_subscription.get('is_muted')) {
+ Y.lp.bugs.bug_subscription.set_up_bug_notification_level_field();
+ }
subscription_overlay.show();
}
function on_failure(id, response, subscription_overlay) {
@@ -784,9 +811,8 @@
*
* @method mute_current_user
* @param subscription {Object} A Y.lp.bugs.subscribe.Subscription object.
- * @param success_callback {Object} A function to be called on success.
*/
-function mute_current_user(subscription, success_callback) {
+function mute_current_user(subscription) {
subscription.enable_spinner('Muting...');
var subscription_link = subscription.get('link');
var subscriber = subscription.get('subscriber');
@@ -805,11 +831,25 @@
success: function(client) {
subscription.disable_spinner('Unmute bug mail');
var flash_node = subscription_link.get('parentNode');
- var anim = Y.lazr.anim.green_flash({ node: flash_node });
- anim.run();
- if (Y.Lang.isValue(success_callback)) {
- success_callback();
+ var mute_anim = Y.lazr.anim.green_flash({ node: flash_node });
+ mute_anim.run();
+
+ // Remove the subscriber's name from the subscriber
+ // list, if it's there.
+ var subscriber_node = Y.one('.' + subscriber.get('css_name'));
+ if (Y.Lang.isValue(subscriber_node)) {
+ var subscriber_anim = Y.lazr.anim.green_flash({
+ node: subscriber_node
+ });
+ subscriber_anim.on('end', function(e) {
+ remove_user_name_link(subscriber_node);
+ set_none_for_empty_subscribers();
+ });
+ subscriber_anim.run();
}
+ subscription.set('is_muted', true);
+ update_mute_after_subscription_change(subscription);
+ update_subscription_after_mute_or_unmute(subscription);
},
failure: error_handler.getFailureHandler()
@@ -817,12 +857,10 @@
parameters: {
person: Y.lp.client.get_absolute_uri(
- subscriber.get('escaped_uri')),
- suppress_notify: false,
- level: bug_notification_level
+ subscriber.get('escaped_uri'))
}
};
- lp_client.named_post(bug_repr.self_link, 'subscribe', config);
+ lp_client.named_post(bug_repr.self_link, 'mute', config);
}
/*
@@ -830,9 +868,8 @@
*
* @method unmute_current_user
* @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
- * @param success_callback {Object} A function to be called on success.
*/
-function unmute_current_user(subscription, success_callback) {
+function unmute_current_user(subscription) {
subscription.enable_spinner('Unmuting...');
var subscription_link = subscription.get('link');
var subscriber = subscription.get('subscriber');
@@ -852,15 +889,20 @@
var flash_node = subscription_link.get('parentNode');
var anim = Y.lazr.anim.green_flash({ node: flash_node });
anim.run();
- if (Y.Lang.isValue(success_callback)) {
- success_callback();
- }
+ subscription.set('is_muted', false);
+ update_mute_after_subscription_change(subscription);
+ update_subscription_after_mute_or_unmute(subscription);
},
failure: error_handler.getFailureHandler()
+ },
+
+ parameters: {
+ person: Y.lp.client.get_absolute_uri(
+ subscriber.get('escaped_uri'))
}
};
- lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
+ lp_client.named_post(bug_repr.self_link, 'unmute', config);
}
/*
@@ -1153,17 +1195,19 @@
function handle_advanced_subscription_overlay(subscription, form_data) {
var link = subscription.get('link');
var link_parent = link.get('parentNode');
+ var mute_subscription = get_mute_subscription();
if (link_parent.hasClass('subscribed-false') &&
- link_parent.hasClass('dup-subscribed-false')) {
- // The user isn't subscribed, so subscribe them.
+ link_parent.hasClass('dup-subscribed-false') &&
+ !mute_subscription.get('is_muted')) {
+ // The user isn't subscribed or muted, so subscribe them.
subscription.set(
'bug_notification_level',
form_data['field.bug_notification_level']);
subscribe_current_user(subscription);
} else if (
form_data['field.subscription'] == 'update-subscription') {
- // The user is already subscribed and wants to update their
- // subscription.
+ // The user is already subscribed or is muted and wants to
+ // update their subscription.
setup_client_and_bug();
var person_name = subscription.get('person').get('name');
var subscription_url =
@@ -1173,6 +1217,9 @@
on: {
success: function(lp_subscription) {
subscription.enable_spinner('Updating subscription...');
+ if (mute_subscription.get('is_muted')) {
+ mute_subscription.enable_spinner('Unmuting...');
+ }
lp_subscription.set(
'bug_notification_level',
form_data['field.bug_notification_level'][0])
@@ -1185,6 +1232,14 @@
node: link_parent
});
anim.run();
+ if (mute_subscription.get('is_muted')) {
+ mute_subscription.set('is_muted', false);
+ mute_subscription.disable_spinner(
+ "Mute bug mail");
+ update_mute_after_subscription_change(
+ mute_subscription);
+ add_user_name_link(subscription);
+ }
},
failure: function(e) {
subscription.disable_spinner(
@@ -1366,4 +1421,4 @@
"lazr.overlay", "lazr.choiceedit", "lp.app.picker",
"lp.client",
"lp.client.plugins", "lp.bugs.subscriber",
- "lp.app.errors"]});
+ "lp.bugs.bug_subscription", "lp.app.errors"]});
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2011-03-15 04:15:45 +0000
+++ lib/lp/bugs/model/bug.py 2011-03-17 15:02:39 +0000
@@ -836,6 +836,27 @@
BugNotificationLevel.NOTHING)
return not subscriptions.is_empty()
+ def mute(self, person, muted_by):
+ """See `IBug`."""
+ # If there's an existing subscription, update it.
+ store = Store.of(self)
+ subscriptions = store.find(
+ BugSubscription,
+ BugSubscription.bug == self,
+ BugSubscription.person == person)
+ if subscriptions.is_empty():
+ return self.subscribe(
+ person, muted_by, level=BugNotificationLevel.NOTHING)
+ else:
+ subscription = subscriptions.one()
+ subscription.bug_notification_level = (
+ BugNotificationLevel.NOTHING)
+ return subscription
+
+ def unmute(self, person, unmuted_by):
+ """See `IBug`."""
+ self.unsubscribe(person, unmuted_by)
+
@property
def subscriptions(self):
"""The set of `BugSubscriptions` for this bug."""
=== modified file 'lib/lp/bugs/templates/bug-subscription.pt'
--- lib/lp/bugs/templates/bug-subscription.pt 2010-10-18 10:14:37 +0000
+++ lib/lp/bugs/templates/bug-subscription.pt 2011-03-17 15:02:39 +0000
@@ -10,6 +10,25 @@
i18n:domain="malone"
>
+ <metal:block fill-slot="head_epilogue">
+ <script type="text/javascript"
+ tal:condition="devmode"
+ tal:content="string:var yui_base='${yui}';"></script>
+ <script type="text/javascript"
+ tal:condition="devmode"
+ tal:define="lp_js string:${icingroot}/build"
+ tal:attributes="src string:${lp_js}/bugs/filebug-dupefinder.js"></script>
+
+ <script type="text/javascript" tal:condition="view/user_is_muted">
+ LPS.use('base', 'node', 'oop', 'event', 'lp.bugs.bug_subscription',
+ function(Y) {
+ Y.on(
+ 'domready',
+ Y.lp.bugs.bug_subscription.set_up_bug_notification_level_field);
+ });
+ </script>
+ </metal:block>
+
<body>
<div metal:fill-slot="main">
=== modified file 'lib/lp/bugs/tests/test_bug.py'
--- lib/lp/bugs/tests/test_bug.py 2011-03-04 16:17:08 +0000
+++ lib/lp/bugs/tests/test_bug.py 2011-03-17 15:02:39 +0000
@@ -44,3 +44,33 @@
# subscription.
with person_logged_in(self.person):
self.assertEqual(False, self.bug.isMuted(self.person))
+
+ def test_mute_mutes_user(self):
+ # Bug.mute() adds a muted subscription for the user passed to
+ # it.
+ with person_logged_in(self.person):
+ muted_subscription = self.bug.mute(
+ self.person, self.person)
+ self.assertEqual(
+ BugNotificationLevel.NOTHING,
+ muted_subscription.bug_notification_level)
+
+ def test_mute_mutes_user_with_existing_subscription(self):
+ # Bug.mute() will update an existing subscription so that it
+ # becomes muted.
+ with person_logged_in(self.person):
+ subscription = self.bug.subscribe(self.person, self.person)
+ muted_subscription = self.bug.mute(self.person, self.person)
+ self.assertEqual(subscription, muted_subscription)
+ self.assertEqual(
+ BugNotificationLevel.NOTHING,
+ subscription.bug_notification_level)
+
+ def test_unmute_unmutes_user(self):
+ # Bug.unmute() will remove a muted subscription for the user
+ # passed to it.
+ with person_logged_in(self.person):
+ self.bug.mute(self.person, self.person)
+ self.assertTrue(self.bug.isMuted(self.person))
+ self.bug.unmute(self.person, self.person)
+ self.assertFalse(self.bug.isMuted(self.person))