← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/person-picker-expand-0 into lp:launchpad

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/person-picker-expand-0 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~sinzui/launchpad/person-picker-expand-0/+merge/70082

Extend the picker to expand to reveal details.

    Launchpad bug:
        https://bugs.launchpad.net/bugs/800361
        https://bugs.launchpad.net/bugs/798759
    Pre-implementation: jcscackett

Update the picker to handle cases where there are three parts to an li.
The picker currently supports title and description which comprise the
first and second line that users see. Some items like person or targets
may have a detail part. In this case choosing an item will expand it to
show the details. The details part will always include a link to select
the items and close the picker, and a link to an external page to see
details outside of the picker.

See http://people.canonical.com/~curtis/person-picker/person-picker-0.html
which was the test.

--------------------------------------------------------------------

RULES

    * When an items has a details part, an expander should be used where
      the title and description are the expander icon (toggle) and the
      details is the content that is revealed.
    * The picker currently knows that data.alt_title_link is an external
      url. This may a requirement
    * Add Picker._renderDetailsUI() to return the formatted details
      * Return null when there are no details.
      * data.details will provide formatted content that is revealed by the
        expander.
      * The select link should the data.title to form:
        _Select fnord_
      * The details link will use the new window icon and data.alt_title_link
        as the href.
    * Update Picker._syncResultsUI() to call _renderDetailsUI().
      When details !== null, create an expander and use title
      and description as the icon node, and user details as the
      content.
    * Added member since or member count

Issues that can be solved in future branches
    * Add affiliation role. Users want to know about maintainers, drivers
      bug supervisors and bug contacts.
    * Close other open expanders when one expands. The picker expanders
      would require less clicks if they behaved like mutually exclusive
      buttons. Only one can be expanded.


QA

    * Choose to reassign a /launchpad bug
    * Search for 'launchpad'
    * Verify that the choosing an item reveals the Select and details links.
    * Verify the details link opens a new window/tab.
    * Verify the ~launchpad entry states how many team members will see it.
    * Search for yourself
    * Verify you see you irc nicks and membership date.
    * Verify that the choosing an item reveals the Select and details links.
    * Verify the details link opens a new window/tab.


LINT

    lib/canonical/launchpad/icing/sprite.css.in
    lib/canonical/launchpad/images/new-window.png
    lib/lp/app/browser/vocabulary.py
    lib/lp/app/browser/tests/test_vocabulary.py
    lib/lp/app/javascript/picker/picker.js
    lib/lp/app/javascript/picker/tests/test_picker.html
    lib/lp/app/javascript/picker/tests/test_picker.js
    lib/lp/bugs/templates/bugtarget-macros-filebug.pt
    lib/lp/registry/model/pillaraffiliation.py
    lib/lp/registry/tests/test_pillaraffiliation.py



TEST

    ./bin/test -vv \
        -t app/.*/test_vocabulary -t test_pillaraffiliation -t test_picker


IMPLEMENTATION

Added the new-window icon.
Updated similar bugs list to use the new-window sprite to "Show the bug".
    lib/canonical/launchpad/images/new-window.png
    lib/canonical/launchpad/icing/sprite.css.in
    lib/lp/bugs/templates/bugtarget-macros-filebug.pt

Update PickerEntry to provide details, and set the IPerson details to include
irc nicks and membership date or member count. Added a new test module because
I could not locate unittests for the PickerEntry adapters.
    lib/lp/app/browser/vocabulary.py
    lib/lp/app/browser/tests/test_vocabulary.py

Updated picker js to format the entry details. The details section includes
a link to select the entry and a link to see the entry's page. The details
data is shown in an expander.
    lib/lp/app/javascript/picker/picker.js
    lib/lp/app/javascript/picker/tests/test_picker.html
    lib/lp/app/javascript/picker/tests/test_picker.js

Fixed bug where test data was hard coded into the implementation. Updated
implementation to trust the pillar attr.
    lib/lp/registry/model/pillaraffiliation.py
    lib/lp/registry/tests/test_pillaraffiliation.py
