Add getCodeReviewVotesFromMessage method
We will use this method to render the votes from the user in the
patchset picker.
Release-Notes: skip
Google-bug-id: b/230605262
Change-Id: I4117d023b40af3d6863aaad236b44abe8ac9b05d
diff --git a/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
index 05492dd..2d76e47 100644
--- a/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
+++ b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
@@ -8,27 +8,14 @@
import {css, html, LitElement, nothing} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {ChangeInfo, PatchSetNumber} from '../../../api/rest-api';
-import {
- LabelExtreme,
- PATCH_SET_PREFIX_PATTERN,
-} from '../../../utils/comment-util';
-import {hasOwnProperty} from '../../../utils/common-util';
+import {LabelExtreme} from '../../../utils/comment-util';
import {getTriggerVotes} from '../../../utils/label-util';
import {ChangeMessage} from '../../../types/common';
import {CheckRun} from '../../../api/checks';
import {subscribe} from '../../lit/subscription-controller';
import {resolve} from '../../../models/dependency';
import {changeModelToken} from '../../../models/change/change-model';
-
-const VOTE_RESET_TEXT = '0 (vote reset)';
-
-interface Score {
- label?: string;
- value?: string;
-}
-
-export const LABEL_TITLE_SCORE_PATTERN =
- /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.:]?$/;
+import {getScores, Score, VOTE_RESET_TEXT} from '../../../utils/message-util';
@customElement('gr-message-scores')
export class GrMessageScores extends LitElement {
@@ -116,7 +103,7 @@
}
override render() {
- const scores = this._getScores(this.message, this.labelExtremes);
+ const scores = getScores(this.message, this.labelExtremes);
const triggerVotes = getTriggerVotes(this.change);
return scores.map(score => this.renderScore(score, triggerVotes));
}
@@ -186,32 +173,6 @@
}
return classes.join(' ');
}
-
- _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};
- });
- }
}
declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index 390ebff..e55631a 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -33,7 +33,6 @@
LabelExtreme,
PATCH_SET_PREFIX_PATTERN,
} from '../../../utils/comment-util';
-import {LABEL_TITLE_SCORE_PATTERN} from '../gr-message-scores/gr-message-scores';
import {getAppContext} from '../../../services/app-context';
import {pluralize} from '../../../utils/string-util';
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
@@ -53,6 +52,7 @@
import {configModelToken} from '../../../models/config/config-model';
import {userModelToken} from '../../../models/user/user-model';
import {subscribe} from '../../lit/subscription-controller';
+import {LABEL_TITLE_SCORE_PATTERN} from '../../../utils/message-util';
const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
diff --git a/polygerrit-ui/app/utils/message-util.ts b/polygerrit-ui/app/utils/message-util.ts
index fffe612..391e1d2 100644
--- a/polygerrit-ui/app/utils/message-util.ts
+++ b/polygerrit-ui/app/utils/message-util.ts
@@ -4,7 +4,26 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {MessageTag} from '../constants/constants';
-import {ChangeId, ChangeMessageInfo} from '../types/common';
+import {
+ AccountInfo,
+ ChangeId,
+ ChangeInfo,
+ ChangeMessage,
+ ChangeMessageInfo,
+ PatchSetNum,
+} from '../types/common';
+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 =
@@ -19,3 +38,76 @@
.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,
+ 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;
+}
diff --git a/polygerrit-ui/app/utils/message-util_test.ts b/polygerrit-ui/app/utils/message-util_test.ts
index 64d765a..bed9376 100644
--- a/polygerrit-ui/app/utils/message-util_test.ts
+++ b/polygerrit-ui/app/utils/message-util_test.ts
@@ -3,50 +3,191 @@
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {getRevertCreatedChangeIds} from './message-util';
+import {
+ getCodeReviewVotesFromMessage,
+ getRevertCreatedChangeIds,
+} from './message-util';
import {assert} from '@open-wc/testing';
import {MessageTag} from '../constants/constants';
-import {ChangeId, ReviewInputTag} from '../api/rest-api';
-import {createChangeMessage} from '../test/test-data-generators';
+import {
+ AccountInfo,
+ ChangeId,
+ ChangeInfo,
+ ChangeMessageInfo,
+ LabelNameToInfoMap,
+ PatchSetNum,
+ ReviewInputTag,
+} from '../api/rest-api';
+import {
+ createAccountWithId,
+ createChange,
+ createChangeMessage,
+ createDetailedLabelInfo,
+} from '../test/test-data-generators';
suite('message-util tests', () => {
- test('getRevertCreatedChangeIds', () => {
- const messages = [
- {
- ...createChangeMessage(),
- message:
- 'Created a revert of this change as If02ca1cd494579d6bb92a157bf1819e3689cd6b1',
- tag: MessageTag.TAG_REVERT as ReviewInputTag,
- },
- {
- ...createChangeMessage(),
- message: 'Created a revert of this change as abc',
- tag: undefined,
- },
- ];
+ suite('getRevertCreatedChangeIds', () => {
+ test('getRevertCreatedChangeIds', () => {
+ const messages = [
+ {
+ ...createChangeMessage(),
+ message:
+ 'Created a revert of this change as If02ca1cd494579d6bb92a157bf1819e3689cd6b1',
+ tag: MessageTag.TAG_REVERT as ReviewInputTag,
+ },
+ {
+ ...createChangeMessage(),
+ message: 'Created a revert of this change as abc',
+ tag: undefined,
+ },
+ ];
- assert.deepEqual(getRevertCreatedChangeIds(messages), [
- 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1' as ChangeId,
- ]);
+ assert.deepEqual(getRevertCreatedChangeIds(messages), [
+ 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1' as ChangeId,
+ ]);
+ });
+
+ test('getRevertCreatedChangeIds with extra spam', () => {
+ const messages = [
+ {
+ ...createChangeMessage(),
+ message:
+ 'Created a revert of this change as IIf02ca1cd494579d6bb92a157bf1819e3689cd6b1',
+ tag: MessageTag.TAG_REVERT as ReviewInputTag,
+ },
+ {
+ ...createChangeMessage(),
+ message: 'Created a revert of this change as abc',
+ tag: undefined,
+ },
+ ];
+
+ assert.deepEqual(getRevertCreatedChangeIds(messages), [
+ 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1' as ChangeId,
+ ]);
+ });
});
- test('getRevertCreatedChangeIds with extra spam', () => {
- const messages = [
- {
- ...createChangeMessage(),
- message:
- 'Created a revert of this change as IIf02ca1cd494579d6bb92a157bf1819e3689cd6b1',
- tag: MessageTag.TAG_REVERT as ReviewInputTag,
- },
- {
- ...createChangeMessage(),
- message: 'Created a revert of this change as abc',
- tag: undefined,
- },
- ];
+ suite('getCodeReviewVotesFromMessage', () => {
+ const account1: AccountInfo = {
+ ...createAccountWithId(1),
+ };
+ const account2: AccountInfo = {
+ ...createAccountWithId(2),
+ };
- assert.deepEqual(getRevertCreatedChangeIds(messages), [
- 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1' as ChangeId,
- ]);
+ const labels: LabelNameToInfoMap = {
+ 'Code-Review': createDetailedLabelInfo(),
+ };
+
+ function createMessage(
+ author: AccountInfo,
+ message: string,
+ ps: number
+ ): ChangeMessageInfo {
+ return {
+ ...createChangeMessage(),
+ author,
+ message,
+ _revision_number: ps as PatchSetNum,
+ };
+ }
+
+ test('no messages', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.equal(actual.size, 0);
+ });
+
+ test('no messages from account', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [createMessage(account2, 'Patch Set 1: Code-Review+1', 1)],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.equal(actual.size, 0);
+ });
+
+ test('one message with code review vote', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [createMessage(account1, 'Patch Set 1: Code-Review+1', 1)],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.deepEqual(
+ actual,
+ new Map([[1 as PatchSetNum, {label: 'Code-Review', value: '+1'}]])
+ );
+ });
+
+ test('vote reset', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [createMessage(account1, 'Patch Set 1: Code-Review-1', 1)],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.deepEqual(
+ actual,
+ new Map([[1 as PatchSetNum, {label: 'Code-Review', value: '-1'}]])
+ );
+ });
+
+ test('latest message wins for same patchset', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [
+ createMessage(account1, 'Patch Set 1: Code-Review-1', 1),
+ createMessage(account1, 'Patch Set 1: Code-Review+1', 1),
+ ],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.deepEqual(
+ actual,
+ new Map([[1 as PatchSetNum, {label: 'Code-Review', value: '+1'}]])
+ );
+ });
+
+ test('messages from different users', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [
+ createMessage(account1, 'Patch Set 1: Code-Review+1', 1),
+ createMessage(account2, 'Patch Set 1: Code-Review-1', 1),
+ ],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.deepEqual(
+ actual,
+ new Map([[1 as PatchSetNum, {label: 'Code-Review', value: '+1'}]])
+ );
+ });
+
+ test('messages for different patchsets', () => {
+ const change: ChangeInfo = {
+ ...createChange(),
+ messages: [
+ createMessage(account1, 'Patch Set 1: Code-Review-1', 1),
+ createMessage(account1, 'Patch Set 2: Code-Review+1', 2),
+ ],
+ labels,
+ };
+ const actual = getCodeReviewVotesFromMessage(change, account1);
+ assert.deepEqual(
+ actual,
+ new Map([
+ [1 as PatchSetNum, {label: 'Code-Review', value: '-1'}],
+ [2 as PatchSetNum, {label: 'Code-Review', value: '+1'}],
+ ])
+ );
+ });
});
});