Merge lp:~jtv/launchpad/bug-827347 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 13730
Proposed branch: lp:~jtv/launchpad/bug-827347
Merge into: lp:launchpad
Diff against target: 428 lines (+224/-84)
3 files modified
lib/lp/registry/browser/distroseries.py (+90/-22)
lib/lp/registry/browser/tests/test_distroseries.py (+97/-27)
lib/lp/registry/templates/distroseries-portlet-derivation.pt (+37/-35)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-827347
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+72030@code.launchpad.net

Commit message

[r=allenap][bug=827347] Link to +localpackagediffs from DistroSeries if there are any differences at all in it.

Description of the change

= Summary =

On the page for a derived distroseries, there's a link to the +localpackagediffs page. The latter page shows only DistroSeriesDifferences that need attention by default, but can also show them all.

Unfortunately the link only shows up if there are DSDs that need attention. If you want to have a look at resolved differences, say, well you could but there's not going to be any link to the page.

== Proposed fix ==

Link to +localpackagediffs (but one that shows all of the DSDs) when the series has any DSDs at all. Show the overall count of DSDs with that link. Add a parenthesized count of DSDs that need attention, if there are any.

The links and counts for the other two types of DSD (present in the parent but not in the derived series, or the other way around) stay essentially the same. A small difference is that the link no longer comprises the full phrase describing the differences; that was altogether too blue and made the separation between the adjacent +localpackagediffs links a bit hard to notice.

So now, the link goes "1234 packages <with differences> (60 _needing attention_)".

== Pre-implementation notes ==

Went through a bit of UI prototyping with Julian and Raphaƫl. Also discussed desired functionality.

== Implementation details ==

I moved some logic, including the construction of links and the formulation of plurals, from the TAL into the browser code. That also lets me test the details at a lower level, without needing to render the view and search the HTML or DOM.

Hopefully you won't find my method names too lofty: wordVersionDifferences, alludeToParent. I'm not doing that for the fun of it. These verbs really seemed to describe what I wanted to do more precisely.

You'll also notice a bit of unrelated cleanup where I replace DistroSeriesDifferenceStatus.items with None. That's because it's being passed to a method that now accepts None to mean "any status."

== Tests ==

{{{
./bin/test -vvc lp.registry.browser.tests.test_distroseries
}}}

== Demo and Q/A ==

Go over the Derived section of a bunch of derived DistroSeries. There should be no differences links where there are no differences. The two links for different versions should distinguish differences that need attention from differences overall, and either should appear only when they have something to display.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/templates/distroseries-portlet-derivation.pt
  lib/lp/registry/browser/tests/test_distroseries.py
  lib/lp/registry/browser/distroseries.py

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/distroseries.py'
2--- lib/lp/registry/browser/distroseries.py 2011-08-16 20:35:27 +0000
3+++ lib/lp/registry/browser/distroseries.py 2011-08-18 12:34:26 +0000
4@@ -101,6 +101,7 @@
5 from lp.registry.interfaces.person import IPersonSet
6 from lp.registry.interfaces.pocket import PackagePublishingPocket
7 from lp.registry.interfaces.series import SeriesStatus
8+from lp.services.browser_helpers import get_plural_text
9 from lp.services.features import getFeatureFlag
10 from lp.services.propertycache import cachedproperty
11 from lp.services.worlddata.interfaces.country import ICountry
12@@ -127,6 +128,11 @@
13 ]
14
15
16+def get_dsd_source():
17+ """For convenience: the `IDistroSeriesDifferenceSource` utility."""
18+ return getUtility(IDistroSeriesDifferenceSource)
19+
20+
21 class DistroSeriesNavigation(GetitemNavigation, BugTargetTraversalMixin,
22 StructuralSubscriptionTargetTraversalMixin):
23
24@@ -400,6 +406,14 @@
25 return 'a parent series'
26
27
28+def word_differences_count(count):
29+ """Express "`count` differences" in words.
30+
31+ For example, "1 package", or "2 packages" and so on.
32+ """
33+ return get_plural_text(count, "%d package", "%d packages") % count
34+
35+
36 class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
37 DerivedDistroSeriesMixin):
38
39@@ -463,29 +477,86 @@
40 def milestone_batch_navigator(self):
41 return BatchNavigator(self.context.all_milestones, self.request)
42
43- def _num_differences(self, difference_type):
44- differences = getUtility(
45- IDistroSeriesDifferenceSource).getForDistroSeries(
46- self.context,
47- difference_type=difference_type,
48- status=(DistroSeriesDifferenceStatus.NEEDS_ATTENTION,))
49+ def countDifferences(self, difference_type, needing_attention_only=True):
50+ """Count the number of differences of a given kind.
51+
52+ :param difference_type: Type of `DistroSeriesDifference` to look for.
53+ :param needing_attention_only: Restrict count to differences that need
54+ attention? If not, count all that can be viewed.
55+ """
56+ if needing_attention_only:
57+ status = (DistroSeriesDifferenceStatus.NEEDS_ATTENTION, )
58+ else:
59+ status = None
60+
61+ differences = get_dsd_source().getForDistroSeries(
62+ self.context, difference_type=difference_type, status=status)
63 return differences.count()
64
65 @cachedproperty
66- def num_differences(self):
67- return self._num_differences(
68+ def num_version_differences_needing_attention(self):
69+ return self.countDifferences(
70 DistroSeriesDifferenceType.DIFFERENT_VERSIONS)
71
72 @cachedproperty
73+ def num_version_differences(self):
74+ return self.countDifferences(
75+ DistroSeriesDifferenceType.DIFFERENT_VERSIONS,
76+ needing_attention_only=False)
77+
78+ def wordVersionDifferences(self):
79+ return word_differences_count(self.num_version_differences)
80+
81+ @cachedproperty
82 def num_differences_in_parent(self):
83- return self._num_differences(
84+ return self.countDifferences(
85 DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES)
86
87+ def wordDifferencesInParent(self):
88+ return word_differences_count(self.num_differences_in_parent)
89+
90 @cachedproperty
91 def num_differences_in_child(self):
92- return self._num_differences(
93+ return self.countDifferences(
94 DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES)
95
96+ def wordDifferencesInChild(self):
97+ return word_differences_count(self.num_differences_in_child)
98+
99+ def alludeToParent(self):
100+ """Wording to refer to the series' parent(s).
101+
102+ If there is a single parent, returns its display name. Otherwise
103+ says "a parent series" (more vague, but we could also name parents
104+ if there are very few).
105+ """
106+ if self.has_unique_parent:
107+ return self.unique_parent.displayname
108+ else:
109+ return "a parent series"
110+
111+ @cachedproperty
112+ def link_to_version_diffs_needing_attention(self):
113+ """Return URL for +localpackagediffs page."""
114+ return canonical_url(self.context, view_name='+localpackagediffs')
115+
116+ @property
117+ def link_to_all_version_diffs(self):
118+ """Return URL for +localdiffs page for all statuses."""
119+ return (
120+ "%s?field.package_type=all"
121+ % self.link_to_version_diffs_needing_attention)
122+
123+ @property
124+ def link_to_differences_in_parent(self):
125+ """Return URL for +missingpackages page."""
126+ return canonical_url(self.context, view_name='+missingpackages')
127+
128+ @property
129+ def link_to_differences_in_child(self):
130+ """Return URL for +uniquepackages page."""
131+ return canonical_url(self.context, view_name='+uniquepackages')
132+
133
134 class DistroSeriesEditView(LaunchpadEditFormView, SeriesStatusMixin):
135 """View class that lets you edit a DistroSeries object.
136@@ -1090,12 +1161,11 @@
137 def cached_differences(self):
138 """Return a batch navigator of filtered results."""
139 package_type_dsd_status = {
140- NON_IGNORED: (
141- DistroSeriesDifferenceStatus.NEEDS_ATTENTION,),
142+ NON_IGNORED: DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
143 HIGHER_VERSION_THAN_PARENT: (
144 DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT),
145 RESOLVED: DistroSeriesDifferenceStatus.RESOLVED,
146- ALL: DistroSeriesDifferenceStatus.items,
147+ ALL: None,
148 }
149
150 # If the package_type option is not supported, add an error to
151@@ -1107,13 +1177,12 @@
152 status = package_type_dsd_status[self.specified_package_type]
153 child_version_higher = (
154 self.specified_package_type == HIGHER_VERSION_THAN_PARENT)
155- differences = getUtility(
156- IDistroSeriesDifferenceSource).getForDistroSeries(
157- self.context, difference_type=self.differences_type,
158- name_filter=self.specified_name_filter,
159- status=status, child_version_higher=child_version_higher,
160- packagesets=self.specified_packagesets_filter,
161- changed_by=self.specified_changed_by_filter)
162+ differences = get_dsd_source().getForDistroSeries(
163+ self.context, difference_type=self.differences_type,
164+ name_filter=self.specified_name_filter, status=status,
165+ child_version_higher=child_version_higher,
166+ packagesets=self.specified_packagesets_filter,
167+ changed_by=self.specified_changed_by_filter)
168
169 return BatchNavigator(differences, self.request)
170
171@@ -1185,8 +1254,7 @@
172
173 :return: A result set of `DistroSeriesDifference`s.
174 """
175- return getUtility(IDistroSeriesDifferenceSource).getSimpleUpgrades(
176- self.context)
177+ return get_dsd_source().getSimpleUpgrades(self.context)
178
179 @action(_("Upgrade Packages"), name="upgrade", condition='canUpgrade')
180 def upgrade(self, action, data):
181
182=== modified file 'lib/lp/registry/browser/tests/test_distroseries.py'
183--- lib/lp/registry/browser/tests/test_distroseries.py 2011-08-18 10:05:38 +0000
184+++ lib/lp/registry/browser/tests/test_distroseries.py 2011-08-18 12:34:26 +0000
185@@ -141,18 +141,55 @@
186 view = create_initialized_view(distroseries, '+index')
187 self.assertEqual(view.needs_linking, None)
188
189- def _createDifferenceAndGetView(self, difference_type):
190+ def _createDifferenceAndGetView(self, difference_type, status=None):
191+ if status is None:
192+ status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION
193 # Helper function to create a valid DSD.
194 dsp = self.factory.makeDistroSeriesParent()
195 self.factory.makeDistroSeriesDifference(
196 derived_series=dsp.derived_series,
197- difference_type=difference_type)
198+ difference_type=difference_type, status=status)
199 return create_initialized_view(dsp.derived_series, '+index')
200
201- def test_num_differences(self):
202- diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
203- view = self._createDifferenceAndGetView(diff_type)
204- self.assertEqual(1, view.num_differences)
205+ def test_num_version_differences_needing_attention(self):
206+ # num_version_differences_needing_attention counts
207+ # different-versions-type differences in needs-attention state.
208+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
209+ view = self._createDifferenceAndGetView(diff_type)
210+ self.assertEqual(1, view.num_version_differences_needing_attention)
211+
212+ def test_num_version_differences_needing_attention_limits_type(self):
213+ # num_version_differences_needing_attention ignores other types
214+ # of difference.
215+ diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
216+ view = self._createDifferenceAndGetView(diff_type)
217+ self.assertEqual(0, view.num_version_differences_needing_attention)
218+
219+ def test_num_version_differences_needing_attention_limits_status(self):
220+ # num_version_differences_needing_attention ignores differences
221+ # that do not need attention.
222+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
223+ view = self._createDifferenceAndGetView(
224+ diff_type, status=DistroSeriesDifferenceStatus.RESOLVED)
225+ self.assertEqual(0, view.num_version_differences_needing_attention)
226+
227+ def test_num_version_differences_counts_all_statuses(self):
228+ # num_version_differences counts DIFFERENT_VERSIONS differences
229+ # of all statuses.
230+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
231+ series = self.factory.makeDistroSeriesParent().derived_series
232+ dsds = [
233+ self.factory.makeDistroSeriesDifference(
234+ series, difference_type=diff_type, status=status)
235+ for status in DistroSeriesDifferenceStatus.items]
236+ view = create_initialized_view(series, '+index')
237+ self.assertEqual(len(dsds), view.num_version_differences)
238+
239+ def test_num_version_differences_ignores_limits_type(self):
240+ # num_version_differences ignores other types of difference.
241+ diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
242+ view = self._createDifferenceAndGetView(diff_type)
243+ self.assertEqual(0, view.num_version_differences)
244
245 def test_num_differences_in_parent(self):
246 diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
247@@ -164,6 +201,56 @@
248 view = self._createDifferenceAndGetView(diff_type)
249 self.assertEqual(1, view.num_differences_in_child)
250
251+ def test_wordVersionDifferences(self):
252+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
253+ view = self._createDifferenceAndGetView(diff_type)
254+ self.assertEqual("1 package", view.wordVersionDifferences())
255+
256+ def test_wordDifferencesInParent(self):
257+ diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
258+ view = self._createDifferenceAndGetView(diff_type)
259+ self.assertEqual("1 package", view.wordDifferencesInParent())
260+
261+ def test_wordDifferencesInChild(self):
262+ diff_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
263+ view = self._createDifferenceAndGetView(diff_type)
264+ self.assertEqual("1 package", view.wordDifferencesInChild())
265+
266+ def test_alludeToParent_names_single_parent(self):
267+ dsp = self.factory.makeDistroSeriesParent()
268+ view = create_initialized_view(dsp.derived_series, '+index')
269+ self.assertEqual(dsp.parent_series.displayname, view.alludeToParent())
270+
271+ def test_alludeToParent_refers_to_multiple_parents_collectively(self):
272+ dsp = self.factory.makeDistroSeriesParent()
273+ self.factory.makeDistroSeriesParent(derived_series=dsp.derived_series)
274+ view = create_initialized_view(dsp.derived_series, '+index')
275+ self.assertEqual("a parent series", view.alludeToParent())
276+
277+ def test_link_to_version_diffs_needing_attention(self):
278+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
279+ view = self._createDifferenceAndGetView(diff_type)
280+ link = view.link_to_version_diffs_needing_attention
281+ self.assertThat(link, EndsWith('/+localpackagediffs'))
282+
283+ def test_link_to_all_version_diffs(self):
284+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
285+ view = self._createDifferenceAndGetView(diff_type)
286+ link = view.link_to_all_version_diffs
287+ self.assertIn('/+localpackagediffs?', link)
288+
289+ def test_link_to_differences_in_parent(self):
290+ diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
291+ view = self._createDifferenceAndGetView(diff_type)
292+ link = view.link_to_differences_in_parent
293+ self.assertThat(link, EndsWith('/+missingpackages'))
294+
295+ def test_link_to_differences_in_child(self):
296+ diff_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
297+ view = self._createDifferenceAndGetView(diff_type)
298+ link = view.link_to_differences_in_child
299+ self.assertThat(link, EndsWith('/+uniquepackages'))
300+
301
302 class DistroSeriesIndexFunctionalTestCase(TestCaseWithFactory):
303 """Test the distroseries +index page."""
304@@ -236,19 +323,7 @@
305 portlet_display = soupmatchers.HTMLContains(
306 soupmatchers.Tag(
307 'Derivation portlet header', 'h2',
308- text='Derived from Sid'),
309- soupmatchers.Tag(
310- 'Differences link', 'a',
311- text=re.compile('\s*1 package with differences\s*'),
312- attrs={'href': re.compile('.*/\+localpackagediffs')}),
313- soupmatchers.Tag(
314- 'Parent diffs link', 'a',
315- text=re.compile('\s*2 packages only in Sid\s*'),
316- attrs={'href': re.compile('.*/\+missingpackages')}),
317- soupmatchers.Tag(
318- 'Child diffs link', 'a',
319- text=re.compile('\s*3 packages only in Deri\s*'),
320- attrs={'href': re.compile('.*/\+uniquepackages')}))
321+ text='Derived from Sid'))
322
323 with person_logged_in(self.simple_user):
324 view = create_initialized_view(
325@@ -268,14 +343,9 @@
326 set_derived_series_ui_feature_flag(self)
327 derived_series = self._setupDifferences(
328 'deri', ['sid1', 'sid2'], 0, 1, 0)
329- portlet_display = soupmatchers.HTMLContains(
330- soupmatchers.Tag(
331- 'Derivation portlet header', 'h2',
332- text='Derived from 2 parents'),
333- soupmatchers.Tag(
334- 'Parent diffs link', 'a',
335- text=re.compile('\s*1 package only in a parent series\s*'),
336- attrs={'href': re.compile('.*/\+missingpackages')}))
337+ portlet_display = soupmatchers.HTMLContains(soupmatchers.Tag(
338+ 'Derivation portlet header', 'h2',
339+ text='Derived from 2 parents'))
340
341 with person_logged_in(self.simple_user):
342 view = create_initialized_view(
343
344=== modified file 'lib/lp/registry/templates/distroseries-portlet-derivation.pt'
345--- lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-07-06 16:19:18 +0000
346+++ lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-08-18 12:34:26 +0000
347@@ -13,44 +13,46 @@
348 <h2>Derived from <tal:name replace="view/number_of_parents"/> parents</h2>
349 </tal:multiple_parents>
350
351- <tal:diffs define="nb_diffs view/num_differences;
352+ <tal:diffs define="nb_diffs view/num_version_differences;
353 nb_diffs_in_parent view/num_differences_in_parent;
354 nb_diffs_in_child view/num_differences_in_child;">
355 <ul id="derivation_stats">
356- <li tal:condition="nb_diffs">
357- <a tal:attributes="href string:${context/fmt:url}/+localpackagediffs"
358- class="sprite info"
359- title="Source package differences between this series and his parent(s)">
360- <tal:nb_diffs replace="nb_diffs"/> package<tal:plural
361- content="string:s"
362- condition="python:nb_diffs!=1"/> with differences
363- </a>
364- </li>
365- <li tal:condition="nb_diffs_in_parent">
366- <a tal:attributes="href string:${context/fmt:url}/+missingpackages"
367- class="sprite info"
368- title="Source packages only in parent series">
369- <tal:one_parent condition="view/has_unique_parent">
370- <tal:nb_diffs replace="nb_diffs_in_parent"/> package<tal:plural
371- content="string:s"
372- condition="python:nb_diffs_in_parent!=1"/> only in <tal:replace
373- replace="view/unique_parent/displayname">Sid</tal:replace>
374- </tal:one_parent>
375- <tal:multiple_parents condition="not: view/has_unique_parent">
376- <tal:nb_diffs replace="nb_diffs_in_parent"/> package<tal:plural
377- content="string:s"
378- condition="python:nb_diffs_in_parent!=1"/> only in a parent series
379- </tal:multiple_parents>
380- </a>
381- </li>
382- <li tal:condition="nb_diffs_in_child">
383- <a tal:attributes="href string:${context/fmt:url}/+uniquepackages"
384- class="sprite info"
385- title="Source packages only in derived series">
386- <tal:nb_diffs replace="nb_diffs_in_child"/> package<tal:plural
387- content="string:s"
388- condition="python:nb_diffs_in_child!=1"/> only in <tal:replace
389- replace="context/displayname">Natty</tal:replace>
390+ <li class="sprite info" tal:condition="nb_diffs">
391+ <tal:differences_count replace="view/wordVersionDifferences">
392+ 123 packages
393+ </tal:differences_count>
394+ <a tal:attributes="href view/link_to_all_version_diffs">
395+ with differences
396+ </a>
397+ <tal:needing_attention
398+ condition="view/num_version_differences_needing_attention">
399+ (<tal:differences_count
400+ replace="view/num_version_differences_needing_attention">9
401+ </tal:differences_count>
402+ <a tal:attributes="href view/link_to_version_diffs_needing_attention">
403+ needing attention</a>)
404+ </tal:needing_attention>
405+ </li>
406+ <li class="sprite info" tal:condition="nb_diffs_in_parent">
407+ <tal:differences_count replace="view/wordDifferencesInParent">
408+ 234 packages
409+ </tal:differences_count>
410+ <a tal:attributes="href view/link_to_differences_in_parent">
411+ only in
412+ <tal:parent replace="view/alludeToParent">
413+ a parent series
414+ </tal:parent>
415+ </a>
416+ </li>
417+ <li class="sprite info" tal:condition="nb_diffs_in_child">
418+ <a tal:attributes="href view/link_to_differences_in_child">
419+ <tal:differences_count replace="view/wordDifferencesInChild">
420+ 345 packages
421+ </tal:differences_count>
422+ only in
423+ <tal:child replace="context/displayname">
424+ Natty
425+ </tal:child>
426 </a>
427 </li>
428 </ul>