Merge lp:~thumper/launchpad/different-cloud into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11475
Proposed branch: lp:~thumper/launchpad/different-cloud
Merge into: lp:launchpad
Diff against target: 685 lines (+248/-193)
12 files modified
lib/lp/code/browser/bazaar.py (+27/-27)
lib/lp/code/configure.zcml (+1/-1)
lib/lp/code/interfaces/branch.py (+6/-3)
lib/lp/code/model/branch.py (+0/-31)
lib/lp/code/model/branchcloud.py (+52/-0)
lib/lp/code/model/revision.py (+1/-2)
lib/lp/code/model/tests/test_branchcloud.py (+65/-95)
lib/lp/code/stories/branches/xx-bazaar-home.txt (+0/-1)
lib/lp/code/stories/branches/xx-branch-tag-cloud.txt (+15/-26)
lib/lp/code/tests/helpers.py (+40/-0)
lib/lp/code/tests/test_helpers.py (+40/-0)
lib/lp/testing/tests/test_standard_test_template.py (+1/-7)
To merge this branch: bzr merge lp:~thumper/launchpad/different-cloud
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+34146@code.launchpad.net

Commit message

Fix the branch cloud timeouts.

Description of the change

Change what we show on the tag cloud for branches.

Instead of showing general total branch counts (hosted and mirrored), we instead show projects that have recent commits (recent being in the last 30 days and using the revision cache table).

By using the revision cache, we get *much* faster queries, and this should completely kill any time-outs we are getting for this page (crosses-fingers).

Tests:
  xx-branch-tag-cloud
  code.tests.test_helpers
  code.model.tests.test_branchcloud

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

doit.

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

