← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~bac/launchpad/lep-projconfig into lp:launchpad/devel

 

Brad Crittenden has proposed merging lp:~bac/launchpad/lep-projconfig into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #70613 Encourage projects to configure all services (eg. bug tracker)
  https://bugs.launchpad.net/bugs/70613


= Summary =

The summary of this work is in a LEP at
https://dev.launchpad.net/LEP/ProjectConfiguration
and bug 70613.

In short, show project owners how to finish setting up their project and
allow all users the ability to see the things they could be doing, if
only they were configured.

== Proposed fix ==

Add a progress bar and icons showing the owner remaining configuration
items.

If some one chooses not to use a LP facility there is no way to make
that known and silence the warning.

== Pre-implementation notes ==

Talks with Jono and Curtis.

== Implementation details ==

As above.

== Tests ==

Since changes were made to the menu links, really all tests need to be run.

== Demo and Q/A ==

http://launchpad.dev/firefox

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/templates/product-index.pt
  lib/canonical/launchpad/webapp/interfaces.py
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/pillar.py
  lib/canonical/launchpad/icing/style-3-0.css.in
  lib/lp/registry/stories/product/xx-product-files.txt
  lib/lp/registry/templates/pillar-involvement-portlet.pt
  lib/lp/registry/browser/tests/pillar-views.txt
  lib/canonical/launchpad/webapp/menu.py
  lib/canonical/launchpad/templates/launchpad-inline-link.pt
  lib/lp/registry/tests/test_product.py

./lib/canonical/launchpad/webapp/interfaces.py
     905: redefinition of unused 'StartRequestEvent' from line 897
     300: E301 expected 1 blank line, found 0
     302: E202 whitespace before ')'
     310: E302 expected 2 blank lines, found 1
     331: E202 whitespace before ')'
     336: E301 expected 1 blank line, found 0
     339: E301 expected 1 blank line, found 0
     341: E301 expected 1 blank line, found 0
     343: E301 expected 1 blank line, found 0
     446: E301 expected 1 blank line, found 0
     453: E301 expected 1 blank line, found 0
     461: E301 expected 1 blank line, found 0
     476: E301 expected 1 blank line, found 0
     520: E302 expected 2 blank lines, found 1
     626: E202 whitespace before ')'
     659: E202 whitespace before ')'
     774: E301 expected 1 blank line, found 0
     844: E301 expected 1 blank line, found 0
     899: E301 expected 1 blank line, found 0
     905: E301 expected 1 blank line, found 2
     217: Line exceeds 78 characters.
./lib/lp/registry/browser/product.py
    2026: E222 multiple spaces after operator
./lib/canonical/launchpad/icing/style-3-0.css.in
    1290: Line exceeds 78 characters.
    1887: Line exceeds 78 characters.
    1903: Line exceeds 78 characters.
    1907: Line exceeds 78 characters.
    1915: Line exceeds 78 characters.
    1919: Line exceeds 78 characters.
    1947: Line exceeds 78 characters.
    1963: Line exceeds 78 characters.
    1971: Line exceeds 78 characters.
    1975: Line exceeds 78 characters.
    1979: Line exceeds 78 characters.
    1983: Line exceeds 78 characters.
    1987: Line exceeds 78 characters.
    1991: Line exceeds 78 characters.
    2004: Line exceeds 78 characters.
    2008: Line exceeds 78 characters.
    2040: Line exceeds 78 characters.
    2052: Line exceeds 78 characters.
    2056: Line exceeds 78 characters.
    2060: Line exceeds 78 characters.
    2068: Line exceeds 78 characters.
    2072: Line exceeds 78 characters.
    2080: Line exceeds 78 characters.
    2108: Line exceeds 78 characters.
    2112: Line exceeds 78 characters.
    2140: Line exceeds 78 characters.
    2148: Line exceeds 78 characters.
    2153: Line exceeds 78 characters.
    2157: Line exceeds 78 characters.
    2162: Line exceeds 78 characters.
    2166: Line exceeds 78 characters.
    2170: Line exceeds 78 characters.
    2174: Line exceeds 78 characters.
    2178: Line exceeds 78 characters.
    2230: Line exceeds 78 characters.
    2238: Line exceeds 78 characters.
    2242: Line exceeds 78 characters.
    2250: Line exceeds 78 characters.
    2262: Line exceeds 78 characters.
    2270: Line exceeds 78 characters.
    2274: Line exceeds 78 characters.
    2278: Line exceeds 78 characters.
    2286: Line exceeds 78 characters.
    2290: Line exceeds 78 characters.
    2294: Line exceeds 78 characters.
    2298: Line exceeds 78 characters.
    2302: Line exceeds 78 characters.
    2311: Line exceeds 78 characters.
    2319: Line exceeds 78 characters.
    2451: Line exceeds 78 characters.
    2452: Line exceeds 78 characters.
    2521: Line exceeds 78 characters.
    2522: Line exceeds 78 characters.
