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