summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElliot Smith <elliot.smith@intel.com>2016-01-15 13:00:54 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-01-15 16:30:00 +0000
commit33b011c1589519db8176c9f5a4abb540698902e6 (patch)
tree104367160098dffb4c05c2120db1c2c8ef470449
parentf8d383d87f0b9d4a4c9ae7b1a6c8ceebf90ef9b0 (diff)
downloadpoky-33b011c1589519db8176c9f5a4abb540698902e6.tar.gz
bitbake: toastergui: implement "today" and "yesterday" filters
Add the "today" and "yesterday" filters to the started_on and completed_on columns in the builds table. During this work, some minor adjustments were made to the behaviour of the builds table: * Amend filter action variable names so they're more succinct. * Retain order in which actions are added to a filter, as this ordering is used in the UI when displaying the filter actions. * Always show the table chrome, otherwise it's not possible to edit the columns shown until there are 10 or more results. * Because date range searches may return no results, make sure that the search bar and "show all results" link are visible when the query returns no results. [YOCTO #8738] (Bitbake rev: f17cfa009e58833e0e55884fa04de8abd522b6bc) 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.py4
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/table.js56
-rw-r--r--bitbake/lib/toaster/toastergui/tablefilter.py140
-rw-r--r--bitbake/lib/toaster/toastergui/tables.py87
-rw-r--r--bitbake/lib/toaster/toastergui/templates/builds-toastertable.html2
-rw-r--r--bitbake/lib/toaster/toastergui/templates/toastertable.html7
6 files changed, 212 insertions, 84 deletions
diff --git a/bitbake/lib/toaster/toastergui/querysetfilter.py b/bitbake/lib/toaster/toastergui/querysetfilter.py
index efa8507050..10cc988bce 100644
--- a/bitbake/lib/toaster/toastergui/querysetfilter.py
+++ b/bitbake/lib/toaster/toastergui/querysetfilter.py
@@ -22,7 +22,3 @@ class QuerysetFilter(object):
22 return queryset.filter(self.criteria) 22 return queryset.filter(self.criteria)
23 else: 23 else:
24 return queryset 24 return queryset
25
26 def count(self, queryset):
27 """ Returns a count of the elements in the filtered queryset """
28 return self.filter(queryset).count()
diff --git a/bitbake/lib/toaster/toastergui/static/js/table.js b/bitbake/lib/toaster/toastergui/static/js/table.js
index b0a8ffb8f9..afe16b5e1b 100644
--- a/bitbake/lib/toaster/toastergui/static/js/table.js
+++ b/bitbake/lib/toaster/toastergui/static/js/table.js
@@ -71,22 +71,11 @@ function tableInit(ctx){
71 71
72 if (tableData.total === 0){ 72 if (tableData.total === 0){
73 tableContainer.hide(); 73 tableContainer.hide();
74 /* If we were searching show the new search bar and return */ 74 $("#new-search-input-"+ctx.tableName).val(tableParams.search);
75 if (tableParams.search){ 75 $("#no-results-"+ctx.tableName).show();
76 $("#new-search-input-"+ctx.tableName).val(tableParams.search);
77 $("#no-results-"+ctx.tableName).show();
78 }
79 table.trigger("table-done", [tableData.total, tableParams]); 76 table.trigger("table-done", [tableData.total, tableParams]);
80 77
81 return; 78 return;
82
83 /* We don't want to clutter the place with the table chrome if there
84 * are only a few results */
85 } else if (tableData.total <= 10 &&
86 !tableParams.filter &&
87 !tableParams.search){
88 $("#table-chrome-"+ctx.tableName).hide();
89 pagination.hide();
90 } else { 79 } else {
91 tableContainer.show(); 80 tableContainer.show();
92 $("#no-results-"+ctx.tableName).hide(); 81 $("#no-results-"+ctx.tableName).hide();
@@ -399,13 +388,14 @@ function tableInit(ctx){
399 388
400 /** 389 /**
401 * Create the DOM/JS for the client side of a TableFilterActionToggle 390 * Create the DOM/JS for the client side of a TableFilterActionToggle
391 * or TableFilterActionDay
402 * 392 *
403 * filterName: (string) internal name for the filter action 393 * filterName: (string) internal name for the filter action
404 * filterActionData: (object) 394 * filterActionData: (object)
405 * filterActionData.count: (number) The number of items this filter will 395 * filterActionData.count: (number) The number of items this filter will
406 * show when selected 396 * show when selected
407 */ 397 */
408 function createActionToggle(filterName, filterActionData) { 398 function createActionRadio(filterName, filterActionData) {
409 var actionStr = '<div class="radio">' + 399 var actionStr = '<div class="radio">' +
410 '<input type="radio" name="filter"' + 400 '<input type="radio" name="filter"' +
411 ' value="' + filterName + '"'; 401 ' value="' + filterName + '"';
@@ -471,8 +461,7 @@ function tableInit(ctx){
471 minDate: new Date(filterActionData.min) 461 minDate: new Date(filterActionData.min)
472 }; 462 };
473 463
474 // create date pickers, setting currently-selected from and to 464 // create date pickers, setting currently-selected from and to dates
475 // dates
476 var selectedFrom = null; 465 var selectedFrom = null;
477 var selectedTo = null; 466 var selectedTo = null;
478 467
@@ -496,6 +485,20 @@ function tableInit(ctx){
496 action.find('[data-date-to-for]').datepicker(options); 485 action.find('[data-date-to-for]').datepicker(options);
497 inputTo.val(selectedTo); 486 inputTo.val(selectedTo);
498 487
488 // if the radio button is checked and one or both of the datepickers are
489 // empty, populate them with today's date
490 radio.change(function () {
491 var now = new Date();
492
493 if (inputFrom.val() === '') {
494 inputFrom.datepicker('setDate', now);
495 }
496
497 if (inputTo.val() === '') {
498 inputTo.datepicker('setDate', now);
499 }
500 });
501
499 // set filter_value based on date pickers when 502 // set filter_value based on date pickers when
500 // one of their values changes 503 // one of their values changes
501 var changeHandler = function () { 504 var changeHandler = function () {
@@ -553,7 +556,8 @@ function tableInit(ctx){
553 { 556 {
554 title: '<label for radio button inside the popup>', 557 title: '<label for radio button inside the popup>',
555 name: '<name of the filter action>', 558 name: '<name of the filter action>',
556 count: <number of items this filter will show> 559 count: <number of items this filter will show>,
560 ... additional data for the action ...
557 } 561 }
558 ] 562 ]
559 } 563 }
@@ -567,11 +571,12 @@ function tableInit(ctx){
567 filter 571 filter
568 572
569 the filterName is set on the column filter icon, and corresponds 573 the filterName is set on the column filter icon, and corresponds
570 to a value in the table's filters property 574 to a value in the table's filter map
571 575
572 when the filter popup's "Apply" button is clicked, the 576 when the filter popup's "Apply" button is clicked, the
573 value for the radio button which is checked is passed in the 577 value for the radio button which is checked is passed in the
574 querystring and applied to the queryset on the table 578 querystring, along with a filter_value, and applied to the
579 queryset on the table
575 */ 580 */
576 var filterActionRadios = $('#filter-actions-' + ctx.tableName); 581 var filterActionRadios = $('#filter-actions-' + ctx.tableName);
577 582
@@ -587,10 +592,12 @@ function tableInit(ctx){
587 var filterName = filterData.name + ':' + 592 var filterName = filterData.name + ':' +
588 filterActionData.action_name; 593 filterActionData.action_name;
589 594
590 if (filterActionData.type === 'toggle') { 595 if (filterActionData.type === 'toggle' ||
591 action = createActionToggle(filterName, filterActionData); 596 filterActionData.type === 'day') {
597 action = createActionRadio(filterName, filterActionData);
592 } 598 }
593 else if (filterActionData.type === 'daterange') { 599 else if (filterActionData.type === 'daterange') {
600 // current values for the from/to dates
594 var filterValue = tableParams.filter_value; 601 var filterValue = tableParams.filter_value;
595 602
596 action = createActionDateRange( 603 action = createActionDateRange(
@@ -601,7 +608,7 @@ function tableInit(ctx){
601 } 608 }
602 609
603 if (action) { 610 if (action) {
604 // Setup the current selected filter, default to 'all' if 611 // Setup the current selected filter; default to 'all' if
605 // no current filter selected 612 // no current filter selected
606 var radioInput = action.children('input[name="filter"]'); 613 var radioInput = action.children('input[name="filter"]');
607 if ((tableParams.filter && 614 if ((tableParams.filter &&
@@ -707,13 +714,12 @@ function tableInit(ctx){
707 tableParams.filter + "']"); 714 tableParams.filter + "']");
708 tableParams.filter_value = checkedFilterValue.val(); 715 tableParams.filter_value = checkedFilterValue.val();
709 716
710 var filterBtn = $("#" + tableParams.filter.split(":")[0]);
711
712 /* All === remove filter */ 717 /* All === remove filter */
713 if (tableParams.filter.match(":all$")) { 718 if (tableParams.filter.match(":all$")) {
714 tableParams.filter = null; 719 tableParams.filter = null;
715 filterBtnActive(filterBtn, false); 720 tableParams.filter_value = null;
716 } else { 721 } else {
722 var filterBtn = $("#" + tableParams.filter.split(":")[0]);
717 filterBtnActive(filterBtn, true); 723 filterBtnActive(filterBtn, true);
718 } 724 }
719 725
diff --git a/bitbake/lib/toaster/toastergui/tablefilter.py b/bitbake/lib/toaster/toastergui/tablefilter.py
index 1ea30da304..bd8decd0e3 100644
--- a/bitbake/lib/toaster/toastergui/tablefilter.py
+++ b/bitbake/lib/toaster/toastergui/tablefilter.py
@@ -18,13 +18,18 @@
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
21from django.db.models import Q, Max, Min 22from django.db.models import Q, Max, Min
22from django.utils import dateparse, timezone 23from django.utils import dateparse, timezone
24from datetime import timedelta
25from querysetfilter import QuerysetFilter
23 26
24class TableFilter(object): 27class TableFilter(object):
25 """ 28 """
26 Stores a filter for a named field, and can retrieve the action 29 Stores a filter for a named field, and can retrieve the action
27 requested from the set of actions for that filter 30 requested from the set of actions for that filter;
31 the order in which actions are added governs the order in which they
32 are returned in the JSON for the filter
28 """ 33 """
29 34
30 def __init__(self, name, title): 35 def __init__(self, name, title):
@@ -32,7 +37,11 @@ class TableFilter(object):
32 self.title = title 37 self.title = title
33 self.__filter_action_map = {} 38 self.__filter_action_map = {}
34 39
40 # retains the ordering of actions
41 self.__filter_action_keys = []
42
35 def add_action(self, action): 43 def add_action(self, action):
44 self.__filter_action_keys.append(action.name)
36 self.__filter_action_map[action.name] = action 45 self.__filter_action_map[action.name] = action
37 46
38 def get_action(self, action_name): 47 def get_action(self, action_name):
@@ -56,7 +65,8 @@ class TableFilter(object):
56 }) 65 })
57 66
58 # add other filter actions 67 # add other filter actions
59 for action_name, filter_action in self.__filter_action_map.iteritems(): 68 for action_name in self.__filter_action_keys:
69 filter_action = self.__filter_action_map[action_name]
60 obj = filter_action.to_json(queryset) 70 obj = filter_action.to_json(queryset)
61 obj['action_name'] = action_name 71 obj['action_name'] = action_name
62 filter_actions.append(obj) 72 filter_actions.append(obj)
@@ -67,6 +77,40 @@ class TableFilter(object):
67 'filter_actions': filter_actions 77 'filter_actions': filter_actions
68 } 78 }
69 79
80class TableFilterQueryHelper(object):
81 def dateStringsToQ(self, field_name, date_from_str, date_to_str):
82 """
83 Convert the date strings from_date_str and to_date_str into a
84 set of args in the form
85
86 {'<field_name>__gte': <date from>, '<field_name>__lte': <date to>}
87
88 where date_from and date_to are Django-timezone-aware dates; then
89 convert that into a Django Q object
90
91 Returns the Q object based on those criteria
92 """
93
94 # one of the values required for the filter is missing, so set
95 # it to the one which was supplied
96 if date_from_str == '':
97 date_from_str = date_to_str
98 elif date_to_str == '':
99 date_to_str = date_from_str
100
101 date_from_naive = dateparse.parse_datetime(date_from_str + ' 00:00:00')
102 date_to_naive = dateparse.parse_datetime(date_to_str + ' 23:59:59')
103
104 tz = timezone.get_default_timezone()
105 date_from = timezone.make_aware(date_from_naive, tz)
106 date_to = timezone.make_aware(date_to_naive, tz)
107
108 args = {}
109 args[field_name + '__gte'] = date_from
110 args[field_name + '__lte'] = date_to
111
112 return Q(**args)
113
70class TableFilterAction(object): 114class TableFilterAction(object):
71 """ 115 """
72 A filter action which displays in the filter popup for a ToasterTable 116 A filter action which displays in the filter popup for a ToasterTable
@@ -99,7 +143,7 @@ class TableFilterAction(object):
99 return { 143 return {
100 'title': self.title, 144 'title': self.title,
101 'type': self.type, 145 'type': self.type,
102 'count': self.queryset_filter.count(queryset) 146 'count': self.filter(queryset).count()
103 } 147 }
104 148
105class TableFilterActionToggle(TableFilterAction): 149class TableFilterActionToggle(TableFilterAction):
@@ -113,15 +157,70 @@ class TableFilterActionToggle(TableFilterAction):
113 super(TableFilterActionToggle, self).__init__(*args) 157 super(TableFilterActionToggle, self).__init__(*args)
114 self.type = 'toggle' 158 self.type = 'toggle'
115 159
160class TableFilterActionDay(TableFilterAction):
161 """
162 A filter action which filters according to the named datetime field and a
163 string representing a day ("today" or "yesterday")
164 """
165
166 TODAY = 'today'
167 YESTERDAY = 'yesterday'
168
169 def __init__(self, name, title, field, day,
170 queryset_filter = QuerysetFilter(), query_helper = TableFilterQueryHelper()):
171 """
172 field: (string) the datetime field to filter by
173 day: (string) "today" or "yesterday"
174 """
175 super(TableFilterActionDay, self).__init__(
176 name,
177 title,
178 queryset_filter
179 )
180 self.type = 'day'
181 self.field = field
182 self.day = day
183 self.query_helper = query_helper
184
185 def filter(self, queryset):
186 """
187 Apply the day filtering before returning the queryset;
188 this is done here as the value of the filter criteria changes
189 depending on when the filtering is applied
190 """
191
192 criteria = None
193 date_str = None
194 now = timezone.now()
195
196 if self.day == self.YESTERDAY:
197 increment = timedelta(days=1)
198 wanted_date = now - increment
199 else:
200 wanted_date = now
201
202 wanted_date_str = wanted_date.strftime('%Y-%m-%d')
203
204 criteria = self.query_helper.dateStringsToQ(
205 self.field,
206 wanted_date_str,
207 wanted_date_str
208 )
209
210 self.queryset_filter.set_criteria(criteria)
211
212 return self.queryset_filter.filter(queryset)
213
116class TableFilterActionDateRange(TableFilterAction): 214class TableFilterActionDateRange(TableFilterAction):
117 """ 215 """
118 A filter action which will filter the queryset by a date range. 216 A filter action which will filter the queryset by a date range.
119 The date range can be set via set_params() 217 The date range can be set via set_params()
120 """ 218 """
121 219
122 def __init__(self, name, title, field, queryset_filter): 220 def __init__(self, name, title, field,
221 queryset_filter = QuerysetFilter(), query_helper = TableFilterQueryHelper()):
123 """ 222 """
124 field: the field to find the max/min range from in the queryset 223 field: (string) the field to find the max/min range from in the queryset
125 """ 224 """
126 super(TableFilterActionDateRange, self).__init__( 225 super(TableFilterActionDateRange, self).__init__(
127 name, 226 name,
@@ -131,9 +230,13 @@ class TableFilterActionDateRange(TableFilterAction):
131 230
132 self.type = 'daterange' 231 self.type = 'daterange'
133 self.field = field 232 self.field = field
233 self.query_helper = query_helper
134 234
135 def set_filter_params(self, params): 235 def set_filter_params(self, params):
136 """ 236 """
237 This filter depends on the user selecting some input, so it needs
238 to have its parameters set before its queryset is filtered
239
137 params: (str) a string of extra parameters for the filtering 240 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 241 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 242 querystring and used to set the criteria on the QuerysetFilter
@@ -143,30 +246,18 @@ class TableFilterActionDateRange(TableFilterAction):
143 # if params are invalid, return immediately, resetting criteria 246 # if params are invalid, return immediately, resetting criteria
144 # on the QuerysetFilter 247 # on the QuerysetFilter
145 try: 248 try:
146 from_date_str, to_date_str = params.split(',') 249 date_from_str, date_to_str = params.split(',')
147 except ValueError: 250 except ValueError:
148 self.queryset_filter.set_criteria(None) 251 self.queryset_filter.set_criteria(None)
149 return 252 return
150 253
151 # one of the values required for the filter is missing, so set 254 # one of the values required for the filter is missing, so set
152 # it to the one which was supplied 255 # it to the one which was supplied
153 if from_date_str == '': 256 criteria = self.query_helper.dateStringsToQ(
154 from_date_str = to_date_str 257 self.field,
155 elif to_date_str == '': 258 date_from_str,
156 to_date_str = from_date_str 259 date_to_str
157 260 )
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) 261 self.queryset_filter.set_criteria(criteria)
171 262
172 def to_json(self, queryset): 263 def to_json(self, queryset):
@@ -179,7 +270,8 @@ class TableFilterActionDateRange(TableFilterAction):
179 data['max'] = queryset.aggregate(Max(self.field))[self.field + '__max'] 270 data['max'] = queryset.aggregate(Max(self.field))[self.field + '__max']
180 271
181 # a range filter has a count of None, as the number of records it 272 # a range filter has a count of None, as the number of records it
182 # will select depends on the date range entered 273 # will select depends on the date range entered and we don't know
274 # that ahead of time
183 data['count'] = None 275 data['count'] = None
184 276
185 return data 277 return data
diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 06ced52eb1..58abe36b05 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -32,6 +32,7 @@ import itertools
32from toastergui.tablefilter import TableFilter 32from toastergui.tablefilter import TableFilter
33from toastergui.tablefilter import TableFilterActionToggle 33from toastergui.tablefilter import TableFilterActionToggle
34from toastergui.tablefilter import TableFilterActionDateRange 34from toastergui.tablefilter import TableFilterActionDateRange
35from toastergui.tablefilter import TableFilterActionDay
35 36
36class ProjectFilters(object): 37class ProjectFilters(object):
37 def __init__(self, project_layers): 38 def __init__(self, project_layers):
@@ -65,20 +66,20 @@ class LayersTable(ToasterTable):
65 66
66 criteria = Q(projectlayer__in=self.project_layers) 67 criteria = Q(projectlayer__in=self.project_layers)
67 68
68 in_project_filter_action = TableFilterActionToggle( 69 in_project_action = TableFilterActionToggle(
69 "in_project", 70 "in_project",
70 "Layers added to this project", 71 "Layers added to this project",
71 QuerysetFilter(criteria) 72 QuerysetFilter(criteria)
72 ) 73 )
73 74
74 not_in_project_filter_action = TableFilterActionToggle( 75 not_in_project_action = TableFilterActionToggle(
75 "not_in_project", 76 "not_in_project",
76 "Layers not added to this project", 77 "Layers not added to this project",
77 QuerysetFilter(~criteria) 78 QuerysetFilter(~criteria)
78 ) 79 )
79 80
80 in_current_project_filter.add_action(in_project_filter_action) 81 in_current_project_filter.add_action(in_project_action)
81 in_current_project_filter.add_action(not_in_project_filter_action) 82 in_current_project_filter.add_action(not_in_project_action)
82 self.add_filter(in_current_project_filter) 83 self.add_filter(in_current_project_filter)
83 84
84 def setup_queryset(self, *args, **kwargs): 85 def setup_queryset(self, *args, **kwargs):
@@ -221,20 +222,20 @@ class MachinesTable(ToasterTable):
221 "Filter by project machines" 222 "Filter by project machines"
222 ) 223 )
223 224
224 in_project_filter_action = TableFilterActionToggle( 225 in_project_action = TableFilterActionToggle(
225 "in_project", 226 "in_project",
226 "Machines provided by layers added to this project", 227 "Machines provided by layers added to this project",
227 project_filters.in_project 228 project_filters.in_project
228 ) 229 )
229 230
230 not_in_project_filter_action = TableFilterActionToggle( 231 not_in_project_action = TableFilterActionToggle(
231 "not_in_project", 232 "not_in_project",
232 "Machines provided by layers not added to this project", 233 "Machines provided by layers not added to this project",
233 project_filters.not_in_project 234 project_filters.not_in_project
234 ) 235 )
235 236
236 in_current_project_filter.add_action(in_project_filter_action) 237 in_current_project_filter.add_action(in_project_action)
237 in_current_project_filter.add_action(not_in_project_filter_action) 238 in_current_project_filter.add_action(not_in_project_action)
238 self.add_filter(in_current_project_filter) 239 self.add_filter(in_current_project_filter)
239 240
240 def setup_queryset(self, *args, **kwargs): 241 def setup_queryset(self, *args, **kwargs):
@@ -354,20 +355,20 @@ class RecipesTable(ToasterTable):
354 'Filter by project recipes' 355 'Filter by project recipes'
355 ) 356 )
356 357
357 in_project_filter_action = TableFilterActionToggle( 358 in_project_action = TableFilterActionToggle(
358 'in_project', 359 'in_project',
359 'Recipes provided by layers added to this project', 360 'Recipes provided by layers added to this project',
360 project_filters.in_project 361 project_filters.in_project
361 ) 362 )
362 363
363 not_in_project_filter_action = TableFilterActionToggle( 364 not_in_project_action = TableFilterActionToggle(
364 'not_in_project', 365 'not_in_project',
365 'Recipes provided by layers not added to this project', 366 'Recipes provided by layers not added to this project',
366 project_filters.not_in_project 367 project_filters.not_in_project
367 ) 368 )
368 369
369 table_filter.add_action(in_project_filter_action) 370 table_filter.add_action(in_project_action)
370 table_filter.add_action(not_in_project_filter_action) 371 table_filter.add_action(not_in_project_action)
371 self.add_filter(table_filter) 372 self.add_filter(table_filter)
372 373
373 def setup_queryset(self, *args, **kwargs): 374 def setup_queryset(self, *args, **kwargs):
@@ -1137,20 +1138,20 @@ class BuildsTable(ToasterTable):
1137 'Filter builds by outcome' 1138 'Filter builds by outcome'
1138 ) 1139 )
1139 1140
1140 successful_builds_filter_action = TableFilterActionToggle( 1141 successful_builds_action = TableFilterActionToggle(
1141 'successful_builds', 1142 'successful_builds',
1142 'Successful builds', 1143 'Successful builds',
1143 QuerysetFilter(Q(outcome=Build.SUCCEEDED)) 1144 QuerysetFilter(Q(outcome=Build.SUCCEEDED))
1144 ) 1145 )
1145 1146
1146 failed_builds_filter_action = TableFilterActionToggle( 1147 failed_builds_action = TableFilterActionToggle(
1147 'failed_builds', 1148 'failed_builds',
1148 'Failed builds', 1149 'Failed builds',
1149 QuerysetFilter(Q(outcome=Build.FAILED)) 1150 QuerysetFilter(Q(outcome=Build.FAILED))
1150 ) 1151 )
1151 1152
1152 outcome_filter.add_action(successful_builds_filter_action) 1153 outcome_filter.add_action(successful_builds_action)
1153 outcome_filter.add_action(failed_builds_filter_action) 1154 outcome_filter.add_action(failed_builds_action)
1154 self.add_filter(outcome_filter) 1155 self.add_filter(outcome_filter)
1155 1156
1156 # started on 1157 # started on
@@ -1159,14 +1160,29 @@ class BuildsTable(ToasterTable):
1159 'Filter by date when build was started' 1160 'Filter by date when build was started'
1160 ) 1161 )
1161 1162
1162 by_started_date_range_filter_action = TableFilterActionDateRange( 1163 started_today_action = TableFilterActionDay(
1164 'today',
1165 'Today\'s builds',
1166 'started_on',
1167 'today'
1168 )
1169
1170 started_yesterday_action = TableFilterActionDay(
1171 'yesterday',
1172 'Yesterday\'s builds',
1173 'started_on',
1174 'yesterday'
1175 )
1176
1177 by_started_date_range_action = TableFilterActionDateRange(
1163 'date_range', 1178 'date_range',
1164 'Build date range', 1179 'Build date range',
1165 'started_on', 1180 'started_on'
1166 QuerysetFilter()
1167 ) 1181 )
1168 1182
1169 started_on_filter.add_action(by_started_date_range_filter_action) 1183 started_on_filter.add_action(started_today_action)
1184 started_on_filter.add_action(started_yesterday_action)
1185 started_on_filter.add_action(by_started_date_range_action)
1170 self.add_filter(started_on_filter) 1186 self.add_filter(started_on_filter)
1171 1187
1172 # completed on 1188 # completed on
@@ -1175,14 +1191,29 @@ class BuildsTable(ToasterTable):
1175 'Filter by date when build was completed' 1191 'Filter by date when build was completed'
1176 ) 1192 )
1177 1193
1178 by_completed_date_range_filter_action = TableFilterActionDateRange( 1194 completed_today_action = TableFilterActionDay(
1195 'today',
1196 'Today\'s builds',
1197 'completed_on',
1198 'today'
1199 )
1200
1201 completed_yesterday_action = TableFilterActionDay(
1202 'yesterday',
1203 'Yesterday\'s builds',
1204 'completed_on',
1205 'yesterday'
1206 )
1207
1208 by_completed_date_range_action = TableFilterActionDateRange(
1179 'date_range', 1209 'date_range',
1180 'Build date range', 1210 'Build date range',
1181 'completed_on', 1211 'completed_on'
1182 QuerysetFilter()
1183 ) 1212 )
1184 1213
1185 completed_on_filter.add_action(by_completed_date_range_filter_action) 1214 completed_on_filter.add_action(completed_today_action)
1215 completed_on_filter.add_action(completed_yesterday_action)
1216 completed_on_filter.add_action(by_completed_date_range_action)
1186 self.add_filter(completed_on_filter) 1217 self.add_filter(completed_on_filter)
1187 1218
1188 # failed tasks 1219 # failed tasks
@@ -1193,18 +1224,18 @@ class BuildsTable(ToasterTable):
1193 1224
1194 criteria = Q(task_build__outcome=Task.OUTCOME_FAILED) 1225 criteria = Q(task_build__outcome=Task.OUTCOME_FAILED)
1195 1226
1196 with_failed_tasks_filter_action = TableFilterActionToggle( 1227 with_failed_tasks_action = TableFilterActionToggle(
1197 'with_failed_tasks', 1228 'with_failed_tasks',
1198 'Builds with failed tasks', 1229 'Builds with failed tasks',
1199 QuerysetFilter(criteria) 1230 QuerysetFilter(criteria)
1200 ) 1231 )
1201 1232
1202 without_failed_tasks_filter_action = TableFilterActionToggle( 1233 without_failed_tasks_action = TableFilterActionToggle(
1203 'without_failed_tasks', 1234 'without_failed_tasks',
1204 'Builds without failed tasks', 1235 'Builds without failed tasks',
1205 QuerysetFilter(~criteria) 1236 QuerysetFilter(~criteria)
1206 ) 1237 )
1207 1238
1208 failed_tasks_filter.add_action(with_failed_tasks_filter_action) 1239 failed_tasks_filter.add_action(with_failed_tasks_action)
1209 failed_tasks_filter.add_action(without_failed_tasks_filter_action) 1240 failed_tasks_filter.add_action(without_failed_tasks_action)
1210 self.add_filter(failed_tasks_filter) 1241 self.add_filter(failed_tasks_filter)
diff --git a/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html b/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html
index 2e32edb100..bf13a66bd1 100644
--- a/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html
+++ b/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html
@@ -18,7 +18,7 @@
18 {% include 'mrb_section.html' %} 18 {% include 'mrb_section.html' %}
19 {% endwith %} 19 {% endwith %}
20 20
21 <h1 class="page-header top-air" data-role="page-title"></h1> 21 <h1 class="page-header top-air" data-role="page-title"></h1>
22 22
23 {% url 'builds' as xhr_table_url %} 23 {% url 'builds' as xhr_table_url %}
24 {% include 'toastertable.html' %} 24 {% include 'toastertable.html' %}
diff --git a/bitbake/lib/toaster/toastergui/templates/toastertable.html b/bitbake/lib/toaster/toastergui/templates/toastertable.html
index 98a715f27d..f0a3aedb74 100644
--- a/bitbake/lib/toaster/toastergui/templates/toastertable.html
+++ b/bitbake/lib/toaster/toastergui/templates/toastertable.html
@@ -32,8 +32,11 @@
32 <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> 32 <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1">
33 <i class="icon-remove"></i> 33 <i class="icon-remove"></i>
34 </a> 34 </a>
35 <button class="btn search-submit-{{table_name}}" >Search</button> 35 <button class="btn search-submit-{{table_name}}">
36 <button class="btn btn-link remove-search-btn-{{table_name}}">Show {{title|lower}} 36 Search
37 </button>
38 <button class="btn btn-link show-all-{{table_name}}">
39 Show {{title|lower}}
37 </button> 40 </button>
38 </form> 41 </form>
39 </div> 42 </div>