Merge lp:~thumper/launchpad/recipe-feature-flag into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12410
Proposed branch: lp:~thumper/launchpad/recipe-feature-flag
Merge into: lp:launchpad
Diff against target: 321 lines (+44/-54)
12 files modified
lib/lp/code/browser/branch.py (+2/-5)
lib/lp/code/browser/sourcepackagerecipe.py (+6/-6)
lib/lp/code/browser/sourcepackagerecipelisting.py (+3/-2)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+13/-1)
lib/lp/code/interfaces/sourcepackagerecipe.py (+5/-12)
lib/lp/code/model/sourcepackagerecipe.py (+0/-3)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+0/-20)
lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt (+6/-1)
lib/lp/code/templates/branch-index.pt (+1/-1)
lib/lp/code/templates/branch-recipes.pt (+2/-1)
lib/lp/code/templates/branchmergequeue-listing.pt (+2/-2)
lib/lp/services/features/flags.py (+4/-0)
To merge this branch: bzr merge lp:~thumper/launchpad/recipe-feature-flag
Reviewer Review Type Date Requested Status
Ian Booth (community) code* Approve
Robert Collins (community) Approve
Martin Pool (community) Needs Fixing
Review via email: mp+50092@code.launchpad.net

Commit message

[r=lifeless,wallyworld][bug=720503] Use feature flags for recipe enablement and beta message showing

Description of the change

Use feature flags exclusively for controlling recipes.

Generally replaced the recipes_enabled method with a direct check
of the feature flag.

There is no point checking now in requestBuild.

lib/lp/code/browser/branch.py
 - the link is only shown on the template if the feature flag is set
   so no need to check it twice

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

Just one thing about this in the context of wgrant's work: the way you're using this with just 'if getFeatureFlag()' is just going to go off Python's cast of the value to boolean, ie any non-empty string counts as true. <https://bugs.launchpad.net/launchpad/+bug/719182> So I wouldn't describe them as true/false for now.

While you're changing it, would you like to the existing flag to code.recipes.enabled for consistency? You would have to ask the losas to duplicate the existing rules.

review: Needs Fixing
Revision history for this message
Ian Booth (wallyworld) wrote :

Nice cleanup work.

I am wondering if:

if not getFeatureFlag(RECIPE_ENABLED_FLAG):

is correct? Doesn't getFeatureFlag(RECIPE_ENABLED_FLAG) return "off" or "on" ie a string?

Also, why "code.recipes_enabled" and not "code.recipes.enabled" ?

review: Needs Information
Revision history for this message
Ian Booth (wallyworld) wrote :

> Nice cleanup work.
>
> I am wondering if:
>
> if not getFeatureFlag(RECIPE_ENABLED_FLAG):
>
> is correct? Doesn't getFeatureFlag(RECIPE_ENABLED_FLAG) return "off" or "on"
> ie a string?
>
> Also, why "code.recipes_enabled" and not "code.recipes.enabled" ?

Oh sorry, I had this open in my browser for a while and didn't see before I submitted that poolie had made similar comments.

Revision history for this message
Tim Penhey (thumper) wrote :

On Thu, 17 Feb 2011 17:29:53 you wrote:
> Review: Needs Fixing
> Just one thing about this in the context of wgrant's work: the way you're
> using this with just 'if getFeatureFlag()' is just going to go off
> Python's cast of the value to boolean, ie any non-empty string counts as
> true. <https://bugs.launchpad.net/launchpad/+bug/719182> So I wouldn't
> describe them as true/false for now.

having true/false is the same as on/off