./lib/lp/registry/stories/product/xx-product-files.txt
       0: narrative uses a moin header.
      36: want exceeds 78 characters.
      44: narrative uses a moin header.
     109: narrative uses a moin header.
     158: narrative uses a moin header.
     386: narrative uses a moin header.
     436: narrative uses a moin header.
     475: narrative uses a moin header.
./lib/lp/registry/browser/tests/pillar-views.txt
     133: narrative exceeds 78 characters.
./lib/canonical/launchpad/webapp/menu.py
      85: E222 multiple spaces after operator
     133: W602 deprecated form of raising exception
     180: E301 expected 1 blank line, found 0
     482: E301 expected 1 blank line, found 0
     500: E302 expected 2 blank lines, found 3
./lib/lp/registry/tests/test_product.py
      30: E302 expected 2 blank lines, found 1

I'll wade through these and correct as necessary.
-- 
https://code.launchpad.net/~bac/launchpad/lep-projconfig/+merge/30999
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bac/launchpad/lep-projconfig into lp:launchpad/devel.
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
--- lib/canonical/launchpad/icing/style-3-0.css.in	2010-07-26 14:48:43 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css.in	2010-07-26 22:01:07 +0000
@@ -686,6 +686,13 @@
     margin: 0;
     padding: 0;
     }
+div.centered {
+    text-align: center;
+    }
+div.centered table {
+    margin: 0 auto;
+    text-align: left;
+    }
 .batch-navigation-links .next {
     /* Next links have icons: */
     background: center right no-repeat;

=== modified file 'lib/canonical/launchpad/templates/launchpad-inline-link.pt'
--- lib/canonical/launchpad/templates/launchpad-inline-link.pt	2009-08-27 02:22:07 +0000
+++ lib/canonical/launchpad/templates/launchpad-inline-link.pt	2010-07-26 22:01:07 +0000
@@ -2,8 +2,10 @@
   xmlns:tal="http://xml.zope.org/namespaces/tal";
   condition="context/enabled">
   <tal:link-linked
-    condition="context/linked"><a
-      href="" class="" title=""
+    condition="context/linked">
+    <table width="100%"><tr>
+        <td width="90%">
+    <a href="" class="" title=""
       tal:condition="context/icon"
       tal:attributes="
         class string:menu-link-${context/name} ${view/sprite_class} ${context/icon};
@@ -13,7 +15,7 @@
       tal:content="structure context/escapedtext"
     /><a
       tal:condition="not: context/icon"
-      style="line-height: 20px;"
+      style="line-height: 40px;"
       href="" class="" title=""
       tal:attributes="
         class string:menu-link-${context/name};
@@ -21,7 +23,16 @@
         title context/summary;
         "
       tal:content="structure context/escapedtext"
-    /></tal:link-linked>
+    /></td>
+        <td>
+    <span tal:condition="python: context.configured==True"
+           class="sprite yes"></span>
+    <span tal:condition="python: context.configured==False"
+           class="sprite no"></span>
+        </td>
+      </tr>
+    </table>
+    </tal:link-linked>
   <tal:link-not-linked
     condition="not: context/linked"><span
       tal:condition="context/icon"

=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
--- lib/canonical/launchpad/webapp/interfaces.py	2010-07-21 08:15:57 +0000
+++ lib/canonical/launchpad/webapp/interfaces.py	2010-07-26 22:01:07 +0000
@@ -238,6 +238,10 @@
     icon_url = Attribute(
         "The full URL for this link's associated icon, if it has one.")
 
+    configured = Attribute(
+        "Whether the item has been configured yet.  If None, no indicator is"
+        "used.  Otherwise it is a boolean.")
+
     def render():
         """Return a HTML representation of the link."""
 

=== modified file 'lib/canonical/launchpad/webapp/menu.py'
--- lib/canonical/launchpad/webapp/menu.py	2010-06-15 19:35:41 +0000
+++ lib/canonical/launchpad/webapp/menu.py	2010-07-26 22:01:07 +0000
@@ -102,7 +102,7 @@
     implements(ILinkData)
 
     def __init__(self, target, text, summary=None, icon=None, enabled=True,
-                 site=None, menu=None):
+                 site=None, menu=None, configured=None):
         """Create a new link to 'target' with 'text' as the link text.
 
         'target' is a relative path, an absolute path, or an absolute url.
@@ -121,6 +121,9 @@
         'blueprint' for a specific site.
 
         :param menu: The sub menu used by the page that the link represents.
+
+        :parem configured: Whether the item has been configured.  If not None,
+        then show a boolean indicator for configured or not configured.
         """
         self.target = target
         self.text = text
