diff options
author | Elliot Smith <elliot.smith@intel.com> | 2016-01-15 13:00:53 +0200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-01-15 16:30:00 +0000 |
commit | f8d383d87f0b9d4a4c9ae7b1a6c8ceebf90ef9b0 (patch) | |
tree | cda7dc3eb23a5b6a97241c965bbd9b0dcddc02eb /bitbake | |
parent | b929889cdd4a36846f9569d89fabd9987e94b39e (diff) | |
download | poky-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>
Diffstat (limited to 'bitbake')
-rw-r--r-- | bitbake/lib/toaster/toastergui/querysetfilter.py | 3 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/table.js | 196 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/tablefilter.py | 113 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/tables.py | 38 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/builds-toastertable.html | 32 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/widgets.py | 32 |
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. |
21 | from django.db.models import Q, Max, Min | ||
22 | from django.utils import dateparse, timezone | ||
21 | 23 | ||
22 | class TableFilter(object): | 24 | class 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 | ||
67 | class TableFilterActionToggle(object): | 70 | class 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 | ||
105 | class 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 | |||
116 | class 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 | |||
99 | class TableFilterMap(object): | 187 | class 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 | |||
29 | from django.views.generic import TemplateView | 29 | from django.views.generic import TemplateView |
30 | import itertools | 30 | import itertools |
31 | 31 | ||
32 | from toastergui.tablefilter import TableFilter, TableFilterActionToggle | 32 | from toastergui.tablefilter import TableFilter |
33 | from toastergui.tablefilter import TableFilterActionToggle | ||
34 | from toastergui.tablefilter import TableFilterActionDateRange | ||
33 | 35 | ||
34 | class ProjectFilters(object): | 36 | class 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 | ||