Merge lp:~stevenk/launchpad/destroy-distribution-upstreamreport into lp:launchpad

Proposed by Steve Kowalik on 2012-11-02
Status: Merged
Approved by: Curtis Hovey on 2012-11-02
Approved revision: no longer in the source branch.
Merged at revision: 16230
Proposed branch: lp:~stevenk/launchpad/destroy-distribution-upstreamreport
Merge into: lp:launchpad
Diff against target: 1946 lines (+2/-1845)
8 files modified
lib/lp/bugs/browser/configure.zcml (+0/-6)
lib/lp/bugs/browser/distribution_upstream_report.py (+0/-391)
lib/lp/bugs/browser/tests/test_distribution_upstream_report.py (+0/-218)
lib/lp/bugs/doc/distribution-upstream-report.txt (+0/-432)
lib/lp/bugs/stories/distribution/xx-distribution-upstream-report.txt (+0/-303)
lib/lp/bugs/templates/distribution-upstream-report.pt (+0/-298)
lib/lp/registry/interfaces/distribution.py (+0/-23)
lib/lp/registry/model/distribution.py (+2/-174)
To merge this branch: bzr merge lp:~stevenk/launchpad/destroy-distribution-upstreamreport
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code 2012-11-02 Approve on 2012-11-02
Review via email: mp+132639@code.launchpad.net

Commit Message

Destroy Distribution:+upstreamreport, its tests and the horse it rode in on.

Description of the Change

Distribution:+upstreamreport is masking a hideous query, has been hit ~ 100 times last month, and its 99% time is 9 seconds. Let's kill it.

To post a comment you must log in.
Curtis Hovey (sinzui) wrote :

