Merge lp:~jamesh/go-unityscopes/filter-updates into lp:go-unityscopes/v2
- filter-updates
- Merge into v2
Proposed by
James Henstridge
Status: | Merged |
---|---|
Approved by: | Xavi Garcia |
Approved revision: | 83 |
Merged at revision: | 73 |
Proposed branch: | lp:~jamesh/go-unityscopes/filter-updates |
Merge into: | lp:go-unityscopes/v2 |
Diff against target: |
908 lines (+326/-207) 12 files modified
filters_base.go (+37/-15) option_selector_filter.go (+32/-20) option_selector_filter_test.go (+23/-12) radio_buttons_filter.go (+13/-17) radio_buttons_filter_test.go (+12/-12) range_input_filter.go (+65/-32) range_input_filter_test.go (+21/-6) rating_filter.go (+11/-15) rating_filter_test.go (+9/-9) switch_filter.go (+11/-15) value_slider_filter.go (+77/-41) value_slider_filter_test.go (+15/-13) |
To merge this branch: | bzr merge lp:~jamesh/go-unityscopes/filter-updates |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Xavi Garcia (community) | Approve | ||
Review via email: mp+287921@code.launchpad.net |
Commit message
Description of the change
Update the filters code to match the latest changes in unity-scopes-api. Only the OptionSelectorF
To post a comment you must log in.
- 83. By James Henstridge
-
Add support for returning default values when FilterState is empty, to
match C++ API.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'filters_base.go' |
2 | --- filters_base.go 2015-04-08 12:49:18 +0000 |
3 | +++ filters_base.go 2016-03-18 11:35:50 +0000 |
4 | @@ -2,7 +2,7 @@ |
5 | |
6 | // Filter is implemented by all scope filter types. |
7 | type Filter interface { |
8 | - serializeFilter() interface{} |
9 | + serializeFilter() map[string]interface{} |
10 | } |
11 | |
12 | type FilterDisplayHints int |
13 | @@ -19,23 +19,32 @@ |
14 | Id string |
15 | DisplayHints FilterDisplayHints |
16 | FilterType string |
17 | -} |
18 | - |
19 | -type filterWithLabel struct { |
20 | + Title string |
21 | +} |
22 | + |
23 | +func (f *filterBase) serializeFilter() map[string]interface{} { |
24 | + v := map[string]interface{}{ |
25 | + "filter_type": f.FilterType, |
26 | + "id": f.Id, |
27 | + "display_hints": f.DisplayHints, |
28 | + } |
29 | + if f.Title != "" { |
30 | + v["title"] = f.Title |
31 | + } |
32 | + return v |
33 | +} |
34 | + |
35 | +type filterWithOptions struct { |
36 | filterBase |
37 | - Label string |
38 | -} |
39 | - |
40 | -type filterWithOptions struct { |
41 | - filterWithLabel |
42 | Options []FilterOption |
43 | } |
44 | |
45 | // AddOption adds a new option to the filter. |
46 | -func (f *filterWithOptions) AddOption(id, label string) { |
47 | +func (f *filterWithOptions) AddOption(id, label string, defaultValue bool) { |
48 | f.Options = append(f.Options, FilterOption{ |
49 | - Id: id, |
50 | - Label: label, |
51 | + Id: id, |
52 | + Label: label, |
53 | + Default: defaultValue, |
54 | }) |
55 | } |
56 | |
57 | @@ -62,12 +71,25 @@ |
58 | func (f *filterWithOptions) ActiveOptions(state FilterState) []string { |
59 | var ret []string |
60 | if state[f.Id] != nil { |
61 | - ret = state[f.Id].([]string) |
62 | + options := state[f.Id].([]interface{}) |
63 | + ret = make([]string, len(options)) |
64 | + for i, opt := range options { |
65 | + ret[i] = opt.(string) |
66 | + } |
67 | + } else { |
68 | + // We don't have this filter in the state object, so |
69 | + // give defaults back. |
70 | + for _, o := range f.Options { |
71 | + if o.Default { |
72 | + ret = append(ret, o.Id) |
73 | + } |
74 | + } |
75 | } |
76 | return ret |
77 | } |
78 | |
79 | type FilterOption struct { |
80 | - Id string `json:"id"` |
81 | - Label string `json:"label"` |
82 | + Id string `json:"id"` |
83 | + Label string `json:"label"` |
84 | + Default bool `json:"default"` |
85 | } |
86 | |
87 | === modified file 'option_selector_filter.go' |
88 | --- option_selector_filter.go 2015-03-16 13:16:41 +0000 |
89 | +++ option_selector_filter.go 2016-03-18 11:35:50 +0000 |
90 | @@ -7,6 +7,7 @@ |
91 | // OptionSelectorFilter is used to implement single-select or multi-select filters. |
92 | type OptionSelectorFilter struct { |
93 | filterWithOptions |
94 | + Label string |
95 | MultiSelect bool |
96 | } |
97 | |
98 | @@ -14,19 +15,33 @@ |
99 | func NewOptionSelectorFilter(id, label string, multiSelect bool) *OptionSelectorFilter { |
100 | return &OptionSelectorFilter{ |
101 | filterWithOptions: filterWithOptions{ |
102 | - filterWithLabel: filterWithLabel{ |
103 | - filterBase: filterBase{ |
104 | - Id: id, |
105 | - DisplayHints: FilterDisplayDefault, |
106 | - FilterType: "option_selector", |
107 | - }, |
108 | - Label: label, |
109 | + filterBase: filterBase{ |
110 | + Id: id, |
111 | + DisplayHints: FilterDisplayDefault, |
112 | + FilterType: "option_selector", |
113 | }, |
114 | }, |
115 | + Label: label, |
116 | MultiSelect: multiSelect, |
117 | } |
118 | } |
119 | |
120 | +type optionSort struct { |
121 | + Options []interface{} |
122 | +} |
123 | + |
124 | +func (s optionSort) Len() int { |
125 | + return len(s.Options) |
126 | +} |
127 | + |
128 | +func (s optionSort) Less(i, j int) bool { |
129 | + return s.Options[i].(string) < s.Options[j].(string) |
130 | +} |
131 | + |
132 | +func (s optionSort) Swap(i, j int) { |
133 | + s.Options[i], s.Options[j] = s.Options[j], s.Options[i] |
134 | +} |
135 | + |
136 | // UpdateState updates the value of a particular option in the filter state. |
137 | func (f *OptionSelectorFilter) UpdateState(state FilterState, optionId string, active bool) { |
138 | if !f.isValidOption(optionId) { |
139 | @@ -38,14 +53,14 @@ |
140 | delete(state, f.Id) |
141 | } |
142 | // If the state isn't in a form we expect, treat it as empty |
143 | - selected, _ := state[f.Id].([]string) |
144 | - sort.Strings(selected) |
145 | - pos := sort.SearchStrings(selected, optionId) |
146 | + selected, _ := state[f.Id].([]interface{}) |
147 | + sort.Sort(optionSort{selected}) |
148 | + pos := sort.Search(len(selected), func(i int) bool { return selected[i].(string) >= optionId }) |
149 | if active { |
150 | if pos == len(selected) { |
151 | selected = append(selected, optionId) |
152 | } else if pos < len(selected) && selected[pos] != optionId { |
153 | - selected = append(selected[:pos], append([]string{optionId}, selected[pos:]...)...) |
154 | + selected = append(selected[:pos], append([]interface{}{optionId}, selected[pos:]...)...) |
155 | } |
156 | } else { |
157 | if pos < len(selected) { |
158 | @@ -55,13 +70,10 @@ |
159 | state[f.Id] = selected |
160 | } |
161 | |
162 | -func (f *OptionSelectorFilter) serializeFilter() interface{} { |
163 | - return map[string]interface{}{ |
164 | - "filter_type": f.FilterType, |
165 | - "id": f.Id, |
166 | - "display_hints": f.DisplayHints, |
167 | - "label": f.Label, |
168 | - "multi_select": f.MultiSelect, |
169 | - "options": f.Options, |
170 | - } |
171 | +func (f *OptionSelectorFilter) serializeFilter() map[string]interface{} { |
172 | + v := f.filterBase.serializeFilter() |
173 | + v["label"] = f.Label |
174 | + v["multi_select"] = f.MultiSelect |
175 | + v["options"] = f.Options |
176 | + return v |
177 | } |
178 | |
179 | === modified file 'option_selector_filter_test.go' |
180 | --- option_selector_filter_test.go 2015-04-17 09:44:12 +0000 |
181 | +++ option_selector_filter_test.go 2016-03-18 11:35:50 +0000 |
182 | @@ -13,8 +13,8 @@ |
183 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayDefault) |
184 | |
185 | filter1.DisplayHints = scopes.FilterDisplayPrimary |
186 | - filter1.AddOption("1", "Option 1") |
187 | - filter1.AddOption("2", "Option 2") |
188 | + filter1.AddOption("1", "Option 1", false) |
189 | + filter1.AddOption("2", "Option 2", false) |
190 | |
191 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayPrimary) |
192 | c.Check(2, Equals, len(filter1.Options)) |
193 | @@ -25,16 +25,16 @@ |
194 | |
195 | // verify the list of options |
196 | c.Check(len(filter1.Options), Equals, 2) |
197 | - c.Check(filter1.Options, DeepEquals, []scopes.FilterOption{scopes.FilterOption{"1", "Option 1"}, scopes.FilterOption{"2", "Option 2"}}) |
198 | + c.Check(filter1.Options, DeepEquals, []scopes.FilterOption{scopes.FilterOption{"1", "Option 1", false}, scopes.FilterOption{"2", "Option 2", false}}) |
199 | } |
200 | |
201 | func (s *S) TestOptionSelectorFilterSingleSelection(c *C) { |
202 | filter1 := scopes.NewOptionSelectorFilter("f1", "Options", false) |
203 | - filter1.AddOption("1", "Option 1") |
204 | - filter1.AddOption("2", "Option 2") |
205 | + filter1.AddOption("1", "Option 1", false) |
206 | + filter1.AddOption("2", "Option 2", false) |
207 | |
208 | fstate := make(scopes.FilterState) |
209 | - _, ok := fstate["route"] |
210 | + _, ok := fstate["f1"] |
211 | c.Check(ok, Equals, false) |
212 | c.Check(filter1.HasActiveOption(fstate), Equals, false) |
213 | |
214 | @@ -64,9 +64,9 @@ |
215 | |
216 | func (s *S) TestOptionSelectorFilterMultiSelection(c *C) { |
217 | filter1 := scopes.NewOptionSelectorFilter("f1", "Options", true) |
218 | - filter1.AddOption("1", "Option 1") |
219 | - filter1.AddOption("2", "Option 2") |
220 | - filter1.AddOption("3", "Option 3") |
221 | + filter1.AddOption("1", "Option 1", false) |
222 | + filter1.AddOption("2", "Option 2", false) |
223 | + filter1.AddOption("3", "Option 3", false) |
224 | |
225 | fstate := make(scopes.FilterState) |
226 | |
227 | @@ -144,12 +144,23 @@ |
228 | |
229 | func (s *S) TestOptionSelectorFilterBadOption(c *C) { |
230 | filter1 := scopes.NewOptionSelectorFilter("f1", "Options", true) |
231 | - filter1.AddOption("1", "Option 1") |
232 | - filter1.AddOption("2", "Option 2") |
233 | - filter1.AddOption("3", "Option 3") |
234 | + filter1.AddOption("1", "Option 1", false) |
235 | + filter1.AddOption("2", "Option 2", false) |
236 | + filter1.AddOption("3", "Option 3", false) |
237 | |
238 | fstate := make(scopes.FilterState) |
239 | |
240 | c.Assert(func() { filter1.UpdateState(fstate, "5", true) }, PanicMatches, "invalid option ID") |
241 | c.Assert(func() { filter1.UpdateState(fstate, "5", false) }, PanicMatches, "invalid option ID") |
242 | } |
243 | + |
244 | +func (s *S) TestOptionSelectorFilterDefaultValue(c *C) { |
245 | + filter := scopes.NewOptionSelectorFilter("f1", "Options", true) |
246 | + filter.AddOption("1", "Option 1", false) |
247 | + filter.AddOption("2", "Option 2", true) |
248 | + filter.AddOption("3", "Option 3", false) |
249 | + |
250 | + fstate := make(scopes.FilterState) |
251 | + c.Check(filter.HasActiveOption(fstate), Equals, true) |
252 | + c.Check(filter.ActiveOptions(fstate), DeepEquals, []string{"2"}) |
253 | +} |
254 | |
255 | === modified file 'radio_buttons_filter.go' |
256 | --- radio_buttons_filter.go 2015-03-16 13:40:29 +0000 |
257 | +++ radio_buttons_filter.go 2016-03-18 11:35:50 +0000 |
258 | @@ -3,21 +3,20 @@ |
259 | // RadioButtonsFilter is a filter that displays mutually exclusive list of options |
260 | type RadioButtonsFilter struct { |
261 | filterWithOptions |
262 | + Label string |
263 | } |
264 | |
265 | // NewRadioButtonsFilter creates a new radio button filter. |
266 | func NewRadioButtonsFilter(id, label string) *RadioButtonsFilter { |
267 | return &RadioButtonsFilter{ |
268 | filterWithOptions: filterWithOptions{ |
269 | - filterWithLabel: filterWithLabel{ |
270 | - filterBase: filterBase{ |
271 | - Id: id, |
272 | - DisplayHints: FilterDisplayDefault, |
273 | - FilterType: "radio_buttons", |
274 | - }, |
275 | - Label: label, |
276 | + filterBase: filterBase{ |
277 | + Id: id, |
278 | + DisplayHints: FilterDisplayDefault, |
279 | + FilterType: "radio_buttons", |
280 | }, |
281 | }, |
282 | + Label: label, |
283 | } |
284 | } |
285 | |
286 | @@ -27,7 +26,7 @@ |
287 | panic("invalid option ID") |
288 | } |
289 | // If the state isn't in a form we expect, treat it as empty |
290 | - selected, _ := state[f.Id].([]string) |
291 | + selected, _ := state[f.Id].([]interface{}) |
292 | |
293 | if active { |
294 | if len(selected) == 0 { |
295 | @@ -41,18 +40,15 @@ |
296 | if len(selected) > 0 && selected[0] == optionId { |
297 | // we have 1 option selected and it's the current one. |
298 | // clear the state |
299 | - selected = make([]string, 0) |
300 | + selected = make([]interface{}, 0) |
301 | } |
302 | } |
303 | state[f.Id] = selected |
304 | } |
305 | |
306 | -func (f *RadioButtonsFilter) serializeFilter() interface{} { |
307 | - return map[string]interface{}{ |
308 | - "filter_type": f.FilterType, |
309 | - "id": f.Id, |
310 | - "display_hints": f.DisplayHints, |
311 | - "label": f.Label, |
312 | - "options": f.Options, |
313 | - } |
314 | +func (f *RadioButtonsFilter) serializeFilter() map[string]interface{} { |
315 | + v := f.filterBase.serializeFilter() |
316 | + v["label"] = f.Label |
317 | + v["options"] = f.Options |
318 | + return v |
319 | } |
320 | |
321 | === modified file 'radio_buttons_filter_test.go' |
322 | --- radio_buttons_filter_test.go 2015-04-17 09:44:12 +0000 |
323 | +++ radio_buttons_filter_test.go 2016-03-18 11:35:50 +0000 |
324 | @@ -12,17 +12,17 @@ |
325 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayDefault) |
326 | |
327 | filter1.DisplayHints = scopes.FilterDisplayPrimary |
328 | - filter1.AddOption("1", "Option 1") |
329 | - filter1.AddOption("2", "Option 2") |
330 | - filter1.AddOption("3", "Option 3") |
331 | + filter1.AddOption("1", "Option 1", false) |
332 | + filter1.AddOption("2", "Option 2", false) |
333 | + filter1.AddOption("3", "Option 3", false) |
334 | |
335 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayPrimary) |
336 | |
337 | // verify the list of options |
338 | c.Check(len(filter1.Options), Equals, 3) |
339 | - c.Check(filter1.Options, DeepEquals, []scopes.FilterOption{scopes.FilterOption{"1", "Option 1"}, |
340 | - scopes.FilterOption{"2", "Option 2"}, |
341 | - scopes.FilterOption{"3", "Option 3"}}) |
342 | + c.Check(filter1.Options, DeepEquals, []scopes.FilterOption{scopes.FilterOption{"1", "Option 1", false}, |
343 | + scopes.FilterOption{"2", "Option 2", false}, |
344 | + scopes.FilterOption{"3", "Option 3", false}}) |
345 | |
346 | // check the selection |
347 | fstate := make(scopes.FilterState) |
348 | @@ -31,9 +31,9 @@ |
349 | |
350 | func (s *S) TestRadioButtonsFilterSingleSelection(c *C) { |
351 | filter1 := scopes.NewRadioButtonsFilter("f1", "Options") |
352 | - filter1.AddOption("1", "Option 1") |
353 | - filter1.AddOption("2", "Option 2") |
354 | - filter1.AddOption("3", "Option 2") |
355 | + filter1.AddOption("1", "Option 1", false) |
356 | + filter1.AddOption("2", "Option 2", false) |
357 | + filter1.AddOption("3", "Option 2", false) |
358 | |
359 | fstate := make(scopes.FilterState) |
360 | _, ok := fstate["route"] |
361 | @@ -66,9 +66,9 @@ |
362 | |
363 | func (s *S) TestRadioButtonsFilterBadOption(c *C) { |
364 | filter1 := scopes.NewRadioButtonsFilter("f1", "Options") |
365 | - filter1.AddOption("1", "Option 1") |
366 | - filter1.AddOption("2", "Option 2") |
367 | - filter1.AddOption("3", "Option 3") |
368 | + filter1.AddOption("1", "Option 1", false) |
369 | + filter1.AddOption("2", "Option 2", false) |
370 | + filter1.AddOption("3", "Option 3", false) |
371 | |
372 | fstate := make(scopes.FilterState) |
373 | |
374 | |
375 | === modified file 'range_input_filter.go' |
376 | --- range_input_filter.go 2015-04-10 12:45:09 +0000 |
377 | +++ range_input_filter.go 2016-03-18 11:35:50 +0000 |
378 | @@ -7,26 +7,46 @@ |
379 | |
380 | // RangeInputFilter is a range filter which allows a start and end value to be entered by user, and any of them is optional. |
381 | type RangeInputFilter struct { |
382 | - filterWithLabel |
383 | - StartLabel string |
384 | - EndLabel string |
385 | - UnitLabel string |
386 | + filterBase |
387 | + DefaultStartValue interface{} |
388 | + DefaultEndValue interface{} |
389 | + StartPrefixLabel string |
390 | + StartPostfixLabel string |
391 | + EndPrefixLabel string |
392 | + EndPostfixLabel string |
393 | + CentralLabel string |
394 | +} |
395 | + |
396 | +func checkRangeValidType(value interface{}) bool { |
397 | + switch value.(type) { |
398 | + case int, float64, nil: |
399 | + return true |
400 | + default: |
401 | + return false |
402 | + } |
403 | } |
404 | |
405 | // NewRangeInputFilter creates a new range input filter. |
406 | -func NewRangeInputFilter(id, label, start_label, end_label, unit_label string) *RangeInputFilter { |
407 | +func NewRangeInputFilter(id string, defaultStartValue, defaultEndValue interface{}, startPrefixLabel, startPostfixLabel, endPrefixLabel, endPostfixLabel, centralLabel string) *RangeInputFilter { |
408 | + if !checkRangeValidType(defaultStartValue) { |
409 | + panic("bad type for defaultStartValue") |
410 | + } |
411 | + if !checkRangeValidType(defaultEndValue) { |
412 | + panic("bad type for defaultEndValue") |
413 | + } |
414 | return &RangeInputFilter{ |
415 | - filterWithLabel: filterWithLabel{ |
416 | - filterBase: filterBase{ |
417 | - Id: id, |
418 | - DisplayHints: FilterDisplayDefault, |
419 | - FilterType: "range_input", |
420 | - }, |
421 | - Label: label, |
422 | + filterBase: filterBase{ |
423 | + Id: id, |
424 | + DisplayHints: FilterDisplayDefault, |
425 | + FilterType: "range_input", |
426 | }, |
427 | - StartLabel: start_label, |
428 | - EndLabel: end_label, |
429 | - UnitLabel: unit_label, |
430 | + DefaultStartValue: defaultStartValue, |
431 | + DefaultEndValue: defaultEndValue, |
432 | + StartPrefixLabel: startPrefixLabel, |
433 | + StartPostfixLabel: startPostfixLabel, |
434 | + EndPrefixLabel: endPrefixLabel, |
435 | + EndPostfixLabel: endPostfixLabel, |
436 | + CentralLabel: centralLabel, |
437 | } |
438 | } |
439 | |
440 | @@ -54,6 +74,15 @@ |
441 | default: |
442 | panic("RangeInputFilter:StartValue Unknown value type") |
443 | } |
444 | + } else { |
445 | + switch v := f.DefaultStartValue.(type) { |
446 | + case float64: |
447 | + return v, true |
448 | + case int: |
449 | + return float64(v), true |
450 | + case nil: |
451 | + return 0, false |
452 | + } |
453 | } |
454 | return start, ok |
455 | } |
456 | @@ -82,19 +111,19 @@ |
457 | default: |
458 | panic("RangeInputFilter:EndValue Unknown value type") |
459 | } |
460 | + } else { |
461 | + switch v := f.DefaultEndValue.(type) { |
462 | + case float64: |
463 | + return v, true |
464 | + case int: |
465 | + return float64(v), true |
466 | + case nil: |
467 | + return 0, false |
468 | + } |
469 | } |
470 | return end, ok |
471 | } |
472 | |
473 | -func (f *RangeInputFilter) checkValidType(value interface{}) bool { |
474 | - switch value.(type) { |
475 | - case int, float64, nil: |
476 | - return true |
477 | - default: |
478 | - return false |
479 | - } |
480 | -} |
481 | - |
482 | func convertToFloat(value interface{}) float64 { |
483 | if value != nil { |
484 | fVal, ok := value.(float64) |
485 | @@ -113,10 +142,10 @@ |
486 | |
487 | // UpdateState updates the value of the filter |
488 | func (f *RangeInputFilter) UpdateState(state FilterState, start, end interface{}) error { |
489 | - if !f.checkValidType(start) { |
490 | + if !checkRangeValidType(start) { |
491 | return errors.New("RangeInputFilter:UpdateState: Bad type for start value. Valid types are int float64 and nil") |
492 | } |
493 | - if !f.checkValidType(end) { |
494 | + if !checkRangeValidType(end) { |
495 | return errors.New("RangeInputFilter:UpdateState: Bad type for end value. Valid types are int float64 and nil") |
496 | } |
497 | |
498 | @@ -136,10 +165,14 @@ |
499 | return nil |
500 | } |
501 | |
502 | -func (f *RangeInputFilter) serializeFilter() interface{} { |
503 | - return map[string]interface{}{ |
504 | - "start_label": f.StartLabel, |
505 | - "end_label": f.EndLabel, |
506 | - "unit_label": f.UnitLabel, |
507 | - } |
508 | +func (f *RangeInputFilter) serializeFilter() map[string]interface{} { |
509 | + v := f.filterBase.serializeFilter() |
510 | + v["default_start_value"] = f.DefaultStartValue |
511 | + v["default_end_value"] = f.DefaultEndValue |
512 | + v["start_prefix_label"] = f.StartPrefixLabel |
513 | + v["start_postfix_label"] = f.StartPostfixLabel |
514 | + v["end_prefix_label"] = f.EndPrefixLabel |
515 | + v["end_postfix_label"] = f.EndPostfixLabel |
516 | + v["central_label"] = f.CentralLabel |
517 | + return v |
518 | } |
519 | |
520 | === modified file 'range_input_filter_test.go' |
521 | --- range_input_filter_test.go 2015-04-17 09:44:12 +0000 |
522 | +++ range_input_filter_test.go 2016-03-18 11:35:50 +0000 |
523 | @@ -6,13 +6,15 @@ |
524 | ) |
525 | |
526 | func (s *S) TestRangeInputFilter(c *C) { |
527 | - filter1 := scopes.NewRangeInputFilter("f1", "Options", "start_label", "end_label", "unit_label") |
528 | + filter1 := scopes.NewRangeInputFilter("f1", nil, nil, "start_prefix", "start_postfix", "end_prefix", "end_postfix", "central") |
529 | c.Check("f1", Equals, filter1.Id) |
530 | - c.Check("Options", Equals, filter1.Label) |
531 | - c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayDefault) |
532 | - c.Check("start_label", Equals, filter1.StartLabel) |
533 | - c.Check("end_label", Equals, filter1.EndLabel) |
534 | - c.Check("unit_label", Equals, filter1.UnitLabel) |
535 | + c.Check(nil, Equals, filter1.DefaultStartValue) |
536 | + c.Check(nil, Equals, filter1.DefaultEndValue) |
537 | + c.Check("start_prefix", Equals, filter1.StartPrefixLabel) |
538 | + c.Check("start_postfix", Equals, filter1.StartPostfixLabel) |
539 | + c.Check("end_prefix", Equals, filter1.EndPrefixLabel) |
540 | + c.Check("end_postfix", Equals, filter1.EndPostfixLabel) |
541 | + c.Check("central", Equals, filter1.CentralLabel) |
542 | |
543 | // check the selection |
544 | fstate := make(scopes.FilterState) |
545 | @@ -155,3 +157,16 @@ |
546 | c.Check(end, Equals, float64(100)) |
547 | c.Check(found, Equals, true) |
548 | } |
549 | + |
550 | +func (s *S) TestRangeInputFilterDefaults(c *C) { |
551 | + filter := scopes.NewRangeInputFilter("f1", 0, 100, "start_prefix", "start_postfix", "end_prefix", "end_postfix", "central") |
552 | + |
553 | + fstate := make(scopes.FilterState) |
554 | + start, ok := filter.StartValue(fstate) |
555 | + c.Check(ok, Equals, true) |
556 | + c.Check(start, Equals, 0.0) |
557 | + |
558 | + end, ok := filter.EndValue(fstate) |
559 | + c.Check(ok, Equals, true) |
560 | + c.Check(end, Equals, 100.0) |
561 | +} |
562 | |
563 | === modified file 'rating_filter.go' |
564 | --- rating_filter.go 2015-04-08 12:49:18 +0000 |
565 | +++ rating_filter.go 2016-03-18 11:35:50 +0000 |
566 | @@ -7,6 +7,7 @@ |
567 | // RatingFilter is a filter that allows for rating-based selection |
568 | type RatingFilter struct { |
569 | filterWithOptions |
570 | + Label string |
571 | OnIcon string |
572 | OffIcon string |
573 | } |
574 | @@ -15,15 +16,13 @@ |
575 | func NewRatingFilter(id, label string) *RatingFilter { |
576 | return &RatingFilter{ |
577 | filterWithOptions: filterWithOptions{ |
578 | - filterWithLabel: filterWithLabel{ |
579 | - filterBase: filterBase{ |
580 | - Id: id, |
581 | - DisplayHints: FilterDisplayDefault, |
582 | - FilterType: "rating", |
583 | - }, |
584 | - Label: label, |
585 | + filterBase: filterBase{ |
586 | + Id: id, |
587 | + DisplayHints: FilterDisplayDefault, |
588 | + FilterType: "rating", |
589 | }, |
590 | }, |
591 | + Label: label, |
592 | } |
593 | } |
594 | |
595 | @@ -49,12 +48,9 @@ |
596 | } |
597 | } |
598 | |
599 | -func (f *RatingFilter) serializeFilter() interface{} { |
600 | - return map[string]interface{}{ |
601 | - "filter_type": f.FilterType, |
602 | - "id": f.Id, |
603 | - "display_hints": f.DisplayHints, |
604 | - "label": f.Label, |
605 | - "options": f.Options, |
606 | - } |
607 | +func (f *RatingFilter) serializeFilter() map[string]interface{} { |
608 | + v := f.filterBase.serializeFilter() |
609 | + v["label"] = f.Label |
610 | + v["options"] = f.Options |
611 | + return v |
612 | } |
613 | |
614 | === modified file 'rating_filter_test.go' |
615 | --- rating_filter_test.go 2015-04-17 09:44:12 +0000 |
616 | +++ rating_filter_test.go 2016-03-18 11:35:50 +0000 |
617 | @@ -12,8 +12,8 @@ |
618 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayDefault) |
619 | |
620 | filter1.DisplayHints = scopes.FilterDisplayPrimary |
621 | - filter1.AddOption("1", "Option 1") |
622 | - filter1.AddOption("2", "Option 2") |
623 | + filter1.AddOption("1", "Option 1", false) |
624 | + filter1.AddOption("2", "Option 2", false) |
625 | |
626 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayPrimary) |
627 | c.Check(2, Equals, len(filter1.Options)) |
628 | @@ -24,14 +24,14 @@ |
629 | |
630 | // verify the list of options |
631 | c.Check(len(filter1.Options), Equals, 2) |
632 | - c.Check(filter1.Options, DeepEquals, []scopes.FilterOption{scopes.FilterOption{"1", "Option 1"}, scopes.FilterOption{"2", "Option 2"}}) |
633 | + c.Check(filter1.Options, DeepEquals, []scopes.FilterOption{scopes.FilterOption{"1", "Option 1", false}, scopes.FilterOption{"2", "Option 2", false}}) |
634 | } |
635 | |
636 | func (s *S) TestRatingFilterMultiSelection(c *C) { |
637 | filter1 := scopes.NewRatingFilter("f1", "Options") |
638 | - filter1.AddOption("1", "Option 1") |
639 | - filter1.AddOption("2", "Option 2") |
640 | - filter1.AddOption("3", "Option 3") |
641 | + filter1.AddOption("1", "Option 1", false) |
642 | + filter1.AddOption("2", "Option 2", false) |
643 | + filter1.AddOption("3", "Option 3", false) |
644 | |
645 | fstate := make(scopes.FilterState) |
646 | |
647 | @@ -80,9 +80,9 @@ |
648 | |
649 | func (s *S) TestRatingFilterBadOption(c *C) { |
650 | filter1 := scopes.NewRatingFilter("f1", "Options") |
651 | - filter1.AddOption("1", "Option 1") |
652 | - filter1.AddOption("2", "Option 2") |
653 | - filter1.AddOption("3", "Option 3") |
654 | + filter1.AddOption("1", "Option 1", false) |
655 | + filter1.AddOption("2", "Option 2", false) |
656 | + filter1.AddOption("3", "Option 3", false) |
657 | |
658 | fstate := make(scopes.FilterState) |
659 | |
660 | |
661 | === modified file 'switch_filter.go' |
662 | --- switch_filter.go 2015-03-16 13:16:41 +0000 |
663 | +++ switch_filter.go 2016-03-18 11:35:50 +0000 |
664 | @@ -2,20 +2,19 @@ |
665 | |
666 | // SwitchFilter is a simple on/off switch filter. |
667 | type SwitchFilter struct { |
668 | - filterWithLabel |
669 | + filterBase |
670 | + Label string |
671 | } |
672 | |
673 | // NewSwitchFilter creates a new switch filter. |
674 | func NewSwitchFilter(id, label string) *SwitchFilter { |
675 | return &SwitchFilter{ |
676 | - filterWithLabel: filterWithLabel{ |
677 | - filterBase: filterBase{ |
678 | - Id: id, |
679 | - DisplayHints: FilterDisplayDefault, |
680 | - FilterType: "switch", |
681 | - }, |
682 | - Label: label, |
683 | + filterBase: filterBase{ |
684 | + Id: id, |
685 | + DisplayHints: FilterDisplayDefault, |
686 | + FilterType: "switch", |
687 | }, |
688 | + Label: label, |
689 | } |
690 | } |
691 | |
692 | @@ -34,11 +33,8 @@ |
693 | state[f.Id] = value |
694 | } |
695 | |
696 | -func (f *SwitchFilter) serializeFilter() interface{} { |
697 | - return map[string]interface{}{ |
698 | - "filter_type": f.FilterType, |
699 | - "id": f.Id, |
700 | - "display_hints": f.DisplayHints, |
701 | - "label": f.Label, |
702 | - } |
703 | +func (f *SwitchFilter) serializeFilter() map[string]interface{} { |
704 | + v := f.filterBase.serializeFilter() |
705 | + v["label"] = f.Label |
706 | + return v |
707 | } |
708 | |
709 | === modified file 'value_slider_filter.go' |
710 | --- value_slider_filter.go 2015-03-16 13:16:41 +0000 |
711 | +++ value_slider_filter.go 2016-03-18 11:35:50 +0000 |
712 | @@ -5,47 +5,80 @@ |
713 | "fmt" |
714 | ) |
715 | |
716 | -type SliderType int |
717 | - |
718 | -const ( |
719 | - LessThan SliderType = 0 |
720 | - MoreThan SliderType = 1 << iota |
721 | -) |
722 | - |
723 | // ValueSliderFilter is a value slider filter that allows for selecting a value within a given range. |
724 | type ValueSliderFilter struct { |
725 | - filterWithLabel |
726 | - Type SliderType |
727 | - DefaultValue float64 |
728 | - Min float64 |
729 | - Max float64 |
730 | - ValueLabelTemplate string |
731 | + filterBase |
732 | + DefaultValue float64 |
733 | + Min float64 |
734 | + Max float64 |
735 | + Labels ValueSliderLabels |
736 | +} |
737 | + |
738 | +type ValueSliderLabels struct { |
739 | + MinLabel string |
740 | + MaxLabel string |
741 | + ExtraLabels []ValueSliderExtraLabel |
742 | +} |
743 | + |
744 | +type ValueSliderExtraLabel struct { |
745 | + Value float64 |
746 | + Label string |
747 | } |
748 | |
749 | // NewValueSliderFilter creates a new value slider filter. |
750 | -func NewValueSliderFilter(id, label, label_template string, min, max float64) *ValueSliderFilter { |
751 | - return &ValueSliderFilter{ |
752 | - filterWithLabel: filterWithLabel{ |
753 | - filterBase: filterBase{ |
754 | - Id: id, |
755 | - DisplayHints: FilterDisplayDefault, |
756 | - FilterType: "value_slider", |
757 | - }, |
758 | - Label: label, |
759 | +func NewValueSliderFilter(id string, min, max, defaultValue float64, labels ValueSliderLabels) *ValueSliderFilter { |
760 | + filter := &ValueSliderFilter{ |
761 | + filterBase: filterBase{ |
762 | + Id: id, |
763 | + DisplayHints: FilterDisplayDefault, |
764 | + FilterType: "value_slider", |
765 | }, |
766 | - ValueLabelTemplate: label_template, |
767 | - Min: min, |
768 | - Max: max, |
769 | - DefaultValue: max, |
770 | + Min: min, |
771 | + Max: max, |
772 | + DefaultValue: defaultValue, |
773 | + Labels: labels, |
774 | + } |
775 | + filter.validate() |
776 | + return filter |
777 | +} |
778 | + |
779 | +func (f *ValueSliderFilter) validate() { |
780 | + if f.Min >= f.Max { |
781 | + panic("Invalid range for value slider filter") |
782 | + } |
783 | + last := f.Min |
784 | + labels := map[string]bool{ |
785 | + f.Labels.MinLabel: true, |
786 | + f.Labels.MaxLabel: true, |
787 | + } |
788 | + // check that values of extra labels grow, i.e. v1 < v2 < v3 ... |
789 | + // and labels are unique and not empty |
790 | + for _, l := range f.Labels.ExtraLabels { |
791 | + if l.Value <= last { |
792 | + panic("Extra label for value slider filter out of sequence") |
793 | + } |
794 | + last = l.Value |
795 | + if l.Label == "" { |
796 | + panic("Extra labels cannot be empty") |
797 | + } |
798 | + if labels[l.Label] { |
799 | + panic("Multiple definitions for extra label") |
800 | + } |
801 | + labels[l.Label] = true |
802 | + } |
803 | + if f.Max <= last { |
804 | + panic("Last extra label value greater than maximum value") |
805 | } |
806 | } |
807 | |
808 | // Value gets value of this filter from filter state object. |
809 | // If the value is not set for the filter it returns false as the second return statement, |
810 | // it returns true otherwise |
811 | -func (f *ValueSliderFilter) Value(state FilterState) (float64, bool) { |
812 | - value, ok := state[f.Id].(float64) |
813 | - return value, ok |
814 | +func (f *ValueSliderFilter) Value(state FilterState) float64 { |
815 | + if value, ok := state[f.Id].(float64); ok { |
816 | + return value |
817 | + } |
818 | + return f.DefaultValue |
819 | } |
820 | |
821 | // UpdateState updates the value of the filter to the given value |
822 | @@ -57,16 +90,19 @@ |
823 | return nil |
824 | } |
825 | |
826 | -func (f *ValueSliderFilter) serializeFilter() interface{} { |
827 | - return map[string]interface{}{ |
828 | - "filter_type": f.FilterType, |
829 | - "id": f.Id, |
830 | - "display_hints": f.DisplayHints, |
831 | - "label": f.Label, |
832 | - "label_template": f.ValueLabelTemplate, |
833 | - "min": f.Min, |
834 | - "max": f.Max, |
835 | - "default": f.DefaultValue, |
836 | - "slider_type": f.Type, |
837 | - } |
838 | +func (f *ValueSliderFilter) serializeFilter() map[string]interface{} { |
839 | + v := f.filterBase.serializeFilter() |
840 | + v["min"] = marshalFloat(f.Min) |
841 | + v["max"] = marshalFloat(f.Max) |
842 | + v["default"] = marshalFloat(f.DefaultValue) |
843 | + extra := make([]interface{}, 0, 2*len(f.Labels.ExtraLabels)) |
844 | + for _, l := range f.Labels.ExtraLabels { |
845 | + extra = append(extra, marshalFloat(l.Value), l.Label) |
846 | + } |
847 | + v["labels"] = map[string]interface{}{ |
848 | + "min_label": f.Labels.MinLabel, |
849 | + "max_label": f.Labels.MaxLabel, |
850 | + "extra_labels": extra, |
851 | + } |
852 | + return v |
853 | } |
854 | |
855 | === modified file 'value_slider_filter_test.go' |
856 | --- value_slider_filter_test.go 2015-04-17 09:44:12 +0000 |
857 | +++ value_slider_filter_test.go 2016-03-18 11:35:50 +0000 |
858 | @@ -6,35 +6,37 @@ |
859 | ) |
860 | |
861 | func (s *S) TestValueSliderFilter(c *C) { |
862 | - filter1 := scopes.NewValueSliderFilter("f1", "Options", "label", 10.0, 100.0) |
863 | + labels := scopes.ValueSliderLabels{ |
864 | + MinLabel: "min", |
865 | + MaxLabel: "max", |
866 | + ExtraLabels: []scopes.ValueSliderExtraLabel{ |
867 | + {50, "middle"}, |
868 | + }, |
869 | + } |
870 | + filter1 := scopes.NewValueSliderFilter("f1", 10.0, 100.0, 50, labels) |
871 | c.Check("f1", Equals, filter1.Id) |
872 | - c.Check("Options", Equals, filter1.Label) |
873 | c.Check(filter1.DisplayHints, Equals, scopes.FilterDisplayDefault) |
874 | - c.Check(filter1.DefaultValue, Equals, 100.0) |
875 | + c.Check(filter1.DefaultValue, Equals, 50.0) |
876 | c.Check(filter1.Min, Equals, 10.0) |
877 | c.Check(filter1.Max, Equals, 100.0) |
878 | - c.Check(filter1.ValueLabelTemplate, Equals, "label") |
879 | + c.Check(filter1.Labels, DeepEquals, labels) |
880 | |
881 | fstate := make(scopes.FilterState) |
882 | - value, ok := filter1.Value(fstate) |
883 | - c.Check(value, Equals, 0.0) |
884 | - c.Check(ok, Equals, false) |
885 | + value := filter1.Value(fstate) |
886 | + c.Check(value, Equals, 50.0) |
887 | |
888 | err := filter1.UpdateState(fstate, 30.5) |
889 | c.Check(err, IsNil) |
890 | - value, ok = filter1.Value(fstate) |
891 | + value = filter1.Value(fstate) |
892 | c.Check(value, Equals, 30.5) |
893 | - c.Check(ok, Equals, true) |
894 | |
895 | err = filter1.UpdateState(fstate, 44.5) |
896 | c.Check(err, IsNil) |
897 | - value, ok = filter1.Value(fstate) |
898 | + value = filter1.Value(fstate) |
899 | c.Check(value, Equals, 44.5) |
900 | - c.Check(ok, Equals, true) |
901 | |
902 | err = filter1.UpdateState(fstate, 3545.33) |
903 | c.Check(err, Not(Equals), nil) |
904 | - value, ok = filter1.Value(fstate) |
905 | + value = filter1.Value(fstate) |
906 | c.Check(value, Equals, 44.5) |
907 | - c.Check(ok, Equals, true) |
908 | } |
Looks good to me, thanks James.