@@ -131,6 +134,7 @@
         self.enabled = enabled
         self.site = site
         self.menu = menu
+        self.configured = configured
 
 Link = LinkData
 
@@ -171,6 +175,13 @@
         else:
             return cgi.escape(text)
 
+    def set_configured(self, value):
+        self._linkdata.configured = value
+    def get_configured(self):
+        return self._linkdata.configured
+
+    configured = property(get_configured, set_configured)
+
     @property
     def icon_url(self):
         """The full URL of this link's associated icon, if it has one."""

=== modified file 'lib/lp/registry/browser/pillar.py'
--- lib/lp/registry/browser/pillar.py	2010-06-17 15:26:05 +0000
+++ lib/lp/registry/browser/pillar.py	2010-07-26 22:01:07 +0000
@@ -78,6 +78,7 @@
 
     configuration_links = []
     visible_disabled_link_names = []
+    show_progress = False
 
     def __init__(self, context, request):
         super(PillarView, self).__init__(context, request)
@@ -154,6 +155,14 @@
             link for link in important_links if not link.enabled],
             key=attrgetter('sort_key'))
 
+    @property
+    def registration_completeness(self):
+        """The percent complete for registration.
+
+        Not used by all pillars.
+        """
+        return None
+
 
 class PillarBugsMenu(ApplicationMenu, StructuralSubscriptionMenuMixin):
     """Base class for pillar bugs menus."""

=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py	2010-07-09 10:22:32 +0000
+++ lib/lp/registry/browser/product.py	2010-07-26 22:01:07 +0000
@@ -349,7 +349,33 @@
     """Encourage configuration of involvement links for projects."""
 
     has_involvement = True
-    visible_disabled_link_names = ['submit_code']
+    show_progress = True
+
+    @property
+    def visible_disabled_link_names(self):
+        """Show all disabled links...except blueprints"""
+        involved_menu = MenuAPI(self).navigation
+        all_links = involved_menu.keys()
+        # The register blueprints link should not be shown since its use is
+        # not encouraged.
+        all_links.remove('register_blueprint')
+        return all_links
+
+    @cachedproperty
+    def configuration_states(self):
+        """Create a dictionary indicating the configuration statuses.
+
+        Each app area will be represented in the return dictionary, except
+        blueprints which we are not currently promoting.
+        """
+        states = {}
+        states['configure_bugtracker'] = (
+            self.official_malone or self.context.bugtracker is not None)
+        states['configure_answers'] = self.official_answers
+        states['configure_translations'] = self.official_rosetta
+        states['configure_codehosting'] = (
+            self.context.development_focus.branch is not None)
+        return states
 
     @property
     def configuration_links(self):
@@ -357,18 +383,38 @@
         overview_menu = MenuAPI(self.context).overview
         series_menu = MenuAPI(self.context.development_focus).overview
         configuration_names = [
+            'configure_bugtracker',
             'configure_answers',
-            'configure_bugtracker',
             'configure_translations',
+            #'configure_blueprints',
             ]
