Files
gerrit/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
Kasper Nilsson 5da159ee8d Fix bug in comment control hiding
The logic for showing/hiding the 'Show x more' control for messages made
some invalid assumptions, specifically regarding the content of the
_visibleMessages array.

This change adds a new function, _getHumanMessages, that is utilized in
the affected functions to prune the array down to its proper contents to
make the calculations about whether the control should be hidden and
what its labels should say much easier.

Bug: Issue 5749
Change-Id: I465fbff9c8deab0e2357461aa9bdf37e0e6a8299
2017-03-09 17:52:50 -08:00

332 lines
11 KiB
JavaScript

// Copyright (C) 2016 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() {
'use strict';
var MAX_INITIAL_SHOWN_MESSAGES = 20;
var MESSAGES_INCREMENT = 5;
var ReportingEvent = {
SHOW_ALL: 'show-all-messages',
SHOW_MORE: 'show-more-messages',
};
Polymer({
is: 'gr-messages-list',
properties: {
changeNum: Number,
messages: {
type: Array,
value: function() { return []; },
},
reviewerUpdates: {
type: Array,
value: function() { return []; },
},
comments: Object,
projectConfig: Object,
showReplyButtons: {
type: Boolean,
value: false,
},
_expanded: {
type: Boolean,
value: false,
observer: '_expandedChanged',
},
_hideAutomated: {
type: Boolean,
value: false,
},
/**
* The messages after processing and including merged reviewer updates.
*/
_processedMessages: {
type: Array,
computed: '_computeItems(messages, reviewerUpdates)',
observer: '_processedMessagesChanged',
},
/**
* The subset of _processedMessages that is visible to the user.
*/
_visibleMessages: {
type: Array,
value: function() { return []; },
},
},
scrollToMessage: function(messageID) {
var el = this.$$('[data-message-id="' + messageID + '"]');
// If the message is hidden, expand the hidden messages back to that
// point.
if (!el) {
for (var index = 0; index < this._processedMessages.length; index++) {
if (this._processedMessages[index].id === messageID) {
break;
}
}
if (index === this._processedMessages.length) { return; }
var newMessages = this._processedMessages.slice(index,
-this._visibleMessages.length);
// Add newMessages to the beginning of _visibleMessages.
this.splice.apply(this, ['_visibleMessages', 0, 0].concat(newMessages));
// Allow the dom-repeat to stamp.
Polymer.dom.flush();
el = this.$$('[data-message-id="' + messageID + '"]');
}
el.set('message.expanded', true);
var top = el.offsetTop;
for (var offsetParent = el.offsetParent;
offsetParent;
offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
window.scrollTo(0, top);
this._highlightEl(el);
},
_isAutomated: function(message) {
return !!(message.reviewer ||
(message.tag && message.tag.indexOf('autogenerated') === 0));
},
_computeItems: function(messages, reviewerUpdates) {
messages = messages || [];
reviewerUpdates = reviewerUpdates || [];
var mi = 0;
var ri = 0;
var result = [];
var mDate;
var rDate;
for (var i = 0; i < messages.length; i++) {
messages[i]._index = i;
}
while (mi < messages.length || ri < reviewerUpdates.length) {
if (mi >= messages.length) {
result = result.concat(reviewerUpdates.slice(ri));
break;
}
if (ri >= reviewerUpdates.length) {
result = result.concat(messages.slice(mi));
break;
}
mDate = mDate || util.parseDate(messages[mi].date);
rDate = rDate || util.parseDate(reviewerUpdates[ri].date);
if (rDate < mDate) {
result.push(reviewerUpdates[ri++]);
rDate = null;
} else {
result.push(messages[mi++]);
mDate = null;
}
}
return result;
},
_expandedChanged: function(exp) {
for (var i = 0; i < this._processedMessages.length; i++) {
this._processedMessages[i].expanded = exp;
if (i < this._visibleMessages.length) {
this.set(['_visibleMessages', i, 'expanded'], exp);
}
}
},
_highlightEl: function(el) {
var highlightedEls =
Polymer.dom(this.root).querySelectorAll('.highlighted');
for (var i = 0; i < highlightedEls.length; i++) {
highlightedEls[i].classList.remove('highlighted');
}
function handleAnimationEnd() {
el.removeEventListener('animationend', handleAnimationEnd);
el.classList.remove('highlighted');
}
el.addEventListener('animationend', handleAnimationEnd);
el.classList.add('highlighted');
},
/**
* @param {boolean} expand
*/
handleExpandCollapse: function(expand) {
this._expanded = expand;
},
_handleExpandCollapseTap: function(e) {
e.preventDefault();
this.handleExpandCollapse(!this._expanded);
},
_handleAutomatedMessageToggleTap: function(e) {
e.preventDefault();
this._hideAutomated = !this._hideAutomated;
},
_handleScrollTo: function(e) {
this.scrollToMessage(e.detail.message.id);
},
_hasAutomatedMessages: function(messages) {
for (var i = 0; messages && i < messages.length; i++) {
if (this._isAutomated(messages[i])) {
return true;
}
}
return false;
},
_computeExpandCollapseMessage: function(expanded) {
return expanded ? 'Collapse all' : 'Expand all';
},
_computeAutomatedToggleText: function(hideAutomated) {
return hideAutomated ? 'Show all messages' : 'Show comments only';
},
/**
* Computes message author's file comments for change's message.
* Method uses this.messages to find next message and relies on messages
* to be sorted by date field descending.
* @param {!Object} comments Hash of arrays of comments, filename as key.
* @param {!Object} message
* @return {!Object} Hash of arrays of comments, filename as key.
*/
_computeCommentsForMessage: function(comments, message) {
if (message._index === undefined || !comments || !this.messages) {
return [];
}
var messages = this.messages || [];
var index = message._index;
var authorId = message.author && message.author._account_id;
var mDate = util.parseDate(message.date).getTime();
// NB: Messages array has oldest messages first.
var nextMDate;
if (index > 0) {
for (var i = index - 1; i >= 0; i--) {
if (messages[i] && messages[i].author &&
messages[i].author._account_id === authorId) {
nextMDate = util.parseDate(messages[i].date).getTime();
break;
}
}
}
var msgComments = {};
for (var file in comments) {
var fileComments = comments[file];
for (var i = 0; i < fileComments.length; i++) {
if (fileComments[i].author &&
fileComments[i].author._account_id !== authorId) {
continue;
}
var cDate = util.parseDate(fileComments[i].updated).getTime();
if (cDate <= mDate) {
if (nextMDate && cDate <= nextMDate) {
continue;
}
msgComments[file] = msgComments[file] || [];
msgComments[file].push(fileComments[i]);
}
}
}
return msgComments;
},
/**
* Returns the number of messages to splice to the beginning of
* _visibleMessages. This is the minimum of the total number of messages
* remaining in the list and the number of messages needed to display five
* more visible messages in the list.
*/
_getDelta: function(visibleMessages, messages, hideAutomated) {
var delta = MESSAGES_INCREMENT;
var msgsRemaining = messages.length - visibleMessages.length;
if (hideAutomated) {
var counter = 0;
var i;
for (i = msgsRemaining; i > 0 && counter < MESSAGES_INCREMENT; i--) {
if (!this._isAutomated(messages[i - 1])) { counter++; }
}
delta = msgsRemaining - i;
}
return Math.min(msgsRemaining, delta);
},
/**
* Gets the number of messages that would be visible, but do not currently
* exist in _visibleMessages.
*/
_numRemaining: function(visibleMessages, messages, hideAutomated) {
if (hideAutomated) {
return this._getHumanMessages(messages).length -
this._getHumanMessages(visibleMessages).length;
}
return messages.length - visibleMessages.length;
},
_computeIncrementText: function(visibleMessages, messages, hideAutomated) {
var delta = this._getDelta(visibleMessages, messages, hideAutomated);
delta = Math.min(
this._numRemaining(visibleMessages, messages, hideAutomated), delta);
return 'Show ' + Math.min(MESSAGES_INCREMENT, delta) + ' more';
},
_getHumanMessages: function(messages) {
return messages.filter(function(msg) {
return !this._isAutomated(msg);
}.bind(this));
},
_computeShowHideTextHidden: function(visibleMessages, messages,
hideAutomated) {
if (hideAutomated) {
messages = this._getHumanMessages(messages);
visibleMessages = this._getHumanMessages(visibleMessages);
}
return visibleMessages.length >= messages.length;
},
_handleShowAllTap: function() {
this._visibleMessages = this._processedMessages;
this.$.reporting.reportInteraction(ReportingEvent.SHOW_ALL);
},
_handleIncrementShownMessages: function() {
var delta = this._getDelta(this._visibleMessages, this._processedMessages,
this._hideAutomated);
var len = this._visibleMessages.length;
var newMessages = this._processedMessages.slice(-(len + delta), -len);
// Add newMessages to the beginning of _visibleMessages
this.splice.apply(this, ['_visibleMessages', 0, 0].concat(newMessages));
this.$.reporting.reportInteraction(ReportingEvent.SHOW_MORE);
},
_processedMessagesChanged: function(messages) {
this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
},
_computeNumMessagesText: function(visibleMessages, messages,
hideAutomated) {
var total = this._numRemaining(visibleMessages, messages, hideAutomated);
return total === 1 ? 'Show 1 message' : 'Show all ' + total + ' messages';
},
});
})();