Append approval info to every comment-added stream event and hook

This change is driven by the RFC proposal[1] to show label
transitions more explicitly in Gerrit. Stream events only
provides label information on review events that contain
a vote transition.  When a user submits a review with only a
reply message the approvals information is not added to the
stream event and hooks. This change will make Gerrit append
approvals info to every comment-added event including the
ones that do not contain vote changes.

Since labels are shown for every event we will add an 'oldValue'
property to the approvalAttribute to distinguish when a vote
transition has occurred.  The 'oldValue' attribute contains
the previous vote score.  This attribute will only appear when
there is a vote transition.

The comment-added stream event will look like this:

  "approvals":[{"type":"Code-Review","description":"Code-Review",
  "value":"-1", "oldValue":"0"}, {"type":"Verified",
  "description":"Verified","value":"0"}],
  "comment":"Patch Set 1: Code-Review+1\n\nMy Message" ...

This change will also a '--$LabelName-oldValue' parameter to
corresponding comment-added change hook:

  hook[comment-added] output:
  --change I2cd8327360ff89338a4e7f0591bf0c037e9aa5db
  --is-draft false --change-url http://localhost:8080/201
  --change-owner John Smith (jsmith@gmail.com) --project okc
  --branch master --author John Smith (jsmith@gmail.com)
  --commit 55079187f7ce1c461de804c13f2dd71d64a85280
  --comment Patch Set 1: Code-Review-1 --Code-Review -1
  --Code-Review-oldValue 0 --Verified 0

[1] https://groups.google.com/d/msg/repo-discuss/soqmGjRpl-4/IIfAF4jbCQAJ

Bug: Issue 3220
Change-Id: Ibc1c64d70e790c7b507db79931f31fd77f25e276
This commit is contained in:
Khai Do
2015-06-04 14:04:27 -07:00
parent 5992220391
commit 71b58990e9
12 changed files with 452 additions and 29 deletions

View File

@@ -67,7 +67,7 @@ This is called whenever a draft change is published.
This is called whenever a comment is added to a change.
====
comment-added --change <change id> --is-draft <boolean> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --topic <topic> --author <comment author> --commit <commit> --comment <comment> [--<approval category id> <score> --<approval category id> <score> ...]
comment-added --change <change id> --is-draft <boolean> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --topic <topic> --author <comment author> --commit <commit> --comment <comment> [--<approval category id> <score> --<approval category id> <score> --<approval category id>-oldValue <score> ...]
====
=== change-merged

View File

@@ -140,6 +140,8 @@ description:: Human readable category of the approval.
value:: Value assigned by the approval, usually a numerical score.
oldValue:: The previous approval score, only present if the value changed as a result of this event.
grantedOn:: Time in seconds since the UNIX epoch when this approval
was added or last updated.

View File

