blob: 50c96401b1ca1de4e5a63844650e16e28473a477 [file] [log] [blame]
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {MessageTag} from '../constants/constants';
import {
AccountInfo,
ChangeId,
ChangeInfo,
ChangeMessage,
ChangeMessageInfo,
PatchSetNum,
} from '../types/common';
import {ParsedChangeInfo} from '../types/types';
import {LabelExtreme, PATCH_SET_PREFIX_PATTERN} from './comment-util';
import {hasOwnProperty} from './common-util';
import {getVotingRange, StandardLabels} from './label-util';
export const VOTE_RESET_TEXT = '0 (vote reset)';
export const LABEL_TITLE_SCORE_PATTERN =
/^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.:]?$/;
export interface Score {
label?: string;
value?: string;
}
function getRevertChangeIdFromMessage(msg: ChangeMessageInfo): ChangeId {
const REVERT_REGEX =
/^Created a revert of this change as .*?(I[0-9a-f]{40})$/;
const changeId = msg.message.match(REVERT_REGEX)?.[1];
if (!changeId) throw new Error('revert changeId not found');
return changeId as ChangeId;
}
export function getRevertCreatedChangeIds(messages: ChangeMessageInfo[]) {
return messages
.filter(m => m.tag === MessageTag.TAG_REVERT)
.map(m => getRevertChangeIdFromMessage(m));
}
export function getScores(
message?: ChangeMessage,
labelExtremes?: LabelExtreme
): Score[] {
if (!message || !message.message || !labelExtremes) {
return [];
}
const line = message.message.split('\n', 1)[0];
const patchSetPrefix = PATCH_SET_PREFIX_PATTERN;
if (!line.match(patchSetPrefix)) {
return [];
}
const scoresRaw = line.split(patchSetPrefix)[1];
if (!scoresRaw) {
return [];
}
return scoresRaw
.split(' ')
.map(s => s.match(LABEL_TITLE_SCORE_PATTERN))
.filter(ms => ms && ms.length === 4 && hasOwnProperty(labelExtremes, ms[2]))
.map(ms => {
const label = ms?.[2];
const value = ms?.[1] === '-' ? VOTE_RESET_TEXT : ms?.[3];
return {label, value};
});
}
/**
* Extracts Code-Review votes from change messages, specifically those posted
* by the provided `account`.
* @param change The change info.
* @param account The account for which to extract the votes.
* @return A map where keys are patch set numbers and values are objects
* containing the label ('Code-Review') and the numeric vote value.
*/
export function getCodeReviewVotesFromMessage(
change?: ChangeInfo | ParsedChangeInfo,
account?: AccountInfo
): Map<PatchSetNum, Score> {
const codeReviewVotes = new Map<PatchSetNum, Score>();
if (!change?.messages || !change?.labels || !account) {
return codeReviewVotes;
}
const labelExtremes: LabelExtreme = {};
for (const labelName of Object.keys(change.labels)) {
const labelInfo = change.labels[labelName];
const range = getVotingRange(labelInfo);
if (range) {
labelExtremes[labelName] = range;
}
}
for (const message of change.messages) {
if (message.author?._account_id !== account._account_id) {
continue;
}
if (!message._revision_number) continue;
const scores = getScores(message as ChangeMessage, labelExtremes);
for (const score of scores) {
if (score.label === StandardLabels.CODE_REVIEW && score.value) {
const value = score.value === VOTE_RESET_TEXT ? '0' : score.value;
codeReviewVotes.set(message._revision_number, {
label: score.label,
value,
});
}
}
}
return codeReviewVotes;
}