diff options
| -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 | ||
