Merge lp:~jamesh/go-unityscopes/filter-updates into lp:go-unityscopes/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
Reviewer Review Type Date Requested Status
Xavi Garcia (community) Approve
Review via email: mp+287921@code.launchpad.net

Description of the change

Update the filters code to match the latest changes in unity-scopes-api. Only the OptionSelectorFilter, ValueSliderFilter, and RangeInputFilter have been touched since those are the ones actually usable in unity8.

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.

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Looks good to me, thanks James.

review: Approve

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 }

Subscribers

People subscribed via source and target branches

to all changes: