Replace Mute/Unmute by Mark as Reviewed/Unreviewed (Part 1)

Muting a change de-highlights the change on the dashboard until a new
patch set is uploaded. Most users were confused about mute since they
had different expections of what mute would do. E.g. they expected
that mute would behave more similar to how mute in Gmail works, and
that the change would disappear from the dashboard until a new patch
is uploaded (see issue 7237).

Mute as it is implemented now behaves more as "Mark as Read" in email
clients. To make this function more intuitive for users this change
adds "Mark as Reviewed" as a replacement for "Mute". It's named
"Mark as Reviewed" as opposed to "Mark as Read" to be consistent with
the existing "reviewed" field in ChangeInfo. For muted changes the
"reviewed" flag is always set to true. The Mute / Unmute REST
endpoints are still kept to allow a migrating Gerrit hosts at
*.googlesource.com.

The REST endpoint for setting a change as reviewed is:

  PUT /changes/<change-id>/reviewed

This name was chosen to stay consistent with the REST endpoint for
setting reviewed flags on files which is:

  PUT /changes/<change>/revisions/<revision>/files/<file>/reviewed

Mute is represented by a star label called 'mute'. The new
MarkAsReviewed REST endpoint uses the new 'reviewed' label instead.
A follow-up change will add a schema migration that takes care about
renaming the existing mute labels.

The Unmute action removes the "mute" label from the change, so that
the "reviewed" flag is no longer overwritten with true. Without mute
star label the "reviewed" flag is again computed based on the change
state. This means renaming "Unmute" to "Mark as Unreviewed" would be
confusing since users would expect from "Mark as Unreviewed" that the
change is non-reviewed afterwards (and not automatically computed
which can result in reviewed = true).

This is why "Mark as Unreviewed" sets a "unreviewed" star label on the
change (in addition to removing the "reviewed" label). This way we
have 3 states:

1. neither "reviewed" nor "unreviewed" label: whether the change is
   highlighted in the dashboard is automatically computed based on the
   change state
2. "reviewed" label: change is not highlighted in the dashboard
3. "unreviewed" label: change is highlighted in the dashboard

Having the "reviewed" and "unreviewed" labels on a change (for the
same patch set) at the same time is invalid and is rejected by the
server.

This means MarkAsReviewed moves the change from state 1 or 3 to state
2, MarkAsUnreviewed moves the change from state 1 or 2 to state 3.

We could also support DELETE requests on /changes/<change>/reviewed
and /changes/<change>/unreviewed for moving the change from state 2
and 3 back to state 1 but we don't have a need for this at the moment,
hence this is not implemented in this change. The change will
automatically move back to state 1 when a new patch set is added. If
manually going back to state 1 is necessary one can always use the
generic stars API to remove the "reviewed" / "unreviewed" star.

Mute is not allowed on own changes and on changes which are ignored.
This limitation is removed for "Mark as Reviewed" so that this funtion
is completely orthogonal to the ignore functionality.

The migration to replace Mute/Unmute by Mark as Reviewed/Unreviewed is
done in multiple steps:

1. Add new REST endpoints for Mark As Reviewed/Unreviewed (this
   change)
2. Adapt PolyGerrit to use Mark As Reviewed/Unreviewed instead of
   Mute/Unmute
3. Run migration for Gerrit hosts at *.googlesource.com that renames
   existing mute labels
4. Remove Mute/Unmute functionality and add schema migration that
   renames existing mute labels

Bug: Issue 7237
Change-Id: I59f4156688dbf1582a33cb4e18f0adb54a73fe35
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2017-10-01 12:29:05 +02:00
parent 512eb25dcd
commit ceb673ecff
11 changed files with 421 additions and 2 deletions

View File

@@ -3409,6 +3409,103 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).mute(true);
}
@Test
public void markAsReviewed() throws Exception {
TestAccount user2 = accountCreator.user2();
PushOneCommit.Result r = createChange();
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
gApi.changes().id(r.getChangeId()).addReviewer(in);
setApiUser(user);
assertThat(gApi.changes().id(r.getChangeId()).get().reviewed).isNull();
gApi.changes().id(r.getChangeId()).markAsReviewed(true);
assertThat(gApi.changes().id(r.getChangeId()).get().reviewed).isTrue();
setApiUser(user2);
sender.clear();
amendChange(r.getChangeId());
setApiUser(user);
assertThat(gApi.changes().id(r.getChangeId()).get().reviewed).isNull();
List<Message> messages = sender.getMessages();
assertThat(messages).hasSize(1);
assertThat(messages.get(0).rcpt()).containsExactly(user.emailAddress);
}
@Test
public void cannotSetUnreviewedLabelForPatchSetThatAlreadyHasReviewedLabel() throws Exception {
String changeId = createChange().getChangeId();
setApiUser(user);
gApi.changes().id(changeId).markAsReviewed(true);
assertThat(gApi.changes().id(changeId).get().reviewed).isTrue();
exception.expect(BadRequestException.class);
exception.expectMessage(
"The labels "
+ StarredChangesUtil.REVIEWED_LABEL
+ "/"
+ 1
+ " and "
+ StarredChangesUtil.UNREVIEWED_LABEL
+ "/"
+ 1
+ " are mutually exclusive. Only one of them can be set.");
gApi.accounts()
.self()
.setStars(
changeId, new StarsInput(ImmutableSet.of(StarredChangesUtil.UNREVIEWED_LABEL + "/1")));
}
@Test
public void cannotSetReviewedLabelForPatchSetThatAlreadyHasUnreviewedLabel() throws Exception {
String changeId = createChange().getChangeId();
setApiUser(user);
gApi.changes().id(changeId).markAsReviewed(false);
assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
exception.expect(BadRequestException.class);
exception.expectMessage(
"The labels "
+ StarredChangesUtil.REVIEWED_LABEL
+ "/"
+ 1
+ " and "
+ StarredChangesUtil.UNREVIEWED_LABEL
+ "/"
+ 1
+ " are mutually exclusive. Only one of them can be set.");
gApi.accounts()
.self()
.setStars(
changeId, new StarsInput(ImmutableSet.of(StarredChangesUtil.REVIEWED_LABEL + "/1")));
}
@Test
public void setReviewedAndUnreviewedLabelsForDifferentPatchSets() throws Exception {
String changeId = createChange().getChangeId();
setApiUser(user);
gApi.changes().id(changeId).markAsReviewed(true);
assertThat(gApi.changes().id(changeId).get().reviewed).isTrue();
amendChange(changeId);
assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
gApi.changes().id(changeId).markAsReviewed(false);
assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
assertThat(gApi.accounts().self().getStars(changeId))
.containsExactly(
StarredChangesUtil.REVIEWED_LABEL + "/" + 1,
StarredChangesUtil.UNREVIEWED_LABEL + "/" + 2);
}
@Test
public void cannotSetInvalidLabel() throws Exception {
String changeId = createChange().getChangeId();