Merge lp:~abentley/launchpad/recipe-index into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~abentley/launchpad/recipe-index
Merge into: lp:launchpad
Diff against target: 1151 lines (+457/-87)
24 files modified
configs/development/build-from-branch.zcml (+36/-0)
lib/lp/buildmaster/interfaces/buildbase.py (+6/-6)
lib/lp/buildmaster/model/builder.py (+2/-2)
lib/lp/code/browser/sourcepackagerecipe.py (+61/-0)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+128/-0)
lib/lp/code/configure.zcml (+8/-1)
lib/lp/code/interfaces/sourcepackagerecipe.py (+23/-2)
lib/lp/code/model/sourcepackagerecipe.py (+27/-8)
lib/lp/code/model/sourcepackagerecipedata.py (+4/-4)
lib/lp/code/templates/sourcepackagerecipe-index.pt (+80/-0)
lib/lp/registry/browser/person.py (+5/-0)
lib/lp/registry/interfaces/person.py (+3/-0)
lib/lp/registry/model/person.py (+6/-0)
lib/lp/soyuz/doc/build-failedtoupload-workflow.txt (+1/-1)
lib/lp/soyuz/doc/build-notification.txt (+2/-2)
lib/lp/soyuz/doc/build.txt (+1/-1)
lib/lp/soyuz/doc/buildd-mass-retry.txt (+4/-4)
lib/lp/soyuz/doc/buildd-slavescanner.txt (+6/-6)
lib/lp/soyuz/doc/gina.txt (+1/-1)
lib/lp/soyuz/stories/ppa/xx-copy-packages.txt (+1/-1)
lib/lp/soyuz/stories/soyuz/xx-build-record.txt (+11/-10)
lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt (+30/-30)
lib/lp/soyuz/stories/soyuz/xx-private-builds.txt (+1/-1)
lib/lp/testing/factory.py (+10/-7)
To merge this branch: bzr merge lp:~abentley/launchpad/recipe-index
Reviewer Review Type Date Requested Status
Michael Nelson (community) ui Approve
Guilherme Salgado (community) Approve
Paul Hummer (community) ui* Approve
Canonical Launchpad Engineering code Pending
Review via email: mp+21730@code.launchpad.net

Description of the change

= Summary =
Provide an index page for recipes

== Proposed fix ==

== Pre-implementation notes ==
Mostly discussed with rockstar, some with thumper

== Implementation details ==
Because we do not want to provide an unfinished UI to Edge users, this page
is only enabled for the test runner and devel.

As well as a view for recipes, a view for builds was provided, so that we can
get user-facing information the view class.

It was determined, in discussion with Muharem, that the default of creating
builders with manual=True is not a sane default for test purposes. This has an
impact on ETA calculations, because manual builders are ignored in such
calculations. Therefore, I added the ability to control the manual flag in
IBuilderSet.new, and forced it to False in the factory method.

In consultation with Michael Hudson, I determined that
_SourcePackageRecipeData had several attributes that it would make sense to
view and manipulate directly. However, it needs to remain a separate class
because it is common to recipes and manifests. So I had SourcePackageRecipe
delegate to it.

There were miscellaneous lint fixes, like calling Storm.__init__ in subclasses.

I adjusted LaunchpadObjectFactory.makeDistroSeries to permit specifying a
displayname for distroseries. I adjusted makeSourcePackageRecipeBuildJob to
allow specifying a build. These changes helped me provide a consistent
homepage for tests.

== Tests ==
bin/test -v test_sourcepackagerecipe

== Demo and Q/A ==
Cannot be Q/A'ed until more pieces are in place.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/buildmaster/model/builder.py
  lib/lp/code/configure.zcml
  lib/lp/code/model/sourcepackagerecipedata.py
  configs/development/build-from-branch.zcml
  lib/lp/testing/factory.py
  lib/lp/code/templates/sourcepackagerecipe-index.pt
  lib/lp/registry/interfaces/person.py
  lib/lp/code/browser/sourcepackagerecipe.py
  lib/lp/code/model/sourcepackagerecipe.py
  lib/lp/code/browser/tests/test_sourcepackagerecipe.py
  lib/lp/code/interfaces/sourcepackagerecipe.py
  lib/lp/registry/browser/person.py
  lib/lp/registry/model/person.py

== Pylint notices ==

lib/lp/code/model/sourcepackagerecipedata.py
    14: [F0401] Unable to import 'bzrlib.plugins.builder.recipe' (No module named builder)
    17: [F0401] Unable to import 'lazr.enum' (No module named enum)

lib/lp/testing/factory.py
    1710: [F0401, LaunchpadObjectFactory.makeRecipe] Unable to import 'bzrlib.plugins.builder.recipe' (No module named builder)

lib/lp/registry/interfaces/person.py
    523: [C0301] Line too long (80/78)
    53: [F0401] Unable to import 'lazr.enum' (No module named enum)
    54: [F0401] Unable to import 'lazr.lifecycle.snapshot' (No module named lifecycle)
    55: [F0401] Unable to import 'lazr.restful.interface' (No module named restful)
    56: [F0401] Unable to import 'lazr.restful.declarations' (No module named restful)
    63: [F0401] Unable to import 'lazr.restful.fields' (No module named restful)
    410: [E1002, PersonNameField._validate] Use super on an old style class
    1414: [C0322, IPersonEditRestricted.addMember] Operator not preceded by a space
    status=copy_field(ITeamMembership['status']),
    ^
    comment=Text(required=False))
    @export_write_operation()
    def addMember(person, reviewer, status=TeamMembershipStatus.APPROVED,
    comment=None, force_team_add=False,
    may_subscribe_to_list=True):
    1455: [C0322, IPersonEditRestricted.acceptInvitationToBeMemberOf] Operator not preceded by a space
    comment=Text())
    ^
    @export_write_operation()
    def acceptInvitationToBeMemberOf(team, comment):
    1467: [C0322, IPersonEditRestricted.declineInvitationToBeMemberOf] Operator not preceded by a space
    comment=Text())
    ^
    @export_write_operation()
    def declineInvitationToBeMemberOf(team, comment):
    1765: [C0322, IPersonSet.newTeam] Operator not preceded by a space
    defaultmembershipperiod='default_membership_period',
    ^
    defaultrenewalperiod='default_renewal_period')
    @operation_parameters(
    subscriptionpolicy=Choice(
    title=_('Subscription policy'), vocabulary=TeamSubscriptionPolicy,
    required=False, default=TeamSubscriptionPolicy.MODERATED))
    @export_factory_operation(
    ITeam, ['name', 'displayname', 'teamdescription',
    'defaultmembershipperiod', 'defaultrenewalperiod'])
    def newTeam(teamowner, name, displayname, teamdescription=None,
    subscriptionpolicy=TeamSubscriptionPolicy.MODERATED,
    defaultmembershipperiod=None, defaultrenewalperiod=None):
    1834: [C0322, IPersonSet.findPerson] Operator not preceded by a space
    created_after=Datetime(
    ^
    title=_("Created after"), required=False),
    created_before=Datetime(
    title=_("Created before"), required=False),
    )
    @operation_returns_collection_of(IPerson)
    @export_read_operation()
    def findPerson(text="", exclude_inactive_accounts=True,
    must_have_email=False,
    created_after=None, created_before=None):

lib/lp/code/model/sourcepackagerecipe.py
    11: [F0401] Unable to import 'lazr.delegates' (No module named delegates)

lib/lp/code/browser/tests/test_sourcepackagerecipe.py
    125: [C0301] Line too long (79/78)

lib/lp/code/interfaces/sourcepackagerecipe.py
    17: [F0401] Unable to import 'lazr.restful.fields' (No module named restful)

lib/lp/registry/browser/person.py
    117: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
    118: [F0401] Unable to import 'lazr.config' (No module named config)
    119: [F0401] Unable to import 'lazr.restful.interface' (No module named restful)
    120: [F0401] Unable to import 'lazr.restful.interfaces' (No module named restful)
    230: [F0401] Unable to import 'lazr.uri' (No module named uri)

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :
Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (18.4 KiB)

This looks good but I have a few questions/suggestions.

 review needsfixing

On Fri, 2010-03-19 at 14:53 +0000, Aaron Bentley wrote:
>
> = Summary =
> Provide an index page for recipes
>
> == Proposed fix ==
>
> == Pre-implementation notes ==
> Mostly discussed with rockstar, some with thumper
>
> == Implementation details ==
> Because we do not want to provide an unfinished UI to Edge users, this
> page
> is only enabled for the test runner and devel.
>
> As well as a view for recipes, a view for builds was provided, so that
> we can
> get user-facing information the view class.
>
> It was determined, in discussion with Muharem, that the default of
> creating
> builders with manual=True is not a sane default for test purposes.
> This has an
> impact on ETA calculations, because manual builders are ignored in
> such
> calculations. Therefore, I added the ability to control the manual
> flag in
> IBuilderSet.new, and forced it to False in the factory method.
>
> In consultation with Michael Hudson, I determined that
> _SourcePackageRecipeData had several attributes that it would make
> sense to
> view and manipulate directly. However, it needs to remain a separate
> class
> because it is common to recipes and manifests. So I had
> SourcePackageRecipe
> delegate to it.
>
> There were miscellaneous lint fixes, like calling Storm.__init__ in
> subclasses.
>
> I adjusted LaunchpadObjectFactory.makeDistroSeries to permit
> specifying a
> displayname for distroseries. I adjusted
> makeSourcePackageRecipeBuildJob to
> allow specifying a build. These changes helped me provide a
> consistent
> homepage for tests.
>
> == Tests ==
> bin/test -v test_sourcepackagerecipe
>
> == Demo and Q/A ==
> Cannot be Q/A'ed until more pieces are in place.
>

> === modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
> --- lib/lp/code/browser/sourcepackagerecipe.py 2010-03-12 18:53:55 +0000
> +++ lib/lp/code/browser/sourcepackagerecipe.py 2010-03-19 14:53:16 +0000
> @@ -6,3 +6,79 @@
> __metaclass__ = type
>
> __all__ = []
> +
> +
> +from canonical.launchpad.webapp import (
> + LaunchpadView)
> +from lp.buildmaster.interfaces.buildbase import BuildStatus
> +
> +
> +class SourcePackageRecipeView(LaunchpadView):
> + """Default view of a SourcePackageRecipe."""
> +
> + @property
> + def title(self):
> + return self.context.name
> +
> + label = title
> +
> + @property
> + def builds(self):
> + """A list of interesting builds.
> +
> + All pending builds are shown, as well as 1-5 recent builds.
> + Recent builds are ordered by date completed.
> + """
> + builds = list(self.context.getBuilds(pending=True))
> + for build in self.context.getBuilds():
> + builds.append(build)
> + if len(builds) >= 5:
> + break
> + builds.reverse()
> + return builds
> +
> +
> +class SourcePackageRecipeBuildView(LaunchpadView):
> + """Default view of a SourcePackageRecipeBuild."""
> +
> + @property
> + def status(self):
> + """A human-friendly status string."""
> + description = {
> + BuildStatus.NEEDSBUILD: 'Pending...

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote :
Download full text (10.5 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Guilherme Salgado wrote:
>> + @property
>> + def status(self):
>> + """A human-friendly status string."""
>> + description = {
>> + BuildStatus.NEEDSBUILD: 'Pending build',
>> + BuildStatus.FULLYBUILT: 'Successful build',
>> + BuildStatus.FAILEDTOBUILD: 'Failed to build',
>> + BuildStatus.MANUALDEPWAIT:
>> + 'Could not build because of missing dependencies',
>> + BuildStatus.CHROOTWAIT:
>> + 'Could not build because of chroot issues',
>> + BuildStatus.SUPERSEDED:
>> + 'Could not build because source package was superseded',
>> + BuildStatus.BUILDING:
>> + 'Currently building',
>> + BuildStatus.FAILEDTOUPLOAD:
>> + 'Could not be uploaded correctly',
>
> Would it not make sense to have these strings as the title of the items
> in the BuildStatus enum?

That's Soyuz code, and I don't want to change it unnecessarily. I don't
know how the existing titles are used, and whether these strings would
make sense in that context.

>> === added file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
>> --- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
>> +++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-03-19 14:53:16 +0000
>> +class TestSourcePackageRecipe(TestCaseWithFactory):
>
> Judging by the test class' name, I was expecting to see a test for
> SourcePackageRecipe, but in fact it's testing SPR's default view. How
> about renaming the test class to TestSourcePackageRecipeView?

Done.

>
>> +
>> + layer = DatabaseFunctionalLayer
>> +
>> + def makeRecipe(self):
>> + chef = self.factory.makePersonNoCommit(displayname='Master Chef',
>> + name='chef')
>> + chocolate = self.factory.makeProduct(name='chocolate')
>> + cake_branch = self.factory.makeProductBranch(owner=chef, name='cake',
>> + product=chocolate)
>
> Mind moving all arguments into the same line, in the calls to
> makeProductBranch and makePerson above? They should either be on a line
> by themselves or be aligned...

Done.

>> + def getMainText(self, recipe):
>> + browser = self.getUserBrowser(canonical_url(recipe))
>> + return extract_text(find_main_content(browser.contents))
>> +
>> + def test_index(self):
>> + recipe = self.makeRecipe()
>> + build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild(
>> + recipe=recipe))
>> + build.buildstate = BuildStatus.FULLYBUILT
>> + build.datebuilt = datetime(2010, 03, 16, tzinfo=utc)
>> + pattern = re.compile(dedent("""\
>> + Master Chef
>> + Branches
>> + Description
>> + This recipe .*changes.
>> + Recipe Information
>> + Owner:
>> + Master Chef
>> + Base branch:
>> + lp://dev/~chef/chocolate/cake
>> + Debian version:
>> + 1.0
>> + Distros:
>> + Secret Squirrel
>> + Build reco...

1=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
2--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-03-18 19:41:22 +0000
3+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-03-22 13:50:35 +0000
4@@ -15,14 +15,13 @@
5 from zope.security.proxy import removeSecurityProxy
6
7 from canonical.launchpad.webapp import canonical_url
8-from canonical.launchpad.webapp.servers import LaunchpadTestRequest
9 from canonical.launchpad.testing.pages import extract_text, find_main_content
10 from lp.buildmaster.interfaces.buildbase import BuildStatus
11 from lp.code.browser.sourcepackagerecipe import SourcePackageRecipeView
12 from lp.testing import (TestCaseWithFactory)
13
14
15-class TestSourcePackageRecipe(TestCaseWithFactory):
16+class TestSourcePackageRecipeView(TestCaseWithFactory):
17
18 layer = DatabaseFunctionalLayer
19
20@@ -30,8 +29,8 @@
21 chef = self.factory.makePersonNoCommit(displayname='Master Chef',
22 name='chef')
23 chocolate = self.factory.makeProduct(name='chocolate')
24- cake_branch = self.factory.makeProductBranch(owner=chef, name='cake',
25- product=chocolate)
26+ cake_branch = self.factory.makeProductBranch(
27+ owner=chef, name='cake', product=chocolate)
28 distroseries = self.factory.makeDistroSeries(
29 displayname='Secret Squirrel')
30 return self.factory.makeSourcePackageRecipe(
31@@ -98,7 +97,7 @@
32 self.assertTrue(pattern.search(main_text), main_text)
33
34 def test_builds(self):
35- """Ensure SourcePackageRecipe.builds is as described."""
36+ """Ensure SourcePackageRecipeView.builds is as described."""
37 recipe = self.makeRecipe()
38 build1 = self.makeBuildJob(recipe=recipe)
39 build2 = self.makeBuildJob(recipe=recipe)
40@@ -106,7 +105,7 @@
41 build4 = self.makeBuildJob(recipe=recipe)
42 build5 = self.makeBuildJob(recipe=recipe)
43 build6 = self.makeBuildJob(recipe=recipe)
44- view = SourcePackageRecipeView(recipe, LaunchpadTestRequest)
45+ view = SourcePackageRecipeView(recipe, None)
46 self.assertEqual(
47 set([build1, build2, build3, build4, build5, build6]),
48 set(view.builds))
49@@ -115,6 +114,8 @@
50 2010, 03, day, tzinfo=utc)
51 set_day(build1, 16)
52 set_day(build2, 15)
53+ # When there are 4+ pending builds, only the the most
54+ # recently-completed build is returned (i.e. build1, not build2)
55 self.assertEqual(
56 set([build1, build3, build4, build5, build6]),
57 set(view.builds))
58
59=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
60--- lib/lp/code/interfaces/sourcepackagerecipe.py 2010-03-19 14:35:22 +0000
61+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2010-03-22 13:53:14 +0000
62@@ -105,7 +105,11 @@
63 """
64
65 def getBuilds(pending=False):
66- """Return a ResultSet of all the specified builds."""
67+ """Return a ResultSet of all the builds in the given state.
68+
69+ :param pending: If True, select all builds that are pending. If
70+ False, select all builds that are not pending.
71+ """
72
73
74 class ISourcePackageRecipeSource(Interface):
75
76=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
77--- lib/lp/code/model/sourcepackagerecipe.py 2010-03-18 19:41:22 +0000
78+++ lib/lp/code/model/sourcepackagerecipe.py 2010-03-22 13:55:25 +0000
79@@ -120,6 +120,7 @@
80 return build
81
82 def getBuilds(self, pending=False):
83+ """See `ISourcePackageRecipe`."""
84 if pending:
85 clauses = [SourcePackageRecipeBuild.datebuilt == None]
86 else:
87
88=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
89--- lib/lp/code/model/sourcepackagerecipedata.py 2010-03-18 19:41:22 +0000
90+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-03-22 13:58:17 +0000
91@@ -50,7 +50,7 @@
92
93 def __init__(self, name, type, comment, line_number, branch, revspec,
94 directory, recipe_data, parent_instruction):
95- super( _SourcePackageRecipeDataInstruction, self).__init__()
96+ super(_SourcePackageRecipeDataInstruction, self).__init__()
97 self.name = unicode(name)
98 self.type = type
99 self.comment = comment
Revision history for this message
Paul Hummer (rockstar) wrote :

I don't have any comments because we've been iterating over this for a week, making changes here and there. This is exactly as we have discussed over the last week.

review: Approve (ui*)
Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (4.0 KiB)

On Mon, 2010-03-22 at 14:09 +0000, Aaron Bentley wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Guilherme Salgado wrote:
> >> + @property
> >> + def status(self):
> >> + """A human-friendly status string."""
> >> + description = {
> >> + BuildStatus.NEEDSBUILD: 'Pending build',
> >> + BuildStatus.FULLYBUILT: 'Successful build',
> >> + BuildStatus.FAILEDTOBUILD: 'Failed to build',
> >> + BuildStatus.MANUALDEPWAIT:
> >> + 'Could not build because of missing dependencies',
> >> + BuildStatus.CHROOTWAIT:
> >> + 'Could not build because of chroot issues',
> >> + BuildStatus.SUPERSEDED:
> >> + 'Could not build because source package was superseded',
> >> + BuildStatus.BUILDING:
> >> + 'Currently building',
> >> + BuildStatus.FAILEDTOUPLOAD:
> >> + 'Could not be uploaded correctly',
> >
> > Would it not make sense to have these strings as the title of the items
> > in the BuildStatus enum?
>
> That's Soyuz code, and I don't want to change it unnecessarily. I don't
> know how the existing titles are used, and whether these strings would
> make sense in that context.
>

Being able to use the enum's titles both here and in soyuz code will
make for more consistent UI and avoid this extra code, so we all win.
Have you checked what the current title is for them? Maybe you could
use what's there already?

Julian, what do you think?

> >> === modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
> >> --- lib/lp/code/model/sourcepackagerecipedata.py 2010-02-05 15:07:44 +0000
> >> +++ lib/lp/code/model/sourcepackagerecipedata.py 2010-03-19 14:53:16 +0000
> >> @@ -9,7 +9,7 @@
> >> """
> >>
> >> __metaclass__ = type
> >> -__all__ = ['_SourcePackageRecipeData']
> >> +__all__ = ['SourcePackageRecipeData']
> >
> > Why did you rename it?
>
> It has become part of the public API, no longer an implementation
> detail, because it manages data that is useful to address directly.
>

Ok.

> >> from bzrlib.plugins.builder.recipe import (
> >> BaseRecipeBranch, MergeInstruction, NestInstruction, RecipeBranch)
> >> @@ -50,6 +50,7 @@
> >>
> >> def __init__(self, name, type, comment, line_number, branch, revspec,
> >> directory, recipe_data, parent_instruction):
> >> + super( _SourcePackageRecipeDataInstruction, self).__init__()
> >
> > Is there a whitespace preceding the class name above?
>
> There was. Gone now.
>
> >> + <dl id="distros">
> >> + <dt>Distros:</dt>
> >
> > Is this really meant to be in the plural? Judging by the code below,
> > I'd expect to see a single distribution series here.
>
> Yes, it is meant to be in the plural. There is a database patch that is
> just waiting for Bjorn's review to change the relationship to a
> many-to-many:
> https://code.edge.launchpad.net/~abentley/launchpad/multiple-series-recipe/+merge/21257
>

Ok, cool.

> > Also, we shouldn't
> > be using 'Distros' in the UI, should we?
>
> Okay, so what should we be using?

Distributions, perha...

Read more...

Revision history for this message
Aaron Bentley (abentley) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Guilherme Salgado wrote:
> Being able to use the enum's titles both here and in soyuz code will
> make for more consistent UI and avoid this extra code, so we all win.
> Have you checked what the current title is for them? Maybe you could
> use what's there already?

I have looked at what's there, and it's not appropriate for us. That
suggests our text won't be appropriate in Soyuz contexts.

Also, we are attempting to land this without any impact on end-users.
Changing the enum titles would have an impact.

> Distributions, perhaps? I know we shouldn't use abbreviations in the
> UI, but maybe 'Distros' is an exception? The UI reviewer ought to know.

Okay.

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkunuO0ACgkQ0F+nu1YWqI11fACfXQRXTz9oMdqEZReXFbW/7T4L
iUMAnjYRuLh1ZQQ4O0+gI1uGSYvoeLnc
=6O54
-----END PGP SIGNATURE-----

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (3.7 KiB)

Hi Aaron and Paul,

This is looking great! First, sorry if I'm re-asking questions that may have already been discussed by you both. But as I don't know the history of the discussions, I'll ask them anyway.

To test this out in the UI, I've put a breakpoint at the end of test_index, and run a devserver using the testrunner config (it'd be great to include instructions like this when requesting a ui review).

The layout looks great - nice and clean. There are a few small things that popped into mind:

The first thing that stands out is that I need to include a %20 in the url to be able to traverse to the recipe... is that intentional? So the url I'm using is:

https://code.launchpad.dev/~chef/+recipe/Cake%20Recipe

Normally we have a human readable displayname, and name is normally cake_recipe, but perhaps you guys decided against this for good reason (we should slugify the displayname for the initial name right?). The mockup for recipe creation assumed it would be a slug: http://people.canonical.com/~michaeln/bfb/create_recipe_v2-collapsed.png

It's unfortunate that branch names are so long - it's the one thing that looks very squashed in your screenshot (looks fine locally with a wider browser of course), but I guess you always have to deal with that :) Is it possible to use a short name here?

Now I'm sure this *is* something you've discussed - but why isn't the traversal for a recipe (or recipes) off the base branch? (is it in case the base-branch is modified?). I'm just looking at the bread-crumbs and wondering whether it will be: "person >> branches >> recipes >> recipe_display_name", which seems a bit weird to me (ie. as opposed to "person >> branches >> branch_short_name >> recipes...").

Similar to Guilherme, I think "Distros" should be "Distribution series" (as it's the series to which you're linking). This is consistent with the product-packages.pt template. There are some cases where we simply use "Series" but that's only when the context is already a distribution.

On a related note: I also agree that we should not need to re-define the buildstate titles/descriptions (what's wrong with "Successfully built 7 minutes ago" in the UI?). I don't see why we can't define a good set of common titles and descriptions (that's why we moved BuildBase and BuildStatus to lp.buildmaster). The main issue seems to be that the current ones (BuildStatus) are still soyuz-specific and I guess changing them will blow this branch out (due to test failures). Can you create a bug and XXX here so that we don't forget. Another option you might consider (still with an XXX) is to augment the current ones here, rather than create a new set with some duplication? (eg. code_specific_descriptions.get(self.context.buildstate, self.context.buildstate.title) or similar). See what you think.

Again, something you've probably already discussed, but in the mockups we were planning on presenting the "Packaging branch" (just a merge statement with the name 'packaging') if one was defined for the recipe (optional, as an advanced recipe might specify multiple packaging related branches). Is it worth adding it to the "Recipe information" portlet? (which should actually be ...

Read more...

review: Needs Information (ui)
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 22 March 2010 18:12:40 Guilherme Salgado wrote:
> On Mon, 2010-03-22 at 14:09 +0000, Aaron Bentley wrote:
> > -----BEGIN PGP SIGNED MESSAGE-----
> > Hash: SHA1
> >
> > Guilherme Salgado wrote:
> > >> + @property
> > >> + def status(self):
> > >> + """A human-friendly status string."""
> > >> + description = {
> > >> + BuildStatus.NEEDSBUILD: 'Pending build',
> > >> + BuildStatus.FULLYBUILT: 'Successful build',
> > >> + BuildStatus.FAILEDTOBUILD: 'Failed to build',
> > >> + BuildStatus.MANUALDEPWAIT:
> > >> + 'Could not build because of missing dependencies',
> > >> + BuildStatus.CHROOTWAIT:
> > >> + 'Could not build because of chroot issues',
> > >> + BuildStatus.SUPERSEDED:
> > >> + 'Could not build because source package was
> > >> superseded', + BuildStatus.BUILDING:
> > >> + 'Currently building',
> > >> + BuildStatus.FAILEDTOUPLOAD:
> > >> + 'Could not be uploaded correctly',
> > >
> > > Would it not make sense to have these strings as the title of the items
> > > in the BuildStatus enum?
> >
> > That's Soyuz code, and I don't want to change it unnecessarily. I don't
> > know how the existing titles are used, and whether these strings would
> > make sense in that context.
>
> Being able to use the enum's titles both here and in soyuz code will
> make for more consistent UI and avoid this extra code, so we all win.
> Have you checked what the current title is for them? Maybe you could
> use what's there already?
>
> Julian, what do you think?

I think that using the enum titles is absolutely the right thing to do.

There are some templates that use the existing titles (grep for
"buildstate/title"), so any changes should get a UI review to make sure they
still make sense, but I fully support anything that makes the UI more
readable!

> > >> + def getRecipe(name):
> > > I wonder if we shouldn't call this getSourcePackageRecipe(name)?
> > > Although it's a lot longer it doesn't leave one wondering what sort of
> > > recipe could a person have.
> >
> > IMO, SourcePackageRecipe is way too long, and the fewer places we use
> > it, the better. I think the confusion will be short-lived, and less
> > typing is a win.
>
> Ok

FWIW I think getSourcePackageRecipe is better. What if we introduce other
kinds of recipes in the future? And all editors I've used have auto-complete
:)

J

Revision history for this message
Aaron Bentley (abentley) wrote :
Download full text (3.2 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Michael Nelson wrote:
> Normally we have a human readable displayname, and name is normally
> cake_recipe, but perhaps you guys decided against this for good
> reason (we should slugify the displayname for the initial name
> right?). The mockup for recipe creation assumed it would be a slug:
> http://people.canonical.com/~michaeln/bfb/create_recipe_v2-collapsed.png

The table does not provide a displayname, so I used the name. It was an
error to use "Cake Recipe" as the name. I should have used
"cake_recipe" or similar.

> It's unfortunate that branch names are so long - it's the one thing
> that looks very squashed in your screenshot (looks fine locally with
> a wider browser of course), but I guess you always have to deal with
> that :) Is it possible to use a short name here?

We use short names automatically when they are available, but most
branches don't have short names.

> Now I'm sure this *is* something you've discussed - but why isn't the
> traversal for a recipe (or recipes) off the base branch?

The canonical URL is already defined in stable/dbstable; it was not
introduced by this branch. Tim proposed the current URL and no one
disagreed, so it was documented in
https://dev.launchpad.net/BuildBranchToArchiveUI/InitialCut

As you note, branch names are long, which is a disadvantage.
Sourcepackagebranches have even longer names. It's also trivial to
change the base branch in a recipe, so varying the name according to the
base branch would make URLs volatile.

> Similar to Guilherme, I think "Distros" should be "Distribution
> series" (as it's the series to which you're linking). This is
> consistent with the product-packages.pt template. There are some
> cases where we simply use "Series" but that's only when the context
> is already a distribution.

Done

> On a related note: I also agree that we should not need to re-define
> the buildstate titles/descriptions (what's wrong with "Successfully
> built 7 minutes ago" in the UI?)

It's not the UI that I was tasked to implement:
http://people.canonical.com/~rockstar/RecipeView.png

>. I don't see why we can't define a
> good set of common titles and descriptions (that's why we moved
> BuildBase and BuildStatus to lp.buildmaster). The main issue seems to
> be that the current ones (BuildStatus) are still soyuz-specific and I
> guess changing them will blow this branch out (due to test failures).

I find the existing ones hard to understand. That's the biggest issue
from my point of view. But they also didn't match the grammar in the UI
mockup.

> Can you create a bug and XXX here so that we don't forget.

Per Julian, I have changed all the enums to match what Code needs.

> Again, something you've probably already discussed, but in the
> mockups we were planning on presenting the "Packaging branch"

We believe that's not required for the initial cut. We are going for
the simplest thing that could possibly work.

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkuo1TAACgkQ0F+nu1YWqI24+QCfUT8AQewwkMkqOV8rbYaLIMch
ncoAnj4u3smwkzElVHGC3Jfa...

Read more...

1=== modified file 'lib/lp/buildmaster/interfaces/buildbase.py'
2--- lib/lp/buildmaster/interfaces/buildbase.py 2010-03-08 12:54:32 +0000
3+++ lib/lp/buildmaster/interfaces/buildbase.py 2010-03-23 14:16:15 +0000
4@@ -40,7 +40,7 @@
5 """
6
7 NEEDSBUILD = DBItem(0, """
8- Needs building
9+ Pending build
10
11 Build record is fresh and needs building. Nothing is yet known to
12 block this build and it is a candidate for building on any free
13@@ -48,7 +48,7 @@
14 """)
15
16 FULLYBUILT = DBItem(1, """
17- Successfully built
18+ Successful build
19
20 Build record is an historic account of the build. The build is complete
21 and needs no further work to complete it. The build log etc are all
22@@ -65,7 +65,7 @@
23 """)
24
25 MANUALDEPWAIT = DBItem(3, """
26- Dependency wait
27+ Could not build because of missing dependencies
28
29 Build record represents a package whose build dependencies cannot
30 currently be satisfied within the relevant DistroArchSeries. This
31@@ -74,7 +74,7 @@
32 """)
33
34 CHROOTWAIT = DBItem(4, """
35- Chroot problem
36+ Could not build because of chroot problem
37
38 Build record represents a build which needs a chroot currently known
39 to be damaged or bad in some way. The buildd maintainer will have to
40@@ -83,7 +83,7 @@
41 """)
42
43 SUPERSEDED = DBItem(5, """
44- Build for superseded Source
45+ Could not build because source package was superseded
46
47 Build record represents a build which never got to happen because the
48 source package release for the build was superseded before the job
49@@ -99,7 +99,7 @@
50 """)
51
52 FAILEDTOUPLOAD = DBItem(7, """
53- Failed to upload
54+ Could not be uploaded correctly
55
56 Build record is an historic account of a build that could not be
57 uploaded correctly. It's mainly genereated by failures in
58
59=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
60--- lib/lp/code/browser/sourcepackagerecipe.py 2010-03-19 14:35:22 +0000
61+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-03-23 14:10:34 +0000
62@@ -44,25 +44,10 @@
63 @property
64 def status(self):
65 """A human-friendly status string."""
66- description = {
67- BuildStatus.NEEDSBUILD: 'Pending build',
68- BuildStatus.FULLYBUILT: 'Successful build',
69- BuildStatus.FAILEDTOBUILD: 'Failed to build',
70- BuildStatus.MANUALDEPWAIT:
71- 'Could not build because of missing dependencies',
72- BuildStatus.CHROOTWAIT:
73- 'Could not build because of chroot issues',
74- BuildStatus.SUPERSEDED:
75- 'Could not build because source package was superseded',
76- BuildStatus.BUILDING:
77- 'Currently building',
78- BuildStatus.FAILEDTOUPLOAD:
79- 'Could not be uploaded correctly',
80- }
81 if self.context.buildstate == BuildStatus.NEEDSBUILD:
82 if self.eta is None:
83 return 'No suitable builders'
84- return description[self.context.buildstate]
85+ return self.context.buildstate.title
86
87 @property
88 def eta(self):
89
90=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
91--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-03-22 14:06:06 +0000
92+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-03-23 14:29:36 +0000
93@@ -34,7 +34,7 @@
94 distroseries = self.factory.makeDistroSeries(
95 displayname='Secret Squirrel')
96 return self.factory.makeSourcePackageRecipe(
97- None, chef, distroseries, None, u'Cake Recipe', cake_branch)
98+ None, chef, distroseries, None, u'cake_recipe', cake_branch)
99
100 def getMainText(self, recipe):
101 browser = self.getUserBrowser(canonical_url(recipe))
102@@ -51,14 +51,14 @@
103 Branches
104 Description
105 This recipe .*changes.
106- Recipe Information
107+ Recipe information
108 Owner:
109 Master Chef
110 Base branch:
111 lp://dev/~chef/chocolate/cake
112 Debian version:
113 1.0
114- Distros:
115+ Distribution series:
116 Secret Squirrel
117 Build records
118 Successful build.on 2010-03-16
119@@ -66,7 +66,7 @@
120 # bzr-builder format 0.2 deb-version 1.0
121 lp://dev/~chef/chocolate/cake"""), re.S)
122 main_text = self.getMainText(recipe)
123- self.assertTrue(pattern.search(main_text), main_text)
124+ self.assertTrue(pattern.search(main_text), repr(main_text))
125
126 def test_index_no_suitable_builders(self):
127 recipe = self.makeRecipe()
128
129=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
130--- lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-03-18 19:41:22 +0000
131+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-03-23 14:04:57 +0000
132@@ -31,7 +31,7 @@
133 <div class="yui-g">
134 <div class="yui-u first">
135 <div class="portlet">
136- <h2>Recipe Information</h2>
137+ <h2>Recipe information</h2>
138 <div class="two-column-list">
139 <dl id="owner">
140 <dt>Owner:</dt>
141@@ -46,7 +46,7 @@
142 <dd tal:content="context/deb_version_template" />
143 </dl>
144 <dl id="distros">
145- <dt>Distros:</dt>
146+ <dt>Distribution series:</dt>
147 <dd>
148 <ul>
149 <li tal:content="structure context/distroseries/fmt:link" />
Revision history for this message
Aaron Bentley (abentley) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Julian Edwards wrote:
> I think that using the enum titles is absolutely the right thing to do.

Okay, I have changed the enum titles.

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkuo1bAACgkQ0F+nu1YWqI3aZwCdESwa/wgUV5x9T71HW8fNIIV2
hmwAni08pSXCzY8bhy1fyXabAA7CLmik
=w0Jz
-----END PGP SIGNATURE-----

Revision history for this message
Guilherme Salgado (salgado) :
review: Approve
Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (4.1 KiB)

Thanks for filling me in on all the info Aaron.

On Tue, Mar 23, 2010 at 3:51 PM, Aaron Bentley <email address hidden> wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Michael Nelson wrote:
>> Normally we have a human readable displayname, and name is normally
>> cake_recipe, but perhaps you guys decided against this for good
>> reason (we should slugify the displayname for the initial name
>> right?). The mockup for recipe creation assumed it would be a slug:
>> http://people.canonical.com/~michaeln/bfb/create_recipe_v2-collapsed.png
>
> The table does not provide a displayname, so I used the name.  It was an
> error to use "Cake Recipe" as the name.  I should have used
> "cake_recipe" or similar.

OK, so the create validation will need to enforce this I guess. Great.

>> Now I'm sure this *is* something you've discussed - but why isn't the
>> traversal for a recipe (or recipes) off the base branch?
>
> The canonical URL is already defined in stable/dbstable; it was not
> introduced by this branch.  Tim proposed the current URL and no one
> disagreed, so it was documented in
> https://dev.launchpad.net/BuildBranchToArchiveUI/InitialCut
>
> As you note, branch names are long, which is a disadvantage.
> Sourcepackagebranches have even longer names.  It's also trivial to
> change the base branch in a recipe, so varying the name according to the
> base branch would make URLs volatile.

I got to chat briefly with Tim and he had a few other points too, so
just for the record in case anyone else asks the same question (I'll
try to document this on the wiki as a FAQ or something):
{{{
08:43 < noodles775> The only reason I could think of (which Aaron also
highlighted) is that it would be fragile if the base branch changes
etc., but the original mockups didn't allow you to change the base
branch of a recipe.
08:43 < thumper> noodles775: and I picked something
08:44 < noodles775> thumper: no problem, as long as you guys have
thought about it.
08:44 < thumper> noodles775: but what about changing the name of a branch?
08:44 < thumper> noodles775: or changing the owner
08:44 < thumper> noodles775: both of those change the unique name of the branch
08:44 < thumper> noodles775: and since the branch is accessed through
the id, the recipe url would chnage
08:45 < thumper> noodles775: also ~user/+recipe/name is short
08:45 < noodles775> thumper: I see, whereas both of those things
wouldn't change the recipe url where it is currently? Great.
08:45 * thumper nods
}}}

>
>> On a related note: I also agree that we should not need to re-define
>> the buildstate titles/descriptions (what's wrong with "Successfully
>> built 7 minutes ago" in the UI?)
>
> It's not the UI that I was tasked to implement:
> http://people.canonical.com/~rockstar/RecipeView.png

Right, if Paul specifically chose "Successful build" because he
thought "Successfully built" was incorrect, then I think it's worth
updating the BuildStatus as you've done, to improve it.

>
>>. I don't see why we can't define a
>> good set of common titles and descriptions (that's why we moved
>> BuildBase and BuildStatus to lp.buildmaster). The main issue seems to
>> be that the current ones (BuildSta...

Read more...

Revision history for this message
Michael Nelson (michael.nelson) :
review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'configs/development/build-from-branch.zcml'
2--- configs/development/build-from-branch.zcml 1970-01-01 00:00:00 +0000
3+++ configs/development/build-from-branch.zcml 2010-04-05 18:28:37 +0000
4@@ -0,0 +1,36 @@
5+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
6+ GNU Affero General Public License version 3 (see the file LICENSE).
7+-->
8+
9+<configure
10+ xmlns="http://namespaces.zope.org/zope"
11+ xmlns:browser="http://namespaces.zope.org/browser"
12+ xmlns:i18n="http://namespaces.zope.org/i18n"
13+ xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
14+ i18n_domain="launchpad">
15+ <facet facet="branches">
16+
17+ <browser:defaultView
18+ for="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipe"
19+ name="+index"
20+ layer="canonical.launchpad.layers.CodeLayer"/>
21+ <browser:page
22+ for="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipe"
23+ class="lp.code.browser.sourcepackagerecipe.SourcePackageRecipeView"
24+ name="+index"
25+ template="../../lib/lp/code/templates/sourcepackagerecipe-index.pt"
26+ permission="zope.Public"/>
27+ </facet>
28+ <facet facet="branches">
29+ <browser:defaultView
30+ for="lp.code.interfaces.sourcepackagerecipebuild.ISourcePackageRecipeBuild"
31+ name="+index"
32+ layer="canonical.launchpad.layers.CodeLayer"/>
33+ <browser:page
34+ for="lp.code.interfaces.sourcepackagerecipebuild.ISourcePackageRecipeBuild"
35+ class="lp.code.browser.sourcepackagerecipe.SourcePackageRecipeBuildView"
36+ name="+index"
37+ template="../../lib/lp/code/templates/sourcepackagerecipe-index.pt"
38+ permission="zope.Public"/>
39+ </facet>
40+</configure>
41
42=== added symlink 'configs/testrunner/build-from-branch.zcml'
43=== target is u'../development/build-from-branch.zcml'
44=== modified file 'lib/lp/buildmaster/interfaces/buildbase.py'
45--- lib/lp/buildmaster/interfaces/buildbase.py 2010-03-25 14:38:25 +0000
46+++ lib/lp/buildmaster/interfaces/buildbase.py 2010-04-05 18:28:37 +0000
47@@ -40,7 +40,7 @@
48 """
49
50 NEEDSBUILD = DBItem(0, """
51- Needs building
52+ Pending build
53
54 Build record is fresh and needs building. Nothing is yet known to
55 block this build and it is a candidate for building on any free
56@@ -48,7 +48,7 @@
57 """)
58
59 FULLYBUILT = DBItem(1, """
60- Successfully built
61+ Successful build
62
63 Build record is an historic account of the build. The build is complete
64 and needs no further work to complete it. The build log etc are all
65@@ -65,7 +65,7 @@
66 """)
67
68 MANUALDEPWAIT = DBItem(3, """
69- Dependency wait
70+ Could not build because of missing dependencies
71
72 Build record represents a package whose build dependencies cannot
73 currently be satisfied within the relevant DistroArchSeries. This
74@@ -74,7 +74,7 @@
75 """)
76
77 CHROOTWAIT = DBItem(4, """
78- Chroot problem
79+ Could not build because of chroot problem
80
81 Build record represents a build which needs a chroot currently known
82 to be damaged or bad in some way. The buildd maintainer will have to
83@@ -83,7 +83,7 @@
84 """)
85
86 SUPERSEDED = DBItem(5, """
87- Build for superseded Source
88+ Could not build because source package was superseded
89
90 Build record represents a build which never got to happen because the
91 source package release for the build was superseded before the job
92@@ -99,7 +99,7 @@
93 """)
94
95 FAILEDTOUPLOAD = DBItem(7, """
96- Failed to upload
97+ Could not be uploaded correctly
98
99 Build record is an historic account of a build that could not be
100 uploaded correctly. It's mainly genereated by failures in
101
102=== modified file 'lib/lp/buildmaster/model/builder.py'
103--- lib/lp/buildmaster/model/builder.py 2010-03-31 02:15:04 +0000
104+++ lib/lp/buildmaster/model/builder.py 2010-04-05 18:28:37 +0000
105@@ -664,12 +664,12 @@
106 raise NotFoundError(name)
107
108 def new(self, processor, url, name, title, description, owner,
109- active=True, virtualized=False, vm_host=None):
110+ active=True, virtualized=False, vm_host=None, manual=True):
111 """See IBuilderSet."""
112 return Builder(processor=processor, url=url, name=name, title=title,
113 description=description, owner=owner, active=active,
114 virtualized=virtualized, vm_host=vm_host,
115- builderok=True, manual=True)
116+ builderok=True, manual=manual)
117
118 def get(self, builder_id):
119 """See IBuilderSet."""
120
121=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
122--- lib/lp/code/browser/sourcepackagerecipe.py 2010-03-12 18:53:55 +0000
123+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-04-05 18:28:37 +0000
124@@ -6,3 +6,64 @@
125 __metaclass__ = type
126
127 __all__ = []
128+
129+
130+from canonical.launchpad.webapp import (
131+ LaunchpadView)
132+from lp.buildmaster.interfaces.buildbase import BuildStatus
133+
134+
135+class SourcePackageRecipeView(LaunchpadView):
136+ """Default view of a SourcePackageRecipe."""
137+
138+ @property
139+ def title(self):
140+ return self.context.name
141+
142+ label = title
143+
144+ @property
145+ def builds(self):
146+ """A list of interesting builds.
147+
148+ All pending builds are shown, as well as 1-5 recent builds.
149+ Recent builds are ordered by date completed.
150+ """
151+ builds = list(self.context.getBuilds(pending=True))
152+ for build in self.context.getBuilds():
153+ builds.append(build)
154+ if len(builds) >= 5:
155+ break
156+ builds.reverse()
157+ return builds
158+
159+
160+class SourcePackageRecipeBuildView(LaunchpadView):
161+ """Default view of a SourcePackageRecipeBuild."""
162+
163+ @property
164+ def status(self):
165+ """A human-friendly status string."""
166+ if self.context.buildstate == BuildStatus.NEEDSBUILD:
167+ if self.eta is None:
168+ return 'No suitable builders'
169+ return self.context.buildstate.title
170+
171+ @property
172+ def eta(self):
173+ """The datetime when the build job is estimated to begin."""
174+ if self.context.buildqueue_record is None:
175+ return None
176+ return self.context.buildqueue_record.getEstimatedJobStartTime()
177+
178+ @property
179+ def date(self):
180+ """The date when the build complete or will begin."""
181+ if self.estimate:
182+ return self.eta
183+ return self.context.datebuilt
184+
185+ @property
186+ def estimate(self):
187+ """If true, the date value is an estimate."""
188+ return (self.context.datebuilt is None and self.eta is not None)
189
190=== added file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
191--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
192+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-04-05 18:28:37 +0000
193@@ -0,0 +1,128 @@
194+# Copyright 2010 Canonical Ltd. This software is licensed under the
195+# GNU Affero General Public License version 3 (see the file LICENSE).
196+
197+"""Tests for the product view classes and templates."""
198+
199+__metaclass__ = type
200+
201+
202+from datetime import datetime
203+from textwrap import dedent
204+import re
205+
206+from pytz import utc
207+from canonical.testing import DatabaseFunctionalLayer
208+from zope.security.proxy import removeSecurityProxy
209+
210+from canonical.launchpad.webapp import canonical_url
211+from canonical.launchpad.testing.pages import extract_text, find_main_content
212+from lp.buildmaster.interfaces.buildbase import BuildStatus
213+from lp.code.browser.sourcepackagerecipe import SourcePackageRecipeView
214+from lp.testing import (TestCaseWithFactory)
215+
216+
217+class TestSourcePackageRecipeView(TestCaseWithFactory):
218+
219+ layer = DatabaseFunctionalLayer
220+
221+ def makeRecipe(self):
222+ chef = self.factory.makePersonNoCommit(displayname='Master Chef',
223+ name='chef')
224+ chocolate = self.factory.makeProduct(name='chocolate')
225+ cake_branch = self.factory.makeProductBranch(
226+ owner=chef, name='cake', product=chocolate)
227+ distroseries = self.factory.makeDistroSeries(
228+ displayname='Secret Squirrel')
229+ return self.factory.makeSourcePackageRecipe(
230+ None, chef, distroseries, None, u'cake_recipe',
231+ u'This recipe builds a foo for disto bar, with my Secret Squirrel'
232+ ' changes.', cake_branch)
233+
234+ def getMainText(self, recipe):
235+ browser = self.getUserBrowser(canonical_url(recipe))
236+ return extract_text(find_main_content(browser.contents))
237+
238+ def test_index(self):
239+ recipe = self.makeRecipe()
240+ build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild(
241+ recipe=recipe))
242+ build.buildstate = BuildStatus.FULLYBUILT
243+ build.datebuilt = datetime(2010, 03, 16, tzinfo=utc)
244+ pattern = re.compile(dedent("""\
245+ Master Chef
246+ Branches
247+ Description
248+ This recipe .*changes.
249+ Recipe information
250+ Owner:
251+ Master Chef
252+ Base branch:
253+ lp://dev/~chef/chocolate/cake
254+ Debian version:
255+ 1.0
256+ Distribution series:
257+ Secret Squirrel
258+ Build records
259+ Successful build.on 2010-03-16
260+ Recipe contents
261+ # bzr-builder format 0.2 deb-version 1.0
262+ lp://dev/~chef/chocolate/cake"""), re.S)
263+ main_text = self.getMainText(recipe)
264+ self.assertTrue(pattern.search(main_text), repr(main_text))
265+
266+ def test_index_no_suitable_builders(self):
267+ recipe = self.makeRecipe()
268+ build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild(
269+ recipe=recipe))
270+ pattern = re.compile(dedent("""\
271+ Build records
272+ No suitable builders
273+ Recipe contents"""), re.S)
274+ main_text = self.getMainText(recipe)
275+ self.assertTrue(pattern.search(main_text), main_text)
276+
277+ def makeBuildJob(self, recipe):
278+ build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
279+ buildjob = self.factory.makeSourcePackageRecipeBuildJob(
280+ recipe_build=build)
281+ return build
282+
283+ def test_index_pending(self):
284+ recipe = self.makeRecipe()
285+ buildjob = self.makeBuildJob(recipe)
286+ builder = self.factory.makeBuilder()
287+ pattern = re.compile(dedent("""\
288+ Build records
289+ Pending build.in .*\(estimated\)
290+ Recipe contents"""), re.S)
291+ main_text = self.getMainText(recipe)
292+ self.assertTrue(pattern.search(main_text), main_text)
293+
294+ def test_builds(self):
295+ """Ensure SourcePackageRecipeView.builds is as described."""
296+ recipe = self.makeRecipe()
297+ build1 = self.makeBuildJob(recipe=recipe)
298+ build2 = self.makeBuildJob(recipe=recipe)
299+ build3 = self.makeBuildJob(recipe=recipe)
300+ build4 = self.makeBuildJob(recipe=recipe)
301+ build5 = self.makeBuildJob(recipe=recipe)
302+ build6 = self.makeBuildJob(recipe=recipe)
303+ view = SourcePackageRecipeView(recipe, None)
304+ self.assertEqual(
305+ set([build1, build2, build3, build4, build5, build6]),
306+ set(view.builds))
307+ def set_day(build, day):
308+ removeSecurityProxy(build).datebuilt = datetime(
309+ 2010, 03, day, tzinfo=utc)
310+ set_day(build1, 16)
311+ set_day(build2, 15)
312+ # When there are 4+ pending builds, only the the most
313+ # recently-completed build is returned (i.e. build1, not build2)
314+ self.assertEqual(
315+ set([build1, build3, build4, build5, build6]),
316+ set(view.builds))
317+ set_day(build3, 14)
318+ set_day(build4, 13)
319+ set_day(build5, 12)
320+ set_day(build6, 11)
321+ self.assertEqual([build5, build4, build3, build2, build1], view.builds)
322
323=== modified file 'lib/lp/code/configure.zcml'
324--- lib/lp/code/configure.zcml 2010-03-25 19:55:16 +0000
325+++ lib/lp/code/configure.zcml 2010-04-05 18:28:37 +0000
326@@ -1004,10 +1004,17 @@
327 <adapter factory="lp.code.model.recipebuilder.RecipeBuildBehavior"
328 permission="zope.Public" />
329
330+ <!-- SourcePackageRecipeData -->
331+ <class
332+ class="lp.code.model.sourcepackagerecipedata.SourcePackageRecipeData">
333+ <allow
334+ interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeData"/>
335+ </class>
336 <!-- SourcePackageRecipe -->
337 <class
338 class="lp.code.model.sourcepackagerecipe.SourcePackageRecipe">
339- <allow interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipe"/>
340+ <allow
341+ interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipe"/>
342 <require
343 permission="launchpad.Edit"
344 set_attributes="builder_recipe build_daily"/>
345
346=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
347--- lib/lp/code/interfaces/sourcepackagerecipe.py 2010-03-26 19:29:56 +0000
348+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2010-04-05 18:28:37 +0000
349@@ -13,6 +13,7 @@
350 __all__ = [
351 'ForbiddenInstruction',
352 'ISourcePackageRecipe',
353+ 'ISourcePackageRecipeData',
354 'ISourcePackageRecipeSource',
355 'TooNewRecipeFormat',
356 ]
357@@ -21,7 +22,7 @@
358 from lazr.restful.fields import CollectionField, Reference
359
360 from zope.interface import Attribute, Interface
361-from zope.schema import Bool, Datetime, Text, TextLine
362+from zope.schema import Bool, Datetime, Object, TextLine, Text
363
364 from canonical.launchpad import _
365 from canonical.launchpad.validators.name import name_validator
366@@ -50,7 +51,20 @@
367 self.newest_supported = newest_supported
368
369
370-class ISourcePackageRecipe(IHasOwner):
371+class ISourcePackageRecipeData(Interface):
372+ """A recipe as database data, not text."""
373+
374+ base_branch = Object(
375+ schema=IBranch, title=_("Base branch"), description=_(
376+ "The base branch to use when building the recipe."))
377+
378+ deb_version_template = TextLine(
379+ title=_('deb-version template'),
380+ description = _(
381+ 'The template that will be used to generate a deb version.'),)
382+
383+
384+class ISourcePackageRecipe(IHasOwner, ISourcePackageRecipeData):
385 """An ISourcePackageRecipe describes how to build a source package.
386
387 More precisely, it describes how to combine a number of branches into a
388@@ -105,6 +119,13 @@
389 able to upload to the archive.
390 """
391
392+ def getBuilds(pending=False):
393+ """Return a ResultSet of all the builds in the given state.
394+
395+ :param pending: If True, select all builds that are pending. If
396+ False, select all builds that are not pending.
397+ """
398+
399
400 class ISourcePackageRecipeSource(Interface):
401 """A utility of this interface can be used to create and access recipes.
402
403=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
404--- lib/lp/code/model/sourcepackagerecipe.py 2010-03-26 19:29:56 +0000
405+++ lib/lp/code/model/sourcepackagerecipe.py 2010-04-05 18:28:37 +0000
406@@ -8,8 +8,10 @@
407 'SourcePackageRecipe',
408 ]
409
410+from lazr.delegates import delegates
411+
412 from storm.locals import (
413- Bool, Int, Reference, ReferenceSet, Store, Storm, Unicode)
414+ Bool, Desc, Int, Reference, ReferenceSet, Store, Storm, Unicode)
415
416 from zope.component import getUtility
417 from zope.interface import classProvides, implements
418@@ -19,10 +21,12 @@
419
420 from lp.archiveuploader.permission import check_upload_to_archive
421 from lp.code.interfaces.sourcepackagerecipe import (
422- ISourcePackageRecipe, ISourcePackageRecipeSource)
423+ ISourcePackageRecipe, ISourcePackageRecipeSource,
424+ ISourcePackageRecipeData)
425 from lp.code.interfaces.sourcepackagerecipebuild import (
426 ISourcePackageRecipeBuildSource)
427-from lp.code.model.sourcepackagerecipedata import _SourcePackageRecipeData
428+from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
429+from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
430 from lp.registry.model.distroseries import DistroSeries
431 from lp.soyuz.interfaces.archive import ArchivePurpose
432 from lp.soyuz.interfaces.component import IComponentSet
433@@ -48,8 +52,11 @@
434 __storm_table__ = 'SourcePackageRecipe'
435
436 implements(ISourcePackageRecipe)
437+
438 classProvides(ISourcePackageRecipeSource)
439
440+ delegates(ISourcePackageRecipeData, context='_recipe_data')
441+
442 id = Int(primary=True)
443
444 date_created = UtcDateTimeCol(notNull=True)
445@@ -77,15 +84,15 @@
446 @property
447 def _recipe_data(self):
448 return Store.of(self).find(
449- _SourcePackageRecipeData,
450- _SourcePackageRecipeData.sourcepackage_recipe == self).one()
451+ SourcePackageRecipeData,
452+ SourcePackageRecipeData.sourcepackage_recipe == self).one()
453
454 def _get_builder_recipe(self):
455- """Accesses of the recipe go to the _SourcePackageRecipeData."""
456+ """Accesses of the recipe go to the SourcePackageRecipeData."""
457 return self._recipe_data.getRecipe()
458
459 def _set_builder_recipe(self, value):
460- """Setting of the recipe goes to the _SourcePackageRecipeData."""
461+ """Setting of the recipe goes to the SourcePackageRecipeData."""
462 self._recipe_data.setRecipe(value)
463
464 builder_recipe = property(_get_builder_recipe, _set_builder_recipe)
465@@ -104,7 +111,7 @@
466 """See `ISourcePackageRecipeSource.new`."""
467 store = IMasterStore(SourcePackageRecipe)
468 sprecipe = SourcePackageRecipe()
469- _SourcePackageRecipeData(builder_recipe, sprecipe)
470+ SourcePackageRecipeData(builder_recipe, sprecipe)
471 sprecipe.registrant = registrant
472 sprecipe.owner = owner
473 sprecipe.sourcepackagename = sourcepackagename
474@@ -132,3 +139,15 @@
475 self, requester, archive)
476 build.queueBuild()
477 return build
478+
479+ def getBuilds(self, pending=False):
480+ """See `ISourcePackageRecipe`."""
481+ if pending:
482+ clauses = [SourcePackageRecipeBuild.datebuilt == None]
483+ else:
484+ clauses = [SourcePackageRecipeBuild.datebuilt != None]
485+ result = Store.of(self).find(
486+ SourcePackageRecipeBuild, SourcePackageRecipeBuild.recipe==self,
487+ *clauses)
488+ result.order_by(Desc(SourcePackageRecipeBuild.datebuilt))
489+ return result
490
491=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
492--- lib/lp/code/model/sourcepackagerecipedata.py 2010-03-11 15:15:51 +0000
493+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-04-05 18:28:37 +0000
494@@ -9,7 +9,7 @@
495 """
496
497 __metaclass__ = type
498-__all__ = ['_SourcePackageRecipeData']
499+__all__ = ['SourcePackageRecipeData']
500
501 from bzrlib.plugins.builder.recipe import (
502 BaseRecipeBranch, MergeInstruction, NestInstruction, RecipeBranch)
503@@ -79,7 +79,7 @@
504 directory = Unicode(allow_none=True)
505
506 recipe_data_id = Int(name='recipe_data', allow_none=False)
507- recipe_data = Reference(recipe_data_id, '_SourcePackageRecipeData.id')
508+ recipe_data = Reference(recipe_data_id, 'SourcePackageRecipeData.id')
509
510 parent_instruction_id = Int(name='parent_instruction', allow_none=True)
511 parent_instruction = Reference(
512@@ -98,7 +98,7 @@
513 return branch
514
515
516-class _SourcePackageRecipeData(Storm):
517+class SourcePackageRecipeData(Storm):
518 """The database representation of a BaseRecipeBranch from bzr-builder.
519
520 This is referenced from the SourcePackageRecipe table as the 'recipe_data'
521@@ -216,7 +216,7 @@
522 def __init__(self, recipe, sourcepackage_recipe):
523 """Initialize from the bzr-builder recipe and link it to a db recipe.
524 """
525- super(_SourcePackageRecipeData, self).__init__()
526+ super(SourcePackageRecipeData, self).__init__()
527 self.setRecipe(recipe)
528 self.sourcepackage_recipe = sourcepackage_recipe
529
530
531=== added file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
532--- lib/lp/code/templates/sourcepackagerecipe-index.pt 1970-01-01 00:00:00 +0000
533+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-04-05 18:28:37 +0000
534@@ -0,0 +1,80 @@
535+<html
536+ xmlns="http://www.w3.org/1999/xhtml"
537+ xmlns:tal="http://xml.zope.org/namespaces/tal"
538+ xmlns:metal="http://xml.zope.org/namespaces/metal"
539+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
540+ metal:use-macro="view/macro:page/main_side"
541+ i18n:domain="launchpad"
542+>
543+
544+
545+<body>
546+
547+<tal:registering metal:fill-slot="registering">
548+ Created by
549+ <tal:registrant replace="structure context/registrant/fmt:link" />
550+ on
551+ <tal:created-on replace="structure context/date_created/fmt:date" />
552+ and last modified on
553+ <tal:last-modified replace="structure context/date_last_modified/fmt:date" />
554+</tal:registering>
555+
556+
557+<div metal:fill-slot="main">
558+ <div class="yui-g first">
559+ <div class="portlet">
560+ <h2>Description</h2>
561+ <tal:description replace="context/description" />
562+ </div>
563+ </div>
564+ <div class="yui-g">
565+ <div class="yui-u first">
566+ <div class="portlet">
567+ <h2>Recipe information</h2>
568+ <div class="two-column-list">
569+ <dl id="owner">
570+ <dt>Owner:</dt>
571+ <dd tal:content="structure context/owner/fmt:link" />
572+ </dl>
573+ <dl id="base-branch">
574+ <dt>Base branch:</dt>
575+ <dd tal:content="structure context/base_branch/fmt:link" />
576+ </dl>
577+ <dl id="debian-version">
578+ <dt>Debian version:</dt>
579+ <dd tal:content="context/deb_version_template" />
580+ </dl>
581+ <dl id="distros">
582+ <dt>Distribution series:</dt>
583+ <dd>
584+ <ul>
585+ <li tal:repeat="curseries context/distroseries"
586+ tal:content="structure curseries/fmt:link" />
587+ </ul>
588+ </dd>
589+ </dl>
590+ </div>
591+ </div>
592+ </div>
593+ <div class="yui-u">
594+ <div class="portlet">
595+ <h2>Build records</h2>
596+ <ul>
597+ <li tal:repeat="build view/builds">
598+ <tal:status replace="build/@@+index/status" />
599+ <tal:date replace="build/@@+index/date/fmt:displaydate" />
600+ <tal:estimate condition="build/@@+index/estimate">
601+ (estimated)
602+ </tal:estimate>
603+ </li>
604+ </ul>
605+ </div>
606+ </div>
607+ </div>
608+ <div class='portlet'>
609+ <h2>Recipe contents</h2>
610+ <pre tal:content="context/builder_recipe" />
611+ </div>
612+ </div>
613+</body>
614+</html>
615
616=== modified file 'lib/lp/registry/browser/person.py'
617--- lib/lp/registry/browser/person.py 2010-03-25 14:50:31 +0000
618+++ lib/lp/registry/browser/person.py 2010-04-05 18:28:37 +0000
619@@ -446,6 +446,11 @@
620 return queryMultiAdapter(
621 (self.context, self.request), name ="+archivesubscriptions")
622
623+ @stepthrough('+recipe')
624+ def traverse_recipe(self, name):
625+ """Traverse to this person's recipes."""
626+ return self.context.getRecipe(name)
627+
628
629 class TeamNavigation(PersonNavigation):
630
631
632=== modified file 'lib/lp/registry/interfaces/person.py'
633--- lib/lp/registry/interfaces/person.py 2010-03-25 14:17:56 +0000
634+++ lib/lp/registry/interfaces/person.py 2010-04-05 18:28:37 +0000
635@@ -872,6 +872,9 @@
636 not teams can be converted into teams.
637 """
638
639+ def getRecipe(name):
640+ """Return the person's recipe with the given name."""
641+
642 def getInvitedMemberships():
643 """Return all TeamMemberships of this team with the INVITED status.
644
645
646=== modified file 'lib/lp/registry/model/person.py'
647--- lib/lp/registry/model/person.py 2010-03-30 17:25:52 +0000
648+++ lib/lp/registry/model/person.py 2010-04-05 18:28:37 +0000
649@@ -2258,6 +2258,12 @@
650
651 return rset
652
653+ def getRecipe(self, name):
654+ from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
655+ return Store.of(self).find(
656+ SourcePackageRecipe, SourcePackageRecipe.owner == self,
657+ SourcePackageRecipe.name == name).one()
658+
659 def isUploader(self, distribution):
660 """See `IPerson`."""
661 permissions = getUtility(IArchivePermissionSet).componentsForUploader(
662
663=== modified file 'lib/lp/soyuz/doc/build-failedtoupload-workflow.txt'
664--- lib/lp/soyuz/doc/build-failedtoupload-workflow.txt 2010-02-16 02:27:24 +0000
665+++ lib/lp/soyuz/doc/build-failedtoupload-workflow.txt 2010-04-05 18:28:37 +0000
666@@ -85,7 +85,7 @@
667 * Architecture: i386
668 * Archive: ubuntu primary archive
669 * Component: main
670- * State: Failed to upload
671+ * State: Could not be uploaded correctly
672 * Duration: a minute
673 * Build Log: http://launchpad.dev/ubuntu/+source/cdrkit/1.0/+build/22/+fil=
674 es/netapplet-1.0.0.tar.gz
675
676=== modified file 'lib/lp/soyuz/doc/build-notification.txt'
677--- lib/lp/soyuz/doc/build-notification.txt 2010-03-06 04:57:40 +0000
678+++ lib/lp/soyuz/doc/build-notification.txt 2010-04-05 18:28:37 +0000
679@@ -178,7 +178,7 @@
680 * Architecture: i386
681 * Archive: ubuntu primary archive
682 * Component: main
683- * State: Needs building
684+ * State: Pending build
685 * Duration: not available
686 * Build Log: not available
687 * Builder: not available
688@@ -314,7 +314,7 @@
689 * Architecture: i386
690 * Archive: ubuntu primary archive
691 * Component: main
692- * State: Build for superseded Source
693+ * State: Could not build because source package was superseded
694 * Duration: not available
695 * Build Log: not available
696 * Builder: not available
697
698=== modified file 'lib/lp/soyuz/doc/build.txt'
699--- lib/lp/soyuz/doc/build.txt 2010-03-06 04:57:40 +0000
700+++ lib/lp/soyuz/doc/build.txt 2010-04-05 18:28:37 +0000
701@@ -63,7 +63,7 @@
702 CHROOTWAIT, SUPERSEDED and FAILEDTOUPLOAD).
703
704 >>> firefox_build.buildstate
705- <DBItem BuildStatus.FULLYBUILT, (1) Successfully built>
706+ <DBItem BuildStatus.FULLYBUILT, (1) Successful build>
707
708 Builds which were already processed also offer additional information
709 about its process such as the time it was started and finished and its
710
711=== modified file 'lib/lp/soyuz/doc/buildd-mass-retry.txt'
712--- lib/lp/soyuz/doc/buildd-mass-retry.txt 2009-04-28 12:59:43 +0000
713+++ lib/lp/soyuz/doc/buildd-mass-retry.txt 2010-04-05 18:28:37 +0000
714@@ -40,9 +40,9 @@
715 DEBUG Intitialising connection.
716 INFO Initialising Build Mass-Retry for 'The Hoary Hedgehog Release/RELEASE'
717 INFO Processing builds in 'Failed to build'
718- INFO Processing builds in 'Dependency wait'
719+ INFO Processing builds in 'Could not build because of missing dependencies'
720 INFO Retrying i386 build of libstdc++ b8p in ubuntu hoary RELEASE (12)
721- INFO Processing builds in 'Chroot problem'
722+ INFO Processing builds in 'Could not build because of chroot problem'
723 INFO Success.
724 INFO Dry-run.
725 <BLANKLINE>
726@@ -62,8 +62,8 @@
727 DEBUG Intitialising connection.
728 INFO Initialising Build Mass-Retry for 'The Hoary Hedgehog Release for hppa (hppa)/RELEASE'
729 INFO Processing builds in 'Failed to build'
730- INFO Processing builds in 'Dependency wait'
731- INFO Processing builds in 'Chroot problem'
732+ INFO Processing builds in 'Could not build because of missing dependencies'
733+ INFO Processing builds in 'Could not build because of chroot problem'
734 INFO Success.
735 INFO Dry-run.
736 <BLANKLINE>
737
738=== modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt'
739--- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-24 04:51:25 +0000
740+++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-05 18:28:37 +0000
741@@ -298,7 +298,7 @@
742 >>> build.dependencies
743 u'baz (>= 1.0.1)'
744 >>> build.buildstate.title
745- 'Dependency wait'
746+ 'Could not build because of missing dependencies'
747
748 WAITING - CHROOTFAIL -> the Chroot for this distroseries is damage, nor
749 builder, but right state stored in Build entry:
750@@ -325,7 +325,7 @@
751 >>> check_mail_sent(last_stub_mail_count)
752 True
753 >>> build.buildstate.title
754- 'Chroot problem'
755+ 'Could not build because of chroot problem'
756
757 WAITING - BUILDERFAIL -> builder has failed by internal error, job is available for next build round:
758
759@@ -352,7 +352,7 @@
760 False
761 >>> build = getUtility(IBuildSet).getByQueueEntry(bqItem6)
762 >>> print build.buildstate.title
763- Needs building
764+ Pending build
765 >>> job = bqItem6.specific_job.job
766 >>> print job.status.title
767 Waiting
768@@ -473,7 +473,7 @@
769 >>> check_mail_sent(last_stub_mail_count)
770 True
771 >>> build.buildstate.title
772- 'Failed to upload'
773+ 'Could not be uploaded correctly'
774
775 Let's check the emails generated by this 'failure'
776 (see build-failedtoupload-workflow.txt for more information):
777@@ -564,7 +564,7 @@
778 >>> build.buildlog is not None
779 True
780 >>> build.buildstate.title
781- 'Successfully built'
782+ 'Successful build'
783 >>> check_mail_sent(last_stub_mail_count)
784 False
785
786@@ -602,7 +602,7 @@
787 False
788 >>> build = getUtility(IBuildSet).getByQueueEntry(bqItem11)
789 >>> print build.buildstate.title
790- Needs building
791+ Pending build
792 >>> job = bqItem11.specific_job.job
793 >>> print job.status.title
794 Waiting
795
796=== modified file 'lib/lp/soyuz/doc/gina.txt'
797--- lib/lp/soyuz/doc/gina.txt 2010-03-17 05:48:45 +0000
798+++ lib/lp/soyuz/doc/gina.txt 2010-04-05 18:28:37 +0000
799@@ -366,7 +366,7 @@
800 >>> print ed.build.processor.name
801 386
802 >>> print ed.build.buildstate
803- Successfully built
804+ Successful build
805 >>> print ed.build.distroarchseries.processorfamily.name
806 x86
807 >>> print ed.build.distroarchseries.architecturetag
808
809=== modified file 'lib/lp/soyuz/stories/ppa/xx-copy-packages.txt'
810--- lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2010-03-10 13:28:55 +0000
811+++ lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2010-04-05 18:28:37 +0000
812@@ -299,7 +299,7 @@
813 James Blackwell
814 PPA for James Blackwell
815 i386 build of pmount 0.1-1
816- Build status Needs building
817+ Build status Pending build
818 Start (2505) What's this?
819 Build details
820 Source: pmount - 0.1-1
821
822=== modified file 'lib/lp/soyuz/stories/soyuz/xx-build-record.txt'
823--- lib/lp/soyuz/stories/soyuz/xx-build-record.txt 2010-03-10 13:28:55 +0000
824+++ lib/lp/soyuz/stories/soyuz/xx-build-record.txt 2010-04-05 18:28:37 +0000
825@@ -62,7 +62,7 @@
826 i386 build of testing 1.0 in ubuntutest breezy-autotest RELEASE
827 ...
828 Build status
829- Needs building
830+ Pending build
831 Start in ... (2505) What's this?
832 Build details
833 Source: testing - 1.0
834@@ -82,7 +82,7 @@
835 i386 build of testing 1.0 in ubuntutest breezy-autotest RELEASE
836 ...
837 Build status
838- Needs building
839+ Pending build
840 Build details
841 Source: testing - 1.0
842 Archive: Primary Archive for Ubuntu Test
843@@ -134,7 +134,7 @@
844
845 >>> print extract_text(find_tag_by_id(admin_browser.contents, 'status'))
846 Build status
847- Needs building
848+ Pending build
849 Start in ... (0) Rescore build What's this?
850
851 Eventually a pending build record will get started, and while it's
852@@ -203,7 +203,7 @@
853 >>> print extract_text(
854 ... find_tag_by_id(anon_browser.contents, 'status'))
855 Build status
856- Failed to upload on Bob The Builder
857+ Could not be uploaded correctly on Bob The Builder
858 Started ... ago
859 Finished ... (took 1 minute, 0.0 seconds)
860 buildlog (7 bytes)
861@@ -229,7 +229,8 @@
862 >>> print extract_text(
863 ... find_tag_by_id(admin_browser.contents, 'status'))
864 Build status
865- Failed to upload on Bob The Builder Retry this build
866+ Could not be uploaded correctly
867+ on Bob The Builder Retry this build
868 Started ... ago
869 Finished ... (took 1 minute, 0.0 seconds)
870 buildlog (7 bytes)
871@@ -246,7 +247,7 @@
872 Retry i386 build of testing 1.0 in ubuntutest breezy-autotest RELEASE
873 ...
874 The status of i386 build of testing 1.0 in ubuntutest
875- breezy-autotest RELEASE is Failed to upload.
876+ breezy-autotest RELEASE is Could not be uploaded correctly.
877 Retrying this build will destroy its history and logs.
878 By default, this build will be retried only after other pending
879 builds; please contact a build daemon administrator if you need
880@@ -260,7 +261,7 @@
881 >>> print extract_text(
882 ... find_tag_by_id(admin_browser.contents, 'status'))
883 Build status
884- Failed to upload on Bob The Builder Retry this build
885+ Could not be uploaded correctly on Bob The Builder Retry this build
886 Started ... ago
887 Finished ... (took 1 minute, 0.0 seconds)
888 buildlog (7 bytes)
889@@ -278,7 +279,7 @@
890
891 >>> print extract_text(find_tag_by_id(admin_browser.contents, 'status'))
892 Build status
893- Needs building
894+ Pending build
895 Start in ... (0) Rescore build What's this?
896
897 >>> admin_browser.getLink("Retry this build").click()
898@@ -315,7 +316,7 @@
899 >>> print extract_text(
900 ... find_tag_by_id(anon_browser.contents, 'status'))
901 Build status
902- Successfully built on Bob The Builder
903+ Successful build on Bob The Builder
904 Started on 2008-01-01
905 Finished on 2008-01-01 (took 5 minutes, 0.0 seconds)
906 buildlog (6 bytes)
907@@ -452,7 +453,7 @@
908
909 >>> print extract_text(find_tag_by_id(anon_browser.contents, 'status'))
910 Build status
911- Successfully built on Bob The Builder
912+ Successful build on Bob The Builder
913 Started on ...
914 Finished on ... (took 5 minutes, 0.0 seconds)
915 buildlog (6 bytes)
916
917=== modified file 'lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt'
918--- lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt 2010-03-08 09:19:45 +0000
919+++ lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt 2010-04-05 18:28:37 +0000
920@@ -136,17 +136,17 @@
921
922 >>> print_build_rows(anon_browser.contents)
923 ------------------------------
924- Successfully built
925+ Successful build
926 hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
927 Build started on 2007-08-10 on Bob The Builder and finished on 2007-08-10
928 taking 15 seconds &mdash; see the log
929 ------------------------------
930- Successfully built
931+ Successful build
932 i386 build of commercialpackage 1.0-1 in ubuntu breezy-autotest RELEASE
933 Build started at an unknown time on an unknown build machine and finished
934 on 2007-08-09 taking
935 ------------------------------
936- Failed to upload
937+ Could not be uploaded correctly
938 i386 build of cdrkit 1.0 in ubuntu breezy-autotest RELEASE
939 Build started on 2007-04-19 on Bob The Builder and finished on 2007-04-20
940 taking a minute &mdash; see the log
941@@ -164,23 +164,23 @@
942 >>> anon_browser.getLink("Next").click()
943 >>> print_build_rows(anon_browser.contents)
944 ------------------------------
945- Build for superseded Source
946+ Could not build because source package was superseded
947 i386 build of at 0.00 in ubuntu warty RELEASE
948 ------------------------------
949- Build for superseded Source
950+ Could not build because source package was superseded
951 i386 build of linux-source-2.6.15 2.6.15.3 in ubuntu warty RELEASE
952 ------------------------------
953- Build for superseded Source
954+ Could not build because source package was superseded
955 i386 build of netapplet 0.99.6-1 in ubuntu warty RELEASE
956 ------------------------------
957- Dependency wait
958+ Could not build because of missing dependencies
959 i386 build of libstdc++ b8p in ubuntu hoary RELEASE
960 Missing dependencies:
961 cpp (&gt;= 4:4.0.1-3), gcc-4.0 (&gt;= 4.0.1-2)
962 Build started on 2006-02-27 on Bob The Builder and finished on 2006-02-28
963 taking six minutes &mdash; see the log
964 ------------------------------
965- Needs building
966+ Pending build
967 i386 build of alsa-utils 1.0.9a-4ubuntu1 in ubuntu hoary RELEASE
968 Pending (10)
969 ------------------------------
970@@ -193,27 +193,27 @@
971 >>> anon_browser.getControl("Filter").click()
972 >>> print_build_rows(anon_browser.contents)
973 ------------------------------
974- Successfully built
975+ Successful build
976 hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
977 Build started on 2007-08-10 on Bob The Builder and finished on 2007-08-10
978 taking 15 seconds &mdash; see the log
979 ------------------------------
980- Successfully built
981+ Successful build
982 i386 build of commercialpackage 1.0-1 in ubuntu breezy-autotest RELEASE
983 Build started at an unknown time on an unknown build machine and finished
984 on 2007-08-09 taking
985 ------------------------------
986- Successfully built
987+ Successful build
988 i386 build of pmount 0.1-1 in ubuntu hoary RELEASE
989 Build started on 2005-03-24 on Bob The Builder and finished on 2005-03-25
990 taking a minute &mdash; see the log
991 ------------------------------
992- Successfully built
993+ Successful build
994 hppa build of pmount 0.1-1 in ubuntu hoary RELEASE
995 Build started on 2005-03-24 on Bob The Builder and finished on 2005-03-25
996 taking a minute &mdash; see the log
997 ------------------------------
998- Successfully built
999+ Successful build
1000 i386 build of pmount 0.1-1 in ubuntu breezy-autotest RELEASE
1001 Build started on 2005-03-24 on Bob The Builder and finished on 2005-03-25
1002 taking a minute &mdash; see the log
1003@@ -223,7 +223,7 @@
1004 >>> anon_browser.getControl("Filter").click()
1005 >>> print_build_rows(anon_browser.contents)
1006 ------------------------------
1007- Dependency wait
1008+ Could not build because of missing dependencies
1009 i386 build of libstdc++ b8p in ubuntu hoary RELEASE
1010 Missing dependencies:
1011 cpp (&gt;= 4:4.0.1-3), gcc-4.0 (&gt;= 4.0.1-2)
1012@@ -301,17 +301,17 @@
1013
1014 >>> print_build_rows(anon_browser.contents)
1015 ------------------------------
1016- Successfully built
1017- hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
1018- Build started on 2007-08-10 and finished on 2007-08-10
1019- taking 15 seconds &mdash; see the log
1020- ------------------------------
1021- Successfully built
1022- hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
1023- Build started on 2007-08-10 and finished on 2007-08-10
1024- taking 15 seconds &mdash; see the log
1025- ------------------------------
1026- Successfully built
1027+ Successful build
1028+ hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
1029+ Build started on 2007-08-10 and finished on 2007-08-10
1030+ taking 15 seconds &mdash; see the log
1031+ ------------------------------
1032+ Successful build
1033+ hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
1034+ Build started on 2007-08-10 and finished on 2007-08-10
1035+ taking 15 seconds &mdash; see the log
1036+ ------------------------------
1037+ Successful build
1038 i386 build of pmount 0.1-1 in ubuntu warty RELEASE
1039 Build started on 2007-07-23 and finished on 2007-07-24
1040 taking a minute &mdash; see the log
1041@@ -334,7 +334,7 @@
1042 >>> anon_browser.getLink("Next").click()
1043 >>> print_build_rows(anon_browser.contents)
1044 ------------------------------
1045- Failed to upload
1046+ Could not be uploaded correctly
1047 i386 build of cdrkit 1.0 in ubuntu breezy-autotest RELEASE
1048 Build started on 2007-04-19 and finished on 2007-04-20
1049 taking a minute &mdash; see the log
1050@@ -344,13 +344,13 @@
1051 Build started on 2006-12-01 and finished on 2006-12-01
1052 taking 1 second &mdash; see the log
1053 ------------------------------
1054- Build for superseded Source
1055+ Could not build because source package was superseded
1056 i386 build of netapplet 0.99.6-1 in ubuntu warty RELEASE
1057 ------------------------------
1058- Build for superseded Source
1059+ Could not build because source package was superseded
1060 i386 build of linux-source-2.6.15 2.6.15.3 in ubuntu warty RELEASE
1061 ------------------------------
1062- Build for superseded Source
1063+ Could not build because source package was superseded
1064 i386 build of at 0.00 in ubuntu warty RELEASE
1065 ------------------------------
1066
1067@@ -360,7 +360,7 @@
1068 >>> anon_browser.getLink("Next").click()
1069 >>> print_build_rows(anon_browser.contents)
1070 ------------------------------
1071- Dependency wait
1072+ Could not build because of missing dependencies
1073 i386 build of libstdc++ b8p in ubuntu hoary RELEASE
1074 Missing dependencies:
1075 cpp (&gt;= 4:4.0.1-3), gcc-4.0 (&gt;= 4.0.1-2)
1076
1077=== modified file 'lib/lp/soyuz/stories/soyuz/xx-private-builds.txt'
1078--- lib/lp/soyuz/stories/soyuz/xx-private-builds.txt 2010-03-08 13:07:07 +0000
1079+++ lib/lp/soyuz/stories/soyuz/xx-private-builds.txt 2010-04-05 18:28:37 +0000
1080@@ -338,7 +338,7 @@
1081 >>> portlet = find_portlet(browser.contents, 'Builds')
1082 >>> print extract_text(portlet)
1083 Builds
1084- breezy-autotest i386 Successfully built
1085+ breezy-autotest i386 Successful build
1086
1087 >>> print browser.getLink('i386').url
1088 http://launchpad.dev/~cprov/+archive/p3a/+build/...
1089
1090=== modified file 'lib/lp/testing/factory.py'
1091--- lib/lp/testing/factory.py 2010-04-05 17:40:35 +0000
1092+++ lib/lp/testing/factory.py 2010-04-05 18:28:37 +0000
1093@@ -1622,12 +1622,14 @@
1094
1095 def makeDistroRelease(self, distribution=None, version=None,
1096 status=SeriesStatus.DEVELOPMENT,
1097- parent_series=None, name=None):
1098+ parent_series=None, name=None, displayname=None):
1099 """Make a new distro release."""
1100 if distribution is None:
1101 distribution = self.makeDistribution()
1102 if name is None:
1103 name = self.getUniqueString()
1104+ if displayname is None:
1105+ displayname = name.capitalize()
1106 if version is None:
1107 version = "%s.0" % self.getUniqueInteger()
1108
1109@@ -1637,7 +1639,7 @@
1110 series = naked_distribution.newSeries(
1111 version=version,
1112 name=name,
1113- displayname=name.capitalize(),
1114+ displayname=displayname,
1115 title=self.getUniqueString(), summary=self.getUniqueString(),
1116 description=self.getUniqueString(),
1117 parent_series=parent_series, owner=distribution.owner)
1118@@ -1716,7 +1718,7 @@
1119
1120 def makeBuilder(self, processor=None, url=None, name=None, title=None,
1121 description=None, owner=None, active=True,
1122- virtualized=True, vm_host=None):
1123+ virtualized=True, vm_host=None, manual=False):
1124 """Make a new builder for i386 virtualized builds by default.
1125
1126 Note: the builder returned will not be able to actually build -
1127@@ -1740,7 +1742,7 @@
1128
1129 return getUtility(IBuilderSet).new(
1130 processor, url, name, title, description, owner, active,
1131- virtualized, vm_host)
1132+ virtualized, vm_host, manual=manual)
1133
1134 def makeRecipe(self, *branches):
1135 """Make a builder recipe that references `branches`.
1136@@ -1800,11 +1802,12 @@
1137
1138 def makeSourcePackageRecipeBuildJob(
1139 self, score=9876, virtualized=True, estimated_duration=64,
1140- sourcename=None):
1141+ sourcename=None, recipe_build=None):
1142 """Create a `SourcePackageRecipeBuildJob` and a `BuildQueue` for
1143 testing."""
1144- recipe_build = self.makeSourcePackageRecipeBuild(
1145- sourcename=sourcename)
1146+ if recipe_build is None:
1147+ recipe_build = self.makeSourcePackageRecipeBuild(
1148+ sourcename=sourcename)
1149 recipe_build_job = recipe_build.makeJob()
1150
1151 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)