Thank you. This page is fatally flawed. I uses data from 2008. It encourages contributors to be working on ekiga instead of zeitgiest. The numbers can be accurately provided by an API script and the community can write and run such a script for packages that are actually seeded in Raring.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/configure.zcml'
2--- lib/lp/bugs/browser/configure.zcml 2012-10-11 04:57:59 +0000
3+++ lib/lp/bugs/browser/configure.zcml 2012-11-02 01:01:22 +0000
4@@ -345,12 +345,6 @@
5 template="../templates/product-cvereport.pt"/>
6 <browser:page
7 for="lp.registry.interfaces.distribution.IDistribution"
8- name="+upstreamreport"
9- class="lp.bugs.browser.distribution_upstream_report.DistributionUpstreamReport"
10- permission="zope.Public"
11- template="../templates/distribution-upstream-report.pt"/>
12- <browser:page
13- for="lp.registry.interfaces.distribution.IDistribution"
14 class="lp.bugs.browser.cvereport.CVEReportView"
15 permission="zope.Public"
16 name="+cve"
17
18=== removed file 'lib/lp/bugs/browser/distribution_upstream_report.py'
19--- lib/lp/bugs/browser/distribution_upstream_report.py 2012-05-25 19:58:27 +0000
20+++ lib/lp/bugs/browser/distribution_upstream_report.py 1970-01-01 00:00:00 +0000
21@@ -1,391 +0,0 @@
22-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
23-# GNU Affero General Public License version 3 (see the file LICENSE).
24-
25-"""Browser views for distributions."""
26-
27-__metaclass__ = type
28-
29-__all__ = [
30- 'DistributionUpstreamReport',
31- ]
32-
33-from operator import attrgetter
34-
35-from lp.app.enums import ServiceUsage
36-from lp.bugs.browser.bugtask import get_buglisting_search_filter_url
37-from lp.services.propertycache import cachedproperty
38-from lp.services.webapp.publisher import (
39- canonical_url,
40- LaunchpadView,
41- )
42-from lp.services.webapp.url import urlappend
43-
44-# TODO: fix column sorting to work for the different colspans, or
45-# alternatively implement a sort option box.
46-# TODO: make the totals column also link to bug listings.
47-# TODO A fourth potential count could be a count of open distribution
48-# bugs that are fixed upstream, which would imply closing the loop
49-# of upstream fixes back to the distribution.
50-
51-
52-class BugReportData:
53- """Represents a row of bug count data in the report.
54-
55- This is a base class and is directly used only for the totals row.
56- See the help text in the template for a verbose description of what
57- the numbers mean. Briefly, we hold three counts:
58-
59- 1. open: bugs with distribution tasks with a status of one of
60- interfaces.bugtask.UNRESOLVED_BUGTASK_STATUSES.
61- 2. triaged: bugs with distribution tasks that are TRIAGED
62- 3. upstream: bugs that are triaged and have an upstream task against
63- them.
64- 4. watched: bugs that are triaged and have an upstream task linked
65- to a watch.
66-
67- This class makes the three latter counts available as percentages and
68- deltas to their predecessor. The report gives the impression of a
69- pipeline where bugs trickle into the next count.
70-
71- The *_class() methods return "good" or nothing, and are intended for
72- use in a CSS class. They calculate their values based on the
73- UPSTREAM_THRESHOLD and WATCH_THRESHOLD class variables. The reason
74- we calculate them is that until we have a way of tracking whether a
75- bug is actually /not/ upstream we can't assume 100% of distribution
76- bugs need upstream tasks.
77- """
78- TRIAGED_THRESHOLD = 75
79- UPSTREAM_THRESHOLD = 90
80- WATCH_THRESHOLD = 90
81-
82- BAD_THRESHOLD = 20
83-
84- def __init__(self, open_bugs=0, triaged_bugs=0, upstream_bugs=0,
85- watched_bugs=0, bugs_with_upstream_patches=0):
86- self.open_bugs = open_bugs
87- self.triaged_bugs = triaged_bugs
88- self.upstream_bugs = upstream_bugs
89- self.watched_bugs = watched_bugs
90- self.bugs_with_upstream_patches = bugs_with_upstream_patches
91-
92- @staticmethod
93- def _as_percentage(number, total):
94- if total:
95- return round(100.0 * number / total, 2)
96- else:
97- return 0.0
98-
99- @property
100- def triaged_bugs_percentage(self):
101- return self._as_percentage(self.triaged_bugs, self.open_bugs)
102-
103- @property
104- def upstream_bugs_percentage(self):
105- return self._as_percentage(self.upstream_bugs, self.open_bugs)
106-
107- @property
108- def watched_bugs_percentage(self):
109- return self._as_percentage(self.watched_bugs, self.upstream_bugs)
110-
111- @property
112- def row_class(self):
113- """Return the class to be used for the current table row.
114-
115- :returns: 'good' if watched_bugs_percentage > WATCH_THRESHOLD;
116- 'bad' if watched_bugs_percentage < BAD_THRESHOLD;
117- '' otherwise.
118- """
119- if self.watched_bugs_percentage > self.WATCH_THRESHOLD:
120- return "good"
121- elif self.watched_bugs_percentage < self.BAD_THRESHOLD:
122- return "bad"
123- else:
124- return ''
125-
126- @staticmethod
127- def _as_value_class(percentage, threshold):
128- if percentage > threshold:
129- return "good"
130- return ""
131-
132- @property
133- def triaged_bugs_class(self):
134- return self._as_value_class(
135- self.triaged_bugs_percentage, self.TRIAGED_THRESHOLD)
136-
137- @property
138- def upstream_bugs_class(self):
139- return self._as_value_class(
140- self.upstream_bugs_percentage, self.UPSTREAM_THRESHOLD)
141-
142- @property
143- def watched_bugs_class(self):
144- return self._as_value_class(
145- self.watched_bugs_percentage, self.WATCH_THRESHOLD)
146-
147- @property
148- def triaged_bugs_delta(self):
149- return self.open_bugs - self.triaged_bugs
150-
151- @property
152- def upstream_bugs_delta(self):
153- return self.open_bugs - self.upstream_bugs
154-
155- @property
156- def watched_bugs_delta(self):
157- return self.upstream_bugs - self.watched_bugs
158-
159-
160-class PackageBugReportData(BugReportData):
161- """Represents a package row in the report.
162-
163- Apart from the counts, includes data to make it easy to link to
164- pages which allow inputting missing information related to the
165- package. Relevant instance variables:
166-
167- - dsp: an IDistributionSourcePackage
168- - dssp: an IDistributionSeriesSourcepackage
169- - product: an IProduct
170- - bugtracker: convenience holder for the product's bugtracker
171- - bug_tracking_usage: convenience enum for
172- IProduct.bug_tracking_usage
173- - *_url: convenience URLs
174- """
175-
176- def __init__(self, dsp, dssp, product, open_bugs, triaged_bugs,
177- upstream_bugs, watched_bugs, bugs_with_upstream_patches):
178- BugReportData.__init__(self, open_bugs, triaged_bugs, upstream_bugs,
179- watched_bugs, bugs_with_upstream_patches)
180- self.dsp = dsp
181- self.dssp = dssp
182- self.product = product
183-
184- dsp_bugs_url = canonical_url(dsp, rootsite='bugs')
185-
186- self.open_bugs_url = urlappend(
187- dsp_bugs_url, get_buglisting_search_filter_url())
188-
189- if product is not None:
190- self.bug_tracking_usage = product.bug_tracking_usage
191- self.branch = product.development_focus.branch
192- else:
193- self.bug_tracking_usage = ServiceUsage.UNKNOWN
194- self.branch = None
195-
196- # If a product is specified, build some convenient links to
197- # pages which allow filling out required information. The
198- # template ensures they are only visible to people who can
199- # actually change the product.
200- if self.product:
201- product_url = canonical_url(product)
202- self.bugtracker = self.product.getExternalBugTracker()
203-
204- self.product_edit_url = product_url + "/+edit"
205- self.bug_supervisor_url = product_url + "/+bugsupervisor"
206-
207- # Create a 'bugtracker_name' attribute for searching.
208- if self.bugtracker is not None:
209- self.bugtracker_name = self.bugtracker.title
210- elif self.product.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
211- self.bugtracker_name = 'Launchpad'
212- else:
213- self.bugtracker_name = None
214-
215- if self.product.bug_supervisor is not None:
216- self.bug_supervisor_name = (
217- self.product.bug_supervisor.displayname)
218- else:
219- self.bug_supervisor_name = None
220- else:
221- # Set bug_supervisor and bugtracker to None so that the
222- # sorting code doesn't choke.
223- self.bug_supervisor_name = None
224- self.bugtracker_name = None
225-
226- # Note that the +edit-packaging page allows launchpad.AnyPerson
227- # so no permissions check needs to be done in the template.
228- self.packaging_url = canonical_url(self.dssp) + "/+edit-packaging"
229- self.triaged_bugs_url = urlappend(
230- dsp_bugs_url, get_buglisting_search_filter_url(status='TRIAGED'))
231-
232- # The triaged delta URL links to all bugs that are open but not
233- # triaged for the current DistributionSourcePackage.
234- untriaged_bug_statuses = [
235- 'CONFIRMED',
236- 'INCOMPLETE_WITHOUT_RESPONSE',
237- 'INCOMPLETE_WITH_RESPONSE',
238- 'NEW',
239- ]
240- untriaged_search_filter_url = get_buglisting_search_filter_url(
241- status=untriaged_bug_statuses)
242- self.triaged_bugs_delta_url = urlappend(
243- dsp_bugs_url, untriaged_search_filter_url)
244-
245- # The upstream URL links to all bugs that are open and have an
246- # open upstream bug task or bug watch.
247- upstream_search_filter_url = get_buglisting_search_filter_url(
248- status_upstream='open_upstream')
249- self.upstream_bugs_url = urlappend(
250- dsp_bugs_url, upstream_search_filter_url)
251-
252- # The upstream delta URL links to all bugs that are open without
253- # an upstream bug task or bug watch.
254- non_upstream_search_filter_url = get_buglisting_search_filter_url(
255- status_upstream='hide_upstream')
256- self.upstream_bugs_delta_url = urlappend(
257- dsp_bugs_url, non_upstream_search_filter_url)
258-
259- # The watch delta URL links to all open upstream bugs that don't
260- # have a bugwatch.
261- unwatched_bugs_search_filter_url = get_buglisting_search_filter_url(
262- status_upstream='pending_bugwatch')
263- self.watched_bugs_delta_url = urlappend(
264- dsp_bugs_url, unwatched_bugs_search_filter_url)
265-
266- # The bugs with upstream patches URL links to all open upstream
267- # bugs that don't have a bugwatch but have patches attached.
268- bugs_with_upstream_patches_filter_url = (
269- get_buglisting_search_filter_url(
270- status_upstream='pending_bugwatch', has_patches=True))
271- self.bugs_with_upstream_patches_url = urlappend(
272- dsp_bugs_url, bugs_with_upstream_patches_filter_url)
273-
274-
275-class DistributionUpstreamReport(LaunchpadView):
276- """Implements the actual upstream report.
277-
278- Most of the work is actually done in the
279- getPackagesAndPublicUpstreamBugCounts API, and in the *Data classes
280- constructed from here.
281- """
282- LIMIT = 100
283-
284- valid_sort_keys = [
285- 'bugtracker_name',
286- 'bug_supervisor_name',
287- 'bugs_with_upstream_patches',
288- 'dsp',
289- 'open_bugs',
290- 'product',
291- 'triaged_bugs',
292- 'triaged_bugs_class',
293- 'triaged_bugs_delta',
294- 'triaged_bugs_percentage',
295- 'upstream_bugs',
296- 'upstream_bugs_class',
297- 'upstream_bugs_delta',
298- 'upstream_bugs_percentage',
299- 'watched_bugs',
300- 'watched_bugs_class',
301- 'watched_bugs_delta',
302- 'watched_bugs_percentage',
303- ]
304-
305- arrow_up = "/@@/arrowUp"
306- arrow_down = "/@@/arrowDown"
307- arrow_blank = "/@@/arrowBlank"
308-
309- @property
310- def page_title(self):
311- return 'Upstream Report for %s' % self.context.title
312-
313- @property
314- def sort_order(self):
315- """Return the sort order for the report.
316-
317- :returns: The sort order as a dict of (sort_key, reversed).
318- """
319- sort_order = self.request.get('sort_by', '-open_bugs')
320-
321- sort_key = sort_order
322- if sort_key.startswith('-'):
323- sort_key = sort_key.replace('-', '')
324- reversed = True
325- else:
326- reversed = False
327-
328- # Validate the sort key before proceeding.
329- if sort_key not in self.valid_sort_keys:
330- return ('open_bugs', True)
331- else:
332- return (sort_key, reversed)
333-
334- @cachedproperty
335- def data(self):
336- """Return the _data list, sorted by `sort_order`."""
337- sort_key, reversed = self.sort_order
338- data = sorted(
339- self._data, key=attrgetter(sort_key), reverse=reversed)
340-
341- return data
342-
343- @cachedproperty
344- def sort_order_links(self):
345- """Return a dict of sort order links based on the current sort_order.
346- """
347- current_sort_key, reversed = self.sort_order
348- sort_order_links = {}
349-
350- # Loop over the possible sort keys and work out what the link
351- # should be for that column.
352- base_url = canonical_url(self.context, view_name='+upstreamreport')
353- for sort_key in self.valid_sort_keys:
354- if sort_key == current_sort_key:
355- if not reversed:
356- sort_order = '-%s' % sort_key
357- arrow = self.arrow_up
358- else:
359- sort_order = sort_key
360- arrow = self.arrow_down
361- else:
362- sort_order = sort_key
363- arrow = self.arrow_blank
364-
365- sort_order_links[sort_key] = {
366- 'link': base_url + '?sort_by=%s' % sort_order,
367- 'arrow': arrow,
368- }
369-
370- return sort_order_links
371-
372- @cachedproperty
373- def current_distro_series(self):
374- """Cache the current distroseries.
375-
376- This avoids us having to reissue this query for each row we want
377- to produce an IDistroSeriesSourcePackage for.
378- """
379- return self.context.currentseries
380-
381- def initialize(self):
382- """Assemble self._data and self.total from upstream count report."""
383- self._data = []
384- self.total = BugReportData()
385- packages_to_exclude = self.context.upstream_report_excluded_packages
386- counts = self.context.getPackagesAndPublicUpstreamBugCounts(
387- limit=self.LIMIT, exclude_packages=packages_to_exclude)
388- # The upstream report is not useful if the distibution
389- # does not track its bugs on Lauchpad or if it does not have a
390- # current distroseries.
391- self.has_upstream_report = (
392- self.context.bug_tracking_usage == ServiceUsage.LAUNCHPAD and
393- self.current_distro_series is not None)
394- if not self.has_upstream_report:
395- return
396- for (dsp, product, open, triaged, upstream, watched,
397- bugs_with_upstream_patches) in counts:
398- # The +edit-packaging page is only available for
399- # IDistributionSeriesSourcepackages, so deduce one here. If
400- # the distribution doesn't have series we can't offer a link
401- # to add packaging information.
402- dssp = self.current_distro_series.getSourcePackage(
403- dsp.sourcepackagename)
404- self.total.open_bugs += open
405- self.total.triaged_bugs += triaged
406- self.total.upstream_bugs += upstream
407- self.total.watched_bugs += watched
408-
409- item = PackageBugReportData(
410- dsp, dssp, product, open, triaged, upstream, watched,
411- bugs_with_upstream_patches)
412- self._data.append(item)
413
414=== removed file 'lib/lp/bugs/browser/tests/test_distribution_upstream_report.py'
415--- lib/lp/bugs/browser/tests/test_distribution_upstream_report.py 2012-06-14 05:18:22 +0000
416+++ lib/lp/bugs/browser/tests/test_distribution_upstream_report.py 1970-01-01 00:00:00 +0000
417@@ -1,218 +0,0 @@
418-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
419-# GNU Affero General Public License version 3 (see the file LICENSE).
420-
421-"""Unit tests for DistributionUpstreamReport."""
422-
423-__metaclass__ = type
424-
425-
426-from soupmatchers import (
427- HTMLContains,
428- Tag,
429- )
430-from testtools.matchers import Not
431-from zope.component import getUtility
432-
433-from lp.app.enums import ServiceUsage
434-from lp.app.interfaces.launchpad import ILaunchpadCelebrities
435-from lp.bugs.browser.distribution_upstream_report import (
436- BugReportData,
437- DistributionUpstreamReport,
438- )
439-from lp.testing import (
440- BrowserTestCase,
441- person_logged_in,
442- TestCase,
443- TestCaseWithFactory,
444- )
445-from lp.testing.layers import (
446- DatabaseFunctionalLayer,
447- LaunchpadFunctionalLayer,
448- )
449-from lp.testing.views import (
450- create_initialized_view,
451- create_view,
452- )
453-
454-
455-class BugReportDataTestCase(TestCase):
456-
457- def make_bug_report_data(self):
458- return BugReportData(
459- open_bugs=90, triaged_bugs=50, upstream_bugs=70, watched_bugs=60)
460-
461- def test_init(self):
462- bug_data = self.make_bug_report_data()
463- self.assertEqual(90, bug_data.open_bugs)
464- self.assertEqual(50, bug_data.triaged_bugs)
465- self.assertEqual(70, bug_data.upstream_bugs)
466- self.assertEqual(60, bug_data.watched_bugs)
467-
468- def test_percentage_properties(self):
469- bug_data = self.make_bug_report_data()
470- self.assertEqual(55.56, bug_data.triaged_bugs_percentage)
471- self.assertEqual(77.78, bug_data.upstream_bugs_percentage)
472- self.assertEqual(85.71, bug_data.watched_bugs_percentage)
473-
474- def test_as_percentage(self):
475- bug_data = self.make_bug_report_data()
476- self.assertEqual(55.56, bug_data._as_percentage(50, 90))
477- self.assertEqual(0.0, bug_data._as_percentage(50, 0))
478-
479- def test_delta_properties(self):
480- bug_data = self.make_bug_report_data()
481- self.assertEqual(40, bug_data.triaged_bugs_delta)
482- self.assertEqual(20, bug_data.upstream_bugs_delta)
483- self.assertEqual(10, bug_data.watched_bugs_delta)
484-
485- def test_as_value_class(self):
486- bug_data = self.make_bug_report_data()
487- self.assertEqual('good', bug_data._as_value_class(60, 50))
488- self.assertEqual('', bug_data._as_value_class(50, 50))
489- self.assertEqual('', bug_data._as_value_class(40, 50))
490-
491- def test_value_class(self):
492- bug_data = self.make_bug_report_data()
493- bug_data.watched_bugs = 80
494- self.assertEqual('', bug_data.triaged_bugs_class)
495- self.assertEqual('', bug_data.upstream_bugs_class)
496- self.assertEqual('good', bug_data.watched_bugs_class)
497-
498- def test_row_class(self):
499- bug_data = self.make_bug_report_data()
500- self.assertEqual('', bug_data.row_class)
501- bug_data.watched_bugs = 80
502- self.assertEqual('good', bug_data.row_class)
503- bug_data.watched_bugs = 11
504- self.assertEqual('bad', bug_data.row_class)
505-
506-
507-class TestDistributionUpstreamReport(TestCaseWithFactory):
508-
509- layer = DatabaseFunctionalLayer
510-
511- def test_valid_sort_keys_are_valid(self):
512- # The valid_sort_keys property of the
513- # DistributionUpstreamReport view contains a list of the sort
514- # keys that the view considers valid. Using any one of these
515- # keys, including when prepended with a '-', will lead to it
516- # being set as the view's sort_order key.
517- ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
518- for sort_key in DistributionUpstreamReport.valid_sort_keys:
519- form = {'sort_by': sort_key}
520- view = create_view(ubuntu, '+upstreamreport', form)
521-
522- # The sort_order property of DistributionUpstreamReport is
523- # a tuple in the form (sort_key, reversed).
524- view_sort_key, view_sort_reversed = view.sort_order
525- self.assertEqual(view_sort_key, sort_key,
526- "Expected a sort_key of '%s', got '%s'" %
527- (sort_key, view_sort_key))
528-
529- # By default, reversed is False.
530- self.assertFalse(view_sort_reversed,
531- "Sort order should not be reversed for a sort_by value of "
532- "%s" % sort_key)
533-
534- # Prepending a '-' to sort_by will reverse the sort.
535- reversed_key = '-%s' % sort_key
536- form = {'sort_by': reversed_key}
537- view = create_view(ubuntu, '+upstreamreport', form)
538-
539- # The sort_key part of view.sort_order will be the same as
540- # for a normal sort.
541- view_sort_key, view_sort_reversed = view.sort_order
542- self.assertEqual(view_sort_key, sort_key,
543- "Expected a sort_key of '%s', got '%s'" %
544- (sort_key, view_sort_key))
545-
546- # But reversed is now True.
547- self.assertTrue(view_sort_reversed,
548- "Sort order should be reversed for a sort_by value of "
549- "%s" % reversed_key)
550-
551- def test_has_upstream_report__no_series_no_bug_tracking(self):
552- # The property DistributionUpstreamReport.has_upstream_report
553- # is False if a distribution does not use Launchpad for bug
554- # tracking and if no current distroseries exists.
555- distribution = self.factory.makeDistribution()
556- view = create_initialized_view(distribution, '+upstreamreport')
557- self.assertNotEqual(
558- ServiceUsage.LAUNCHPAD, distribution.bug_tracking_usage)
559- self.assertIs(None, distribution.currentseries)
560- self.assertFalse(view.has_upstream_report)
561-
562- def test_has_upstream_report__no_distroseries_with_bug_tracking(self):
563- # The property DistributionUpstreamReport.has_upstream_report
564- # is False if a distribution does not have a current
565- # distroseries, even if Luanchpad is used for bug tracking.
566- distribution = self.factory.makeDistribution()
567- view = create_initialized_view(distribution, '+upstreamreport')
568- with person_logged_in(distribution.owner):
569- distribution.official_malone = True
570- self.assertIs(None, distribution.currentseries)
571- self.assertFalse(view.has_upstream_report)
572-
573- def test_has_upstream_report__with_distroseries_no_bug_tracking(self):
574- # The property DistributionUpstreamReport.has_upstream_report
575- # is False if a distribution has a current distroseries, but
576- # if Launchpad is not used for bug tracking.
577- distribution = self.factory.makeDistroSeries().distribution
578- view = create_initialized_view(distribution, '+upstreamreport')
579- self.assertIsNot(None, distribution.currentseries)
580- self.assertNotEqual(
581- ServiceUsage.LAUNCHPAD, distribution.bug_tracking_usage)
582- self.assertFalse(view.has_upstream_report)
583-
584- def test_has_upstream_report__with_distroseries_and_bug_tracking(self):
585- # The property DistributionUpstreamReport.has_upstream_report
586- # is True if a distribution has a current distroseries and if it
587- # uses Launchpad for bug tracking.
588- distribution = self.factory.makeDistroSeries().distribution
589- with person_logged_in(distribution.owner):
590- distribution.official_malone = True
591- view = create_initialized_view(distribution, '+upstreamreport')
592- self.assertIsNot(None, distribution.currentseries)
593- self.assertEqual(
594- ServiceUsage.LAUNCHPAD, distribution.bug_tracking_usage)
595- self.assertTrue(view.has_upstream_report)
596-
597-
598-class TestDistributionUpstreamReportPage(BrowserTestCase):
599- """Tests for the +upstream report page."""
600-
601- layer = LaunchpadFunctionalLayer
602-
603- def getTagMatchers(self):
604- """Return matchers for the tag saying "launchpad is not used
605- for development" and the tag containing the upstream report."""
606- no_lp_usage = Tag(
607- 'no-lp-usage', 'div', attrs={'id': 'no-lp-usage'})
608- no_bugs_filed = Tag('lp-used', 'div', attrs={'id': 'lp-used'})
609- return no_lp_usage, no_bugs_filed
610-
611- def test_no_upstream_report_for_unconfigured_distros(self):
612- # If DistributionUpstreamReport.has_upstream_report is False,
613- # the +upstream-report page does not show the report.
614- distribution = self.factory.makeDistribution()
615- browser = self.getViewBrowser(
616- distribution, '+upstreamreport', no_login=True)
617- no_lp_usage, no_bugs_filed = self.getTagMatchers()
618- self.assertThat(browser.contents, Not(HTMLContains(no_bugs_filed)))
619- # Instead, a message tells the user that no report is
620- # available.
621- self.assertThat(browser.contents, HTMLContains(no_lp_usage))
622-
623- def test_upstream_report_for_configured_distros(self):
624- # If DistributionUpstreamReport.has_upstream_report is True,
625- # the +upstream-report page does shows the report.
626- distribution = self.factory.makeDistroSeries().distribution
627- with person_logged_in(distribution.owner):
628- distribution.official_malone = True
629- browser = self.getViewBrowser(
630- distribution, '+upstreamreport', no_login=True)
631- no_lp_usage, no_bugs_filed = self.getTagMatchers()
632- self.assertThat(browser.contents, HTMLContains(no_bugs_filed))
633- # A message telling the user that no report is available
634- # is not shown.
635- self.assertThat(browser.contents, Not(HTMLContains(no_lp_usage)))
636
637=== removed file 'lib/lp/bugs/doc/distribution-upstream-report.txt'
638--- lib/lp/bugs/doc/distribution-upstream-report.txt 2012-05-25 20:15:08 +0000
639+++ lib/lp/bugs/doc/distribution-upstream-report.txt 1970-01-01 00:00:00 +0000
640@@ -1,432 +0,0 @@
641-Upstream reports
642-================
643-
644-For a distribution's bug tracking process to be successful, it's vital
645-that it is able to communicate upstream bugs to the relevant upstream
646-project and monitor them as they change. Launchpad offers functionality
647-to allow a distribution to focus on and improve this process.
648-
649- >>> from storm.store import Store
650- >>> from lp.testing import login
651- >>> from lp.bugs.tests.bug import (
652- ... create_bug_from_strings)
653- >>> from lp.registry.interfaces.sourcepackagename import (
654- ... ISourcePackageNameSet)
655- >>> from lp.registry.interfaces.distribution import IDistributionSet
656- >>> from lp.registry.interfaces.product import IProductSet
657- >>> from lp.registry.interfaces.packaging import (
658- ... IPackagingUtil, PackagingType)
659- >>> from lp.registry.interfaces.person import IPersonSet
660- >>> from lp.bugs.interfaces.bugtask import IBugTaskSet, BugTaskStatus
661- >>> from lp.bugs.interfaces.bugwatch import IBugWatchSet
662-
663- >>> distroset = getUtility(IDistributionSet)
664- >>> ubuntu = distroset.getByName('ubuntu')
665- >>> debian = distroset.getByName('debian')
666- >>> kubuntu = distroset.getByName('kubuntu')
667-
668-
669-The API
670--------
671-
672-IDistribution has a special API that allows you to assemble data for a
673-bug report that associates packages with upstream information linked to
674-them.
675-
676- >>> def print_report(data):
677- ... for dsp, product, open, triaged, upstream, watch, patch in data:
678- ... print dsp.name, product and product.name or None
679- ... print open, triaged, upstream, watch, patch
680-
681-A first set of reports, entirely based on sampledata. There are no
682-triaged bugs, but there are some upstream ones with watches:
683-
684- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts())
685- linux-source-2.6.15 None 1 0 0 0 0
686- mozilla-firefox firefox 1 0 1 1 0
687- thunderbird None 1 0 1 1 0
688-
689- >>> print_report(debian.getPackagesAndPublicUpstreamBugCounts())
690- mozilla-firefox None 3 0 2 1 0
691-
692- >>> print_report(kubuntu.getPackagesAndPublicUpstreamBugCounts())
693-
694-getPackagesAndPublicUpstreamBugCounts() accepts an `exclude_packages`
695-parameter. This is a list of the source packages that shouldn't be
696-included in the report that getPackagesAndPublicUpstreamBugCounts()
697-returns.
698-
699- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(
700- ... exclude_packages=['linux-source-2.6.15']))
701- mozilla-firefox firefox 1 0 1 1 0
702- thunderbird None 1 0 1 1 0
703-
704-To get the list of excluded packages for a distribution we can look at
705-its `upstream_report_excluded_packages` property. For Kubuntu and
706-Debian, this returns an empty list.
707-
708- >>> debian.upstream_report_excluded_packages
709- []
710-
711- >>> kubuntu.upstream_report_excluded_packages
712- []
713-
714-For Ubuntu, however, there is a list of excluded packages.
715-
716- >>> ubuntu.upstream_report_excluded_packages
717- ['apport'...]
718-
719-If we triage a bugtask on firefox and thunderbird we'll see the count
720-for triaged bugs updated:
721-
722- >>> login('foo.bar@canonical.com')
723- >>> mark = getUtility(IPersonSet).getByName('mark')
724- >>> ls_bug = getUtility(IBugTaskSet).get(23)
725- >>> ls_bug.transitionToStatus(BugTaskStatus.TRIAGED, mark)
726- >>> Store.of(ls_bug).flush()
727- >>> mf_bug = getUtility(IBugTaskSet).get(17)
728- >>> mf_bug.transitionToStatus(BugTaskStatus.TRIAGED, mark)
729- >>> Store.of(mf_bug).flush()
730- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts())
731- linux-source-2.6.15 None 1 0 0 0 0
732- mozilla-firefox firefox 1 1 1 1 0
733- thunderbird None 1 1 1 1 0
734-
735-We add two new bugs to pmount in Ubuntu. From now on we'll limit the
736-results to 3 packages (as a demonstration of the API) so thunderbird
737-will be popped off the list:
738-
739- >>> bug = create_bug_from_strings(distribution='ubuntu',
740- ... sourcepackagename='pmount', owner='name12',
741- ... summary='pmount used to work', description='fix it',
742- ... status=BugTaskStatus.TRIAGED)
743- >>> bug = create_bug_from_strings(distribution='ubuntu',
744- ... sourcepackagename='pmount', owner='name12',
745- ... summary='pmount has issues', description='fix it again',
746- ... status=BugTaskStatus.TRIAGED)
747- >>> ubuntu_pmount_task = bug.bugtasks[0]
748- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
749- pmount None 2 2 0 0 0
750- linux-source-2.6.15 None 1 0 0 0 0
751- mozilla-firefox firefox 1 1 1 1 0
752-
753-As you can see, there is no packaging data for pmount in Ubuntu, so no
754-upstream is reported for it. Let's fix that:
755-
756- >>> pmount_spn = getUtility(ISourcePackageNameSet).queryByName('pmount')
757- >>> name12 = getUtility(IPersonSet).getByName('name12')
758- >>> pmount = getUtility(IProductSet).createProduct(
759- ... name12, 'pmount', 'pmount', 'pmount', 'pmount')
760- >>> packaging = getUtility(IPackagingUtil).createPackaging(
761- ... pmount.getSeries('trunk'), pmount_spn,
762- ... ubuntu.currentseries, PackagingType.PRIME, name12)
763- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
764- pmount pmount 2 2 0 0 0
765- linux-source-2.6.15 None 1 0 0 0 0
766- mozilla-firefox firefox 1 1 1 1 0
767-
768-We then add an upstream task to the second pmount bug:
769-
770- >>> task = getUtility(IBugTaskSet).createTask(bug, name12, pmount)
771- >>> Store.of(task).flush()
772- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
773- pmount pmount 2 2 1 0 0
774- linux-source-2.6.15 None 1 0 0 0 0
775- mozilla-firefox firefox 1 1 1 1 0
776-
777-The last column counts those bugs with upstream tasks that have patches
778-attached but which don't have an upstream bugwatch. If we add a ordinary
779-attachment to our pmount bug, the value of the last column does not
780-change...
781-
782- >>> attachment = factory.makeBugAttachment(bug)
783- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
784- pmount pmount 2 2 1 0 0
785- linux-source-2.6.15 None 1 0 0 0 0
786- mozilla-firefox firefox 1 1 1 1 0
787-
788-...but when we make this attachment a patch, the value of the column
789-increases.
790-
791- >>> from lp.bugs.interfaces.bugattachment import BugAttachmentType
792- >>> attachment.type = BugAttachmentType.PATCH
793- >>> Store.of(attachment).flush()
794- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
795- pmount pmount 2 2 1 0 1
796- linux-source-2.6.15 None 1 0 0 0 0
797- mozilla-firefox firefox 1 1 1 1 0
798-
799-Note that we count only bugs with patches for products that do not
800-use Malone officially.
801-
802- >>> pmount.official_malone = True
803- >>> Store.of(pmount).flush()
804- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
805- pmount pmount 2 2 1 1 0
806- linux-source-2.6.15 None 1 0 0 0 0
807- mozilla-firefox firefox 1 1 1 1 0
808-
809- >>> pmount.official_malone = False
810- >>> Store.of(pmount).flush()
811-
812-Linking that task to a bugwatch increases the watch counts and decreases
813-the count of bugs having patches but no bug watch.
814-
815- >>> url = "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=666"
816- >>> [watch] = getUtility(IBugWatchSet).fromText(url, bug, name12)
817- >>> task.bugwatch = watch
818- >>> Store.of(task).flush()
819- >>> print_report(ubuntu.getPackagesAndPublicUpstreamBugCounts(limit=3))
820- pmount pmount 2 2 1 1 0
821- linux-source-2.6.15 None 1 0 0 0 0
822- mozilla-firefox firefox 1 1 1 1 0
823-
824-
825-The view
826---------
827-
828-We test that the view data is constructed sanely and without any hidden
829-defects. Let's set up some helpers to make it easier for us to output
830-them:
831-
832- >>> from lp.testing.systemdocs import create_view
833-
834- >>> def print_numbers(data):
835- ... for f in ['open_bugs',
836- ... 'triaged_bugs',
837- ... 'upstream_bugs',
838- ... 'watched_bugs',
839- ... 'triaged_bugs_percentage',
840- ... 'upstream_bugs_percentage',
841- ... 'watched_bugs_percentage',
842- ... 'triaged_bugs_class',
843- ... 'upstream_bugs_class',
844- ... 'watched_bugs_class',
845- ... 'triaged_bugs_delta',
846- ... 'upstream_bugs_delta',
847- ... 'watched_bugs_delta']:
848- ... print getattr(data, f),
849-
850- >>> def print_helpers(data):
851- ... print data.dsp.name, data.dsp.distribution.name,
852- ... if data.dssp:
853- ... print data.dssp.distroseries.name
854- ... else:
855- ... print "NO SERIES"
856- ... if data.product:
857- ... print data.product.name
858- ... else:
859- ... print "NO PRODUCT"
860- ... for f in ['bug_supervisor_url', 'product_edit_url',
861- ... 'upstream_bugs_url', 'upstream_bugs_delta_url',
862- ... 'watched_bugs_delta_url']:
863- ... t = getattr(data, f, "NO URL")
864- ... print t.replace(
865- ... "http://bugs.launchpad.dev/ubuntu/+source/", "**")
866- ... print "--"
867-
868-Get an Ubuntu view:
869-
870- >>> view = create_view(ubuntu, '+upstreamreport')
871- >>> view.initialize()
872-
873-Here are the helper URLs we construct:
874-
875- >>> for item in view.data:
876- ... print_helpers(item)
877- pmount ubuntu hoary
878- pmount
879- http://launchpad.dev/pmount/+bugsupervisor
880- http://launchpad.dev/pmount/+edit
881- **pmount/+bugs?search=Search&field.status_upstream=open_upstream
882- **pmount/+bugs?search=Search&field.status_upstream=hide_upstream
883- **pmount/+bugs?search=Search&field.status_upstream=pending_bugwatch
884- --
885- linux-source-2.6.15 ubuntu hoary
886- NO PRODUCT
887- NO URL
888- NO URL
889- **linux-source-2.6.15/+bugs?...&field.status_upstream=open_upstream
890- **linux-source-2.6.15/+bugs?...h&field.status_upstream=hide_upstream
891- **linux-source-2.6.15/+bugs?...&field.status_upstream=pending_bugwatch
892- --
893- mozilla-firefox ubuntu hoary
894- firefox
895- http://launchpad.dev/firefox/+bugsupervisor
896- http://launchpad.dev/firefox/+edit
897- **mozilla-firefox/+bugs?search=Search&field.status_upstream=open_upstream
898- **mozilla-firefox/+bugs?search=Search&field.status_upstream=hide_upstream
899- **mozilla-firefox/+bugs?...&field.status_upstream=pending_bugwatch
900- --
901- thunderbird ubuntu hoary
902- NO PRODUCT
903- NO URL
904- NO URL
905- **thunderbird/+bugs?search=Search&field.status_upstream=open_upstream
906- **thunderbird/+bugs?search=Search&field.status_upstream=hide_upstream
907- **thunderbird/+bugs?search=Search&field.status_upstream=pending_bugwatch
908- --
909-
910-Let's print out the counts and percentages:
911-
912- >>> for item in view.data:
913- ... print_numbers(item)
914- ... print
915- 2 2 1 1 100.0 50.0 100.0 good good 0 1 0
916- 1 0 0 0 0.0 0.0 0.0 1 1 0
917- 1 1 1 1 100.0 100.0 100.0 good good good 0 0 0
918- 1 1 1 1 100.0 100.0 100.0 good good good 0 0 0
919-
920-And the total line:
921-
922- >>> print_numbers(view.total)
923- 5 4 3 3 80.0 60.0 100.0 good good 1 2 0
924-
925-
926-Sorting the report
927-------------------
928-
929-The upstream report is sortable by each of the columns displayed. We'll
930-demonstrate this using the Ubuntu report.
931-
932- >>> view = create_view(ubuntu, '+upstreamreport')
933-
934-The view has a sort_order property. This returns a tuple of (sort_key,
935-reversed), where sort_key is a string which can be mapped to one of the
936-properties of PackageBugReportData and reversed is a boolean which
937-indicates whether the current sort is ascending (reversed=False) or
938-descending (reversed=True). By default, the sort_order for any report is
939-number of open bugs, descending.
940-
941- >>> view.sort_order
942- ('open_bugs', True)
943-
944-The sort order can be changed by altering the sort_by request parameter.
945-
946- >>> form = {'sort_by': 'product'}
947-
948- >>> view = create_view(ubuntu, '+upstreamreport', form)
949- >>> view.sort_order
950- ('product', False)
951-
952-Prepending a '-' to the sort_by parameter will cause the sort_order to
953-be reversed.
954-
955- >>> form = {'sort_by': '-product'}
956- >>> view = create_view(ubuntu, '+upstreamreport', form)
957-
958- >>> view.sort_order
959- ('product', True)
960-
961-The DistributionUpstreamReport view has a list of valid sort keys. If
962-we try to sort by a key that isn't in that list we'll get the default
963-sort_order back. (See test_distribution_upstream_report.py in
964-browser/tests for further testing of this).
965-
966- >>> form = {'sort_by': 'ifthisisvalidilleatmyhat'}
967- >>> view = create_view(ubuntu, '+upstreamreport', form)
968-
969- >>> view.sort_order
970- ('open_bugs', True)
971-
972-The DistributionUpstreamReport view also has a sort_order_links
973-property. This is a dict of URLs which is used to create the links in
974-the sortable table header on the +upstreamreport page for the
975-distribution.
976-
977-The current sort_order is the default one.
978-
979- >>> view.sort_order
980- ('open_bugs', True)
981-
982-All the links, by default will link to a standard forward sort for their
983-particular sort_key. In this case, this is also true of the open_bugs
984-key, since this is at the moment reverse-sorted.
985-
986- >>> def print_sort_order_links(view, key='link'):
987- ... for sort_key in sorted(view.sort_order_links):
988- ... link_dict = view.sort_order_links[sort_key]
989- ... print sort_key, link_dict[key]
990-
991- >>> print_sort_order_links(view)
992- bug_supervisor_name http://...?sort_by=bug_supervisor_name
993- bugs_with_upstream_patches http:...?sort_by=bugs_with_upstream_patches
994- bugtracker_name http://...?sort_by=bugtracker_name
995- dsp http://...?sort_by=dsp
996- open_bugs http://...?sort_by=open_bugs
997- product http://...?sort_by=product
998- triaged_bugs http://...?sort_by=triaged_bugs
999- triaged_bugs_class http://...?sort_by=triaged_bugs_class
1000- triaged_bugs_delta http://...?sort_by=triaged_bugs_delta
1001- triaged_bugs_percentage http://...?sort_by=triaged_bugs_percentage
1002- upstream_bugs http://...?sort_by=upstream_bugs
1003- upstream_bugs_class http://...?sort_by=upstream_bugs_class
1004- upstream_bugs_delta http://...?sort_by=upstream_bugs_delta
1005- upstream_bugs_percentage http://...?sort_by=upstream_bugs_percentage
1006- watched_bugs http://...?sort_by=watched_bugs
1007- watched_bugs_class http://...?sort_by=watched_bugs_class
1008- watched_bugs_delta http://...?sort_by=watched_bugs_delta
1009- watched_bugs_percentage http://...?sort_by=watched_bugs_percentage
1010-
1011-Changing the sort_order to a forward sort of, say, bug_supervisor_name
1012-will change the link for that sort key. The others will remain
1013-unaffected.
1014-
1015- >>> form = {'sort_by': 'bug_supervisor_name'}
1016- >>> view = create_view(ubuntu, '+upstreamreport', form)
1017-
1018- >>> view.sort_order
1019- ('bug_supervisor_name', False)
1020-
1021- >>> print_sort_order_links(view)
1022- bug_supervisor_name http://...?sort_by=-bug_supervisor_name
1023- bugs_with_upstream_patches http:...?sort_by=bugs_with_upstream_patches
1024- bugtracker_name http://...?sort_by=bugtracker_name...
1025-
1026-Each sort_order_links dict has an 'arrow' key. This is the URL of the
1027-arrow icon to be displayed next to the link in the table header.
1028-
1029- >>> print_sort_order_links(view, 'arrow')
1030- bug_supervisor_name /@@/arrowUp
1031- bugs_with_upstream_patches /@@/arrowBlank
1032- bugtracker_name /@@/arrowBlank
1033- dsp /@@/arrowBlank
1034- open_bugs /@@/arrowBlank
1035- product /@@/arrowBlank
1036- triaged_bugs /@@/arrowBlank
1037- triaged_bugs_class /@@/arrowBlank
1038- triaged_bugs_delta /@@/arrowBlank
1039- triaged_bugs_percentage /@@/arrowBlank
1040- upstream_bugs /@@/arrowBlank
1041- upstream_bugs_class /@@/arrowBlank
1042- upstream_bugs_delta /@@/arrowBlank
1043- upstream_bugs_percentage /@@/arrowBlank
1044- watched_bugs /@@/arrowBlank
1045- watched_bugs_class /@@/arrowBlank
1046- watched_bugs_delta /@@/arrowBlank
1047- watched_bugs_percentage /@@/arrowBlank
1048-
1049-Altering the sort order will change the arrow for the current sort order
1050-accordingly.
1051-
1052- >>> form = {'sort_by': '-bug_supervisor_name'}
1053- >>> view = create_view(ubuntu, '+upstreamreport', form)
1054-
1055- >>> view.sort_order
1056- ('bug_supervisor_name', True)
1057-
1058- >>> print_sort_order_links(view, 'arrow')
1059- bug_supervisor_name /@@/arrowDown
1060- bugs_with_upstream_patches /@@/arrowBlank
1061- bugtracker_name /@@/arrowBlank...
1062-
1063- >>> form = {'sort_by': '-bugtracker_name'}
1064- >>> view = create_view(ubuntu, '+upstreamreport', form)
1065-
1066- >>> view.sort_order
1067- ('bugtracker_name', True)
1068-
1069- >>> print_sort_order_links(view, 'arrow')
1070- bug_supervisor_name /@@/arrowBlank
1071- bugs_with_upstream_patches /@@/arrowBlank
1072- bugtracker_name /@@/arrowDown...
1073
1074=== removed file 'lib/lp/bugs/stories/distribution/xx-distribution-upstream-report.txt'
1075--- lib/lp/bugs/stories/distribution/xx-distribution-upstream-report.txt 2012-10-02 06:36:44 +0000
1076+++ lib/lp/bugs/stories/distribution/xx-distribution-upstream-report.txt 1970-01-01 00:00:00 +0000
1077@@ -1,303 +0,0 @@
1078-Distribution upstream report pages
1079-==================================
1080-
1081-Upstream report pages for distributions contain all sorts of fun
1082-information that describe how well the bugs are forwarded to upstreams.
1083-
1084-We start this test off by creating some bugs and tasks to ensure we have
1085-rich enough information to display. We create one bug against
1086-linux-source with a firefox task, and we add a tomcat task against
1087-pre-existing linux-source bug 10. We transition the Ubuntu tasks to
1088-TRIAGED to ensure that they are picked up by the report.
1089-
1090- >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
1091- >>> from lp.testing import login, logout
1092- >>> from lp.services.database.sqlbase import flush_database_updates
1093- >>> from lp.bugs.tests.bug import (
1094- ... create_bug_from_strings, create_task_from_strings,
1095- ... update_task_status)
1096-
1097- >>> login('foo.bar@canonical.com')
1098-
1099- >>> watchurl = "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s"
1100- >>> bug = create_bug_from_strings(distribution='ubuntu',
1101- ... sourcepackagename='linux-source-2.6.15', owner='name12',
1102- ... summary='take one', description='funk philosophy',
1103- ... status=BugTaskStatus.TRIAGED)
1104- >>> update_task_status(bug.bugtasks[0].id, 'mark',
1105- ... BugTaskStatus.TRIAGED)
1106- >>> update_task_status(25, 'mark', BugTaskStatus.TRIAGED)
1107- >>> task = create_task_from_strings(bug.id, 'name12', 'firefox')
1108- >>> task = create_task_from_strings(10, 'name12', 'tomcat')
1109- >>> update_task_status(23, 'mark', BugTaskStatus.TRIAGED)
1110- >>> update_task_status(17, 'mark', BugTaskStatus.TRIAGED)
1111-
1112- >>> flush_database_updates()
1113- >>> import transaction
1114- >>> transaction.commit()
1115- >>> logout()
1116-
1117-So let's check out the actual table:
1118-
1119- >>> browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1120- >>> table = find_tag_by_id(browser.contents, 'upstream-report-content')
1121- >>> print extract_text(table, True)
1122- linux-source-2.6.15 Missing corresponding project. (find) (link)
1123- 2 2 100.00 0 2 100.00 0 1 50.00 1 -
1124- mozilla-firefox Mozilla Firefox Launchpad Unspecified
1125- 1 1 100.00 0 1 100.00 0
1126- thunderbird Missing corresponding project. (find) (link)
1127- 1 1 100.00 0 1 100.00 0 1 100.00 0 -
1128-
1129-XXX kiko 2008-02-01 bug=188020: One thing to note from the counts above
1130-is that while the linux-source/tomcat task is counted as pending a
1131-bugwatch, the mozilla-firefox one is not -- and the reason for that is
1132-that mozilla-firefox's linked upstream, firefox, officially uses malone.
1133-We count it as "watched" even though it's really native. The problem
1134-becomes clearer as you look at the totals:
1135-
1136- >>> table = find_tag_by_id(browser.contents, 'upstream-report-totals')
1137- >>> print extract_text(table)
1138- Totals: 4 4 100.00 0 4 100.00 0 3 75.00 1 0
1139-
1140-This 3 in the watched column is caused by a problem similar to the
1141-above; if you do a count of the values in the cells it would be 2, of
1142-course. Reason is that watch counts against mozilla-firefox are not
1143-rendered, but totalized as if they were watched, and the total line
1144-doesn't add up.
1145-
1146-This and the display problem above should be fixed. We could avoid
1147-totalizing the watched bugs for native upstreams, but then we'd have a
1148-broken percentage when comparing with bugs marked upstream. Perhaps
1149-we'd solve this by separating the watched column into natively watched
1150-and remote watched, but I don't know how to fix this right now. See
1151-bug #188020 -- kiko, 2008-02-01
1152-
1153-
1154-Useful links
1155-------------
1156-
1157-The table includes a number of convenience links:
1158-
1159- >>> table = find_tag_by_id(browser.contents, 'upstream-report-content')
1160- >>> all_anchors = table.findAll('a')
1161- >>> base_href = browser.url
1162- >>> for anchor in all_anchors:
1163- ... url = extract_link_from_tag(anchor, base_href)
1164- ... url = url.replace("http://bugs.launchpad.dev/ubuntu", "**")
1165- ... url = url.replace("&", "\n&")
1166- ... print extract_text(anchor), url
1167- linux-source-2.6.15 **/+source/linux-source-2.6.15
1168- (find) http://bugs.launchpad.dev/projects
1169- (link) **/hoary/+source/linux-source-2.6.15/+edit-packaging
1170- 2 **/+source/linux-source-2.6.15/+bugs?search=Search
1171- 2 **/+source/linux-source-2.6.15/+bugs?search=Search
1172- &field.status=TRIAGED
1173- 0 **/+source/linux-source-2.6.15/+bugs?search=Search
1174- &field.status=CONFIRMED
1175- &field.status=INCOMPLETE_WITHOUT_RESPONSE
1176- &field.status=INCOMPLETE_WITH_RESPONSE
1177- &field.status=NEW
1178- 2 **/+source/linux-source-2.6.15/+bugs?search=Search
1179- &field.status_upstream=open_upstream
1180- 0 **/+source/linux-source-2.6.15/+bugs?search=Search
1181- &field.status_upstream=hide_upstream
1182- 1 **/+source/linux-source-2.6.15/+bugs?search=Search
1183- &field.status_upstream=pending_bugwatch
1184- ...
1185-
1186-
1187-Links to bug queries
1188-....................
1189-
1190-Let's look more carefully at the above six links associated with counts.
1191-
1192- >>> count_anchors = all_anchors[3:9]
1193- >>> (open_url, triaged_url, triaged_delta, upstream_url,
1194- ... upstream_delta_url, watch_delta_url) = \
1195- ... [str(extract_link_from_tag(x, base_href)) for x in count_anchors]
1196-
1197-Link 1. Open bugs (2)
1198-
1199- >>> from lp.bugs.tests.bug import print_bugtasks
1200- >>> browser.open(open_url)
1201- >>> browser.title
1202- 'Bugs : \xe2\x80\x9clinux-source-2.6.15\xe2\x80\x9d package : Ubuntu'
1203- >>> print_bugtasks(browser.contents)
1204- 10 another test bug linux-source-2.6.15 (Ubuntu) Medium Triaged
1205- 16 take one linux-source-2.6.15 (Ubuntu) Undecided Triaged
1206-
1207-Link 2. Triaged bugs (2)
1208-
1209- >>> browser.open(triaged_url)
1210- >>> browser.title
1211- 'Bugs : \xe2\x80\x9clinux-source-2.6.15\xe2\x80\x9d package : Ubuntu'
1212- >>> print_bugtasks(browser.contents)
1213- 10 another test bug linux-source-2.6.15 (Ubuntu) Medium Triaged
1214- 16 take one linux-source-2.6.15 (Ubuntu) Undecided Triaged
1215-
1216-Link 3: Open bugs that aren't triaged (0)
1217-
1218- >>> browser.open(triaged_delta)
1219- >>> browser.title
1220- 'Bugs : \xe2\x80\x9clinux-source-2.6.15\xe2\x80\x9d package : Ubuntu'
1221- >>> print extract_text(
1222- ... find_tag_by_id(browser.contents, 'bugs-table-listing'))
1223- No results for search
1224-
1225-Link 4: Triaged bugs that are upstream issues (2)
1226-
1227- >>> browser.open(upstream_url)
1228- >>> browser.title
1229- 'Bugs : \xe2\x80\x9clinux-source-2.6.15\xe2\x80\x9d package : Ubuntu'
1230- >>> print_bugtasks(browser.contents)
1231- 10 another test bug linux-source-2.6.15 (Ubuntu) Medium Triaged
1232- 16 take one linux-source-2.6.15 (Ubuntu) Undecided Triaged
1233-
1234-Link 5: Triaged bugs that haven't been marked upstream (0)
1235-
1236- >>> browser.open(upstream_delta_url)
1237- >>> browser.title
1238- 'Bugs : \xe2\x80\x9clinux-source-2.6.15\xe2\x80\x9d package : Ubuntu'
1239- >>> print extract_text(
1240- ... find_tag_by_id(browser.contents, 'bugs-table-listing'))
1241- No results for search
1242-
1243-Link 6: Triaged bugs marked upstream lacking a watch (1)
1244-
1245- >>> browser.open(watch_delta_url)
1246- >>> browser.title
1247- 'Bugs : \xe2\x80\x9clinux-source-2.6.15\xe2\x80\x9d package : Ubuntu'
1248- >>> print_bugtasks(browser.contents)
1249- 10 another test bug linux-source-2.6.15 (Ubuntu) Medium Triaged
1250-
1251-
1252-Links to fix registration data
1253-..............................
1254-
1255-As you saw before, we also offer links to find products and connect packages
1256-with no packaging links (though you need to be logged in to actually see or
1257-link them):
1258-
1259- >>> browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1260- >>> browser.getLink('find').click()
1261- >>> browser.title
1262- 'Projects registered in Launchpad'
1263- >>> user_browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1264- >>> user_browser.getLink('link').click()
1265- >>> print user_browser.title
1266- Link to an upstream project : ...
1267-
1268-
1269-If you are logged in and can edit the upstream project, you can also set
1270-a bug tracker and a bug supervisor. Let's first fix Firefox to stop using
1271-Malone officially for the sake of this test:
1272-
1273- >>> login('foo.bar@canonical.com')
1274- >>> from zope.component import getUtility
1275- >>> from lp.registry.interfaces.product import IProductSet
1276- >>> firefox = getUtility(IProductSet).getByName('firefox')
1277- >>> firefox.official_malone = False
1278- >>> flush_database_updates()
1279- >>> transaction.commit()
1280- >>> logout()
1281-
1282-And check out the report:
1283-
1284- >>> admin_browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1285- >>> admin_browser.getLink('fix').click()
1286- >>> print admin_browser.title
1287- Change Mozilla Firefox's...
1288-
1289- >>> admin_browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1290- >>> admin_browser.getLink('change').click()
1291- >>> print admin_browser.title
1292- Edit bug supervisor for...
1293-
1294-If you're not allowed to edit the product, no love for you, though:
1295-
1296- >>> user_browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1297- >>> user_browser.getLink('fix')
1298- Traceback (most recent call last):
1299- ...
1300- LinkNotFoundError
1301-
1302- >>> user_browser.getLink('change')
1303- Traceback (most recent call last):
1304- ...
1305- LinkNotFoundError
1306-
1307-Note that we also cope correctly with the case in which a project's
1308-bugtracker is inherited from it's project group:
1309-
1310- >>> login('foo.bar@canonical.com')
1311- >>> from lp.bugs.interfaces.bugtracker import IBugTrackerSet
1312- >>> mozilla_org = getUtility(IBugTrackerSet).getByName("mozilla.org")
1313- >>> firefox.project.bugtracker = mozilla_org
1314- >>> flush_database_updates()
1315- >>> transaction.commit()
1316- >>> logout()
1317-
1318-See how we nicely display the bugtracker in the table's second row now.
1319-Note also that the watch counts for linux-source and mozilla-firefox
1320-change to account for the fact that firefox no longer uses_malone.
1321-
1322- >>> browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1323- >>> table = find_tag_by_id(browser.contents, 'upstream-report-content')
1324- >>> print extract_text(table, True)
1325- linux-source-2.6.15 Missing corresponding project. (find) (link)
1326- 2 2 100.00 0 2 100.00 0 0 0.00 2 -
1327- mozilla-firefox Mozilla Firefox The Mozilla.org Bug Tracker Unspecified
1328- 1 1 100.00 0 1 100.00 0 0 0.00 1 -
1329- thunderbird Missing corresponding project. (find) (link)
1330- 1 1 100.00 0 1 100.00 0 1 100.00 0 -
1331-
1332-If we now add an attachment to the bug we created earlier, the number
1333-of bugs with patches for upstream increases for linux-source-2.6.15.
1334-
1335- >>> login('foo.bar@canonical.com')
1336- >>> attachment = factory.makeBugAttachment(bug, is_patch=True)
1337- >>> logout()
1338- >>> browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1339- >>> table = find_tag_by_id(browser.contents, 'upstream-report-content')
1340- >>> print extract_text(table, True)
1341- linux-source-2.6.15 Missing corresponding project. (find) (link)
1342- 2 2 100.00 0 2 100.00 0 0 0.00 2 1
1343- mozilla-firefox Mozilla Firefox The Mozilla.org Bug Tracker Unspecified
1344- 1 1 100.00 0 1 100.00 0 0 0.00 1 -
1345- thunderbird Missing corresponding project. (find) (link)
1346- 1 1 100.00 0 1 100.00 0 1 100.00 0 -
1347-
1348-Finally, you can associate default branches to products. This is done by
1349-specifying a branch for the product's main_series:
1350-
1351- >>> admin_browser.open("http://launchpad.dev/firefox/trunk/+linkbranch")
1352- >>> branch_control = admin_browser.getControl(name="field.branch")
1353- >>> branch_control.value = "~name12/firefox/main"
1354- >>> admin_browser.getControl("Update").click()
1355- >>> admin_browser.url
1356- 'http://launchpad.dev/firefox/trunk'
1357-
1358-The report now renders an icon and a link to the branch page:
1359-
1360- >>> browser.open("http://bugs.launchpad.dev/ubuntu/+upstreamreport")
1361- >>> browser.getLink('branch').click()
1362- >>> browser.url
1363- 'http://code.launchpad.dev/~name12/firefox/main'
1364-
1365-(This means that anyone can use bzr branch lp:firefox to pull this code.)
1366-
1367-
1368-Empty distributions
1369--------------------
1370-
1371-If a distribution does not use Launchpad for bug tracking, no
1372-upstream report is shown, only a message that no data is available.
1373-
1374- >>> browser.open("http://bugs.launchpad.dev/debian/+upstreamreport")
1375- >>> table = find_tag_by_id(browser.contents, 'upstream-report-content')
1376- >>> print table
1377- None
1378- >>> message = find_tag_by_id(browser.contents, 'no-lp-usage')
1379- >>> print extract_text(message)
1380- This distribution does not use Launchpad for development.
1381
1382=== removed file 'lib/lp/bugs/templates/distribution-upstream-report.pt'
1383--- lib/lp/bugs/templates/distribution-upstream-report.pt 2012-05-25 18:39:46 +0000
1384+++ lib/lp/bugs/templates/distribution-upstream-report.pt 1970-01-01 00:00:00 +0000
1385@@ -1,298 +0,0 @@
1386-<distribution-upstream-report
1387- xmlns="http://www.w3.org/1999/xhtml"
1388- xmlns:tal="http://xml.zope.org/namespaces/tal"
1389- xmlns:metal="http://xml.zope.org/namespaces/metal"
1390- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
1391- metal:use-macro="view/macro:page/main_only"
1392- i18n:domain="launchpad">
1393-
1394- <metal:heading fill-slot="head_epilogue">
1395- <style type="text/css">
1396- <!--
1397- td.amount a { display: block; }
1398- tr.good { background: #d7ffbf; }
1399- tr.bad { background: #ffbfbf; }
1400- -->
1401- </style>
1402- </metal:heading>
1403-
1404- <div metal:fill-slot="heading">
1405- <h1>
1406- Upstream report for
1407- <tal:distro replace="context/displayname" />
1408- </h1>
1409- </div>
1410-
1411- <div metal:fill-slot="main">
1412- <div class="top-portlet">
1413- <div tal:condition="not:view/has_upstream_report"
1414- id="no-lp-usage">
1415- This distribution does not use Launchpad for development.
1416- </div>
1417- <div tal:condition="view/has_upstream_report"
1418- id="lp-used">
1419- <tal:no-bugs tal:condition="not: view/data">
1420- <span tal:replace="context/displayname" /> has no bugs filed
1421- against it.
1422- </tal:no-bugs>
1423-
1424- <p tal:condition="view/data">
1425- See the
1426- <a href="https://wiki.ubuntu.com/Bugs/Upstream/UpstreamReport"
1427- >UpstreamReport wiki page</a>
1428- for information on how to read this report.
1429- </p>
1430-
1431- <table class="listing" id="upstream-report"
1432- tal:condition="view/data">
1433- <thead tal:define="links view/sort_order_links">
1434- <tr>
1435- <th>
1436- <a id="sort-dsp" tal:attributes="href links/dsp/link">Package</a>
1437- <img id="sortarrow-dsp" tal:attributes="src links/dsp/arrow" />
1438- </th>
1439- <th colspan="2">
1440- <a id="sort-product" tal:attributes="href links/product/link">Project</a>
1441- <img id="sortarrow-product" tal:attributes="src links/product/arrow" />
1442- </th>
1443- <th>
1444- <a id="sort-bugtracker-name"
1445- tal:attributes="href links/bugtracker_name/link"
1446- >Bugtracker</a>
1447- <img id="sortarrow-bugtracker-name"
1448- tal:attributes="src links/bugtracker_name/arrow" />
1449- </th>
1450- <th>
1451- <a id="sort-bug-supervisor-name"
1452- tal:attributes="href links/bug_supervisor_name/link"
1453- >Upstream Contact</a>
1454- <img id="sortarrow-bug-supervisor-name"
1455- tal:attributes="src links/bug_supervisor_name/arrow" />
1456- </th>
1457- <th class="amount">
1458- <a id="sort-open-bugs"
1459- tal:attributes="href links/open_bugs/link">Open</a>
1460- <img id="sortarrow-open-bugs"
1461- tal:attributes="src links/open_bugs/arrow" />
1462- </th>
1463- <th class="amount">
1464- <a id="sort-triaged-bugs"
1465- tal:attributes="href links/triaged_bugs/link"
1466- >Triaged</a>
1467- <img id="sortarrow-triaged-bugs"
1468- tal:attributes="src links/triaged_bugs/arrow" />
1469- </th>
1470- <th class="amount">
1471- <a id="sort-triaged-percentage"
1472- tal:attributes="href links/triaged_bugs_percentage/link"
1473- >%</a>
1474- <img id="sortarrow-triaged-percentage"
1475- tal:attributes="src links/triaged_bugs_percentage/arrow" />
1476- </th>
1477- <th class="amount">
1478- <a id="sort-triaged-delta"
1479- tal:attributes="href links/triaged_bugs_delta/link"
1480- >&Delta;</a>
1481- <img id="sortarrow-triaged-delta"
1482- tal:attributes="src links/triaged_bugs_delta/arrow" />
1483- </th>
1484- <th class="amount">
1485- <a id="sort-upstream-bugs"
1486- tal:attributes="href links/upstream_bugs/link"
1487- >Upstream</a>
1488- <img id="sortarrow-upstream-bugs"
1489- tal:attributes="src links/upstream_bugs/arrow" />
1490- </th>
1491- <th class="amount">
1492- <a id="sort-upstream-percentage"
1493- tal:attributes="href links/upstream_bugs_percentage/link"
1494- >%</a>
1495- <img id="sortarrow-upstream-percentage"
1496- tal:attributes="src links/upstream_bugs_percentage/arrow" />
1497- </th>
1498- <th class="amount">
1499- <a id="sort-upstream-delta"
1500- tal:attributes="href links/upstream_bugs_delta/link"
1501- >&Delta;</a>
1502- <img id="sortarrow-upstream-delta"
1503- tal:attributes="src links/upstream_bugs_delta/arrow" />
1504- </th>
1505- <th class="amount">
1506- <a id="sort-watched-bugs"
1507- tal:attributes="href links/watched_bugs/link">Watch</a>
1508- <img id="sortarrow-watched-bugs"
1509- tal:attributes="src links/watched_bugs/arrow" />
1510- </th>
1511- <th class="amount">
1512- <a id="sort-watched-percentage"
1513- tal:attributes="href links/watched_bugs_percentage/link"
1514- >%</a>
1515- <img id="sortarrow-watched-percentage"
1516- tal:attributes="src links/watched_bugs_percentage/arrow" />
1517- </th>
1518- <th class="amount">
1519- <a id="sort-watched-delta"
1520- tal:attributes="href links/watched_bugs_delta/link"
1521- >&Delta;</a>
1522- <img id="sortarrow-watched-delta"
1523- tal:attributes="src links/watched_bugs_delta/arrow" />
1524- </th>
1525- <th class="amount">
1526- <a id="sort-bugs-with-upstream-patches"
1527- tal:attributes="href links/bugs_with_upstream_patches/link"
1528- >Patches for upstream</a>
1529- <img id="sortarrow-bugs-with-upstream-patches"
1530- tal:attributes="src links/bugs_with_upstream_patches/arrow" />
1531- </th>
1532- </tr>
1533- </thead>
1534- <tbody id="upstream-report-content">
1535- <tr tal:repeat="item view/data" tal:attributes="class item/row_class">
1536- <td>
1537- <a tal:attributes="href item/dsp/fmt:url"
1538- tal:content="item/dsp/name"></a>
1539- </td>
1540- <tal:has-product condition="item/product">
1541- <td style="padding-right: 0" align="center">
1542- <a tal:attributes="href item/branch/fmt:url;
1543- title string:You can use `bzr branch lp:${item/product/name}' to fetch this project's code"
1544- tal:condition="item/branch">
1545- <img alt="(branch)" src="/@@/branch" /></a>
1546- </td>
1547- <td style="padding-left: 0" align="center">
1548- <a tal:attributes="href item/product/fmt:url"
1549- ><img src="/@@/yes"
1550- tal:attributes="title item/product/title" /></a>
1551- </td>
1552- <tal:has-bugtracker condition="item/bugtracker">
1553- <td align="center">
1554- <a tal:attributes="href item/bugtracker/fmt:url"
1555- ><img src="/@@/yes"
1556- tal:attributes="title item/bugtracker/title" /></a>
1557- </td>
1558- </tal:has-bugtracker>
1559- <tal:has-no-bugtracker condition="not: item/bugtracker">
1560- <td tal:condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD"
1561- align="center">
1562- <img src="/@@/yes" title="Launchpad" />
1563- </td>
1564- <td tal:condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD"
1565- align="center">
1566- <img src="/@@/no" title="Unknown" />
1567- <a tal:condition="item/product/required:launchpad.Edit"
1568- tal:attributes="href item/product_edit_url">
1569- <small>(fix)</small></a></td>
1570- </tal:has-no-bugtracker>
1571- <tal:has-bug-supervisor condition="item/product/bug_supervisor">
1572- <td align="center">
1573- <a tal:attributes="href item/product/bug_supervisor/fmt:url"
1574- ><img src="/@@/yes"
1575- tal:attributes="title item/product/bug_supervisor/fmt:displayname"
1576- /></a>
1577- </td>
1578- </tal:has-bug-supervisor>
1579- <tal:has-no-bug-supervisor condition="not: item/product/bug_supervisor">
1580- <td align="center">
1581- <img src="/@@/no" title="Unspecified" />
1582- <a tal:condition="item/product/required:launchpad.Edit"
1583- tal:attributes="href item/bug_supervisor_url">
1584- <small>(change)</small></a></td>
1585- </tal:has-no-bug-supervisor>
1586- </tal:has-product>
1587- <tal:has-no-product condition="not: item/product">
1588- <td align="center" class="bad" colspan="4">
1589- Missing corresponding project.
1590- <a href="/projects"><small>(find)</small></a>
1591- <a tal:attributes="href item/packaging_url">
1592- <small>(link)</small></a>
1593- </td>
1594- </tal:has-no-product>
1595- <td class="amount">
1596- <a tal:attributes="href item/open_bugs_url"
1597- tal:content="item/open_bugs"></a>
1598- </td>
1599- <td tal:attributes="class string:amount ${item/triaged_bugs_class}">
1600- <a tal:attributes="href item/triaged_bugs_url"
1601- tal:content="item/triaged_bugs"></a>
1602- </td>
1603- <td tal:attributes="class string:amount ${item/triaged_bugs_class}"
1604- tal:content="item/triaged_bugs_percentage/fmt:float/.2" />
1605- <td tal:attributes="class string:amount ${item/triaged_bugs_class}">
1606- <a tal:attributes="href item/triaged_bugs_delta_url"
1607- tal:content="item/triaged_bugs_delta"></a>
1608- </td>
1609- <td tal:attributes="class string:amount ${item/upstream_bugs_class}">
1610- <a tal:attributes="href item/upstream_bugs_url"
1611- tal:content="item/upstream_bugs"></a>
1612- </td>
1613- <td tal:attributes="class string:amount ${item/upstream_bugs_class}"
1614- tal:content="item/upstream_bugs_percentage/fmt:float/.2" />
1615- <td tal:attributes="class string:amount ${item/upstream_bugs_class}">
1616- <a tal:attributes="href item/upstream_bugs_delta_url"
1617- tal:content="item/upstream_bugs_delta"></a>
1618- </td>
1619- <tal:upstream-in-launchpad
1620- condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD">
1621- <td colspan="4" class="good">&nbsp;</td>
1622- </tal:upstream-in-launchpad>
1623- <tal:upstream-not-in-launchpad
1624- condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD">
1625- <td tal:attributes="class string:amount ${item/watched_bugs_class}"
1626- tal:content="item/watched_bugs" />
1627- <td tal:attributes="class string:amount ${item/watched_bugs_class}"
1628- tal:content="item/watched_bugs_percentage/fmt:float/.2" />
1629- <td tal:attributes="class string:amount ${item/watched_bugs_class}">
1630- <a tal:attributes="href item/watched_bugs_delta_url"
1631- tal:content="item/watched_bugs_delta"></a>
1632- </td>
1633- <td tal:attributes="class string:amount ${item/watched_bugs_class}">
1634- <tal:has-bugs-with-upstream-patches
1635- condition="item/bugs_with_upstream_patches">
1636- <a tal:attributes="href item/bugs_with_upstream_patches_url"
1637- tal:content="item/bugs_with_upstream_patches"></a>
1638- </tal:has-bugs-with-upstream-patches>
1639- <tal:has-no-bugs-with-upstream-patches
1640- condition="not: item/bugs_with_upstream_patches">
1641- -
1642- </tal:has-no-bugs-with-upstream-patches>
1643- </td>
1644- </tal:upstream-not-in-launchpad>
1645- </tr>
1646- </tbody>
1647- <tfoot id="upstream-report-totals">
1648- <tr>
1649- <td colspan="5" class="amount"><b>Totals:</b></td>
1650- <td class="amount"
1651- tal:content="view/total/open_bugs" />
1652- <td class="amount"
1653- tal:content="view/total/triaged_bugs" />
1654- <td class="amount"
1655- tal:content="view/total/triaged_bugs_percentage/fmt:float/.2" />
1656- <td class="amount"
1657- tal:content="view/total/triaged_bugs_delta" />
1658- <td class="amount"
1659- tal:content="view/total/upstream_bugs" />
1660- <td class="amount"
1661- tal:content="view/total/upstream_bugs_percentage/fmt:float/.2" />
1662- <td class="amount"
1663- tal:content="view/total/upstream_bugs_delta" />
1664- <td class="amount"
1665- tal:content="view/total/watched_bugs" />
1666- <td class="amount"
1667- tal:content="view/total/watched_bugs_percentage/fmt:float/.2" />
1668- <td class="amount"
1669- tal:content="view/total/watched_bugs_delta" />
1670- <td class="amount"
1671- tal:content="view/total/bugs_with_upstream_patches" />
1672- </tr>
1673- </tfoot>
1674- </table>
1675-
1676- <div tal:condition="view/data"
1677- align="right"><small>Top <span tal:content="view/data/count:len" />
1678- packages listed.</small></div>
1679- </div>
1680- </div>
1681- </div>
1682-
1683-</distribution-upstream-report>
1684
1685=== modified file 'lib/lp/registry/interfaces/distribution.py'
1686--- lib/lp/registry/interfaces/distribution.py 2012-10-24 09:47:43 +0000
1687+++ lib/lp/registry/interfaces/distribution.py 2012-11-02 01:01:22 +0000
1688@@ -346,10 +346,6 @@
1689 all_distro_archive_ids = Attribute(
1690 "A list containing the IDs of all the non-PPA archives.")
1691
1692- upstream_report_excluded_packages = Attribute(
1693- "A list of the source packages that should not be shown on the "
1694- "upstream report for this Distribution.")
1695-
1696 has_published_binaries = Bool(
1697 title=_("Has Published Binaries"),
1698 description=_("True if this distribution has binaries published "
1699@@ -606,25 +602,6 @@
1700 If the component_name supplied is unknown, None is returned.
1701 """
1702
1703- def getPackagesAndPublicUpstreamBugCounts(limit=50,
1704- exclude_packages=None):
1705- """Return list of tuples of packages, upstreams and public bug counts.
1706-
1707- :param limit: The maximum number of rows to return.
1708- :param exclude_packages: A list of source packages to exclude.
1709- These should be specified as strings which correspond with
1710- SourcePackageName.name.
1711- :returns: [(IDistroSourcePackage, IProduct, int, int, int, int), ...]
1712-
1713- This API is quite specialized; it returns a list of up to limit
1714- tuples containing IProducts and three different bug counts:
1715- - open bugs
1716- - triaged bugs
1717- - open bugs with an upstream task
1718- - open bugs with upstream tasks that are either linked to
1719- bug watches or to products that use_malone.
1720- """
1721-
1722 def getAllowedBugInformationTypes():
1723 """Get the information types that a bug in this distribution can have.
1724
1725
1726=== modified file 'lib/lp/registry/model/distribution.py'
1727--- lib/lp/registry/model/distribution.py 2012-10-24 09:43:58 +0000
1728+++ lib/lp/registry/model/distribution.py 2012-11-02 01:01:22 +0000
1729@@ -81,10 +81,6 @@
1730 from lp.blueprints.model.sprint import HasSprintsMixin
1731 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
1732 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
1733-from lp.bugs.interfaces.bugtask import (
1734- BugTaskStatus,
1735- DB_UNRESOLVED_BUGTASK_STATUSES,
1736- )
1737 from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
1738 from lp.bugs.model.bugtarget import (
1739 BugTargetBase,
1740@@ -116,7 +112,6 @@
1741 MirrorStatus,
1742 )
1743 from lp.registry.interfaces.oopsreferences import IHasOOPSReferences
1744-from lp.registry.interfaces.packaging import PackagingType
1745 from lp.registry.interfaces.person import (
1746 validate_person,
1747 validate_person_or_closed_team,
1748@@ -152,7 +147,6 @@
1749 from lp.services.database.enumcol import EnumCol
1750 from lp.services.database.lpstorm import IStore
1751 from lp.services.database.sqlbase import (
1752- cursor,
1753 quote,
1754 SQLBase,
1755 sqlvalues,
1756@@ -339,8 +333,7 @@
1757
1758 _answers_usage = EnumCol(
1759 dbName="answers_usage", notNull=True,
1760- schema=ServiceUsage,
1761- default=ServiceUsage.UNKNOWN)
1762+ schema=ServiceUsage, default=ServiceUsage.UNKNOWN)
1763
1764 def _get_answers_usage(self):
1765 if self._answers_usage != ServiceUsage.UNKNOWN:
1766@@ -389,8 +382,7 @@
1767
1768 translations_usage = EnumCol(
1769 dbName="translations_usage", notNull=True,
1770- schema=ServiceUsage,
1771- default=ServiceUsage.UNKNOWN)
1772+ schema=ServiceUsage, default=ServiceUsage.UNKNOWN)
1773
1774 @property
1775 def codehosting_usage(self):
1776@@ -1424,170 +1416,6 @@
1777 # Otherwise we defer to the caller.
1778 return None
1779
1780- @property
1781- def upstream_report_excluded_packages(self):
1782- """See `IDistribution`."""
1783- # If the current distribution is Ubuntu, return a specific set
1784- # of excluded packages. Otherwise return an empty list.
1785- ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
1786- if self == ubuntu:
1787- #XXX gmb 2009-02-02: bug 324298
1788- # This needs to be managed in a nicer, non-hardcoded
1789- # fashion.
1790- excluded_packages = [
1791- 'apport',
1792- 'casper',
1793- 'displayconfig-gtk',
1794- 'flashplugin-nonfree',
1795- 'gnome-app-install',
1796- 'nvidia-graphics-drivers-177',
1797- 'software-properties',
1798- 'sun-java6',
1799- 'synaptic',
1800- 'ubiquity',
1801- 'ubuntu-meta',
1802- 'update-manager',
1803- 'update-notifier',
1804- 'usb-creator',
1805- 'usplash',
1806- ]
1807- else:
1808- excluded_packages = []
1809-
1810- return excluded_packages
1811-
1812- def getPackagesAndPublicUpstreamBugCounts(self, limit=50,
1813- exclude_packages=None):
1814- """See `IDistribution`."""
1815- from lp.registry.model.product import Product
1816-
1817- if exclude_packages is None or len(exclude_packages) == 0:
1818- # If exclude_packages is None or an empty list we set it to
1819- # be a list containing a single empty string. This is so
1820- # that we can quote() it properly for the query below ('NOT
1821- # IN ()' is not valid SQL).
1822- exclude_packages = ['']
1823- else:
1824- # Otherwise, listify exclude_packages so that we're not
1825- # trying to quote() a security proxy object.
1826- exclude_packages = list(exclude_packages)
1827-
1828- # This method collects three open bug counts for
1829- # sourcepackagenames in this distribution first, and then caches
1830- # product information before rendering everything into a list of
1831- # tuples.
1832- cur = cursor()
1833- cur.execute("""
1834- SELECT SPN.id, SPN.name,
1835- COUNT(DISTINCT Bugtask.bug) AS open_bugs,
1836- COUNT(DISTINCT CASE WHEN Bugtask.status = %(triaged)s THEN
1837- Bugtask.bug END) AS bugs_triaged,
1838- COUNT(DISTINCT CASE WHEN Bugtask.status IN %(unresolved)s THEN
1839- RelatedBugTask.bug END) AS bugs_affecting_upstream,
1840- COUNT(DISTINCT CASE WHEN Bugtask.status in %(unresolved)s AND
1841- (RelatedBugTask.bugwatch IS NOT NULL OR
1842- RelatedProduct.official_malone IS TRUE) THEN
1843- RelatedBugTask.bug END) AS bugs_with_upstream_bugwatch,
1844- COUNT(DISTINCT CASE WHEN Bugtask.status in %(unresolved)s AND
1845- RelatedBugTask.bugwatch IS NULL AND
1846- RelatedProduct.official_malone IS FALSE AND
1847- Bug.latest_patch_uploaded IS NOT NULL
1848- THEN
1849- RelatedBugTask.bug END)
1850- AS bugs_with_upstream_patches
1851- FROM
1852- SourcePackageName AS SPN
1853- JOIN Bugtask ON SPN.id = Bugtask.sourcepackagename
1854- JOIN Bug ON Bug.id = Bugtask.bug
1855- LEFT OUTER JOIN Bugtask AS RelatedBugtask ON (
1856- RelatedBugtask.bug = Bugtask.bug
1857- AND RelatedBugtask.id != Bugtask.id
1858- AND RelatedBugtask.product IS NOT NULL
1859- AND RelatedBugtask.status != %(invalid)s
1860- )
1861- LEFT OUTER JOIN Product AS RelatedProduct ON (
1862- RelatedBugtask.product = RelatedProduct.id
1863- )
1864- WHERE
1865- Bugtask.distribution = %(distro)s
1866- AND Bugtask.sourcepackagename = spn.id
1867- AND Bugtask.distroseries IS NULL
1868- AND Bugtask.status IN %(unresolved)s
1869- AND Bug.information_type IN %(public_types)s
1870- AND Bug.duplicateof IS NULL
1871- AND spn.name NOT IN %(excluded_packages)s
1872- GROUP BY SPN.id, SPN.name
1873- HAVING COUNT(DISTINCT Bugtask.bug) > 0
1874- ORDER BY open_bugs DESC, SPN.name LIMIT %(limit)s
1875- """ % {'invalid': quote(BugTaskStatus.INVALID),
1876- 'triaged': quote(BugTaskStatus.TRIAGED),
1877- 'limit': limit,
1878- 'distro': self.id,
1879- 'unresolved': quote(DB_UNRESOLVED_BUGTASK_STATUSES),
1880- 'excluded_packages': quote(exclude_packages),
1881- 'public_types': quote(PUBLIC_INFORMATION_TYPES),
1882- })
1883- counts = cur.fetchall()
1884- cur.close()
1885- if not counts:
1886- # If no counts are returned it means that there are no
1887- # source package names in the database -- because the counts
1888- # would just return zero if no bugs existed. And if there
1889- # are no packages are in the database, all bets are off.
1890- return []
1891-
1892- # Next step is to extract which IDs actually show up in the
1893- # output we generate, and cache them.
1894- spn_ids = [item[0] for item in counts]
1895- list(SourcePackageName.select("SourcePackageName.id IN %s"
1896- % sqlvalues(spn_ids)))
1897-
1898- # Finally find out what products are attached to these source
1899- # packages (if any) and cache them too. The ordering of the
1900- # query by Packaging.id ensures that the dictionary holds the
1901- # latest entries for situations where we have multiple entries.
1902- cur = cursor()
1903- cur.execute("""
1904- SELECT Packaging.sourcepackagename, Product.id
1905- FROM Product, Packaging, ProductSeries, DistroSeries
1906- WHERE ProductSeries.product = Product.id AND
1907- DistroSeries.distribution = %s AND
1908- Packaging.distroseries = DistroSeries.id AND
1909- Packaging.productseries = ProductSeries.id AND
1910- Packaging.sourcepackagename IN %s AND
1911- Packaging.packaging = %s AND
1912- Product.active IS TRUE
1913- ORDER BY Packaging.id
1914- """ % sqlvalues(self.id, spn_ids, PackagingType.PRIME))
1915- sources_to_products = dict(cur.fetchall())
1916- cur.close()
1917- if sources_to_products:
1918- # Cache some more information to avoid us having to hit the
1919- # database hard for each product rendered.
1920- list(Product.select("Product.id IN %s" %
1921- sqlvalues(sources_to_products.values()),
1922- prejoins=["bug_supervisor", "bugtracker", "project",
1923- "development_focus.branch"]))
1924-
1925- # Okay, we have all the information good to go, so assemble it
1926- # in a reasonable data structure.
1927- results = []
1928- for (spn_id, spn_name, open_bugs, bugs_triaged,
1929- bugs_affecting_upstream, bugs_with_upstream_bugwatch,
1930- bugs_with_upstream_patches) in counts:
1931- sourcepackagename = SourcePackageName.get(spn_id)
1932- dsp = self.getSourcePackage(sourcepackagename)
1933- if spn_id in sources_to_products:
1934- product_id = sources_to_products[spn_id]
1935- product = Product.get(product_id)
1936- else:
1937- product = None
1938- results.append(
1939- (dsp, product, open_bugs, bugs_triaged,
1940- bugs_affecting_upstream, bugs_with_upstream_bugwatch,
1941- bugs_with_upstream_patches))
1942- return results
1943-
1944 def getAllowedBugInformationTypes(self):
1945 """See `IDistribution.`"""
1946 return FREE_INFORMATION_TYPES