'); textArea.addClass('materialize-textarea note-field'); textArea.appendTo(inputField); textArea.val(note); if (_isReadOnly) { textArea.attr('disabled', true); } content.appendTo(wrapper); var footer = $(''); if (!_isReadOnly) { var save = $('Save
').appendTo(footer); save.click(function() { saveCallback(ack, wrapper, key, test, branchSet, deviceSet, testCaseSet, textArea.val()); }); } var close = $('Close').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 = $('
'); var details = $('
').appendTo(wrapper); details.addClass(_isReadOnly ? 's12' : 's11') var testDiv = $('
' + test + '
').appendTo(details); var infoBtn = $('').appendTo(testDiv); infoBtn.append('info_outline'); 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 += ' (+' + (branches.length - 1) + ')'; } $('
Branches: ' + branchesSummary + '
').appendTo(details); var devicesSummary = 'All'; if (!!devices && devices.length == 1) { devicesSummary = devices[0]; } else if (!!devices && devices.length > 1) { devicesSummary = devices[0]; devicesSummary += ' (+' + (devices.length - 1) + ')'; } $('
Devices: ' + devicesSummary + '
').appendTo(details); var testCaseSummary = 'All'; if (!!testCases && testCases.length == 1) { testCaseSummary = testCases[0]; } else if (!!testCases && testCases.length > 1) { testCaseSummary = testCases[0]; testCaseSummary += ' (+' + (testCases.length - 1) + ')'; } details.append('
Test Cases: ' + testCaseSummary + '
'); if (!_isReadOnly) { var btnContainer = $('
'); var clear = $(''); clear.append('clear'); 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 = $('
'); var headerRow = $('
'); var acks = $('
'); if (!_isReadOnly) { var inputWrapper = $('
'); var input = $('').appendTo(inputWrapper); inputWrapper.append(''); inputWrapper.appendTo(searchRow); input.sizedAutocomplete({ source: allTests, classes: { 'ui-autocomplete': 'card autocomplete-dropdown' }, parent: input }); var btnWrapper = $('
'); var btn = $(''); btn.append('add'); 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 = $('
').appendTo(headerRow); if (_isReadOnly) { headerCol.append('

' + _readOnlySummary + '

'); } else { headerCol.append('

' + _writableSummary + '

'); } 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(''); }; })(jQuery);
Javascript  |  440行  |  16.81 KB

/**
 * 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 + '&timestamp=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);