Merge changes from topic 'star-labels-part-2'
* changes: Support ignore label that suppresses notifications on update Include star labels into ChangeInfo
This commit is contained in:
@@ -43,6 +43,24 @@ be updated from there.
|
||||
|
||||
The default star is represented by the special star label 'star'.
|
||||
|
||||
[[ignore-star]]
|
||||
== Ignore Star
|
||||
|
||||
If the ignore star is set by a user, this user gets no email
|
||||
notifications for updates of that change, even if this user is a
|
||||
reviewer of the change or the change is matched by a project watch of
|
||||
the user.
|
||||
|
||||
Since changes can only be ignored once they are created, users that
|
||||
watch a project will always get the email notifications for the change
|
||||
creation. Only then the change can be ignored.
|
||||
|
||||
Users that are added as reviewer to a change that they have ignored
|
||||
will be notified about this, so that they know about the review
|
||||
request. They can the decide to remove the ignore star.
|
||||
|
||||
The ignore star is represented by the special star label 'ignore'.
|
||||
|
||||
[[query-stars]]
|
||||
== Query Stars
|
||||
|
||||
|
@@ -24,6 +24,7 @@ import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithSecondUserId;
|
||||
import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
|
||||
import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
|
||||
import static com.google.gerrit.server.StarredChangesUtil.IGNORE_LABEL;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
@@ -36,6 +37,7 @@ import com.google.gerrit.acceptance.AccountCreator;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.extensions.api.accounts.EmailInput;
|
||||
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
||||
import com.google.gerrit.extensions.api.changes.StarsInput;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
@@ -53,6 +55,7 @@ import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import com.google.gerrit.testutil.FakeEmailSender.Message;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
@@ -180,12 +183,14 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
.starChange(triplet);
|
||||
ChangeInfo change = info(triplet);
|
||||
assertThat(change.starred).isTrue();
|
||||
assertThat(change.stars).contains(DEFAULT_LABEL);
|
||||
|
||||
gApi.accounts()
|
||||
.self()
|
||||
.unstarChange(triplet);
|
||||
change = info(triplet);
|
||||
assertThat(change.starred).isNull();
|
||||
assertThat(change.stars).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -199,6 +204,8 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "red", "blue")));
|
||||
ChangeInfo change = info(triplet);
|
||||
assertThat(change.starred).isTrue();
|
||||
assertThat(change.stars)
|
||||
.containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
|
||||
assertThat(gApi.accounts().self().getStars(triplet))
|
||||
.containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
|
||||
List<ChangeInfo> starredChanges =
|
||||
@@ -207,12 +214,15 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
ChangeInfo starredChange = starredChanges.get(0);
|
||||
assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
|
||||
assertThat(starredChange.starred).isTrue();
|
||||
assertThat(starredChange.stars)
|
||||
.containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
|
||||
|
||||
gApi.accounts().self().setStars(triplet,
|
||||
new StarsInput(ImmutableSet.of("yellow"),
|
||||
ImmutableSet.of(DEFAULT_LABEL, "blue")));
|
||||
change = info(triplet);
|
||||
assertThat(change.starred).isNull();
|
||||
assertThat(change.stars).containsExactly("red", "yellow").inOrder();
|
||||
assertThat(gApi.accounts().self().getStars(triplet)).containsExactly(
|
||||
"red", "yellow").inOrder();
|
||||
starredChanges = gApi.accounts().self().getStarredChanges();
|
||||
@@ -220,6 +230,7 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
starredChange = starredChanges.get(0);
|
||||
assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
|
||||
assertThat(starredChange.starred).isNull();
|
||||
assertThat(starredChange.stars).containsExactly("red", "yellow").inOrder();
|
||||
|
||||
setApiUser(user);
|
||||
exception.expect(AuthException.class);
|
||||
@@ -239,6 +250,70 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
"another invalid label")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void starWithDefaultAndIgnoreLabel() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
String triplet = project.get() + "~master~" + r.getChangeId();
|
||||
exception.expect(BadRequestException.class);
|
||||
exception.expectMessage("The labels " + DEFAULT_LABEL
|
||||
+ " and " + IGNORE_LABEL + " are mutually exclusive."
|
||||
+ " Only one of them can be set.");
|
||||
gApi.accounts().self().setStars(triplet,
|
||||
new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "blue", IGNORE_LABEL)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoreChange() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
|
||||
AddReviewerInput in = new AddReviewerInput();
|
||||
in.reviewer = user.email;
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.addReviewer(in);
|
||||
|
||||
TestAccount user2 = accounts.user2();
|
||||
in = new AddReviewerInput();
|
||||
in.reviewer = user2.email;
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.addReviewer(in);
|
||||
|
||||
setApiUser(user);
|
||||
gApi.accounts().self().setStars(r.getChangeId(),
|
||||
new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
|
||||
|
||||
sender.clear();
|
||||
setApiUser(admin);
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.abandon();
|
||||
List<Message> messages = sender.getMessages();
|
||||
assertThat(messages).hasSize(1);
|
||||
assertThat(messages.get(0).rcpt()).containsExactly(user2.emailAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addReviewerToIgnoredChange() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
|
||||
setApiUser(user);
|
||||
gApi.accounts().self().setStars(r.getChangeId(),
|
||||
new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
|
||||
|
||||
sender.clear();
|
||||
setApiUser(admin);
|
||||
|
||||
AddReviewerInput in = new AddReviewerInput();
|
||||
in.reviewer = user.email;
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.addReviewer(in);
|
||||
List<Message> messages = sender.getMessages();
|
||||
assertThat(messages).hasSize(1);
|
||||
assertThat(messages.get(0).rcpt()).containsExactly(user.emailAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suggestAccounts() throws Exception {
|
||||
String adminUsername = "admin";
|
||||
|
@@ -36,6 +36,7 @@ public class ChangeInfo {
|
||||
public Timestamp updated;
|
||||
public Timestamp submitted;
|
||||
public Boolean starred;
|
||||
public Collection<String> stars;
|
||||
public Boolean reviewed;
|
||||
public SubmitType submitType;
|
||||
public Boolean mergeable;
|
||||
|
@@ -95,7 +95,7 @@ public class AccountDashboardScreen extends Screen implements ChangeListScreen {
|
||||
}
|
||||
|
||||
private static String queryIncoming(String who) {
|
||||
return "is:open reviewer:" + who + " -owner:" + who;
|
||||
return "is:open reviewer:" + who + " -owner:" + who + " -star:ignore";
|
||||
}
|
||||
|
||||
private static String queryClosed(String who) {
|
||||
|
@@ -65,11 +65,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@@ -116,6 +113,13 @@ public class StarredChangesUtil {
|
||||
Joiner.on(", ").join(invalidLabels)));
|
||||
}
|
||||
|
||||
static IllegalLabelException mutuallyExclusiveLabels(String label1,
|
||||
String label2) {
|
||||
return new IllegalLabelException(
|
||||
String.format("The labels %s and %s are mutually exclusive."
|
||||
+ " Only one of them can be set.", label1, label2));
|
||||
}
|
||||
|
||||
IllegalLabelException(String message) {
|
||||
super(message);
|
||||
}
|
||||
@@ -125,6 +129,7 @@ public class StarredChangesUtil {
|
||||
LoggerFactory.getLogger(StarredChangesUtil.class);
|
||||
|
||||
public static final String DEFAULT_LABEL = "star";
|
||||
public static final String IGNORE_LABEL = "ignore";
|
||||
public static final ImmutableSortedSet<String> DEFAULT_LABELS =
|
||||
ImmutableSortedSet.of(DEFAULT_LABEL);
|
||||
|
||||
@@ -180,6 +185,7 @@ public class StarredChangesUtil {
|
||||
if (labels.isEmpty()) {
|
||||
deleteRef(repo, refName, oldObjectId);
|
||||
} else {
|
||||
checkMutuallyExclusiveLabels(labels);
|
||||
updateLabels(repo, refName, oldObjectId, labels);
|
||||
}
|
||||
|
||||
@@ -289,18 +295,6 @@ public class StarredChangesUtil {
|
||||
return changeData.get(0).stars();
|
||||
}
|
||||
|
||||
public Set<Account.Id> byChangeFromIndex(Change.Id changeId, String label)
|
||||
throws OrmException, NoSuchChangeException {
|
||||
Set<Account.Id> accounts = new HashSet<>();
|
||||
for (Map.Entry<Account.Id, Collection<String>> e : byChangeFromIndex(
|
||||
changeId).asMap().entrySet()) {
|
||||
if (e.getValue().contains(label)) {
|
||||
accounts.add(e.getKey());
|
||||
}
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ResultSet<Change.Id> queryFromIndex(final Account.Id accountId) {
|
||||
try {
|
||||
@@ -380,6 +374,13 @@ public class StarredChangesUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkMutuallyExclusiveLabels(Set<String> labels) {
|
||||
if (labels.containsAll(ImmutableSet.of(DEFAULT_LABEL, IGNORE_LABEL))) {
|
||||
throw IllegalLabelException.mutuallyExclusiveLabels(DEFAULT_LABEL,
|
||||
IGNORE_LABEL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateLabels(Set<String> labels) {
|
||||
if (labels == null) {
|
||||
return;
|
||||
|
@@ -89,6 +89,7 @@ import com.google.gerrit.server.ChangeMessagesUtil;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.GpgException;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.StarredChangesUtil;
|
||||
import com.google.gerrit.server.WebLinks;
|
||||
import com.google.gerrit.server.account.AccountLoader;
|
||||
import com.google.gerrit.server.api.accounts.AccountInfoComparator;
|
||||
@@ -377,7 +378,6 @@ public class ChangeJson {
|
||||
return info;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private ChangeInfo toChangeInfo(ChangeData cd,
|
||||
Optional<PatchSet.Id> limitToPsId) throws PatchListNotAvailableException,
|
||||
GpgException, OrmException, IOException {
|
||||
@@ -421,9 +421,17 @@ public class ChangeJson {
|
||||
out.created = in.getCreatedOn();
|
||||
out.updated = in.getLastUpdatedOn();
|
||||
out._number = in.getId().get();
|
||||
out.starred = user.getStarredChanges().contains(in.getId())
|
||||
|
||||
if (user.isIdentifiedUser()) {
|
||||
Collection<String> stars = cd.stars().get(user.getAccountId());
|
||||
out.starred = stars.contains(StarredChangesUtil.DEFAULT_LABEL)
|
||||
? true
|
||||
: null;
|
||||
if (!stars.isEmpty()) {
|
||||
out.stars = stars;
|
||||
}
|
||||
}
|
||||
|
||||
if (in.getStatus().isOpen() && has(REVIEWED) && user.isIdentifiedUser()) {
|
||||
Account.Id accountId = user.getAccountId();
|
||||
out.reviewed = cd.reviewedBy().contains(accountId) ? true : null;
|
||||
|
@@ -71,9 +71,16 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
||||
static final Schema<ChangeData> V29 =
|
||||
schema(V28, ChangeField.HASHTAG_CASE_AWARE);
|
||||
|
||||
@Deprecated
|
||||
static final Schema<ChangeData> V30 =
|
||||
schema(V29, ChangeField.STAR, ChangeField.STARBY);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static final Schema<ChangeData> V31 = new Schema.Builder<ChangeData>()
|
||||
.add(V30)
|
||||
.remove(ChangeField.STARREDBY)
|
||||
.build();
|
||||
|
||||
public static final String NAME = "changes";
|
||||
public static final ChangeSchemaDefinitions INSTANCE =
|
||||
new ChangeSchemaDefinitions();
|
||||
|
@@ -16,6 +16,7 @@ package com.google.gerrit.server.mail;
|
||||
|
||||
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
@@ -29,6 +30,7 @@ import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.StarredChangesUtil;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.mail.ProjectWatch.Watchers;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
import com.google.gerrit.server.patch.PatchListEntry;
|
||||
@@ -49,9 +51,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
@@ -304,11 +308,22 @@ public abstract class ChangeEmail extends NotificationEmail {
|
||||
}
|
||||
|
||||
try {
|
||||
// BCC anyone who has starred this change.
|
||||
// BCC anyone who has starred this change
|
||||
// and remove anyone who has ignored this change.
|
||||
//
|
||||
for (Account.Id accountId : args.starredChangesUtil.byChangeFromIndex(
|
||||
change.getId(), StarredChangesUtil.DEFAULT_LABEL)) {
|
||||
super.add(RecipientType.BCC, accountId);
|
||||
Multimap<Account.Id, String> stars =
|
||||
args.starredChangesUtil.byChangeFromIndex(change.getId());
|
||||
for (Map.Entry<Account.Id, Collection<String>> e :
|
||||
stars.asMap().entrySet()) {
|
||||
if (e.getValue().contains(StarredChangesUtil.DEFAULT_LABEL)) {
|
||||
super.add(RecipientType.BCC, e.getKey());
|
||||
}
|
||||
if (e.getValue().contains(StarredChangesUtil.IGNORE_LABEL)) {
|
||||
AccountState accountState = args.accountCache.get(e.getKey());
|
||||
if (accountState != null) {
|
||||
removeUser(accountState.getAccount());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (OrmException | NoSuchChangeException err) {
|
||||
// Just don't BCC everyone. Better to send a partial message to those
|
||||
|
@@ -486,7 +486,7 @@ public abstract class OutgoingEmail {
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
private void removeUser(Account user) {
|
||||
protected void removeUser(Account user) {
|
||||
String fromEmail = user.getPreferredEmail();
|
||||
for (Iterator<Address> j = smtpRcptTo.iterator(); j.hasNext();) {
|
||||
if (j.next().email.equals(fromEmail)) {
|
||||
|
Reference in New Issue
Block a user