| /** |
| * Copyright (c) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you |
| * may not use this file except in compliance with the License. You may |
| * obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| * implied. See the License for the specific language governing |
| * permissions and limitations under the License. |
| */ |
| |
| (function($) { |
| |
| var _isModalOpen = false; |
| var _isReadOnly = true; |
| var _allTestsSet = new Set(); |
| var _allBranches = []; |
| var _allDevices = []; |
| |
| var _writableSummary = 'Known test failures are acknowledged below for specific branch and \ |
| device configurations, and corresponding test breakage alerts will be silenced. Click an \ |
| entry to edit or see more information about the test failure.' |
| var _readOnlySummary = 'Known test failures are acknowledged below for specific branch and \ |
| device configurations, and corresponding test breakage alerts will be silenced. Click an \ |
| entry to see more information about the test failure. To add, edit, or remove a test \ |
| acknowledgment, contact a VTS Dashboard administrator.' |
| |
| $.widget('custom.sizedAutocomplete', $.ui.autocomplete, { |
| options: { |
| parent: '' |
| }, |
| _resizeMenu: function() { |
| this.menu.element.outerWidth($(this.options.parent).width()); |
| } |
| }); |
| |
| /** |
| * Remove an acknowledgment from the list. |
| * @param ack (jQuery object) The object for acknowledgment. |
| * @param key (String) The value to display next to the label. |
| */ |
| function removeAcknowledgment(ack, key) { |
| if (ack.hasClass('disabled')) { |
| return; |
| } |
| ack.addClass('disabled'); |
| $.ajax({ |
| url: '/api/test_acknowledgments/' + key, |
| type: 'DELETE' |
| }).always(function() { |
| ack.removeClass('disabled'); |
| }).then(function() { |
| ack.slideUp(150, function() { |
| ack.remove(); |
| }); |
| }); |
| } |
| |
| /** |
| * Callback for when a chip is removed from a chiplist. |
| * @param text (String) The value stored in the chip. |
| * @param allChipsSet (Set) The set of all chip values. |
| * @param allIndicator (jQuery object) The object for "All" indicator adjacent to the chips. |
| */ |
| function chipRemoveCallback(text, allChipsSet, allIndicator) { |
| allChipsSet.delete(text); |
| if (allChipsSet.size == 0) { |
| allIndicator.show(); |
| } |
| } |
| |
| /** |
| * Add chips to the chip UI. |
| * @param allChipsSet (Set) The set of all chip values. |
| * @param container (jQuery object) The object in which to insert the chips. |
| * @param chipList (list) The list of chip values to insert. |
| * @param allIndicator (jQuery object) The object for "All" indicator adjacent to the chips. |
| */ |
| function addChips(allChipsSet, container, chipList, allIndicator) { |
| if (chipList && chipList.length > 0) { |
| chipList.forEach(function(text) { |
| if (allChipsSet.has(text)) return; |
| var chip = $('<span class="chip">' + text + '</span>'); |
| if (!_isReadOnly) { |
| var icon = $('<i class="material-icons">clear</i>').appendTo(chip); |
| icon.click(function() { |
| chipRemoveCallback(text, allChipsSet, allIndicator); |
| }); |
| } |
| chip.appendTo(container); |
| allChipsSet.add(text); |
| }); |
| allIndicator.hide(); |
| } |
| } |
| |
| /** |
| * Create a chip input UI. |
| * @param container (jQuery object) The object in which to insert the input box. |
| * @param placeholder (String) The placeholder text to display in the input. |
| * @param allChipsSet (Set) The set of all chip values. |
| * @param chipContainer (jQuery object) The object in which to insert new chips from the input. |
| * @param allIndicator (jQuery object) The object for "All" indicator adjacent to the chips. |
| * @returns The chip input jQuery object. |
| */ |
| function addChipInput(container, placeholder, allChipsSet, chipContainer, allIndicator) { |
| var input = $('<input type="text"></input>'); |
| input.attr('placeholder', placeholder); |
| input.keyup(function(e) { |
| if (e.keyCode === 13 && input.val().trim()) { |
| addChips(allChipsSet, chipContainer, [input.val()], allIndicator); |
| input.val(''); |
| } |
| }); |
| var addButton = $('<i class="material-icons add-button">add</i>'); |
| addButton.click(function() { |
| if (input.val().trim()) { |
| addChips(allChipsSet, chipContainer, [input.val()], allIndicator); |
| input.val(''); |
| addButton.hide(); |
| } |
| }); |
| addButton.hide(); |
| input.focus(function() { |
| addButton.show(); |
| }); |
| input.focusout(function() { |
| if (!input.val().trim()) { |
| addButton.hide(); |
| } |
| }); |
| var holder = $('<div class="col s12 input-container"></div>').appendTo(container); |
| input.appendTo(holder); |
| addButton.appendTo(holder); |
| return input; |
| } |
| |
| /** |
| * Callback to save changes to the acknowledgment. |
| * @param ack (jQuery object) The object for acknowledgment. |
| * @param modal (jQuery object) The jQueryUI modal object which invoked the callback. |
| * @param key (String) The key associated with the acknowledgment. |
| * @param test (String) The test name in the acknowledgment. |
| * @param branchSet (Set) The set of all branches in the acknowledgment. |
| * @param deviceSet (Set) The set of all devoces in the acknowledgment. |
| * @param testCaseSet (Set) The set of all test cases in the acknowledgment. |
| * @param note (String) The note in the acknowledgment. |
| */ |
| function saveCallback(ack, modal, key, test, branchSet, deviceSet, testCaseSet, note) { |
| var allEmpty = true; |
| var firstUnemptyInput = null; |
| var vals = modal.find('.modal-section>.input-container>input').each(function(_, input) { |
| if (!!$(input).val()) { |
| allEmpty = false; |
| if (!firstUnemptyInput) firstUnemptyInput = $(input); |
| } |
| }); |
| if (!allEmpty) { |
| firstUnemptyInput.focus(); |
| return false; |
| } |
| var branches = Array.from(branchSet); |
| branches.sort(); |
| var devices = Array.from(deviceSet); |
| devices.sort(); |
| var testCaseNames = Array.from(testCaseSet); |
| testCaseNames.sort(); |
| var data = { |
| 'key' : key, |
| 'testName' : test, |
| 'branches' : branches, |
| 'devices' : devices, |
| 'testCaseNames' : testCaseNames, |
| 'note': note |
| }; |
| $.post('/api/test_acknowledgments', JSON.stringify(data)).done(function(newKey) { |
| var newAck = createAcknowledgment(newKey, test, branches, devices, testCaseNames, note); |
| if (key == null) { |
| ack.replaceWith(newAck.hide()); |
| newAck.slideDown(150); |
| } else { |
| ack.replaceWith(newAck); |
| } |
| }).always(function() { |
| modal.modal({ |
| complete: function() { _isModalOpen = false; } |
| }); |
| modal.modal('close'); |
| }); |
| } |
| |
| /** |
| * Callback to save changes to the acknowledgment. |
| * @param ack (jQuery object) The object for the acknowledgment. |
| * @param key (String) The key associated with the acknowledgment. |
| * @param test (String) The test name in the acknowledgment. |
| * @param branches (list) The list of all branches in the acknowledgment. |
| * @param devices (Set) The list of all devoces in the acknowledgment. |
| * @param testCases (Set) The list of all test cases in the acknowledgment. |
| * @param note (String) The note in the acknowledgment. |
| */ |
| function showModal(ack, key, test, branches, devices, testCases, note) { |
| if (_isModalOpen) { |
| return; |
| } |
| _isModalOpen = true; |
| var wrapper = $('#modal'); |
| wrapper.empty(); |
| wrapper.modal(); |
| var content = $('<div class="modal-content"><h4>Test Acknowledgment</h4></div>'); |
| var row = $('<div class="row"></div>').appendTo(content); |
| row.append('<div class="col s12"><h5><b>Test: </b>' + test + '</h5></div>'); |
| |
| var branchSet = new Set(); |
| var branchContainer = $('<div class="col l4 s12 modal-section"></div>').appendTo(row); |
| var branchHeader = $('<h5></h5>').appendTo(branchContainer); |
| branchHeader.append('<b>Branches:</b>'); |
| var allBranchesLabel = $('<span> All</span>').appendTo(branchHeader); |
| var branchChips = $('<div class="col s12 chips branch-chips"></div>').appendTo(branchContainer); |
| addChips(branchSet, branchChips, branches, allBranchesLabel); |
| if (!_isReadOnly) { |
| var branchInput = addChipInput( |
| branchContainer, 'Specify a branch...', branchSet, branchChips, allBranchesLabel); |
| branchInput.sizedAutocomplete({ |
| source: _allBranches, |
| classes: { |
| 'ui-autocomplete': 'card autocomplete-dropdown' |
| }, |
| parent: branchInput |
| }); |
| } |
| |
| var deviceSet = new Set(); |
| var deviceContainer = $('<div class="col l4 s12 modal-section"></div>').appendTo(row); |
| var deviceHeader = $('<h5></h5>').appendTo(deviceContainer); |
| deviceHeader.append('<b>Devices:</b>'); |
| var allDevicesLabel = $('<span> All</span>').appendTo(deviceHeader); |
| var deviceChips = $('<div class="col s12 chips device-chips"></div>').appendTo(deviceContainer); |
| addChips(deviceSet, deviceChips, devices, allDevicesLabel); |
| if (!_isReadOnly) { |
| var deviceInput = addChipInput( |
| deviceContainer, 'Specify a device...', deviceSet, deviceChips, allDevicesLabel); |
| deviceInput.sizedAutocomplete({ |
| source: _allDevices, |
| classes: { |
| 'ui-autocomplete': 'card autocomplete-dropdown' |
| }, |
| parent: deviceInput |
| }); |
| } |
| |
| var testCaseSet = new Set(); |
| var testCaseContainer = $('<div class="col l4 s12 modal-section"></div>').appendTo(row); |
| var testCaseHeader = $('<h5></h5>').appendTo(testCaseContainer); |
| testCaseHeader.append('<b>Test Cases:</b>'); |
| var allTestCasesLabel = $('<span> All</span>').appendTo(testCaseHeader); |
| var testCaseChips = $('<div class="col s12 chips test-case-chips"></div>').appendTo( |
| testCaseContainer); |
| addChips(testCaseSet, testCaseChips, testCases, allTestCasesLabel); |
| var testCaseInput = null; |
| if (!_isReadOnly) { |
| testCaseInput = addChipInput( |
| testCaseContainer, 'Specify a test case...', testCaseSet, testCaseChips, allTestCasesLabel); |
| } |
| |
| row.append('<div class="col s12"><h5><b>Note:</b></h5></div>'); |
| var inputField = $('<div class="input-field col s12"></div>').appendTo(row); |
| var textArea = $('<textarea placeholder="Type a note..."></textarea>'); |
| textArea.addClass('materialize-textarea note-field'); |
| textArea.appendTo(inputField); |
| textArea.val(note); |
| if (_isReadOnly) { |
| textArea.attr('disabled', true); |
| } |
| |
| content.appendTo(wrapper); |
| var footer = $('<div class="modal-footer"></div>'); |
| if (!_isReadOnly) { |
| var save = $('<a class="btn">Save</a></div>').appendTo(footer); |
| save.click(function() { |
| saveCallback(ack, wrapper, key, test, branchSet, deviceSet, testCaseSet, textArea.val()); |
| }); |
| } |
| var close = $('<a class="btn-flat">Close</a></div>').appendTo(footer); |
| close.click(function() { |
| wrapper.modal({ |
| complete: function() { _isModalOpen = false; } |
| }); |
| wrapper.modal('close'); |
| }) |
| footer.appendTo(wrapper); |
| if (!_isReadOnly) { |
| $.get('/api/test_run?test=' + test + '×tamp=latest').done(function(data) { |
| var allTestCases = data.reduce(function(array, column) { |
| return array.concat(column.data); |
| }, []); |
| testCaseInput.sizedAutocomplete({ |
| source: allTestCases, |
| classes: { |
| 'ui-autocomplete': 'card autocomplete-dropdown' |
| }, |
| parent: testCaseInput |
| }); |
| }).always(function() { |
| wrapper.modal('open'); |
| }); |
| } else { |
| wrapper.modal('open'); |
| } |
| } |
| |
| /** |
| * Create a test acknowledgment object. |
| * @param key (String) The key associated with the acknowledgment. |
| * @param test (String) The test name in the acknowledgment. |
| * @param branches (list) The list of all branches in the acknowledgment. |
| * @param devices (Set) The list of all devoces in the acknowledgment. |
| * @param testCases (Set) The list of all test cases in the acknowledgment. |
| * @param note (String) The note in the acknowledgment. |
| */ |
| function createAcknowledgment(key, test, branches, devices, testCases, note) { |
| var wrapper = $('<div class="col s12 ack-entry"></div>'); |
| var details = $('<div class="col card hoverable"></div>').appendTo(wrapper); |
| details.addClass(_isReadOnly ? 's12' : 's11') |
| var testDiv = $('<div class="col s12"><b>' + test + '</b></div>').appendTo(details); |
| var infoBtn = $('<span class="info-icon right"></a>').appendTo(testDiv); |
| infoBtn.append('<i class="material-icons">info_outline</i>'); |
| details.click(function() { |
| showModal(wrapper, key, test, branches, devices, testCases, note); |
| }); |
| var branchesSummary = 'All'; |
| if (!!branches && branches.length == 1) { |
| branchesSummary = branches[0]; |
| } else if (!!branches && branches.length > 1) { |
| branchesSummary = branches[0]; |
| branchesSummary += '<span class="count-indicator"> (+' + (branches.length - 1) + ')</span>'; |
| } |
| $('<div class="col l4 s12"><b>Branches: </b>' + branchesSummary + '</div>').appendTo(details); |
| var devicesSummary = 'All'; |
| if (!!devices && devices.length == 1) { |
| devicesSummary = devices[0]; |
| } else if (!!devices && devices.length > 1) { |
| devicesSummary = devices[0]; |
| devicesSummary += '<span class="count-indicator"> (+' + (devices.length - 1) + ')</span>'; |
| } |
| $('<div class="col l4 s12"><b>Devices: </b>' + devicesSummary + '</div>').appendTo(details); |
| var testCaseSummary = 'All'; |
| if (!!testCases && testCases.length == 1) { |
| testCaseSummary = testCases[0]; |
| } else if (!!testCases && testCases.length > 1) { |
| testCaseSummary = testCases[0]; |
| testCaseSummary += '<span class="count-indicator"> (+' + (testCases.length - 1) + ')</span>'; |
| } |
| details.append('<div class="col l4 s12"><b>Test Cases: </b>' + testCaseSummary + '</div>'); |
| |
| if (!_isReadOnly) { |
| var btnContainer = $('<div class="col s1 center btn-container"></div>'); |
| |
| var clear = $('<a class="col s12 btn-flat remove-button"></a>'); |
| clear.append('<i class="material-icons">clear</i>'); |
| clear.attr('title', 'Remove'); |
| clear.click(function() { removeAcknowledgment(wrapper, key); }); |
| clear.appendTo(btnContainer); |
| |
| btnContainer.appendTo(wrapper); |
| } |
| return wrapper; |
| } |
| |
| /** |
| * Create a test acknowledgments UI. |
| * @param allTests (list) The list of all test names. |
| * @param allBranches (list) The list of all branches. |
| * @param allDevices (list) The list of all device names. |
| * @param testAcknowledgments (list) JSON-serialized TestAcknowledgmentEntity object list. |
| * @param readOnly (boolean) True if the acknowledgments are read-only, false if mutable. |
| */ |
| $.fn.testAcknowledgments = function( |
| allTests, allBranches, allDevices, testAcknowledgments, readOnly) { |
| var self = $(this); |
| _allTestsSet = new Set(allTests); |
| _allBranches = allBranches; |
| _allDevices = allDevices; |
| _isReadOnly = readOnly; |
| var searchRow = $('<div class="search-row"></div>'); |
| var headerRow = $('<div></div>'); |
| var acks = $('<div class="acknowledgments"></div>'); |
| |
| if (!_isReadOnly) { |
| var inputWrapper = $('<div class="input-field col s8"></div>'); |
| var input = $('<input type="text"></input>').appendTo(inputWrapper); |
| inputWrapper.append('<label>Search for tests to add an acknowledgment</label>'); |
| inputWrapper.appendTo(searchRow); |
| input.sizedAutocomplete({ |
| source: allTests, |
| classes: { |
| 'ui-autocomplete': 'card autocomplete-dropdown' |
| }, |
| parent: input |
| }); |
| |
| var btnWrapper = $('<div class="col s1"></div>'); |
| var btn = $('<a class="btn waves-effect waves-light red btn-floating"></a>'); |
| btn.append('<i class="material-icons">add</a>'); |
| btn.appendTo(btnWrapper); |
| btnWrapper.appendTo(searchRow); |
| btn.click(function() { |
| if (!_allTestsSet.has(input.val())) return; |
| var ack = createAcknowledgment(undefined, input.val()); |
| ack.hide().prependTo(acks); |
| showModal(ack, undefined, input.val()); |
| }); |
| searchRow.appendTo(self); |
| } |
| |
| var headerCol = $('<div class="col s12 section-header-col"></div>').appendTo(headerRow); |
| if (_isReadOnly) { |
| headerCol.append('<p class="acknowledgment-info">' + _readOnlySummary + '</p>'); |
| } else { |
| headerCol.append('<p class="acknowledgment-info">' + _writableSummary + '</p>'); |
| } |
| headerRow.appendTo(self); |
| |
| testAcknowledgments.forEach(function(ack) { |
| var wrapper = createAcknowledgment( |
| ack.key, ack.testName, ack.branches, ack.devices, ack.testCaseNames, ack.note); |
| wrapper.appendTo(acks); |
| }); |
| acks.appendTo(self); |
| |
| self.append('<div class="modal modal-fixed-footer acknowledgments-modal" id="modal"></div>'); |
| }; |
| |
| })(jQuery); |