launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02601
[Merge] lp:~thumper/launchpad/recipe-inline-edit-recipe-text into lp:launchpad
Tim Penhey has proposed merging lp:~thumper/launchpad/recipe-inline-edit-recipe-text into lp:launchpad with lp:~thumper/launchpad/daily-ajax as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#673530 Inline editing for recipes
https://bugs.launchpad.net/bugs/673530
For more details, see:
https://code.launchpad.net/~thumper/launchpad/recipe-inline-edit-recipe-text/+merge/49585
This branch adds the ability to edit the recipe text on the
main recipe page.
As part of this I've tweaked the styles so we don't get the
horrible overlap at the top of the multiline editors. I
believe this was missed during the yui 3.1 -> 3.2 transition
as the yui- prefix became yui3-.
lib/lp/code/errors.py
- annotated the RecipeParseError to be a 400 Bad Request error.
- also for the NoSuchBranch and PrivateBranchRecipe errors as
they can also be raised when setting recipe text
- pulled the webservice_error up to the base class instead of
having it in each of the three derived classes.
lib/lp/code/interfaces/sourcepackagerecipe.py
- change the setRecipeText from being exported as a writable
operatioln to be a mutator for the recipe_text attribute.
I talked with Leonard about the stack of adapters here.
The operate from bottom to top.
lib/lp/code/model/sourcepackagerecipe.py
- in order to get the full error returned to the client, we
need to call the "expose" method from lazr.restful. I
chatted with Leonard about this one too as it seemed a
little strange. There was some reason at some stage, but
he couldn't recall the underlying reason at the time I
was asking. We may perhaps want to re-evaluate the need
for this method at some time.
--
https://code.launchpad.net/~thumper/launchpad/recipe-inline-edit-recipe-text/+merge/49585
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~thumper/launchpad/recipe-inline-edit-recipe-text into lp:launchpad.
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
--- lib/canonical/launchpad/icing/style-3-0.css.in 2011-02-15 01:41:21 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css.in 2011-02-15 01:41:23 +0000
@@ -947,18 +947,18 @@
top: -3px;
margin-left: 0.5em;
}
-.yui-ieditor {
+.yui3-ieditor {
padding-right: 288px;
}
-.lazr-multiline-edit .yui-ieditor {
+h1 .yui3-ieditor-errors {
+ font-size: 0.5em;
+ }
+.lazr-multiline-edit .yui3-ieditor {
padding-right: 0;
}
.lazr-multiline-edit textarea {
max-width: inherit;
}
-.lazr-multiline-edit .yui-ieditor-input {
- padding-right: 5px !important;
- }
.widget-hd.js-action {
/* The js-action class is also used for non-links, for example, with
expand/collapse sections. */
@@ -1825,8 +1825,8 @@
font-size: 93%;
margin: 1em 0;
}
-div#edit-description .yui-ieditor-input,
-div#edit-commit_message .yui-ieditor-input {
+
+.yui3-ieditor-multiline .yui3-ieditor-input {
top: 0;
}
=== modified file 'lib/lp/bugs/interfaces/bugsupervisor.py'
--- lib/lp/bugs/interfaces/bugsupervisor.py 2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/interfaces/bugsupervisor.py 2011-02-15 01:41:23 +0000
@@ -36,9 +36,6 @@
"all bugs and will receive email about all activity on all bugs "
"for this project, so that should be a factor in your decision. "
"The bug supervisor will also have access to all private bugs."),
-
-
-
required=False, vocabulary='ValidPersonOrTeam', readonly=True))
@mutator_for(bug_supervisor)
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2011-02-15 01:41:21 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2011-02-15 01:41:23 +0000
@@ -72,6 +72,7 @@
from lp.app.browser.lazrjs import (
BooleanChoiceWidget,
InlineEditPickerWidget,
+ TextAreaEditorWidget,
)
from lp.app.browser.tales import format_link
from lp.app.widgets.itemswidgets import (
@@ -264,6 +265,12 @@
step_title='Select a PPA')
@property
+ def recipe_text_widget(self):
+ """The recipe text as widget HTML."""
+ recipe_text = ISourcePackageRecipe['recipe_text']
+ return TextAreaEditorWidget(self.context, recipe_text, title="")
+
+ @property
def daily_build_widget(self):
return BooleanChoiceWidget(
self.context, ISourcePackageRecipe['build_daily'],
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2011-01-21 21:12:58 +0000
+++ lib/lp/code/errors.py 2011-02-15 01:41:23 +0000
@@ -42,10 +42,17 @@
import httplib
-from lazr.restful.declarations import webservice_error
+from bzrlib.plugins.builder.recipe import RecipeParseError
+from lazr.restful.declarations import (
+ error_status,
+ webservice_error,
+ )
from lp.app.errors import NameLookupFailed
+# Annotate the RecipeParseError's with a 400 webservice status.
+error_status(400)(RecipeParseError)
+
class BadBranchMergeProposalSearchContext(Exception):
"""The context is not valid for a branch merge proposal search."""
@@ -204,11 +211,15 @@
class NoSuchBranch(NameLookupFailed):
"""Raised when we try to load a branch that does not exist."""
+ webservice_error(400)
+
_message_prefix = "No such branch"
class PrivateBranchRecipe(Exception):
+ webservice_error(400)
+
def __init__(self, branch):
message = (
'Recipe may not refer to private branch: %s' %
@@ -265,6 +276,8 @@
class TooNewRecipeFormat(Exception):
"""The format of the recipe supplied was too new."""
+ webservice_error(400)
+
def __init__(self, supplied_format, newest_supported):
super(TooNewRecipeFormat, self).__init__()
self.supplied_format = supplied_format
@@ -273,6 +286,8 @@
class RecipeBuildException(Exception):
+ webservice_error(400)
+
def __init__(self, recipe, distroseries, template):
self.recipe = recipe
self.distroseries = distroseries
@@ -283,8 +298,6 @@
class TooManyBuilds(RecipeBuildException):
"""A build was requested that exceeded the quota."""
- webservice_error(400)
-
def __init__(self, recipe, distroseries):
RecipeBuildException.__init__(
self, recipe, distroseries,
@@ -295,8 +308,6 @@
class BuildAlreadyPending(RecipeBuildException):
"""A build was requested when an identical build was already pending."""
- webservice_error(400)
-
def __init__(self, recipe, distroseries):
RecipeBuildException.__init__(
self, recipe, distroseries,
@@ -306,8 +317,6 @@
class BuildNotAllowedForDistro(RecipeBuildException):
"""A build was requested against an unsupported distroseries."""
- webservice_error(400)
-
def __init__(self, recipe, distroseries):
RecipeBuildException.__init__(
self, recipe, distroseries,
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-15 01:41:21 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-15 01:41:23 +0000
@@ -25,13 +25,17 @@
export_as_webservice_entry,
export_write_operation,
exported,
+ mutator_for,
+ operation_for_version,
operation_parameters,
+ operation_removed_in_version,
REQUEST_USER,
)
from lazr.restful.fields import (
CollectionField,
Reference,
)
+from lazr.restful.interface import copy_field
from zope.interface import (
Attribute,
Interface,
@@ -97,7 +101,7 @@
required=True, readonly=True,
vocabulary='ValidPersonOrTeam'))
- recipe_text = exported(Text())
+ recipe_text = exported(Text(readonly=True))
def isOverQuota(requester, distroseries):
"""True if the recipe/requester/distroseries combo is >= quota.
@@ -135,6 +139,13 @@
class ISourcePackageRecipeEdit(Interface):
"""ISourcePackageRecipe methods that require launchpad.Edit permission."""
+ @mutator_for(ISourcePackageRecipeView['recipe_text'])
+ @operation_parameters(
+ recipe_text=copy_field(
+ ISourcePackageRecipeView['recipe_text']))
+ @export_write_operation()
+ @operation_for_version("devel")
+ @operation_removed_in_version("devel")
@operation_parameters(recipe_text=Text())
@export_write_operation()
def setRecipeText(recipe_text):
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2011-02-03 03:39:14 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2011-02-15 01:41:23 +0000
@@ -15,7 +15,10 @@
datetime,
timedelta,
)
+
+from bzrlib.plugins.builder.recipe import RecipeParseError
from lazr.delegates import delegates
+from lazr.restful.error import expose
from pytz import utc
from storm.expr import (
And,
@@ -50,7 +53,10 @@
from lp.code.errors import (
BuildAlreadyPending,
BuildNotAllowedForDistro,
+ NoSuchBranch,
+ PrivateBranchRecipe,
TooManyBuilds,
+ TooNewRecipeFormat,
)
from lp.code.interfaces.sourcepackagerecipe import (
ISourcePackageRecipe,
@@ -159,8 +165,13 @@
return self._recipe_data.base_branch
def setRecipeText(self, recipe_text):
- parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
- self._recipe_data.setRecipe(parsed)
+ try:
+ parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
+ self._recipe_data.setRecipe(parsed)
+ except (RecipeParseError, NoSuchBranch, PrivateBranchRecipe,
+ TooNewRecipeFormat) as e:
+ expose(e)
+ raise
@property
def recipe_text(self):
=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
--- lib/lp/code/templates/sourcepackagerecipe-index.pt 2011-02-15 01:41:21 +0000
+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2011-02-15 01:41:23 +0000
@@ -12,6 +12,10 @@
.binary-build .indent {
padding-left: 2em;
}
+ #edit-recipe_text {
+ font-family: "UbuntuBeta Mono","Ubuntu Mono",monospace;
+ margin-top: -15px;
+ }
</style>
</metal:block>
@@ -164,7 +168,7 @@
</div>
<div class='portlet'>
<h2>Recipe contents</h2>
- <pre tal:content="context/recipe_text" />
+ <tal:widget replace="structure view/recipe_text_widget"/>
</div>
</div>
</div>
=== modified file 'lib/lp/code/windmill/tests/test_recipe_index.py'
--- lib/lp/code/windmill/tests/test_recipe_index.py 2011-02-15 01:41:21 +0000
+++ lib/lp/code/windmill/tests/test_recipe_index.py 2011-02-15 01:41:23 +0000
@@ -44,3 +44,29 @@
freshly_fetched_recipe = Store.of(recipe).find(
SourcePackageRecipe, SourcePackageRecipe.id == recipe.id).one()
self.assertTrue(freshly_fetched_recipe.build_daily)
+
+ def test_inline_recipe_text_errors(self):
+ eric = self.factory.makePerson(
+ name="eric", displayname="Eric the Viking", password="test",
+ email="eric@xxxxxxxxxxx")
+ recipe = self.factory.makeSourcePackageRecipe(owner=eric)
+ recipe_text = recipe.recipe_text + 'merge WTF?'
+ transaction.commit()
+
+ client, start_url = self.getClientFor(recipe, user=eric)
+ client.click(
+ jquery=u'("div#edit-recipe_text a.yui3-editable_text-trigger")[0]')
+ client.waits.forElement(
+ jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-input")',
+ timeout=FOR_ELEMENT)
+ client.type(
+ text=recipe_text,
+ jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-input")[0]')
+ client.click(
+ jquery=u'("div#edit-recipe_text button.yui3-ieditor-submit_button")[0]')
+ client.waits.forElement(
+ jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-errors")',
+ timeout=FOR_ELEMENT)
+ client.asserts.assertTextIn(
+ jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-errors")[0]',
+ validator=u'End of line while looking for the branch url.')