| // 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); |