@@ -0,0 +1,253 @@
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.server.event;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.project.Util.category;
import static com.google.gerrit.server.project.Util.value;
import static org.junit.Assert.fail;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.UserScopedEventListener;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.data.ApprovalAttribute;
import com.google.gerrit.server.events.CommentAddedEvent;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class CommentAddedEventIT extends AbstractDaemonTest {
@Inject
private IdentifiedUser.GenericFactory factory;
@Inject
private DynamicSet<UserScopedEventListener> source;
private final LabelType label = category("CustomLabel",
value(1, "Positive"),
value(0, "No score"),
value(-1, "Negative"));
private final LabelType pLabel = category("CustomLabel2",
value(1, "Positive"),
value(0, "No score"));
private RegistrationHandle eventListenerRegistration;
private CommentAddedEvent lastCommentAddedEvent;
@Before
public void setUp() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
AccountGroup.UUID anonymousUsers =
SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
Util.allow(cfg, Permission.forLabel(label.getName()), -1, 1, anonymousUsers,
"refs/heads/*");
Util.allow(cfg, Permission.forLabel(pLabel.getName()), 0, 1, anonymousUsers,
"refs/heads/*");
saveProjectConfig(project, cfg);
eventListenerRegistration = source.add(new UserScopedEventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof CommentAddedEvent) {
lastCommentAddedEvent = (CommentAddedEvent) event;
}
}
@Override
public CurrentUser getUser() {
return factory.create(user.id);
}
});
}
@After
public void cleanup() {
eventListenerRegistration.remove();
}
private void saveLabelConfig() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
cfg.getLabelSections().put(label.getName(), label);
cfg.getLabelSections().put(pLabel.getName(), pLabel);
saveProjectConfig(project, cfg);
}
@Test
public void newChangeWithVote() throws Exception {
saveLabelConfig();
// push a new change with -1 vote
PushOneCommit.Result r = createChange();
ReviewInput reviewInput = new ReviewInput().label(
label.getName(), (short)-1);
revision(r).review(reviewInput);
String newVote = lastCommentAddedEvent.approvals.get()[0].value;
String oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo("0");
assertThat(newVote).isEqualTo("-1");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: %s-1", label.getName()));
}
@Test
public void newPatchSetWithVote() throws Exception {
saveLabelConfig();
// push a new change
PushOneCommit.Result r = createChange();
ReviewInput reviewInput = new ReviewInput().message(label.getName());
revision(r).review(reviewInput);
// push a new revision with +1 vote
ChangeInfo c = get(r.getChangeId());
r = amendChange(c.changeId);
reviewInput = new ReviewInput().label(
label.getName(), (short)1);
revision(r).review(reviewInput);
String newVote = lastCommentAddedEvent.approvals.get()[0].value;
String oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo("0");
assertThat(newVote).isEqualTo("1");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 2: %s+1", label.getName()));
}
@Test
public void reviewChange() throws Exception {
saveLabelConfig();
// push a change
PushOneCommit.Result r = createChange();
// review with message only, do not apply votes
ReviewInput reviewInput = new ReviewInput().message(label.getName());
revision(r).review(reviewInput);
// reply message only so votes are excluded from comment
assertThat(lastCommentAddedEvent.approvals.get()).isNull();
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1:\n\n%s", label.getName()));
// transition from un-voted to -1 vote
reviewInput = new ReviewInput().label(label.getName(), -1);
revision(r).review(reviewInput);
String newVote = lastCommentAddedEvent.approvals.get()[0].value;
String oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo("0");
assertThat(newVote).isEqualTo("-1");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: %s-1", label.getName()));
// transition vote from -1 to 0
reviewInput = new ReviewInput().label(label.getName(), 0);
revision(r).review(reviewInput);
newVote = lastCommentAddedEvent.approvals.get()[0].value;
oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo("-1");
assertThat(newVote).isEqualTo("0");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: -%s", label.getName()));
// transition vote from 0 to 1
reviewInput = new ReviewInput().label(label.getName(), 1);
revision(r).review(reviewInput);
newVote = lastCommentAddedEvent.approvals.get()[0].value;
oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo("0");
assertThat(newVote).isEqualTo("1");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: %s+1", label.getName()));
// transition vote from 1 to -1
reviewInput = new ReviewInput().label(label.getName(), -1);
revision(r).review(reviewInput);
newVote = lastCommentAddedEvent.approvals.get()[0].value;
oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo("1");
assertThat(newVote).isEqualTo("-1");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: %s-1", label.getName()));
// review with message only, do not apply votes
reviewInput = new ReviewInput().message(label.getName());
revision(r).review(reviewInput);
newVote = lastCommentAddedEvent.approvals.get()[0].value;
oldVote = lastCommentAddedEvent.approvals.get()[0].oldValue;
assertThat(oldVote).isEqualTo(null); // no vote change so not included
assertThat(newVote).isEqualTo("-1");
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1:\n\n%s", label.getName()));
}
@Test
public void reviewChange_MultipleVotes() throws Exception {
saveLabelConfig();
PushOneCommit.Result r = createChange();
ReviewInput reviewInput = new ReviewInput().label(label.getName(), -1);
reviewInput.message = label.getName();
revision(r).review(reviewInput);
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: %s-1\n\n%s",
label.getName(), label.getName()));
reviewInput = new ReviewInput().label(pLabel.getName(), 1);
reviewInput.message = pLabel.getName();
revision(r).review(reviewInput);
c = get(r.getChangeId());
q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(lastCommentAddedEvent.comment).isEqualTo(
String.format("Patch Set 1: %s+1\n\n%s",
pLabel.getName(), pLabel.getName()));
assertThat(lastCommentAddedEvent.approvals.get()).hasLength(2);
for (ApprovalAttribute approval : lastCommentAddedEvent.approvals.get()) {
if (approval.type.equals(label.getName())) {
assertThat(approval.value).isEqualTo("-1");
assertThat(approval.oldValue).isNull();
} else if (approval.type.equals(pLabel.getName())) {
assertThat(approval.value).isEqualTo("1");
assertThat(approval.oldValue).isEqualTo("0");
} else {
fail("Unexpected label: " + approval.type);
}
}
}
}

