Merge lp:~bac/launchpad/lep-projconfig into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 11281
Proposed branch: lp:~bac/launchpad/lep-projconfig
Merge into: lp:launchpad
Diff against target: 342 lines (+165/-26)
10 files modified
lib/canonical/launchpad/icing/style-3-0.css.in (+7/-0)
lib/canonical/launchpad/webapp/menu.py (+2/-1)
lib/lp/registry/browser/pillar.py (+8/-0)
lib/lp/registry/browser/product.py (+60/-9)
lib/lp/registry/browser/productseries.py (+2/-1)
lib/lp/registry/browser/tests/pillar-views.txt (+44/-4)
lib/lp/registry/stories/product/xx-product-development-focus.txt (+2/-2)
lib/lp/registry/templates/pillar-involvement-portlet.pt (+36/-5)
lib/lp/registry/templates/product-index.pt (+2/-2)
lib/lp/registry/tests/test_product.py (+2/-2)
To merge this branch: bzr merge lp:~bac/launchpad/lep-projconfig
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Launchpad code reviewers Pending
Review via email: mp+30999@code.launchpad.net

Commit message

Show project maintainers application configuration links and a progress bar showing completion status.

Description of the change

= 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.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (8.4 KiB)

Hi Brad.

This change looks great. I see I see extra white space between the download
portlet and announcements.

I think we need a model change to support this :( I an configure Answers to
not be official and my application may not be translatable. So the progress
bar will never be complete. Consider Launchpad. It does not have a po file,
so it cannot be translated, but the UI requires me to set translations to be
official to make the bar be complete. I think we need to None, True, or False
to make this work. I am seeing this same issue my the feature I am working
on. I propose we switch from bool to a vocab or UNKNOWN, LAUNCHPAD, EXTERNAL.
We may only need this some of the apps.

I have a lot of concern about the implementation. I do not think ILink, its
model, and template needed to change. I certainly do not think we should
be making invalid HTML on all Launchpad pages for a single portlet that
appears on one page. I think the fix is easy, but I have not spent a lot
of time looking at the issue.

> === 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;
> + }

We used to have rules like this for the 1.0 layouts. I guess It was naive
of me to remove them.

> === 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=""

This looks scary. I think it is wrong. This file is used to adapt every link,
and most links are inline text.

/me runs lp

This cannot land. It is create tables in paragraphs and inline markup that
cannot contain tables.

Since we are designing a presentation that will appear on exactly one page
and we have not discussed reusing it. I expected to see either a subclass
or some form of delegates so that the portlet's needs did not step on any
other part of launchpad. The Involvement portlet is already a bundle of
exceptions for menus and does its own layout. I think the view and existing
template could be doing all the work.

Maybe we should have looked closer at why the Involvement used CSS to place
a right aligned background image.

...

> === 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'...

Read more...

review: Needs Fixing (code and ui)
Revision history for this message
Brad Crittenden (bac) wrote :

Hi Curtis,
Thanks for the review and the excellent suggestions to save me from myself.

Diff at http://pastebin.ubuntu.com/472353/
New screenshot at http://people.canonical.com/~bac/config-progress-bar.png

Revision history for this message
Curtis Hovey (sinzui) wrote :

The inline diff is really messed up so I am just looking at the paste.

in lib/lp/registry/templates/pillar-involvement-portlet.pt, I see invalid markup. A list must contain an li, and it can never contain a table. The ul is not needed since the table is a block element.

+ <ul tal:condition="view/configuration_links" style="padding-top: 1em">
+ <table style="width: 100%">

maybe this works
     <table style="padding-top: 1em; width: 100%"
       tal:condition="view/configuration_links">

review: Needs Fixing (code)
Revision history for this message
Brad Crittenden (bac) wrote :

Sorry I failed to see and strip out that <ul>.

Diff: http://pastebin.ubuntu.com/472388/

Revision history for this message
Curtis Hovey (sinzui) wrote :

