Merge lp:~frankban/juju-quickstart/improve-field-choices into lp:juju-quickstart
- improve-field-choices
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 43 |
Proposed branch: | lp:~frankban/juju-quickstart/improve-field-choices |
Merge into: | lp:juju-quickstart |
Diff against target: |
293 lines (+132/-19) 7 files modified
quickstart/cli/forms.py (+30/-7) quickstart/cli/ui.py (+3/-0) quickstart/cli/views.py (+7/-2) quickstart/models/envs.py (+1/-0) quickstart/tests/cli/test_forms.py (+85/-6) quickstart/tests/cli/test_views.py (+5/-3) quickstart/tests/models/test_fields.py (+1/-1) |
To merge this branch: | bzr merge lp:~frankban/juju-quickstart/improve-field-choices |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email:
|
Commit message
Description of the change
Improve field choices handling.
Implement clickable choices: the user can update
the corresponding edit widget by clicking choices.
Also changed the default series field behavior so
that unset values default to "precise".
Tests: `make check`.
QA: start the demo app
(`make` and `./cli-
Use it to edit existing environments and
to create new ones (ec2 and local).
Check that choice fields
(e.g. ec2's regions or default series)
can be updated by clicking the choices.
Also check that, if the default series
is not set, it defaults to "precise".
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gary Poster (gary) wrote : | # |
QA first:
- Very cool. Looks good generally.
- It looks like Urwid can support an "underline" setting. I think that
would make it easier to see the clickable help text (for both
autogeneration and these choices). WDYT?
- In my default terminal width (80x24) I see "possible values are:
precise, quantal, raring, saucy". If I use the arrow keys then I can
see there is a final option (", but this field can also be _left
empty_"). With the mouse I am out of luck. I'd like to improve that if
we could. Options include letting the line wrap, if Urwid supports
that; showing a clickable "MORE->" or "..." or something; or simply
trying to reduce the length of the clickable text.
In line with the last, simplest idea, what about replacing the four
lines with something like the following? I'm assuming we can make the
first text wrappable easily, but if not maybe this idea needs tweaking.
To select a value, type above, _clear the field_ to get the default
value (precise), or click one of the following choices:
_precise_, _quantal_, _raring_, _saucy_, _trusty_
Mm. Darn. I see in the region field that trying to reduce the length
of the clickable text is insufficient. Those options go on for days.
That will need line wrap or something else similar
- Relatedly, and probably for the future (and maybe never), it would be
nice if the fields that can be automatically generated are allowed to be
empty, which defaults to using the autogeneration.
- I see that we actually explicitly set precise in the file if it is
empty (as you describe in the cover letter). However, IIUC, Juju will
work fine if you do not specify a series. The current *Juju* default
behavior is to default to precise, but probably will change to
defaulting to trusty eventually. That makes me think for our
opinionated usage that we should begin rendering the field with
"precise" but if someone explicitly deletes the field then it is simply
empty (and the "empty" semantics are described in the fields help text).
WDYT?
Thank you!
- 48. By Francesco Banconi
-
Some changes as per review.
- 49. By Francesco Banconi
-
Checkpoint.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gary Poster (gary) wrote : | # |
LGTM. Thank you!
https:/
File quickstart/
https:/
quickstart/
Sorry for not catching this and other similar ones earlier.
- 50. By Francesco Banconi
-
Checkpoint.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
On 2014/01/07 13:50:48, gary.poster wrote:
> - It looks like Urwid can support an "underline" setting. I think
that would
> make it easier to see the clickable help text (for both autogeneration
and these
> choices). WDYT?
Sounds good, done.
> - In my default terminal width (80x24) I see "possible values are:
precise,
> quantal, raring, saucy". If I use the arrow keys then I can see there
is a
> final option (", but this field can also be _left empty_"). With the
mouse I am
> out of luck. I'd like to improve that if we could. Options include
letting the
> line wrap, if Urwid supports that; showing a clickable "MORE->" or
"..." or
> something; or simply trying to reduce the length of the clickable
text.
> In line with the last, simplest idea, what about replacing the four
lines with
> something like the following? I'm assuming we can make the first text
wrappable
> easily, but if not maybe this idea needs tweaking.
> To select a value, type above, _clear the field_ to get the default
value
> (precise), or click one of the following choices:
> _precise_, _quantal_, _raring_, _saucy_, _trusty_
> Mm. Darn. I see in the region field that trying to reduce the length
of the
> clickable text is insufficient. Those options go on for days. That
will need
> line wrap or something else similar
Fixed using a GridFlow in place of the Columns widget (which does not
support automatic line wrapping). This way the choices adapt better to
the screen size.
> - I see that we actually explicitly set precise in the file if it is
empty (as
> you describe in the cover letter). However, IIUC, Juju will work fine
if you do
> not specify a series. The current *Juju* default behavior is to
default to
> precise, but probably will change to defaulting to trusty eventually.
That
> makes me think for our opinionated usage that we should begin
rendering the
> field with "precise" but if someone explicitly deletes the field then
it is
> simply empty (and the "empty" semantics are described in the fields
help text).
> WDYT?
I think the current behavior is wrong and what you suggested is better.
I was wrong thinking the default series (if unset) is the host series.
Fixed, now we pre-fill precise when creating a new environment.
Thank you!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gary Poster (gary) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Improve field choices handling.
Implement clickable choices: the user can update
the corresponding edit widget by clicking choices.
Also changed the default series field behavior so
that unset values default to "precise".
Tests: `make check`.
QA: start the demo app
(`make` and `./cli-
Use it to edit existing environments and
to create new ones (ec2 and local).
Check that choice fields
(e.g. ec2's regions or default series)
can be updated by clicking the choices.
Also check that, if the default series
is not set, it defaults to "precise".
R=gary.poster
CC=
https:/
Preview Diff
1 | === modified file 'quickstart/cli/forms.py' |
2 | --- quickstart/cli/forms.py 2014-01-02 17:04:25 +0000 |
3 | +++ quickstart/cli/forms.py 2014-01-08 12:01:09 +0000 |
4 | @@ -48,7 +48,7 @@ |
5 | """ |
6 | generate_callback = ui.thunk(_generate, generate_callable, edit_widget) |
7 | generate_button = ui.MenuButton( |
8 | - ('highlight', 'Click here to automatically generate'), |
9 | + ('field button', 'Click here to automatically generate'), |
10 | generate_callback) |
11 | return urwid.Columns([ |
12 | ('pack', generate_button), |
13 | @@ -56,6 +56,32 @@ |
14 | ]) |
15 | |
16 | |
17 | +def _create_choices_widget(choices, required, edit_widget): |
18 | + """Create and return a choices widget. |
19 | + |
20 | + The widget displays the given choices for a specific form field. Clicking a |
21 | + choice updates the corresponding edit widget. |
22 | + """ |
23 | + widgets = [urwid.Text('possible values are:')] |
24 | + callback = edit_widget.set_edit_text |
25 | + buttons = [ |
26 | + ui.MenuButton(('field button', choice), ui.thunk(callback, choice)) |
27 | + for choice in choices |
28 | + ] |
29 | + cell_width = max(button.base_widget.pack()[0] for button in buttons) |
30 | + widgets.append(urwid.GridFlow(buttons, cell_width, 1, 0, 'left')) |
31 | + if not required: |
32 | + button = ui.MenuButton( |
33 | + ('field button', 'left empty'), ui.thunk(callback, '')) |
34 | + widgets.append(urwid.Columns([ |
35 | + ('pack', urwid.Text('but this field can also be ')), button, |
36 | + ])) |
37 | + help = urwid.Text( |
38 | + 'click the choices to auto-fill the field with the standard options') |
39 | + widgets.append(help) |
40 | + return urwid.Pile(widgets) |
41 | + |
42 | + |
43 | def create_string_widget(field, value, error): |
44 | """Create a string widget and return a tuple (widget, value_getter). |
45 | |
46 | @@ -101,12 +127,9 @@ |
47 | # as part of the help message. |
48 | choices = getattr(field, 'choices', None) |
49 | if choices is not None: |
50 | - # XXX frankban 2014-01-02: make the choices clickable and update the |
51 | - # edit widget when a choice is clicked. |
52 | - text = 'possible values are {}'.format(', '.join(choices)) |
53 | - if not field.required: |
54 | - text = '{}, but this field can also be left empty'.format(text) |
55 | - widgets.append(urwid.Text(text)) |
56 | + choices_widget = _create_choices_widget( |
57 | + tuple(choices), field.required, edit_widget) |
58 | + widgets.append(choices_widget) |
59 | if field.default is not None: |
60 | widgets.append( |
61 | urwid.Text('default if not set: {}'.format(field.default))) |
62 | |
63 | === modified file 'quickstart/cli/ui.py' |
64 | --- quickstart/cli/ui.py 2014-01-03 13:32:25 +0000 |
65 | +++ quickstart/cli/ui.py 2014-01-08 12:01:09 +0000 |
66 | @@ -39,6 +39,8 @@ |
67 | ('error', 'light red', 'black'), |
68 | ('error status', 'light red', 'light gray'), |
69 | ('error selected', 'light red', 'dark blue'), |
70 | + ('field button', 'white,underline', 'black'), |
71 | + ('field button selected', 'white,underline', 'dark blue'), |
72 | ('footer', 'black', 'light gray'), |
73 | ('message', 'white', 'dark green'), |
74 | ('header', 'white', 'dark magenta'), |
75 | @@ -54,6 +56,7 @@ |
76 | None: 'selected', |
77 | 'control alert': 'error selected', |
78 | 'error': 'error selected', |
79 | + 'field button': 'field button selected', |
80 | 'highlight': 'selected', |
81 | } |
82 | # Define a default padding for the Urwid application. |
83 | |
84 | === modified file 'quickstart/cli/views.py' |
85 | --- quickstart/cli/views.py 2014-01-03 13:55:57 +0000 |
86 | +++ quickstart/cli/views.py 2014-01-08 12:01:09 +0000 |
87 | @@ -102,6 +102,7 @@ |
88 | |
89 | import urwid |
90 | |
91 | +from quickstart import settings |
92 | from quickstart.cli import ( |
93 | base, |
94 | forms, |
95 | @@ -188,10 +189,14 @@ |
96 | 'highlight', 'Use the links below to create new environments')), |
97 | urwid.Divider(), |
98 | ]) |
99 | + # The Juju GUI can be safely installed in the bootstrap node only if its |
100 | + # series is "precise". Suggest this setting by pre-filling the value. |
101 | + preferred_series = settings.JUJU_GUI_PREFERRED_SERIES |
102 | widgets.extend([ |
103 | ui.MenuButton( |
104 | ['\N{BULLET} new ', ('highlight', env_type), ' environment'], |
105 | - ui.thunk(edit_view, {'type': env_type}) |
106 | + ui.thunk(edit_view, { |
107 | + 'type': env_type, 'default-series': preferred_series}) |
108 | ) |
109 | for env_type in envs.get_supported_env_types(env_type_db) |
110 | ]) |
111 | @@ -317,7 +322,7 @@ |
112 | |
113 | The last value (env_data) indicates whether this view is used to create a |
114 | new environment or to change an existing one. In the former case, env_data |
115 | - only includes the "type" key. If instead the environment already exists, |
116 | + does not include the "name" key. If instead the environment already exists, |
117 | env_data includes the "name" key and all the other environment info. |
118 | """ |
119 | env_db = copy.deepcopy(env_db) |
120 | |
121 | === modified file 'quickstart/models/envs.py' |
122 | --- quickstart/models/envs.py 2014-01-03 11:38:43 +0000 |
123 | +++ quickstart/models/envs.py 2014-01-08 12:01:09 +0000 |
124 | @@ -334,6 +334,7 @@ |
125 | default_series_field = fields.ChoiceField( |
126 | 'default-series', choices=settings.JUJU_DEFAULT_SERIES, |
127 | label='default series', required=False, |
128 | + default=settings.JUJU_GUI_PREFERRED_SERIES, |
129 | help='the default Ubuntu series to use for the bootstrap node') |
130 | is_default_field = fields.BoolField( |
131 | 'is-default', label='default', allow_mixed=False, required=True, |
132 | |
133 | === modified file 'quickstart/tests/cli/test_forms.py' |
134 | --- quickstart/tests/cli/test_forms.py 2014-01-02 17:04:25 +0000 |
135 | +++ quickstart/tests/cli/test_forms.py 2014-01-08 12:01:09 +0000 |
136 | @@ -56,7 +56,89 @@ |
137 | mock_edit_widget.set_edit_text.assert_called_once_with('generated') |
138 | |
139 | |
140 | -class TestCreateStringWidget(unittest.TestCase): |
141 | +class ChoicesTestsMixin(object): |
142 | + """Helpers for inspecting the choices widget.""" |
143 | + |
144 | + def get_buttons(self, choices_widget): |
145 | + """Return the MenuButton instances included in the choices_widget.""" |
146 | + # The grid widget including choices is the second widget in the pile. |
147 | + grid = choices_widget.contents[1][0] |
148 | + buttons = [ |
149 | + widget for widget, _ in grid.contents |
150 | + if isinstance(widget, ui.MenuButton) |
151 | + ] |
152 | + # Check if the unset button is present. |
153 | + columns_or_text = choices_widget.contents[2][0] |
154 | + if isinstance(columns_or_text, urwid.Columns): |
155 | + buttons.append(columns_or_text.contents[1][0]) |
156 | + return buttons |
157 | + |
158 | + def assert_choices(self, expected_choices, choices_widget): |
159 | + """Ensure the given choices widget is well formed. |
160 | + |
161 | + The message displayed should equal the given expected_message. |
162 | + """ |
163 | + obtained_choices = [ |
164 | + cli_helpers.get_button_caption(button) for |
165 | + button in self.get_buttons(choices_widget) |
166 | + ] |
167 | + self.assertEqual(expected_choices, obtained_choices) |
168 | + |
169 | + |
170 | +class TestCreateChoicesWidget(ChoicesTestsMixin, unittest.TestCase): |
171 | + |
172 | + def setUp(self): |
173 | + # Set up a mock edit widget and choices. |
174 | + self.mock_edit_widget = mock.Mock() |
175 | + self.choices = ['Kirk', 'Picard', 'Sisko'] |
176 | + |
177 | + def test_widget_message(self): |
178 | + # The resulting widget includes all the choices. |
179 | + widget = forms._create_choices_widget( |
180 | + self.choices, True, self.mock_edit_widget) |
181 | + # The resulting pile widget is composed by choices and help message. |
182 | + self.assert_choices(self.choices, widget) |
183 | + help_widget = widget.contents[2][0] |
184 | + self.assertEqual( |
185 | + 'click the choices to auto-fill the field with the ' |
186 | + 'standard options', |
187 | + help_widget.text) |
188 | + |
189 | + def test_widget_buttons(self): |
190 | + # The widget includes a button for each choice. |
191 | + widget = forms._create_choices_widget( |
192 | + self.choices, True, self.mock_edit_widget) |
193 | + buttons = self.get_buttons(widget) |
194 | + self.assertEqual(3, len(buttons)) |
195 | + self.assertEqual( |
196 | + self.choices, map(cli_helpers.get_button_caption, buttons)) |
197 | + |
198 | + def test_choice_click(self): |
199 | + # Clicking a choice updates the edit widget. |
200 | + widget = forms._create_choices_widget( |
201 | + self.choices, True, self.mock_edit_widget) |
202 | + buttons = self.get_buttons(widget) |
203 | + for button in buttons: |
204 | + choice = cli_helpers.get_button_caption(button) |
205 | + # Click the button. |
206 | + cli_helpers.emit(button) |
207 | + # Ensure the edit widget has been updated accordingly. |
208 | + self.mock_edit_widget.set_edit_text.assert_called_with(choice) |
209 | + |
210 | + def test_not_required(self): |
211 | + # If the field is not required, an option to unset the field is |
212 | + # displayed. |
213 | + widget = forms._create_choices_widget( |
214 | + self.choices, False, self.mock_edit_widget) |
215 | + self.assert_choices(self.choices + ['left empty'], widget) |
216 | + buttons = self.get_buttons(widget) |
217 | + # The last button is used to unset the edit widget. |
218 | + self.assertEqual(4, len(buttons)) |
219 | + cli_helpers.emit(buttons[-1]) |
220 | + self.mock_edit_widget.set_edit_text.assert_called_once_with('') |
221 | + |
222 | + |
223 | +class TestCreateStringWidget(ChoicesTestsMixin, unittest.TestCase): |
224 | |
225 | def inspect_widget(self, widget, field): |
226 | """Return a list of sub-widgets composing the given string widget. |
227 | @@ -165,10 +247,7 @@ |
228 | field = fields.ChoiceField('captain', choices=('Kirk', 'Picard')) |
229 | widget, _ = forms.create_string_widget(field, 'Luc', 'invalid name') |
230 | choices = self.inspect_widget(widget, field)[6] |
231 | - self.assertEqual( |
232 | - 'possible values are Kirk, Picard, ' |
233 | - 'but this field can also be left empty', |
234 | - choices.text) |
235 | + self.assert_choices(['Kirk', 'Picard', 'left empty'], choices) |
236 | |
237 | def test_choices_required_field(self): |
238 | # Possible choices are properly displayed below the edit widget for |
239 | @@ -177,7 +256,7 @@ |
240 | 'captain', required=True, choices=('Janeway', 'Sisko')) |
241 | widget, _ = forms.create_string_widget(field, 'Luc', 'invalid name') |
242 | choices = self.inspect_widget(widget, field)[6] |
243 | - self.assertEqual('possible values are Janeway, Sisko', choices.text) |
244 | + self.assert_choices(['Janeway', 'Sisko'], choices) |
245 | |
246 | |
247 | class TestCreateBoolWidget(unittest.TestCase): |
248 | |
249 | === modified file 'quickstart/tests/cli/test_views.py' |
250 | --- quickstart/tests/cli/test_views.py 2014-01-03 14:13:34 +0000 |
251 | +++ quickstart/tests/cli/test_views.py 2014-01-08 12:01:09 +0000 |
252 | @@ -24,6 +24,7 @@ |
253 | import mock |
254 | import urwid |
255 | |
256 | +from quickstart import settings |
257 | from quickstart.cli import ( |
258 | base, |
259 | forms, |
260 | @@ -209,7 +210,8 @@ |
261 | cli_helpers.emit(button) |
262 | mock_env_edit.assert_called_once_with( |
263 | self.app, self.env_type_db, env_db, self.save_callable, |
264 | - {'type': env_type}) |
265 | + {'type': env_type, |
266 | + 'default-series': settings.JUJU_GUI_PREFERRED_SERIES}) |
267 | # Reset the mock so that it does not include any calls on the next |
268 | # loop cycle. |
269 | mock_env_edit.reset_mock() |
270 | @@ -457,8 +459,8 @@ |
271 | def call_view(self, env_name='lxc', env_type=None): |
272 | """Call the view passing the env_data corresponding to env_name. |
273 | |
274 | - If env_type provider, the view is a creation form, env_name is ignored |
275 | - and a new env_data is passed to the view. |
276 | + If env_type is provided, the view is a creation form, env_name is |
277 | + ignored and a new env_data is passed to the view. |
278 | """ |
279 | if env_type is None: |
280 | self.env_data = envs.get_env_data(self.env_db, env_name) |
281 | |
282 | === modified file 'quickstart/tests/models/test_fields.py' |
283 | --- quickstart/tests/models/test_fields.py 2013-12-17 11:39:55 +0000 |
284 | +++ quickstart/tests/models/test_fields.py 2014-01-08 12:01:09 +0000 |
285 | @@ -440,7 +440,7 @@ |
286 | |
287 | def test_validation_error_not_in_choices(self): |
288 | # A ValueError is raised by choice fields if the value is not included |
289 | - # in the specified choices/ |
290 | + # in the specified choices. |
291 | field = self.field_class( |
292 | 'word', choices=self.choices, label='selected word') |
293 | expected = ('the selected word requires the value to be one of the ' |
Reviewers: mp+200658_ code.launchpad. net,
Message:
Please take a look.
Description:
Improve field choices handling.
Implement clickable choices: the user can update
the corresponding edit widget by clicking choices.
Also changed the default series field behavior so
that unset values default to "precise".
Tests: `make check`.
QA: start the demo app app-demo. py).
(`make` and `./cli-
Use it to edit existing environments and
to create new ones (ec2 and local).
Check that choice fields
(e.g. ec2's regions or default series)
can be updated by clicking the choices.
Also check that, if the default series
is not set, it defaults to "precise".
https:/ /code.launchpad .net/~frankban/ juju-quickstart /improve- field-choices/ +merge/ 200658
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/34630044/
Affected files (+236, -22 lines): cli/forms. py models/ envs.py models/ fields. py tests/cli/ test_forms. py tests/models/ test_fields. py
A [revision details]
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/