Merge lp:~mbp/launchpad/flags-gui into lp:launchpad
| Status: | Merged | ||||
|---|---|---|---|---|---|
| Approved by: | Jeroen T. Vermeulen on 2010-09-29 | ||||
| Approved revision: | no longer in the source branch. | ||||
| Merged at revision: | 11657 | ||||
| Proposed branch: | lp:~mbp/launchpad/flags-gui | ||||
| Merge into: | lp:launchpad | ||||
| Diff against target: |
693 lines (+476/-42) 11 files modified
lib/lp/services/features/__init__.py (+11/-1) lib/lp/services/features/browser/configure.zcml (+25/-0) lib/lp/services/features/browser/edit.py (+84/-0) lib/lp/services/features/browser/tests/test_feature_editor.py (+138/-0) lib/lp/services/features/configure.zcml (+3/-0) lib/lp/services/features/flags.py (+19/-29) lib/lp/services/features/model.py (+1/-0) lib/lp/services/features/rulesource.py (+116/-0) lib/lp/services/features/templates/feature-rules.pt (+18/-0) lib/lp/services/features/tests/test_flags.py (+58/-11) lib/lp/services/features/webapp.py (+3/-1) |
||||
| To merge this branch: | bzr merge lp:~mbp/launchpad/flags-gui | ||||
| Related bugs: |
|
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Jeroen T. Vermeulen (community) | code | 2010-09-23 | Approve on 2010-09-29 |
| Launchpad code reviewers | 2010-09-28 | Pending | |
|
Review via email:
|
|||
Commit Message
Feature flags UI.
Description of the Change
This adds two pages, https:/
through which you can control feature flags.
The former is readonly and for ~launchpad; the latter is only for ~admins.
See screenshot <https:/
Very simple one-rule-per-line textarea, which is the simplest thing that could possibly work.
This could do with a couple more tests for the ui, and perhaps some testing/handling of syntax errors, but aside from that should be good to go.
| Martin Pool (mbp) wrote : | # |
Hi, thanks for putting so much work into the review. Everything I
haven't commented on I agree with.
> The UI isn't very nice, but since this is meant for internal use only I can see the value in getting something simple working quickly.
Right, getting something better than raw sql is useful, and perhaps
some experience with it will show what people most want to have
changed. If you have ideas about what an ideal UI would be like I'd
be happy to hear them, at least for a later branch.
On 27 September 2010 22:17, Jeroen T. Vermeulen <email address hidden> wrote:
> Review: Needs Fixing code
> Hi Martin,
>
However I don't think this branch is quite ready yet.
>
>
> 1 === added directory 'lib/lp/
> 2 === added file 'lib/lp/
> 3 --- lib/lp/
> 4 +++ lib/lp/
> 5 @@ -0,0 +1,45 @@
> 6 +# Copyright 2010 Canonical Ltd. This software is licensed under the
> 7 +# GNU Affero General Public License version 3 (see the file LICENSE).
>
> I don't much care for putting code in __init__.py. I can see that it keeps the imports shorter, but it's unusual enough that it may be easy to miss.
OK. Can I make it just browser.py, or should I have a submodule with
a named file within it?
>
>
> 8 +
> 9 +"""Feature control view"""
> 10 +
>
> Where's the __all__?
>
>
> 11 +
> 12 +import logging
> 13 +
> 14 +
> 15 +from zope.interface import Interface
> 16 +from zope.schema import (
> 17 + Text,
> 18 + )
> 19 +
> 20 +from canonical.
> 21 + action,
> 22 + LaunchpadFormView,
> 23 + )
> 24 +
> 25 +
> 26 +class IFeatureControl
> 27 + """Interface specifically for editing a text form of feature rules"""
> 28 +
> 29 + def __init__(self, context):
> 30 + self.context = context
> 31 +
> 32 + feature_rules = Text(title=
> 33 +
> 34 +
> 35 +class FeatureControlV
>
> Needs a docstring.
>
>
> 91 === added file 'lib/lp/
> 92 --- lib/lp/
> 93 +++ lib/lp/
>
> 130 +class TestFeatureCont
> 131 +
> 132 + layer = DatabaseFunctio
> 133 +
> 134 + def getUserBrowserA
> 135 + """Make a TestBrowser authenticated as a team member.
> 136 +
> 137 + :param teams: List of teams to add the new user to.
> 138 + """
>
> This method dithers between being a test setup helper and a test verification helper. Things end up muddled: when you expect getUserBrowserA
| Martin Pool (mbp) wrote : | # |
Oh, I'll just mention that one reason i used a textarea even for the readonly view is so that the rules can easily be copied/pasted, mailed, and then re-pasted back in to the writable view. So I will stick with that for now, but add some text explaining how the fields should be interpreted.
| Martin Pool (mbp) wrote : | # |
> I don't much care for putting code in __init__.py. I can see that it keeps the imports shorter, but it's unusual enough that it may be easy to miss.
I realize you said this an a preference not a rule, but I got the idea this was common because there are dozens of existing modules with nontrivial inits. Perhaps that's no longer prefered. If so, it should go into https:/
>> 147 + def getFeatureRules
> Needs a docstring.
This is the kind of thing where I question "everything should have a docstring." It's in test code; it's only used in this class; the name is pretty selfexplanatory and it's only two lines of code.
> A pilot error would lead to an oops in the best case or lots of inconvenienced users in the worst case. What if they accidentally enter one field too many, for instance? What of duplicate lines? What of near-duplicate lines with different values? Even if the data is meant to be entered only by careful and usually sober admins, I'd like to see a bit more checking and a lot more testing!
If we look at the various species of pilot error:
0- syntactically invalid, for instance having not enough fields or conflicting priorities: you'll get an oops; it would be much nicer to give you a clean message explaining where you went wrong. however the change won't be committed to the database and it has no lasting impact.
1 - syntactically valid, but not what you meant to say: will be committed to the database and may cause arbitrarily weird things to happen to users.
To me 1 is more important to guard against for obvious reasons.
To reduce the chance of that happening there are various things we can do:
* show a diff of the changes before they're applied
* have a smarter editor that knows what options are available and what values they can take
* allow reverting changes
* store and show a history of changes
I think fixing 1 will largely obsolete 0 because we'll go away from just having a plaintext editor; so I'm not so interested in fixing it by itself. (This is, I admit, also an argument from impatience.)
I think the risk is tolerable because the people with access to this already can and do run arbitrary shell and sql commands, and the damage they can do here is strictly less than is possible with them. I would like to get this just rolling too, and give them something better than raw sql to use.
| Martin Pool (mbp) wrote : | # |
The current changes address, I think, everything substantial here. The ui is not ideal, but I think is better than raw sql, and I'm impatient to get something like that up.
I may see about changing it to have just one url, that appears in either readonly or read-write mode as appropriate.
| Robert Collins (lifeless) wrote : | # |
On Tue, Sep 28, 2010 at 10:23 PM, Martin Pool <email address hidden> wrote:
>> I don't much care for putting code in __init__.py. I can see that it keeps the imports shorter, but it's unusual enough that it may be easy to miss.
>
> I realize you said this an a preference not a rule, but I got the idea this was common because there are dozens of existing modules with nontrivial inits. Perhaps that's no longer prefered. If so, it should go into https:/
Putting stuff in __init__ is fine. If its not clear, or the file is
'large', split it out - but thats a normal rule anyhow.
>>> 147 + def getFeatureRules
>> Needs a docstring.
>
> This is the kind of thing where I question "everything should have a docstring." It's in test code; it's only used in this class; the name is pretty selfexplanatory and it's only two lines of code.
Agreed; I certainly woudn't put a docstring unless its nonobvious what it is.
> I think the risk is tolerable because the people with access to this already can and do run arbitrary shell and sql commands, and the damage they can do here is strictly less than is possible with them. I would like to get this just rolling too, and give them something better than raw sql to use.
Also by definition these config values will (at worst) enable a
feature to users that didn't expect it (or conversely disable things
we didn't want disabled [because they needed an explicit-on to work].
Not a huge issue. I like the idea of a diff showing 'this is what
changed', if you care to do it - but I wouldn't block on having it.
-Rob
| Martin Pool (mbp) wrote : | # |
I'm going to do one more change here, which is to consider unifying
the view and edit pages, which will also fix an issue of a useless
"submit" button appearing on the view page, and perhaps will remove
some unnecessary repetition. Then I think this is ready to land; more
can be done both in UI and other aspects of flags but this is an
incremental improvement.
| Jeroen T. Vermeulen (jtv) wrote : | # |
Hi Martin,
Following up in two parts. This is part one. I, too, will skip the parts I agree with and which seem to require no further action.
> > 1 === added directory 'lib/lp/
> > 2 === added file 'lib/lp/
> > 3 --- lib/lp/
> 00:00:00 +0000
> > 4 +++ lib/lp/
> 08:17:44 +0000
> > 5 @@ -0,0 +1,45 @@
> > 6 +# Copyright 2010 Canonical Ltd. This software is licensed under the
> > 7 +# GNU Affero General Public License version 3 (see the file
> LICENSE).
> >
> > I don't much care for putting code in __init__.py. I can see that it keeps
> the imports shorter, but it's unusual enough that it may be easy to miss.
>
> OK. Can I make it just browser.py, or should I have a submodule with
> a named file within it?
If a nontrivial __init__.py is already a normal thing to write, then there's no reason to worry about it here after all. So it's fine to leave it as it is.
> > 91 === added file
> 'lib/lp/
> > 92 --- lib/lp/
> 1970-01-01 00:00:00 +0000
> > 93 +++ lib/lp/
> 2010-09-27 08:17:44 +0000
> >
> > 130 +class TestFeatureCont
> > 131 +
> > 132 + layer = DatabaseFunctio
> > 133 +
> > 134 + def getUserBrowserA
> > 135 + """Make a TestBrowser authenticated as a team member.
> > 136 +
> > 137 + :param teams: List of teams to add the new user to.
> > 138 + """
> >
> > This method dithers between being a test setup helper and a test
> verification helper. Things end up muddled: when you expect
> getUserBrowserA
> assertion about the "open the page" part but the code makes it look as if
> you're talking about the "add user to teams" part instead.
>
> Hm, so perhaps (if it's possible) it would be clearer to write something like:
>
> browser = getUserBrowserA
> self.assertRais
Yes, exactly. And AFAICS that should work.
> > 152 + def getFeaturePageB
> > 153 + url = self.getFeature
> > 154 + admin_team = getUtility(
> > 155 + return self.getUserBro
> >
> > It looks to me as if BrowserTestCase ought to have a "getAdminBrowser."
> Have you considered creating one?
>
> Right, that's bug 646563, but this branch is getting big enough already.
Very well.
> > 165 + def test_feature_
> > 166 + addFeatureFlag
> > 167 + ('default', 'ui.icing', u'3.0', 100),
> > 168 + ('beta_user', 'ui.icing', u'4.0', 300),
> > 169 + ])
> > 170 + browser = self.getFeature
> > 171 + te...
| Jeroen T. Vermeulen (jtv) wrote : | # |
This is part two of my follow-up. I'm close to an Approved vote, but a quick glance at the current diff still shows the irregularly indented test strings. Have you been fixing the formatting problems I pointed out?
> >> 147 + def getFeatureRules
> > Needs a docstring.
>
> This is the kind of thing where I question "everything should have a
> docstring." It's in test code; it's only used in this class; the name is
> pretty selfexplanatory and it's only two lines of code.
It would have been, apart from the uncertainty over the read/edit page dichotomy. Your updated test has methods for both, making this a lot clearer to me. With that I no longer care about the docstring.
> > A pilot error would lead to an oops in the best case or lots of
> inconvenienced users in the worst case. What if they accidentally enter one
> field too many, for instance? What of duplicate lines? What of near-
> duplicate lines with different values? Even if the data is meant to be entered
> only by careful and usually sober admins, I'd like to see a bit more checking
> and a lot more testing!
>
> If we look at the various species of pilot error:
>
> 0- syntactically invalid, for instance having not enough fields or conflicting
> priorities: you'll get an oops; it would be much nicer to give you a clean
> message explaining where you went wrong. however the change won't be
> committed to the database and it has no lasting impact.
>
> 1 - syntactically valid, but not what you meant to say: will be committed to
> the database and may cause arbitrarily weird things to happen to users.
>
> To me 1 is more important to guard against for obvious reasons.
>
> To reduce the chance of that happening there are various things we can do:
> * show a diff of the changes before they're applied
> * have a smarter editor that knows what options are available and what values
> they can take
> * allow reverting changes
> * store and show a history of changes
>
> I think fixing 1 will largely obsolete 0 because we'll go away from just
> having a plaintext editor; so I'm not so interested in fixing it by itself.
> (This is, I admit, also an argument from impatience.)
>
> I think the risk is tolerable because the people with access to this already
> can and do run arbitrary shell and sql commands, and the damage they can do
> here is strictly less than is possible with them. I would like to get this
> just rolling too, and give them something better than raw sql to use.
Still, a separate input validation pass will more or less automatically lead to better error detection and reporting. On IRC you mentioned that you can do that in a validate method, which is the right way to do it. Adding the explanatory text will also help make some mistakes more obvious.
Jeroen
| Martin Pool (mbp) wrote : | # |
> > OK. Can I make it just browser.py, or should I have a submodule with
> > a named file within it?
>
> If a nontrivial __init__.py is already a normal thing to write, then there's
> no reason to worry about it here after all. So it's fine to leave it as it
> is.
I already moved it, and perhaps it's better that way as it leaves room for growth.
> > Hm, so perhaps (if it's possible) it would be clearer to write something
> like:
> >
> > browser = getUserBrowserA
> > self.assertRais
>
> Yes, exactly. And AFAICS that should work.
Yep, and that is better.
> I think ignoring whitespace would be a fine thing to do, assuming that a
> separate test verifies that the whitespace conforms to expectations.
>
> A Matcher would be very nice here.
Done, and reformatted to not be specially indented.
>
> > Actually I think for most testing, we want to just change the
> > featurecontroller, not actually change the database. And perhaps it
> > should be a test fixture not on that monolith class?
>
> I could imagine a need for both "create a rule in the database" and "create a
> rule in whatever rules source I'm currently using." Which you want this
> function for is up to you, and I defer to your judgment.
There's some other existing code that's directly poking things in to the db. I'd like to reconsider the way it sets things up, but separately from this.
| Martin Pool (mbp) wrote : | # |
> This is part two of my follow-up. I'm close to an Approved vote, but a quick
> glance at the current diff still shows the irregularly indented test strings.
> Have you been fixing the formatting problems I pointed out?
I have, and now that one is done too.
> Still, a separate input validation pass will more or less automatically lead
> to better error detection and reporting. On IRC you mentioned that you can do
> that in a validate method, which is the right way to do it. Adding the
> explanatory text will also help make some mistakes more obvious.
I did that.
| Jeroen T. Vermeulen (jtv) wrote : | # |
Much better, thanks. Still a bit of un-indented string in there, but believe it or not I'm not here to stop you from getting your branch in. :)
I'll proceed to land it for you, as requested.

Hi Martin,
The UI isn't very nice, but since this is meant for internal use only I can see the value in getting something simple working quickly. However I don't think this branch is quite ready yet.
1 === added directory 'lib/lp/ services/ features/ browser' services/ features/ browser/ __init_ _.py' services/ features/ browser/ __init_ _.py 1970-01-01 00:00:00 +0000 services/ features/ browser/ __init_ _.py 2010-09-27 08:17:44 +0000
2 === added file 'lib/lp/
3 --- lib/lp/
4 +++ lib/lp/
5 @@ -0,0 +1,45 @@
6 +# Copyright 2010 Canonical Ltd. This software is licensed under the
7 +# GNU Affero General Public License version 3 (see the file LICENSE).
I don't much care for putting code in __init__.py. I can see that it keeps the imports shorter, but it's unusual enough that it may be easy to miss.
8 +
9 +"""Feature control view"""
10 +
Where's the __all__?
11 + launchpad. webapp import ( Form(Interface) : u"Feature rules") iew(LaunchpadFo rmView) :
12 +import logging
13 +
14 +
15 +from zope.interface import Interface
16 +from zope.schema import (
17 + Text,
18 + )
19 +
20 +from canonical.
21 + action,
22 + LaunchpadFormView,
23 + )
24 +
25 +
26 +class IFeatureControl
27 + """Interface specifically for editing a text form of feature rules"""
28 +
29 + def __init__(self, context):
30 + self.context = context
31 +
32 + feature_rules = Text(title=
33 +
34 +
35 +class FeatureControlV
Needs a docstring.
91 === added file 'lib/lp/ services/ features/ browser/ tests/test_ feature_ editor. py' services/ features/ browser/ tests/test_ feature_ editor. py 1970-01-01 00:00:00 +0000 services/ features/ browser/ tests/test_ feature_ editor. py 2010-09-27 08:17:44 +0000
92 --- lib/lp/
93 +++ lib/lp/
130 +class TestFeatureCont rolPage( BrowserTestCase ): nalLayer sTeamMember( self, url, teams):
131 +
132 + layer = DatabaseFunctio
133 +
134 + def getUserBrowserA
135 + """Make a TestBrowser authenticated as a team member.
136 +
137 + :param teams: List of teams to add the new user to.
138 + """
This method dithers between being a test setup helper and a test verification helper. Things end up muddled: when you expect getUserBrowserA sTeamMember to raise an authorization error, you're making an assertion about the "open the page" part but the code makes it look as if you're talking about the "add user to teams" part instead.
139 + # XXX bug=646563: To make a UserBrowser, you must know the password. This
140 + # should be separated out into test infrastructure. -- mbp 20100923
The XXX format as I recall it goes:
# XXX MartinPool 2010-09-23 bug=646563: To make a UserBrowser, you
# etc.
141 + user = self.factory. makePerson( password= 'test') logged_ in(team. teamowner) : user, reviewer= team.teamowner) wser(url, user=user, password='test')
142 + for team in teams:
143 + with person_
144 + team.addMember(
145 + return self.getUserBro
I would suggest not opening "url" here, so that getUserBrowserA sTeamMember can serve as an unmistakable setup helper. That also leaves you free to assume an admin identity just once (or remove...