← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~benji/launchpad/bug-777786 into lp:launchpad

 

Benji York has proposed merging lp:~benji/launchpad/bug-777786 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~benji/launchpad/bug-777786/+merge/61274

Bug 777786 notes that it's somewhat rude to show users a "Stop my emails
from this subscription" for subscriptions via teams that have a contact
address because it won't actually stop the email.  This branch replaces
that link with a link to contact the team with a pre-populated message
that the user can edit.

I also fixed a bit of lint in lib/lp/registry/interfaces/mailinglist.py.

The make lint report is clean.

The tests are in lib/lp/registry/javascript/tests/test_structural_subscription.js.

-- 
https://code.launchpad.net/~benji/launchpad/bug-777786/+merge/61274
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~benji/launchpad/bug-777786 into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
--- lib/lp/bugs/browser/structuralsubscription.py	2011-05-10 15:16:05 +0000
+++ lib/lp/bugs/browser/structuralsubscription.py	2011-05-17 15:59:31 +0000
@@ -14,7 +14,10 @@
     'StructuralSubscribersPortletView',
     ]
 
-from operator import attrgetter
+from operator import (
+    attrgetter,
+    itemgetter,
+    )
 
 from lazr.restful.interfaces import (
     IJSONRequestCache,
@@ -491,20 +494,33 @@
         subscriber = subscription.subscriber
         for filter in subscription.bug_filters:
             is_team = subscriber.isTeam()
-            user_is_team_admin = (is_team and
-                                  subscriber in administered_teams)
+            user_is_team_admin = (
+                is_team and subscriber in administered_teams)
+            team_has_contact_address = (
+                is_team and subscriber.preferredemail is not None)
+            mailing_list = subscriber.mailing_list
+            user_is_on_team_mailing_list = (
+                team_has_contact_address and
+                mailing_list is not None and
+                mailing_list.is_usable and
+                mailing_list.getSubscription(subscriber) is not None)
             record['filters'].append(dict(
                 filter=filter,
                 subscriber_link=absoluteURL(subscriber, api_request),
-                subscriber_url = canonical_url(
+                subscriber_url=canonical_url(
                     subscriber, rootsite='mainsite'),
+                target_bugs_url=canonical_url(
+                    target, rootsite='bugs'),
                 subscriber_title=subscriber.title,
                 subscriber_is_team=is_team,
                 user_is_team_admin=user_is_team_admin,
+                team_has_contact_address=team_has_contact_address,
+                user_is_on_team_mailing_list=user_is_on_team_mailing_list,
                 can_mute=filter.isMuteAllowed(user),
-                is_muted=filter.muted(user) is not None))
+                is_muted=filter.muted(user) is not None,
+                target_title=target.title))
     info = info.values()
-    info.sort(key=lambda item: item['target_url'])
+    info.sort(key=itemgetter('target_url'))
     IJSONRequestCache(request).objects['subscription_info'] = info
 
 

=== modified file 'lib/lp/registry/interfaces/mailinglist.py'
--- lib/lp/registry/interfaces/mailinglist.py	2011-05-12 21:33:10 +0000
+++ lib/lp/registry/interfaces/mailinglist.py	2011-05-17 15:59:31 +0000
@@ -110,7 +110,7 @@
     INACTIVE = DBItem(7, """
         Inactive
 
-        A previously active mailing lit has been made inactive by its team
+        A previously active mailing list has been made inactive by its team
         owner.
         """)
 
@@ -132,7 +132,7 @@
 
         The mailing list has been flagged for deactivation by the team owner.
         Mailman will be informed of this and will take the necessary actions
-        to deactive the list.
+        to deactivate the list.
         """)
 
     MOD_FAILED = DBItem(11, """