They will operate exactly the same way. I had the following discussion with
lifeless about that bug (didn't realise the actual bug at the time).

<thumper> lifeless: what do you think about converting feature flag values of
"off" and "false" to False ?
<lifeless> thumper: i'm pretty unkeen
<lifeless> thumper: what issue are you trying to solve?
<thumper> lifeless: I'm adding a feature flag "code.recipe.beta" to control the
message and the beta icon
<thumper> lifeless: so we'll add a rule that says code.recipe.beta -> "on"
<thumper> lifeless: but if we set a rule to say code.recipe.beta -> 'off'
<lifeless> thumper: theres a rule for this already isn't there ?
<thumper> lifeless: I want it to work
<thumper> no
<thumper> we have code.recipe_enabled
<thumper> which is different
<thumper> I'd like to rename that to "code.recipe.enabled"
<thumper> enabled controls whether we barf on recipe actions
<lifeless> thumper: so this is to let you go to non-beta without tying it to a
deploy ?
<thumper> beta is just to show the messages
<thumper> yes
<thumper> exactly
<lifeless> and then you'll delete both rules entirely.
<thumper> aye
<wallyworld> thumper: can you tell me where our existing web service api tests
are? i've had a boy look but can't see them
<lifeless> I would just do the current thing - 'if <thing>: ... '
<thumper> yeah, I know I _can)
<lifeless> thumper: the default is None/'', and you can add rules of ''
<thumper> I just think it would make it more clear
<lifeless> I have think it makes it more awkward
<thumper> getFeatureFlag('some.feature') should be False if some.feature ==
"off" or "false"
<lifeless> Not at that layer
<lifeless> and I really don't see the argument for a higher layer at the
moment
<lifeless> it seems like cruft
<thumper> find
<thumper> fine
<thumper> we can agree to disagree and I'll not change it
<lifeless> I agree that there is some confusion

> While you're changing it, would you like to the existing flag to
> code.recipes.enabled for consistency? You would have to ask the losas to
> duplicate the existing rules.

I did seriously consider this. But we are likely to be removing both rules
within two weeks, so I didn't bother.

Revision history for this message
Tim Penhey (thumper) wrote :

On Thu, 17 Feb 2011 17:36:07 Ian Booth wrote:
> Review: Needs Information
> Nice cleanup work.
>
> I am wondering if:
>
> if not getFeatureFlag(RECIPE_ENABLED_FLAG):
>
> is correct? Doesn't getFeatureFlag(RECIPE_ENABLED_FLAG) return "off" or
> "on" ie a string?
>
> Also, why "code.recipes_enabled" and not "code.recipes.enabled" ?

Because "code.recipes_enabled" is what it is now, and has established rules.

I just decided to limit the changes (even though the old one doesn't follow
the suggested naming guidelines).

Revision history for this message
Ian Booth (wallyworld) wrote :

Sorry, I'm confused. If the value of the feature flag is "off", then:

if not getFeatureFlag(RECIPE_ENABLED_FLAG):
    do something

will not work as expected since both "off" and "on" evaluate to True in a conditional expression.
So shouldn't the above code be:

if getFeatureFlag(RECIPE_ENABLED_FLAG) == "off":
    do something

How does the code as it exists in the current mp correct?

review: Needs Information
Revision history for this message
Tim Penhey (thumper) wrote :

On Thu, 17 Feb 2011 18:13:52 Ian Booth wrote:
> Review: Needs Information
> Sorry, I'm confused. If the value of the feature flag is "off", then:
>
> if not getFeatureFlag(RECIPE_ENABLED_FLAG):
> do something
>
> will not work as expected since both "off" and "on" evaluate to True in a
> conditional expression. So shouldn't the above code be:

It will work as expected... as the only value the feature flag will have is
"on".

Then we delete all the checks, then delete the flag.

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

As Martin notes, the doc for the new flag is inconsistent with wgrants cleanup, but this flag will have a very short lifespan so it really doesn't bother me.

review: Approve
Revision history for this message
Ian Booth (wallyworld) wrote :

> On Thu, 17 Feb 2011 18:13:52 Ian Booth wrote:
> > Review: Needs Information
> > Sorry, I'm confused. If the value of the feature flag is "off", then:
> >
> > if not getFeatureFlag(RECIPE_ENABLED_FLAG):
> > do something
> >
> > will not work as expected since both "off" and "on" evaluate to True in a
> > conditional expression. So shouldn't the above code be:
>
> It will work as expected... as the only value the feature flag will have is
> "on".
>
> Then we delete all the checks, then delete the flag.

Ok. I was going by the +feature-info doc which says the default value of the flag is "off". I'll give in to popular opinion but still am not 100% comfortable with the bigger picture implementation issue since it is not obvious or intuitive what the expected behaviour is and even though *this* flag will have a short life, the problem remains for other flags.

review: Approve (code*)
Revision history for this message
Martin Pool (mbp) wrote :

On 17 February 2011 18:56, Ian Booth <email address hidden> wrote:
> Ok. I was going by the +feature-info doc which says the default value of the flag is "off". I'll give in to popular opinion but still am not 100% comfortable with the bigger picture implementation issue since it is not obvious or intuitive what the expected behaviour is and even though *this* flag will have a short life, the problem remains for other flags.