+        config_statuses = self.configuration_states
+        for key in configuration_names:
+            overview_menu[key].configured = (
+            config_statuses[key])
         configuration_links = [
             overview_menu[name] for name in configuration_names]
+        # Add the branch configuration in separately.
         set_branch = series_menu['set_branch']
         set_branch.text = 'Configure project branch'
+        set_branch.configured = (
+            config_statuses['configure_codehosting'])
         configuration_links.append(set_branch)
-        return sorted([
-            link for link in configuration_links if link.enabled],
-            key=attrgetter('sort_key'))
+        return [
+            link for link in configuration_links if link.enabled]
+
+    @property
+    def registration_completeness(self):
+        """The percent complete for registration."""
+        configured = 0
+        config_statuses = self.configuration_states
+        for key, value in config_statuses.items():
+            if value:
+                configured += 1
+        scale = 100
+        done = int(float(configured) / len(config_statuses) * scale)
+        undone = scale - done
+        return dict(done=done, undone=undone)
 
 
 class ProductNavigationMenu(NavigationMenu):

=== modified file 'lib/lp/registry/browser/tests/pillar-views.txt'
--- lib/lp/registry/browser/tests/pillar-views.txt	2010-05-03 18:44:42 +0000
+++ lib/lp/registry/browser/tests/pillar-views.txt	2010-07-26 22:01:07 +0000
@@ -53,10 +53,12 @@
       <h2>Get Involved</h2>
       <ul class="involvement">
         <li>
-          <a href=... class="...bugs">Report a bug</a>
+          <table...
+          <a href=... class="...bugs">Report a bug</a>...
         </li>
         <li>
-          <a href=... class="...answers">Ask a question</a>
+          <table...
+          <a href=... class="...answers">Ask a question</a>...
         </li>
       </ul>
     ...
@@ -80,7 +82,11 @@
 
     >>> for link in view.visible_disabled_links:
     ...     print link.name
+    report_bug
+    ask_question
+    help_translate
     submit_code
+
     >>> for link in view.configuration_links:
     ...     print link.name
     configure_answers
@@ -88,6 +94,42 @@
     configure_translations
     set_branch
 
+The registration status is determined with the 'configuration_states'
+property.  Notice that blueprints are not included in the
+configuration links nor the completeness computation as the use of
+blueprints is not promoted.
+
+    >>> for key in sorted(view.configuration_states.keys()):
+    ...     print key, view.configuration_states[key]
+    configure_answers False
+    configure_bugtracker False
+    configure_codehosting False
+    configure_translations False
+
+The percentage of the registration completed can be determined by
+using the 'registration_completeness' property, which returns a
+dictionary, which makes it easy for use in the page template.
+
+    >>> print pretty(view.registration_completeness)
+    {'done': 0,
+     'undone': 100}
+
+Changing the product's official usage is reflected in the view properties.
+
+    >>> product.official_malone = True
+    >>> view = create_view(product, '+get-involved')
+    >>> for key in sorted(view.configuration_states.keys()):
+    ...     print key, view.configuration_states[key]
+    configure_answers False
+    configure_bugtracker True
+    configure_codehosting False
+    configure_translations False
+
+    >>> print pretty(view.registration_completeness)
+    {'done': 25,
+     'undone': 75}
+
+
 Project groups are supported too, but they only display the applications used by
 their products.
 

=== modified file 'lib/lp/registry/stories/product/xx-product-files.txt'
--- lib/lp/registry/stories/product/xx-product-files.txt	2010-01-15 21:44:23 +0000
+++ lib/lp/registry/stories/product/xx-product-files.txt	2010-07-26 22:01:07 +0000
@@ -361,15 +361,22 @@
 are listed within series in reverse chronological order, except
 'trunk' the developer focus, is first.
 
+    >>> def print_dl_files(contents):
+    ...     content = find_main_content(contents)
+    ...     rows = content.findAll('tr')
+    ...     for row in rows[1:]:
+    ...         a_list = row.findAll('a')
+    ...         if len(a_list) > 0:
+    ...             name = a_list[0].string
+    ...             if name == 'Add download file':
+    ...                 continue
+    ...             print name
+
     >>> firefox_owner.open('http://launchpad.dev/firefox/+download')
     >>> firefox_owner.url
     'http://launchpad.dev/firefox/+download'
