Update attention set behaviour when commenting on a thread.
Previously the rule worked like this:
When reply comment is added on a thread, every previous commenters
(except for the user leaving a reply) on the thread is added if:
- They are not the user leaving a reply
- If thread is unresolved
- If thread is resolved, and the commenter have not voted Code-Review
with a maximum vote yet.
After this change:
- If change owner replies and thread is unresolved: every commenter on
the thread is brought back to attention set
- If change owner replies and thread is resolved: only commenters that
haven't voted Code-Review are brought to attention set
- If reviewer replies and thread is unresolved, only bring owner to
attention set
- If reviewer replies and thread is resolved, bring all other commenters
who haven't voted Code-Review to attention set.
The logic doesn't account for users who don't have permissions to leave
a max CR vote. But this is the same for the current state as well. This
can be a further improvement in the future.
This is FE-only change, this will affect only comments published via
Web-UI. There will be a corresponding check to the BE logic in a
subsequent change.
Release-Notes: Attention set when commenting on a thread takes into
account owner and unresolved status.
Google-Bug-Id: b/347949247
Change-Id: I849a8dda069c74d97c75ce86706f35ea64717670
(cherry picked from commit f42012d837c1e15e0a598f41b66c999bae6ecb67)
diff --git a/Documentation/user-attention-set.txt b/Documentation/user-attention-set.txt
index 9825478..469fcdd 100644
--- a/Documentation/user-attention-set.txt
+++ b/Documentation/user-attention-set.txt
@@ -32,12 +32,20 @@
changing the attention set:
* If reviewers are added to a change, then they are added to the attention set.
- * Exception: A reviewer adding themselves along with a comment or vote.
+ ** Exception: A reviewer adding themselves along with a comment or vote.
* If an active change is submitted, abandoned or reset to "work in progress",
then all users are removed from the attention set.
* Replying (commenting, voting or just writing a change message) removes the
replying user from the attention set. And it adds all participants of comment
- conversations that the user is replying to.
+ conversations that the user is replying to. Specifically
+ ** If owner is replying and thread is resolved: all participants who have not
+ given Code-Review yet, are added to the attention set.
+ ** If owner is replying and thread is unresolved: all participants are added
+ to the attention set.
+ ** If non-owner is replying and thread is unresolved: only owner is added to
+ the attention set.
+ ** If non-owner is replying and thread is resolved: all participants who have
+ not given Code-Review yet, are added to the attention set.
* If a *reviewer* replies, then the change owner (and uploader) are added to the
attention set.
* For merged and abandoned changes the owner is added only when a human creates
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index c76e928..e6717b2 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -1687,9 +1687,10 @@
if (this.change.status === ChangeStatus.NEW) {
// Add everyone that the user is replying to in a comment thread.
- this.computeCommentAccounts(draftCommentThreads).forEach(id =>
- newAttention.add(id)
- );
+ this.computeCommentAccountsForAttention(
+ draftCommentThreads,
+ isUploader
+ ).forEach(id => newAttention.add(id));
// Remove the current user.
newAttention.delete(this.account._account_id);
// Add all new reviewers, but not the current reviewer, if they are also
@@ -1756,18 +1757,48 @@
return this.isOwner && addedIds.length >= minimum;
}
- computeCommentAccounts(threads: CommentThread[]) {
+ /**
+ * Pick previous commenters for addition to attention set.
+ *
+ * For every thread:
+ * - If owner replied and thread is unresolved: add all commenters.
+ * - If owner replied and thread is resolved: add commenters who need to vote.
+ * - If reviewer replied and thread is resolved: add commenters who need to vote.
+ * - If reviewer replied and thread is unresolved: only add owner
+ * (owner added outside this function).
+ */
+ computeCommentAccountsForAttention(
+ threads: CommentThread[],
+ isUploader: boolean
+ ) {
const crLabel = this.change?.labels?.[StandardLabels.CODE_REVIEW];
const maxCrVoteAccountIds = getMaxAccounts(crLabel).map(a => a._account_id);
const accountIds = new Set<AccountId>();
threads.forEach(thread => {
const unresolved = isUnresolved(thread);
+ let ignoreVoteCheck = false;
+ if (unresolved) {
+ if (this.isOwner || isUploader) {
+ // Owner replied but didn't resolve, we assume clarification was asked
+ // add everyone on the thread to attention set.
+ ignoreVoteCheck = true;
+ } else {
+ // Reviewer replied owner is still the one to act. No need to add
+ // commenters.
+ return;
+ }
+ }
+ // If thread is resolved, we only bring back the commenters who have not
+ // yet left max Code-Review vote.
thread.comments.forEach(comment => {
if (comment.author) {
// A comment author must have an account_id.
const authorId = comment.author._account_id!;
- const hasGivenMaxReviewVote = maxCrVoteAccountIds.includes(authorId);
- if (unresolved || !hasGivenMaxReviewVote) accountIds.add(authorId);
+ const needsToVote =
+ !maxCrVoteAccountIds.includes(authorId) && // Didn't give max-vote
+ this.uploader?._account_id !== authorId && // Not uploader
+ this.change?.owner._account_id !== authorId; // Not owner
+ if (ignoreVoteCheck || needsToVote) accountIds.add(authorId);
}
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index 7c8aece..d7f8946 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -1161,7 +1161,72 @@
);
});
- test('computeCommentAccounts', () => {
+ test('computeCommentAccountsForAttention owner comments', () => {
+ element.change = {
+ ...createChange(),
+ labels: {
+ 'Code-Review': {
+ all: [
+ {_account_id: 1 as AccountId, value: 0},
+ {_account_id: 2 as AccountId, value: 1},
+ {_account_id: 3 as AccountId, value: 2},
+ ],
+ values: {
+ '-2': 'This shall not be submitted',
+ '-1': 'I would prefer this is not submitted as is',
+ ' 0': 'No score',
+ '+1': 'Looks good to me, but someone else must approve',
+ '+2': 'Looks good to me, approved',
+ },
+ },
+ },
+ };
+ element.isOwner = true;
+ const threads = [
+ {
+ ...createCommentThread([
+ {
+ ...createComment(),
+ id: '1' as UrlEncodedCommentId,
+ author: {_account_id: 1 as AccountId},
+ unresolved: false,
+ },
+ {
+ ...createComment(),
+ id: '2' as UrlEncodedCommentId,
+ in_reply_to: '1' as UrlEncodedCommentId,
+ author: {_account_id: 2 as AccountId},
+ unresolved: true,
+ },
+ ]),
+ },
+ {
+ ...createCommentThread([
+ {
+ ...createComment(),
+ id: '3' as UrlEncodedCommentId,
+ author: {_account_id: 3 as AccountId},
+ unresolved: false,
+ },
+ {
+ ...createComment(),
+ id: '4' as UrlEncodedCommentId,
+ in_reply_to: '3' as UrlEncodedCommentId,
+ author: {_account_id: 4 as AccountId},
+ unresolved: false,
+ },
+ ]),
+ },
+ ];
+ const actualAccounts = [
+ ...element.computeCommentAccountsForAttention(threads, false),
+ ];
+ // Account 3 is not included, because the comment is resolved *and* they
+ // have given the highest possible vote on the Code-Review label.
+ assert.sameMembers(actualAccounts, [1, 2, 4]);
+ });
+
+ test('computeCommentAccountsForAttention reviewer comments', () => {
element.change = {
...createChange(),
labels: {
@@ -1211,16 +1276,30 @@
...createComment(),
id: '4' as UrlEncodedCommentId,
in_reply_to: '3' as UrlEncodedCommentId,
+ author: element.change.owner,
+ unresolved: false,
+ },
+ {
+ ...createComment(),
+ id: '5' as UrlEncodedCommentId,
+ in_reply_to: '4' as UrlEncodedCommentId,
author: {_account_id: 4 as AccountId},
unresolved: false,
},
]),
},
];
- const actualAccounts = [...element.computeCommentAccounts(threads)];
+ const actualAccounts = [
+ ...element.computeCommentAccountsForAttention(threads, false),
+ ];
+ // Accounts 1 and 2 are not included, because the thread is still unresolved
+ // and the new comment is from another reviewer.
// Account 3 is not included, because the comment is resolved *and* they
// have given the highest possible vote on the Code-Review label.
- assert.sameMembers(actualAccounts, [1, 2, 4]);
+ // element.change.owner is similarly not included, because they don't need
+ // to vote. (In the overall logic owner is added as part of
+ // computeNewAttention)
+ assert.sameMembers(actualAccounts, [4]);
});
test('label picker', async () => {