View File

@@ -22,23 +22,38 @@ import static com.google.gerrit.server.project.Util.value;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.UserScopedEventListener;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.events.CommentAddedEvent;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class CustomLabelIT extends AbstractDaemonTest {
@Inject
private IdentifiedUser.GenericFactory factory;
@Inject
private DynamicSet<UserScopedEventListener> source;
private final LabelType label = category("CustomLabel",
value(1, "Positive"),
value(0, "No score"),
@@ -48,6 +63,9 @@ public class CustomLabelIT extends AbstractDaemonTest {
value(1, "Positive"),
value(0, "No score"));
private CommentAddedEvent lastCommentAddedEvent;
private RegistrationHandle eventListenerRegistration;
@Before
public void setUp() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
@@ -58,6 +76,26 @@ public class CustomLabelIT extends AbstractDaemonTest {
Util.allow(cfg, Permission.forLabel(P.getName()), 0, 1, anonymousUsers,
"refs/heads/*");
saveProjectConfig(project, cfg);
eventListenerRegistration = source.add(new UserScopedEventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof CommentAddedEvent) {
lastCommentAddedEvent = (CommentAddedEvent) event;
}
}
@Override
public CurrentUser getUser() {
return factory.create(user.id);
}
});
}
@After
public void cleanup() {
eventListenerRegistration.remove();
db.close();
}
@Test
@@ -124,13 +162,18 @@ public class CustomLabelIT extends AbstractDaemonTest {
.id(r.getChangeId())
.addReviewer(in);
revision(r).review(new ReviewInput().label(P.getName(), 0));
ReviewInput input = new ReviewInput().label(P.getName(), 0);
input.message = "foo";
revision(r).review(input);
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(P.getName());
assertThat(q.all).hasSize(2);
assertThat(q.disliked).isNull();
assertThat(q.rejected).isNull();
assertThat(q.blocking).isNull();
assertThat(lastCommentAddedEvent.comment).isEqualTo(
"Patch Set 1:\n\n" + input.message);
}
@Test

View File

@@ -423,7 +423,8 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener,
@Override
public void doCommentAddedHook(final Change change, Account account,
PatchSet patchSet, String comment, final Map<String, Short> approvals,
ReviewDb db) throws OrmException {
final Map<String, Short> oldApprovals, ReviewDb db)
throws OrmException {
CommentAddedEvent event = new CommentAddedEvent(change);
Supplier<AccountState> owner = getAccountSupplier(change.getOwner());
@@ -441,7 +442,8 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener,
ApprovalAttribute[] r = new ApprovalAttribute[approvals.size()];
int i = 0;
for (Map.Entry<String, Short> approval : approvals.entrySet()) {
r[i++] = getApprovalAttribute(labelTypes, approval);
r[i++] = getApprovalAttribute(labelTypes, approval,
oldApprovals);
}
return r;
}
@@ -473,11 +475,17 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener,
change.getProject()).getLabelTypes();
for (Map.Entry<String, Short> approval : approvals.entrySet()) {
LabelType lt = labelTypes.byLabel(approval.getKey());
if (lt != null) {
if (lt != null && approval.getValue() != null) {
addArg(args, "--" + lt.getName(), Short.toString(approval.getValue()));
if (oldApprovals != null && !oldApprovals.isEmpty()) {
Short oldValue = oldApprovals.get(approval.getKey());
if (oldValue != null) {
addArg(args, "--" + lt.getName() + "-oldValue",
Short.toString(oldValue));
}
}
}
}
runHook(change.getProject(), commentAddedHook, args);
}
@@ -856,18 +864,29 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener,
/**
* Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
* @param labelTypes
* @param approval
* @param oldApprovals
* @return object suitable for serialization to JSON
*/
private ApprovalAttribute getApprovalAttribute(LabelTypes labelTypes,
Entry<String, Short> approval) {
Entry<String, Short> approval,
Map<String, Short> oldApprovals) {
ApprovalAttribute a = new ApprovalAttribute();
a.type = approval.getKey();
if (oldApprovals != null && !oldApprovals.isEmpty()) {
if (oldApprovals.get(approval.getKey()) != null) {
a.oldValue = Short.toString(oldApprovals.get(approval.getKey()));
}
}
LabelType lt = labelTypes.byLabel(approval.getKey());
if (lt != null) {
a.description = lt.getName();
}
a.value = Short.toString(approval.getValue());
if (approval.getValue() != null) {
a.value = Short.toString(approval.getValue());
}
return a;
}

