Merge lp:~bac/launchpad/bug-643538-code into lp:launchpad
- bug-643538-code
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Brad Crittenden | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 11656 | ||||
Proposed branch: | lp:~bac/launchpad/bug-643538-code | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1761 lines (+770/-224) 22 files modified
lib/canonical/launchpad/icing/style.css (+19/-9) lib/lp/code/browser/branch.py (+35/-26) lib/lp/code/browser/branchlisting.py (+42/-13) lib/lp/code/browser/configure.zcml (+13/-1) lib/lp/code/browser/tests/test_branch.py (+6/-3) lib/lp/code/browser/tests/test_product.py (+166/-15) lib/lp/code/stories/branches/xx-branch-deletion.txt (+29/-15) lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt (+6/-6) lib/lp/code/stories/branches/xx-creating-branches.txt (+47/-3) lib/lp/code/stories/branches/xx-person-branches.txt (+2/-2) lib/lp/code/stories/branches/xx-private-branch-listings.txt (+20/-10) lib/lp/code/stories/branches/xx-product-branches.txt (+114/-40) lib/lp/code/stories/codeimport/xx-create-codeimport.txt (+21/-1) lib/lp/code/templates/branch-listing.pt (+4/-4) lib/lp/code/templates/product-branch-summary.pt (+70/-29) lib/lp/code/templates/product-branches.pt (+55/-29) lib/lp/code/templates/product-portlet-codestatistics-content.pt (+55/-0) lib/lp/code/templates/product-portlet-codestatistics.pt (+11/-0) lib/lp/registry/browser/product.py (+5/-10) lib/lp/registry/browser/tests/pillar-views.txt (+11/-0) lib/lp/registry/model/product.py (+2/-1) lib/lp/registry/tests/test_service_usage.py (+37/-7) |
||||
To merge this branch: | bzr merge lp:~bac/launchpad/bug-643538-code | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guilherme Salgado (community) | Approve | ||
Curtis Hovey (community) | code | Approve | |
Matthew Revell (community) | text | Approve | |
Gavin Panella (community) | code | Approve | |
Matthew Revell | text | Pending | |
Review via email: mp+36377@code.launchpad.net |
Commit message
State the project's code usage, either Launchpad or elsewhere.
Description of the change
= Summary =
Launchpad must state the project's code usage. The product +code-index page needs to clearly state if the project is using Launchpad. If it isn't it should refer to where it is hosted.
== Proposed fix ==
Conditionally present the correct message. Also moved some of the code summary information into portlets in order to be more like the other application areas.
== Pre-implementation notes ==
Chats with Curtis, Edwin, and Jon.
== Implementation details ==
As above.
== Tests ==
Basically all of the code tests need to run:
bin/test -vvm lp.code
== Demo and Q/A ==
Create a new project and visit:
https:/
= Launchpad lint =
The lint issues are pretty much intractable. I cleaned up a lot.
Linting changed files:
lib/lp/
lib/canonical
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
./lib/lp/
83: want exceeds 78 characters.
93: want exceeds 78 characters.
./lib/lp/
409: Line exceeds 78 characters.
Brad Crittenden (bac) wrote : | # |
Brad Crittenden (bac) wrote : | # |
I've noticed on the screen where no code has been registered[1] there is a link to +set-branch (Configure code hosting) but no link to +add-branch (seen later as "Register a branch"). This seems like an oversight because using the former you cannot mirror a branch, only set in Launchpad and import.
[1] http://
Gavin Panella (allenap) wrote : | # |
Cool branch :)
The comments I have are all really minor.
Gavin.
[1]
def getDistroDevelS
- """Since distribution.
+ """distribution
try:
return self._distro_
except KeyError:
result = distribution.
return result
I think your change doesn't describe what the code does... but that's
because the code is broken! The following line is the culprit:
Every time the method is called the cache gets reset. I think this
line can be removed.
[2]
+ self.branch = self.developmen
development_
be a property so that development_
it is needed.
[3]
+ self.assertEqua
...
+ self.assertNotE
I think this (and others that test against None) should be done with
assertIs(None, ...). Can't remember what difference is makes though :)
[4]
+ def test_portlets_
+ # If the BranchUsage is HOSTED then the portlets are shown.
s/HOSTED/EXTERNAL/
[5]
+ <a tal:attributes=
Doesn't really matter, but it might be clearer expressed as:
<a tal:attributes=
[6]
+ <div tal:condition=
+ <p>
+ New branches you create for <tal:name replace=
+ are <strong>
+ </p>
+ </div>
+ <div tal:condition=
+ <p>
+ New branches you create for <tal:name replace=
+ are <strong>
+ </p>
+ </div>
The tal:condition could go on the subordinate <p> tags, then there's
only one <div>.
[7]
+ <tal:comment condition=
+ The view/*_
+ template can be rendered by a view that does not have count
+ information available.
+ </tal:comment>
There aren't any view/*_
go.
[8]
+ configured = sum([1 for v in config_
Don't need the list comprehension.
Brad Crittenden (bac) wrote : | # |
On Sep 23, 2010, at 11:49 , Gavin Panella wrote:
> Review: Approve code
> Cool branch :)
>
> The comments I have are all really minor.
Thanks for the helpful review Gavin.
>
> Gavin.
>
>
> [1]
>
> def getDistroDevelS
> - """Since distribution.
> + """distribution
> self._distro_
> try:
> return self._distro_
> except KeyError:
> result = distribution.
> self._distro_
> return result
>
> I think your change doesn't describe what the code does... but that's
> because the code is broken! The following line is the culprit:
>
> self._distro_
>
> Every time the method is called the cache gets reset. I think this
> line can be removed.
Thanks for looking. My change was just to stop lint from complaining about the comment being too long -- I didn't even see the broken code.
>
>
> [2]
>
> + self.branch = self.developmen
>
> development_
> be a property so that development_
> it is needed.
>
Good catch.
>
> [3]
>
> + self.assertEqua
> ...
> + self.assertNotE
>
> I think this (and others that test against None) should be done with
> assertIs(None, ...). Can't remember what difference is makes though :)
>
Done
>
> [4]
>
> + def test_portlets_
> + # If the BranchUsage is HOSTED then the portlets are shown.
>
> s/HOSTED/EXTERNAL/
>
Done
>
> [5]
>
> + <a tal:attributes=
>
> Doesn't really matter, but it might be clearer expressed as:
>
> <a tal:attributes=
> tal:content=
>
>
Yes, much nicer.
> [6]
>
> + <div tal:condition=
> + <p>
> + New branches you create for <tal:name replace=
> + are <strong>
> + </p>
> + </div>
> + <div tal:condition=
> + <p>
> + New branches you create for <tal:name replace=
> + are <strong>
> + </p>
> + </div>
>
> The tal:condition could go on the subordinate <p> tags, then there's
> only one <div>.
>
Done.
>
> [7]
>
> + <tal:comment condition=
> + The view/*_
> + template can be rendered by a view that does not have count
> + information available.
> + </tal:comment>
>
> There aren't any view/*_
> go.
>
>
c-n-p error
> [8]
>
> ...
Matthew Revell (matthew.revell) wrote : | # |
Thanks for this work Brad. I have a couple of suggestions:
When no code hosting/mirroring is configured, I think the page would be easier to read if some of the text were split up.
For screen shots 0 and 1, I suggest the following:
There are no branches of ticktock in Launchpad. You can change this by:
* activating code hosting directly on Launchpad (read more)
* asking Launchpad to mirror a Bazaar branch hosted elsewhere (read more)
* asking Launchpad to import code from Git, Subversion or CVS into a Bazaar branch (read more).
I have removed Mercurial because Tim tells me it is still very much experimental (i.e. works in around 20% of cases).
Other than that, I'm Matthew Revell and I approve this message.
Guilherme Salgado (salgado) wrote : | # |
This looks good but I'm not sure the portlets will work nice with real data. For instance, if you see https:/
Also, it looks a bit weird having the two different styles of links (the Register a branch one uses a bigger font and the icon is to its left while the others use a smaller font and have the icon to the right) on the portlet. And by the way, are we moving the action links that were once inlined back into portlets?
Brad Crittenden (bac) wrote : | # |
Salgado thanks for the review and the questions.
I tried creating some branches with absurdly long names to see what happened. Using a browser that is almost 1400 pixels wide I get http://
Shrinking the browser from there causes bad behavior seen at http://
The other application pages have portlets similar to the one I'm proposing. The link styles are patterned after the involvement portlet on a project index page as seen by a project owner so it is on unprecedented. I think the layout needs some tweaking.
Curtis Hovey (sinzui) wrote : | # |
The branch urls/names/
I think the font size difference in the links is a real problem. They should be the same, particularly since both presentations were added for 3.0. I do not know which is wrong. The problem might be hard to find because one might be using styles from the deprecated style sheet. I cannot judge the scope of this issue. it might be worthy of a separate bug, and it may be in launchpad-web if it is a universal issue.
Brad Crittenden (bac) wrote : | # |
Hi Curtis, Matthew and Salgado.
I have made the changes as requested by you and they are shown in screenshots 7-9 at http://
* Bulleted list
* Capitalizing the items in the portlets
* Using break-long-words on the branch names in the table allows them to collapse when the window shrinks
* Don't
The requested changes required a number of test changes, especially the change to not show the portlets when code hosting has not yet been configured. Curtis in addition to mentoring the UI could you look at the latest code diff at http://
Curtis Hovey (sinzui) wrote : | # |
The code looks fine. You fixed the sprite and comprehension issues I pointed out in the paste.
I was disappointed by the story additions, the stories did not really tell a story. I was also concerned that the /applets is owned by Foo Bar, so the test for an story about an owner was performed by an admin :(. We looked and did not see an intersection with the unittests in lp/code/
Guilherme Salgado (salgado) wrote : | # |
On Tue, 2010-09-28 at 19:50 +0000, Brad Crittenden wrote:
> Hi Curtis, Matthew and Salgado.
>
> I have made the changes as requested by you and they are shown in
> screenshots 7-9 at http://
>
> * Bulleted list
> * Capitalizing the items in the portlets
> * Using break-long-words on the branch names in the table allows them
> to collapse when the window shrinks
That certainly improves things, but my main concern was with the
horizontal space that is now taken by the portlets. Given that the
portlets have virtually no content (just action links and 3 lines of
statistics), we're wasting a significant amount (IMHO) of real state,
which will make the branches list a bit too narrow on a 800px wide
browser window, like I tend to use[1]
(<http://
You've said that other application pages have portlets, and that's true
for the bugs app page, but in that case they have a significant amount
of content (the tag cloud, pre-defined searches and action links), which
is not the case here. Also, the translations app page, on the other
hand, doesn't have any portlets, so I'm still not convinced the code app
page should have them. Unless there's a policy I'm not aware of?
[1] Even though my monitor's resolution is higher than that, I tend to
use less than 800px wide browser windows because most pages work fine on
those and I think Launchpad should as well.
Curtis Hovey (sinzui) wrote : | # |
On Tue, 2010-09-28 at 21:07 +0000, Guilherme Salgado wrote:
> You've said that other application pages have portlets, and that's
> true
> for the bugs app page, but in that case they have a significant amount
> of content (the tag cloud, pre-defined searches and action links),
> which
> is not the case here. Also, the translations app page, on the other
> hand, doesn't have any portlets, so I'm still not convinced the code
> app
> page should have them. Unless there's a policy I'm not aware of?
The intent of the features is to make the root pages for projects to use
the same layouts and conventions. Code is using a 2.0 layout still.
Translations remains a conundrum. The user should trust that global
actions and set searches are in the right portlets.
This feature work is driven by our own strategists complaint that users
see conflicting designs when they work across applications.
{{{
Hello Curtis, Strategons,
Attached are screenshots of each of the six tabs of Launchpad for a
newly created project.
I want you to look at them. Make a note of the fonts, the styling of
the action portlets, the alignment of cells, the busyness of the
pages, the balance of the layouts, the colours used, the amount of
text on each page.
You will notice that there is significant difference between each
page. If you look at the pages with a detached eye, you may well come
to the conclusion that none of them are very nice to look at.
I think that this is a problem. I don't mean to put down anyone on the
team, but I would be embarrassed to present on these pages at a
conference.
...
}}}
This lead to an extension of the bridging-the-gap theme in June.
Launchpad must state where services are hosted so that they can complete
their task. Launchpad must state when the service is unknown, hosted, or
external. The applications must behave the same way when unknown,
external, or hosted so that users know what to expect when they use
multiple applications.
--
__Curtis C. Hovey_________
http://
Guilherme Salgado (salgado) wrote : | # |
Thanks for the explanation, Curtis.
Since it looks like this has been thought through carefully, I think
it's reasonable to trade some screen real estate with better
consistency.
review approve
Preview Diff
1 | === modified file 'lib/canonical/launchpad/icing/style.css' | |||
2 | --- lib/canonical/launchpad/icing/style.css 2010-05-28 19:47:23 +0000 | |||
3 | +++ lib/canonical/launchpad/icing/style.css 2010-09-28 22:26:02 +0000 | |||
4 | @@ -550,6 +550,16 @@ | |||
5 | 550 | color: black !important; | 550 | color: black !important; |
6 | 551 | } | 551 | } |
7 | 552 | 552 | ||
8 | 553 | .code-links td.code-count { | ||
9 | 554 | text-align: right; | ||
10 | 555 | padding-right: 0.5em; | ||
11 | 556 | } | ||
12 | 557 | |||
13 | 558 | .code-links td.code-link { | ||
14 | 559 | text-align: left; | ||
15 | 560 | margin: 0 0 0 0; | ||
16 | 561 | } | ||
17 | 562 | |||
18 | 553 | 563 | ||
19 | 554 | /* === Bugs === */ | 564 | /* === Bugs === */ |
20 | 555 | /* The Launchpad Bugs application uses a maroon color: */ | 565 | /* The Launchpad Bugs application uses a maroon color: */ |
21 | @@ -585,6 +595,15 @@ | |||
22 | 585 | padding-right: 1em; | 595 | padding-right: 1em; |
23 | 586 | } | 596 | } |
24 | 587 | 597 | ||
25 | 598 | .bug-links td.bugs-count { | ||
26 | 599 | text-align: right; | ||
27 | 600 | padding-right: 0.5em; | ||
28 | 601 | } | ||
29 | 602 | |||
30 | 603 | .bug-links td.bugs-link { | ||
31 | 604 | text-align: left; | ||
32 | 605 | } | ||
33 | 606 | |||
34 | 588 | /* --- Blueprints --- */ | 607 | /* --- Blueprints --- */ |
35 | 589 | 608 | ||
36 | 590 | body.tab-specifications #actions, body.tab-specifications .results { | 609 | body.tab-specifications #actions, body.tab-specifications .results { |
37 | @@ -652,15 +671,6 @@ | |||
38 | 652 | margin: 0.5em; | 671 | margin: 0.5em; |
39 | 653 | } | 672 | } |
40 | 654 | 673 | ||
41 | 655 | .bug-links td.bugs-count { | ||
42 | 656 | text-align: right; | ||
43 | 657 | padding-right: 0.5em; | ||
44 | 658 | } | ||
45 | 659 | |||
46 | 660 | .bug-links td.bugs-link { | ||
47 | 661 | text-align: left; | ||
48 | 662 | } | ||
49 | 663 | |||
50 | 664 | /* ====== Content area styles ====== */ | 674 | /* ====== Content area styles ====== */ |
51 | 665 | 675 | ||
52 | 666 | /* -- Front pages -- */ | 676 | /* -- Front pages -- */ |
53 | 667 | 677 | ||
54 | === modified file 'lib/lp/code/browser/branch.py' | |||
55 | --- lib/lp/code/browser/branch.py 2010-08-24 10:45:57 +0000 | |||
56 | +++ lib/lp/code/browser/branch.py 2010-09-28 22:26:02 +0000 | |||
57 | @@ -16,6 +16,7 @@ | |||
58 | 16 | 'BranchReviewerEditView', | 16 | 'BranchReviewerEditView', |
59 | 17 | 'BranchMergeQueueView', | 17 | 'BranchMergeQueueView', |
60 | 18 | 'BranchMirrorStatusView', | 18 | 'BranchMirrorStatusView', |
61 | 19 | 'BranchMirrorMixin', | ||
62 | 19 | 'BranchNameValidationMixin', | 20 | 'BranchNameValidationMixin', |
63 | 20 | 'BranchNavigation', | 21 | 'BranchNavigation', |
64 | 21 | 'BranchEditMenu', | 22 | 'BranchEditMenu', |
65 | @@ -374,7 +375,39 @@ | |||
66 | 374 | return Link('+new-recipe', text, enabled=enabled, icon='add') | 375 | return Link('+new-recipe', text, enabled=enabled, icon='add') |
67 | 375 | 376 | ||
68 | 376 | 377 | ||
70 | 377 | class BranchView(LaunchpadView, FeedsMixin): | 378 | class BranchMirrorMixin: |
71 | 379 | """Provide mirror_location property. | ||
72 | 380 | |||
73 | 381 | Requires self.branch to be set by the class using this mixin. | ||
74 | 382 | """ | ||
75 | 383 | |||
76 | 384 | @property | ||
77 | 385 | def mirror_location(self): | ||
78 | 386 | """Check the mirror location to see if it is a private one.""" | ||
79 | 387 | branch = self.branch | ||
80 | 388 | |||
81 | 389 | # If the user has edit permissions, then show the actual location. | ||
82 | 390 | if check_permission('launchpad.Edit', branch): | ||
83 | 391 | return branch.url | ||
84 | 392 | |||
85 | 393 | # XXX: Tim Penhey, 2008-05-30 | ||
86 | 394 | # Instead of a configuration hack we should support the users | ||
87 | 395 | # specifying whether or not they want the mirror location | ||
88 | 396 | # hidden or not. Given that this is a database patch, | ||
89 | 397 | # it isn't going to happen today. | ||
90 | 398 | # See bug 235916 | ||
91 | 399 | hosts = config.codehosting.private_mirror_hosts.split(',') | ||
92 | 400 | private_mirror_hosts = [name.strip() for name in hosts] | ||
93 | 401 | |||
94 | 402 | uri = URI(branch.url) | ||
95 | 403 | for private_host in private_mirror_hosts: | ||
96 | 404 | if uri.underDomain(private_host): | ||
97 | 405 | return '<private server>' | ||
98 | 406 | |||
99 | 407 | return branch.url | ||
100 | 408 | |||
101 | 409 | |||
102 | 410 | class BranchView(LaunchpadView, FeedsMixin, BranchMirrorMixin): | ||
103 | 378 | 411 | ||
104 | 379 | feed_types = ( | 412 | feed_types = ( |
105 | 380 | BranchFeedLink, | 413 | BranchFeedLink, |
106 | @@ -387,6 +420,7 @@ | |||
107 | 387 | label = page_title | 420 | label = page_title |
108 | 388 | 421 | ||
109 | 389 | def initialize(self): | 422 | def initialize(self): |
110 | 423 | self.branch = self.context | ||
111 | 390 | self.notices = [] | 424 | self.notices = [] |
112 | 391 | # Replace our context with a decorated branch, if it is not already | 425 | # Replace our context with a decorated branch, if it is not already |
113 | 392 | # decorated. | 426 | # decorated. |
114 | @@ -586,31 +620,6 @@ | |||
115 | 586 | return url.startswith("http") | 620 | return url.startswith("http") |
116 | 587 | 621 | ||
117 | 588 | @property | 622 | @property |
118 | 589 | def mirror_location(self): | ||
119 | 590 | """Check the mirror location to see if it is a private one.""" | ||
120 | 591 | branch = self.context | ||
121 | 592 | |||
122 | 593 | # If the user has edit permissions, then show the actual location. | ||
123 | 594 | if check_permission('launchpad.Edit', branch): | ||
124 | 595 | return branch.url | ||
125 | 596 | |||
126 | 597 | # XXX: Tim Penhey, 2008-05-30 | ||
127 | 598 | # Instead of a configuration hack we should support the users | ||
128 | 599 | # specifying whether or not they want the mirror location | ||
129 | 600 | # hidden or not. Given that this is a database patch, | ||
130 | 601 | # it isn't going to happen today. | ||
131 | 602 | # See bug 235916 | ||
132 | 603 | hosts = config.codehosting.private_mirror_hosts.split(',') | ||
133 | 604 | private_mirror_hosts = [name.strip() for name in hosts] | ||
134 | 605 | |||
135 | 606 | uri = URI(branch.url) | ||
136 | 607 | for private_host in private_mirror_hosts: | ||
137 | 608 | if uri.underDomain(private_host): | ||
138 | 609 | return '<private server>' | ||
139 | 610 | |||
140 | 611 | return branch.url | ||
141 | 612 | |||
142 | 613 | @property | ||
143 | 614 | def show_merge_links(self): | 623 | def show_merge_links(self): |
144 | 615 | """Return whether or not merge proposal links should be shown. | 624 | """Return whether or not merge proposal links should be shown. |
145 | 616 | 625 | ||
146 | 617 | 626 | ||
147 | === modified file 'lib/lp/code/browser/branchlisting.py' | |||
148 | --- lib/lp/code/browser/branchlisting.py 2010-08-31 11:11:09 +0000 | |||
149 | +++ lib/lp/code/browser/branchlisting.py 2010-09-28 22:26:02 +0000 | |||
150 | @@ -1,4 +1,4 @@ | |||
152 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
153 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
154 | 3 | 3 | ||
155 | 4 | """Base class view for branch listings.""" | 4 | """Base class view for branch listings.""" |
156 | @@ -83,15 +83,18 @@ | |||
157 | 83 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb | 83 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
158 | 84 | from canonical.launchpad.webapp.publisher import LaunchpadView | 84 | from canonical.launchpad.webapp.publisher import LaunchpadView |
159 | 85 | from canonical.widgets import LaunchpadDropdownWidget | 85 | from canonical.widgets import LaunchpadDropdownWidget |
160 | 86 | from lp.app.browser.tales import MenuAPI | ||
161 | 86 | from lp.blueprints.interfaces.specificationbranch import ( | 87 | from lp.blueprints.interfaces.specificationbranch import ( |
162 | 87 | ISpecificationBranchSet, | 88 | ISpecificationBranchSet, |
163 | 88 | ) | 89 | ) |
164 | 89 | from lp.bugs.interfaces.bugbranch import IBugBranchSet | 90 | from lp.bugs.interfaces.bugbranch import IBugBranchSet |
165 | 91 | from lp.code.browser.branch import BranchMirrorMixin | ||
166 | 90 | from lp.code.browser.branchmergeproposallisting import ( | 92 | from lp.code.browser.branchmergeproposallisting import ( |
167 | 91 | ActiveReviewsView, | 93 | ActiveReviewsView, |
168 | 92 | PersonActiveReviewsView, | 94 | PersonActiveReviewsView, |
169 | 93 | PersonProductActiveReviewsView, | 95 | PersonProductActiveReviewsView, |
170 | 94 | ) | 96 | ) |
171 | 97 | from lp.code.browser.summary import BranchCountSummaryView | ||
172 | 95 | from lp.code.enums import ( | 98 | from lp.code.enums import ( |
173 | 96 | BranchLifecycleStatus, | 99 | BranchLifecycleStatus, |
174 | 97 | BranchLifecycleStatusFilter, | 100 | BranchLifecycleStatusFilter, |
175 | @@ -124,7 +127,6 @@ | |||
176 | 124 | IPersonProduct, | 127 | IPersonProduct, |
177 | 125 | IPersonProductFactory, | 128 | IPersonProductFactory, |
178 | 126 | ) | 129 | ) |
179 | 127 | from lp.registry.interfaces.pocket import PackagePublishingPocket | ||
180 | 128 | from lp.registry.interfaces.product import IProduct | 130 | from lp.registry.interfaces.product import IProduct |
181 | 129 | from lp.registry.interfaces.series import SeriesStatus | 131 | from lp.registry.interfaces.series import SeriesStatus |
182 | 130 | from lp.registry.interfaces.sourcepackage import ISourcePackageFactory | 132 | from lp.registry.interfaces.sourcepackage import ISourcePackageFactory |
183 | @@ -421,8 +423,7 @@ | |||
184 | 421 | return sorted(links, key=attrgetter('pocket')) | 423 | return sorted(links, key=attrgetter('pocket')) |
185 | 422 | 424 | ||
186 | 423 | def getDistroDevelSeries(self, distribution): | 425 | def getDistroDevelSeries(self, distribution): |
189 | 424 | """Since distribution.currentseries hits the DB every time, cache it.""" | 426 | """distribution.currentseries hits the DB every time so cache it.""" |
188 | 425 | self._distro_series_map = {} | ||
190 | 426 | try: | 427 | try: |
191 | 427 | return self._distro_series_map[distribution] | 428 | return self._distro_series_map[distribution] |
192 | 428 | except KeyError: | 429 | except KeyError: |
193 | @@ -777,7 +778,7 @@ | |||
194 | 777 | """A branch listing that has no associated product or person.""" | 778 | """A branch listing that has no associated product or person.""" |
195 | 778 | 779 | ||
196 | 779 | field_names = ['lifecycle'] | 780 | field_names = ['lifecycle'] |
198 | 780 | no_sort_by = (BranchListingSort.DEFAULT,) | 781 | no_sort_by = (BranchListingSort.DEFAULT, ) |
199 | 781 | 782 | ||
200 | 782 | no_branch_message = ( | 783 | no_branch_message = ( |
201 | 783 | 'There are no branches that match the current status filter.') | 784 | 'There are no branches that match the current status filter.') |
202 | @@ -932,8 +933,8 @@ | |||
203 | 932 | def active_reviews(self): | 933 | def active_reviews(self): |
204 | 933 | text = get_plural_text( | 934 | text = get_plural_text( |
205 | 934 | self.active_review_count, | 935 | self.active_review_count, |
208 | 935 | 'active review or unmerged proposal', | 936 | 'active review', |
209 | 936 | 'active reviews or unmerged proposals') | 937 | 'active reviews') |
210 | 937 | return Link('+activereviews', text) | 938 | return Link('+activereviews', text) |
211 | 938 | 939 | ||
212 | 939 | def addbranch(self): | 940 | def addbranch(self): |
213 | @@ -1022,7 +1023,7 @@ | |||
214 | 1022 | 1023 | ||
215 | 1023 | page_title = _('Subscribed') | 1024 | page_title = _('Subscribed') |
216 | 1024 | label_template = 'Bazaar branches subscribed to by %(displayname)s' | 1025 | label_template = 'Bazaar branches subscribed to by %(displayname)s' |
218 | 1025 | no_sort_by = (BranchListingSort.DEFAULT,) | 1026 | no_sort_by = (BranchListingSort.DEFAULT, ) |
219 | 1026 | 1027 | ||
220 | 1027 | def _getCollection(self): | 1028 | def _getCollection(self): |
221 | 1028 | return getUtility(IAllBranches).subscribedBy(self.context) | 1029 | return getUtility(IAllBranches).subscribedBy(self.context) |
222 | @@ -1122,8 +1123,8 @@ | |||
223 | 1122 | def active_reviews(self): | 1123 | def active_reviews(self): |
224 | 1123 | text = get_plural_text( | 1124 | text = get_plural_text( |
225 | 1124 | self.active_review_count, | 1125 | self.active_review_count, |
228 | 1125 | 'active review or unmerged proposal', | 1126 | 'Active review', |
229 | 1126 | 'active reviews or unmerged proposals') | 1127 | 'Active reviews') |
230 | 1127 | return Link('+activereviews', text, site='code') | 1128 | return Link('+activereviews', text, site='code') |
231 | 1128 | 1129 | ||
232 | 1129 | @enabled_with_permission('launchpad.Commercial') | 1130 | @enabled_with_permission('launchpad.Commercial') |
233 | @@ -1140,7 +1141,7 @@ | |||
234 | 1140 | """A base class for product branch listings.""" | 1141 | """A base class for product branch listings.""" |
235 | 1141 | 1142 | ||
236 | 1142 | show_series_links = True | 1143 | show_series_links = True |
238 | 1143 | no_sort_by = (BranchListingSort.PRODUCT,) | 1144 | no_sort_by = (BranchListingSort.PRODUCT, ) |
239 | 1144 | label_template = 'Bazaar branches of %(displayname)s' | 1145 | label_template = 'Bazaar branches of %(displayname)s' |
240 | 1145 | 1146 | ||
241 | 1146 | def _getCollection(self): | 1147 | def _getCollection(self): |
242 | @@ -1180,8 +1181,23 @@ | |||
243 | 1180 | return message % self.context.displayname | 1181 | return message % self.context.displayname |
244 | 1181 | 1182 | ||
245 | 1182 | 1183 | ||
246 | 1184 | class ProductBranchStatisticsView(BranchCountSummaryView, | ||
247 | 1185 | ProductBranchListingView): | ||
248 | 1186 | """Portlet containing branch statistics.""" | ||
249 | 1187 | |||
250 | 1188 | @property | ||
251 | 1189 | def branch_text(self): | ||
252 | 1190 | text = super(ProductBranchStatisticsView, self).branch_text | ||
253 | 1191 | return text.capitalize() | ||
254 | 1192 | |||
255 | 1193 | @property | ||
256 | 1194 | def commit_text(self): | ||
257 | 1195 | text = super(ProductBranchStatisticsView, self).commit_text | ||
258 | 1196 | return text.capitalize() | ||
259 | 1197 | |||
260 | 1198 | |||
261 | 1183 | class ProductCodeIndexView(ProductBranchListingView, SortSeriesMixin, | 1199 | class ProductCodeIndexView(ProductBranchListingView, SortSeriesMixin, |
263 | 1184 | ProductDownloadFileMixin): | 1200 | ProductDownloadFileMixin, BranchMirrorMixin): |
264 | 1185 | """Initial view for products on the code virtual host.""" | 1201 | """Initial view for products on the code virtual host.""" |
265 | 1186 | 1202 | ||
266 | 1187 | show_set_development_focus = True | 1203 | show_set_development_focus = True |
267 | @@ -1193,6 +1209,10 @@ | |||
268 | 1193 | self.revision_cache = revision_cache.inProduct(self.product) | 1209 | self.revision_cache = revision_cache.inProduct(self.product) |
269 | 1194 | 1210 | ||
270 | 1195 | @property | 1211 | @property |
271 | 1212 | def branch(self): | ||
272 | 1213 | return self.development_focus_branch | ||
273 | 1214 | |||
274 | 1215 | @property | ||
275 | 1196 | def form_action(self): | 1216 | def form_action(self): |
276 | 1197 | return "+branches" | 1217 | return "+branches" |
277 | 1198 | 1218 | ||
278 | @@ -1240,6 +1260,7 @@ | |||
279 | 1240 | # skip subsequent series where the lifecycle status is Merged or | 1260 | # skip subsequent series where the lifecycle status is Merged or |
280 | 1241 | # Abandoned | 1261 | # Abandoned |
281 | 1242 | sorted_series = self.sorted_active_series_list | 1262 | sorted_series = self.sorted_active_series_list |
282 | 1263 | |||
283 | 1243 | def show_branch(branch): | 1264 | def show_branch(branch): |
284 | 1244 | if self.selected_lifecycle_status is None: | 1265 | if self.selected_lifecycle_status is None: |
285 | 1245 | return True | 1266 | return True |
286 | @@ -1323,6 +1344,14 @@ | |||
287 | 1323 | def committer_text(self): | 1344 | def committer_text(self): |
288 | 1324 | return get_plural_text(self.committer_count, _('person'), _('people')) | 1345 | return get_plural_text(self.committer_count, _('person'), _('people')) |
289 | 1325 | 1346 | ||
290 | 1347 | @property | ||
291 | 1348 | def configure_codehosting(self): | ||
292 | 1349 | """Get the menu link for configuring code hosting.""" | ||
293 | 1350 | series_menu = MenuAPI(self.context.development_focus).overview | ||
294 | 1351 | set_branch = series_menu['set_branch'] | ||
295 | 1352 | set_branch.text = 'Configure code hosting' | ||
296 | 1353 | return set_branch | ||
297 | 1354 | |||
298 | 1326 | 1355 | ||
299 | 1327 | class ProductBranchesView(ProductBranchListingView): | 1356 | class ProductBranchesView(ProductBranchListingView): |
300 | 1328 | """View for branch listing for a product.""" | 1357 | """View for branch listing for a product.""" |
301 | @@ -1353,7 +1382,7 @@ | |||
302 | 1353 | class ProjectBranchesView(BranchListingView): | 1382 | class ProjectBranchesView(BranchListingView): |
303 | 1354 | """View for branch listing for a project.""" | 1383 | """View for branch listing for a project.""" |
304 | 1355 | 1384 | ||
306 | 1356 | no_sort_by = (BranchListingSort.DEFAULT,) | 1385 | no_sort_by = (BranchListingSort.DEFAULT, ) |
307 | 1357 | extra_columns = ('author', 'product') | 1386 | extra_columns = ('author', 'product') |
308 | 1358 | label_template = 'Bazaar branches of %(displayname)s' | 1387 | label_template = 'Bazaar branches of %(displayname)s' |
309 | 1359 | show_series_links = True | 1388 | show_series_links = True |
310 | 1360 | 1389 | ||
311 | === modified file 'lib/lp/code/browser/configure.zcml' | |||
312 | --- lib/lp/code/browser/configure.zcml 2010-09-22 18:37:57 +0000 | |||
313 | +++ lib/lp/code/browser/configure.zcml 2010-09-28 22:26:02 +0000 | |||
314 | @@ -1,4 +1,4 @@ | |||
316 | 1 | <!-- Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | <!-- Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
317 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | 2 | GNU Affero General Public License version 3 (see the file LICENSE). |
318 | 3 | --> | 3 | --> |
319 | 4 | 4 | ||
320 | @@ -961,6 +961,18 @@ | |||
321 | 961 | permission="zope.Public" | 961 | permission="zope.Public" |
322 | 962 | name="+code-index" | 962 | name="+code-index" |
323 | 963 | template="../templates/product-branches.pt"/> | 963 | template="../templates/product-branches.pt"/> |
324 | 964 | <browser:page | ||
325 | 965 | for="lp.registry.interfaces.product.IProduct" | ||
326 | 966 | class="lp.code.browser.branchlisting.ProductBranchStatisticsView" | ||
327 | 967 | permission="zope.Public" | ||
328 | 968 | name="+portlet-product-codestatistics" | ||
329 | 969 | template="../templates/product-portlet-codestatistics.pt"/> | ||
330 | 970 | <browser:page | ||
331 | 971 | for="lp.registry.interfaces.product.IProduct" | ||
332 | 972 | class="lp.code.browser.branchlisting.ProductBranchStatisticsView" | ||
333 | 973 | permission="zope.Public" | ||
334 | 974 | name="+portlet-product-codestatistics-content" | ||
335 | 975 | template="../templates/product-portlet-codestatistics-content.pt"/> | ||
336 | 964 | <browser:page | 976 | <browser:page |
337 | 965 | for="lp.registry.interfaces.product.IProduct" | 977 | for="lp.registry.interfaces.product.IProduct" |
338 | 966 | layer="lp.code.publisher.CodeLayer" | 978 | layer="lp.code.publisher.CodeLayer" |
339 | 967 | 979 | ||
340 | === modified file 'lib/lp/code/browser/tests/test_branch.py' | |||
341 | --- lib/lp/code/browser/tests/test_branch.py 2010-08-20 20:31:18 +0000 | |||
342 | +++ lib/lp/code/browser/tests/test_branch.py 2010-09-28 22:26:02 +0000 | |||
343 | @@ -45,7 +45,6 @@ | |||
344 | 45 | ) | 45 | ) |
345 | 46 | from lp.code.interfaces.branchtarget import IBranchTarget | 46 | from lp.code.interfaces.branchtarget import IBranchTarget |
346 | 47 | from lp.testing import ( | 47 | from lp.testing import ( |
347 | 48 | ANONYMOUS, | ||
348 | 49 | login, | 48 | login, |
349 | 50 | login_person, | 49 | login_person, |
350 | 51 | logout, | 50 | logout, |
351 | @@ -78,6 +77,7 @@ | |||
352 | 78 | branch_type=BranchType.MIRRORED, | 77 | branch_type=BranchType.MIRRORED, |
353 | 79 | url="http://example.com/good/mirror") | 78 | url="http://example.com/good/mirror") |
354 | 80 | view = BranchView(branch, LaunchpadTestRequest()) | 79 | view = BranchView(branch, LaunchpadTestRequest()) |
355 | 80 | view.initialize() | ||
356 | 81 | self.assertTrue(view.user is None) | 81 | self.assertTrue(view.user is None) |
357 | 82 | self.assertEqual( | 82 | self.assertEqual( |
358 | 83 | "http://example.com/good/mirror", view.mirror_location) | 83 | "http://example.com/good/mirror", view.mirror_location) |
359 | @@ -89,6 +89,7 @@ | |||
360 | 89 | branch_type=BranchType.MIRRORED, | 89 | branch_type=BranchType.MIRRORED, |
361 | 90 | url="http://private.example.com/bzr-mysql/mysql-5.0") | 90 | url="http://private.example.com/bzr-mysql/mysql-5.0") |
362 | 91 | view = BranchView(branch, LaunchpadTestRequest()) | 91 | view = BranchView(branch, LaunchpadTestRequest()) |
363 | 92 | view.initialize() | ||
364 | 92 | self.assertTrue(view.user is None) | 93 | self.assertTrue(view.user is None) |
365 | 93 | self.assertEqual( | 94 | self.assertEqual( |
366 | 94 | "<private server>", view.mirror_location) | 95 | "<private server>", view.mirror_location) |
367 | @@ -106,6 +107,7 @@ | |||
368 | 106 | logout() | 107 | logout() |
369 | 107 | login('eric@example.com') | 108 | login('eric@example.com') |
370 | 108 | view = BranchView(branch, LaunchpadTestRequest()) | 109 | view = BranchView(branch, LaunchpadTestRequest()) |
371 | 110 | view.initialize() | ||
372 | 109 | self.assertEqual(view.user, owner) | 111 | self.assertEqual(view.user, owner) |
373 | 110 | self.assertEqual( | 112 | self.assertEqual( |
374 | 111 | "http://private.example.com/bzr-mysql/mysql-5.0", | 113 | "http://private.example.com/bzr-mysql/mysql-5.0", |
375 | @@ -126,6 +128,7 @@ | |||
376 | 126 | logout() | 128 | logout() |
377 | 127 | login('other@example.com') | 129 | login('other@example.com') |
378 | 128 | view = BranchView(branch, LaunchpadTestRequest()) | 130 | view = BranchView(branch, LaunchpadTestRequest()) |
379 | 131 | view.initialize() | ||
380 | 129 | self.assertEqual(view.user, other) | 132 | self.assertEqual(view.user, other) |
381 | 130 | self.assertEqual( | 133 | self.assertEqual( |
382 | 131 | "<private server>", view.mirror_location) | 134 | "<private server>", view.mirror_location) |
383 | @@ -160,7 +163,7 @@ | |||
384 | 160 | len(branch.mirror_status_message) | 163 | len(branch.mirror_status_message) |
385 | 161 | <= branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH, | 164 | <= branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH, |
386 | 162 | "branch.mirror_status_message longer than expected: %r" | 165 | "branch.mirror_status_message longer than expected: %r" |
388 | 163 | % (branch.mirror_status_message,)) | 166 | % (branch.mirror_status_message, )) |
389 | 164 | self.assertEqual( | 167 | self.assertEqual( |
390 | 165 | branch.mirror_status_message, branch_view.mirror_status_message) | 168 | branch.mirror_status_message, branch_view.mirror_status_message) |
391 | 166 | self.assertEqual( | 169 | self.assertEqual( |
392 | @@ -185,7 +188,7 @@ | |||
393 | 185 | 'whiteboard': '', | 188 | 'whiteboard': '', |
394 | 186 | 'owner': arbitrary_person, | 189 | 'owner': arbitrary_person, |
395 | 187 | 'author': arbitrary_person, | 190 | 'author': arbitrary_person, |
397 | 188 | 'product': arbitrary_product | 191 | 'product': arbitrary_product, |
398 | 189 | } | 192 | } |
399 | 190 | add_view.add_action.success(data) | 193 | add_view.add_action.success(data) |
400 | 191 | # Make sure that next_mirror_time is a datetime, not an sqlbuilder | 194 | # Make sure that next_mirror_time is a datetime, not an sqlbuilder |
401 | 192 | 195 | ||
402 | === modified file 'lib/lp/code/browser/tests/test_product.py' | |||
403 | --- lib/lp/code/browser/tests/test_product.py 2010-08-24 02:16:53 +0000 | |||
404 | +++ lib/lp/code/browser/tests/test_product.py 2010-09-28 22:26:02 +0000 | |||
405 | @@ -1,4 +1,4 @@ | |||
407 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
408 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
409 | 3 | 3 | ||
410 | 4 | """Tests for the product view classes and templates.""" | 4 | """Tests for the product view classes and templates.""" |
411 | @@ -10,20 +10,25 @@ | |||
412 | 10 | timedelta, | 10 | timedelta, |
413 | 11 | ) | 11 | ) |
414 | 12 | import unittest | 12 | import unittest |
415 | 13 | |||
416 | 14 | from mechanize import LinkNotFoundError | 13 | from mechanize import LinkNotFoundError |
417 | 15 | import pytz | 14 | import pytz |
421 | 16 | from zope.component import ( | 15 | from zope.component import getUtility |
422 | 17 | getMultiAdapter, | 16 | |
423 | 18 | getUtility, | 17 | from canonical.launchpad.testing.pages import ( |
424 | 18 | extract_text, | ||
425 | 19 | find_tag_by_id, | ||
426 | 19 | ) | 20 | ) |
427 | 20 | |||
428 | 21 | from canonical.launchpad.webapp import canonical_url | 21 | from canonical.launchpad.webapp import canonical_url |
429 | 22 | from canonical.launchpad.webapp.servers import LaunchpadTestRequest | ||
430 | 23 | from canonical.testing import DatabaseFunctionalLayer | 22 | from canonical.testing import DatabaseFunctionalLayer |
431 | 23 | from lp.app.enums import ServiceUsage | ||
432 | 24 | from lp.code.enums import ( | ||
433 | 25 | BranchType, | ||
434 | 26 | BranchVisibilityRule, | ||
435 | 27 | ) | ||
436 | 24 | from lp.code.interfaces.revision import IRevisionSet | 28 | from lp.code.interfaces.revision import IRevisionSet |
437 | 25 | from lp.testing import ( | 29 | from lp.testing import ( |
438 | 26 | ANONYMOUS, | 30 | ANONYMOUS, |
439 | 31 | BrowserTestCase, | ||
440 | 27 | login, | 32 | login, |
441 | 28 | login_person, | 33 | login_person, |
442 | 29 | TestCaseWithFactory, | 34 | TestCaseWithFactory, |
443 | @@ -32,9 +37,8 @@ | |||
444 | 32 | from lp.testing.views import create_initialized_view | 37 | from lp.testing.views import create_initialized_view |
445 | 33 | 38 | ||
446 | 34 | 39 | ||
450 | 35 | class TestProductCodeIndexView(TestCaseWithFactory): | 40 | class ProductTestBase(TestCaseWithFactory): |
451 | 36 | """Tests for the product code home page.""" | 41 | """Common methods for tests herein.""" |
449 | 37 | |||
452 | 38 | layer = DatabaseFunctionalLayer | 42 | layer = DatabaseFunctionalLayer |
453 | 39 | 43 | ||
454 | 40 | def makeProductAndDevelopmentFocusBranch(self, **branch_args): | 44 | def makeProductAndDevelopmentFocusBranch(self, **branch_args): |
455 | @@ -49,6 +53,10 @@ | |||
456 | 49 | product.development_focus.branch = branch | 53 | product.development_focus.branch = branch |
457 | 50 | return product, branch | 54 | return product, branch |
458 | 51 | 55 | ||
459 | 56 | |||
460 | 57 | class TestProductCodeIndexView(ProductTestBase): | ||
461 | 58 | """Tests for the product code home page.""" | ||
462 | 59 | |||
463 | 52 | def getBranchSummaryBrowseLinkForProduct(self, product): | 60 | def getBranchSummaryBrowseLinkForProduct(self, product): |
464 | 53 | """Get the 'browse code' link from the product's code home. | 61 | """Get the 'browse code' link from the product's code home. |
465 | 54 | 62 | ||
466 | @@ -98,13 +106,15 @@ | |||
467 | 98 | 106 | ||
468 | 99 | def test_initial_branches_contains_dev_focus_branch(self): | 107 | def test_initial_branches_contains_dev_focus_branch(self): |
469 | 100 | product, branch = self.makeProductAndDevelopmentFocusBranch() | 108 | product, branch = self.makeProductAndDevelopmentFocusBranch() |
471 | 101 | view = create_initialized_view(product, '+code-index', rootsite='code') | 109 | view = create_initialized_view(product, '+code-index', |
472 | 110 | rootsite='code') | ||
473 | 102 | self.assertIn(branch, view.initial_branches) | 111 | self.assertIn(branch, view.initial_branches) |
474 | 103 | 112 | ||
475 | 104 | def test_initial_branches_does_not_contain_private_dev_focus_branch(self): | 113 | def test_initial_branches_does_not_contain_private_dev_focus_branch(self): |
476 | 105 | product, branch = self.makeProductAndDevelopmentFocusBranch( | 114 | product, branch = self.makeProductAndDevelopmentFocusBranch( |
477 | 106 | private=True) | 115 | private=True) |
479 | 107 | view = create_initialized_view(product, '+code-index', rootsite='code') | 116 | view = create_initialized_view(product, '+code-index', |
480 | 117 | rootsite='code') | ||
481 | 108 | self.assertNotIn(branch, view.initial_branches) | 118 | self.assertNotIn(branch, view.initial_branches) |
482 | 109 | 119 | ||
483 | 110 | def test_committer_count_with_revision_authors(self): | 120 | def test_committer_count_with_revision_authors(self): |
484 | @@ -120,7 +130,8 @@ | |||
485 | 120 | date_generator=date_generator) | 130 | date_generator=date_generator) |
486 | 121 | getUtility(IRevisionSet).updateRevisionCacheForBranch(branch) | 131 | getUtility(IRevisionSet).updateRevisionCacheForBranch(branch) |
487 | 122 | 132 | ||
489 | 123 | view = create_initialized_view(product, '+code-index', rootsite='code') | 133 | view = create_initialized_view(product, '+code-index', |
490 | 134 | rootsite='code') | ||
491 | 124 | self.assertEqual(view.committer_count, 1) | 135 | self.assertEqual(view.committer_count, 1) |
492 | 125 | 136 | ||
493 | 126 | def test_committers_count_private_branch(self): | 137 | def test_committers_count_private_branch(self): |
494 | @@ -138,10 +149,150 @@ | |||
495 | 138 | date_generator=date_generator) | 149 | date_generator=date_generator) |
496 | 139 | getUtility(IRevisionSet).updateRevisionCacheForBranch(branch) | 150 | getUtility(IRevisionSet).updateRevisionCacheForBranch(branch) |
497 | 140 | 151 | ||
499 | 141 | view = create_initialized_view(product, '+code-index', rootsite='code') | 152 | view = create_initialized_view(product, '+code-index', |
500 | 153 | rootsite='code') | ||
501 | 142 | self.assertEqual(view.committer_count, 1) | 154 | self.assertEqual(view.committer_count, 1) |
502 | 143 | 155 | ||
503 | 144 | 156 | ||
504 | 157 | class TestProductCodeIndexServiceUsages(ProductTestBase, BrowserTestCase): | ||
505 | 158 | """Tests for the product code page, especially the usage messasges.""" | ||
506 | 159 | |||
507 | 160 | def test_external_mirrored(self): | ||
508 | 161 | # Test that the correct URL is displayed for a mirrored branch. | ||
509 | 162 | product, branch = self.makeProductAndDevelopmentFocusBranch( | ||
510 | 163 | branch_type=BranchType.MIRRORED, | ||
511 | 164 | url="http://example.com/mybranch") | ||
512 | 165 | self.assertEqual(ServiceUsage.EXTERNAL, product.codehosting_usage) | ||
513 | 166 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
514 | 167 | login(ANONYMOUS) | ||
515 | 168 | content = find_tag_by_id(browser.contents, 'external') | ||
516 | 169 | text = extract_text(content) | ||
517 | 170 | expected = ("%(product_title)s hosts its code at %(branch_url)s. " | ||
518 | 171 | "Launchpad has a mirror of the master branch " | ||
519 | 172 | "and you can create branches from it." % dict( | ||
520 | 173 | product_title=product.title, | ||
521 | 174 | branch_url=branch.url)) | ||
522 | 175 | self.assertTextMatchesExpressionIgnoreWhitespace(expected, text) | ||
523 | 176 | |||
524 | 177 | def test_external_remote(self): | ||
525 | 178 | # Test that a remote branch is shown properly. | ||
526 | 179 | product, branch = self.makeProductAndDevelopmentFocusBranch( | ||
527 | 180 | branch_type=BranchType.REMOTE, | ||
528 | 181 | url="http://example.com/mybranch") | ||
529 | 182 | self.assertEqual(ServiceUsage.EXTERNAL, | ||
530 | 183 | product.codehosting_usage) | ||
531 | 184 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
532 | 185 | login(ANONYMOUS) | ||
533 | 186 | content = find_tag_by_id(browser.contents, 'external') | ||
534 | 187 | text = extract_text(content) | ||
535 | 188 | expected = ("%(product_title)s hosts its code at %(branch_url)s. " | ||
536 | 189 | "Launchpad does not have a copy of the remote " | ||
537 | 190 | "branch." % dict( | ||
538 | 191 | product_title=product.title, | ||
539 | 192 | branch_url=branch.url)) | ||
540 | 193 | self.assertTextMatchesExpressionIgnoreWhitespace(expected, text) | ||
541 | 194 | |||
542 | 195 | def test_unknown(self): | ||
543 | 196 | product = self.factory.makeProduct() | ||
544 | 197 | self.assertEqual(ServiceUsage.UNKNOWN, product.codehosting_usage) | ||
545 | 198 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
546 | 199 | login(ANONYMOUS) | ||
547 | 200 | content = find_tag_by_id(browser.contents, 'unknown') | ||
548 | 201 | text = extract_text(content) | ||
549 | 202 | expected = ( | ||
550 | 203 | "Launchpad does not know where %(product_title)s " | ||
551 | 204 | "hosts its code.*" % | ||
552 | 205 | dict(product_title=product.title)) | ||
553 | 206 | self.assertTextMatchesExpressionIgnoreWhitespace(expected, text) | ||
554 | 207 | |||
555 | 208 | def test_on_launchpad(self): | ||
556 | 209 | product, branch = self.makeProductAndDevelopmentFocusBranch() | ||
557 | 210 | self.assertEqual(ServiceUsage.LAUNCHPAD, product.codehosting_usage) | ||
558 | 211 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
559 | 212 | login(ANONYMOUS) | ||
560 | 213 | text = extract_text(find_tag_by_id( | ||
561 | 214 | browser.contents, 'branch-count-summary')) | ||
562 | 215 | expected = "1 active branch owned by 1 person.*" | ||
563 | 216 | self.assertTextMatchesExpressionIgnoreWhitespace(expected, text) | ||
564 | 217 | |||
565 | 218 | def test_view_mirror_location(self): | ||
566 | 219 | url = "http://example.com/mybranch" | ||
567 | 220 | product, branch = self.makeProductAndDevelopmentFocusBranch( | ||
568 | 221 | branch_type=BranchType.MIRRORED, | ||
569 | 222 | url=url) | ||
570 | 223 | view = create_initialized_view(product, '+code-index', | ||
571 | 224 | rootsite='code') | ||
572 | 225 | self.assertEqual(url, view.mirror_location) | ||
573 | 226 | |||
574 | 227 | |||
575 | 228 | class TestProductBranchesViewPortlets(ProductTestBase, BrowserTestCase): | ||
576 | 229 | """Tests for the portlets.""" | ||
577 | 230 | |||
578 | 231 | def test_portlet_not_shown_for_UNKNOWN(self): | ||
579 | 232 | # If the BranchUsage is UNKNOWN then the portlets are not shown. | ||
580 | 233 | product = self.factory.makeProduct() | ||
581 | 234 | self.assertEqual(ServiceUsage.UNKNOWN, product.codehosting_usage) | ||
582 | 235 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
583 | 236 | contents = browser.contents | ||
584 | 237 | self.assertIs(None, find_tag_by_id(contents, 'branch-portlet')) | ||
585 | 238 | self.assertIs(None, find_tag_by_id(contents, 'privacy')) | ||
586 | 239 | self.assertIs(None, find_tag_by_id(contents, 'involvement')) | ||
587 | 240 | self.assertIs(None, find_tag_by_id( | ||
588 | 241 | contents, 'portlet-product-codestatistics')) | ||
589 | 242 | |||
590 | 243 | def test_portlets_shown_for_HOSTED(self): | ||
591 | 244 | # If the BranchUsage is HOSTED then the portlets are shown. | ||
592 | 245 | product, branch = self.makeProductAndDevelopmentFocusBranch() | ||
593 | 246 | self.assertEqual(ServiceUsage.LAUNCHPAD, product.codehosting_usage) | ||
594 | 247 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
595 | 248 | contents = browser.contents | ||
596 | 249 | self.assertIsNot(None, find_tag_by_id(contents, 'branch-portlet')) | ||
597 | 250 | self.assertIsNot(None, find_tag_by_id(contents, 'privacy')) | ||
598 | 251 | self.assertIsNot(None, find_tag_by_id(contents, 'involvement')) | ||
599 | 252 | self.assertIsNot(None, find_tag_by_id( | ||
600 | 253 | contents, 'portlet-product-codestatistics')) | ||
601 | 254 | |||
602 | 255 | def test_portlets_shown_for_EXTERNAL(self): | ||
603 | 256 | # If the BranchUsage is EXTERNAL then the portlets are shown. | ||
604 | 257 | url = "http://example.com/mybranch" | ||
605 | 258 | product, branch = self.makeProductAndDevelopmentFocusBranch( | ||
606 | 259 | branch_type=BranchType.MIRRORED, | ||
607 | 260 | url=url) | ||
608 | 261 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
609 | 262 | contents = browser.contents | ||
610 | 263 | self.assertIsNot(None, find_tag_by_id(contents, 'branch-portlet')) | ||
611 | 264 | self.assertIsNot(None, find_tag_by_id(contents, 'privacy')) | ||
612 | 265 | self.assertIsNot(None, find_tag_by_id(contents, 'involvement')) | ||
613 | 266 | self.assertIsNot(None, find_tag_by_id( | ||
614 | 267 | contents, 'portlet-product-codestatistics')) | ||
615 | 268 | |||
616 | 269 | def test_is_private(self): | ||
617 | 270 | team_owner = self.factory.makePerson() | ||
618 | 271 | team = self.factory.makeTeam(team_owner) | ||
619 | 272 | product = self.factory.makeProduct(owner=team_owner) | ||
620 | 273 | branch = self.factory.makeProductBranch(product=product) | ||
621 | 274 | login_person(product.owner) | ||
622 | 275 | product.development_focus.branch = branch | ||
623 | 276 | product.setBranchVisibilityTeamPolicy( | ||
624 | 277 | team, BranchVisibilityRule.PRIVATE) | ||
625 | 278 | view = create_initialized_view( | ||
626 | 279 | product, '+code-index', rootsite='code', principal=product.owner) | ||
627 | 280 | text = extract_text(find_tag_by_id(view.render(), 'privacy')) | ||
628 | 281 | expected = ("New branches you create for %(name)s are private " | ||
629 | 282 | "initially.*" % dict(name=product.displayname)) | ||
630 | 283 | self.assertTextMatchesExpressionIgnoreWhitespace(expected, text) | ||
631 | 284 | |||
632 | 285 | def test_is_public(self): | ||
633 | 286 | product = self.factory.makeProduct() | ||
634 | 287 | branch = self.factory.makeProductBranch(product=product) | ||
635 | 288 | login_person(product.owner) | ||
636 | 289 | product.development_focus.branch = branch | ||
637 | 290 | browser = self.getUserBrowser(canonical_url(product, rootsite='code')) | ||
638 | 291 | text = extract_text(find_tag_by_id(browser.contents, 'privacy')) | ||
639 | 292 | expected = ("New branches you create for %(name)s are public " | ||
640 | 293 | "initially.*" % dict(name=product.displayname)) | ||
641 | 294 | self.assertTextMatchesExpressionIgnoreWhitespace(expected, text) | ||
642 | 295 | |||
643 | 296 | |||
644 | 145 | def test_suite(): | 297 | def test_suite(): |
645 | 146 | return unittest.TestLoader().loadTestsFromName(__name__) | 298 | return unittest.TestLoader().loadTestsFromName(__name__) |
646 | 147 | |||
647 | 148 | 299 | ||
648 | === modified file 'lib/lp/code/stories/branches/xx-branch-deletion.txt' | |||
649 | --- lib/lp/code/stories/branches/xx-branch-deletion.txt 2010-05-13 16:22:19 +0000 | |||
650 | +++ lib/lp/code/stories/branches/xx-branch-deletion.txt 2010-09-28 22:26:02 +0000 | |||
651 | @@ -4,20 +4,34 @@ | |||
652 | 4 | can be deleted. The main use for this is to allow users to delete | 4 | can be deleted. The main use for this is to allow users to delete |
653 | 5 | branches that have been created in error. | 5 | branches that have been created in error. |
654 | 6 | 6 | ||
657 | 7 | >>> browser = setupBrowser(auth="Basic test@canonical.com:test") | 7 | >>> from lp.code.enums import BranchType |
658 | 8 | >>> browser.open('http://code.launchpad.dev/firefox') | 8 | >>> login(ANONYMOUS) |
659 | 9 | >>> alice = factory.makePerson(name="alice", password="test", | ||
660 | 10 | ... email="alice@example.com") | ||
661 | 11 | >>> product = factory.makeProduct( | ||
662 | 12 | ... name='earthlynx', displayname="Earth Lynx", owner=alice) | ||
663 | 13 | >>> branch = factory.makeProductBranch( | ||
664 | 14 | ... product=product, branch_type=BranchType.HOSTED) | ||
665 | 15 | >>> productseries = factory.makeProductSeries( | ||
666 | 16 | ... product=product, branch=branch) | ||
667 | 17 | >>> login_person(alice) | ||
668 | 18 | >>> product.development_focus = productseries | ||
669 | 19 | >>> logout() | ||
670 | 20 | |||
671 | 21 | >>> browser = setupBrowser(auth="Basic alice@example.com:test") | ||
672 | 22 | >>> browser.open('http://code.launchpad.dev/earthlynx') | ||
673 | 9 | >>> browser.getLink("Register a branch").click() | 23 | >>> browser.getLink("Register a branch").click() |
674 | 10 | >>> browser.getControl('Branch URL').value = 'http://foo.bar.com/oops' | 24 | >>> browser.getControl('Branch URL').value = 'http://foo.bar.com/oops' |
675 | 11 | >>> browser.getControl('Name').value = 'to-delete' | 25 | >>> browser.getControl('Name').value = 'to-delete' |
676 | 12 | >>> browser.getControl('Register Branch').click() | 26 | >>> browser.getControl('Register Branch').click() |
677 | 13 | >>> print browser.title | 27 | >>> print browser.title |
679 | 14 | to-delete : Code : Mozilla Firefox | 28 | to-delete : Code : Earth Lynx |
680 | 15 | 29 | ||
681 | 16 | The newly created branch has an action 'Delete branch'. | 30 | The newly created branch has an action 'Delete branch'. |
682 | 17 | 31 | ||
683 | 18 | >>> delete_link = browser.getLink('Delete branch') | 32 | >>> delete_link = browser.getLink('Delete branch') |
684 | 19 | >>> print delete_link.url | 33 | >>> print delete_link.url |
686 | 20 | http://code.launchpad.dev/~name12/firefox/to-delete/+delete | 34 | http://code.launchpad.dev/~alice/earthlynx/to-delete/+delete |
687 | 21 | 35 | ||
688 | 22 | When the user clicks on the link, they are informed what will happen if they | 36 | When the user clicks on the link, they are informed what will happen if they |
689 | 23 | delete the branch. | 37 | delete the branch. |
690 | @@ -25,7 +39,7 @@ | |||
691 | 25 | >>> delete_link.click() | 39 | >>> delete_link.click() |
692 | 26 | >>> print extract_text(find_main_content(browser.contents)) | 40 | >>> print extract_text(find_main_content(browser.contents)) |
693 | 27 | Delete branch | 41 | Delete branch |
695 | 28 | Mozilla Firefox... | 42 | Earth Lynx... |
696 | 29 | Branch deletion is permanent. | 43 | Branch deletion is permanent. |
697 | 30 | or Cancel | 44 | or Cancel |
698 | 31 | 45 | ||
699 | @@ -35,15 +49,15 @@ | |||
700 | 35 | 49 | ||
701 | 36 | >>> browser.getControl('Delete').click() | 50 | >>> browser.getControl('Delete').click() |
702 | 37 | >>> print browser.url | 51 | >>> print browser.url |
704 | 38 | http://code.launchpad.dev/firefox | 52 | http://code.launchpad.dev/earthlynx |
705 | 39 | >>> for message in get_feedback_messages(browser.contents): | 53 | >>> for message in get_feedback_messages(browser.contents): |
706 | 40 | ... print message | 54 | ... print message |
708 | 41 | Branch ~name12/firefox/to-delete deleted... | 55 | Branch ~alice/earthlynx/to-delete deleted... |
709 | 42 | 56 | ||
710 | 43 | If the branch is junk, then the user is taken back to the code listing for | 57 | If the branch is junk, then the user is taken back to the code listing for |
711 | 44 | the deleted branch's owner. | 58 | the deleted branch's owner. |
712 | 45 | 59 | ||
714 | 46 | >>> browser.open('http://code.launchpad.dev/~name12') | 60 | >>> browser.open('http://code.launchpad.dev/~alice') |
715 | 47 | >>> browser.getLink("Register a branch").click() | 61 | >>> browser.getLink("Register a branch").click() |
716 | 48 | >>> browser.getControl('Hosted').click() | 62 | >>> browser.getControl('Hosted').click() |
717 | 49 | >>> browser.getControl('Name').value = 'to-delete' | 63 | >>> browser.getControl('Name').value = 'to-delete' |
718 | @@ -51,14 +65,15 @@ | |||
719 | 51 | >>> browser.getLink('Delete branch').click() | 65 | >>> browser.getLink('Delete branch').click() |
720 | 52 | >>> browser.getControl('Delete').click() | 66 | >>> browser.getControl('Delete').click() |
721 | 53 | >>> print browser.url | 67 | >>> print browser.url |
723 | 54 | http://code.launchpad.dev/~name12 | 68 | http://code.launchpad.dev/~alice |
724 | 55 | >>> for message in get_feedback_messages(browser.contents): | 69 | >>> for message in get_feedback_messages(browser.contents): |
725 | 56 | ... print message | 70 | ... print message |
727 | 57 | Branch ~name12/+junk/to-delete deleted... | 71 | Branch ~alice/+junk/to-delete deleted... |
728 | 58 | 72 | ||
729 | 59 | Branches that are stacked upon cannot be deleted. | 73 | Branches that are stacked upon cannot be deleted. |
730 | 60 | 74 | ||
732 | 61 | >>> login('admin@canonical.com') | 75 | >>> from lp.testing.sampledata import ADMIN_EMAIL |
733 | 76 | >>> login(ADMIN_EMAIL) | ||
734 | 62 | >>> stacked_upon = factory.makeAnyBranch() | 77 | >>> stacked_upon = factory.makeAnyBranch() |
735 | 63 | >>> stacked = factory.makeAnyBranch(stacked_on=stacked_upon) | 78 | >>> stacked = factory.makeAnyBranch(stacked_on=stacked_upon) |
736 | 64 | >>> branch_location = canonical_url(stacked_upon) | 79 | >>> branch_location = canonical_url(stacked_upon) |
737 | @@ -82,12 +97,11 @@ | |||
738 | 82 | >>> from lp.registry.interfaces.pocket import PackagePublishingPocket | 97 | >>> from lp.registry.interfaces.pocket import PackagePublishingPocket |
739 | 83 | >>> from lp.registry.interfaces.person import IPersonSet | 98 | >>> from lp.registry.interfaces.person import IPersonSet |
740 | 84 | >>> login(ANONYMOUS) | 99 | >>> login(ANONYMOUS) |
741 | 85 | >>> name12 = getUtility(IPersonSet).getByName('name12') | ||
742 | 86 | >>> ubuntu_branches = getUtility(ILaunchpadCelebrities).ubuntu_branches | 100 | >>> ubuntu_branches = getUtility(ILaunchpadCelebrities).ubuntu_branches |
743 | 87 | >>> ignored = removeSecurityProxy(ubuntu_branches).addMember( | 101 | >>> ignored = removeSecurityProxy(ubuntu_branches).addMember( |
747 | 88 | ... name12, ubuntu_branches.teamowner) | 102 | ... alice, ubuntu_branches.teamowner) |
748 | 89 | >>> login_person(name12) | 103 | >>> login_person(alice) |
749 | 90 | >>> branch = factory.makePackageBranch(owner=name12) | 104 | >>> branch = factory.makePackageBranch(owner=alice) |
750 | 91 | >>> package = branch.sourcepackage | 105 | >>> package = branch.sourcepackage |
751 | 92 | >>> package.setBranch( | 106 | >>> package.setBranch( |
752 | 93 | ... PackagePublishingPocket.RELEASE, branch, branch.registrant) | 107 | ... PackagePublishingPocket.RELEASE, branch, branch.registrant) |
753 | 94 | 108 | ||
754 | === modified file 'lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt' | |||
755 | --- lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt 2010-01-14 23:39:06 +0000 | |||
756 | +++ lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt 2010-09-28 22:26:02 +0000 | |||
757 | @@ -63,11 +63,11 @@ | |||
758 | 63 | 63 | ||
759 | 64 | >>> browser.open('http://code.launchpad.dev/fooix') | 64 | >>> browser.open('http://code.launchpad.dev/fooix') |
760 | 65 | >>> print_tag_with_id(browser.contents, 'merge-counts') | 65 | >>> print_tag_with_id(browser.contents, 'merge-counts') |
766 | 66 | 3 active reviews or unmerged proposals | 66 | 3 Active reviews |
767 | 67 | 67 | ||
768 | 68 | The 'active reviews or unmerged proposals' text links to the active reviews page. | 68 | The 'active reviews' text links to the active reviews page. |
769 | 69 | 69 | ||
770 | 70 | >>> browser.getLink('active reviews or unmerged proposals').click() | 70 | >>> browser.getLink('Active reviews').click() |
771 | 71 | >>> print browser.title | 71 | >>> print browser.title |
772 | 72 | Active code reviews for Fooix... | 72 | Active code reviews for Fooix... |
773 | 73 | 73 | ||
774 | @@ -94,7 +94,7 @@ | |||
775 | 94 | >>> browser.open('http://code.launchpad.dev/~albert') | 94 | >>> browser.open('http://code.launchpad.dev/~albert') |
776 | 95 | >>> print_tag_with_id(browser.contents, 'page-summary') | 95 | >>> print_tag_with_id(browser.contents, 'page-summary') |
777 | 96 | 1 owned branch, 1 registered branch, 1 subscribed branch | 96 | 1 owned branch, 1 registered branch, 1 subscribed branch |
779 | 97 | 1 active review or unmerged proposal | 97 | 1 active review |
780 | 98 | 98 | ||
781 | 99 | The person's active reviews also lists all of their currently requested | 99 | The person's active reviews also lists all of their currently requested |
782 | 100 | reviews. | 100 | reviews. |
783 | 101 | 101 | ||
784 | === modified file 'lib/lp/code/stories/branches/xx-creating-branches.txt' | |||
785 | --- lib/lp/code/stories/branches/xx-creating-branches.txt 2010-09-27 19:39:21 +0000 | |||
786 | +++ lib/lp/code/stories/branches/xx-creating-branches.txt 2010-09-28 22:26:02 +0000 | |||
787 | @@ -15,6 +15,19 @@ | |||
788 | 15 | 15 | ||
789 | 16 | Hosted branches use Launchpad as their primary location. | 16 | Hosted branches use Launchpad as their primary location. |
790 | 17 | 17 | ||
791 | 18 | >>> from lp.registry.interfaces.product import IProductSet | ||
792 | 19 | >>> from lp.code.enums import BranchType | ||
793 | 20 | >>> from zope.component import getUtility | ||
794 | 21 | >>> login(ANONYMOUS) | ||
795 | 22 | >>> redfish = getUtility(IProductSet).getByName('redfish') | ||
796 | 23 | >>> branch = factory.makeProductBranch( | ||
797 | 24 | ... product=redfish, branch_type=BranchType.HOSTED) | ||
798 | 25 | >>> productseries = factory.makeProductSeries( | ||
799 | 26 | ... product=redfish, branch=branch) | ||
800 | 27 | >>> login_person(redfish.owner) | ||
801 | 28 | >>> redfish.development_focus = productseries | ||
802 | 29 | >>> logout() | ||
803 | 30 | |||
804 | 18 | >>> browser = setupBrowser(auth="Basic test@canonical.com:test") | 31 | >>> browser = setupBrowser(auth="Basic test@canonical.com:test") |
805 | 19 | >>> browser.open('http://code.launchpad.dev/redfish') | 32 | >>> browser.open('http://code.launchpad.dev/redfish') |
806 | 20 | >>> browser.getLink("Register a branch").click() | 33 | >>> browser.getLink("Register a branch").click() |
807 | @@ -122,6 +135,29 @@ | |||
808 | 122 | Let's make sure we can load the branch creation form on a product. | 135 | Let's make sure we can load the branch creation form on a product. |
809 | 123 | 136 | ||
810 | 124 | >>> user_browser.getLink("Register a branch").click() | 137 | >>> user_browser.getLink("Register a branch").click() |
811 | 138 | Traceback (most recent call last): | ||
812 | 139 | ... | ||
813 | 140 | LinkNotFoundError | ||
814 | 141 | |||
815 | 142 | The link is not there because the product has not been configured to | ||
816 | 143 | do code hosting yet. The development focus must be set with a branch first. | ||
817 | 144 | |||
818 | 145 | >>> owner_browser = setupBrowser(auth="Basic foo.bar@canonical.com:test") | ||
819 | 146 | >>> owner_browser.open('http://code.launchpad.dev/applets') | ||
820 | 147 | >>> owner_browser.getLink('Configure code hosting').click() | ||
821 | 148 | >>> print owner_browser.url | ||
822 | 149 | http://code.launchpad.dev/applets/trunk/+setbranch | ||
823 | 150 | |||
824 | 151 | >>> owner_browser.getControl( | ||
825 | 152 | ... 'Create a new, empty branch').click() | ||
826 | 153 | >>> owner_browser.getControl('Branch name').value = 'trunk' | ||
827 | 154 | >>> owner_browser.getControl('Update').click() | ||
828 | 155 | |||
829 | 156 | Now that code hosting has been configured, a regular user will be able | ||
830 | 157 | to register a branch. | ||
831 | 158 | |||
832 | 159 | >>> user_browser.open('http://code.launchpad.dev/applets') | ||
833 | 160 | >>> user_browser.getLink("Register a branch").click() | ||
834 | 125 | >>> print user_browser.url | 161 | >>> print user_browser.url |
835 | 126 | http://code.launchpad.dev/applets/+addbranch | 162 | http://code.launchpad.dev/applets/+addbranch |
836 | 127 | 163 | ||
837 | @@ -313,7 +349,7 @@ | |||
838 | 313 | URL validation should check that the entered URL is not the root of a | 349 | URL validation should check that the entered URL is not the root of a |
839 | 314 | site. | 350 | site. |
840 | 315 | 351 | ||
842 | 316 | >>> user_browser.open('http://code.launchpad.dev/firefox') | 352 | >>> user_browser.open('http://code.launchpad.dev/applets') |
843 | 317 | >>> user_browser.getLink("Register a branch").click() | 353 | >>> user_browser.getLink("Register a branch").click() |
844 | 318 | >>> user_browser.getControl('Branch URL').value = 'http://example.com' | 354 | >>> user_browser.getControl('Branch URL').value = 'http://example.com' |
845 | 319 | >>> user_browser.getControl('Name').value = 'unique-name' | 355 | >>> user_browser.getControl('Name').value = 'unique-name' |
846 | @@ -326,7 +362,7 @@ | |||
847 | 326 | 362 | ||
848 | 327 | URL validation should check that the entered URL is not from Launchpad. | 363 | URL validation should check that the entered URL is not from Launchpad. |
849 | 328 | 364 | ||
851 | 329 | >>> user_browser.open('http://code.launchpad.dev/firefox') | 365 | >>> user_browser.open('http://code.launchpad.dev/applets') |
852 | 330 | >>> user_browser.getLink("Register a branch").click() | 366 | >>> user_browser.getLink("Register a branch").click() |
853 | 331 | >>> user_browser.getControl('Branch URL').value = ( | 367 | >>> user_browser.getControl('Branch URL').value = ( |
854 | 332 | ... 'http://code.launchpad.dev/~testuser/') | 368 | ... 'http://code.launchpad.dev/~testuser/') |
855 | @@ -341,7 +377,7 @@ | |||
856 | 341 | As well as checking against the root site set in the config, a check is | 377 | As well as checking against the root site set in the config, a check is |
857 | 342 | also done against the value stored as a database constraint. | 378 | also done against the value stored as a database constraint. |
858 | 343 | 379 | ||
860 | 344 | >>> user_browser.open('http://code.launchpad.dev/firefox') | 380 | >>> user_browser.open('http://code.launchpad.dev/applets') |
861 | 345 | >>> user_browser.getLink("Register a branch").click() | 381 | >>> user_browser.getLink("Register a branch").click() |
862 | 346 | >>> user_browser.getControl('Branch URL').value = ( | 382 | >>> user_browser.getControl('Branch URL').value = ( |
863 | 347 | ... 'http://bazaar.launchpad.net/foo/bar/') | 383 | ... 'http://bazaar.launchpad.net/foo/bar/') |
864 | @@ -377,6 +413,14 @@ | |||
865 | 377 | When registering a branch from the product pages, there is no product | 413 | When registering a branch from the product pages, there is no product |
866 | 378 | widget, so errors are set at the page level. | 414 | widget, so errors are set at the page level. |
867 | 379 | 415 | ||
868 | 416 | >>> owner_browser = setupBrowser(auth="Basic test@canonical.com:test") | ||
869 | 417 | >>> owner_browser.open('http://code.launchpad.dev/landscape') | ||
870 | 418 | >>> owner_browser.getLink('Configure code hosting').click() | ||
871 | 419 | >>> owner_browser.getControl( | ||
872 | 420 | ... 'Create a new, empty branch').click() | ||
873 | 421 | >>> owner_browser.getControl('Branch name').value = 'trunk' | ||
874 | 422 | >>> owner_browser.getControl('Update').click() | ||
875 | 423 | |||
876 | 380 | >>> user_browser.open('http://code.launchpad.dev/landscape') | 424 | >>> user_browser.open('http://code.launchpad.dev/landscape') |
877 | 381 | >>> user_browser.getLink("Register a branch").click() | 425 | >>> user_browser.getLink("Register a branch").click() |
878 | 382 | >>> user_browser.getControl('Branch URL').value = 'http://foo.com/bar' | 426 | >>> user_browser.getControl('Branch URL').value = 'http://foo.com/bar' |
879 | 383 | 427 | ||
880 | === modified file 'lib/lp/code/stories/branches/xx-person-branches.txt' | |||
881 | --- lib/lp/code/stories/branches/xx-person-branches.txt 2010-05-27 02:19:27 +0000 | |||
882 | +++ lib/lp/code/stories/branches/xx-person-branches.txt 2010-09-28 22:26:02 +0000 | |||
883 | @@ -101,7 +101,7 @@ | |||
884 | 101 | >>> eric_browser.open('http://code.launchpad.dev/~eric') | 101 | >>> eric_browser.open('http://code.launchpad.dev/~eric') |
885 | 102 | >>> print_tag_with_id(eric_browser.contents, 'page-summary') | 102 | >>> print_tag_with_id(eric_browser.contents, 'page-summary') |
886 | 103 | 1 owned branch, 1 registered branch, 1 subscribed branch | 103 | 1 owned branch, 1 registered branch, 1 subscribed branch |
888 | 104 | 0 active reviews or unmerged proposals | 104 | 0 active reviews |
889 | 105 | 105 | ||
890 | 106 | Now we'll create another branch, and unsubscribe the owner from it. | 106 | Now we'll create another branch, and unsubscribe the owner from it. |
891 | 107 | 107 | ||
892 | @@ -113,4 +113,4 @@ | |||
893 | 113 | >>> eric_browser.open('http://code.launchpad.dev/~eric') | 113 | >>> eric_browser.open('http://code.launchpad.dev/~eric') |
894 | 114 | >>> print_tag_with_id(eric_browser.contents, 'page-summary') | 114 | >>> print_tag_with_id(eric_browser.contents, 'page-summary') |
895 | 115 | 2 owned branches, 2 registered branches, 1 subscribed branch | 115 | 2 owned branches, 2 registered branches, 1 subscribed branch |
897 | 116 | 0 active reviews or unmerged proposals | 116 | 0 active reviews |
898 | 117 | 117 | ||
899 | === modified file 'lib/lp/code/stories/branches/xx-private-branch-listings.txt' | |||
900 | --- lib/lp/code/stories/branches/xx-private-branch-listings.txt 2010-09-03 00:25:07 +0000 | |||
901 | +++ lib/lp/code/stories/branches/xx-private-branch-listings.txt 2010-09-28 22:26:02 +0000 | |||
902 | @@ -1,4 +1,5 @@ | |||
904 | 1 | = Private Branch Listings = | 1 | Private Branch Listings |
905 | 2 | ======================= | ||
906 | 2 | 3 | ||
907 | 3 | All pages that show branch listings to users should only show branches | 4 | All pages that show branch listings to users should only show branches |
908 | 4 | that the user is allowed to see. | 5 | that the user is allowed to see. |
909 | @@ -15,7 +16,8 @@ | |||
910 | 15 | ... reset_all_branch_last_modified) | 16 | ... reset_all_branch_last_modified) |
911 | 16 | >>> reset_all_branch_last_modified() | 17 | >>> reset_all_branch_last_modified() |
912 | 17 | 18 | ||
914 | 18 | == Additional sample data == | 19 | Additional sample data |
915 | 20 | ---------------------- | ||
916 | 19 | 21 | ||
917 | 20 | Adding a private branch that is only visible by No Privileges Person | 22 | Adding a private branch that is only visible by No Privileges Person |
918 | 21 | (and Launchpad administrators). | 23 | (and Launchpad administrators). |
919 | @@ -37,7 +39,8 @@ | |||
920 | 37 | >>> logout() | 39 | >>> logout() |
921 | 38 | 40 | ||
922 | 39 | 41 | ||
924 | 40 | == The code home page == | 42 | The code home page |
925 | 43 | ------------------ | ||
926 | 41 | 44 | ||
927 | 42 | The code home page shows lists of recently imported, changed, and | 45 | The code home page shows lists of recently imported, changed, and |
928 | 43 | registered branches. | 46 | registered branches. |
929 | @@ -60,7 +63,8 @@ | |||
930 | 60 | Logged in users should only be able to see public branches, and private | 63 | Logged in users should only be able to see public branches, and private |
931 | 61 | branches that they are subscribed to or are the owner of. | 64 | branches that they are subscribed to or are the owner of. |
932 | 62 | 65 | ||
934 | 63 | >>> no_priv_browser = setupBrowser(auth='Basic no-priv@canonical.com:test') | 66 | >>> no_priv_browser = setupBrowser( |
935 | 67 | ... auth='Basic no-priv@canonical.com:test') | ||
936 | 64 | >>> print_recently_registered_branches(no_priv_browser) | 68 | >>> print_recently_registered_branches(no_priv_browser) |
937 | 65 | '...~no-priv/landscape/testing-branch...<span...class="sprite private"...' | 69 | '...~no-priv/landscape/testing-branch...<span...class="sprite private"...' |
938 | 66 | '...~mark/+junk/testdoc...' | 70 | '...~mark/+junk/testdoc...' |
939 | @@ -91,7 +95,8 @@ | |||
940 | 91 | '...~name12/gnome-terminal/scanned...' | 95 | '...~name12/gnome-terminal/scanned...' |
941 | 92 | 96 | ||
942 | 93 | 97 | ||
944 | 94 | == Landscape code listing page == | 98 | Landscape code listing page |
945 | 99 | --------------------------- | ||
946 | 95 | 100 | ||
947 | 96 | One of the most obvious places to hide private branches are the code | 101 | One of the most obvious places to hide private branches are the code |
948 | 97 | listing tab. | 102 | listing tab. |
949 | @@ -103,12 +108,13 @@ | |||
950 | 103 | ... # So print the text shown in the application summary. | 108 | ... # So print the text shown in the application summary. |
951 | 104 | ... if table is None: | 109 | ... if table is None: |
952 | 105 | ... print extract_text(find_tag_by_id( | 110 | ... print extract_text(find_tag_by_id( |
954 | 106 | ... browser.contents, 'application-summary')) | 111 | ... browser.contents, 'branch-summary')) |
955 | 107 | ... else: | 112 | ... else: |
956 | 108 | ... for row in table.tbody.fetch('tr'): | 113 | ... for row in table.tbody.fetch('tr'): |
957 | 109 | ... print extract_text(row) | 114 | ... print extract_text(row) |
958 | 110 | 115 | ||
959 | 111 | >>> print_landscape_code_listing(anon_browser) | 116 | >>> print_landscape_code_listing(anon_browser) |
960 | 117 | Launchpad does not know where The Landscape Project hosts its code... | ||
961 | 112 | There are no branches for The Landscape Project in Launchpad... | 118 | There are no branches for The Landscape Project in Launchpad... |
962 | 113 | 119 | ||
963 | 114 | >>> print_landscape_code_listing(no_priv_browser) | 120 | >>> print_landscape_code_listing(no_priv_browser) |
964 | @@ -124,7 +130,8 @@ | |||
965 | 124 | lp://dev/~no-priv/landscape/testing-branch Development ... | 130 | lp://dev/~no-priv/landscape/testing-branch Development ... |
966 | 125 | 131 | ||
967 | 126 | 132 | ||
969 | 127 | == Person code listing pages == | 133 | Person code listing pages |
970 | 134 | ------------------------- | ||
971 | 128 | 135 | ||
972 | 129 | The person code listings is the other obvious place to filter out the | 136 | The person code listings is the other obvious place to filter out the |
973 | 130 | viewable branches. | 137 | viewable branches. |
974 | @@ -173,7 +180,8 @@ | |||
975 | 173 | >>> print_person_code_listing(landscape_dev_browser, '/+ownedbranches') | 180 | >>> print_person_code_listing(landscape_dev_browser, '/+ownedbranches') |
976 | 174 | Total of 10 branches listed | 181 | Total of 10 branches listed |
977 | 175 | lp://dev/~name12/landscape/feature-x Development ... | 182 | lp://dev/~name12/landscape/feature-x Development ... |
979 | 176 | >>> print_person_code_listing(landscape_dev_browser, '/+registeredbranches') | 183 | >>> print_person_code_listing(landscape_dev_browser, |
980 | 184 | ... '/+registeredbranches') | ||
981 | 177 | Total of 11 branches listed | 185 | Total of 11 branches listed |
982 | 178 | lp://dev/~landscape-developers/landscape/trunk Development ... | 186 | lp://dev/~landscape-developers/landscape/trunk Development ... |
983 | 179 | lp://dev/~name12/landscape/feature-x Development ... | 187 | lp://dev/~name12/landscape/feature-x Development ... |
984 | @@ -190,7 +198,8 @@ | |||
985 | 190 | lp://dev/~name12/landscape/feature-x Development ... | 198 | lp://dev/~name12/landscape/feature-x Development ... |
986 | 191 | 199 | ||
987 | 192 | 200 | ||
989 | 193 | == Bug branch links == | 201 | Bug branch links |
990 | 202 | ---------------- | ||
991 | 194 | 203 | ||
992 | 195 | When a private branch is linked to a bug, the bug branch link is only | 204 | When a private branch is linked to a bug, the bug branch link is only |
993 | 196 | visible to those that would be able to see the branch. | 205 | visible to those that would be able to see the branch. |
994 | @@ -227,7 +236,8 @@ | |||
995 | 227 | No bug branch links | 236 | No bug branch links |
996 | 228 | 237 | ||
997 | 229 | 238 | ||
999 | 230 | == Branches set as primary branches for product series == | 239 | Branches set as primary branches for product series |
1000 | 240 | --------------------------------------------------- | ||
1001 | 231 | 241 | ||
1002 | 232 | When a branch is set as the user branch for product series, the details | 242 | When a branch is set as the user branch for product series, the details |
1003 | 233 | must be visible to those that are entitled to see it, but hidden from | 243 | must be visible to those that are entitled to see it, but hidden from |
1004 | 234 | 244 | ||
1005 | === modified file 'lib/lp/code/stories/branches/xx-product-branches.txt' | |||
1006 | --- lib/lp/code/stories/branches/xx-product-branches.txt 2010-08-19 14:22:01 +0000 | |||
1007 | +++ lib/lp/code/stories/branches/xx-product-branches.txt 2010-09-28 22:26:02 +0000 | |||
1008 | @@ -34,25 +34,34 @@ | |||
1009 | 34 | If there are not any branches, a helpful message is shown. | 34 | If there are not any branches, a helpful message is shown. |
1010 | 35 | 35 | ||
1011 | 36 | >>> def get_summary(browser): | 36 | >>> def get_summary(browser): |
1013 | 37 | ... return find_tag_by_id(browser.contents, 'application-summary') | 37 | ... return find_tag_by_id(browser.contents, 'branch-summary') |
1014 | 38 | >>> summary = get_summary(browser) | 38 | >>> summary = get_summary(browser) |
1015 | 39 | >>> print extract_text(summary) | 39 | >>> print extract_text(summary) |
1017 | 40 | There are no branches for Gnome Applets in Launchpad. | 40 | Launchpad does not know where The Gnome Panel Applets hosts its code. |
1018 | 41 | There are no branches for Gnome Applets in Launchpad. You can change this by: | ||
1019 | 42 | activating code hosting directly on Launchpad. (read more) | ||
1020 | 43 | asking Launchpad to mirror a Bazaar branch hosted elsewhere. (read more) | ||
1021 | 44 | asking Launchpad to import code from Git, Subversion, or CVS into a | ||
1022 | 45 | Bazaar branch. (read more) | ||
1023 | 46 | Getting started with code hosting in Launchpad. | ||
1024 | 47 | |||
1025 | 48 | |||
1026 | 41 | If there are Bazaar branches of Gnome Applets in a publicly | 49 | If there are Bazaar branches of Gnome Applets in a publicly |
1027 | 42 | accessible location, Launchpad can act as a mirror of the branch | 50 | accessible location, Launchpad can act as a mirror of the branch |
1028 | 43 | by registering a Mirrored branch. Read more. | 51 | by registering a Mirrored branch. Read more. |
1029 | 44 | Launchpad can also act as a primary location for Bazaar branches of | 52 | Launchpad can also act as a primary location for Bazaar branches of |
1030 | 45 | Gnome Applets. Read more. | 53 | Gnome Applets. Read more. |
1031 | 46 | Launchpad can import code from CVS, Subversion, Mercurial or Git | 54 | Launchpad can import code from CVS, Subversion, Mercurial or Git |
1033 | 47 | into Bazaar branches. Read more. | 55 | into Bazaar branches. Read more... |
1034 | 48 | 56 | ||
1035 | 49 | The 'Read more' links go to the help wiki. | 57 | The 'Read more' links go to the help wiki. |
1036 | 50 | 58 | ||
1037 | 51 | >>> for anchor in summary.fetch('a'): | 59 | >>> for anchor in summary.fetch('a'): |
1038 | 52 | ... print anchor['href'] | 60 | ... print anchor['href'] |
1039 | 61 | https://help.launchpad.net/Code/UploadingABranch | ||
1040 | 53 | https://help.launchpad.net/Code/MirroredBranches | 62 | https://help.launchpad.net/Code/MirroredBranches |
1041 | 54 | https://help.launchpad.net/Code/UploadingABranch | ||
1042 | 55 | https://help.launchpad.net/VcsImports | 63 | https://help.launchpad.net/VcsImports |
1043 | 64 | https://help.launchpad.net/Code | ||
1044 | 56 | 65 | ||
1045 | 57 | 66 | ||
1046 | 58 | Link to the product downloads | 67 | Link to the product downloads |
1047 | @@ -63,6 +72,7 @@ | |||
1048 | 63 | 72 | ||
1049 | 64 | >>> browser.open('http://code.launchpad.dev/netapplet') | 73 | >>> browser.open('http://code.launchpad.dev/netapplet') |
1050 | 65 | >>> print extract_text(get_summary(browser)) | 74 | >>> print extract_text(get_summary(browser)) |
1051 | 75 | Launchpad does not know where Network Applet hosts its code... | ||
1052 | 66 | There are no branches for NetApplet in Launchpad. | 76 | There are no branches for NetApplet in Launchpad. |
1053 | 67 | ... | 77 | ... |
1054 | 68 | There are download files available for NetApplet. | 78 | There are download files available for NetApplet. |
1055 | @@ -83,8 +93,6 @@ | |||
1056 | 83 | >>> browser.open('http://code.launchpad.dev/evolution') | 93 | >>> browser.open('http://code.launchpad.dev/evolution') |
1057 | 84 | >>> summary = get_summary(browser) | 94 | >>> summary = get_summary(browser) |
1058 | 85 | >>> print extract_text(get_summary(browser)) | 95 | >>> print extract_text(get_summary(browser)) |
1059 | 86 | 3 active branches ... | ||
1060 | 87 | 0 active reviews or unmerged proposals | ||
1061 | 88 | You can get a copy of the development focus branch using the | 96 | You can get a copy of the development focus branch using the |
1062 | 89 | command: | 97 | command: |
1063 | 90 | bzr branch lp://dev/evolution | 98 | bzr branch lp://dev/evolution |
1064 | @@ -124,7 +132,8 @@ | |||
1065 | 124 | Firstly lets associate release--0.9.1 with the 1.0 series. | 132 | Firstly lets associate release--0.9.1 with the 1.0 series. |
1066 | 125 | 133 | ||
1067 | 126 | >>> admin_browser.open('http://launchpad.dev/firefox/1.0/+linkbranch') | 134 | >>> admin_browser.open('http://launchpad.dev/firefox/1.0/+linkbranch') |
1069 | 127 | >>> admin_browser.getControl('Branch').value = '~mark/firefox/release--0.9.1' | 135 | >>> admin_browser.getControl('Branch').value = ( |
1070 | 136 | ... '~mark/firefox/release--0.9.1') | ||
1071 | 128 | >>> admin_browser.getControl('Update').click() | 137 | >>> admin_browser.getControl('Update').click() |
1072 | 129 | 138 | ||
1073 | 130 | >>> browser.open('http://code.launchpad.dev/firefox') | 139 | >>> browser.open('http://code.launchpad.dev/firefox') |
1074 | @@ -155,32 +164,78 @@ | |||
1075 | 155 | lp://dev/~mark/firefox/release-0.8 Development ... | 164 | lp://dev/~mark/firefox/release-0.8 Development ... |
1076 | 156 | 165 | ||
1077 | 157 | 166 | ||
1080 | 158 | Floating buttons | 167 | Involvement portlet |
1081 | 159 | ================ | 168 | =================== |
1082 | 160 | 169 | ||
1085 | 161 | There are two buttons that show on the right hand side of the screen | 170 | There are several links in the side portlet: 'Register a branch', |
1086 | 162 | for project branch listings. 'Register a branch' and 'Import a branch'. | 171 | 'Import a branch', 'Configure code hosting', and 'Define branch |
1087 | 172 | visibility'. The links are only shown if the user has permission to | ||
1088 | 173 | perform the task. | ||
1089 | 163 | 174 | ||
1090 | 164 | >>> from zope.component import getUtility | 175 | >>> from zope.component import getUtility |
1091 | 165 | >>> from lp.registry.interfaces.product import IProductSet | 176 | >>> from lp.registry.interfaces.product import IProductSet |
1093 | 166 | >>> login('admin@canonical.com') | 177 | >>> login(ANONYMOUS) |
1094 | 167 | >>> product = getUtility(IProductSet).getByName('firefox') | 178 | >>> product = getUtility(IProductSet).getByName('firefox') |
1095 | 168 | >>> old_branch = product.development_focus.branch | 179 | >>> old_branch = product.development_focus.branch |
1096 | 180 | >>> login_person(product.owner) | ||
1097 | 169 | >>> product.development_focus.branch = None | 181 | >>> product.development_focus.branch = None |
1098 | 170 | >>> logout() | 182 | >>> logout() |
1099 | 171 | >>> def print_links(browser): | 183 | >>> def print_links(browser): |
1101 | 172 | ... links = find_tag_by_id(browser.contents, 'floating-links') | 184 | ... links = find_tag_by_id(browser.contents, 'involvement') |
1102 | 185 | ... if links is None: | ||
1103 | 186 | ... print 'None' | ||
1104 | 187 | ... return | ||
1105 | 173 | ... for link in links.findAll('a'): | 188 | ... for link in links.findAll('a'): |
1106 | 174 | ... print extract_text(link) | 189 | ... print extract_text(link) |
1109 | 175 | >>> browser.open('http://code.launchpad.dev/firefox') | 190 | |
1110 | 176 | >>> print_links(browser) | 191 | >>> def setup_code_hosting(productname): |
1111 | 192 | ... admin_browser.open('http://code.launchpad.dev/%s' % productname) | ||
1112 | 193 | ... admin_browser.getLink('Configure code hosting').click() | ||
1113 | 194 | ... admin_browser.getControl( | ||
1114 | 195 | ... 'Create a new, empty branch').click() | ||
1115 | 196 | ... admin_browser.getControl('Branch name').value = 'trunk' | ||
1116 | 197 | ... admin_browser.getControl('Update').click() | ||
1117 | 198 | |||
1118 | 199 | The involvement portlet is not shown if the product does not have code | ||
1119 | 200 | hosting configured or if it is not using Launchpad. | ||
1120 | 201 | |||
1121 | 202 | >>> print product.codehosting_usage.name | ||
1122 | 203 | UNKNOWN | ||
1123 | 204 | >>> admin_browser.open('http://code.launchpad.dev/firefox') | ||
1124 | 205 | >>> print_links(admin_browser) | ||
1125 | 206 | None | ||
1126 | 207 | |||
1127 | 208 | >>> setup_code_hosting('firefox') | ||
1128 | 209 | >>> print product.codehosting_usage.name | ||
1129 | 210 | LAUNCHPAD | ||
1130 | 211 | >>> admin_browser.open('http://code.launchpad.dev/firefox') | ||
1131 | 212 | >>> print_links(admin_browser) | ||
1132 | 213 | Register a branch | ||
1133 | 214 | Import a branch | ||
1134 | 215 | Configure code hosting | ||
1135 | 216 | Define branch visibility | ||
1136 | 217 | |||
1137 | 218 | The owner of the project sees the links for the activities he can | ||
1138 | 219 | perform, everything except defining branch visibility. | ||
1139 | 220 | |||
1140 | 221 | >>> owner_browser = setupBrowser(auth='Basic test@canonical.com:test') | ||
1141 | 222 | >>> owner_browser.open('http://code.launchpad.dev/firefox') | ||
1142 | 223 | >>> print_links(owner_browser) | ||
1143 | 224 | Register a branch | ||
1144 | 225 | Import a branch | ||
1145 | 226 | Configure code hosting | ||
1146 | 227 | |||
1147 | 228 | And a regular user can only register and import branches. | ||
1148 | 229 | |||
1149 | 230 | >>> user_browser.open('http://code.launchpad.dev/firefox') | ||
1150 | 231 | >>> print_links(user_browser) | ||
1151 | 177 | Register a branch | 232 | Register a branch |
1152 | 178 | Import a branch | 233 | Import a branch |
1153 | 179 | 234 | ||
1154 | 180 | If the product specifies that it officially uses Launchpad code, then | 235 | If the product specifies that it officially uses Launchpad code, then |
1155 | 181 | the 'Import a branch' button is still shown. | 236 | the 'Import a branch' button is still shown. |
1156 | 182 | 237 | ||
1158 | 183 | >>> login('admin@canonical.com') | 238 | >>> login_person(product.owner) |
1159 | 184 | >>> product.development_focus.branch = old_branch | 239 | >>> product.development_focus.branch = old_branch |
1160 | 185 | >>> logout() | 240 | >>> logout() |
1161 | 186 | >>> browser.open('http://code.launchpad.dev/firefox') | 241 | >>> browser.open('http://code.launchpad.dev/firefox') |
1162 | @@ -189,34 +244,54 @@ | |||
1163 | 189 | Import a branch | 244 | Import a branch |
1164 | 190 | 245 | ||
1165 | 191 | 246 | ||
1168 | 192 | Nice wording of summary numbers | 247 | The statistics portlet |
1169 | 193 | =============================== | 248 | ====================== |
1170 | 194 | 249 | ||
1171 | 195 | The text that is shown giving a summary of the number of branches | 250 | The text that is shown giving a summary of the number of branches |
1172 | 196 | shows correct singular and plural forms. | 251 | shows correct singular and plural forms. |
1173 | 197 | 252 | ||
1175 | 198 | >>> def print_summary(product): | 253 | >>> def get_stats_portlet(browser): |
1176 | 254 | ... return find_tag_by_id( | ||
1177 | 255 | ... browser.contents, | ||
1178 | 256 | ... 'portlet-product-codestatistics') | ||
1179 | 257 | >>> def print_portlet(product): | ||
1180 | 199 | ... browser.open('http://code.launchpad.dev/%s' % product) | 258 | ... browser.open('http://code.launchpad.dev/%s' % product) |
1186 | 200 | ... print extract_text(get_summary(browser)) | 259 | ... portlet = get_stats_portlet(browser) |
1187 | 201 | 260 | ... if portlet is None: | |
1188 | 202 | >>> print_summary('gnome-terminal') | 261 | ... print 'None' |
1189 | 203 | 8 active branches owned by 1 person and 2 teams, 0 commits in the last month | 262 | ... else: |
1190 | 204 | ... | 263 | ... print extract_text(portlet) |
1191 | 264 | |||
1192 | 265 | >>> setup_code_hosting('gnome-terminal') | ||
1193 | 266 | >>> print_portlet('gnome-terminal') | ||
1194 | 267 | 0 Active reviews | ||
1195 | 268 | 9 Active branches owned by 2 people and 2 teams | ||
1196 | 269 | 0 Commits in the last month | ||
1197 | 270 | |||
1198 | 205 | >>> from lp.testing import ANONYMOUS, login, logout | 271 | >>> from lp.testing import ANONYMOUS, login, logout |
1199 | 206 | >>> login(ANONYMOUS) | 272 | >>> login(ANONYMOUS) |
1200 | 207 | >>> fooix = factory.makeProduct('fooix') | 273 | >>> fooix = factory.makeProduct('fooix') |
1201 | 208 | >>> ignored = factory.makeProductBranch(fooix) | 274 | >>> ignored = factory.makeProductBranch(fooix) |
1213 | 209 | >>> ignored = factory.makeProductBranch(fooix) | 275 | >>> logout() |
1214 | 210 | >>> logout() | 276 | >>> setup_code_hosting('fooix') |
1215 | 211 | >>> print_summary('fooix') | 277 | >>> print_portlet('fooix') |
1216 | 212 | 2 active branches owned by 2 people, 0 commits in the last month | 278 | 0 Active reviews |
1217 | 213 | ... | 279 | 2 Active branches owned by 2 people |
1218 | 214 | >>> print_summary('evolution') | 280 | 0 Commits in the last month |
1219 | 215 | 3 active branches owned by 1 person and 1 team, 0 commits in the last month | 281 | |
1220 | 216 | ... | 282 | >>> print_portlet('evolution') |
1221 | 217 | >>> print_summary('iso-codes') | 283 | 0 Active reviews |
1222 | 218 | 1 active branch owned by 1 person, 0 commits in the last month | 284 | 3 Active branches owned by 1 person and 1 team |
1223 | 219 | ... | 285 | 0 Commits in the last month |
1224 | 286 | |||
1225 | 287 | >>> login(ANONYMOUS) | ||
1226 | 288 | >>> dinky = factory.makeProduct('dinky') | ||
1227 | 289 | >>> logout() | ||
1228 | 290 | >>> setup_code_hosting('dinky') | ||
1229 | 291 | >>> print_portlet('dinky') | ||
1230 | 292 | 0 Active reviews | ||
1231 | 293 | 1 Active branch owned by 1 person | ||
1232 | 294 | 0 Commits in the last month | ||
1233 | 220 | 295 | ||
1234 | 221 | 296 | ||
1235 | 222 | Product has Branches, but none initially visible | 297 | Product has Branches, but none initially visible |
1236 | @@ -224,17 +299,16 @@ | |||
1237 | 224 | 299 | ||
1238 | 225 | It is a bit of an edge case, but if there are branches for a product but all | 300 | It is a bit of an edge case, but if there are branches for a product but all |
1239 | 226 | of them are either merged or abandoned and there is no development focus | 301 | of them are either merged or abandoned and there is no development focus |
1241 | 227 | branch, then they will not appear on the initial branch listing. | 302 | branch, then they will not appear on the initial branch listing and |
1242 | 303 | the portlets will not be shown. | ||
1243 | 228 | 304 | ||
1244 | 229 | >>> admin_browser.open('http://code.launchpad.dev/~carlos/iso-codes/0.35') | 305 | >>> admin_browser.open('http://code.launchpad.dev/~carlos/iso-codes/0.35') |
1245 | 230 | >>> admin_browser.getLink('Change branch details').click() | 306 | >>> admin_browser.getLink('Change branch details').click() |
1246 | 231 | >>> admin_browser.getControl('Abandoned').click() | 307 | >>> admin_browser.getControl('Abandoned').click() |
1247 | 232 | >>> admin_browser.getControl('Change Branch').click() | 308 | >>> admin_browser.getControl('Change Branch').click() |
1248 | 233 | 309 | ||
1253 | 234 | >>> browser.open('http://code.launchpad.dev/iso-codes') | 310 | >>> print_portlet('iso-codes') |
1254 | 235 | >>> print extract_text(get_summary(browser)) | 311 | None |
1251 | 236 | 0 active branches, 0 commits in the last month | ||
1252 | 237 | 0 active reviews or unmerged proposals | ||
1255 | 238 | 312 | ||
1256 | 239 | >>> message = find_tag_by_id(browser.contents, 'no-branch-message') | 313 | >>> message = find_tag_by_id(browser.contents, 'no-branch-message') |
1257 | 240 | >>> print extract_text(message) | 314 | >>> print extract_text(message) |
1258 | 241 | 315 | ||
1259 | === modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt' | |||
1260 | --- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2010-08-14 16:39:07 +0000 | |||
1261 | +++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2010-09-28 22:26:02 +0000 | |||
1262 | @@ -28,7 +28,27 @@ | |||
1263 | 28 | 28 | ||
1264 | 29 | >>> browser.open('http://code.launchpad.dev/firefox') | 29 | >>> browser.open('http://code.launchpad.dev/firefox') |
1265 | 30 | >>> browser.getLink('Import a branch').click() | 30 | >>> browser.getLink('Import a branch').click() |
1267 | 31 | 31 | Traceback (most recent call last): | |
1268 | 32 | ... | ||
1269 | 33 | LinkNotFoundError | ||
1270 | 34 | |||
1271 | 35 | The owner can configure code hosting for the project and then | ||
1272 | 36 | importing will be available to any user. | ||
1273 | 37 | |||
1274 | 38 | >>> owner_browser = setupBrowser(auth="Basic test@canonical.com:test") | ||
1275 | 39 | >>> owner_browser.open('http://code.launchpad.dev/firefox') | ||
1276 | 40 | >>> owner_browser.getLink('Configure code hosting').click() | ||
1277 | 41 | >>> owner_browser.getControl( | ||
1278 | 42 | ... 'Import a branch').click() | ||
1279 | 43 | >>> owner_browser.getControl('Branch URL').value = 'git://example.com/firefox' | ||
1280 | 44 | >>> owner_browser.getControl('Git').click() | ||
1281 | 45 | >>> owner_browser.getControl('Branch name').value = 'trunk' | ||
1282 | 46 | >>> owner_browser.getControl('Update').click() | ||
1283 | 47 | |||
1284 | 48 | Now a regular user can import another branch. | ||
1285 | 49 | |||
1286 | 50 | >>> browser.open('http://code.launchpad.dev/firefox') | ||
1287 | 51 | >>> browser.getLink('Import a branch').click() | ||
1288 | 32 | 52 | ||
1289 | 33 | Requesting a Subversion import | 53 | Requesting a Subversion import |
1290 | 34 | ============================== | 54 | ============================== |
1291 | 35 | 55 | ||
1292 | === modified file 'lib/lp/code/templates/branch-listing.pt' | |||
1293 | --- lib/lp/code/templates/branch-listing.pt 2010-04-11 22:45:09 +0000 | |||
1294 | +++ lib/lp/code/templates/branch-listing.pt 2010-09-28 22:26:02 +0000 | |||
1295 | @@ -74,16 +74,16 @@ | |||
1296 | 74 | </tal:ignore> | 74 | </tal:ignore> |
1297 | 75 | <tr tal:condition="product/required:launchpad.Edit" | 75 | <tr tal:condition="product/required:launchpad.Edit" |
1298 | 76 | tal:define="edit_link product/development_focus/fmt:url/+linkbranchtoseries"> | 76 | tal:define="edit_link product/development_focus/fmt:url/+linkbranchtoseries"> |
1300 | 77 | <td colspan="5" class="branch-no-dev-focus">A development focus branch hasn't | 77 | <td colspan="5" class="branch-no-dev-focus">A development focus branch hasn't |
1301 | 78 | been specified, <a tal:attributes="href edit_link">set it now</a>.</td> | 78 | been specified, <a tal:attributes="href edit_link">set it now</a>.</td> |
1302 | 79 | </tr> | 79 | </tr> |
1303 | 80 | </tal:missing-dev-focus> | 80 | </tal:missing-dev-focus> |
1304 | 81 | </tal:allow-setting-dev-focus> | 81 | </tal:allow-setting-dev-focus> |
1305 | 82 | <tr tal:repeat="branch context/branches"> | 82 | <tr tal:repeat="branch context/branches"> |
1306 | 83 | <td> | 83 | <td> |
1310 | 84 | <img src="/@@/branch" /> <a tal:attributes="href branch/fmt:url" | 84 | <a tal:attributes="href branch/fmt:url" |
1311 | 85 | tal:content="branch/bzr_identity"> | 85 | tal:content="structure branch/bzr_identity/fmt:break-long-words" |
1312 | 86 | Name | 86 | class="sprite branch">Name |
1313 | 87 | </a> | 87 | </a> |
1314 | 88 | <tal:associated-series repeat="series branch/active_series" | 88 | <tal:associated-series repeat="series branch/active_series" |
1315 | 89 | condition="context/view/show_series_links"> | 89 | condition="context/view/show_series_links"> |
1316 | 90 | 90 | ||
1317 | === modified file 'lib/lp/code/templates/product-branch-summary.pt' | |||
1318 | --- lib/lp/code/templates/product-branch-summary.pt 2010-08-13 16:09:45 +0000 | |||
1319 | +++ lib/lp/code/templates/product-branch-summary.pt 2010-09-28 22:26:02 +0000 | |||
1320 | @@ -7,42 +7,59 @@ | |||
1321 | 7 | lang="en" | 7 | lang="en" |
1322 | 8 | dir="ltr" | 8 | dir="ltr" |
1323 | 9 | i18n:domain="launchpad" | 9 | i18n:domain="launchpad" |
1325 | 10 | id="application-summary"> | 10 | id="branch-summary"> |
1326 | 11 | |||
1327 | 12 | <div id="unknown" tal:condition="context/codehosting_usage/enumvalue:UNKNOWN"> | ||
1328 | 13 | <p> | ||
1329 | 14 | <strong> | ||
1330 | 15 | Launchpad does not know where <tal:project_title replace="context/title" /> | ||
1331 | 16 | hosts its code. | ||
1332 | 17 | </strong> | ||
1333 | 18 | </p> | ||
1334 | 19 | </div> | ||
1335 | 20 | |||
1336 | 21 | <div id="external" | ||
1337 | 22 | tal:condition="context/codehosting_usage/enumvalue:EXTERNAL"> | ||
1338 | 23 | <p> | ||
1339 | 24 | <strong> | ||
1340 | 25 | <tal:project_title replace="context/title" /> hosts its code at | ||
1341 | 26 | <a tal:attributes="href view/mirror_location" | ||
1342 | 27 | tal:content="view/mirror_location"></a>. | ||
1343 | 28 | </strong> | ||
1344 | 29 | </p> | ||
1345 | 30 | <p tal:condition="context/homepageurl"> | ||
1346 | 31 | You can learn more at the project's | ||
1347 | 32 | <a tal:attributes="href context/homepageurl">web page</a>. | ||
1348 | 33 | </p> | ||
1349 | 34 | <p tal:condition="view/branch/branch_type/enumvalue:MIRRORED"> | ||
1350 | 35 | Launchpad has a mirror of the master branch and you can create branches | ||
1351 | 36 | from it. | ||
1352 | 37 | </p> | ||
1353 | 38 | <p tal:condition="view/branch/branch_type/enumvalue:REMOTE"> | ||
1354 | 39 | Launchpad does not have a copy of the remote branch. | ||
1355 | 40 | </p> | ||
1356 | 41 | </div> | ||
1357 | 11 | 42 | ||
1358 | 12 | <tal:no-branches condition="not: view/branch_count"> | 43 | <tal:no-branches condition="not: view/branch_count"> |
1359 | 13 | There are no branches for <tal:project-name replace="context/displayname"/> | 44 | There are no branches for <tal:project-name replace="context/displayname"/> |
1379 | 14 | in Launchpad. | 45 | in Launchpad. You can change this by: |
1380 | 15 | <ul> | 46 | |
1381 | 16 | <li>If there are Bazaar branches of | 47 | <ul class="bulleted" style="margin-top:1em;"> |
1382 | 17 | <tal:project-name replace="context/displayname"/> | 48 | |
1383 | 18 | in a publicly accessible location, | 49 | <li>activating code hosting directly on |
1384 | 19 | Launchpad can act as a mirror of the branch by registering a | 50 | Launchpad. (<a href="https://help.launchpad.net/Code/UploadingABranch">read |
1385 | 20 | <em>Mirrored</em> branch. | 51 | more</a>)</li> |
1386 | 21 | <a href="https://help.launchpad.net/Code/MirroredBranches">Read more.</a> | 52 | |
1387 | 22 | </li> | 53 | <li>asking Launchpad to mirror a Bazaar branch hosted |
1388 | 23 | <li>Launchpad can also act as a primary | 54 | elsewhere. (<a href="https://help.launchpad.net/Code/MirroredBranches">read |
1389 | 24 | location for Bazaar branches of | 55 | more</a>)</li> |
1390 | 25 | <tal:project-name replace="context/displayname"/>. | 56 | |
1391 | 26 | <a href="https://help.launchpad.net/Code/UploadingABranch">Read more.</a> | 57 | <li>asking Launchpad to import code from Git, Subversion, or CVS into a |
1392 | 27 | </li> | 58 | Bazaar branch. (<a href="https://help.launchpad.net/VcsImports">read more</a>)</li> |
1374 | 28 | |||
1375 | 29 | <li>Launchpad can import code from CVS, Subversion, Mercurial or Git | ||
1376 | 30 | into Bazaar branches. <a | ||
1377 | 31 | href="https://help.launchpad.net/VcsImports">Read more.</a> </li> | ||
1378 | 32 | |||
1393 | 33 | </ul> | 59 | </ul> |
1394 | 34 | </tal:no-branches> | 60 | </tal:no-branches> |
1395 | 35 | 61 | ||
1396 | 36 | <tal:has-branches condition="view/branch_count"> | 62 | <tal:has-branches condition="view/branch_count"> |
1397 | 37 | <p tal:replace="structure context/@@+count-summary"/> | ||
1398 | 38 | <p id="merge-counts" | ||
1399 | 39 | tal:define="menu context/menu:branches"> | ||
1400 | 40 | <strong class="count" tal:content="menu/active_review_count">5</strong> | ||
1401 | 41 | <tal:link | ||
1402 | 42 | define="link menu/active_reviews" | ||
1403 | 43 | replace="structure link/render" | ||
1404 | 44 | /> | ||
1405 | 45 | </p> | ||
1406 | 46 | <div tal:condition="view/has_development_focus_branch" | 63 | <div tal:condition="view/has_development_focus_branch" |
1407 | 47 | style="margin: 1em 0" | 64 | style="margin: 1em 0" |
1408 | 48 | tal:define="config modules/canonical.config/config; | 65 | tal:define="config modules/canonical.config/config; |
1409 | @@ -60,6 +77,30 @@ | |||
1410 | 60 | </div> | 77 | </div> |
1411 | 61 | 78 | ||
1412 | 62 | </tal:has-branches> | 79 | </tal:has-branches> |
1413 | 80 | |||
1414 | 81 | <div tal:condition="context/codehosting_usage/enumvalue:UNKNOWN"> | ||
1415 | 82 | <div | ||
1416 | 83 | tal:condition="not: context/codehosting_usage/enumvalue:LAUNCHPAD" | ||
1417 | 84 | tal:define="configure_codehosting view/configure_codehosting | | ||
1418 | 85 | nothing"> | ||
1419 | 86 | <p style="margin-top: 10px;"> | ||
1420 | 87 | <a class="sprite maybe" | ||
1421 | 88 | href="https://help.launchpad.net/Code">Getting started | ||
1422 | 89 | with code hosting in Launchpad</a>.</p> | ||
1423 | 90 | |||
1424 | 91 | <p tal:condition="context/required:launchpad.Edit" | ||
1425 | 92 | id="no-code-edit" | ||
1426 | 93 | > | ||
1427 | 94 | <a tal:condition="configure_codehosting" | ||
1428 | 95 | tal:replace="structure configure_codehosting/fmt:link"/> | ||
1429 | 96 | </p> | ||
1430 | 97 | <p tal:define="menu context/menu:branches; | ||
1431 | 98 | link menu/branch_visibility" | ||
1432 | 99 | tal:condition="link/enabled" | ||
1433 | 100 | tal:content="structure link/render"></p> | ||
1434 | 101 | </div> | ||
1435 | 102 | </div> | ||
1436 | 103 | |||
1437 | 63 | <p tal:condition="view/latest_release_with_download_files"> | 104 | <p tal:condition="view/latest_release_with_download_files"> |
1438 | 64 | <img src="/@@/download"/> There are | 105 | <img src="/@@/download"/> There are |
1439 | 65 | <a tal:define="rooturl modules/canonical.launchpad.webapp.vhosts/allvhosts/configs/mainsite/rooturl" | 106 | <a tal:define="rooturl modules/canonical.launchpad.webapp.vhosts/allvhosts/configs/mainsite/rooturl" |
1440 | 66 | 107 | ||
1441 | === modified file 'lib/lp/code/templates/product-branches.pt' | |||
1442 | --- lib/lp/code/templates/product-branches.pt 2009-09-17 00:27:40 +0000 | |||
1443 | +++ lib/lp/code/templates/product-branches.pt 2010-09-28 22:26:02 +0000 | |||
1444 | @@ -3,40 +3,66 @@ | |||
1445 | 3 | xmlns:tal="http://xml.zope.org/namespaces/tal" | 3 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
1446 | 4 | xmlns:metal="http://xml.zope.org/namespaces/metal" | 4 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
1447 | 5 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | 5 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1449 | 6 | metal:use-macro="view/macro:page/main_only" | 6 | metal:use-macro="view/macro:page/main_side" |
1450 | 7 | i18n:domain="launchpad" | 7 | i18n:domain="launchpad" |
1451 | 8 | > | 8 | > |
1452 | 9 | 9 | ||
1453 | 10 | <body> | 10 | <body> |
1454 | 11 | 11 | ||
1483 | 12 | <div metal:fill-slot="main"> | 12 | <metal:side fill-slot="side" tal:define="context_menu context/menu:context"> |
1484 | 13 | 13 | <div id="branch-portlet" | |
1485 | 14 | <div style="float:right" id="floating-links" | 14 | tal:condition="not: context/codehosting_usage/enumvalue:UNKNOWN"> |
1486 | 15 | tal:define="menu context/menu:branches"> | 15 | <div id="privacy" |
1487 | 16 | <div tal:define="link menu/branch_add" | 16 | tal:define="are_private view/new_branches_are_private" |
1488 | 17 | tal:condition="link/enabled" | 17 | tal:attributes="class python: are_private and 'first portlet private' or 'first portlet public'"> |
1489 | 18 | tal:content="structure link/render" /> | 18 | |
1490 | 19 | <div tal:define="link menu/code_import" | 19 | <p tal:condition="not:view/new_branches_are_private" id="privacy-text"> |
1491 | 20 | tal:condition="link/enabled" | 20 | New branches you create for <tal:name replace="context/displayname"/> |
1492 | 21 | tal:content="structure link/render" /> | 21 | are <strong>public</strong> initially. |
1493 | 22 | <div tal:define="link menu/branch_visibility" | 22 | </p> |
1494 | 23 | tal:condition="link/enabled" | 23 | |
1495 | 24 | tal:content="structure link/render" /> | 24 | <p tal:condition="view/new_branches_are_private" id="privacy-text"> |
1496 | 25 | </div> | 25 | New branches you create for <tal:name replace="context/displayname"/> |
1497 | 26 | 26 | are <strong>private</strong> initially. | |
1498 | 27 | <div id="private-policy" tal:condition="view/new_branches_are_private" | 27 | </p> |
1499 | 28 | class="informational message"> | 28 | |
1500 | 29 | New branches you create for <tal:name replace="context/displayname"/> | 29 | </div> |
1501 | 30 | are <strong>private</strong> initially. | 30 | |
1502 | 31 | </div> | 31 | <div id="involvement" class="portlet" |
1503 | 32 | 32 | tal:define="menu context/menu:branches"> | |
1504 | 33 | <tal:branch-summary content="structure context/@@+branch-summary" /> | 33 | <ul class="involvement"> |
1505 | 34 | 34 | <li style="border: none"> | |
1506 | 35 | <tal:has-branches condition="view/branch_count" | 35 | <a href="+addbranch" class="menu-link-addbranch sprite code"> |
1507 | 36 | define="branches view/branches"> | 36 | Register a branch |
1508 | 37 | <tal:branchlisting content="structure branches/@@+branch-listing" /> | 37 | </a> |
1509 | 38 | </tal:has-branches> | 38 | </li> |
1510 | 39 | </div> | 39 | </ul> |
1511 | 40 | <p style="margin-top:10px;" | ||
1512 | 41 | tal:define="link menu/code_import" | ||
1513 | 42 | tal:condition="link/enabled" | ||
1514 | 43 | tal:content="structure link/render"></p> | ||
1515 | 44 | <p tal:define="configure_codehosting view/configure_codehosting | nothing" | ||
1516 | 45 | tal:condition="configure_codehosting" | ||
1517 | 46 | tal:replace="structure configure_codehosting/fmt:link"></p> | ||
1518 | 47 | <p tal:define="link menu/branch_visibility" | ||
1519 | 48 | tal:condition="link/enabled" | ||
1520 | 49 | tal:content="structure link/render"></p> | ||
1521 | 50 | </div> | ||
1522 | 51 | |||
1523 | 52 | <div tal:replace="structure context/@@+portlet-product-codestatistics" /> | ||
1524 | 53 | </div> | ||
1525 | 54 | </metal:side> | ||
1526 | 55 | |||
1527 | 56 | <tal:main metal:fill-slot="main"> | ||
1528 | 57 | |||
1529 | 58 | <tal:branch-summary content="structure context/@@+branch-summary" /> | ||
1530 | 59 | |||
1531 | 60 | <tal:has-branches condition="view/branch_count" | ||
1532 | 61 | define="branches view/branches"> | ||
1533 | 62 | <tal:branchlisting content="structure branches/@@+branch-listing" /> | ||
1534 | 63 | </tal:has-branches> | ||
1535 | 64 | |||
1536 | 65 | </tal:main> | ||
1537 | 40 | 66 | ||
1538 | 41 | </body> | 67 | </body> |
1539 | 42 | </html> | 68 | </html> |
1540 | 43 | 69 | ||
1541 | === added file 'lib/lp/code/templates/product-portlet-codestatistics-content.pt' | |||
1542 | --- lib/lp/code/templates/product-portlet-codestatistics-content.pt 1970-01-01 00:00:00 +0000 | |||
1543 | +++ lib/lp/code/templates/product-portlet-codestatistics-content.pt 2010-09-28 22:26:02 +0000 | |||
1544 | @@ -0,0 +1,55 @@ | |||
1545 | 1 | <tal:portlet-product-codestatistics-content | ||
1546 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
1547 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal"> | ||
1548 | 4 | |||
1549 | 5 | <tr tal:define="menu context/menu:branches" class="code-links" | ||
1550 | 6 | id="merge-counts"> | ||
1551 | 7 | <td class="code-count" | ||
1552 | 8 | tal:define="count menu/active_review_count" | ||
1553 | 9 | tal:content="count" /> | ||
1554 | 10 | <td> | ||
1555 | 11 | <tal:link | ||
1556 | 12 | define="link menu/active_reviews" | ||
1557 | 13 | replace="structure link/render" | ||
1558 | 14 | /> | ||
1559 | 15 | </td> | ||
1560 | 16 | </tr> | ||
1561 | 17 | |||
1562 | 18 | <tr class="code-links" id="branch-count-summary"> | ||
1563 | 19 | <td class="code-count" | ||
1564 | 20 | tal:define="count view/branch_count" | ||
1565 | 21 | tal:content="count" /> | ||
1566 | 22 | <td> | ||
1567 | 23 | <tal:branches replace="view/branch_text">branches</tal:branches | ||
1568 | 24 | ><tal:has-branches condition="view/branch_count"> | ||
1569 | 25 | owned by | ||
1570 | 26 | <tal:individuals condition="view/person_owner_count"> | ||
1571 | 27 | <tal:owners content="view/person_owner_count">42</tal:owners> | ||
1572 | 28 | <tal:people replace="view/person_text">people</tal:people | ||
1573 | 29 | ></tal:individuals | ||
1574 | 30 | ><tal:teams condition="view/team_owner_count"> | ||
1575 | 31 | <tal:individuals condition="view/person_owner_count"> | ||
1576 | 32 | and | ||
1577 | 33 | </tal:individuals> | ||
1578 | 34 | <tal:toc content="view/team_owner_count">1</tal:toc> | ||
1579 | 35 | <tal:people replace="view/team_text">team</tal:people | ||
1580 | 36 | ></tal:teams></tal:has-branches> | ||
1581 | 37 | </td> | ||
1582 | 38 | </tr> | ||
1583 | 39 | |||
1584 | 40 | <tr class="code-links"> | ||
1585 | 41 | <td class="code-count" | ||
1586 | 42 | tal:define="count view/commit_count" | ||
1587 | 43 | tal:content="count" /> | ||
1588 | 44 | <td> | ||
1589 | 45 | <tal:commits replace="view/commit_text">commits</tal:commits> | ||
1590 | 46 | <tal:has-committers condition="view/committer_count"> | ||
1591 | 47 | by | ||
1592 | 48 | <tal:cc content="view/committer_count">4</tal:cc> | ||
1593 | 49 | <tal:people replace="view/committer_text">people</tal:people> | ||
1594 | 50 | </tal:has-committers> | ||
1595 | 51 | in the last month | ||
1596 | 52 | </td> | ||
1597 | 53 | </tr> | ||
1598 | 54 | |||
1599 | 55 | </tal:portlet-product-codestatistics-content> | ||
1600 | 0 | 56 | ||
1601 | === added file 'lib/lp/code/templates/product-portlet-codestatistics.pt' | |||
1602 | --- lib/lp/code/templates/product-portlet-codestatistics.pt 1970-01-01 00:00:00 +0000 | |||
1603 | +++ lib/lp/code/templates/product-portlet-codestatistics.pt 2010-09-28 22:26:02 +0000 | |||
1604 | @@ -0,0 +1,11 @@ | |||
1605 | 1 | <div | ||
1606 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
1607 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
1608 | 4 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
1609 | 5 | class="portlet" id="portlet-product-codestatistics"> | ||
1610 | 6 | |||
1611 | 7 | <table class="code-links"> | ||
1612 | 8 | <tbody id="portlet-product-codestatistics" | ||
1613 | 9 | tal:content="structure context/@@+portlet-product-codestatistics-content" /> | ||
1614 | 10 | </table> | ||
1615 | 11 | </div> | ||
1616 | 0 | 12 | ||
1617 | === modified file 'lib/lp/registry/browser/product.py' | |||
1618 | --- lib/lp/registry/browser/product.py 2010-09-28 14:58:40 +0000 | |||
1619 | +++ lib/lp/registry/browser/product.py 2010-09-28 22:26:02 +0000 | |||
1620 | @@ -458,9 +458,7 @@ | |||
1621 | 458 | # Add the branch configuration in separately. | 458 | # Add the branch configuration in separately. |
1622 | 459 | set_branch = series_menu['set_branch'] | 459 | set_branch = series_menu['set_branch'] |
1623 | 460 | set_branch.text = 'Configure project branch' | 460 | set_branch.text = 'Configure project branch' |
1627 | 461 | set_branch.summary = "Specify the location of this projects code." | 461 | set_branch.summary = "Specify the location of this project's code." |
1625 | 462 | set_branch.configured = ( | ||
1626 | 463 | ) | ||
1628 | 464 | config_list.append( | 462 | config_list.append( |
1629 | 465 | dict(link=set_branch, | 463 | dict(link=set_branch, |
1630 | 466 | configured=config_statuses['configure_codehosting'])) | 464 | configured=config_statuses['configure_codehosting'])) |
1631 | @@ -469,11 +467,8 @@ | |||
1632 | 469 | @property | 467 | @property |
1633 | 470 | def registration_completeness(self): | 468 | def registration_completeness(self): |
1634 | 471 | """The percent complete for registration.""" | 469 | """The percent complete for registration.""" |
1635 | 472 | configured = 0 | ||
1636 | 473 | config_statuses = self.configuration_states | 470 | config_statuses = self.configuration_states |
1640 | 474 | for key, value in config_statuses.items(): | 471 | configured = sum(1 for val in config_statuses.values() if val) |
1638 | 475 | if value: | ||
1639 | 476 | configured += 1 | ||
1641 | 477 | scale = 100 | 472 | scale = 100 |
1642 | 478 | done = int(float(configured) / len(config_statuses) * scale) | 473 | done = int(float(configured) / len(config_statuses) * scale) |
1643 | 479 | undone = scale - done | 474 | undone = scale - done |
1644 | @@ -1396,9 +1391,9 @@ | |||
1645 | 1396 | def setUpFields(self): | 1391 | def setUpFields(self): |
1646 | 1397 | super(ProductConfigureBase, self).setUpFields() | 1392 | super(ProductConfigureBase, self).setUpFields() |
1647 | 1398 | if self.usage_fieldname is not None: | 1393 | if self.usage_fieldname is not None: |
1651 | 1399 | # The usage fields are shared among pillars. But when referring to | 1394 | # The usage fields are shared among pillars. But when referring |
1652 | 1400 | # an individual object in Launchpad it is better to call it by its | 1395 | # to an individual object in Launchpad it is better to call it by |
1653 | 1401 | # real name, i.e. 'project' instead of 'pillar'. | 1396 | # its real name, i.e. 'project' instead of 'pillar'. |
1654 | 1402 | usage_field = self.form_fields.get(self.usage_fieldname) | 1397 | usage_field = self.form_fields.get(self.usage_fieldname) |
1655 | 1403 | if usage_field: | 1398 | if usage_field: |
1656 | 1404 | usage_field.custom_widget = CustomWidgetFactory( | 1399 | usage_field.custom_widget = CustomWidgetFactory( |
1657 | 1405 | 1400 | ||
1658 | === modified file 'lib/lp/registry/browser/tests/pillar-views.txt' | |||
1659 | --- lib/lp/registry/browser/tests/pillar-views.txt 2010-09-25 14:29:32 +0000 | |||
1660 | +++ lib/lp/registry/browser/tests/pillar-views.txt 2010-09-28 22:26:02 +0000 | |||
1661 | @@ -182,6 +182,17 @@ | |||
1662 | 182 | >>> print view.codehosting_usage.name | 182 | >>> print view.codehosting_usage.name |
1663 | 183 | LAUNCHPAD | 183 | LAUNCHPAD |
1664 | 184 | 184 | ||
1665 | 185 | >>> from lp.code.enums import BranchType | ||
1666 | 186 | >>> remote = factory.makeProduct() | ||
1667 | 187 | >>> branch = factory.makeProductBranch(product=remote, | ||
1668 | 188 | ... branch_type=BranchType.REMOTE) | ||
1669 | 189 | >>> remote.official_codehosting | ||
1670 | 190 | False | ||
1671 | 191 | >>> view = create_view(remote, '+get-involved') | ||
1672 | 192 | >>> print view.codehosting_usage.name | ||
1673 | 193 | UNKNOWN | ||
1674 | 194 | |||
1675 | 195 | |||
1676 | 185 | Project groups cannot make links to register a branch, so | 196 | Project groups cannot make links to register a branch, so |
1677 | 186 | official_codehosting is always false. | 197 | official_codehosting is always false. |
1678 | 187 | 198 | ||
1679 | 188 | 199 | ||
1680 | === modified file 'lib/lp/registry/model/product.py' | |||
1681 | --- lib/lp/registry/model/product.py 2010-09-27 18:16:28 +0000 | |||
1682 | +++ lib/lp/registry/model/product.py 2010-09-28 22:26:02 +0000 | |||
1683 | @@ -404,7 +404,8 @@ | |||
1684 | 404 | return ServiceUsage.UNKNOWN | 404 | return ServiceUsage.UNKNOWN |
1685 | 405 | elif self.development_focus.branch.branch_type == BranchType.HOSTED: | 405 | elif self.development_focus.branch.branch_type == BranchType.HOSTED: |
1686 | 406 | return ServiceUsage.LAUNCHPAD | 406 | return ServiceUsage.LAUNCHPAD |
1688 | 407 | elif self.development_focus.branch.branch_type == BranchType.MIRRORED: | 407 | elif self.development_focus.branch.branch_type in ( |
1689 | 408 | BranchType.MIRRORED, BranchType.REMOTE): | ||
1690 | 408 | return ServiceUsage.EXTERNAL | 409 | return ServiceUsage.EXTERNAL |
1691 | 409 | return ServiceUsage.NOT_APPLICABLE | 410 | return ServiceUsage.NOT_APPLICABLE |
1692 | 410 | 411 | ||
1693 | 411 | 412 | ||
1694 | === modified file 'lib/lp/registry/tests/test_service_usage.py' | |||
1695 | --- lib/lp/registry/tests/test_service_usage.py 2010-09-22 00:52:15 +0000 | |||
1696 | +++ lib/lp/registry/tests/test_service_usage.py 2010-09-28 22:26:02 +0000 | |||
1697 | @@ -8,6 +8,7 @@ | |||
1698 | 8 | from canonical.testing import DatabaseFunctionalLayer | 8 | from canonical.testing import DatabaseFunctionalLayer |
1699 | 9 | 9 | ||
1700 | 10 | from lp.app.enums import ServiceUsage | 10 | from lp.app.enums import ServiceUsage |
1701 | 11 | from lp.code.enums import BranchType | ||
1702 | 11 | from lp.testing import ( | 12 | from lp.testing import ( |
1703 | 12 | login_person, | 13 | login_person, |
1704 | 13 | TestCaseWithFactory, | 14 | TestCaseWithFactory, |
1705 | @@ -56,13 +57,6 @@ | |||
1706 | 56 | True, | 57 | True, |
1707 | 57 | self.target.official_answers) | 58 | self.target.official_answers) |
1708 | 58 | 59 | ||
1709 | 59 | def test_codehosting_usage(self): | ||
1710 | 60 | # Only test get for codehosting; this has no setter because the | ||
1711 | 61 | # state is derived from other data. | ||
1712 | 62 | self.assertEqual( | ||
1713 | 63 | ServiceUsage.UNKNOWN, | ||
1714 | 64 | self.target.codehosting_usage) | ||
1715 | 65 | |||
1716 | 66 | def test_translations_usage_no_data(self): | 60 | def test_translations_usage_no_data(self): |
1717 | 67 | # By default, we don't know anything about a target | 61 | # By default, we don't know anything about a target |
1718 | 68 | self.assertEqual( | 62 | self.assertEqual( |
1719 | @@ -195,6 +189,42 @@ | |||
1720 | 195 | super(TestProductUsageEnums, self).setUp() | 189 | super(TestProductUsageEnums, self).setUp() |
1721 | 196 | self.target = self.factory.makeProduct() | 190 | self.target = self.factory.makeProduct() |
1722 | 197 | 191 | ||
1723 | 192 | def test_codehosting_unknown(self): | ||
1724 | 193 | # A default product has UNKNOWN usage. | ||
1725 | 194 | self.assertEqual( | ||
1726 | 195 | ServiceUsage.UNKNOWN, | ||
1727 | 196 | self.target.codehosting_usage) | ||
1728 | 197 | |||
1729 | 198 | def test_codehosting_mirrored_branch(self): | ||
1730 | 199 | # A mirrored branch is EXTERNAL. | ||
1731 | 200 | login_person(self.target.owner) | ||
1732 | 201 | self.target.development_focus.branch = self.factory.makeProductBranch( | ||
1733 | 202 | product=self.target, | ||
1734 | 203 | branch_type=BranchType.MIRRORED) | ||
1735 | 204 | self.assertEqual( | ||
1736 | 205 | ServiceUsage.EXTERNAL, | ||
1737 | 206 | self.target.codehosting_usage) | ||
1738 | 207 | |||
1739 | 208 | def test_codehosting_remote_branch(self): | ||
1740 | 209 | # A remote branch is EXTERNAL. | ||
1741 | 210 | login_person(self.target.owner) | ||
1742 | 211 | self.target.development_focus.branch = self.factory.makeProductBranch( | ||
1743 | 212 | product=self.target, | ||
1744 | 213 | branch_type=BranchType.REMOTE) | ||
1745 | 214 | self.assertEqual( | ||
1746 | 215 | ServiceUsage.EXTERNAL, | ||
1747 | 216 | self.target.codehosting_usage) | ||
1748 | 217 | |||
1749 | 218 | def test_codehosting_hosted_branch(self): | ||
1750 | 219 | # A branch on Launchpad is HOSTED. | ||
1751 | 220 | login_person(self.target.owner) | ||
1752 | 221 | self.target.development_focus.branch = self.factory.makeProductBranch( | ||
1753 | 222 | product=self.target, | ||
1754 | 223 | branch_type=BranchType.HOSTED) | ||
1755 | 224 | self.assertEqual( | ||
1756 | 225 | ServiceUsage.LAUNCHPAD, | ||
1757 | 226 | self.target.codehosting_usage) | ||
1758 | 227 | |||
1759 | 198 | 228 | ||
1760 | 199 | class TestProductSeriesUsageEnums( | 229 | class TestProductSeriesUsageEnums( |
1761 | 200 | TestCaseWithFactory, | 230 | TestCaseWithFactory, |
Screenshots can be seen at: http:// people. canonical. com/~bac/ code_usage/