Merge lp:~wgrant/launchpad/bug-708999-ff-docs into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12406
Proposed branch: lp:~wgrant/launchpad/bug-708999-ff-docs
Merge into: lp:launchpad
Diff against target: 276 lines (+89/-27)
8 files modified
lib/canonical/launchpad/browser/librarian.py (+3/-3)
lib/lp/services/features/browser/info.py (+7/-0)
lib/lp/services/features/browser/tests/test_feature_info.py (+9/-0)
lib/lp/services/features/flags.py (+37/-16)
lib/lp/services/features/templates/feature-info.pt (+21/-4)
lib/lp/services/features/templates/feature-rules.pt (+5/-1)
lib/lp/services/memcache/client.py (+5/-1)
lib/lp/services/memcache/doc/tales-cache.txt (+2/-2)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-708999-ff-docs
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+49758@code.launchpad.net

Commit message

[r=lifeless][bug=708999] Document code.branchmergequeue and code.incremental_diffs.enabled feature flags, and clean up the docs for the others.

Description of the change

This branch fixes bug #708999 by documenting the two undocumented feature flags.

It also cleans and reformats the value domains and descriptions of several other flags to be more consistent and informative.

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

Thanks for addressing this bug.

To me those explanations don't add a lot beyond the name of the variable.

For instance
  'shows branch merge queues if non-empty'
if the bmq is non-empty?

From looking at the usage of the flags it looks like this means "shows them if the flag is boolean true", and the values are not the strings 'empty' and 'nonempty' but rather you can use any string, which Python will treat as true?

I think that shows we badly need a standard way to express boolean flags.

I would document them as

  if true (a non-empty string), shows the branch merge queue user interface

  if true (a non-empty string), show incremental diffs on merge proposals

Revision history for this message
William Grant (wgrant) wrote :

After much discussion on IRC, I went with a more thorough revamp. I standardised the boolean type to empty/non-empty (which most of the flags already respected, despite their docs), and documented the types. I also expanded the descriptions, documented soyuz.derived-distros-ui.enabled, and added a link to further documentation on the dev wiki.

The new pages can be seen at <http://people.canonical.com/~wgrant/launchpad/feature-flag-docs/>.

Only one flag will require changes on production: "memcache pageid:ProjectGroup:+milestones 13 disabled" needs an s/disabled//.

Revision history for this message
Martin Pool (mbp) wrote :

<poolie> wgrant, also hard_timeout, contrary to its docs, seems to be
in milliseconds
 not seconds

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

I fully agree that we need a standard way of doing boolean FeatureFlags. I believe the lack of it indicates that Feature Flags are incomplete, since enabling/disabling features was described as key functionality.

But I think using empty/non-empty to indicate boolean is pretty gross.

Revision history for this message
William Grant (wgrant) wrote :

Sure, it is really gross, but all except two boolean flags already used that convention. It needs additional thought and work to make booleans work nicely.

Revision history for this message
Martin Pool (mbp) wrote :

On 17 February 2011 03:16, Aaron Bentley <email address hidden> wrote:
> I fully agree that we need a standard way of doing boolean FeatureFlags.  I believe the lack of it indicates that Feature Flags are incomplete, since enabling/disabling features was described as key functionality.
>
> But I think using empty/non-empty to indicate boolean is pretty gross.

I think it's gross too. I filed
<https://bugs.launchpad.net/launchpad/+bug/719182> for that. I think
documenting what currently exists has some value, and making things
consistent also. I'm not really happy about making them consistent
with the worst precedented approach, but doing it one step at a time
is good too.

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

FWIW I really like the pithy nature of what we have at the moment.
Please don't lose that.

Revision history for this message
Martin Pool (mbp) wrote :

On 17 February 2011 11:31, Robert Collins <email address hidden> wrote:
> FWIW I really like the pithy nature of what we have at the moment.
> Please don't lose that.

So can we say it should make sense to people who didn't develop the
feature, but still one sentence?

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

On Thu, Feb 17, 2011 at 1:49 PM, Martin Pool <email address hidden> wrote:
> On 17 February 2011 11:31, Robert Collins <email address hidden> wrote:
>> FWIW I really like the pithy nature of what we have at the moment.
>> Please don't lose that.
>
> So can we say it should make sense to people who didn't develop the
> feature, but still one sentence?

I don't understand.

Revision history for this message
Martin Pool (mbp) wrote :

