Convert gr-reviewer-updates-parser to ts
Change-Id: I56f9757b2de05b78d756ad98f2c342cb3dc81ba9
This commit is contained in:
@@ -15,37 +15,104 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {parseDate} from '../../../utils/date-util.js';
|
import {parseDate} from '../../../utils/date-util';
|
||||||
import {MessageTag} from '../../../constants/constants.js';
|
import {MessageTag, ReviewerState} from '../../../constants/constants';
|
||||||
|
import {
|
||||||
|
AccountInfo,
|
||||||
|
ChangeInfo,
|
||||||
|
ChangeMessageInfo,
|
||||||
|
ReviewerUpdateInfo,
|
||||||
|
Timestamp,
|
||||||
|
} from '../../../types/common';
|
||||||
|
import {hasOwnProperty} from '../../../utils/common-util';
|
||||||
|
|
||||||
const MESSAGE_REVIEWERS_THRESHOLD_MILLIS = 500;
|
const MESSAGE_REVIEWERS_THRESHOLD_MILLIS = 500;
|
||||||
const REVIEWER_UPDATE_THRESHOLD_MILLIS = 6000;
|
const REVIEWER_UPDATE_THRESHOLD_MILLIS = 6000;
|
||||||
|
|
||||||
|
interface ChangeInfoParserInput extends ChangeInfo {
|
||||||
|
messages: ChangeMessageInfo[];
|
||||||
|
reviewer_updates: ReviewerUpdateInfo[]; // Always has at least 1 item
|
||||||
|
}
|
||||||
|
|
||||||
|
function isChangeInfoParserInput(
|
||||||
|
change: ChangeInfo
|
||||||
|
): change is ChangeInfoParserInput {
|
||||||
|
return !!(
|
||||||
|
change.messages &&
|
||||||
|
change.reviewer_updates &&
|
||||||
|
change.reviewer_updates.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParserBatch {
|
||||||
|
author: AccountInfo;
|
||||||
|
date: Timestamp;
|
||||||
|
type: 'REVIEWER_UPDATE';
|
||||||
|
tag: MessageTag.TAG_REVIEWER_UPDATE;
|
||||||
|
updates?: UpdateItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParserBatchWithNonEmptyUpdates extends ParserBatch {
|
||||||
|
updates: UpdateItem[]; // Always has at least 1 items
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormattedReviewerUpdateInfo {
|
||||||
|
author: AccountInfo;
|
||||||
|
date: Timestamp;
|
||||||
|
type: 'REVIEWER_UPDATE';
|
||||||
|
tag: MessageTag.TAG_REVIEWER_UPDATE;
|
||||||
|
updates: {message: string; reviewers: AccountInfo[]}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isParserBatchWithNonEmptyUpdates(
|
||||||
|
x: ParserBatch
|
||||||
|
): x is ParserBatchWithNonEmptyUpdates {
|
||||||
|
return !!(x.updates && x.updates.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateItem {
|
||||||
|
reviewer: AccountInfo;
|
||||||
|
state: ReviewerState;
|
||||||
|
prev_state?: ReviewerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParsedChangeInfo extends Omit<ChangeInfo, 'reviewer_updates'> {
|
||||||
|
reviewer_updates?: ReviewerUpdateInfo[] | FormattedReviewerUpdateInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReviewersGroupByMessage = {[message: string]: AccountInfo[]};
|
||||||
|
|
||||||
export class GrReviewerUpdatesParser {
|
export class GrReviewerUpdatesParser {
|
||||||
constructor(change) {
|
// TODO(TS): The parser several times reassigns different types to
|
||||||
|
// reviewer_updates. After parse complete, the result has ParsedChangeInfo
|
||||||
|
// type. This class should be refactored to avoid reassignment.
|
||||||
|
private readonly result: ChangeInfoParserInput;
|
||||||
|
|
||||||
|
private _batch: ParserBatch | null = null;
|
||||||
|
|
||||||
|
private _updateItems: {[accountId: string]: UpdateItem} | null = null;
|
||||||
|
|
||||||
|
private readonly _lastState: {[accountId: string]: ReviewerState} = {};
|
||||||
|
|
||||||
|
constructor(change: ChangeInfoParserInput) {
|
||||||
this.result = {...change};
|
this.result = {...change};
|
||||||
this._lastState = {};
|
|
||||||
this._batch = null;
|
|
||||||
this._updateItems = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes messages that describe removed reviewers, since reviewer_updates
|
* Removes messages that describe removed reviewers, since reviewer_updates
|
||||||
* are used.
|
* are used.
|
||||||
*/
|
*/
|
||||||
_filterRemovedMessages() {
|
private _filterRemovedMessages() {
|
||||||
this.result.messages = this.result.messages.filter(
|
this.result.messages = this.result.messages.filter(
|
||||||
message => message.tag !== MessageTag.TAG_DELETE_REVIEWER
|
message => message.tag !== MessageTag.TAG_DELETE_REVIEWER
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is a part of _groupUpdates(). Creates a new batch of updates.
|
* Is a part of _groupUpdates(). Creates a new batch of updates.
|
||||||
*
|
|
||||||
* @param {Object} update instance of ReviewerUpdateInfo
|
|
||||||
*/
|
*/
|
||||||
_startBatch(update) {
|
private _startBatch(update: ReviewerUpdateInfo): ParserBatch {
|
||||||
this._updateItems = [];
|
this._updateItems = {};
|
||||||
return {
|
return {
|
||||||
author: update.updated_by,
|
author: update.updated_by,
|
||||||
date: update.updated,
|
date: update.updated,
|
||||||
@@ -58,13 +125,11 @@ export class GrReviewerUpdatesParser {
|
|||||||
* Is a part of _groupUpdates(). Validates current batch:
|
* Is a part of _groupUpdates(). Validates current batch:
|
||||||
* - filters out updates that don't change reviewer state.
|
* - filters out updates that don't change reviewer state.
|
||||||
* - updates current reviewer state.
|
* - updates current reviewer state.
|
||||||
*
|
|
||||||
* @param {Object} update instance of ReviewerUpdateInfo
|
|
||||||
*/
|
*/
|
||||||
_completeBatch(update) {
|
private _completeBatch(batch: ParserBatch) {
|
||||||
const items = [];
|
const items = [];
|
||||||
for (const accountId in this._updateItems) {
|
for (const accountId in this._updateItems) {
|
||||||
if (!this._updateItems.hasOwnProperty(accountId)) continue;
|
if (!hasOwnProperty(this._updateItems, accountId)) continue;
|
||||||
const updateItem = this._updateItems[accountId];
|
const updateItem = this._updateItems[accountId];
|
||||||
if (this._lastState[accountId] !== updateItem.state) {
|
if (this._lastState[accountId] !== updateItem.state) {
|
||||||
this._lastState[accountId] = updateItem.state;
|
this._lastState[accountId] = updateItem.state;
|
||||||
@@ -72,7 +137,7 @@ export class GrReviewerUpdatesParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
this._batch.updates = items;
|
batch.updates = items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +148,7 @@ export class GrReviewerUpdatesParser {
|
|||||||
* - Non-change updates are discarded within a group
|
* - Non-change updates are discarded within a group
|
||||||
* - Groups with no-change updates are discarded (eg CC -> CC)
|
* - Groups with no-change updates are discarded (eg CC -> CC)
|
||||||
*/
|
*/
|
||||||
_groupUpdates() {
|
_groupUpdates(): ParserBatchWithNonEmptyUpdates[] {
|
||||||
const updates = this.result.reviewer_updates;
|
const updates = this.result.reviewer_updates;
|
||||||
const newUpdates = updates.reduce((newUpdates, update) => {
|
const newUpdates = updates.reduce((newUpdates, update) => {
|
||||||
if (!this._batch) {
|
if (!this._batch) {
|
||||||
@@ -93,64 +158,67 @@ export class GrReviewerUpdatesParser {
|
|||||||
const batchUpdateDate = parseDate(this._batch.date).getTime();
|
const batchUpdateDate = parseDate(this._batch.date).getTime();
|
||||||
const reviewerId = update.reviewer._account_id.toString();
|
const reviewerId = update.reviewer._account_id.toString();
|
||||||
if (
|
if (
|
||||||
updateDate - batchUpdateDate >
|
updateDate - batchUpdateDate > REVIEWER_UPDATE_THRESHOLD_MILLIS ||
|
||||||
REVIEWER_UPDATE_THRESHOLD_MILLIS ||
|
|
||||||
update.updated_by._account_id !== this._batch.author._account_id
|
update.updated_by._account_id !== this._batch.author._account_id
|
||||||
) {
|
) {
|
||||||
// Next sequential update should form new group.
|
// Next sequential update should form new group.
|
||||||
this._completeBatch();
|
this._completeBatch(this._batch);
|
||||||
if (this._batch.updates && this._batch.updates.length) {
|
if (isParserBatchWithNonEmptyUpdates(this._batch)) {
|
||||||
newUpdates.push(this._batch);
|
newUpdates.push(this._batch);
|
||||||
}
|
}
|
||||||
this._batch = this._startBatch(update);
|
this._batch = this._startBatch(update);
|
||||||
}
|
}
|
||||||
this._updateItems[reviewerId] = {
|
// _startBatch assigns _updateItems. When _groupUpdates is calling,
|
||||||
|
// _batch and _updateItems are not set => _startBatch is called. The
|
||||||
|
// _startBatch method assigns _updateItems
|
||||||
|
const updateItems = this._updateItems!;
|
||||||
|
updateItems[reviewerId] = {
|
||||||
reviewer: update.reviewer,
|
reviewer: update.reviewer,
|
||||||
state: update.state,
|
state: update.state,
|
||||||
};
|
};
|
||||||
if (this._lastState[reviewerId]) {
|
if (this._lastState[reviewerId]) {
|
||||||
this._updateItems[reviewerId].prev_state = this._lastState[reviewerId];
|
updateItems[reviewerId].prev_state = this._lastState[reviewerId];
|
||||||
}
|
}
|
||||||
return newUpdates;
|
return newUpdates;
|
||||||
}, []);
|
}, [] as ParserBatchWithNonEmptyUpdates[]);
|
||||||
this._completeBatch();
|
// reviewer_updates always has at least 1 item
|
||||||
if (this._batch.updates && this._batch.updates.length) {
|
// (otherwise parse is not created) => updates.reduce calls callback
|
||||||
newUpdates.push(this._batch);
|
// at least once and callback assigns this._batch
|
||||||
|
const batch = this._batch!;
|
||||||
|
this._completeBatch(batch);
|
||||||
|
if (isParserBatchWithNonEmptyUpdates(batch)) {
|
||||||
|
newUpdates.push(batch);
|
||||||
}
|
}
|
||||||
this.result.reviewer_updates = newUpdates;
|
((this.result
|
||||||
|
.reviewer_updates as unknown) as ParserBatchWithNonEmptyUpdates[]) = newUpdates;
|
||||||
|
return newUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates update message for reviewer state change.
|
* Generates update message for reviewer state change.
|
||||||
*
|
|
||||||
* @param {string} prev previous reviewer state.
|
|
||||||
* @param {string} state current reviewer state.
|
|
||||||
* @return {string}
|
|
||||||
*/
|
*/
|
||||||
_getUpdateMessage(prev, state) {
|
private _getUpdateMessage(
|
||||||
if (prev === 'REMOVED' || !prev) {
|
prevReviewerState: string | undefined,
|
||||||
return 'Added to ' + state.toLowerCase() + ': ';
|
currentReviewerState: string
|
||||||
} else if (state === 'REMOVED') {
|
): string {
|
||||||
if (prev) {
|
if (prevReviewerState === 'REMOVED' || !prevReviewerState) {
|
||||||
return 'Removed from ' + prev.toLowerCase() + ': ';
|
return `Added to ${currentReviewerState.toLowerCase()}: `;
|
||||||
|
} else if (currentReviewerState === 'REMOVED') {
|
||||||
|
if (prevReviewerState) {
|
||||||
|
return `Removed from ${prevReviewerState.toLowerCase()}: `;
|
||||||
} else {
|
} else {
|
||||||
return 'Removed : ';
|
return 'Removed : ';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return `Moved from ${prevReviewerState.toLowerCase()} to ${currentReviewerState.toLowerCase()}: `;
|
||||||
'Moved from ' + prev.toLowerCase() + ' to ' + state.toLowerCase() + ': '
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groups updates for same category (eg CC->CC) into a hash arrays of
|
* Groups updates for same category (eg CC->CC) into a hash arrays of
|
||||||
* reviewers.
|
* reviewers.
|
||||||
*
|
|
||||||
* @param {!Array<!Object>} updates Array of ReviewerUpdateItemInfo.
|
|
||||||
* @return {!Object} Hash of arrays of AccountInfo, message as key.
|
|
||||||
*/
|
*/
|
||||||
_groupUpdatesByMessage(updates) {
|
_groupUpdatesByMessage(updates: UpdateItem[]): ReviewersGroupByMessage {
|
||||||
return updates.reduce((result, item) => {
|
return updates.reduce((result, item) => {
|
||||||
const message = this._getUpdateMessage(item.prev_state, item.state);
|
const message = this._getUpdateMessage(item.prev_state, item.state);
|
||||||
if (!result[message]) {
|
if (!result[message]) {
|
||||||
@@ -158,7 +226,7 @@ export class GrReviewerUpdatesParser {
|
|||||||
}
|
}
|
||||||
result[message].push(item.reviewer);
|
result[message].push(item.reviewer);
|
||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {} as ReviewersGroupByMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,18 +236,20 @@ export class GrReviewerUpdatesParser {
|
|||||||
* @see https://gerrit-review.googlesource.com/c/94490/
|
* @see https://gerrit-review.googlesource.com/c/94490/
|
||||||
*/
|
*/
|
||||||
_formatUpdates() {
|
_formatUpdates() {
|
||||||
for (const update of this.result.reviewer_updates) {
|
const reviewerUpdates = (this.result
|
||||||
|
.reviewer_updates as unknown) as ParserBatchWithNonEmptyUpdates[];
|
||||||
|
for (const update of reviewerUpdates) {
|
||||||
const grouppedReviewers = this._groupUpdatesByMessage(update.updates);
|
const grouppedReviewers = this._groupUpdatesByMessage(update.updates);
|
||||||
const newUpdates = [];
|
const newUpdates: {message: string; reviewers: AccountInfo[]}[] = [];
|
||||||
for (const message in grouppedReviewers) {
|
for (const message in grouppedReviewers) {
|
||||||
if (grouppedReviewers.hasOwnProperty(message)) {
|
if (hasOwnProperty(grouppedReviewers, message)) {
|
||||||
newUpdates.push({
|
newUpdates.push({
|
||||||
message,
|
message,
|
||||||
reviewers: grouppedReviewers[message],
|
reviewers: grouppedReviewers[message],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update.updates = newUpdates;
|
((update as unknown) as FormattedReviewerUpdateInfo).updates = newUpdates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +259,8 @@ export class GrReviewerUpdatesParser {
|
|||||||
* TODO(viktard): Remove when server-side serves reviewer updates like so.
|
* TODO(viktard): Remove when server-side serves reviewer updates like so.
|
||||||
*/
|
*/
|
||||||
_advanceUpdates() {
|
_advanceUpdates() {
|
||||||
const updates = this.result.reviewer_updates;
|
const updates = (this.result
|
||||||
|
.reviewer_updates as unknown) as FormattedReviewerUpdateInfo[];
|
||||||
const messages = this.result.messages;
|
const messages = this.result.messages;
|
||||||
messages.forEach((message, index) => {
|
messages.forEach((message, index) => {
|
||||||
const messageDate = parseDate(message.date).getTime();
|
const messageDate = parseDate(message.date).getTime();
|
||||||
@@ -207,9 +278,9 @@ export class GrReviewerUpdatesParser {
|
|||||||
parseDate(update.date).getTime() -
|
parseDate(update.date).getTime() -
|
||||||
MESSAGE_REVIEWERS_THRESHOLD_MILLIS;
|
MESSAGE_REVIEWERS_THRESHOLD_MILLIS;
|
||||||
update.date = new Date(timestamp)
|
update.date = new Date(timestamp)
|
||||||
.toISOString()
|
.toISOString()
|
||||||
.replace('T', ' ')
|
.replace('T', ' ')
|
||||||
.replace('Z', '000000');
|
.replace('Z', '000000') as Timestamp;
|
||||||
}
|
}
|
||||||
if (nextMessageDate && date > nextMessageDate) {
|
if (nextMessageDate && date > nextMessageDate) {
|
||||||
break;
|
break;
|
||||||
@@ -218,15 +289,12 @@ export class GrReviewerUpdatesParser {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse(change) {
|
static parse(change: ChangeInfo): ParsedChangeInfo {
|
||||||
if (
|
// TODO(TS): The !change condition should be removed when all files are converted to TS
|
||||||
!change ||
|
if (!change || !isChangeInfoParserInput(change)) {
|
||||||
!change.messages ||
|
|
||||||
!change.reviewer_updates ||
|
|
||||||
!change.reviewer_updates.length
|
|
||||||
) {
|
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = new GrReviewerUpdatesParser(change);
|
const parser = new GrReviewerUpdatesParser(change);
|
||||||
parser._filterRemovedMessages();
|
parser._filterRemovedMessages();
|
||||||
parser._groupUpdates();
|
parser._groupUpdates();
|
||||||
@@ -234,4 +302,4 @@ export class GrReviewerUpdatesParser {
|
|||||||
parser._advanceUpdates();
|
parser._advanceUpdates();
|
||||||
return parser.result;
|
return parser.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user