-    >>> content = find_main_content(firefox_owner.contents)
-    >>> rows = content.findAll('tr')
-    >>> for row in rows[1:]:
-    ...     a_list = row.findAll('a')
-    ...     if len(a_list) > 0:
-    ...        print a_list[0].string
+
+    >>> print_dl_files(firefox_owner.contents)
     firefox_0.9.2.orig.tar.gz
     foo09.txt
     foo3.txt
@@ -420,12 +427,7 @@
     >>> firefox_owner.open('http://launchpad.dev/firefox/+download')
     >>> firefox_owner.url
     'http://launchpad.dev/firefox/+download'
-    >>> content = find_main_content(firefox_owner.contents)
-    >>> rows = content.findAll('tr')
-    >>> for row in rows[1:]:
-    ...     a_list = row.findAll('a')
-    ...     if len(a_list) > 0:
-    ...        print a_list[0].string
+    >>> print_dl_files(firefox_owner.contents)
     firefox_0.9.2.orig.tar.gz
     foo09.txt
     foo3.txt

=== modified file 'lib/lp/registry/templates/pillar-involvement-portlet.pt'
--- lib/lp/registry/templates/pillar-involvement-portlet.pt	2010-06-15 19:37:37 +0000
+++ lib/lp/registry/templates/pillar-involvement-portlet.pt	2010-07-26 22:01:07 +0000
@@ -31,6 +31,29 @@
     </tal:disabled>
   </ul>
 
+  <tal:editor tal:condition="context/required:launchpad.Edit">
+    <tal:show_configuration
+       tal:condition="registration"
+       tal:define="registration view/registration_completeness">
+      <h2>Configuration Progress</h2>
+      <div class="centered">
+          <table width="80%" border="1">
+            <tr>
+              <td>
+              <img
+                 tal:attributes="alt string:${registration/done}% registration complete;
+                                 title string:${registration/done}% registration complete;
+                                 width registration/done;
+                                 style string:width: ${registration/done}%;"
+                 src="/@@/green-bar"
+                 height="10"/>
+              </td>
+            </tr>
+          </table>
+      </div>
+    </tal:show_configuration>
+  </tal:editor>
+
   <ul tal:condition="view/configuration_links" style="padding-top: 1em">
     <li tal:repeat="link view/configuration_links">
       <a tal:replace="structure link/fmt:link" />

=== modified file 'lib/lp/registry/templates/product-index.pt'
--- lib/lp/registry/templates/product-index.pt	2010-06-24 13:13:01 +0000
+++ lib/lp/registry/templates/product-index.pt	2010-07-26 22:01:07 +0000
@@ -241,6 +241,8 @@
       define="overview_menu context/menu:overview">
       <tal:menu replace="structure view/@@+global-actions" />
 
+      <div tal:replace="structure context/@@+get-involved" />
+
       <div id="downloads" class="top-portlet downloads"
         tal:define="release view/latest_release_with_download_files">
         <h2>Downloads</h2>
@@ -262,8 +264,6 @@
         </p>
       </div>
 
-      <div tal:replace="structure context/@@+get-involved" />
-
       <div tal:replace="structure context/@@+portlet-latestannouncements" />
     </tal:side>
 

=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py	2010-04-21 16:15:36 +0000
+++ lib/lp/registry/tests/test_product.py	2010-07-26 22:01:07 +0000
@@ -17,6 +17,7 @@
 from canonical.testing import LaunchpadFunctionalLayer
 
 from canonical.launchpad.ftests import syncUpdate
+from canonical.launchpad.testing.pages import find_tags_by_class
 
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.product import License
@@ -82,9 +83,11 @@
             get_feedback_messages(firefox_owner.contents),
             [u"Your file 'foo\xa5.txt' has been uploaded."])
         firefox_owner.open('http://launchpad.dev/firefox/+download')
+
         content = find_main_content(firefox_owner.contents)
-        rows = content.findAll('tr')
-        print
+        tables = find_tags_by_class(str(content), 'listing')
+        rows = tables[1].findAll('tr')
+
         a_list = rows[-1].findAll('a')
         # 1st row
         a_element = a_list[0]


Follow ups