View File

@@ -60,13 +60,15 @@ public interface ChangeHooks {
* @param account The gerrit user who added the comment.
* @param patchSet The patchset this comment is related to.
* @param comment The comment given.
* @param approvals Map of label IDs to scores.
* @param approvals Map of label IDs to scores
* @param oldApprovals Map of label IDs to old approval scores
* @param db The review database.
* @throws OrmException
*/
void doCommentAddedHook(Change change, Account account,
PatchSet patchSet, String comment,
Map<String, Short> approvals, ReviewDb db)
Map<String, Short> approvals, Map<String, Short> oldApprovals,
ReviewDb db)
throws OrmException;
/**

View File

@@ -61,7 +61,8 @@ public final class DisabledChangeHooks implements ChangeHooks, EventDispatcher {
@Override
public void doCommentAddedHook(Change change, Account account,
PatchSet patchSet, String comment,
Map<String, Short> approvals, ReviewDb db) {
Map<String, Short> approvals, Map<String, Short> oldApprovals,
ReviewDb db) {
}
@Override

View File

@@ -21,6 +21,7 @@ import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
import com.google.common.base.MoreObjects;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -49,6 +50,7 @@ import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
@@ -68,6 +70,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -82,6 +85,7 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
LoggerFactory.getLogger(ChangeInserter.class);
private final ProjectControl.GenericFactory projectControlFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetUtil psUtil;
private final ChangeHooks hooks;
@@ -121,6 +125,7 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
@Inject
ChangeInserter(ProjectControl.GenericFactory projectControlFactory,
ChangeControl.GenericFactory changeControlFactory,
PatchSetInfoFactory patchSetInfoFactory,
PatchSetUtil psUtil,
ChangeHooks hooks,
@@ -133,6 +138,7 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
@Assisted RevCommit commit,
@Assisted String refName) {
this.projectControlFactory = projectControlFactory;
this.changeControlFactory = changeControlFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.psUtil = psUtil;
this.hooks = hooks;
@@ -355,7 +361,7 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
}
@Override
public void postUpdate(Context ctx) throws OrmException {
public void postUpdate(Context ctx) throws OrmException, NoSuchChangeException {
if (sendMail) {
Runnable sender = new Runnable() {
@Override
@@ -386,13 +392,33 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
}
}
/** For labels that are not set in this operation, show the "current" value
* of 0, and no oldValue as the value was not modified by this operation.
* For labels that are set in this operation, the value was modified, so
* show a transition from an oldValue of 0 to the new value.
**/
if (runHooks) {
ReviewDb db = ctx.getDb();
hooks.doPatchsetCreatedHook(change, patchSet, db);
if (approvals != null && !approvals.isEmpty()) {
ChangeControl changeControl = changeControlFactory.controlFor(
db, change, ctx.getUser());
List<LabelType> labels = changeControl.getLabelTypes().getLabelTypes();
Map<String, Short> allApprovals = new HashMap<>();
Map<String, Short> oldApprovals = new HashMap<>();
for (LabelType lt : labels){
allApprovals.put(lt.getName(), (short) 0);
oldApprovals.put(lt.getName(), null);
}
for (Map.Entry<String, Short> entry : approvals.entrySet()) {
if (entry.getValue() != 0) {
allApprovals.put(entry.getKey(), entry.getValue());
oldApprovals.put(entry.getKey(), (short) 0);
}
}
hooks.doCommentAddedHook(change,
ctx.getUser().asIdentifiedUser().getAccount(), patchSet, null,
approvals, db);
allApprovals, oldApprovals, db);
}
}
}