-- 
https://code.launchpad.net/~sinzui/launchpad/person-picker-expand-0/+merge/70082
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/person-picker-expand-0 into lp:launchpad.
=== modified file 'lib/canonical/launchpad/icing/sprite.css.in'
--- lib/canonical/launchpad/icing/sprite.css.in	2011-06-02 18:23:05 +0000
+++ lib/canonical/launchpad/icing/sprite.css.in	2011-08-01 21:59:27 +0000
@@ -109,6 +109,10 @@
     background-image: url(/@@/link.png); /* sprite-ref: icon-sprites */
     background-repeat: no-repeat;
     }
+.new-window {
+    background-image: url(/@@/new-window.png); /* sprite-ref: icon-sprites */
+    background-repeat: no-repeat;
+    }
 .mail {
     background-image: url(/@@/mail.png); /* sprite-ref: icon-sprites */
     background-repeat: no-repeat;

=== added file 'lib/canonical/launchpad/images/new-window.png'
Binary files lib/canonical/launchpad/images/new-window.png	1970-01-01 00:00:00 +0000 and lib/canonical/launchpad/images/new-window.png	2011-08-01 21:59:27 +0000 differ
=== added file 'lib/lp/app/browser/tests/test_vocabulary.py'
--- lib/lp/app/browser/tests/test_vocabulary.py	1970-01-01 00:00:00 +0000
+++ lib/lp/app/browser/tests/test_vocabulary.py	2011-08-01 21:59:27 +0000
@@ -0,0 +1,108 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test vocabulary adapters."""
+
+__metaclass__ = type
+
+from datetime import datetime
+
+import pytz
+
+from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.app.browser.vocabulary import IPickerEntry
+from lp.registry.interfaces.irc import IIrcIDSet
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+
+
+class PersonPickerEntryAdapterTestCase(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_person_to_pickerentry(self):
+        # IPerson can be adpated to IPickerEntry.
+        person = self.factory.makePerson()
+        adapter = IPickerEntry(person)
+        self.assertTrue(IPickerEntry.providedBy(adapter))
+
+    def test_PersonPickerEntryAdapter_email_anonymous(self):
+        # Anonymous users cannot see entry email addresses.
+        person = self.factory.makePerson(email='snarf@xxxxxx')
+        entry = IPickerEntry(person).getPickerEntry(None)
+        self.assertEqual('<email address hidden>', entry.description)
+
+    def test_PersonPickerEntryAdapter_visible_email_logged_in(self):
+        # Logged in users can see visible email addresses.
+        observer = self.factory.makePerson()
+        login_person(observer)
+        person = self.factory.makePerson(email='snarf@xxxxxx')
+        entry = IPickerEntry(person).getPickerEntry(None)
+        self.assertEqual('snarf@xxxxxx', entry.description)
+
+    def test_PersonPickerEntryAdapter_hidden_email_logged_in(self):
+        # Logged in users cannot see hidden email addresses.
+        person = self.factory.makePerson(email='snarf@xxxxxx')
+        login_person(person)
+        person.hide_email_addresses = True
+        observer = self.factory.makePerson()
+        login_person(observer)
+        entry = IPickerEntry(person).getPickerEntry(None)
+        self.assertEqual('<email address hidden>', entry.description)
+
+    def test_PersonPickerEntryAdapter_no_email_logged_in(self):
+        # Teams without email address have no desriptions.
+        team = self.factory.makeTeam()
+        observer = self.factory.makePerson()
+        login_person(observer)
+        entry = IPickerEntry(team).getPickerEntry(None)
+        self.assertEqual(None, entry.description)
+
+    def test_PersonPickerEntryAdapter_logged_in(self):
+        # Logged in users can see visible email addresses.
+        observer = self.factory.makePerson()
+        login_person(observer)
+        person = self.factory.makePerson(
+            email='snarf@xxxxxx', name='snarf')
+        entry = IPickerEntry(person).getPickerEntry(None)
+        self.assertEqual('sprite person', entry.css)
+        self.assertEqual('sprite new-window', entry.link_css)
+
+    def test_PersonPickerEntryAdapter_enhanced_picker_enabled_user(self):
+        # The enhanced person picker provides more information for users.
+        person = self.factory.makePerson(email='snarf@xxxxxx', name='snarf')
+        creation_date = datetime(
+            2005, 01, 30, 0, 0, 0, 0, pytz.timezone('UTC'))
+        removeSecurityProxy(person).datecreated = creation_date
+        getUtility(IIrcIDSet).new(person, 'eg.dom', 'snarf')
+        getUtility(IIrcIDSet).new(person, 'ex.dom', 'pting')
+        entry = IPickerEntry(person).getPickerEntry(
+            None, enhanced_picker_enabled=True)
+        self.assertEqual('http://launchpad.dev/~snarf', entry.alt_title_link)
+        self.assertEqual(
+            ['snarf on eg.dom, pting on ex.dom', 'Member since 2005-01-30'],
+            entry.details)
+
+    def test_PersonPickerEntryAdapter_enhanced_picker_enabled_team(self):
+        # The enhanced person picker provides more information for teams.
+        team = self.factory.makeTeam(email='fnord@xxxxxx', name='fnord')
+        entry = IPickerEntry(team).getPickerEntry(
+            None, enhanced_picker_enabled=True)
+        self.assertEqual('http://launchpad.dev/~fnord', entry.alt_title_link)
+        self.assertEqual(['Team members: 1'], entry.details)
+
+    def test_PersonPickerEntryAdapter_enhanced_picker_enabled_badges(self):
+        # The enhanced person picker provides affilliation information.
+        person = self.factory.makePerson(email='snarf@xxxxxx', name='snarf')
+        project = self.factory.makeProduct(name='fnord', owner=person)
+        bugtask = self.factory.makeBugTask(target=project)
+        entry = IPickerEntry(person).getPickerEntry(
+            bugtask, enhanced_picker_enabled=True)
+        self.assertEqual(1, len(entry.badges))
+        self.assertEqual('/@@/product-badge', entry.badges[0]['url'])
+        self.assertEqual('Affiliated with Fnord', entry.badges[0]['alt'])

=== modified file 'lib/lp/app/browser/vocabulary.py'
--- lib/lp/app/browser/vocabulary.py	2011-07-18 00:30:18 +0000
+++ lib/lp/app/browser/vocabulary.py	2011-08-01 21:59:27 +0000
@@ -32,6 +32,7 @@
 from canonical.launchpad.webapp.interfaces import NoCanonicalUrl
 from canonical.launchpad.webapp.publisher import canonical_url
 from lp.app.browser.tales import (
+    DateTimeFormatterAPI,
     IRCNicknameFormatterAPI,
     ObjectImageDisplayAPI,
     )
@@ -61,6 +62,7 @@
     css = Attribute('CSS Class')
     alt_title = Attribute('Alternative title')
     title_link = Attribute('URL used for anchor on title')
+    details = Attribute('An optional list of information about the entry')
     alt_title_link = Attribute('URL used for anchor on alt title')
     link_css = Attribute('CSS Class for links')
     badges = Attribute('List of badge img attributes')
@@ -72,13 +74,14 @@
     implements(IPickerEntry)
 
     def __init__(self, description=None, image=None, css=None, alt_title=None,
-                 title_link=None, alt_title_link=None, link_css='js-action',
-                 badges=None, metadata=None):
+                 title_link=None, details=None, alt_title_link=None,
+                 link_css='sprite new-window', badges=None, metadata=None):
         self.description = description
         self.image = image
         self.css = css
         self.alt_title = alt_title
         self.title_link = title_link
+        self.details = details
         self.alt_title_link = alt_title_link
         self.link_css = link_css
         self.badges = badges
@@ -154,6 +157,7 @@
             # We will linkify the person's name so it can be clicked to open
             # the page for that person.
             extra.alt_title_link = canonical_url(person, rootsite='mainsite')
+            extra.details = []
             # We will display the person's irc nick(s) after their email
             # address in the description text.
             irc_nicks = None
@@ -162,11 +166,14 @@
                     [IRCNicknameFormatterAPI(ircid).displayname()
                     for ircid in person.ircnicknames])
             if irc_nicks:
-                if extra.description:
-                    extra.description = ("%s (%s)" %
-                        (extra.description, irc_nicks))
-                else:
-                    extra.description = "%s" % irc_nicks
+                    extra.details.append(irc_nicks)
+            if person.is_team:
+                extra.details.append(
+                    'Team members: %s' % person.all_member_count)
+            else:
+                extra.details.append(
+                    'Member since %s' % DateTimeFormatterAPI(
+                        person.datecreated).date())
 
         return extra
 
@@ -283,6 +290,8 @@
                 entry['alt_title'] = picker_entry.alt_title
             if picker_entry.title_link is not None:
                 entry['title_link'] = picker_entry.title_link
+            if picker_entry.details is not None:
+                entry['details'] = picker_entry.details
             if picker_entry.alt_title_link is not None:
                 entry['alt_title_link'] = picker_entry.alt_title_link
             if picker_entry.link_css is not None:

=== modified file 'lib/lp/app/javascript/picker/picker.js'
--- lib/lp/app/javascript/picker/picker.js	2011-07-29 18:50:58 +0000
+++ lib/lp/app/javascript/picker/picker.js	2011-08-01 21:59:27 +0000
@@ -381,26 +381,11 @@
             data.title, data.title_link, data.link_css);
         li_title.appendChild(title);
         if (data.alt_title) {
-            var alt_link = null;
-            if (data.alt_title_link) {
-                alt_link =Y.Node.create('<a></a>')
-                    .addClass(data.link_css)
-                    .addClass('discreet');
-                alt_link.set('text', " Details...")
-                    .set('href', data.alt_title_link);
-                Y.on('click', function(e) {
-                    e.halt();
-                    window.open(data.alt_title_link);
-                }, alt_link);
-            }
             li_title.appendChild('&nbsp;(');
             var alt_title_node = Y.Node.create('<span></span>')
                 .set('text', data.alt_title);
             li_title.appendChild(alt_title_node);
             li_title.appendChild(')');
-            if (alt_link !== null) {
-                li_title.appendChild(alt_link);
-            }
         }
         return li_title;
     },
@@ -413,7 +398,8 @@
      */
     _renderBadgesUI: function(data) {
         if (data.badges) {
-            var badges = Y.Node.create('<div></div>').addClass('badge');
+            var badges = Y.Node.create('<div>Affiliation:</div>')
+                .addClass('badge');
             Y.each(data.badges, function(badge_info) {
                 var badge_url = badge_info.url;
                 var badge_alt = badge_info.alt;
@@ -444,6 +430,48 @@
     },
 
     /**
+     * Render a node containing the optional details part of the picker entry.
+     * @param data a json data object with the details to render
+     */
+    _renderDetailsUI: function(data) {
+        if (!data.details && !data.alt_title_link) {
+            return null;
+        }
+        var details_node = Y.Node.create('<div></div>')
+            .addClass('sprite')
+            .addClass(C_RESULT_DESCRIPTION);
+        if (Y.Lang.isArray(data.details)) {
+            var data_node = Y.Node.create('<div></div>');
+            var last_index = data.details.length - 1;
+            Y.Array.each(data.details, function(detail, i) {
+                data_node.append(Y.Node.create(detail));
+                if (i < last_index) {
+                    data_node.append(Y.Node.create('<br />'));
+                }
+                });
+            details_node.append(data_node);
+        }
+        var links = [];
+        links.push(Y.Node.create(
+            '<a class="sprite yes save" href="#">Select ' +
+                data.title + '</a>'));
+        links[0].on('click', function (e, value) {
+            this.fire(SAVE, value);
+            }, this, data);
+        links.push(this._text_or_link(
+            'View details', data.alt_title_link, data.link_css));
+        var link_list = Y.Node.create('<ul></ul>')
+            .addClass('horizontal');
+        Y.Array.each(links, function(link, i) {
+            var li = Y.Node.create('<li></li>');
+            li.append(link);
+            link_list.append(li);
+            });
+        details_node.append(link_list);
+        return details_node;
+    },
+
+    /**
      * Update the UI based on the results attribute.
      *
      * @method _syncResultsUI
@@ -463,6 +491,8 @@
             var li_title = this._renderTitleUI(data);
             // Sort out the description div.
             var li_desc = this._renderDescriptionUI(data);
+            // Sort out the optional details div.
+            var li_details = this._renderDetailsUI(data);
             // Put the list item together.
             var li = Y.Node.create('<li></li>').addClass(
                 i % 2 ? Y.lazr.ui.CSS_ODD : Y.lazr.ui.CSS_EVEN);
@@ -476,12 +506,22 @@
             if (li_badges !== null) {
                 li.appendChild(li_badges);
             }
-            li.appendChild(li_title);
-            li.appendChild(li_desc);
-            // Attach handlers.
-            li.on('click', function (e, value) {
-                this.fire(SAVE, value);
-            }, this, data);
+            var summary_node = Y.Node.create('<div></div>');
+            summary_node.appendChild(li_title);
+            summary_node.appendChild(li_desc);
+            li.appendChild(summary_node);
+            if (li_details) {
+                // Use explicit save link.
+                li.appendChild(li_details);
+                li.expander = new Y.lp.app.widgets.expander.Expander(
+                    summary_node, li_details);
+                li.expander.setUp();
+            } else {
+                // Attach implicit save handler.
+                li.on('click', function (e, value) {
+                    this.fire(SAVE, value);
+                    }, this, data);
+            }
 
             this._results_box.appendChild(li);
         }, this);
@@ -1082,5 +1122,6 @@
 }, "0.1", {"skinnable": true,
            "requires": ["oop", "event", "event-focus", "node", "plugin",
                         "substitute", "widget", "widget-stdmod",
-                        "lazr.overlay", "lazr.anim", "lazr.base"]
+                        "lazr.overlay", "lazr.anim", "lazr.base",
+                        "lp.app.widgets.expander"]
 });

=== modified file 'lib/lp/app/javascript/picker/tests/test_picker.html'
--- lib/lp/app/javascript/picker/tests/test_picker.html	2011-07-08 06:21:11 +0000
+++ lib/lp/app/javascript/picker/tests/test_picker.html	2011-08-01 21:59:27 +0000
@@ -15,7 +15,9 @@
   <!-- The module under test -->
   <script type="text/javascript" src="../../overlay/overlay.js"></script>
   <script type="text/javascript" src="../picker.js"></script>
+  <script type="text/javascript" src="../../expander.js"></script>
   <script type="text/javascript" src="../../anim/anim.js"></script>
+  <script type="text/javascript" src="../../effects/effects.js"></script>
   <script type="text/javascript" src="../../lazr/lazr.js"></script>
 
   <!-- The test suite -->

=== modified file 'lib/lp/app/javascript/picker/tests/test_picker.js'
--- lib/lp/app/javascript/picker/tests/test_picker.js	2011-07-29 18:50:58 +0000
+++ lib/lp/app/javascript/picker/tests/test_picker.js	2011-08-01 21:59:27 +0000
@@ -171,13 +171,63 @@
             this.picker, 'a.cool-style:nth-child(1)', 'Joe Schmo',
             'http://somewhere.com/');
         check_link(
-            this.picker, 'a.cool-style:nth-child(3)', ' Details...',
+            this.picker, 'a.cool-style:last-child', 'View details',
             'http://somewhereelse.com/');
         var alt_text_node = this.picker.get('boundingBox')
             .one('.yui3-picker-result-title span');
         Assert.areEqual('Joe Again <foo></foo>', alt_text_node.get('text'));
     },
 
+    test_details: function () {
+        // The details of the li is the content node of the expander.
+        this.picker.render();
+        this.picker.set('results', [
+            {
+                css: 'yui3-blah-blue',
+                value: 'jschmo',
+                title: 'Joe Schmo',
+                description: 'joe@xxxxxxxxxxx',
+                details: ['joe on irc.freenode.net', 'Member since 2007'],
+                alt_title_link: '/~jschmo'
+            }
+        ]);
+        var bb = this.picker.get('boundingBox');
+        var li = bb.one('.yui3-picker-results li');
+        var details = li.expander.content_node;
+        Assert.areEqual(
+            'joe on irc.freenode.net<br>Member since 2007',
+            details.one('div').getContent());
+        Assert.areEqual(
+            'Select Joe Schmo', details.one('ul li:first-child').get('text'));
+        Assert.areEqual(
+            'View details', details.one('ul li:last-child').get('text'));
+    },
+
+    test_details_save_link: function () {
+        // The select link the li's details saves the selection.
+        this.picker.render();
+        this.picker.set('results', [
+            {
+                css: 'yui3-blah-blue',
+                value: 'jschmo',
+                title: 'Joe Schmo',
+                description: 'joe@xxxxxxxxxxx',
+                alt_title_link: 'http://somewhereelse.com',
+                link_css: 'cool-style'
+            }
+        ]);
+        var bb = this.picker.get('boundingBox');
+        var link_node = bb.one('a.save');
+        Assert.areEqual('Select Joe Schmo', link_node.get('text'));
+        Assert.areEqual(window.location, link_node.get('href'));
+        var selected_value = null;
+        this.picker.subscribe('save', function(e) {
+            selected_value = e.details[0].value;
+        }, this);
+        simulate(bb, 'a.save', 'click');
+        Assert.areEqual('jschmo', selected_value);
+    },
+
     test_title_badges: function () {
         this.picker.render();
         var badge_info = [

=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2011-07-20 15:02:50 +0000
+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2011-08-01 21:59:27 +0000
@@ -389,7 +389,8 @@
                       content="bug/date_last_updated/fmt:approximatedate">
                     2007-07-03
                   </tal:last-updated>
-                  <a class="view-bug-link" target="_blank"
+                  &nbsp;&nbsp;
+                  <a class="sprite new-window view-bug-link" target="_blank"
 		      tal:attributes="href bug/fmt:url"
                   >view this bug</a>
                 </span>

=== modified file 'lib/lp/registry/model/pillaraffiliation.py'
--- lib/lp/registry/model/pillaraffiliation.py	2011-06-03 12:26:05 +0000
+++ lib/lp/registry/model/pillaraffiliation.py	2011-08-01 21:59:27 +0000
@@ -30,6 +30,7 @@
 
 from canonical.launchpad.interfaces.launchpad import IHasIcon
 from lp.bugs.interfaces.bugtask import IBugTask
+from lp.registry.interfaces.distribution import IDistribution
 
 
 class IHasAffiliation(Interface):
@@ -80,14 +81,11 @@
                 icon_url = context.icon.getURL()
                 return icon_url
             return default_url
-        
-        if self.context.distribution or self.context.distroseries:
-            icon_url = getIconUrl(
-                self.context.distribution or self.context.distroseries.distribution,
-                "/@@/distribution-badge")
-            return BadgeDetails(icon_url, "Affiliated with Ubuntu")
-        if self.context.product or self.context.productseries:
-            icon_url = getIconUrl(
-                self.context.product or self.context.productseries.product,
-                "/@@/product-badge")
-            return BadgeDetails(icon_url, "Affiliated with Launchpad itself")
+
+        alt_text = "Affiliated with %s" % pillar.displayname
+        if IDistribution.providedBy(pillar):
+            icon_url = getIconUrl(pillar, "/@@/distribution-badge")
+            return BadgeDetails(icon_url, alt_text)
+        else:
+            icon_url = getIconUrl(pillar, "/@@/product-badge")
+            return BadgeDetails(icon_url, alt_text)

=== modified file 'lib/lp/registry/tests/test_pillaraffiliation.py'
--- lib/lp/registry/tests/test_pillaraffiliation.py	2011-06-04 04:34:59 +0000
+++ lib/lp/registry/tests/test_pillaraffiliation.py	2011-08-01 21:59:27 +0000
@@ -17,17 +17,17 @@
     def test_bugtask_distro_affiliation(self):
         # A person who owns a bugtask distro is affiliated.
         person = self.factory.makePerson()
-        distro = self.factory.makeDistribution(owner=person)
+        distro = self.factory.makeDistribution(owner=person, name='pting')
         bugtask = self.factory.makeBugTask(target=distro)
         badge = IHasAffiliation(bugtask).getAffiliationBadge(person)
         self.assertEqual(
-            badge, ("/@@/distribution-badge", "Affiliated with Ubuntu"))
+            badge, ("/@@/distribution-badge", "Affiliated with Pting"))
 
     def test_bugtask_product_affiliation(self):
         # A person who owns a bugtask product is affiliated.
         person = self.factory.makePerson()
-        product = self.factory.makeProduct(owner=person)
+        product = self.factory.makeProduct(owner=person, name='pting')
         bugtask = self.factory.makeBugTask(target=product)
         badge = IHasAffiliation(bugtask).getAffiliationBadge(person)
         self.assertEqual(
-            badge, ("/@@/product-badge", "Affiliated with Launchpad itself"))
+            badge, ("/@@/product-badge", "Affiliated with Pting"))