Looks good to land.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
2--- lib/canonical/launchpad/icing/style-3-0.css.in 2010-07-26 14:48:43 +0000
3+++ lib/canonical/launchpad/icing/style-3-0.css.in 2010-08-03 12:43:45 +0000
4@@ -686,6 +686,13 @@
5 margin: 0;
6 padding: 0;
7 }
8+div.centered {
9+ text-align: center;
10+ }
11+div.centered table {
12+ margin: 0 auto;
13+ text-align: left;
14+ }
15 .batch-navigation-links .next {
16 /* Next links have icons: */
17 background: center right no-repeat;
18
19=== modified file 'lib/canonical/launchpad/webapp/menu.py'
20--- lib/canonical/launchpad/webapp/menu.py 2010-06-15 19:35:41 +0000
21+++ lib/canonical/launchpad/webapp/menu.py 2010-08-03 12:43:45 +0000
22@@ -122,12 +122,13 @@
23
24 :param menu: The sub menu used by the page that the link represents.
25 """
26+
27 self.target = target
28 self.text = text
29 self.summary = summary
30 self.icon = icon
31 if not isinstance(enabled, bool):
32- raise AssertionError, "enabled must be boolean, got %r" % enabled
33+ raise AssertionError("enabled must be boolean, got %r" % enabled)
34 self.enabled = enabled
35 self.site = site
36 self.menu = menu
37
38=== modified file 'lib/lp/registry/browser/pillar.py'
39--- lib/lp/registry/browser/pillar.py 2010-06-17 15:26:05 +0000
40+++ lib/lp/registry/browser/pillar.py 2010-08-03 12:43:45 +0000
41@@ -154,6 +154,14 @@
42 link for link in important_links if not link.enabled],
43 key=attrgetter('sort_key'))
44
45+ @property
46+ def registration_completeness(self):
47+ """The percent complete for registration.
48+
49+ Not used by all pillars.
50+ """
51+ return None
52+
53
54 class PillarBugsMenu(ApplicationMenu, StructuralSubscriptionMenuMixin):
55 """Base class for pillar bugs menus."""
56
57=== modified file 'lib/lp/registry/browser/product.py'
58--- lib/lp/registry/browser/product.py 2010-08-02 02:13:52 +0000
59+++ lib/lp/registry/browser/product.py 2010-08-03 12:43:45 +0000
60@@ -350,26 +350,77 @@
61 """Encourage configuration of involvement links for projects."""
62
63 has_involvement = True
64- visible_disabled_link_names = ['submit_code']
65+
66+ @property
67+ def visible_disabled_link_names(self):
68+ """Show all disabled links...except blueprints"""
69+ involved_menu = MenuAPI(self).navigation
70+ all_links = involved_menu.keys()
71+ # The register blueprints link should not be shown since its use is
72+ # not encouraged.
73+ all_links.remove('register_blueprint')
74+ return all_links
75+
76+ @cachedproperty
77+ def configuration_states(self):
78+ """Create a dictionary indicating the configuration statuses.
79+
80+ Each app area will be represented in the return dictionary, except
81+ blueprints which we are not currently promoting.
82+ """
83+ states = {}
84+ states['configure_bugtracker'] = (
85+ self.official_malone or self.context.bugtracker is not None)
86+ states['configure_answers'] = self.official_answers
87+ states['configure_translations'] = self.official_rosetta
88+ states['configure_codehosting'] = (
89+ self.context.development_focus.branch is not None)
90+ return states
91
92 @property
93 def configuration_links(self):
94- """The enabled involvement links."""
95+ """The enabled involvement links.
96+
97+ Returns a list of dicts keyed by:
98+ 'link' -- the menu link, and
99+ 'configured' -- a boolean representing the configuration status.
100+ """
101 overview_menu = MenuAPI(self.context).overview
102 series_menu = MenuAPI(self.context.development_focus).overview
103 configuration_names = [
104+ 'configure_bugtracker',
105 'configure_answers',
106- 'configure_bugtracker',
107 'configure_translations',
108+ #'configure_blueprints',
109 ]
110- configuration_links = [
111- overview_menu[name] for name in configuration_names]
112+ config_list = []
113+ config_statuses = self.configuration_states
114+ for key in configuration_names:
115+ config_list.append(dict(link=overview_menu[key],
116+ configured=config_statuses[key]))
117+
118+ # Add the branch configuration in separately.
119 set_branch = series_menu['set_branch']
120 set_branch.text = 'Configure project branch'
121- configuration_links.append(set_branch)
122- return sorted([
123- link for link in configuration_links if link.enabled],
124- key=attrgetter('sort_key'))
125+ set_branch.configured = (
126+ )
127+ config_list.append(
128+ dict(link=set_branch,
129+ configured=config_statuses['configure_codehosting']))
130+ return config_list
131+
132+ @property
133+ def registration_completeness(self):
134+ """The percent complete for registration."""
135+ configured = 0
136+ config_statuses = self.configuration_states
137+ for key, value in config_statuses.items():
138+ if value:
139+ configured += 1
140+ scale = 100
141+ done = int(float(configured) / len(config_statuses) * scale)
142+ undone = scale - done
143+ return dict(done=done, undone=undone)
144
145
146 class ProductNavigationMenu(NavigationMenu):
147
148=== modified file 'lib/lp/registry/browser/productseries.py'
149--- lib/lp/registry/browser/productseries.py 2010-08-02 02:51:42 +0000
150+++ lib/lp/registry/browser/productseries.py 2010-08-03 12:43:45 +0000
151@@ -215,7 +215,8 @@
152 series_menu = MenuAPI(self.context).overview
153 set_branch = series_menu['set_branch']
154 set_branch.text = 'Configure series branch'
155- return [set_branch]
156+ return [dict(link=set_branch,
157+ configured=self.official_codehosting)]
158
159
160 class ProductSeriesOverviewMenu(
161
162=== modified file 'lib/lp/registry/browser/tests/pillar-views.txt'
163--- lib/lp/registry/browser/tests/pillar-views.txt 2010-05-03 18:44:42 +0000
164+++ lib/lp/registry/browser/tests/pillar-views.txt 2010-08-03 12:43:45 +0000
165@@ -53,10 +53,10 @@
166 <h2>Get Involved</h2>
167 <ul class="involvement">
168 <li>
169- <a href=... class="...bugs">Report a bug</a>
170+ <a href=... class="...bugs">Report a bug</a>...
171 </li>
172 <li>
173- <a href=... class="...answers">Ask a question</a>
174+ <a href=... class="...answers">Ask a question</a>...
175 </li>
176 </ul>
177 ...
178@@ -80,14 +80,54 @@
179
180 >>> for link in view.visible_disabled_links:
181 ... print link.name
182+ report_bug
183+ ask_question
184+ help_translate
185 submit_code
186+
187 >>> for link in view.configuration_links:
188- ... print link.name
189+ ... print link['link'].name
190+ configure_bugtracker
191 configure_answers
192- configure_bugtracker
193 configure_translations
194 set_branch
195
196+The registration status is determined with the 'configuration_states'
197+property. Notice that blueprints are not included in the
198+configuration links nor the completeness computation as the use of
199+blueprints is not promoted.
200+
201+ >>> for key in sorted(view.configuration_states.keys()):
202+ ... print key, view.configuration_states[key]
203+ configure_answers False
204+ configure_bugtracker False
205+ configure_codehosting False
206+ configure_translations False
207+
208+The percentage of the registration completed can be determined by
209+using the 'registration_completeness' property, which returns a
210+dictionary, which makes it easy for use in the page template.
211+
212+ >>> print pretty(view.registration_completeness)
213+ {'done': 0,
214+ 'undone': 100}
215+
216+Changing the product's official usage is reflected in the view properties.
217+
218+ >>> product.official_malone = True
219+ >>> view = create_view(product, '+get-involved')
220+ >>> for key in sorted(view.configuration_states.keys()):
221+ ... print key, view.configuration_states[key]
222+ configure_answers False
223+ configure_bugtracker True
224+ configure_codehosting False
225+ configure_translations False
226+
227+ >>> print pretty(view.registration_completeness)
228+ {'done': 25,
229+ 'undone': 75}
230+
231+
232 Project groups are supported too, but they only display the applications used by
233 their products.
234
235
236=== modified file 'lib/lp/registry/stories/product/xx-product-development-focus.txt'
237--- lib/lp/registry/stories/product/xx-product-development-focus.txt 2010-04-09 16:19:25 +0000
238+++ lib/lp/registry/stories/product/xx-product-development-focus.txt 2010-08-03 12:43:45 +0000
239@@ -73,10 +73,10 @@
240 <span class="invisible-link">Change details</span>
241 (http://launchpad.dev/fooix/+edit)
242 >>> print_involvement_portlet(owner_browser)
243+ Configure bug tracker
244+ http://launchpad.dev/fooix/+configure-bugtracker
245 Configure support tracker
246 http://launchpad.dev/fooix/+configure-answers
247- Configure bug tracker
248- http://launchpad.dev/fooix/+configure-bugtracker
249 Configure translations
250 http://launchpad.dev/fooix/+configure-translations
251 Configure project branch
252
253=== modified file 'lib/lp/registry/templates/pillar-involvement-portlet.pt'
254--- lib/lp/registry/templates/pillar-involvement-portlet.pt 2010-06-15 19:37:37 +0000
255+++ lib/lp/registry/templates/pillar-involvement-portlet.pt 2010-08-03 12:43:45 +0000
256@@ -31,9 +31,40 @@
257 </tal:disabled>
258 </ul>
259
260- <ul tal:condition="view/configuration_links" style="padding-top: 1em">
261- <li tal:repeat="link view/configuration_links">
262- <a tal:replace="structure link/fmt:link" />
263- </li>
264- </ul>
265+ <tal:editor condition="context/required:launchpad.Edit"
266+ define="registration view/registration_completeness">
267+ <tal:show_configuration condition="registration">
268+ <h2>Configuration Progress</h2>
269+ <div class="centered">
270+ <table width="80%" border="1">
271+ <tr>
272+ <td>
273+ <img
274+ tal:attributes="alt string:${registration/done}% registration complete;
275+ title string:${registration/done}% registration complete;
276+ width registration/done;
277+ style string:width: ${registration/done}%;"
278+ src="/@@/green-bar"
279+ height="10"/>
280+ </td>
281+ </tr>
282+ </table>
283+ </div>
284+ </tal:show_configuration>
285+
286+ <table style="width: 100%; padding-top: 1em"
287+ tal:condition="view/configuration_links">
288+ <tal:item repeat="link_status view/configuration_links">
289+ <tr>
290+ <td>
291+ <a tal:replace="structure link_status/link/fmt:link" />
292+ </td>
293+ <td style="text-align: right;" tal:condition="registration">
294+ <tal:yes-no replace="structure link_status/configured/image:boolean" />
295+ </td>
296+ </tr>
297+ </tal:item>
298+ </table>
299+
300+ </tal:editor>
301 </div>
302
303=== modified file 'lib/lp/registry/templates/product-index.pt'
304--- lib/lp/registry/templates/product-index.pt 2010-06-24 13:13:01 +0000
305+++ lib/lp/registry/templates/product-index.pt 2010-08-03 12:43:45 +0000
306@@ -241,6 +241,8 @@
307 define="overview_menu context/menu:overview">
308 <tal:menu replace="structure view/@@+global-actions" />
309
310+ <div tal:replace="structure context/@@+get-involved" />
311+
312 <div id="downloads" class="top-portlet downloads"
313 tal:define="release view/latest_release_with_download_files">
314 <h2>Downloads</h2>
315@@ -262,8 +264,6 @@
316 </p>
317 </div>
318
319- <div tal:replace="structure context/@@+get-involved" />
320-
321 <div tal:replace="structure context/@@+portlet-latestannouncements" />
322 </tal:side>
323
324
325=== modified file 'lib/lp/registry/tests/test_product.py'
326--- lib/lp/registry/tests/test_product.py 2010-04-21 16:15:36 +0000
327+++ lib/lp/registry/tests/test_product.py 2010-08-03 12:43:45 +0000
328@@ -1,4 +1,4 @@
329-# Copyright 2009 Canonical Ltd. This software is licensed under the
330+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
331 # GNU Affero General Public License version 3 (see the file LICENSE).
332
333 __metaclass__ = type
334@@ -84,7 +84,7 @@
335 firefox_owner.open('http://launchpad.dev/firefox/+download')
336 content = find_main_content(firefox_owner.contents)
337 rows = content.findAll('tr')
338- print
339+
340 a_list = rows[-1].findAll('a')
341 # 1st row
342 a_element = a_list[0]