View File

@@ -21,6 +21,7 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -82,6 +83,7 @@ import org.slf4j.LoggerFactory;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -350,7 +352,8 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
private ChangeMessage message;
private List<PatchLineComment> comments = new ArrayList<>();
private List<String> labelDelta = new ArrayList<>();
private Map<String, Short> categories = new HashMap<>();
private Map<String, Short> approvals = new HashMap<>();
private Map<String, Short> oldApprovals = new HashMap<>();
private Op(PatchSet.Id psId, ReviewInput in) {
this.psId = psId;
@@ -393,7 +396,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
}
try {
hooks.doCommentAddedHook(notes.getChange(), user.getAccount(), ps,
message.getMessage(), categories, ctx.getDb());
message.getMessage(), approvals, oldApprovals, ctx.getDb());
} catch (OrmException e) {
log.warn("ChangeHook.doCommentAddedHook delivery failed", e);
}
@@ -514,43 +517,77 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
return drafts;
}
private Map<String, Short> approvalsByKey(
Collection<PatchSetApproval> patchsetApprovals) {
Map<String, Short> labels = new HashMap<>();
for (PatchSetApproval psa : patchsetApprovals) {
labels.put(psa.getLabel(), psa.getValue());
}
return labels;
}
private boolean updateLabels(ChangeContext ctx)
throws OrmException, ResourceConflictException {
Map<String, Short> labels = in.labels;
if (labels == null) {
labels = Collections.emptyMap();
}
Map<String, Short> inLabels = MoreObjects.firstNonNull(in.labels,
Collections.<String, Short> emptyMap());
List<PatchSetApproval> del = Lists.newArrayList();
List<PatchSetApproval> ups = Lists.newArrayList();
Map<String, PatchSetApproval> current = scanLabels(ctx, del);
// get all approvals in cases of quick approve vote
Map<String, Short> allApprovals = Collections.emptyMap();
if (current != null) {
allApprovals = approvalsByKey(current.values());
}
allApprovals.putAll(inLabels);
// get previous label votes
Map<String, Short> currentLabels = new HashMap<>();
for (Map.Entry<String, PatchSetApproval> ent : current.entrySet()) {
currentLabels.put(ent.getValue().getLabel(), ent.getValue().getValue());
}
Map<String, Short> previous = new HashMap<>();
for (Map.Entry<String, Short> ent : allApprovals.entrySet()) {
if (!currentLabels.containsKey(ent.getKey())) {
previous.put(ent.getKey(), (short)0);
} else {
previous.put(ent.getKey(), currentLabels.get(ent.getKey()));
}
}
ChangeUpdate update = ctx.getUpdate(psId);
LabelTypes labelTypes = ctx.getControl().getLabelTypes();
for (Map.Entry<String, Short> ent : labels.entrySet()) {
for (Map.Entry<String, Short> ent : allApprovals.entrySet()) {
String name = ent.getKey();
LabelType lt = checkNotNull(labelTypes.byLabel(name), name);
PatchSetApproval c = current.remove(lt.getName());
String normName = lt.getName();
approvals.put(normName, (short) 0);
if (ent.getValue() == null || ent.getValue() == 0) {
// User requested delete of this label.
oldApprovals.put(normName, null);
if (c != null) {
if (c.getValue() != 0) {
addLabelDelta(normName, (short) 0);
oldApprovals.put(normName, previous.get(normName));
}
del.add(c);
update.putApproval(ent.getKey(), (short) 0);
update.putApproval(normName, (short) 0);
}
} else if (c != null && c.getValue() != ent.getValue()) {
c.setValue(ent.getValue());
c.setGranted(ctx.getWhen());
ups.add(c);
addLabelDelta(normName, c.getValue());
categories.put(normName, c.getValue());
update.putApproval(ent.getKey(), ent.getValue());
oldApprovals.put(normName, previous.get(normName));
approvals.put(normName, c.getValue());
update.putApproval(normName, ent.getValue());
} else if (c != null && c.getValue() == ent.getValue()) {
current.put(normName, c);
oldApprovals.put(normName, null);
approvals.put(normName, c.getValue());
} else if (c == null) {
c = new PatchSetApproval(new PatchSetApproval.Key(
psId,
@@ -560,9 +597,10 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
c.setGranted(ctx.getWhen());
ups.add(c);
addLabelDelta(normName, c.getValue());
categories.put(normName, c.getValue());
oldApprovals.put(normName, previous.get(normName));
approvals.put(normName, c.getValue());
update.putReviewer(user.getAccountId(), REVIEWER);
update.putApproval(ent.getKey(), ent.getValue());
update.putApproval(normName, ent.getValue());
}
}

View File

@@ -18,7 +18,7 @@ public class ApprovalAttribute {
public String type;
public String description;
public String value;
public String oldValue;
public Long grantedOn;
public AccountAttribute by;
}

View File

@@ -620,6 +620,7 @@ public class EventFactory {
a.value = Short.toString(approval.getValue());
a.by = asAccountAttribute(approval.getAccountId());
a.grantedOn = approval.getGranted().getTime() / 1000L;
a.oldValue = null;
LabelType lt = labelTypes.byLabel(approval.getLabelId());
if (lt != null) {

View File

@@ -45,6 +45,8 @@ import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.LabelVote;
@@ -93,6 +95,7 @@ public class ReplaceOp extends BatchUpdate.Op {
private final PatchSetUtil psUtil;
private final ChangeData.Factory changeDataFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeKindCache changeKindCache;
private final ChangeMessagesUtil cmUtil;
private final ChangeHooks hooks;
@@ -127,6 +130,7 @@ public class ReplaceOp extends BatchUpdate.Op {
@AssistedInject
ReplaceOp(PatchSetUtil psUtil,
ChangeData.Factory changeDataFactory,
ChangeControl.GenericFactory changeControlFactory,
ChangeKindCache changeKindCache,
ChangeMessagesUtil cmUtil,
ChangeHooks hooks,
@@ -149,6 +153,7 @@ public class ReplaceOp extends BatchUpdate.Op {
@Assisted @Nullable PushCertificate pushCertificate) {
this.psUtil = psUtil;
this.changeDataFactory = changeDataFactory;
this.changeControlFactory = changeControlFactory;
this.changeKindCache = changeKindCache;
this.cmUtil = cmUtil;
this.hooks = hooks;
@@ -391,12 +396,45 @@ public class ReplaceOp extends BatchUpdate.Op {
hooks.doChangeMergedHook(change, account, newPatchSet, ctx.getDb(),
commit.getName());
}
if (!approvals.isEmpty()) {
hooks.doCommentAddedHook(change, account, newPatchSet, null, approvals,
ctx.getDb());
try{
runHook(ctx);
} catch (Exception e) {
log.warn("ChangeHook.doCommentAddedHook delivery failed", e);
}
}
private void runHook(final Context ctx) throws NoSuchChangeException,
OrmException {
if (approvals.isEmpty()) {
return;
}
/** For labels that are not set in this operation, show the "current" value
* of 0, and no oldValue as the value was not modified by this operation.
* For labels that are set in this operation, the value was modified, so
* show a transition from an oldValue of 0 to the new value.
**/
ChangeControl changeControl = changeControlFactory.controlFor(
ctx.getDb(), change, ctx.getUser());
List<LabelType> labels = changeControl.getLabelTypes().getLabelTypes();
Map<String, Short> allApprovals = new HashMap<>();
Map<String, Short> oldApprovals = new HashMap<>();
for (LabelType lt : labels){
allApprovals.put(lt.getName(), (short) 0);
oldApprovals.put(lt.getName(), null);
}
for (Map.Entry<String, Short> entry : approvals.entrySet()) {
if (entry.getValue() != 0) {
allApprovals.put(entry.getKey(), entry.getValue());
oldApprovals.put(entry.getKey(), (short) 0);
}
}
hooks.doCommentAddedHook(change,
ctx.getUser().asIdentifiedUser().getAccount(), newPatchSet, null,
allApprovals, oldApprovals, ctx.getDb());
}
private void sendMergedEmail(final Context ctx) {
sendEmailExecutor.submit(requestScopePropagator.wrap(new Runnable() {
@Override