
These tags are preserved by the Closure compiler and vulcanize in order to serve the license notices embedded in the outputs. In a standalone Gerrit server, these license are also covered in the LICENSES.txt served with the documentation. When serving PG assets from a CDN, it's less obvious what the corresponding LICENSES.txt file is, since the CDN is not directly linked to a running Gerrit server. Safer to embed the licenses in the assets themselves. Change-Id: Id1add1451fad1baa7916882a6bda02c326ccc988
226 lines
7.7 KiB
JavaScript
226 lines
7.7 KiB
JavaScript
/**
|
|
* @license
|
|
* 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(window) {
|
|
'use strict';
|
|
|
|
// Prevent redefinition.
|
|
if (window.GrReviewerUpdatesParser) { return; }
|
|
|
|
function GrReviewerUpdatesParser(change) {
|
|
// TODO (viktard): Polyfill Object.assign for IE.
|
|
this.result = Object.assign({}, change);
|
|
this._lastState = {};
|
|
}
|
|
|
|
GrReviewerUpdatesParser.parse = function(change) {
|
|
if (!change ||
|
|
!change.messages ||
|
|
!change.reviewer_updates ||
|
|
!change.reviewer_updates.length) {
|
|
return change;
|
|
}
|
|
const parser = new GrReviewerUpdatesParser(change);
|
|
parser._filterRemovedMessages();
|
|
parser._groupUpdates();
|
|
parser._formatUpdates();
|
|
parser._advanceUpdates();
|
|
return parser.result;
|
|
};
|
|
|
|
GrReviewerUpdatesParser.MESSAGE_REVIEWERS_THRESHOLD_MILLIS = 500;
|
|
GrReviewerUpdatesParser.REVIEWER_UPDATE_THRESHOLD_MILLIS = 6000;
|
|
|
|
GrReviewerUpdatesParser.prototype.result = null;
|
|
GrReviewerUpdatesParser.prototype._batch = null;
|
|
GrReviewerUpdatesParser.prototype._updateItems = null;
|
|
GrReviewerUpdatesParser.prototype._lastState = null;
|
|
|
|
/**
|
|
* Removes messages that describe removed reviewers, since reviewer_updates
|
|
* are used.
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._filterRemovedMessages = function() {
|
|
this.result.messages = this.result.messages.filter(message => {
|
|
return message.tag !== 'autogenerated:gerrit:deleteReviewer';
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Is a part of _groupUpdates(). Creates a new batch of updates.
|
|
* @param {Object} update instance of ReviewerUpdateInfo
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._startBatch = function(update) {
|
|
this._updateItems = [];
|
|
return {
|
|
author: update.updated_by,
|
|
date: update.updated,
|
|
type: 'REVIEWER_UPDATE',
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Is a part of _groupUpdates(). Validates current batch:
|
|
* - filters out updates that don't change reviewer state.
|
|
* - updates current reviewer state.
|
|
* @param {Object} update instance of ReviewerUpdateInfo
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._completeBatch = function(update) {
|
|
const items = [];
|
|
for (const accountId in this._updateItems) {
|
|
if (!this._updateItems.hasOwnProperty(accountId)) continue;
|
|
const updateItem = this._updateItems[accountId];
|
|
if (this._lastState[accountId] !== updateItem.state) {
|
|
this._lastState[accountId] = updateItem.state;
|
|
items.push(updateItem);
|
|
}
|
|
}
|
|
if (items.length) {
|
|
this._batch.updates = items;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Groups reviewer updates. Sequential updates are grouped if:
|
|
* - They were performed within short timeframe (6 seconds)
|
|
* - Made by the same person
|
|
* - Non-change updates are discarded within a group
|
|
* - Groups with no-change updates are discarded (eg CC -> CC)
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._groupUpdates = function() {
|
|
const updates = this.result.reviewer_updates;
|
|
const newUpdates = updates.reduce((newUpdates, update) => {
|
|
if (!this._batch) {
|
|
this._batch = this._startBatch(update);
|
|
}
|
|
const updateDate = util.parseDate(update.updated).getTime();
|
|
const batchUpdateDate = util.parseDate(this._batch.date).getTime();
|
|
const reviewerId = update.reviewer._account_id.toString();
|
|
if (updateDate - batchUpdateDate >
|
|
GrReviewerUpdatesParser.REVIEWER_UPDATE_THRESHOLD_MILLIS ||
|
|
update.updated_by._account_id !== this._batch.author._account_id) {
|
|
// Next sequential update should form new group.
|
|
this._completeBatch();
|
|
if (this._batch.updates && this._batch.updates.length) {
|
|
newUpdates.push(this._batch);
|
|
}
|
|
this._batch = this._startBatch(update);
|
|
}
|
|
this._updateItems[reviewerId] = {
|
|
reviewer: update.reviewer,
|
|
state: update.state,
|
|
};
|
|
if (this._lastState[reviewerId]) {
|
|
this._updateItems[reviewerId].prev_state = this._lastState[reviewerId];
|
|
}
|
|
return newUpdates;
|
|
}, []);
|
|
this._completeBatch();
|
|
if (this._batch.updates && this._batch.updates.length) {
|
|
newUpdates.push(this._batch);
|
|
}
|
|
this.result.reviewer_updates = newUpdates;
|
|
};
|
|
|
|
/**
|
|
* Generates update message for reviewer state change.
|
|
* @param {string} prev previous reviewer state.
|
|
* @param {string} state current reviewer state.
|
|
* @return {string}
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._getUpdateMessage = function(prev, state) {
|
|
if (prev === 'REMOVED' || !prev) {
|
|
return 'added to ' + state + ': ';
|
|
} else if (state === 'REMOVED') {
|
|
if (prev) {
|
|
return 'removed from ' + prev + ': ';
|
|
} else {
|
|
return 'removed : ';
|
|
}
|
|
} else {
|
|
return 'moved from ' + prev + ' to ' + state + ': ';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Groups updates for same category (eg CC->CC) into a hash arrays of
|
|
* reviewers.
|
|
* @param {!Array<!Object>} updates Array of ReviewerUpdateItemInfo.
|
|
* @return {!Object} Hash of arrays of AccountInfo, message as key.
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._groupUpdatesByMessage = function(updates) {
|
|
return updates.reduce((result, item) => {
|
|
const message = this._getUpdateMessage(item.prev_state, item.state);
|
|
if (!result[message]) {
|
|
result[message] = [];
|
|
}
|
|
result[message].push(item.reviewer);
|
|
return result;
|
|
}, {});
|
|
};
|
|
|
|
/**
|
|
* Generates text messages for grouped reviewer updates.
|
|
* Formats reviewer updates to a (not yet implemented) EventInfo instance.
|
|
* @see https://gerrit-review.googlesource.com/c/94490/
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._formatUpdates = function() {
|
|
for (const update of this.result.reviewer_updates) {
|
|
const grouppedReviewers = this._groupUpdatesByMessage(update.updates);
|
|
const newUpdates = [];
|
|
for (const message in grouppedReviewers) {
|
|
if (grouppedReviewers.hasOwnProperty(message)) {
|
|
newUpdates.push({
|
|
message,
|
|
reviewers: grouppedReviewers[message],
|
|
});
|
|
}
|
|
}
|
|
update.updates = newUpdates;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Moves reviewer updates that are within short time frame of change messages
|
|
* back in time so they would come before change messages.
|
|
* TODO(viktard): Remove when server-side serves reviewer updates like so.
|
|
*/
|
|
GrReviewerUpdatesParser.prototype._advanceUpdates = function() {
|
|
const updates = this.result.reviewer_updates;
|
|
const messages = this.result.messages;
|
|
messages.forEach((message, index) => {
|
|
const messageDate = util.parseDate(message.date).getTime();
|
|
const nextMessageDate = index === messages.length - 1 ? null :
|
|
util.parseDate(messages[index + 1].date).getTime();
|
|
for (const update of updates) {
|
|
const date = util.parseDate(update.date).getTime();
|
|
if (date >= messageDate
|
|
&& (!nextMessageDate || date < nextMessageDate)) {
|
|
const timestamp = util.parseDate(update.date).getTime() -
|
|
GrReviewerUpdatesParser.MESSAGE_REVIEWERS_THRESHOLD_MILLIS;
|
|
update.date = new Date(timestamp)
|
|
.toISOString().replace('T', ' ').replace('Z', '000000');
|
|
}
|
|
if (nextMessageDate && date > nextMessageDate) {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
window.GrReviewerUpdatesParser = GrReviewerUpdatesParser;
|
|
})(window);
|