blob: 21a6bc63dc005e59f512ee4798bc0bf87560275b [file] [log] [blame]
// 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;
}
var parser = new GrReviewerUpdatesParser(change);
parser._filterRemovedMessages();
parser._groupUpdates();
parser._formatUpdates();
return parser.result;
};
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(function(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) {
var items = [];
for (var accountId in this._updateItems) {
if (!this._updateItems.hasOwnProperty(accountId)) continue;
var 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() {
var updates = this.result.reviewer_updates;
var newUpdates = updates.reduce(function(newUpdates, update) {
if (!this._batch) {
this._batch = this._startBatch(update);
}
var updateDate = util.parseDate(update.updated).getTime();
var batchUpdateDate = util.parseDate(this._batch.date).getTime();
var 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;
}.bind(this), []);
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(function(result, item) {
var message = this._getUpdateMessage(item.prev_state, item.state);
if (!result[message]) {
result[message] = [];
}
result[message].push(item.reviewer);
return result;
}.bind(this), {});
};
/**
* 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() {
this.result.reviewer_updates.forEach(function(update) {
var grouppedReviewers = this._groupUpdatesByMessage(update.updates);
var newUpdates = [];
for (var message in grouppedReviewers) {
if (grouppedReviewers.hasOwnProperty(message)) {
newUpdates.push({
message: message,
reviewers: grouppedReviewers[message],
});
}
}
update.updates = newUpdates;
}.bind(this));
};
window.GrReviewerUpdatesParser = GrReviewerUpdatesParser;
})(window);