We may need to adjust this to normalise in some fashion but this is certainly an improvement.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/browser/bazaar.py'
--- lib/lp/code/browser/bazaar.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/browser/bazaar.py 2010-08-31 01:22:44 +0000
@@ -93,17 +93,17 @@
9393
94class ProductInfo:94class ProductInfo:
9595
96 def __init__(96 def __init__(self, name, commits, author_count, size, elapsed):
97 self, product_name, num_branches, branch_size, elapsed):97 self.name = name
98 self.name = product_name98 self.url = '/' + name
99 self.url = '/' + product_name99 self.commits = commits
100 self.num_branches = num_branches100 self.author_count = author_count
101 self.branch_size = branch_size101 self.size = size
102 self.elapsed_since_commit = elapsed102 self.elapsed_since_commit = elapsed
103103
104 @property104 @property
105 def branch_class(self):105 def tag_class(self):
106 return "cloud-size-%s" % self.branch_size106 return "cloud-size-%s" % self.size
107107
108 @property108 @property
109 def time_darkness(self):109 def time_darkness(self):
@@ -111,30 +111,32 @@
111 return "light"111 return "light"
112 if self.elapsed_since_commit.days < 7:112 if self.elapsed_since_commit.days < 7:
113 return "dark"113 return "dark"
114 if self.elapsed_since_commit.days < 31:114 if self.elapsed_since_commit.days < 14:
115 return "medium"115 return "medium"
116 return "light"116 return "light"
117117
118 @property118 @property
119 def html_class(self):119 def html_class(self):
120 return "%s cloud-%s" % (self.branch_class, self.time_darkness)120 return "%s cloud-%s" % (self.tag_class, self.time_darkness)
121121
122 @property122 @property
123 def html_title(self):123 def html_title(self):
124 if self.num_branches == 1:124 if self.commits == 1:
125 size = "1 branch"125 size = "1 commit"
126 else:126 else:
127 size = "%d branches" % self.num_branches127 size = "%d commits" % self.commits
128 if self.elapsed_since_commit is None:128 if self.author_count == 1:
129 commit = "no commits yet"129 who = "1 person"
130 elif self.elapsed_since_commit.days == 0:130 else:
131 who = "%s people" % self.author_count
132 if self.elapsed_since_commit.days == 0:
131 commit = "last commit less than a day old"133 commit = "last commit less than a day old"
132 elif self.elapsed_since_commit.days == 1:134 elif self.elapsed_since_commit.days == 1:
133 commit = "last commit one day old"135 commit = "last commit one day old"
134 else:136 else:
135 commit = (137 commit = (
136 "last commit %d days old" % self.elapsed_since_commit.days)138 "last commit %d days old" % self.elapsed_since_commit.days)
137 return "%s, %s" % (size, commit)139 return "%s by %s, %s" % (size, who, commit)
138140
139141
140class BazaarProjectsRedirect(LaunchpadView):142class BazaarProjectsRedirect(LaunchpadView):
@@ -177,7 +179,9 @@
177 # is the first item of the tuple returned, and is guaranteed to be179 # is the first item of the tuple returned, and is guaranteed to be
178 # unique by the sql query.180 # unique by the sql query.
179 product_info = sorted(181 product_info = sorted(
180 list(getUtility(IBranchCloud).getProductsWithInfo(num_products)))182 getUtility(IBranchCloud).getProductsWithInfo(num_products))
183 if len(product_info) == 0:
184 return
181 now = datetime.today()185 now = datetime.today()
182 counts = sorted(zip(*product_info)[1])186 counts = sorted(zip(*product_info)[1])
183 size_mapping = {187 size_mapping = {
@@ -187,14 +191,10 @@
187 0.8: 'large',191 0.8: 'large',
188 1.0: 'largest',192 1.0: 'largest',
189 }193 }
190 num_branches_to_size = self._make_distribution_map(194 num_commits_to_size = self._make_distribution_map(
191 counts, size_mapping)195 counts, size_mapping)
192196
193 for product_name, num_branches, last_revision_date in product_info:197 for name, commits, author_count, last_revision_date in product_info:
194 # Projects with no branches are not interesting.198 size = num_commits_to_size[commits]
195 if num_branches == 0:
196 continue
197 branch_size = num_branches_to_size[num_branches]
198 elapsed = now - last_revision_date199 elapsed = now - last_revision_date
199 yield ProductInfo(200 yield ProductInfo(name, commits, author_count, size, elapsed)
200 product_name, num_branches, branch_size, elapsed)
201201
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2010-08-19 03:01:51 +0000
+++ lib/lp/code/configure.zcml 2010-08-31 01:22:44 +0000
@@ -568,7 +568,7 @@
568 <allow interface="lp.code.interfaces.branch.IBranchDelta"/>568 <allow interface="lp.code.interfaces.branch.IBranchDelta"/>
569 </class>569 </class>
570 <securedutility570 <securedutility
571 class="lp.code.model.branch.BranchCloud"571 class="lp.code.model.branchcloud.BranchCloud"
572 provides="lp.code.interfaces.branch.IBranchCloud">572 provides="lp.code.interfaces.branch.IBranchCloud">
573 <allow interface="lp.code.interfaces.branch.IBranchCloud"/>573 <allow interface="lp.code.interfaces.branch.IBranchCloud"/>
574 </securedutility>574 </securedutility>
575575
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/interfaces/branch.py 2010-08-31 01:22:44 +0000
@@ -1277,9 +1277,12 @@
1277 """1277 """
12781278
1279 def getProductsWithInfo(num_products=None):1279 def getProductsWithInfo(num_products=None):
1280 """Get products with their branch activity information.1280 """Get products with their recent activity information.
12811281
1282 :return: a `ResultSet` of (product, num_branches, last_revision_date).1282 The counts are for the last 30 days.
1283
1284 :return: a `ResultSet` of (product, num_commits, num_authors,
1285 last_revision_date).
1283 """1286 """
12841287
12851288
12861289
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/branch.py 2010-08-31 01:22:44 +0000
@@ -1306,37 +1306,6 @@
1306 return branches1306 return branches
13071307
13081308
1309class BranchCloud:
1310 """See `IBranchCloud`."""
1311
1312 def getProductsWithInfo(self, num_products=None, store_flavor=None):
1313 """See `IBranchCloud`."""
1314 # Circular imports are fun.
1315 from lp.registry.model.product import Product
1316 # It doesn't matter if this query is even a whole day out of date, so
1317 # use the slave store by default.
1318 if store_flavor is None:
1319 store_flavor = SLAVE_FLAVOR
1320 store = getUtility(IStoreSelector).get(MAIN_STORE, store_flavor)
1321 # Get all products, the count of all hosted & mirrored branches and
1322 # the last revision date.
1323 result = store.find(
1324 (Product.name, Count(Branch.id), Max(Revision.revision_date)),
1325 Branch.private == False,
1326 Branch.product == Product.id,
1327 Or(Branch.branch_type == BranchType.HOSTED,
1328 Branch.branch_type == BranchType.MIRRORED),
1329 Branch.last_scanned_id == Revision.revision_id)
1330 result = result.group_by(Product.name)
1331 result = result.order_by(Desc(Count(Branch.id)))
1332 if num_products:
1333 result.config(limit=num_products)
1334 # XXX: JonathanLange 2009-02-10: The revision date in the result set
1335 # isn't timezone-aware. Not sure why this is. Doesn't matter too much
1336 # for the purposes of cloud calculation though.
1337 return result
1338
1339
1340def update_trigger_modified_fields(branch):1309def update_trigger_modified_fields(branch):
1341 """Make the trigger updated fields reload when next accessed."""1310 """Make the trigger updated fields reload when next accessed."""
1342 # Not all the fields are exposed through the interface, and some are read1311 # Not all the fields are exposed through the interface, and some are read
13431312
=== added file 'lib/lp/code/model/branchcloud.py'
--- lib/lp/code/model/branchcloud.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/branchcloud.py 2010-08-31 01:22:44 +0000
@@ -0,0 +1,52 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""The implementation of the branch cloud."""
5
6__metaclass__ = type
7__all__ = [
8 'BranchCloud',
9 ]
10
11
12from datetime import datetime, timedelta
13
14import pytz
15from storm.expr import Alias, Func
16from storm.locals import Count, Desc, Max, Not
17from zope.interface import classProvides
18
19from canonical.launchpad.interfaces.lpstorm import ISlaveStore
20
21from lp.code.interfaces.branch import IBranchCloud
22from lp.code.model.revision import RevisionCache
23from lp.registry.model.product import Product
24
25
26class BranchCloud:
27 """See `IBranchCloud`."""
28
29 classProvides(IBranchCloud)
30
31 @staticmethod
32 def getProductsWithInfo(num_products=None):
33 """See `IBranchCloud`."""
34 distinct_revision_author = Func(
35 "distinct", RevisionCache.revision_author_id)
36 commits = Alias(Count(RevisionCache.revision_id))
37 epoch = datetime.now(pytz.UTC) - timedelta(days=30)
38 # It doesn't matter if this query is even a whole day out of date, so
39 # use the slave store.
40 result = ISlaveStore(RevisionCache).find(
41 (Product.name,
42 commits,
43 Count(distinct_revision_author),
44 Max(RevisionCache.revision_date)),
45 RevisionCache.product == Product.id,
46 Not(RevisionCache.private),
47 RevisionCache.revision_date >= epoch)
48 result = result.group_by(Product.name)
49 result = result.order_by(Desc(commits))
50 if num_products:
51 result.config(limit=num_products)
52 return result
053
=== modified file 'lib/lp/code/model/revision.py'
--- lib/lp/code/model/revision.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/revision.py 2010-08-31 01:22:44 +0000
@@ -609,8 +609,7 @@
609 revision_author_id = Int(name='revision_author', allow_none=False)609 revision_author_id = Int(name='revision_author', allow_none=False)
610 revision_author = Reference(revision_author_id, 'RevisionAuthor.id')610 revision_author = Reference(revision_author_id, 'RevisionAuthor.id')
611611
612 revision_date = DateTime(612 revision_date = UtcDateTimeCol(notNull=True)
613 name='revision_date', allow_none=False, tzinfo=pytz.UTC)
614613
615 product_id = Int(name='product', allow_none=True)614 product_id = Int(name='product', allow_none=True)
616 product = Reference(product_id, 'Product.id')615 product = Reference(product_id, 'Product.id')
617616
=== modified file 'lib/lp/code/model/tests/test_branchcloud.py'
--- lib/lp/code/model/tests/test_branchcloud.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/tests/test_branchcloud.py 2010-08-31 01:22:44 +0000
@@ -5,27 +5,22 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8from datetime import (8from datetime import datetime, timedelta
9 datetime,9import transaction
10 timedelta,
11 )
12import unittest10import unittest
1311
14import pytz12import pytz
13from storm.locals import Store
15from zope.component import getUtility14from zope.component import getUtility
16from zope.security.proxy import removeSecurityProxy
1715
18from canonical.launchpad.testing.databasehelpers import (16from canonical.launchpad.testing.databasehelpers import (
19 remove_all_sample_data_branches,17 remove_all_sample_data_branches,
20 )18 )
21from canonical.launchpad.webapp.interfaces import MASTER_FLAVOR
22from canonical.testing.layers import DatabaseFunctionalLayer19from canonical.testing.layers import DatabaseFunctionalLayer
23from lp.code.enums import BranchType
24from lp.code.interfaces.branch import IBranchCloud20from lp.code.interfaces.branch import IBranchCloud
25from lp.testing import (21from lp.code.model.revision import RevisionCache
26 TestCaseWithFactory,22from lp.code.tests.helpers import make_project_branch_with_revisions
27 time_counter,23from lp.testing import TestCaseWithFactory, time_counter
28 )
2924
3025
31class TestBranchCloud(TestCaseWithFactory):26class TestBranchCloud(TestCaseWithFactory):
@@ -39,40 +34,40 @@
3934
40 def getProductsWithInfo(self, num_products=None):35 def getProductsWithInfo(self, num_products=None):
41 """Get product cloud information."""36 """Get product cloud information."""
42 # We use the MASTER_FLAVOR so that data changes made in these tests37 # Since we use the slave store to get the information, we need to
43 # are visible to the query in getProductsWithInfo. The default38 # commit the transaction to make the information visible to the slave.
44 # implementation uses the SLAVE_FLAVOR.39 transaction.commit()
45 return self._branch_cloud.getProductsWithInfo(40 cloud_info = self._branch_cloud.getProductsWithInfo(num_products)
46 num_products, store_flavor=MASTER_FLAVOR)41 # The last commit time is timezone unaware as the storm Max function
42 # doesn't take into account the type that it is aggregating, so whack
43 # the UTC tz on it here for easier comparing in the tests.
44 def add_utc(value):
45 return value.replace(tzinfo=pytz.UTC)
46 return [
47 (name, commits, authors, add_utc(last_commit))
48 for name, commits, authors, last_commit in cloud_info]
4749
48 def makeBranch(self, product=None, branch_type=None,50 def makeBranch(self, product=None, last_commit_date=None, private=False,
49 last_commit_date=None, private=False):51 revision_count=None):
50 """Make a product branch with a particular last commit date"""52 """Make a product branch with a particular last commit date"""
51 revision_count = 553 if revision_count is None:
54 revision_count = 5
52 delta = timedelta(days=1)55 delta = timedelta(days=1)
53 if last_commit_date is None:56 if last_commit_date is None:
54 date_generator = None57 # By default we create revisions that are within the last 30 days.
58 date_generator = time_counter(
59 datetime.now(pytz.UTC) - timedelta(days=25), delta)
55 else:60 else:
56 start_date = last_commit_date - delta * (revision_count - 1)61 start_date = last_commit_date - delta * (revision_count - 1)
57 # The output of getProductsWithInfo doesn't include timezone
58 # information -- not sure why. To make the tests a little clearer,
59 # this method expects last_commit_date to be a naive datetime that
60 # can be compared directly with the output of getProductsWithInfo.
61 start_date = start_date.replace(tzinfo=pytz.UTC)
62 date_generator = time_counter(start_date, delta)62 date_generator = time_counter(start_date, delta)
63 branch = self.factory.makeProductBranch(63 branch = make_project_branch_with_revisions(
64 product=product, branch_type=branch_type, private=private)64 self.factory, date_generator, product, private, revision_count)
65 if branch_type != BranchType.REMOTE:
66 self.factory.makeRevisionsForBranch(
67 removeSecurityProxy(branch), count=revision_count,
68 date_generator=date_generator)
69 return branch65 return branch
7066
71 def test_empty_with_no_branches(self):67 def test_empty_with_no_branches(self):
72 # getProductsWithInfo returns an empty result set if there are no68 # getProductsWithInfo returns an empty result set if there are no
73 # branches in the database.69 # branches in the database.
74 products_with_info = self.getProductsWithInfo()70 self.assertEqual([], self.getProductsWithInfo())
75 self.assertEqual([], list(products_with_info))
7671
77 def test_empty_products_not_counted(self):72 def test_empty_products_not_counted(self):
78 # getProductsWithInfo doesn't include products that don't have any73 # getProductsWithInfo doesn't include products that don't have any
@@ -80,76 +75,51 @@
80 #75 #
81 # Note that this is tested implicitly by test_empty_with_no_branches,76 # Note that this is tested implicitly by test_empty_with_no_branches,
82 # since there are such products in the sample data.77 # since there are such products in the sample data.
83 product = self.factory.makeProduct()78 self.factory.makeProduct()
84 products_with_info = self.getProductsWithInfo()79 self.assertEqual([], self.getProductsWithInfo())
85 self.assertEqual([], list(products_with_info))
8680
87 def test_empty_branches_not_counted(self):81 def test_empty_branches_not_counted(self):
88 # getProductsWithInfo doesn't consider branches that lack revision82 # getProductsWithInfo doesn't consider branches that lack revision
89 # data, 'empty branches', to contribute to the count of branches on a83 # data, 'empty branches', to contribute to the count of branches on a
90 # product.84 # product.
91 branch = self.factory.makeProductBranch()85 self.factory.makeProductBranch()
92 products_with_info = self.getProductsWithInfo()86 self.assertEqual([], self.getProductsWithInfo())
93 self.assertEqual([], list(products_with_info))
94
95 def test_import_branches_not_counted(self):
96 # getProductsWithInfo doesn't consider imported branches to contribute
97 # to the count of branches on a product.
98 branch = self.makeBranch(branch_type=BranchType.IMPORTED)
99 products_with_info = self.getProductsWithInfo()
100 self.assertEqual([], list(products_with_info))
101
102 def test_remote_branches_not_counted(self):
103 # getProductsWithInfo doesn't consider remote branches to contribute
104 # to the count of branches on a product.
105 branch = self.makeBranch(branch_type=BranchType.REMOTE)
106 products_with_info = self.getProductsWithInfo()
107 self.assertEqual([], list(products_with_info))
10887
109 def test_private_branches_not_counted(self):88 def test_private_branches_not_counted(self):
110 # getProductsWithInfo doesn't count private branches.89 # getProductsWithInfo doesn't count private branches.
111 branch = self.makeBranch(private=True)90 self.makeBranch(private=True)
112 products_with_info = self.getProductsWithInfo()91 self.assertEqual([], self.getProductsWithInfo())
113 self.assertEqual([], list(products_with_info))92
11493 def test_revisions_counted(self):
115 def test_hosted_and_mirrored_counted(self):94 # getProductsWithInfo includes products that public revisions.
116 # getProductsWithInfo includes products that have hosted or mirrored95 last_commit_date = datetime.now(pytz.UTC) - timedelta(days=5)
117 # branches with revisions.96 product = self.factory.makeProduct()
118 product = self.factory.makeProduct()
119 self.makeBranch(product=product, branch_type=BranchType.HOSTED)
120 last_commit_date = datetime(2007, 1, 5)
121 self.makeBranch(
122 product=product, branch_type=BranchType.MIRRORED,
123 last_commit_date=last_commit_date)
124 products_with_info = self.getProductsWithInfo()
125 self.assertEqual(
126 [(product.name, 2, last_commit_date)], list(products_with_info))
127
128 def test_includes_products_with_branches_with_revisions(self):
129 # getProductsWithInfo includes all products that have branches with
130 # revisions.
131 last_commit_date = datetime(2008, 12, 25)
132 branch = self.makeBranch(last_commit_date=last_commit_date)
133 products_with_info = self.getProductsWithInfo()
134 self.assertEqual(
135 [(branch.product.name, 1, last_commit_date)],
136 list(products_with_info))
137
138 def test_uses_latest_revision_date(self):
139 # getProductsWithInfo uses the most recent revision date from all the
140 # branches in that product.
141 product = self.factory.makeProduct()
142 self.makeBranch(
143 product=product, last_commit_date=datetime(2008, 12, 25))
144 last_commit_date = datetime(2009, 01, 01)
145 self.makeBranch(product=product, last_commit_date=last_commit_date)97 self.makeBranch(product=product, last_commit_date=last_commit_date)
146 products_with_info = self.getProductsWithInfo()98 self.assertEqual(
147 self.assertEqual(99 [(product.name, 5, 1, last_commit_date)],
148 [(product.name, 2, last_commit_date)], list(products_with_info))100 self.getProductsWithInfo())
149101
150 def test_sorted_by_branch_count(self):102 def test_only_recent_revisions_counted(self):
103 # If the revision cache has revisions for the project, but they are
104 # over 30 days old, we don't count them.
105 product = self.factory.makeProduct()
106 date_generator = time_counter(
107 datetime.now(pytz.UTC) - timedelta(days=33),
108 delta=timedelta(days=2))
109 store = Store.of(product)
110 for i in range(4):
111 revision = self.factory.makeRevision(
112 revision_date=date_generator.next())
113 cache = RevisionCache(revision)
114 cache.product = product
115 store.add(cache)
116 self.assertEqual(
117 [(product.name, 2, 2, revision.revision_date)],
118 self.getProductsWithInfo())
119
120 def test_sorted_by_commit_count(self):
151 # getProductsWithInfo returns a result set sorted so that the products121 # getProductsWithInfo returns a result set sorted so that the products
152 # with the most branches come first.122 # with the most commits come first.
153 product1 = self.factory.makeProduct()123 product1 = self.factory.makeProduct()
154 for i in range(3):124 for i in range(3):
155 self.makeBranch(product=product1)125 self.makeBranch(product=product1)
@@ -158,7 +128,7 @@
158 self.makeBranch(product=product2)128 self.makeBranch(product=product2)
159 self.assertEqual(129 self.assertEqual(
160 [product2.name, product1.name],130 [product2.name, product1.name],
161 [name for name, count, last_commit131 [name for name, commits, count, last_commit
162 in self.getProductsWithInfo()])132 in self.getProductsWithInfo()])
163133
164 def test_limit(self):134 def test_limit(self):
@@ -176,7 +146,7 @@
176 self.makeBranch(product=product3)146 self.makeBranch(product=product3)
177 self.assertEqual(147 self.assertEqual(
178 [product3.name, product2.name],148 [product3.name, product2.name],
179 [name for name, count, last_commit149 [name for name, commits, count, last_commit
180 in self.getProductsWithInfo(num_products=2)])150 in self.getProductsWithInfo(num_products=2)])
181151
182152
183153
=== modified file 'lib/lp/code/stories/branches/xx-bazaar-home.txt'
--- lib/lp/code/stories/branches/xx-bazaar-home.txt 2010-07-22 12:02:30 +0000
+++ lib/lp/code/stories/branches/xx-bazaar-home.txt 2010-08-31 01:22:44 +0000
@@ -31,7 +31,6 @@
31 >>> preview = find_tag_by_id(browser.contents, 'project-cloud-preview')31 >>> preview = find_tag_by_id(browser.contents, 'project-cloud-preview')
32 >>> print extract_text(preview)32 >>> print extract_text(preview)
33 Projects with active branches33 Projects with active branches
34 firefox
35 see all projects&#8230;34 see all projects&#8230;
3635
37 >>> print preview.fetch('a')[-1]['href']36 >>> print preview.fetch('a')[-1]['href']
3837
=== modified file 'lib/lp/code/stories/branches/xx-branch-tag-cloud.txt'
--- lib/lp/code/stories/branches/xx-branch-tag-cloud.txt 2010-04-01 13:31:28 +0000
+++ lib/lp/code/stories/branches/xx-branch-tag-cloud.txt 2010-08-31 01:22:44 +0000
@@ -1,8 +1,20 @@
1= Projects with active branches =1Projects with active branches
2=============================
23
3The tag cloud of projects is one way in which the number and scope of available4The tag cloud of projects is one way in which the number and scope of available
4bazaar branches is shown to the user.5bazaar branches is shown to the user.
56
7 >>> login(ANONYMOUS)
8 >>> from lp.code.tests.helpers import make_project_cloud_data
9 >>> from datetime import datetime, timedelta
10 >>> import pytz
11 >>> now = datetime.now(pytz.UTC)
12 >>> make_project_cloud_data(factory, [
13 ... ('wibble', 35, 2, now - timedelta(days=2)),
14 ... ('linux', 110, 1, now - timedelta(days=8)),
15 ... ])
16 >>> logout()
17
6 >>> anon_browser.open("http://code.launchpad.dev/projects")18 >>> anon_browser.open("http://code.launchpad.dev/projects")
7 >>> print anon_browser.title19 >>> print anon_browser.title
8 Projects with active branches20 Projects with active branches
@@ -14,28 +26,5 @@
14 >>> tags = find_tag_by_id(anon_browser.contents, 'project-tags')26 >>> tags = find_tag_by_id(anon_browser.contents, 'project-tags')
15 >>> for anchor in tags.fetch('a'):27 >>> for anchor in tags.fetch('a'):
16 ... print anchor.renderContents(), anchor['class']28 ... print anchor.renderContents(), anchor['class']
17 firefox cloud-size-largest cloud-light29 linux cloud-size-largest cloud-medium
1830 wibble cloud-size-smallest cloud-dark
19If the project is using bzr as its main branch repository then the class is
20different from the other projects. This allows the CSS to show these projects
21in a different colour.
22
23Update firefox to officially use codehosting:
24
25 >>> from lp.registry.interfaces.product import IProductSet
26 >>> from zope.component import getUtility
27 >>> login('admin@canonical.com')
28 >>> firefox = getUtility(IProductSet).getByName('firefox')
29 >>> firefox.development_focus.branch = factory.makeBranch(
30 ... product=firefox)
31 >>> logout()
32
33The class for firefox in the project tag cloud will now show 'highlight' rather
34than shade.
35
36 >>> anon_browser.open("http://code.launchpad.dev/projects")
37 >>> tags = find_tag_by_id(anon_browser.contents, 'project-tags')
38 >>> for anchor in tags.fetch('a'):
39 ... if anchor.renderContents() == 'firefox':
40 ... print anchor['class']
41 cloud-size-largest cloud-light
4231
=== modified file 'lib/lp/code/tests/helpers.py'
--- lib/lp/code/tests/helpers.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/tests/helpers.py 2010-08-31 01:22:44 +0000
@@ -9,12 +9,15 @@
9 'make_erics_fooix_project',9 'make_erics_fooix_project',
10 'make_linked_package_branch',10 'make_linked_package_branch',
11 'make_official_package_branch',11 'make_official_package_branch',
12 'make_project_branch_with_revisions',
13 'make_project_cloud_data',
12 ]14 ]
1315
1416
15from datetime import timedelta17from datetime import timedelta
16from difflib import unified_diff18from difflib import unified_diff
17from itertools import count19from itertools import count
20import transaction
1821
19from zope.component import getUtility22from zope.component import getUtility
20from zope.security.proxy import (23from zope.security.proxy import (
@@ -27,6 +30,7 @@
27 IBranchMergeProposalJobSource,30 IBranchMergeProposalJobSource,
28 )31 )
29from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch32from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
33from lp.code.interfaces.revision import IRevisionSet
30from lp.code.interfaces.seriessourcepackagebranch import (34from lp.code.interfaces.seriessourcepackagebranch import (
31 IMakeOfficialBranchLinks,35 IMakeOfficialBranchLinks,
32 )36 )
@@ -256,3 +260,39 @@
256 ICanHasLinkedBranch(suite_sourcepackage).setBranch,260 ICanHasLinkedBranch(suite_sourcepackage).setBranch,
257 branch, registrant)261 branch, registrant)
258 return branch262 return branch
263
264
265def make_project_branch_with_revisions(factory, date_generator, product=None,
266 private=None, revision_count=None):
267 """Make a new branch with revisions."""
268 if revision_count is None:
269 revision_count = 5
270 branch = factory.makeProductBranch(product=product, private=private)
271 naked_branch = removeSecurityProxy(branch)
272 factory.makeRevisionsForBranch(
273 naked_branch, count=revision_count, date_generator=date_generator)
274 # The code that updates the revision cache doesn't need to care about
275 # the privacy of the branch.
276 getUtility(IRevisionSet).updateRevisionCacheForBranch(naked_branch)
277 return branch
278
279
280def make_project_cloud_data(factory, details):
281 """Make test data to populate the project cloud.
282
283 Details is a list of tuples containing:
284 (project-name, num_commits, num_authors, last_commit)
285 """
286 delta = timedelta(seconds=1)
287 for project_name, num_commits, num_authors, last_commit in details:
288 project = factory.makeProduct(name=project_name)
289 start_date = last_commit - delta * (num_commits - 1)
290 gen = time_counter(start_date, delta)
291 commits_each = num_commits / num_authors
292 for committer in range(num_authors - 1):
293 make_project_branch_with_revisions(
294 factory, gen, project, commits_each)
295 num_commits -= commits_each
296 make_project_branch_with_revisions(
297 factory, gen, project, revision_count=num_commits)
298 transaction.commit()
259299
=== added file 'lib/lp/code/tests/test_helpers.py'
--- lib/lp/code/tests/test_helpers.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/tests/test_helpers.py 2010-08-31 01:22:44 +0000
@@ -0,0 +1,40 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test the code test helpers found in helpers.py."""
5
6__metaclass__ = type
7
8from datetime import datetime, timedelta
9import pytz
10
11from zope.component import getUtility
12
13from canonical.testing import DatabaseFunctionalLayer
14
15from lp.code.interfaces.branchcollection import IAllBranches
16from lp.code.tests.helpers import make_project_cloud_data
17from lp.registry.interfaces.product import IProductSet
18from lp.testing import TestCaseWithFactory
19
20
21class TestMakeProjectCloudData(TestCaseWithFactory):
22 # Make sure that make_project_cloud_data works.
23
24 layer = DatabaseFunctionalLayer
25
26 def test_single_project(self):
27 # Make a single project with one commit from one person.
28 now = datetime.now(pytz.UTC)
29 commit_time = now - timedelta(days=2)
30 make_project_cloud_data(self.factory, [
31 ('fooix', 1, 1, commit_time),
32 ])
33 # Make sure we have a new project called fooix.
34 fooix = getUtility(IProductSet).getByName('fooix')
35 self.assertIsNot(None, fooix)
36 # There should be one branch with one commit.
37 [branch] = list(
38 getUtility(IAllBranches).inProduct(fooix).getBranches())
39 self.assertEqual(1, branch.revision_count)
40 self.assertEqual(commit_time, branch.getTipRevision().revision_date)
041
=== modified file 'lib/lp/testing/tests/test_standard_test_template.py'
--- lib/lp/testing/tests/test_standard_test_template.py 2010-08-05 14:15:56 +0000
+++ lib/lp/testing/tests/test_standard_test_template.py 2010-08-31 01:22:44 +0000
@@ -5,10 +5,8 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import unittest
9
10from canonical.testing import DatabaseFunctionalLayer8from canonical.testing import DatabaseFunctionalLayer
11from lp.testing import TestCase9from lp.testing import TestCase # or TestCaseWithFactory
1210
1311
14class TestSomething(TestCase):12class TestSomething(TestCase):
@@ -24,7 +22,3 @@
2422
25 # XXX: Assertions take expected value first, actual value second.23 # XXX: Assertions take expected value first, actual value second.
26 self.assertEqual(4, 2 + 2)24 self.assertEqual(4, 2 + 2)
27
28
29def test_suite():
30 return unittest.TestLoader().loadTestsFromName(__name__)