Merge lp:~thumper/launchpad/recipe-binary-builds into lp:launchpad
- recipe-binary-builds
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | no longer in the source branch. |
Merged at revision: | 12000 |
Proposed branch: | lp:~thumper/launchpad/recipe-binary-builds |
Merge into: | lp:launchpad |
Diff against target: |
435 lines (+189/-17) 8 files modified
lib/canonical/launchpad/doc/hierarchical-menu.txt (+6/-0) lib/lp/code/browser/sourcepackagerecipebuild.py (+4/-3) lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+84/-5) lib/lp/code/templates/sourcepackagerecipe-index.pt (+49/-4) lib/lp/soyuz/browser/build.py (+33/-0) lib/lp/soyuz/model/processor.py (+6/-0) lib/lp/soyuz/templates/build-index.pt (+1/-2) lib/lp/testing/factory.py (+6/-3) |
To merge this branch: | bzr merge lp:~thumper/launchpad/recipe-binary-builds |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Paul Hummer (community) | ui | Approve | |
Review via email: mp+40686@code.launchpad.net |
Commit message
[r=abentley]
Description of the change
Adds binary build information to the latest builds section on the recipe index page.
http://
I made some of the properties of the view that were accessed multiple times
cached properties. This required a few tweaks in the tests too.
The binary builds for the successful recipe builds are indented slightly and
show underneath the recipe build that they relate to.
During interactive hacking, I found that I wanted some nicer repr methods
for the processors.
A drive-by fix on the soyuz build-index.pt to just specify the content as
buildlog instead of using tal:content=
Aaron Bentley (abentley) wrote : | # |
You are showing the start time for binary builds, while the source package recipe builds show the end time. The time values in this column should have a consistent meaning.
Tim Penhey (thumper) wrote : | # |
Aaron, I've gone with the expected completed time, and added the same properties to the BuildView. I feel that we should use a common mixin, but I can't think where to actually put the code.
My first thought was actually something like lp.shared.browser or lp.app.
Not sure.
Aaron Bentley (abentley) wrote : | # |
Looks good. At some point, we might want to collapse eta and date into a single method call that returns a tuple.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/doc/hierarchical-menu.txt' | |||
2 | --- lib/canonical/launchpad/doc/hierarchical-menu.txt 2010-10-18 22:24:59 +0000 | |||
3 | +++ lib/canonical/launchpad/doc/hierarchical-menu.txt 2010-11-30 01:38:20 +0000 | |||
4 | @@ -90,6 +90,7 @@ | |||
5 | 90 | # have to create fake views and other stuff to test breadcrumbs here. The | 90 | # have to create fake views and other stuff to test breadcrumbs here. The |
6 | 91 | # functionality provided by that method is tested in | 91 | # functionality provided by that method is tested in |
7 | 92 | # webapp/tests/test_breadcrumbs.py. | 92 | # webapp/tests/test_breadcrumbs.py. |
8 | 93 | >>> make_breadcrumb_func = Hierarchy.makeBreadcrumbForRequestedPage | ||
9 | 93 | >>> Hierarchy.makeBreadcrumbForRequestedPage = lambda self: None | 94 | >>> Hierarchy.makeBreadcrumbForRequestedPage = lambda self: None |
10 | 94 | 95 | ||
11 | 95 | # Note that the Hierarchy assigns the breadcrumb's URL, but we need to | 96 | # Note that the Hierarchy assigns the breadcrumb's URL, but we need to |
12 | @@ -269,3 +270,8 @@ | |||
13 | 269 | 270 | ||
14 | 270 | >>> homepage_hierarchy.render().strip() | 271 | >>> homepage_hierarchy.render().strip() |
15 | 271 | u'' | 272 | u'' |
16 | 273 | |||
17 | 274 | |||
18 | 275 | Put the monkey patched method back. | ||
19 | 276 | |||
20 | 277 | >>> Hierarchy.makeBreadcrumbForRequestedPage = make_breadcrumb_func | ||
21 | 272 | 278 | ||
22 | === modified file 'lib/lp/code/browser/sourcepackagerecipebuild.py' | |||
23 | --- lib/lp/code/browser/sourcepackagerecipebuild.py 2010-11-23 23:22:27 +0000 | |||
24 | +++ lib/lp/code/browser/sourcepackagerecipebuild.py 2010-11-30 01:38:20 +0000 | |||
25 | @@ -34,6 +34,7 @@ | |||
26 | 34 | ISourcePackageRecipeBuild, | 34 | ISourcePackageRecipeBuild, |
27 | 35 | ) | 35 | ) |
28 | 36 | from lp.services.job.interfaces.job import JobStatus | 36 | from lp.services.job.interfaces.job import JobStatus |
29 | 37 | from lp.services.propertycache import cachedproperty | ||
30 | 37 | 38 | ||
31 | 38 | 39 | ||
32 | 39 | UNEDITABLE_BUILD_STATES = ( | 40 | UNEDITABLE_BUILD_STATES = ( |
33 | @@ -95,7 +96,7 @@ | |||
34 | 95 | BuildStatus.FAILEDTOUPLOAD: 'Could not be uploaded correctly', | 96 | BuildStatus.FAILEDTOUPLOAD: 'Could not be uploaded correctly', |
35 | 96 | }.get(self.context.status, self.context.status.title) | 97 | }.get(self.context.status, self.context.status.title) |
36 | 97 | 98 | ||
38 | 98 | @property | 99 | @cachedproperty |
39 | 99 | def eta(self): | 100 | def eta(self): |
40 | 100 | """The datetime when the build job is estimated to complete. | 101 | """The datetime when the build job is estimated to complete. |
41 | 101 | 102 | ||
42 | @@ -114,14 +115,14 @@ | |||
43 | 114 | duration = queue_record.estimated_duration | 115 | duration = queue_record.estimated_duration |
44 | 115 | return start_time + duration | 116 | return start_time + duration |
45 | 116 | 117 | ||
47 | 117 | @property | 118 | @cachedproperty |
48 | 118 | def date(self): | 119 | def date(self): |
49 | 119 | """The date when the build completed or is estimated to complete.""" | 120 | """The date when the build completed or is estimated to complete.""" |
50 | 120 | if self.estimate: | 121 | if self.estimate: |
51 | 121 | return self.eta | 122 | return self.eta |
52 | 122 | return self.context.date_finished | 123 | return self.context.date_finished |
53 | 123 | 124 | ||
55 | 124 | @property | 125 | @cachedproperty |
56 | 125 | def estimate(self): | 126 | def estimate(self): |
57 | 126 | """If true, the date value is an estimate.""" | 127 | """If true, the date value is an estimate.""" |
58 | 127 | if self.context.date_finished is not None: | 128 | if self.context.date_finished is not None: |
59 | 128 | 129 | ||
60 | === modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py' | |||
61 | --- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-11-24 18:40:48 +0000 | |||
62 | +++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-11-30 01:38:20 +0000 | |||
63 | @@ -42,6 +42,7 @@ | |||
64 | 42 | from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT | 42 | from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT |
65 | 43 | from lp.code.tests.helpers import recipe_parser_newest_version | 43 | from lp.code.tests.helpers import recipe_parser_newest_version |
66 | 44 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 44 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
67 | 45 | from lp.services.propertycache import clear_property_cache | ||
68 | 45 | from lp.soyuz.model.processor import ProcessorFamily | 46 | from lp.soyuz.model.processor import ProcessorFamily |
69 | 46 | from lp.testing import ( | 47 | from lp.testing import ( |
70 | 47 | ANONYMOUS, | 48 | ANONYMOUS, |
71 | @@ -691,7 +692,7 @@ | |||
72 | 691 | 692 | ||
73 | 692 | class TestSourcePackageRecipeView(TestCaseForRecipe): | 693 | class TestSourcePackageRecipeView(TestCaseForRecipe): |
74 | 693 | 694 | ||
76 | 694 | layer = DatabaseFunctionalLayer | 695 | layer = LaunchpadFunctionalLayer |
77 | 695 | 696 | ||
78 | 696 | def test_index(self): | 697 | def test_index(self): |
79 | 697 | recipe = self.makeRecipe() | 698 | recipe = self.makeRecipe() |
80 | @@ -716,7 +717,7 @@ | |||
81 | 716 | Distribution series: Secret Squirrel | 717 | Distribution series: Secret Squirrel |
82 | 717 | 718 | ||
83 | 718 | Latest builds | 719 | Latest builds |
85 | 719 | Status Time Distribution series Archive | 720 | Status When complete Distribution series Archive |
86 | 720 | Successful build on 2010-03-16 Secret Squirrel Secret PPA | 721 | Successful build on 2010-03-16 Secret Squirrel Secret PPA |
87 | 721 | Request build\(s\) | 722 | Request build\(s\) |
88 | 722 | 723 | ||
89 | @@ -724,12 +725,86 @@ | |||
90 | 724 | # bzr-builder format 0.2 deb-version {debupstream}-0~{revno} | 725 | # bzr-builder format 0.2 deb-version {debupstream}-0~{revno} |
91 | 725 | lp://dev/~chef/chocolate/cake""", self.getMainText(recipe)) | 726 | lp://dev/~chef/chocolate/cake""", self.getMainText(recipe)) |
92 | 726 | 727 | ||
93 | 728 | def test_index_success_with_buildlog(self): | ||
94 | 729 | # The buildlog is shown if it is there. | ||
95 | 730 | recipe = self.makeRecipe() | ||
96 | 731 | build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild( | ||
97 | 732 | recipe=recipe, distroseries=self.squirrel, archive=self.ppa)) | ||
98 | 733 | build.status = BuildStatus.FULLYBUILT | ||
99 | 734 | build.date_started = datetime(2010, 03, 16, tzinfo=utc) | ||
100 | 735 | build.date_finished = datetime(2010, 03, 16, tzinfo=utc) | ||
101 | 736 | build.log = self.factory.makeLibraryFileAlias() | ||
102 | 737 | |||
103 | 738 | self.assertTextMatchesExpressionIgnoreWhitespace("""\ | ||
104 | 739 | Latest builds | ||
105 | 740 | Status .* Archive | ||
106 | 741 | Successful build on 2010-03-16 buildlog \(.*\) Secret Squirrel Secret PPA | ||
107 | 742 | Request build\(s\)""", self.getMainText(recipe)) | ||
108 | 743 | |||
109 | 744 | def test_index_success_with_binary_builds(self): | ||
110 | 745 | # Binary builds are shown after the recipe builds if there are any. | ||
111 | 746 | recipe = self.makeRecipe() | ||
112 | 747 | build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild( | ||
113 | 748 | recipe=recipe, distroseries=self.squirrel, archive=self.ppa)) | ||
114 | 749 | build.status = BuildStatus.FULLYBUILT | ||
115 | 750 | build.date_started = datetime(2010, 03, 16, tzinfo=utc) | ||
116 | 751 | build.date_finished = datetime(2010, 03, 16, tzinfo=utc) | ||
117 | 752 | build.log = self.factory.makeLibraryFileAlias() | ||
118 | 753 | package_name = self.factory.getOrMakeSourcePackageName('chocolate') | ||
119 | 754 | source_package_release = self.factory.makeSourcePackageRelease( | ||
120 | 755 | archive=self.ppa, sourcepackagename=package_name, distroseries=self.squirrel, | ||
121 | 756 | source_package_recipe_build=build, version='0+r42') | ||
122 | 757 | builder = self.factory.makeBuilder() | ||
123 | 758 | binary_build = self.factory.makeBinaryPackageBuild( | ||
124 | 759 | source_package_release=source_package_release, | ||
125 | 760 | distroarchseries=self.squirrel.nominatedarchindep, | ||
126 | 761 | processor=builder.processor) | ||
127 | 762 | binary_build.queueBuild() | ||
128 | 763 | |||
129 | 764 | self.assertTextMatchesExpressionIgnoreWhitespace("""\ | ||
130 | 765 | Latest builds | ||
131 | 766 | Status .* Archive | ||
132 | 767 | Successful build on 2010-03-16 buildlog \(.*\) Secret Squirrel Secret PPA | ||
133 | 768 | chocolate - 0\+r42 in .* \(estimated\) i386 | ||
134 | 769 | Request build\(s\)""", self.getMainText(recipe)) | ||
135 | 770 | |||
136 | 771 | def test_index_success_with_completed_binary_build(self): | ||
137 | 772 | # Binary builds show their buildlog too. | ||
138 | 773 | recipe = self.makeRecipe() | ||
139 | 774 | build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild( | ||
140 | 775 | recipe=recipe, distroseries=self.squirrel, archive=self.ppa)) | ||
141 | 776 | build.status = BuildStatus.FULLYBUILT | ||
142 | 777 | build.date_started = datetime(2010, 03, 16, tzinfo=utc) | ||
143 | 778 | build.date_finished = datetime(2010, 03, 16, tzinfo=utc) | ||
144 | 779 | build.log = self.factory.makeLibraryFileAlias() | ||
145 | 780 | package_name = self.factory.getOrMakeSourcePackageName('chocolate') | ||
146 | 781 | source_package_release = self.factory.makeSourcePackageRelease( | ||
147 | 782 | archive=self.ppa, sourcepackagename=package_name, distroseries=self.squirrel, | ||
148 | 783 | source_package_recipe_build=build, version='0+r42') | ||
149 | 784 | builder = self.factory.makeBuilder() | ||
150 | 785 | binary_build = removeSecurityProxy(self.factory.makeBinaryPackageBuild( | ||
151 | 786 | source_package_release=source_package_release, | ||
152 | 787 | distroarchseries=self.squirrel.nominatedarchindep, | ||
153 | 788 | processor=builder.processor)) | ||
154 | 789 | binary_build.queueBuild() | ||
155 | 790 | binary_build.status = BuildStatus.FULLYBUILT | ||
156 | 791 | binary_build.date_started = datetime(2010, 04, 16, tzinfo=utc) | ||
157 | 792 | binary_build.date_finished = datetime(2010, 04, 16, tzinfo=utc) | ||
158 | 793 | binary_build.log = self.factory.makeLibraryFileAlias() | ||
159 | 794 | |||
160 | 795 | self.assertTextMatchesExpressionIgnoreWhitespace("""\ | ||
161 | 796 | Latest builds | ||
162 | 797 | Status .* Archive | ||
163 | 798 | Successful build on 2010-03-16 buildlog \(.*\) Secret Squirrel Secret PPA | ||
164 | 799 | chocolate - 0\+r42 on 2010-04-16 buildlog \(.*\) i386 | ||
165 | 800 | Request build\(s\)""", self.getMainText(recipe)) | ||
166 | 801 | |||
167 | 727 | def test_index_no_builds(self): | 802 | def test_index_no_builds(self): |
168 | 728 | """A message should be shown when there are no builds.""" | 803 | """A message should be shown when there are no builds.""" |
169 | 729 | recipe = self.makeRecipe() | 804 | recipe = self.makeRecipe() |
170 | 730 | self.assertTextMatchesExpressionIgnoreWhitespace("""\ | 805 | self.assertTextMatchesExpressionIgnoreWhitespace("""\ |
171 | 731 | Latest builds | 806 | Latest builds |
173 | 732 | Status Time Distribution series Archive | 807 | Status .* Archive |
174 | 733 | This recipe has not been built yet.""", self.getMainText(recipe)) | 808 | This recipe has not been built yet.""", self.getMainText(recipe)) |
175 | 734 | 809 | ||
176 | 735 | def test_index_no_suitable_builders(self): | 810 | def test_index_no_suitable_builders(self): |
177 | @@ -738,7 +813,7 @@ | |||
178 | 738 | recipe=recipe, distroseries=self.squirrel, archive=self.ppa)) | 813 | recipe=recipe, distroseries=self.squirrel, archive=self.ppa)) |
179 | 739 | self.assertTextMatchesExpressionIgnoreWhitespace(""" | 814 | self.assertTextMatchesExpressionIgnoreWhitespace(""" |
180 | 740 | Latest builds | 815 | Latest builds |
182 | 741 | Status Time Distribution series Archive | 816 | Status .* Archive |
183 | 742 | No suitable builders Secret Squirrel Secret PPA | 817 | No suitable builders Secret Squirrel Secret PPA |
184 | 743 | Request build\(s\)""", self.getMainText(recipe)) | 818 | Request build\(s\)""", self.getMainText(recipe)) |
185 | 744 | 819 | ||
186 | @@ -756,7 +831,7 @@ | |||
187 | 756 | self.factory.makeBuilder() | 831 | self.factory.makeBuilder() |
188 | 757 | pattern = """\ | 832 | pattern = """\ |
189 | 758 | Latest builds | 833 | Latest builds |
191 | 759 | Status Time Distribution series Archive | 834 | Status .* Archive |
192 | 760 | Pending build in .* \(estimated\) Secret Squirrel Secret PPA | 835 | Pending build in .* \(estimated\) Secret Squirrel Secret PPA |
193 | 761 | Request build\(s\) | 836 | Request build\(s\) |
194 | 762 | 837 | ||
195 | @@ -946,8 +1021,10 @@ | |||
196 | 946 | view = self.makeBuildView() | 1021 | view = self.makeBuildView() |
197 | 947 | self.assertTrue(view.estimate) | 1022 | self.assertTrue(view.estimate) |
198 | 948 | view.context.buildqueue_record.job.start() | 1023 | view.context.buildqueue_record.job.start() |
199 | 1024 | clear_property_cache(view) | ||
200 | 949 | self.assertTrue(view.estimate) | 1025 | self.assertTrue(view.estimate) |
201 | 950 | removeSecurityProxy(view.context).date_finished = datetime.now(utc) | 1026 | removeSecurityProxy(view.context).date_finished = datetime.now(utc) |
202 | 1027 | clear_property_cache(view) | ||
203 | 951 | self.assertFalse(view.estimate) | 1028 | self.assertFalse(view.estimate) |
204 | 952 | 1029 | ||
205 | 953 | def test_eta(self): | 1030 | def test_eta(self): |
206 | @@ -966,11 +1043,13 @@ | |||
207 | 966 | recipe_build=build) | 1043 | recipe_build=build) |
208 | 967 | queue_entry._now = lambda: datetime(1970, 1, 1, 0, 0, 0, 0, utc) | 1044 | queue_entry._now = lambda: datetime(1970, 1, 1, 0, 0, 0, 0, utc) |
209 | 968 | self.factory.makeBuilder() | 1045 | self.factory.makeBuilder() |
210 | 1046 | clear_property_cache(view) | ||
211 | 969 | self.assertIsNot(None, view.eta) | 1047 | self.assertIsNot(None, view.eta) |
212 | 970 | self.assertEqual( | 1048 | self.assertEqual( |
213 | 971 | queue_entry.getEstimatedJobStartTime() + | 1049 | queue_entry.getEstimatedJobStartTime() + |
214 | 972 | queue_entry.estimated_duration, view.eta) | 1050 | queue_entry.estimated_duration, view.eta) |
215 | 973 | queue_entry.job.start() | 1051 | queue_entry.job.start() |
216 | 1052 | clear_property_cache(view) | ||
217 | 974 | self.assertEqual( | 1053 | self.assertEqual( |
218 | 975 | queue_entry.job.date_started + queue_entry.estimated_duration, | 1054 | queue_entry.job.date_started + queue_entry.estimated_duration, |
219 | 976 | view.eta) | 1055 | view.eta) |
220 | 977 | 1056 | ||
221 | === modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt' | |||
222 | --- lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-11-09 04:24:09 +0000 | |||
223 | +++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-11-30 01:38:20 +0000 | |||
224 | @@ -7,6 +7,13 @@ | |||
225 | 7 | i18n:domain="launchpad" | 7 | i18n:domain="launchpad" |
226 | 8 | > | 8 | > |
227 | 9 | 9 | ||
228 | 10 | <metal:block fill-slot="head_epilogue"> | ||
229 | 11 | <style type="text/css"> | ||
230 | 12 | .binary-build .indent { | ||
231 | 13 | padding-left: 2em; | ||
232 | 14 | } | ||
233 | 15 | </style> | ||
234 | 16 | </metal:block> | ||
235 | 10 | 17 | ||
236 | 11 | <body> | 18 | <body> |
237 | 12 | 19 | ||
238 | @@ -86,14 +93,15 @@ | |||
239 | 86 | <thead> | 93 | <thead> |
240 | 87 | <tr> | 94 | <tr> |
241 | 88 | <th>Status</th> | 95 | <th>Status</th> |
243 | 89 | <th>Time</th> | 96 | <th>When complete</th> |
244 | 90 | <th>Distribution series</th> | 97 | <th>Distribution series</th> |
245 | 91 | <th>Archive</th> | 98 | <th>Archive</th> |
246 | 92 | </tr> | 99 | </tr> |
247 | 93 | </thead> | 100 | </thead> |
248 | 94 | <tbody> | 101 | <tbody> |
250 | 95 | <tr tal:repeat="build view/builds"> | 102 | <tal:recipe-builds repeat="build view/builds"> |
251 | 96 | <tal:build-view define="buildview nocall:build/@@+index"> | 103 | <tal:build-view define="buildview nocall:build/@@+index"> |
252 | 104 | <tr> | ||
253 | 97 | <td> | 105 | <td> |
254 | 98 | <span tal:replace="structure build/image:icon" /> | 106 | <span tal:replace="structure build/image:icon" /> |
255 | 99 | <a tal:content="buildview/status" | 107 | <a tal:content="buildview/status" |
256 | @@ -102,8 +110,15 @@ | |||
257 | 102 | <td> | 110 | <td> |
258 | 103 | <tal:date replace="buildview/date/fmt:displaydate" /> | 111 | <tal:date replace="buildview/date/fmt:displaydate" /> |
259 | 104 | <tal:estimate condition="buildview/estimate"> | 112 | <tal:estimate condition="buildview/estimate"> |
261 | 105 | (estimated) | 113 | (estimated) |
262 | 106 | </tal:estimate> | 114 | </tal:estimate> |
263 | 115 | |||
264 | 116 | <tal:build-log define="file build/log" | ||
265 | 117 | tal:condition="file"> | ||
266 | 118 | <a class="sprite download" | ||
267 | 119 | tal:attributes="href build/log_url">buildlog</a> | ||
268 | 120 | (<span tal:replace="file/content/filesize/fmt:bytes" />) | ||
269 | 121 | </tal:build-log> | ||
270 | 107 | </td> | 122 | </td> |
271 | 108 | <td> | 123 | <td> |
272 | 109 | <tal:distro | 124 | <tal:distro |
273 | @@ -112,8 +127,38 @@ | |||
274 | 112 | <td> | 127 | <td> |
275 | 113 | <tal:archive replace="structure build/archive/fmt:link"/> | 128 | <tal:archive replace="structure build/archive/fmt:link"/> |
276 | 114 | </td> | 129 | </td> |
277 | 115 | </tal:build-view> | ||
278 | 116 | </tr> | 130 | </tr> |
279 | 131 | <tal:binary-builds repeat="binary buildview/binary_builds"> | ||
280 | 132 | <tr tal:define="binaryview nocall:binary/@@+index" | ||
281 | 133 | class="binary-build"> | ||
282 | 134 | <td class="indent"> | ||
283 | 135 | <span tal:replace="structure binary/image:icon"/> | ||
284 | 136 | <a tal:content="binary/source_package_release/title" | ||
285 | 137 | tal:attributes="href binary/fmt:url">package - version</a> | ||
286 | 138 | </td> | ||
287 | 139 | <td> | ||
288 | 140 | <tal:date replace="binaryview/date/fmt:displaydate" /> | ||
289 | 141 | <tal:estimate condition="binaryview/estimate"> | ||
290 | 142 | (estimated) | ||
291 | 143 | </tal:estimate> | ||
292 | 144 | |||
293 | 145 | <tal:build-log define="file binary/log" | ||
294 | 146 | tal:condition="file"> | ||
295 | 147 | <a class="sprite download" | ||
296 | 148 | tal:attributes="href binary/log_url">buildlog</a> | ||
297 | 149 | (<span tal:replace="file/content/filesize/fmt:bytes" />) | ||
298 | 150 | </tal:build-log> | ||
299 | 151 | </td> | ||
300 | 152 | <td class="indent"> | ||
301 | 153 | <a class="sprite distribution" | ||
302 | 154 | tal:define="archseries binary/distro_arch_series" | ||
303 | 155 | tal:attributes="href archseries/fmt:url" | ||
304 | 156 | tal:content="archseries/architecturetag">i386</a> | ||
305 | 157 | </td> | ||
306 | 158 | </tr> | ||
307 | 159 | </tal:binary-builds> | ||
308 | 160 | </tal:build-view> | ||
309 | 161 | </tal:recipe-builds> | ||
310 | 117 | </tbody> | 162 | </tbody> |
311 | 118 | </table> | 163 | </table> |
312 | 119 | <p tal:condition="not: view/builds"> | 164 | <p tal:condition="not: view/builds"> |
313 | 120 | 165 | ||
314 | === modified file 'lib/lp/soyuz/browser/build.py' | |||
315 | --- lib/lp/soyuz/browser/build.py 2010-11-23 23:22:27 +0000 | |||
316 | +++ lib/lp/soyuz/browser/build.py 2010-11-30 01:38:20 +0000 | |||
317 | @@ -242,6 +242,39 @@ | |||
318 | 242 | self.context.status == BuildStatus.NEEDSBUILD and | 242 | self.context.status == BuildStatus.NEEDSBUILD and |
319 | 243 | self.context.buildqueue_record.job.status == JobStatus.WAITING) | 243 | self.context.buildqueue_record.job.status == JobStatus.WAITING) |
320 | 244 | 244 | ||
321 | 245 | @cachedproperty | ||
322 | 246 | def eta(self): | ||
323 | 247 | """The datetime when the build job is estimated to complete. | ||
324 | 248 | |||
325 | 249 | This is the BuildQueue.estimated_duration plus the | ||
326 | 250 | Job.date_started or BuildQueue.getEstimatedJobStartTime. | ||
327 | 251 | """ | ||
328 | 252 | if self.context.buildqueue_record is None: | ||
329 | 253 | return None | ||
330 | 254 | queue_record = self.context.buildqueue_record | ||
331 | 255 | if queue_record.job.status == JobStatus.WAITING: | ||
332 | 256 | start_time = queue_record.getEstimatedJobStartTime() | ||
333 | 257 | if start_time is None: | ||
334 | 258 | return None | ||
335 | 259 | else: | ||
336 | 260 | start_time = queue_record.job.date_started | ||
337 | 261 | duration = queue_record.estimated_duration | ||
338 | 262 | return start_time + duration | ||
339 | 263 | |||
340 | 264 | @cachedproperty | ||
341 | 265 | def date(self): | ||
342 | 266 | """The date when the build completed or is estimated to complete.""" | ||
343 | 267 | if self.estimate: | ||
344 | 268 | return self.eta | ||
345 | 269 | return self.context.date_finished | ||
346 | 270 | |||
347 | 271 | @cachedproperty | ||
348 | 272 | def estimate(self): | ||
349 | 273 | """If true, the date value is an estimate.""" | ||
350 | 274 | if self.context.date_finished is not None: | ||
351 | 275 | return False | ||
352 | 276 | return self.eta is not None | ||
353 | 277 | |||
354 | 245 | 278 | ||
355 | 246 | class BuildRetryView(BuildView): | 279 | class BuildRetryView(BuildView): |
356 | 247 | """View class for retrying `IBinaryPackageBuild`s""" | 280 | """View class for retrying `IBinaryPackageBuild`s""" |
357 | 248 | 281 | ||
358 | === modified file 'lib/lp/soyuz/model/processor.py' | |||
359 | --- lib/lp/soyuz/model/processor.py 2010-08-20 20:31:18 +0000 | |||
360 | +++ lib/lp/soyuz/model/processor.py 2010-11-30 01:38:20 +0000 | |||
361 | @@ -38,6 +38,9 @@ | |||
362 | 38 | title = StringCol(dbName='title', notNull=True) | 38 | title = StringCol(dbName='title', notNull=True) |
363 | 39 | description = StringCol(dbName='description', notNull=True) | 39 | description = StringCol(dbName='description', notNull=True) |
364 | 40 | 40 | ||
365 | 41 | def __repr__(self): | ||
366 | 42 | return "<Processor %r>" % self.title | ||
367 | 43 | |||
368 | 41 | 44 | ||
369 | 42 | class ProcessorFamily(SQLBase): | 45 | class ProcessorFamily(SQLBase): |
370 | 43 | implements(IProcessorFamily) | 46 | implements(IProcessorFamily) |
371 | @@ -55,6 +58,9 @@ | |||
372 | 55 | return Processor(family=self, name=name, title=title, | 58 | return Processor(family=self, name=name, title=title, |
373 | 56 | description=description) | 59 | description=description) |
374 | 57 | 60 | ||
375 | 61 | def __repr__(self): | ||
376 | 62 | return "<ProcessorFamily %r>" % self.title | ||
377 | 63 | |||
378 | 58 | 64 | ||
379 | 59 | class ProcessorFamilySet: | 65 | class ProcessorFamilySet: |
380 | 60 | implements(IProcessorFamilySet) | 66 | implements(IProcessorFamilySet) |
381 | 61 | 67 | ||
382 | === modified file 'lib/lp/soyuz/templates/build-index.pt' | |||
383 | --- lib/lp/soyuz/templates/build-index.pt 2010-08-06 16:01:38 +0000 | |||
384 | +++ lib/lp/soyuz/templates/build-index.pt 2010-11-30 01:38:20 +0000 | |||
385 | @@ -190,8 +190,7 @@ | |||
386 | 190 | <li tal:define="file context/log" | 190 | <li tal:define="file context/log" |
387 | 191 | tal:condition="file"> | 191 | tal:condition="file"> |
388 | 192 | <a class="sprite download" | 192 | <a class="sprite download" |
391 | 193 | tal:attributes="href context/log_url" | 193 | tal:attributes="href context/log_url">buildlog</a> |
390 | 194 | tal:content="string: buildlog">BUILDLOG</a> | ||
392 | 195 | (<span tal:replace="file/content/filesize/fmt:bytes" />) | 194 | (<span tal:replace="file/content/filesize/fmt:bytes" />) |
393 | 196 | </li> | 195 | </li> |
394 | 197 | <li tal:define="file context/upload_log" | 196 | <li tal:define="file context/upload_log" |
395 | 198 | 197 | ||
396 | === modified file 'lib/lp/testing/factory.py' | |||
397 | --- lib/lp/testing/factory.py 2010-11-25 17:24:59 +0000 | |||
398 | +++ lib/lp/testing/factory.py 2010-11-30 01:38:20 +0000 | |||
399 | @@ -2771,7 +2771,7 @@ | |||
400 | 2771 | 2771 | ||
401 | 2772 | def makeBinaryPackageBuild(self, source_package_release=None, | 2772 | def makeBinaryPackageBuild(self, source_package_release=None, |
402 | 2773 | distroarchseries=None, archive=None, builder=None, | 2773 | distroarchseries=None, archive=None, builder=None, |
404 | 2774 | status=None, pocket=None): | 2774 | status=None, pocket=None, date_created=None, processor=None): |
405 | 2775 | """Create a BinaryPackageBuild. | 2775 | """Create a BinaryPackageBuild. |
406 | 2776 | 2776 | ||
407 | 2777 | If archive is not supplied, the source_package_release is used | 2777 | If archive is not supplied, the source_package_release is used |
408 | @@ -2795,7 +2795,8 @@ | |||
409 | 2795 | self.makeSourcePackagePublishingHistory( | 2795 | self.makeSourcePackagePublishingHistory( |
410 | 2796 | distroseries=source_package_release.upload_distroseries, | 2796 | distroseries=source_package_release.upload_distroseries, |
411 | 2797 | archive=archive, sourcepackagerelease=source_package_release) | 2797 | archive=archive, sourcepackagerelease=source_package_release) |
413 | 2798 | processor = self.makeProcessor() | 2798 | if processor is None: |
414 | 2799 | processor = self.makeProcessor() | ||
415 | 2799 | if distroarchseries is None: | 2800 | if distroarchseries is None: |
416 | 2800 | distroarchseries = self.makeDistroArchSeries( | 2801 | distroarchseries = self.makeDistroArchSeries( |
417 | 2801 | distroseries=source_package_release.upload_distroseries, | 2802 | distroseries=source_package_release.upload_distroseries, |
418 | @@ -2804,6 +2805,8 @@ | |||
419 | 2804 | status = BuildStatus.NEEDSBUILD | 2805 | status = BuildStatus.NEEDSBUILD |
420 | 2805 | if pocket is None: | 2806 | if pocket is None: |
421 | 2806 | pocket = PackagePublishingPocket.RELEASE | 2807 | pocket = PackagePublishingPocket.RELEASE |
422 | 2808 | if date_created is None: | ||
423 | 2809 | date_created = self.getUniqueDate() | ||
424 | 2807 | binary_package_build = getUtility(IBinaryPackageBuildSet).new( | 2810 | binary_package_build = getUtility(IBinaryPackageBuildSet).new( |
425 | 2808 | source_package_release=source_package_release, | 2811 | source_package_release=source_package_release, |
426 | 2809 | processor=processor, | 2812 | processor=processor, |
427 | @@ -2811,7 +2814,7 @@ | |||
428 | 2811 | status=status, | 2814 | status=status, |
429 | 2812 | archive=archive, | 2815 | archive=archive, |
430 | 2813 | pocket=pocket, | 2816 | pocket=pocket, |
432 | 2814 | date_created=self.getUniqueDate()) | 2817 | date_created=date_created) |
433 | 2815 | naked_build = removeSecurityProxy(binary_package_build) | 2818 | naked_build = removeSecurityProxy(binary_package_build) |
434 | 2816 | naked_build.builder = builder | 2819 | naked_build.builder = builder |
435 | 2817 | return binary_package_build | 2820 | return binary_package_build |
This looks really excellent!