On 17 February 2011 13:02, Robert Collins <email address hidden> wrote:
> On Thu, Feb 17, 2011 at 1:49 PM, Martin Pool <email address hidden> wrote:
>> On 17 February 2011 11:31, Robert Collins <email address hidden> wrote:
>>> FWIW I really like the pithy nature of what we have at the moment.
>>> Please don't lose that.
>>
>> So can we say it should make sense to people who didn't develop the
>> feature, but still one sentence?
>
> I don't understand.

Which aspect of pithiness were you talking about?

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

On Thu, Feb 17, 2011 at 6:16 PM, Martin Pool <email address hidden> wrote:

> Which aspect of pithiness were you talking about?

the feature flags code itself and the code using it.

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

The domain_value_info test is a little weird - is it really looking for the str() of the named tuples to be in the page?

The memcache flag seems a little hard to follow the implementation of now, but other than that this seems ok. I note that the memcache implementation has gotten substantially longer, and that seems undesirable to me. Lets talk.

Revision history for this message
William Grant (wgrant) wrote :

That test is similar to the others. It checks that each member of each namedtuple is displayed.

I agree with your memcache concern, but it was either that or add a default mechanism.

Revision history for this message
Robert Collins (lifeless) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/browser/librarian.py'
--- lib/canonical/launchpad/browser/librarian.py 2011-01-20 23:55:37 +0000
+++ lib/canonical/launchpad/browser/librarian.py 2011-02-18 08:19:14 +0000
@@ -83,8 +83,8 @@
8383
84 If the file is public, it will redirect to the files http url.84 If the file is public, it will redirect to the files http url.
8585
86 Otherwise if the feature flag publicrestrictedlibrarian is set to 'on'86 Otherwise if the feature flag publicrestrictedlibrarian is true this will
87 this will allocate a token and redirect to the aliases private url.87 allocate a token and redirect to the aliases private url.
8888
89 Otherwise it will proxy the file in the appserver.89 Otherwise it will proxy the file in the appserver.
9090
@@ -179,7 +179,7 @@
179 if not self.context.restricted:179 if not self.context.restricted:
180 # Public file, just point the client at the right place.180 # Public file, just point the client at the right place.
181 return RedirectionView(self.context.http_url, self.request), ()181 return RedirectionView(self.context.http_url, self.request), ()
182 if getFeatureFlag(u'publicrestrictedlibrarian') != 'on':182 if not getFeatureFlag(u'publicrestrictedlibrarian'):
183 # Restricted file and we have not enabled the public183 # Restricted file and we have not enabled the public
184 # restricted librarian yet :- deliver inline.184 # restricted librarian yet :- deliver inline.
185 self._when_streaming()185 self._when_streaming()
186186
=== modified file 'lib/lp/services/features/browser/info.py'
--- lib/lp/services/features/browser/info.py 2011-01-26 22:07:08 +0000
+++ lib/lp/services/features/browser/info.py 2011-02-18 08:19:14 +0000
@@ -15,6 +15,7 @@
15from lp.services.features.flags import (15from lp.services.features.flags import (
16 flag_info,16 flag_info,
17 undocumented_flags,17 undocumented_flags,
18 value_domain_info,
18 )19 )
19from lp.services.features.scopes import (20from lp.services.features.scopes import (
20 HANDLERS,21 HANDLERS,
@@ -25,6 +26,7 @@
2526
26# Named tuples to use when passing flag and scope data to the template.27# Named tuples to use when passing flag and scope data to the template.
27Flag = namedtuple('Flag', ('name', 'domain', 'description', 'default'))28Flag = namedtuple('Flag', ('name', 'domain', 'description', 'default'))
29ValueDomain = namedtuple('ValueDomain', ('name', 'description'))
28Scope = namedtuple('Scope', ('regex', 'description'))30Scope = namedtuple('Scope', ('regex', 'description'))
2931
3032
@@ -45,6 +47,11 @@
45 return ', '.join(undocumented_flags)47 return ', '.join(undocumented_flags)
4648
47 @property49 @property
50 def value_domain_info(self):
51 """A list of flags as named tuples, ready to be rendered."""
52 return map(ValueDomain._make, value_domain_info)
53
54 @property
48 def undocumented_scopes(self):55 def undocumented_scopes(self):
49 """Scope names referenced during process lifetime but not documented.56 """Scope names referenced during process lifetime but not documented.
50 """57 """
5158
=== modified file 'lib/lp/services/features/browser/tests/test_feature_info.py'
--- lib/lp/services/features/browser/tests/test_feature_info.py 2011-01-26 22:07:08 +0000
+++ lib/lp/services/features/browser/tests/test_feature_info.py 2011-02-18 08:19:14 +0000
@@ -19,6 +19,7 @@
19 flag_info,19 flag_info,
20 NullFeatureController,20 NullFeatureController,
21 undocumented_flags,21 undocumented_flags,
22 value_domain_info,
22 )23 )
23from lp.services.features.scopes import (24from lp.services.features.scopes import (
24 HANDLERS,25 HANDLERS,
@@ -66,6 +67,14 @@
66 for item in record:67 for item in record:
67 self.assertThat(browser.contents, Contains(item))68 self.assertThat(browser.contents, Contains(item))
6869
70 def test_value_domain_documentation_displayed(self):
71 """The value domain documentation is displayed on the page."""
72 browser = self.getUserBrowserAsAdmin()
73 browser.open(self.getFeatureInfoUrl())
74 for record in value_domain_info:
75 for item in record:
76 self.assertThat(browser.contents, Contains(item))
77
69 def test_scope_documentation_displayed(self):78 def test_scope_documentation_displayed(self):
70 """The scope documentation is displayed on the page."""79 """The scope documentation is displayed on the page."""
71 browser = self.getUserBrowserAsAdmin()80 browser = self.getUserBrowserAsAdmin()
7281
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2011-02-14 18:21:01 +0000
+++ lib/lp/services/features/flags.py 2011-02-18 08:19:14 +0000
@@ -6,6 +6,7 @@
6 'flag_info',6 'flag_info',
7 'NullFeatureController',7 'NullFeatureController',
8 'undocumented_flags',8 'undocumented_flags',
9 'value_domain_info',
9 ]10 ]
1011
1112
@@ -17,36 +18,56 @@
1718
18__metaclass__ = type19__metaclass__ = type
1920
21
22value_domain_info = sorted([
23 ('boolean',
24 'Any non-empty value is true; an empty value is false.'),
25 ('float',
26 'The flag value is set to the given floating point number.'),
27 ])
28
20# This table of flag name, value domain, and prose documentation is used to29# This table of flag name, value domain, and prose documentation is used to
21# generate the web-visible feature flag documentation.30# generate the web-visible feature flag documentation.
22flag_info = sorted([31flag_info = sorted([
32 ('code.branchmergequeue',
33 'boolean',
34 'Enables merge queue pages and lists them on branch pages.',
35 ''),
36 ('code.incremental_diffs.enabled',
37 'boolean',
38 'Shows incremental diffs on merge proposals.',
39 ''),
23 ('code.recipes_enabled',40 ('code.recipes_enabled',
24 '[on|off]',41 'boolean',
25 'enable recipes',42 'Enables source package recipes in the API and UI.',
26 'off'),43 ''),
27 ('hard_timeout',44 ('hard_timeout',
28 'float',45 'float',
29 'sets the hard timeout in seconds',46 'Sets the hard request timeout in milliseconds.',
30 ''),47 ''),
31 ('malone.advanced-subscriptions.enabled',48 ('malone.advanced-subscriptions.enabled',
32 '[on|off]',49 'boolean',
33 'enables advanced subscriptions features',50 'Enables advanced bug subscription features.',
34 'off'),51 ''),
35 ('malone.disable_targetnamesearch',52 ('malone.disable_targetnamesearch',
36 '[empty|nonempty]',53 'boolean',
37 'If nonempty targetnames are not consulted during text search.',54 'If true, disables consultation of target names during bug text search.',
38 ''),55 ''),
39 ('memcache',56 ('memcache',
40 '[enabled|disabled]',57 'boolean',
41 'enables/disables memcache',58 'Enables use of memcached where it is supported.',
42 'enabled'),59 'enabled'),
43 ('publicrestrictedlibrarian',60 ('publicrestrictedlibrarian',
44 '[on|off]',61 'boolean',
45 'redirect to private URLs instead of proxying',62 'Redirects to private librarian files instead of proxying them.',
46 'off'),63 ''),
64 ('soyuz.derived-series-ui.enabled',
65 'boolean',
66 'Enables derivative distributions pages.',
67 ''),
47 ('visible_render_time',68 ('visible_render_time',
48 'empty|nonempty',69 'boolean',
49 'enables showing the page render overheads in the login widget',70 'Shows the server-side page render time in the login widget.',
50 ''),71 ''),
51 ])72 ])
5273
5374
=== modified file 'lib/lp/services/features/templates/feature-info.pt'
--- lib/lp/services/features/templates/feature-info.pt 2011-01-20 18:33:25 +0000
+++ lib/lp/services/features/templates/feature-info.pt 2011-02-18 08:19:14 +0000
@@ -29,7 +29,7 @@
29 </tbody>29 </tbody>
30 </table>30 </table>
3131
32 <p>32 <p/>
33 <h2>Undocumented flags</h2>33 <h2>Undocumented flags</h2>
34 These flags were referenced during this process' lifetime but are not34 These flags were referenced during this process' lifetime but are not
35 documented:35 documented:
@@ -38,7 +38,24 @@
38 </strong>38 </strong>
39 <strong tal:content="view/undocumented_flags">list of flags</strong>39 <strong tal:content="view/undocumented_flags">list of flags</strong>
4040
41 <p>41 <p/>
42 <h2>Value domains</h2>
43 <table class="listing">
44 <thead>
45 <tr>
46 <th>Name</th>
47 <th>Description</th>
48 </tr>
49 </thead>
50 <tbody>
51 <tr tal:repeat="info view/value_domain_info">
52 <td tal:content="info/name">name here</td>
53 <td tal:content="info/description">description here</td>
54 </tr>
55 </tbody>
56 </table>
57
58 <p/>
42 <h2>Scopes</h2>59 <h2>Scopes</h2>
4360
44 The table below describes the currently available scopes. The first column61 The table below describes the currently available scopes. The first column
@@ -46,7 +63,7 @@
46 "pageid:foo" scopes match the regex "pageid:") and the second gives a63 "pageid:foo" scopes match the regex "pageid:") and the second gives a
47 description of when the scope is active.64 description of when the scope is active.
4865
49 <p>66 <p/>
50 <table class="listing">67 <table class="listing">
51 <thead>68 <thead>
52 <tr>69 <tr>
@@ -62,7 +79,7 @@
62 </tbody>79 </tbody>
63 </table>80 </table>
6481
65 <p>82 <p/>
66 <h2>Undocumented scopes</h2>83 <h2>Undocumented scopes</h2>
67 These scopes were referenced during this process' lifetime but are not84 These scopes were referenced during this process' lifetime but are not
68 documented:85 documented:
6986
=== modified file 'lib/lp/services/features/templates/feature-rules.pt'
--- lib/lp/services/features/templates/feature-rules.pt 2011-01-26 22:07:08 +0000
+++ lib/lp/services/features/templates/feature-rules.pt 2011-02-18 08:19:14 +0000
@@ -18,8 +18,12 @@
18 </p>18 </p>
19 <tal:diff replace="structure view/diff"/>19 <tal:diff replace="structure view/diff"/>
20 </div>20 </div>
21 <p>
21 For more information about the available feature flags and scopes see22 For more information about the available feature flags and scopes see
22 the <a href="+feature-info">feature flag info</a>.23 the <a href="+feature-info">feature flag info</a>. For other
24 documentation on feature flags, see the
25 <a href="https://dev.launchpad.net/FeatureFlags">development wiki</a>.
26 </p>
23 </div>27 </div>
24 </div>28 </div>
25</div>29</div>
2630
=== modified file 'lib/lp/services/memcache/client.py'
--- lib/lp/services/memcache/client.py 2010-09-12 05:52:41 +0000
+++ lib/lp/services/memcache/client.py 2011-02-18 08:19:14 +0000
@@ -36,7 +36,11 @@
3636
37 @property37 @property
38 def _enabled(self):38 def _enabled(self):
39 return features.getFeatureFlag('memcache') != 'disabled'39 configured_value = features.getFeatureFlag('memcache')
40 if configured_value is None:
41 return True
42 else:
43 return configured_value
4044
41 def get(self, key):45 def get(self, key):
42 if not self._enabled:46 if not self._enabled:
4347
=== modified file 'lib/lp/services/memcache/doc/tales-cache.txt'
--- lib/lp/services/memcache/doc/tales-cache.txt 2010-11-02 17:47:05 +0000
+++ lib/lp/services/memcache/doc/tales-cache.txt 2011-02-18 08:19:14 +0000
@@ -394,10 +394,10 @@
394---------394---------
395395
396Memcache in templates can be disabled entirely by setting the memcache flag to396Memcache in templates can be disabled entirely by setting the memcache flag to
397'disabled'.397the empty string.
398398
399 >>> from lp.services.features.testing import FeatureFixture399 >>> from lp.services.features.testing import FeatureFixture
400 >>> fixture = FeatureFixture({'memcache': 'disabled'})400 >>> fixture = FeatureFixture({'memcache': ''})
401 >>> fixture.setUp()401 >>> fixture.setUp()
402402
403And now what cached before will not cache.403And now what cached before will not cache.