summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElliot Smith <elliot.smith@intel.com>2016-01-15 13:00:53 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-01-15 16:30:00 +0000
commitf8d383d87f0b9d4a4c9ae7b1a6c8ceebf90ef9b0 (patch)
treecda7dc3eb23a5b6a97241c965bbd9b0dcddc02eb
parentb929889cdd4a36846f9569d89fabd9987e94b39e (diff)
downloadpoky-f8d383d87f0b9d4a4c9ae7b1a6c8ceebf90ef9b0.tar.gz
bitbake: toastergui: implement date range filters for builds
Implement the completed_on and started_on filtering for builds. Also separate the name of a filter ("filter" in the querystring) from its value ("filter_value" in the querystring). This enables filtering to be defined in the querystring more intuitively, and also makes it easier to add other types of filter (e.g. by day). [YOCTO #8738] (Bitbake rev: d47c32e88c2d4a423f4d94d49759e557f425a539) Signed-off-by: Elliot Smith <elliot.smith@intel.com> Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/toaster/toastergui/querysetfilter.py3
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/table.js196
-rw-r--r--bitbake/lib/toaster/toastergui/tablefilter.py113
-rw-r--r--bitbake/lib/toaster/toastergui/tables.py38
-rw-r--r--bitbake/lib/toaster/toastergui/templates/builds-toastertable.html32
-rw-r--r--bitbake/lib/toaster/toastergui/widgets.py32
6 files changed, 330 insertions, 84 deletions
diff --git a/bitbake/lib/toaster/toastergui/querysetfilter.py b/bitbake/lib/toaster/toastergui/querysetfilter.py
index dbae239370..efa8507050 100644
--- a/bitbake/lib/toaster/toastergui/querysetfilter.py
+++ b/bitbake/lib/toaster/toastergui/querysetfilter.py
@@ -2,10 +2,11 @@ class QuerysetFilter(object):
2 """ Filter for a queryset """ 2 """ Filter for a queryset """
3 3
4 def __init__(self, criteria=None): 4 def __init__(self, criteria=None):
5 self.criteria = None
5 if criteria: 6 if criteria:
6 self.set_criteria(criteria) 7 self.set_criteria(criteria)
7 8
8 def set_criteria(self, criteria = None): 9 def set_criteria(self, criteria):
9 """ 10 """
10 criteria is an instance of django.db.models.Q; 11 criteria is an instance of django.db.models.Q;
11 see https://docs.djangoproject.com/en/1.9/ref/models/querysets/#q-objects 12 see https://docs.djangoproject.com/en/1.9/ref/models/querysets/#q-objects
diff --git a/bitbake/lib/toaster/toastergui/static/js/table.js b/bitbake/lib/toaster/toastergui/static/js/table.js
index 63f8a1fed7..b0a8ffb8f9 100644
--- a/bitbake/lib/toaster/toastergui/static/js/table.js
+++ b/bitbake/lib/toaster/toastergui/static/js/table.js
@@ -397,11 +397,140 @@ function tableInit(ctx){
397 $.cookie("cols", JSON.stringify(disabled_cols)); 397 $.cookie("cols", JSON.stringify(disabled_cols));
398 } 398 }
399 399
400 /**
401 * Create the DOM/JS for the client side of a TableFilterActionToggle
402 *
403 * filterName: (string) internal name for the filter action
404 * filterActionData: (object)
405 * filterActionData.count: (number) The number of items this filter will
406 * show when selected
407 */
408 function createActionToggle(filterName, filterActionData) {
409 var actionStr = '<div class="radio">' +
410 '<input type="radio" name="filter"' +
411 ' value="' + filterName + '"';
412
413 if (Number(filterActionData.count) == 0) {
414 actionStr += ' disabled="disabled"';
415 }
416
417 actionStr += ' id="' + filterName + '">' +
418 '<input type="hidden" name="filter_value" value="on"' +
419 ' data-value-for="' + filterName + '">' +
420 '<label class="filter-title"' +
421 ' for="' + filterName + '">' +
422 filterActionData.title +
423 ' (' + filterActionData.count + ')' +
424 '</label>' +
425 '</div>';
426
427 return $(actionStr);
428 }
429
430 /**
431 * Create the DOM/JS for the client side of a TableFilterActionDateRange
432 *
433 * filterName: (string) internal name for the filter action
434 * filterValue: (string) from,to date range in format yyyy-mm-dd,yyyy-mm-dd;
435 * used to select the current values for the from/to datepickers;
436 * if this is partial (e.g. "yyyy-mm-dd,") only the applicable datepicker
437 * will have a date pre-selected; if empty, neither will
438 * filterActionData: (object) data for generating the action's HTML
439 * filterActionData.title: label for the radio button
440 * filterActionData.max: (string) maximum date for the pickers, in ISO 8601
441 * datetime format
442 * filterActionData.min: (string) minimum date for the pickers, ISO 8601
443 * datetime
444 */
445 function createActionDateRange(filterName, filterValue, filterActionData) {
446 var action = $('<div class="radio">' +
447 '<input type="radio" name="filter"' +
448 ' value="' + filterName + '" ' +
449 ' id="' + filterName + '">' +
450 '<input type="hidden" name="filter_value" value=""' +
451 ' data-value-for="' + filterName + '">' +
452 '<label class="filter-title"' +
453 ' for="' + filterName + '">' +
454 filterActionData.title +
455 '</label>' +
456 '<input type="text" maxlength="10" class="input-small"' +
457 ' data-date-from-for="' + filterName + '">' +
458 '<span class="help-inline">to</span>' +
459 '<input type="text" maxlength="10" class="input-small"' +
460 ' data-date-to-for="' + filterName + '">' +
461 '<span class="help-inline get-help">(yyyy-mm-dd)</span>' +
462 '</div>');
463
464 var radio = action.find('[type="radio"]');
465 var value = action.find('[data-value-for]');
466
467 // make the datepickers for the range
468 var options = {
469 dateFormat: 'yy-mm-dd',
470 maxDate: new Date(filterActionData.max),
471 minDate: new Date(filterActionData.min)
472 };
473
474 // create date pickers, setting currently-selected from and to
475 // dates
476 var selectedFrom = null;
477 var selectedTo = null;
478
479 var selectedFromAndTo = [];
480 if (filterValue) {
481 selectedFromAndTo = filterValue.split(',');
482 }
483
484 if (selectedFromAndTo.length == 2) {
485 selectedFrom = selectedFromAndTo[0];
486 selectedTo = selectedFromAndTo[1];
487 }
488
489 options.defaultDate = selectedFrom;
490 var inputFrom =
491 action.find('[data-date-from-for]').datepicker(options);
492 inputFrom.val(selectedFrom);
493
494 options.defaultDate = selectedTo;
495 var inputTo =
496 action.find('[data-date-to-for]').datepicker(options);
497 inputTo.val(selectedTo);
498
499 // set filter_value based on date pickers when
500 // one of their values changes
501 var changeHandler = function () {
502 value.val(inputFrom.val() + ',' + inputTo.val());
503 };
504
505 inputFrom.change(changeHandler);
506 inputTo.change(changeHandler);
507
508 // check the associated radio button on clicking a date picker
509 var checkRadio = function () {
510 radio.prop('checked', 'checked');
511 };
512
513 inputFrom.focus(checkRadio);
514 inputTo.focus(checkRadio);
515
516 // selecting a date in a picker constrains the date you can
517 // set in the other picker
518 inputFrom.change(function () {
519 inputTo.datepicker('option', 'minDate', inputFrom.val());
520 });
521
522 inputTo.change(function () {
523 inputFrom.datepicker('option', 'maxDate', inputTo.val());
524 });
525
526 return action;
527 }
528
400 function filterOpenClicked(){ 529 function filterOpenClicked(){
401 var filterName = $(this).data('filter-name'); 530 var filterName = $(this).data('filter-name');
402 531
403 /* We need to pass in the curren search so that the filter counts take 532 /* We need to pass in the current search so that the filter counts take
404 * into account the current search filter 533 * into account the current search term
405 */ 534 */
406 var params = { 535 var params = {
407 'name' : filterName, 536 'name' : filterName,
@@ -443,46 +572,44 @@ function tableInit(ctx){
443 when the filter popup's "Apply" button is clicked, the 572 when the filter popup's "Apply" button is clicked, the
444 value for the radio button which is checked is passed in the 573 value for the radio button which is checked is passed in the
445 querystring and applied to the queryset on the table 574 querystring and applied to the queryset on the table
446 */ 575 */
576 var filterActionRadios = $('#filter-actions-' + ctx.tableName);
447 577
448 var filterActionRadios = $('#filter-actions-'+ctx.tableName); 578 $('#filter-modal-title-' + ctx.tableName).text(filterData.title);
449 579
450 $('#filter-modal-title-'+ctx.tableName).text(filterData.title); 580 filterActionRadios.empty();
451
452 filterActionRadios.text("");
453 581
582 // create a radio button + form elements for each action associated
583 // with the filter on this column of the table
454 for (var i in filterData.filter_actions) { 584 for (var i in filterData.filter_actions) {
455 var filterAction = filterData.filter_actions[i];
456 var action = null; 585 var action = null;
586 var filterActionData = filterData.filter_actions[i];
587 var filterName = filterData.name + ':' +
588 filterActionData.action_name;
457 589
458 if (filterAction.type === 'toggle') { 590 if (filterActionData.type === 'toggle') {
459 var actionTitle = filterAction.title + ' (' + filterAction.count + ')'; 591 action = createActionToggle(filterName, filterActionData);
460 592 }
461 action = $('<label class="radio">' + 593 else if (filterActionData.type === 'daterange') {
462 '<input type="radio" name="filter" value="">' + 594 var filterValue = tableParams.filter_value;
463 '<span class="filter-title">' + 595
464 actionTitle + 596 action = createActionDateRange(
465 '</span>' + 597 filterName,
466 '</label>'); 598 filterValue,
467 599 filterActionData
468 var radioInput = action.children("input"); 600 );
469 if (Number(filterAction.count) == 0) { 601 }
470 radioInput.attr("disabled", "disabled");
471 }
472
473 radioInput.val(filterData.name + ':' + filterAction.action_name);
474 602
475 /* Setup the current selected filter, default to 'all' if 603 if (action) {
476 * no current filter selected. 604 // Setup the current selected filter, default to 'all' if
477 */ 605 // no current filter selected
606 var radioInput = action.children('input[name="filter"]');
478 if ((tableParams.filter && 607 if ((tableParams.filter &&
479 tableParams.filter === radioInput.val()) || 608 tableParams.filter === radioInput.val()) ||
480 filterAction.action_name == 'all') { 609 filterActionData.action_name == 'all') {
481 radioInput.attr("checked", "checked"); 610 radioInput.attr("checked", "checked");
482 } 611 }
483 }
484 612
485 if (action) {
486 filterActionRadios.append(action); 613 filterActionRadios.append(action);
487 } 614 }
488 } 615 }
@@ -571,7 +698,14 @@ function tableInit(ctx){
571 filterBtnActive($(filterBtn), false); 698 filterBtnActive($(filterBtn), false);
572 }); 699 });
573 700
574 tableParams.filter = $(this).find("input[type='radio']:checked").val(); 701 // checked radio button
702 var checkedFilter = $(this).find("input[name='filter']:checked");
703 tableParams.filter = checkedFilter.val();
704
705 // hidden field holding the value for the checked filter
706 var checkedFilterValue = $(this).find("input[data-value-for='" +
707 tableParams.filter + "']");
708 tableParams.filter_value = checkedFilterValue.val();
575 709
576 var filterBtn = $("#" + tableParams.filter.split(":")[0]); 710 var filterBtn = $("#" + tableParams.filter.split(":")[0]);
577 711
diff --git a/bitbake/lib/toaster/toastergui/tablefilter.py b/bitbake/lib/toaster/toastergui/tablefilter.py
index b42fd52865..1ea30da304 100644
--- a/bitbake/lib/toaster/toastergui/tablefilter.py
+++ b/bitbake/lib/toaster/toastergui/tablefilter.py
@@ -18,12 +18,15 @@
18# You should have received a copy of the GNU General Public License along 18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc., 19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21from django.db.models import Q, Max, Min
22from django.utils import dateparse, timezone
21 23
22class TableFilter(object): 24class TableFilter(object):
23 """ 25 """
24 Stores a filter for a named field, and can retrieve the action 26 Stores a filter for a named field, and can retrieve the action
25 requested for that filter 27 requested from the set of actions for that filter
26 """ 28 """
29
27 def __init__(self, name, title): 30 def __init__(self, name, title):
28 self.name = name 31 self.name = name
29 self.title = title 32 self.title = title
@@ -64,42 +67,128 @@ class TableFilter(object):
64 'filter_actions': filter_actions 67 'filter_actions': filter_actions
65 } 68 }
66 69
67class TableFilterActionToggle(object): 70class TableFilterAction(object):
68 """ 71 """
69 Stores a single filter action which will populate one radio button of 72 A filter action which displays in the filter popup for a ToasterTable
70 a ToasterTable filter popup; this filter can either be on or off and 73 and uses an associated QuerysetFilter to filter the queryset for that
71 has no other parameters 74 ToasterTable
72 """ 75 """
73 76
74 def __init__(self, name, title, queryset_filter): 77 def __init__(self, name, title, queryset_filter):
75 self.name = name 78 self.name = name
76 self.title = title 79 self.title = title
77 self.__queryset_filter = queryset_filter 80 self.queryset_filter = queryset_filter
78 self.type = 'toggle' 81
82 # set in subclasses
83 self.type = None
79 84
80 def set_params(self, params): 85 def set_filter_params(self, params):
81 """ 86 """
82 params: (str) a string of extra parameters for the action; 87 params: (str) a string of extra parameters for the action;
83 the structure of this string depends on the type of action; 88 the structure of this string depends on the type of action;
84 it's ignored for a toggle filter action, which is just on or off 89 it's ignored for a toggle filter action, which is just on or off
85 """ 90 """
86 pass 91 if not params:
92 return
87 93
88 def filter(self, queryset): 94 def filter(self, queryset):
89 return self.__queryset_filter.filter(queryset) 95 return self.queryset_filter.filter(queryset)
90 96
91 def to_json(self, queryset): 97 def to_json(self, queryset):
92 """ Dump as a JSON object """ 98 """ Dump as a JSON object """
93 return { 99 return {
94 'title': self.title, 100 'title': self.title,
95 'type': self.type, 101 'type': self.type,
96 'count': self.__queryset_filter.count(queryset) 102 'count': self.queryset_filter.count(queryset)
97 } 103 }
98 104
105class TableFilterActionToggle(TableFilterAction):
106 """
107 A single filter action which will populate one radio button of
108 a ToasterTable filter popup; this filter can either be on or off and
109 has no other parameters
110 """
111
112 def __init__(self, *args):
113 super(TableFilterActionToggle, self).__init__(*args)
114 self.type = 'toggle'
115
116class TableFilterActionDateRange(TableFilterAction):
117 """
118 A filter action which will filter the queryset by a date range.
119 The date range can be set via set_params()
120 """
121
122 def __init__(self, name, title, field, queryset_filter):
123 """
124 field: the field to find the max/min range from in the queryset
125 """
126 super(TableFilterActionDateRange, self).__init__(
127 name,
128 title,
129 queryset_filter
130 )
131
132 self.type = 'daterange'
133 self.field = field
134
135 def set_filter_params(self, params):
136 """
137 params: (str) a string of extra parameters for the filtering
138 in the format "2015-12-09,2015-12-11" (from,to); this is passed in the
139 querystring and used to set the criteria on the QuerysetFilter
140 associated with this action
141 """
142
143 # if params are invalid, return immediately, resetting criteria
144 # on the QuerysetFilter
145 try:
146 from_date_str, to_date_str = params.split(',')
147 except ValueError:
148 self.queryset_filter.set_criteria(None)
149 return
150
151 # one of the values required for the filter is missing, so set
152 # it to the one which was supplied
153 if from_date_str == '':
154 from_date_str = to_date_str
155 elif to_date_str == '':
156 to_date_str = from_date_str
157
158 date_from_naive = dateparse.parse_datetime(from_date_str + ' 00:00:00')
159 date_to_naive = dateparse.parse_datetime(to_date_str + ' 23:59:59')
160
161 tz = timezone.get_default_timezone()
162 date_from = timezone.make_aware(date_from_naive, tz)
163 date_to = timezone.make_aware(date_to_naive, tz)
164
165 args = {}
166 args[self.field + '__gte'] = date_from
167 args[self.field + '__lte'] = date_to
168
169 criteria = Q(**args)
170 self.queryset_filter.set_criteria(criteria)
171
172 def to_json(self, queryset):
173 """ Dump as a JSON object """
174 data = super(TableFilterActionDateRange, self).to_json(queryset)
175
176 # additional data about the date range covered by the queryset's
177 # records, retrieved from its <field> column
178 data['min'] = queryset.aggregate(Min(self.field))[self.field + '__min']
179 data['max'] = queryset.aggregate(Max(self.field))[self.field + '__max']
180
181 # a range filter has a count of None, as the number of records it
182 # will select depends on the date range entered
183 data['count'] = None
184
185 return data
186
99class TableFilterMap(object): 187class TableFilterMap(object):
100 """ 188 """
101 Map from field names to Filter objects for those fields 189 Map from field names to TableFilter objects for those fields
102 """ 190 """
191
103 def __init__(self): 192 def __init__(self):
104 self.__filters = {} 193 self.__filters = {}
105 194
diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 0941637704..06ced52eb1 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -29,7 +29,9 @@ from django.core.urlresolvers import reverse
29from django.views.generic import TemplateView 29from django.views.generic import TemplateView
30import itertools 30import itertools
31 31
32from toastergui.tablefilter import TableFilter, TableFilterActionToggle 32from toastergui.tablefilter import TableFilter
33from toastergui.tablefilter import TableFilterActionToggle
34from toastergui.tablefilter import TableFilterActionDateRange
33 35
34class ProjectFilters(object): 36class ProjectFilters(object):
35 def __init__(self, project_layers): 37 def __init__(self, project_layers):
@@ -1070,6 +1072,7 @@ class BuildsTable(ToasterTable):
1070 help_text='The date and time when the build started', 1072 help_text='The date and time when the build started',
1071 hideable=True, 1073 hideable=True,
1072 orderable=True, 1074 orderable=True,
1075 filter_name='started_on_filter',
1073 static_data_name='started_on', 1076 static_data_name='started_on',
1074 static_data_template=started_on_template) 1077 static_data_template=started_on_template)
1075 1078
@@ -1077,6 +1080,7 @@ class BuildsTable(ToasterTable):
1077 help_text='The date and time when the build finished', 1080 help_text='The date and time when the build finished',
1078 hideable=False, 1081 hideable=False,
1079 orderable=True, 1082 orderable=True,
1083 filter_name='completed_on_filter',
1080 static_data_name='completed_on', 1084 static_data_name='completed_on',
1081 static_data_template=completed_on_template) 1085 static_data_template=completed_on_template)
1082 1086
@@ -1149,6 +1153,38 @@ class BuildsTable(ToasterTable):
1149 outcome_filter.add_action(failed_builds_filter_action) 1153 outcome_filter.add_action(failed_builds_filter_action)
1150 self.add_filter(outcome_filter) 1154 self.add_filter(outcome_filter)
1151 1155
1156 # started on
1157 started_on_filter = TableFilter(
1158 'started_on_filter',
1159 'Filter by date when build was started'
1160 )
1161
1162 by_started_date_range_filter_action = TableFilterActionDateRange(
1163 'date_range',
1164 'Build date range',
1165 'started_on',
1166 QuerysetFilter()
1167 )
1168
1169 started_on_filter.add_action(by_started_date_range_filter_action)
1170 self.add_filter(started_on_filter)
1171
1172 # completed on
1173 completed_on_filter = TableFilter(
1174 'completed_on_filter',
1175 'Filter by date when build was completed'
1176 )
1177
1178 by_completed_date_range_filter_action = TableFilterActionDateRange(
1179 'date_range',
1180 'Build date range',
1181 'completed_on',
1182 QuerysetFilter()
1183 )
1184
1185 completed_on_filter.add_action(by_completed_date_range_filter_action)
1186 self.add_filter(completed_on_filter)
1187
1152 # failed tasks 1188 # failed tasks
1153 failed_tasks_filter = TableFilter( 1189 failed_tasks_filter = TableFilter(
1154 'failed_tasks_filter', 1190 'failed_tasks_filter',
diff --git a/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html b/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html
index f7604fd7a4..2e32edb100 100644
--- a/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html
+++ b/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html
@@ -1,4 +1,13 @@
1{% extends 'base.html' %} 1{% extends 'base.html' %}
2{% load static %}
3
4{% block extraheadcontent %}
5 <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
6 <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
7 <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
8 <script src="{% static 'js/jquery-ui.min.js' %}">
9 </script>
10{% endblock %}
2 11
3{% block title %} All builds - Toaster {% endblock %} 12{% block title %} All builds - Toaster {% endblock %}
4 13
@@ -34,29 +43,6 @@
34 43
35 titleElt.text(title); 44 titleElt.text(title);
36 }); 45 });
37
38 /* {% if last_date_from and last_date_to %}
39 // TODO initialize the date range controls;
40 // this will need to be added via ToasterTable
41 date_init(
42 "started_on",
43 "{{last_date_from}}",
44 "{{last_date_to}}",
45 "{{dateMin_started_on}}",
46 "{{dateMax_started_on}}",
47 "{{daterange_selected}}"
48 );
49
50 date_init(
51 "completed_on",
52 "{{last_date_from}}",
53 "{{last_date_to}}",
54 "{{dateMin_completed_on}}",
55 "{{dateMax_completed_on}}",
56 "{{daterange_selected}}"
57 );
58 {% endif %}
59 */
60 }); 46 });
61 </script> 47 </script>
62{% endblock %} 48{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/widgets.py b/bitbake/lib/toaster/toastergui/widgets.py
index 8790340db9..47de30d631 100644
--- a/bitbake/lib/toaster/toastergui/widgets.py
+++ b/bitbake/lib/toaster/toastergui/widgets.py
@@ -183,13 +183,13 @@ class ToasterTable(TemplateView):
183 183
184 return template.render(context) 184 return template.render(context)
185 185
186 def apply_filter(self, filters, **kwargs): 186 def apply_filter(self, filters, filter_value, **kwargs):
187 """ 187 """
188 Apply a filter submitted in the querystring to the ToasterTable 188 Apply a filter submitted in the querystring to the ToasterTable
189 189
190 filters: (str) in the format: 190 filters: (str) in the format:
191 '<filter name>:<action name>!<action params>' 191 '<filter name>:<action name>'
192 where <action params> is optional 192 filter_value: (str) parameters to pass to the named filter
193 193
194 <filter name> and <action name> are used to look up the correct filter 194 <filter name> and <action name> are used to look up the correct filter
195 in the ToasterTable's filter map; the <action params> are set on 195 in the ToasterTable's filter map; the <action params> are set on
@@ -199,15 +199,8 @@ class ToasterTable(TemplateView):
199 self.setup_filters(**kwargs) 199 self.setup_filters(**kwargs)
200 200
201 try: 201 try:
202 filter_name, action_name_and_params = filters.split(':') 202 filter_name, action_name = filters.split(':')
203 203 action_params = urllib.unquote_plus(filter_value)
204 action_name = None
205 action_params = None
206 if re.search('!', action_name_and_params):
207 action_name, action_params = action_name_and_params.split('!')
208 action_params = urllib.unquote_plus(action_params)
209 else:
210 action_name = action_name_and_params
211 except ValueError: 204 except ValueError:
212 return 205 return
213 206
@@ -217,7 +210,7 @@ class ToasterTable(TemplateView):
217 try: 210 try:
218 table_filter = self.filter_map.get_filter(filter_name) 211 table_filter = self.filter_map.get_filter(filter_name)
219 action = table_filter.get_action(action_name) 212 action = table_filter.get_action(action_name)
220 action.set_params(action_params) 213 action.set_filter_params(action_params)
221 self.queryset = action.filter(self.queryset) 214 self.queryset = action.filter(self.queryset)
222 except KeyError: 215 except KeyError:
223 # pass it to the user - programming error here 216 # pass it to the user - programming error here
@@ -247,13 +240,20 @@ class ToasterTable(TemplateView):
247 240
248 241
249 def get_data(self, request, **kwargs): 242 def get_data(self, request, **kwargs):
250 """Returns the data for the page requested with the specified 243 """
251 parameters applied""" 244 Returns the data for the page requested with the specified
245 parameters applied
246
247 filters: filter and action name, e.g. "outcome:build_succeeded"
248 filter_value: value to pass to the named filter+action, e.g. "on"
249 (for a toggle filter) or "2015-12-11,2015-12-12" (for a date range filter)
250 """
252 251
253 page_num = request.GET.get("page", 1) 252 page_num = request.GET.get("page", 1)
254 limit = request.GET.get("limit", 10) 253 limit = request.GET.get("limit", 10)
255 search = request.GET.get("search", None) 254 search = request.GET.get("search", None)
256 filters = request.GET.get("filter", None) 255 filters = request.GET.get("filter", None)
256 filter_value = request.GET.get("filter_value", "on")
257 orderby = request.GET.get("orderby", None) 257 orderby = request.GET.get("orderby", None)
258 nocache = request.GET.get("nocache", None) 258 nocache = request.GET.get("nocache", None)
259 259
@@ -285,7 +285,7 @@ class ToasterTable(TemplateView):
285 if search: 285 if search:
286 self.apply_search(search) 286 self.apply_search(search)
287 if filters: 287 if filters:
288 self.apply_filter(filters, **kwargs) 288 self.apply_filter(filters, filter_value, **kwargs)
289 if orderby: 289 if orderby:
290 self.apply_orderby(orderby) 290 self.apply_orderby(orderby)
291 291