@@ -246,8 +246,7 @@
         title=_('Review date'),
         description=_('The date on which this mailing list registration was '
                       'reviewed, or None if the registration has not yet '
-                      'been reviewed.')
-        )
+                      'been reviewed.'))
 
     date_activated = Datetime(
         title=_('Activation date'),
@@ -255,8 +254,7 @@
                       'meaning that the Mailman process has successfully '
                       'created it.  This may be None if the mailing list '
                       'has not yet been activated, or that its activation '
-                      'has failed.')
-        )
+                      'has failed.'))
 
     status = Choice(
         title=_('Status'),
@@ -434,8 +432,8 @@
     def getReviewableMessages(message_id_filter=None):
         """Return the set of all held messages for this list requiring review.
 
-        :param message_id_filter: If supplied only messages with message ids in
-            the filter are returned.
+        :param message_id_filter: If supplied only messages with message ids
+            in the filter are returned.
         :return: A sequence of `IMessageApproval`s for this mailing list,
             where the status is `PostedMessageStatus.NEW`.  The returned set
             is ordered first by the date the message was posted, then by

=== modified file 'lib/lp/registry/javascript/structural-subscription.js'
--- lib/lp/registry/javascript/structural-subscription.js	2011-05-11 21:01:58 +0000
+++ lib/lp/registry/javascript/structural-subscription.js	2011-05-17 15:59:31 +0000
@@ -1239,7 +1239,7 @@
                     } else {
                         filter_info.is_muted = false;
                     }
-                    handle_mute(node, filter_info.is_muted);
+                    handle_mute(node, filter_info);
                     },
                  failure: error_handler.getFailureHandler()
                 }
@@ -1304,20 +1304,27 @@
 /**
  * For a given filter node, set it up properly based on mute state.
  */
-function handle_mute(node, muted) {
+function handle_mute(node, filter_info) {
     var control = node.one('a.mute-subscription');
     var label = node.one('em.mute-label');
     var description = node.one('.filter-description');
-    if (muted) {
-        control.set('text', 'Send my emails for this subscription');
-        control.replaceClass(MUTE_ICON_CLASS, UNMUTE_ICON_CLASS);
-        label.setStyle('display', null);
-        description.setStyle('color', '#bbb');
+    if (!filter_info.team_has_contact_address) {
+        // If the subscriber is a team that has a contact address, then the
+        // user muting the subscription won't do anything, so don't show them
+        // an option that won't do them any good.
+        if (filter_info.is_muted) {
+            control.set('text', 'Send my emails for this subscription');
+            control.replaceClass(MUTE_ICON_CLASS, UNMUTE_ICON_CLASS);
+            label.setStyle('display', null);
+            description.setStyle('color', '#bbb');
+        } else {
+            control.set('text', 'Stop my emails from this subscription');
+            control.replaceClass(UNMUTE_ICON_CLASS, MUTE_ICON_CLASS);
+            label.setStyle('display', 'none');
+            description.setStyle('color', null);
+        }
     } else {
-        control.set('text', 'Stop my emails from this subscription');
-        control.replaceClass(UNMUTE_ICON_CLASS, MUTE_ICON_CLASS);
         label.setStyle('display', 'none');
-        description.setStyle('color', null);
     }
 }
 
@@ -1335,23 +1342,31 @@
         '<strong class="filter-name"></strong>'));
 
     if (filter_info.can_mute) {
-        filter_node.appendChild(Y.Node.create(
-            '<em class="mute-label" style="padding-left: 1em;">You '+
-                'do not receive emails from this subscription.</em>'));
+        filter_node.append(Y.Node.create('<em/>')
+            .set('text', 'You do not receive emails from this subscription.')
+            .addClass('mute-label')
+            .setStyle('paddingLeft', '1em'));
     }
 
     var control = filter_node.appendChild(
         Y.Node.create('<span style="float: right"></span>'));
 
     if (filter_info.can_mute) {
-        var link = control.appendChild(Y.Node.create(
-            '<a href="#" class="sprite js-action mute-subscription"></a>'));
-        var help = control.appendChild(Y.Node.create(
-            '<a target="help" class="sprite maybe mute-help"'+
-            '    style="visibility: hidden;"'+
-            '    href="/+help/structural-subscription-mute.html">'+
-            '  <span class="invisible-link">Delivery help</span>'+
-            '</a>'));
+        var link = control.appendChild(Y.Node.create('<a/>')
+            .set('href', '#')
+            .addClass('sprite')
+            .addClass('js-action')
+            .addClass('mute-subscription'));
+        var help = control.appendChild(Y.Node.create('<a/>')
+            .set('href', '/+help/structural-subscription-mute.html')
+            .set('target', 'help')
+            .addClass('sprite')
+            .addClass('maybe')
+            .addClass('mute-help')
+            .setStyle('visibility', 'hidden')
+            .append(Y.Node.create('<span/>')
+                .addClass('invisible-link')
+                .set('text', 'Delivery help')));
         // For some reason the help link will not appear in Chrome unless
         // there is a non-empty element immediately after the help node.
         control.append(Y.Node.create('<span>&nbsp;</span>'));
@@ -1362,7 +1377,7 @@
             help.setStyle('visibility', 'visible');
             // Every time we trigger the display of the help link we need to
             // cancel any pending hiding of the help link so it doesn't
-            // dissapear on us.  If there isn't one pending, this is a NOP.
+            // disappear on us.  If there isn't one pending, this is a NOP.
             clearTimeout(hide_help_timeout);
         };
         var hide_help = function () {
@@ -1376,20 +1391,57 @@
 
     if (can_edit(filter_info)) {
         // User can edit the subscription.
-        control.append(Y.Node.create(
-            '<a href="#" style="margin-right: 2em;"'+
-            '    class="sprite modify edit js-action edit-subscription">'+
-            '  Edit this subscription</a>'+
-            '<a href="#" class="sprite modify remove js-action '+
-            '    delete-subscription">Unsubscribe</a>'));
-    }
-
-    filter_node.appendChild(
-        Y.Node.create('<div style="padding-left: 1em" '+
-                      'class="filter-description"></div>'));
+        control.append(Y.Node.create('<a/>')
+            .set('href', '#')
+            .set('text', 'Edit this subscription')
+            .setStyle('marginRight', '2em')
+            .addClass('sprite')
+            .addClass('modify')
+            .addClass('edit')
+            .addClass('js-action')
+            .addClass('edit-subscription'));
+        control.append(Y.Node.create('<a/>')
+            .set('href', '#')
+            .set('text', 'Unsubscribe')
+            .addClass('sprite')
+            .addClass('modify')
+            .addClass('remove')
+            .addClass('js-action')
+            .addClass('delete-subscription'));
+    }
+
+    if (filter_info.team_has_contact_address
+            && !filter_info.user_is_team_admin) {
+        console.log(filter_info);
+        var subject = urlEncode('Team contact address and subscriptions');
+        var user_participation;
+        if (filter_info.user_is_on_team_mailing_list) {
+            user_participation = 'subscribe to the team\'s mailing list';
+        } else {
+            user_participation = 'be a part of the team';
+        }
+        var message = urlEncode(
+            'Hello.  I receive email notifications about bugs in '+
+            filter_info.target_title+' because of a team subscription for '+
+            filter_info.subscriber_title+'. I would like to continue to '+
+            user_participation+', but I would like to receive less email '+
+            'from this subscription.  Could you remove the team contact '+
+            'email address so that the team members can manage their own '+
+            'subscriptions (see '+filter_info.subscriber_url+'), or delete '+
+            'or reduce level of the subscription itself (see '+
+            filter_info.target_bugs_url+'/+subscriptions)?\n\nThank you.');
+        control.append(Y.Node.create('<a/>')
+            .set('href', filter_info.subscriber_url+'/+contactuser'+
+                '?field.message='+message+'&field.subject='+subject)
+            .set('text', 'Request team administrators change'));
+    }
+
+    filter_node.append(Y.Node.create('<div/>')
+        .setStyle('paddingLeft', '1em')
+        .addClass('filter-description'));
 
     if (filter_info.can_mute) {
-        handle_mute(filter_node, filter_info.is_muted);
+        handle_mute(filter_node, filter_info);
     }
 
     fill_filter_description(filter_node, filter_info, filter);
@@ -1397,6 +1449,10 @@
     return filter_node;
 }
 
+// Expose in the namespace for testing purposes.
+namespace._create_filter_node = create_filter_node;
+
+
 /**
  * Create a node with subscription description.
  */
@@ -1424,7 +1480,7 @@
         // and see the information you expect to see.
         LP.cache['structural-subscription-filter-'+filter_id.toString()] =
             filter;
-        node.appendChild(create_filter_node(filter_id, filter_info, filter));
+        node.append(create_filter_node(filter_id, filter_info, filter));
         filter_id += 1;
     }
     return node;
@@ -1641,7 +1697,7 @@
     var team_info = get_team_info(form_data);
 
     var filter_info = {
-        filter : filter,
+        filter: filter,
         subscriber_is_team: team_info.is_team,
         user_is_team_admin: team_info.is_team,
         can_mute: team_info.is_team,

=== modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js'
--- lib/lp/registry/javascript/tests/test_structural_subscription.js	2011-05-11 21:05:17 +0000
+++ lib/lp/registry/javascript/tests/test_structural_subscription.js	2011-05-17 15:59:31 +0000
@@ -716,6 +716,159 @@
     suite.add(test_case);
 
     suite.add(new Y.Test.Case({
+        name: 'Structural Subscription team contact',
+
+        _should: {error: {}},
+
+        setUp: function() {
+            // Monkeypatch LP to avoid network traffic and to allow
+            // insertion of test data.
+            this.original_lp = monkeypatch_LP();
+
+            LP.cache.subscription_info = [{
+                target_url: 'http://example.com',
+                target_title:'Example project',
+                filters: [{
+                    filter: {
+                        description: 'DESCRIPTION',
+                        statuses: [],
+                        importances: [],
+                        tags: [],
+                        find_all_tags: true,
+                        bug_notification_level: 'Discussion',
+                        self_link: 'http://example.com/a_filter'
+                        },
+                    can_mute: true,
+                    is_muted: false,
+                    team_has_contact_address: true,
+                    user_is_team_admin: false,
+                    subscriber_is_team: true,
+                    subscriber_url: 'http://example.com/subscriber',
+                    subscriber_title: 'Thidwick'
+                }]
+            }];
+            this.configuration = {
+                content_box: content_box_id,
+                lp_client: make_lp_client_stub()
+            };
+
+            this.content_node = create_test_node();
+            Y.one('body').appendChild(this.content_node);
+        },
+
+        tearDown: function() {
+            window.LP = this.original_lp;
+            remove_test_node();
+            delete this.content_node;
+        },
+
+        test_administrative_change_link: function() {
+            var filter_info = {
+                    filter: {
+                        description: 'DESCRIPTION',
+                        statuses: [],
+                        importances: [],
+                        tags: [],
+                        find_all_tags: true,
+                        bug_notification_level: 'Discussion',
+                        self_link: 'http://example.com/a_filter'
+                        },
+                    can_mute: true,
+                    is_muted: false,
+                    team_has_contact_address: true,
+                    user_is_team_admin: false,
+                    user_is_on_team_mailing_list: true,
+                    subscriber_is_team: true,
+                    subscriber_url: 'http://example.com/subscriber',
+                    subscriber_title: 'Thidwick'
+            };
+            var node = module._create_filter_node(
+                'ID', filter_info, filter_info.filter);
+            var content = node.getContent();
+            // If a subscription is via a team and the user isn't a team
+            // admin and the team has a contact address, the user gets a link
+            // to request the administrators change the subscription or drop
+            // the contact address.
+            Assert.areNotEqual(
+                -1,
+                content.search(/Request team administrators change/));
+            // If the team's contact address is to a (launchpad-managed)
+            // mailing list, then the pre-filled in email message is phrased
+            // accordingly.
+            Assert.areNotEqual(
+                -1,
+                content.search(/subscribe%20to%20the%20team/));
+        },
+
+        test_administrative_change_link_no_mailing_list: function() {
+            var filter_info = {
+                    filter: {
+                        description: 'DESCRIPTION',
+                        statuses: [],
+                        importances: [],
+                        tags: [],
+                        find_all_tags: true,
+                        bug_notification_level: 'Discussion',
+                        self_link: 'http://example.com/a_filter'
+                        },
+                    can_mute: true,
+                    is_muted: false,
+                    team_has_contact_address: true,
+                    user_is_team_admin: false,
+                    user_is_on_team_mailing_list: false,
+                    subscriber_is_team: true,
+                    subscriber_url: 'http://example.com/subscriber',
+                    subscriber_title: 'Thidwick'
+            };
+            var node = module._create_filter_node(
+                'ID', filter_info, filter_info.filter);
+            var content = node.getContent();
+            // If a subscription is via a team and the user isn't a team
+            // admin and the team has a contact address, the user gets a link
+            // to request the administrators change the subscription or drop
+            // the contact address.
+            Assert.areNotEqual(
+                -1,
+                content.search(/Request team administrators change/));
+            // If the team's contact address is not a (launchpad-managed)
+            // mailing list, then the pre-filled in email message is phrased
+            // accordingly.
+            Assert.areNotEqual(
+                -1,
+                content.search(/be%20a%20part%20of%20the%20team/));
+        },
+
+        test_mute_not_shown_when_ineffectual: function() {
+            // If muting the subscription in question won't have an effect,
+            // then the mute link isn't shown.
+            var filter_info = {
+                    filter: {
+                        description: 'DESCRIPTION',
+                        statuses: [],
+                        importances: [],
+                        tags: [],
+                        find_all_tags: true,
+                        bug_notification_level: 'Discussion',
+                        self_link: 'http://example.com/a_filter'
+                        },
+                    can_mute: true,
+                    is_muted: false,
+                    team_has_contact_address: true,
+                    user_is_team_admin: false,
+                    user_is_on_team_mailing_list: true,
+                    subscriber_is_team: true,
+                    subscriber_url: 'http://example.com/subscriber',
+                    subscriber_title: 'Thidwick'
+            };
+            var node = module._create_filter_node(
+                'ID', filter_info, filter_info.filter);
+            Assert.areEqual(
+                -1,
+                content.search(/Stop my emails/));
+        }
+    }));
+
+    suite.add(new Y.Test.Case({
         name: 'Structural Subscription: deleting failed filters',
 
         _should: {error: {}},