Home | History | Annotate | Download | only in js
      1 /**
      2  * Copyright (c) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you
      5  * may not use this file except in compliance with the License. You may
      6  * obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
     13  * implied. See the License for the specific language governing
     14  * permissions and limitations under the License.
     15  */
     16 
     17 (function($) {
     18 
     19   var _isModalOpen = false;
     20   var _isReadOnly = true;
     21   var _allTestsSet = new Set();
     22   var _allBranches = [];
     23   var _allDevices = [];
     24 
     25   var _writableSummary = 'Known test failures are acknowledged below for specific branch and \
     26     device configurations, and corresponding test breakage alerts will be silenced. Click an \
     27     entry to edit or see more information about the test failure.'
     28   var _readOnlySummary = 'Known test failures are acknowledged below for specific branch and \
     29     device configurations, and corresponding test breakage alerts will be silenced. Click an \
     30     entry to see  more information about the test failure. To add, edit, or remove a test \
     31     acknowledgment, contact a VTS Dashboard administrator.'
     32 
     33   $.widget('custom.sizedAutocomplete', $.ui.autocomplete, {
     34     options: {
     35       parent: ''
     36     },
     37     _resizeMenu: function() {
     38       this.menu.element.outerWidth($(this.options.parent).width());
     39     }
     40   });
     41 
     42   /**
     43    * Remove an acknowledgment from the list.
     44    * @param ack (jQuery object) The object for acknowledgment.
     45    * @param key (String) The value to display next to the label.
     46    */
     47   function removeAcknowledgment(ack, key) {
     48     if (ack.hasClass('disabled')) {
     49       return;
     50     }
     51     ack.addClass('disabled');
     52     $.ajax({
     53       url: '/api/test_acknowledgments/' + key,
     54       type: 'DELETE'
     55     }).always(function() {
     56       ack.removeClass('disabled');
     57     }).then(function() {
     58       ack.slideUp(150, function() {
     59         ack.remove();
     60       });
     61     });
     62   }
     63 
     64   /**
     65    * Callback for when a chip is removed from a chiplist.
     66    * @param text (String) The value stored in the chip.
     67    * @param allChipsSet (Set) The set of all chip values.
     68    * @param allIndicator (jQuery object) The object for "All" indicator adjacent to the chips.
     69    */
     70   function chipRemoveCallback(text, allChipsSet, allIndicator) {
     71     allChipsSet.delete(text);
     72     if (allChipsSet.size == 0) {
     73       allIndicator.show();
     74     }
     75   }
     76 
     77   /**
     78    * Add chips to the chip UI.
     79    * @param allChipsSet (Set) The set of all chip values.
     80    * @param container (jQuery object) The object in which to insert the chips.
     81    * @param chipList (list) The list of chip values to insert.
     82    * @param allIndicator (jQuery object) The object for "All" indicator adjacent to the chips.
     83    */
     84   function addChips(allChipsSet, container, chipList, allIndicator) {
     85     if (chipList && chipList.length > 0) {
     86       chipList.forEach(function(text) {
     87         if (allChipsSet.has(text)) return;
     88         var chip = $('<span class="chip">' + text + '</span>');
     89         if (!_isReadOnly) {
     90           var icon = $('<i class="material-icons">clear</i>').appendTo(chip);
     91           icon.click(function() {
     92             chipRemoveCallback(text, allChipsSet, allIndicator);
     93           });
     94         }
     95         chip.appendTo(container);
     96         allChipsSet.add(text);
     97       });
     98       allIndicator.hide();
     99     }
    100   }
    101 
    102   /**
    103    * Create a chip input UI.
    104    * @param container (jQuery object) The object in which to insert the input box.
    105    * @param placeholder (String) The placeholder text to display in the input.
    106    * @param allChipsSet (Set) The set of all chip values.
    107    * @param chipContainer (jQuery object) The object in which to insert new chips from the input.
    108    * @param allIndicator (jQuery object) The object for "All" indicator adjacent to the chips.
    109    * @returns The chip input jQuery object.
    110    */
    111   function addChipInput(container, placeholder, allChipsSet, chipContainer, allIndicator) {
    112     var input = $('<input type="text"></input>');
    113     input.attr('placeholder', placeholder);
    114     input.keyup(function(e) {
    115       if (e.keyCode === 13 && input.val().trim()) {
    116         addChips(allChipsSet, chipContainer, [input.val()], allIndicator);
    117         input.val('');
    118       }
    119     });
    120     var addButton = $('<i class="material-icons add-button">add</i>');
    121     addButton.click(function() {
    122       if (input.val().trim()) {
    123         addChips(allChipsSet, chipContainer, [input.val()], allIndicator);
    124         input.val('');
    125         addButton.hide();
    126       }
    127     });
    128     addButton.hide();
    129     input.focus(function() {
    130       addButton.show();
    131     });
    132     input.focusout(function() {
    133       if (!input.val().trim()) {
    134         addButton.hide();
    135       }
    136     });
    137     var holder = $('<div class="col s12 input-container"></div>').appendTo(container);
    138     input.appendTo(holder);
    139     addButton.appendTo(holder);
    140     return input;
    141   }
    142 
    143   /**
    144    * Callback to save changes to the acknowledgment.
    145    * @param ack (jQuery object) The object for acknowledgment.
    146    * @param modal (jQuery object) The jQueryUI modal object which invoked the callback.
    147    * @param key (String) The key associated with the acknowledgment.
    148    * @param test (String) The test name in the acknowledgment.
    149    * @param branchSet (Set) The set of all branches in the acknowledgment.
    150    * @param deviceSet (Set) The set of all devoces in the acknowledgment.
    151    * @param testCaseSet (Set) The set of all test cases in the acknowledgment.
    152    * @param note (String) The note in the acknowledgment.
    153    */
    154   function saveCallback(ack, modal, key, test, branchSet, deviceSet, testCaseSet, note) {
    155     var allEmpty = true;
    156     var firstUnemptyInput = null;
    157     var vals = modal.find('.modal-section>.input-container>input').each(function(_, input) {
    158       if (!!$(input).val()) {
    159         allEmpty = false;
    160         if (!firstUnemptyInput) firstUnemptyInput = $(input);
    161       }
    162     });
    163     if (!allEmpty) {
    164       firstUnemptyInput.focus();
    165       return false;
    166     }
    167     var branches = Array.from(branchSet);
    168     branches.sort();
    169     var devices = Array.from(deviceSet);
    170     devices.sort();
    171     var testCaseNames = Array.from(testCaseSet);
    172     testCaseNames.sort();
    173     var data = {
    174       'key' : key,
    175       'testName' : test,
    176       'branches' : branches,
    177       'devices' : devices,
    178       'testCaseNames' : testCaseNames,
    179       'note': note
    180     };
    181     $.post('/api/test_acknowledgments', JSON.stringify(data)).done(function(newKey) {
    182       var newAck = createAcknowledgment(newKey, test, branches, devices, testCaseNames, note);
    183       if (key == null) {
    184         ack.replaceWith(newAck.hide());
    185         newAck.slideDown(150);
    186       } else {
    187         ack.replaceWith(newAck);
    188       }
    189     }).always(function() {
    190       modal.modal({
    191         complete: function() { _isModalOpen = false; }
    192       });
    193       modal.modal('close');
    194     });
    195   }
    196 
    197   /**
    198    * Callback to save changes to the acknowledgment.
    199    * @param ack (jQuery object) The object for the acknowledgment.
    200    * @param key (String) The key associated with the acknowledgment.
    201    * @param test (String) The test name in the acknowledgment.
    202    * @param branches (list) The list of all branches in the acknowledgment.
    203    * @param devices (Set) The list of all devoces in the acknowledgment.
    204    * @param testCases (Set) The list of all test cases in the acknowledgment.
    205    * @param note (String) The note in the acknowledgment.
    206    */
    207   function showModal(ack, key, test, branches, devices, testCases, note) {
    208     if (_isModalOpen) {
    209       return;
    210     }
    211     _isModalOpen = true;
    212     var wrapper = $('#modal');
    213     wrapper.empty();
    214     wrapper.modal();
    215     var content = $('<div class="modal-content"><h4>Test Acknowledgment</h4></div>');
    216     var row = $('<div class="row"></div>').appendTo(content);
    217     row.append('<div class="col s12"><h5><b>Test: </b>' + test + '</h5></div>');
    218 
    219     var branchSet = new Set();
    220     var branchContainer = $('<div class="col l4 s12 modal-section"></div>').appendTo(row);
    221     var branchHeader = $('<h5></h5>').appendTo(branchContainer);
    222     branchHeader.append('<b>Branches:</b>');
    223     var allBranchesLabel = $('<span> All</span>').appendTo(branchHeader);
    224     var branchChips = $('<div class="col s12 chips branch-chips"></div>').appendTo(branchContainer);
    225     addChips(branchSet, branchChips, branches, allBranchesLabel);
    226     if (!_isReadOnly) {
    227       var branchInput = addChipInput(
    228         branchContainer, 'Specify a branch...', branchSet, branchChips, allBranchesLabel);
    229       branchInput.sizedAutocomplete({
    230         source: _allBranches,
    231         classes: {
    232           'ui-autocomplete': 'card autocomplete-dropdown'
    233         },
    234         parent: branchInput
    235       });
    236     }
    237 
    238     var deviceSet = new Set();
    239     var deviceContainer = $('<div class="col l4 s12 modal-section"></div>').appendTo(row);
    240     var deviceHeader = $('<h5></h5>').appendTo(deviceContainer);
    241     deviceHeader.append('<b>Devices:</b>');
    242     var allDevicesLabel = $('<span> All</span>').appendTo(deviceHeader);
    243     var deviceChips = $('<div class="col s12 chips device-chips"></div>').appendTo(deviceContainer);
    244     addChips(deviceSet, deviceChips, devices, allDevicesLabel);
    245     if (!_isReadOnly) {
    246       var deviceInput = addChipInput(
    247         deviceContainer, 'Specify a device...', deviceSet, deviceChips, allDevicesLabel);
    248       deviceInput.sizedAutocomplete({
    249         source: _allDevices,
    250         classes: {
    251           'ui-autocomplete': 'card autocomplete-dropdown'
    252         },
    253         parent: deviceInput
    254       });
    255     }
    256 
    257     var testCaseSet = new Set();
    258     var testCaseContainer = $('<div class="col l4 s12 modal-section"></div>').appendTo(row);
    259     var testCaseHeader = $('<h5></h5>').appendTo(testCaseContainer);
    260     testCaseHeader.append('<b>Test Cases:</b>');
    261     var allTestCasesLabel = $('<span> All</span>').appendTo(testCaseHeader);
    262     var testCaseChips = $('<div class="col s12 chips test-case-chips"></div>').appendTo(
    263       testCaseContainer);
    264     addChips(testCaseSet, testCaseChips, testCases, allTestCasesLabel);
    265     var testCaseInput = null;
    266     if (!_isReadOnly) {
    267       testCaseInput = addChipInput(
    268         testCaseContainer, 'Specify a test case...', testCaseSet, testCaseChips, allTestCasesLabel);
    269     }
    270 
    271     row.append('<div class="col s12"><h5><b>Note:</b></h5></div>');
    272     var inputField = $('<div class="input-field col s12"></div>').appendTo(row);
    273     var textArea = $('<textarea placeholder="Type a note..."></textarea>');
    274     textArea.addClass('materialize-textarea note-field');
    275     textArea.appendTo(inputField);
    276     textArea.val(note);
    277     if (_isReadOnly) {
    278       textArea.attr('disabled', true);
    279     }
    280 
    281     content.appendTo(wrapper);
    282     var footer = $('<div class="modal-footer"></div>');
    283     if (!_isReadOnly) {
    284       var save = $('<a class="btn">Save</a></div>').appendTo(footer);
    285       save.click(function() {
    286         saveCallback(ack, wrapper, key, test, branchSet, deviceSet, testCaseSet, textArea.val());
    287       });
    288     }
    289     var close = $('<a class="btn-flat">Close</a></div>').appendTo(footer);
    290     close.click(function() {
    291       wrapper.modal({
    292         complete: function() { _isModalOpen = false; }
    293       });
    294       wrapper.modal('close');
    295     })
    296     footer.appendTo(wrapper);
    297     if (!_isReadOnly) {
    298       $.get('/api/test_run?test=' + test + '&timestamp=latest').done(function(data) {
    299         var allTestCases = data.reduce(function(array, column) {
    300           return array.concat(column.data);
    301         }, []);
    302         testCaseInput.sizedAutocomplete({
    303           source: allTestCases,
    304           classes: {
    305             'ui-autocomplete': 'card autocomplete-dropdown'
    306           },
    307           parent: testCaseInput
    308         });
    309       }).always(function() {
    310         wrapper.modal('open');
    311       });
    312     } else {
    313       wrapper.modal('open');
    314     }
    315   }
    316 
    317   /**
    318    * Create a test acknowledgment object.
    319    * @param key (String) The key associated with the acknowledgment.
    320    * @param test (String) The test name in the acknowledgment.
    321    * @param branches (list) The list of all branches in the acknowledgment.
    322    * @param devices (Set) The list of all devoces in the acknowledgment.
    323    * @param testCases (Set) The list of all test cases in the acknowledgment.
    324    * @param note (String) The note in the acknowledgment.
    325    */
    326   function createAcknowledgment(key, test, branches, devices, testCases, note) {
    327     var wrapper = $('<div class="col s12 ack-entry"></div>');
    328     var details = $('<div class="col card hoverable"></div>').appendTo(wrapper);
    329     details.addClass(_isReadOnly ? 's12' : 's11')
    330     var testDiv = $('<div class="col s12"><b>' + test + '</b></div>').appendTo(details);
    331     var infoBtn = $('<span class="info-icon right"></a>').appendTo(testDiv);
    332     infoBtn.append('<i class="material-icons">info_outline</i>');
    333     details.click(function() {
    334       showModal(wrapper, key, test, branches, devices, testCases, note);
    335     });
    336     var branchesSummary = 'All';
    337     if (!!branches && branches.length == 1) {
    338       branchesSummary = branches[0];
    339     } else if (!!branches && branches.length > 1) {
    340       branchesSummary = branches[0];
    341       branchesSummary += '<span class="count-indicator"> (+' + (branches.length - 1) + ')</span>';
    342     }
    343     $('<div class="col l4 s12"><b>Branches: </b>' + branchesSummary + '</div>').appendTo(details);
    344     var devicesSummary = 'All';
    345     if (!!devices && devices.length == 1) {
    346       devicesSummary = devices[0];
    347     } else if (!!devices && devices.length > 1) {
    348       devicesSummary = devices[0];
    349       devicesSummary += '<span class="count-indicator"> (+' + (devices.length - 1) + ')</span>';
    350     }
    351     $('<div class="col l4 s12"><b>Devices: </b>' + devicesSummary + '</div>').appendTo(details);
    352     var testCaseSummary = 'All';
    353     if (!!testCases && testCases.length == 1) {
    354       testCaseSummary = testCases[0];
    355     } else if (!!testCases && testCases.length > 1) {
    356       testCaseSummary = testCases[0];
    357       testCaseSummary += '<span class="count-indicator"> (+' + (testCases.length - 1) + ')</span>';
    358     }
    359     details.append('<div class="col l4  s12"><b>Test Cases: </b>' + testCaseSummary + '</div>');
    360 
    361     if (!_isReadOnly) {
    362       var btnContainer = $('<div class="col s1 center btn-container"></div>');
    363 
    364       var clear = $('<a class="col s12 btn-flat remove-button"></a>');
    365       clear.append('<i class="material-icons">clear</i>');
    366       clear.attr('title', 'Remove');
    367       clear.click(function() { removeAcknowledgment(wrapper, key); });
    368       clear.appendTo(btnContainer);
    369 
    370       btnContainer.appendTo(wrapper);
    371     }
    372     return wrapper;
    373   }
    374 
    375   /**
    376    * Create a test acknowledgments UI.
    377    * @param allTests (list) The list of all test names.
    378    * @param allBranches (list) The list of all branches.
    379    * @param allDevices (list) The list of all device names.
    380    * @param testAcknowledgments (list) JSON-serialized TestAcknowledgmentEntity object list.
    381    * @param readOnly (boolean) True if the acknowledgments are read-only, false if mutable.
    382    */
    383   $.fn.testAcknowledgments = function(
    384       allTests, allBranches, allDevices, testAcknowledgments, readOnly) {
    385     var self = $(this);
    386     _allTestsSet = new Set(allTests);
    387     _allBranches = allBranches;
    388     _allDevices = allDevices;
    389     _isReadOnly = readOnly;
    390     var searchRow = $('<div class="search-row"></div>');
    391     var headerRow = $('<div></div>');
    392     var acks = $('<div class="acknowledgments"></div>');
    393 
    394     if (!_isReadOnly) {
    395       var inputWrapper = $('<div class="input-field col s8"></div>');
    396       var input = $('<input type="text"></input>').appendTo(inputWrapper);
    397       inputWrapper.append('<label>Search for tests to add an acknowledgment</label>');
    398       inputWrapper.appendTo(searchRow);
    399       input.sizedAutocomplete({
    400         source: allTests,
    401         classes: {
    402           'ui-autocomplete': 'card autocomplete-dropdown'
    403         },
    404         parent: input
    405       });
    406 
    407       var btnWrapper = $('<div class="col s1"></div>');
    408       var btn = $('<a class="btn waves-effect waves-light red btn-floating"></a>');
    409       btn.append('<i class="material-icons">add</a>');
    410       btn.appendTo(btnWrapper);
    411       btnWrapper.appendTo(searchRow);
    412       btn.click(function() {
    413         if (!_allTestsSet.has(input.val())) return;
    414         var ack = createAcknowledgment(undefined, input.val());
    415         ack.hide().prependTo(acks);
    416         showModal(ack, undefined, input.val());
    417       });
    418       searchRow.appendTo(self);
    419     }
    420 
    421     var headerCol = $('<div class="col s12 section-header-col"></div>').appendTo(headerRow);
    422     if (_isReadOnly) {
    423       headerCol.append('<p class="acknowledgment-info">' + _readOnlySummary + '</p>');
    424     } else {
    425       headerCol.append('<p class="acknowledgment-info">' + _writableSummary + '</p>');
    426     }
    427     headerRow.appendTo(self);
    428 
    429     testAcknowledgments.forEach(function(ack) {
    430       var wrapper = createAcknowledgment(
    431         ack.key, ack.testName, ack.branches, ack.devices, ack.testCaseNames, ack.note);
    432       wrapper.appendTo(acks);
    433     });
    434     acks.appendTo(self);
    435 
    436     self.append('<div class="modal modal-fixed-footer acknowledgments-modal" id="modal"></div>');
    437   };
    438 
    439 })(jQuery);
    440