You're quite right, I'm just suggesting we tackle that as
<https://bugs.launchpad.net/launchpad/+bug/719182>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branch.py'
2--- lib/lp/code/browser/branch.py 2011-02-02 15:43:31 +0000
3+++ lib/lp/code/browser/branch.py 2011-02-18 08:58:49 +0000
4@@ -143,7 +143,6 @@
5 from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
6 from lp.code.interfaces.branchtarget import IBranchTarget
7 from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
8-from lp.code.interfaces.sourcepackagerecipe import recipes_enabled
9 from lp.registry.interfaces.person import (
10 IPerson,
11 IPersonSet,
12@@ -385,10 +384,8 @@
13 '+upgrade', 'Upgrade this branch', icon='edit', enabled=enabled)
14
15 def create_recipe(self):
16- if not self.context.private and recipes_enabled():
17- enabled = True
18- else:
19- enabled = False
20+ # You can't create a recipe for a private branch.
21+ enabled = not self.context.private
22 text = 'Create packaging recipe'
23 return Link('+new-recipe', text, enabled=enabled, icon='add')
24
25
26=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
27--- lib/lp/code/browser/sourcepackagerecipe.py 2011-02-17 22:49:24 +0000
28+++ lib/lp/code/browser/sourcepackagerecipe.py 2011-02-18 08:58:49 +0000
29@@ -91,8 +91,10 @@
30 ISourcePackageRecipe,
31 ISourcePackageRecipeSource,
32 MINIMAL_RECIPE_TEXT,
33+ RECIPE_BETA_FLAG,
34 )
35 from lp.registry.interfaces.pocket import PackagePublishingPocket
36+from lp.services.features import getFeatureFlag
37 from lp.services.propertycache import cachedproperty
38 from lp.soyuz.model.archive import Archive
39
40@@ -185,10 +187,9 @@
41 """Default view of a SourcePackageRecipe."""
42
43 def initialize(self):
44- # XXX: rockstar: This should be removed when source package recipes
45- # are put into production. spec=sourcepackagerecipes
46 super(SourcePackageRecipeView, self).initialize()
47- self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
48+ if getFeatureFlag(RECIPE_BETA_FLAG):
49+ self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
50 recipe = self.context
51 if recipe.build_daily and recipe.daily_build_archive is None:
52 self.request.response.addWarningNotification(
53@@ -524,9 +525,8 @@
54
55 def initialize(self):
56 super(SourcePackageRecipeAddView, self).initialize()
57- # XXX: rockstar: This should be removed when source package recipes
58- # are put into production. spec=sourcepackagerecipes
59- self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
60+ if getFeatureFlag(RECIPE_BETA_FLAG):
61+ self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
62 widget = self.widgets['use_ppa']
63 current_value = widget._getFormValue()
64 self.use_ppa_existing = render_radio_widget_part(
65
66=== modified file 'lib/lp/code/browser/sourcepackagerecipelisting.py'
67--- lib/lp/code/browser/sourcepackagerecipelisting.py 2011-01-20 23:48:36 +0000
68+++ lib/lp/code/browser/sourcepackagerecipelisting.py 2011-02-18 08:58:49 +0000
69@@ -19,7 +19,8 @@
70 LaunchpadView,
71 Link,
72 )
73-from lp.code.interfaces.sourcepackagerecipe import recipes_enabled
74+from lp.code.interfaces.sourcepackagerecipe import RECIPE_ENABLED_FLAG
75+from lp.services.features import getFeatureFlag
76
77
78 class HasRecipesMenuMixin:
79@@ -30,7 +31,7 @@
80 enabled = False
81 if self.context.getRecipes().count():
82 enabled = True
83- if not recipes_enabled():
84+ if not getFeatureFlag(RECIPE_ENABLED_FLAG):
85 enabled = False
86 return Link(
87 '+recipes', text, icon='info', enabled=enabled, site='code')
88
89=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
90--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-17 10:10:29 +0000
91+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-18 08:58:49 +0000
92@@ -43,13 +43,18 @@
93 from lp.code.browser.sourcepackagerecipebuild import (
94 SourcePackageRecipeBuildView,
95 )
96-from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT
97+from lp.code.interfaces.sourcepackagerecipe import (
98+ MINIMAL_RECIPE_TEXT,
99+ RECIPE_BETA_FLAG,
100+ RECIPE_ENABLED_FLAG,
101+ )
102 from lp.code.tests.helpers import recipe_parser_newest_version
103 from lp.registry.interfaces.person import (
104 IPersonSet,
105 TeamSubscriptionPolicy,
106 )
107 from lp.registry.interfaces.pocket import PackagePublishingPocket
108+from lp.services.features.testing import FeatureFixture
109 from lp.services.propertycache import clear_property_cache
110 from lp.soyuz.model.processor import ProcessorFamily
111 from lp.testing import (
112@@ -194,6 +199,13 @@
113
114 layer = DatabaseFunctionalLayer
115
116+ def setUp(self):
117+ super(TestSourcePackageRecipeAddView, self).setUp()
118+ self.useFixture(
119+ FeatureFixture(
120+ {RECIPE_ENABLED_FLAG: 'on',
121+ RECIPE_BETA_FLAG: 'true'}))
122+
123 def makeBranch(self):
124 product = self.factory.makeProduct(
125 name='ratatouille', displayname='Ratatouille')
126
127=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
128--- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-17 02:23:25 +0000
129+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-18 08:58:49 +0000
130@@ -14,7 +14,8 @@
131 'ISourcePackageRecipeData',
132 'ISourcePackageRecipeSource',
133 'MINIMAL_RECIPE_TEXT',
134- 'recipes_enabled',
135+ 'RECIPE_BETA_FLAG',
136+ 'RECIPE_ENABLED_FLAG',
137 ]
138
139
140@@ -57,7 +58,6 @@
141 from lp.registry.interfaces.distroseries import IDistroSeries
142 from lp.registry.interfaces.pocket import PackagePublishingPocket
143 from lp.registry.interfaces.role import IHasOwner
144-from lp.services import features
145 from lp.services.fields import (
146 Description,
147 PersonChoice,
148@@ -66,6 +66,9 @@
149 from lp.soyuz.interfaces.archive import IArchive
150
151
152+RECIPE_ENABLED_FLAG = u'code.recipes_enabled'
153+RECIPE_BETA_FLAG = u'code.recipes.beta'
154+
155 MINIMAL_RECIPE_TEXT = dedent(u'''\
156 # bzr-builder format 0.3 deb-version {debupstream}-0~{revno}
157 %s
158@@ -221,13 +224,3 @@
159
160 def exists(owner, name):
161 """Check to see if a recipe by the same name and owner exists."""
162-
163-
164-def recipes_enabled():
165- """Return True if recipes are enabled."""
166- # Features win:
167- if features.getFeatureFlag(u'code.recipes_enabled'):
168- return True
169- if not config.build_from_branch.enabled:
170- return False
171- return True
172
173=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
174--- lib/lp/code/model/sourcepackagerecipe.py 2011-02-17 22:45:31 +0000
175+++ lib/lp/code/model/sourcepackagerecipe.py 2011-02-18 08:58:49 +0000
176@@ -62,7 +62,6 @@
177 ISourcePackageRecipe,
178 ISourcePackageRecipeData,
179 ISourcePackageRecipeSource,
180- recipes_enabled,
181 )
182 from lp.code.interfaces.sourcepackagerecipebuild import (
183 ISourcePackageRecipeBuildSource,
184@@ -248,8 +247,6 @@
185 def requestBuild(self, archive, requester, distroseries, pocket,
186 manual=False):
187 """See `ISourcePackageRecipe`."""
188- if not recipes_enabled():
189- raise ValueError('Source package recipe builds disabled.')
190 if not archive.is_ppa:
191 raise NonPPABuildRequest
192
193
194=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
195--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-17 22:45:31 +0000
196+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-18 08:58:49 +0000
197@@ -39,7 +39,6 @@
198 ISourcePackageRecipe,
199 ISourcePackageRecipeSource,
200 MINIMAL_RECIPE_TEXT,
201- recipes_enabled,
202 )
203 from lp.code.interfaces.sourcepackagerecipebuild import (
204 ISourcePackageRecipeBuild,
205@@ -72,7 +71,6 @@
206 login,
207 login_person,
208 person_logged_in,
209- set_feature_flag,
210 TestCaseWithFactory,
211 ws_object,
212 )
213@@ -376,24 +374,6 @@
214 queue_record.score()
215 self.assertEqual(2705, queue_record.lastscore)
216
217- def test_requestBuildHonoursConfig(self):
218- recipe = self.factory.makeSourcePackageRecipe()
219- (distroseries,) = list(recipe.distroseries)
220- ppa = self.factory.makeArchive()
221- self.pushConfig('build_from_branch', enabled=False)
222- self.assertRaises(
223- ValueError, recipe.requestBuild, ppa, ppa.owner, distroseries,
224- PackagePublishingPocket.RELEASE)
225-
226- def test_recipes_enabled_config(self):
227- self.pushConfig('build_from_branch', enabled=False)
228- self.assertFalse(recipes_enabled())
229-
230- def test_recipes_enabled_flag(self):
231- self.pushConfig('build_from_branch', enabled=False)
232- set_feature_flag(u'code.recipes_enabled', u'on')
233- self.assertTrue(recipes_enabled())
234-
235 def test_requestBuildRejectsOverQuota(self):
236 """Build requests that exceed quota raise an exception."""
237 requester = self.factory.makePerson(name='requester')
238
239=== modified file 'lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt'
240--- lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt 2010-08-23 04:48:17 +0000
241+++ lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt 2011-02-18 08:58:49 +0000
242@@ -5,7 +5,10 @@
243 Pages that want to display lists of recipes use the recipe-listing
244 page template, and views derived from RecipeListingView.
245
246-
247+ >>> from lp.services.features.testing import FeatureFixture
248+ >>> fixture = FeatureFixture(
249+ ... {'code.recipes_enabled': 'on', 'code.recipes.beta': 'true'})
250+ >>> fixture.setUp()
251 >>> def print_recipe_listing_head(browser):
252 ... table = find_tag_by_id(browser.contents, 'recipetable')
253 ... for row in table.thead.fetch('tr'):
254@@ -135,3 +138,5 @@
255 generic-string... lp://dev/... ...
256 generic-string... lp://dev/... ...
257 generic-string... lp://dev/... ...
258+
259+ >>> fixture.cleanUp()
260
261=== modified file 'lib/lp/code/templates/branch-index.pt'
262--- lib/lp/code/templates/branch-index.pt 2010-11-08 14:11:57 +0000
263+++ lib/lp/code/templates/branch-index.pt 2011-02-18 08:58:49 +0000
264@@ -111,7 +111,7 @@
265 replace="structure context/@@++branch-pending-merges" />
266 <tal:branch-recipes
267 replace="structure context/@@++branch-recipes"
268- condition="modules/lp.code.interfaces.sourcepackagerecipe/recipes_enabled"
269+ condition="request/features/code.recipes_enabled"
270 />
271 <tal:related-bugs-specs
272 replace="structure context/@@++branch-related-bugs-specs" />
273
274=== modified file 'lib/lp/code/templates/branch-recipes.pt'
275--- lib/lp/code/templates/branch-recipes.pt 2010-10-31 22:27:36 +0000
276+++ lib/lp/code/templates/branch-recipes.pt 2011-02-18 08:58:49 +0000
277@@ -25,7 +25,8 @@
278 tal:condition="link/enabled"
279 tal:replace="structure link/render"
280 />
281- <img src="/@@/beta" alt="beta" title="This feature is in beta" />
282+ <img src="/@@/beta" alt="beta" title="This feature is in beta"
283+ tal:condition="request/features/code.recipes.beta"/>
284 </div>
285
286 </div>
287
288=== modified file 'lib/lp/code/templates/branchmergequeue-listing.pt'
289--- lib/lp/code/templates/branchmergequeue-listing.pt 2010-11-01 12:35:07 +0000
290+++ lib/lp/code/templates/branchmergequeue-listing.pt 2011-02-18 08:58:49 +0000
291@@ -10,13 +10,13 @@
292
293 <div metal:fill-slot="main">
294
295- <div tal:condition="not: features/code.branchmergequeue">
296+ <div tal:condition="not: request/features/code.branchmergequeue">
297 <em>
298 No merge queues
299 </em>
300 </div>
301
302- <div tal:condition="features/code.branchmergequeue">
303+ <div tal:condition="request/features/code.branchmergequeue">
304
305 <tal:has-queues condition="view/mergequeue_count">
306
307
308=== modified file 'lib/lp/services/features/flags.py'
309--- lib/lp/services/features/flags.py 2011-02-15 08:04:21 +0000
310+++ lib/lp/services/features/flags.py 2011-02-18 08:58:49 +0000
311@@ -41,6 +41,10 @@
312 'boolean',
313 'Enables source package recipes in the API and UI.',
314 ''),
315+ ('code.recipes.beta',
316+ 'boolean',
317+ 'True if recipes are still in beta',
318+ ''),
319 ('hard_timeout',
320 'float',
321 'Sets the hard request timeout in milliseconds.',