blob: 358ce922cdbc2c0c00a6817038ab6ef14640a57a [file] [log] [blame]
// Copyright (C) 2020 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.server;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.HumanComment;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.change.EmailReviewComments;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.extensions.events.CommentAdded;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.CommentsRejectedException;
import com.google.gerrit.server.update.Context;
import com.google.gerrit.server.update.RepoView;
import com.google.gerrit.server.util.LabelVote;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link BatchUpdateOp} that can be used to publish draft comments
*
* <p>This class uses the {@link PublishCommentUtil} to publish draft comments and fires the
* necessary event for this.
*/
public class PublishCommentsOp implements BatchUpdateOp {
private final PatchSetUtil psUtil;
private final ChangeNotes.Factory changeNotesFactory;
private final ChangeMessagesUtil cmUtil;
private final CommentAdded commentAdded;
private final CommentsUtil commentsUtil;
private final EmailReviewComments.Factory email;
private final List<LabelVote> labelDelta = new ArrayList<>();
private final Project.NameKey projectNameKey;
private final PatchSet.Id psId;
private final PublishCommentUtil publishCommentUtil;
private List<HumanComment> comments = new ArrayList<>();
private ChangeMessage message;
private IdentifiedUser user;
public interface Factory {
PublishCommentsOp create(PatchSet.Id psId, Project.NameKey projectNameKey);
}
@Inject
public PublishCommentsOp(
ChangeNotes.Factory changeNotesFactory,
ChangeMessagesUtil cmUtil,
CommentAdded commentAdded,
CommentsUtil commentsUtil,
EmailReviewComments.Factory email,
PatchSetUtil psUtil,
PublishCommentUtil publishCommentUtil,
@Assisted PatchSet.Id psId,
@Assisted Project.NameKey projectNameKey) {
this.cmUtil = cmUtil;
this.changeNotesFactory = changeNotesFactory;
this.commentAdded = commentAdded;
this.commentsUtil = commentsUtil;
this.email = email;
this.psId = psId;
this.publishCommentUtil = publishCommentUtil;
this.psUtil = psUtil;
this.projectNameKey = projectNameKey;
}
@Override
public boolean updateChange(ChangeContext ctx)
throws ResourceConflictException, UnprocessableEntityException, IOException,
PatchListNotAvailableException, CommentsRejectedException {
user = ctx.getIdentifiedUser();
comments = commentsUtil.draftByChangeAuthor(ctx.getNotes(), ctx.getUser().getAccountId());
// PublishCommentsOp should update a separate ChangeUpdate Object than the one used by other ops
// For example, with the "publish comments on PS upload" workflow,
// There are 2 ops: ReplaceOp & PublishCommentsOp, where each updates its own ChangeUpdate
// This is required since
// 1. a ChangeUpdate has only 1 change message
// 2. Each ChangeUpdate results in 1 commit in NoteDb
// We do it this way so that the execution results in 2 different commits in NoteDb
ChangeUpdate changeUpdate = ctx.getDistinctUpdate(psId);
publishCommentUtil.publish(ctx, changeUpdate, comments, null);
return insertMessage(ctx, changeUpdate);
}
@Override
public void postUpdate(Context ctx) {
if (message == null || comments.isEmpty()) {
return;
}
ChangeNotes changeNotes = changeNotesFactory.createChecked(projectNameKey, psId.changeId());
PatchSet ps = psUtil.get(changeNotes, psId);
NotifyResolver.Result notify = ctx.getNotify(changeNotes.getChangeId());
if (notify.shouldNotify()) {
RepoView repoView;
try {
repoView = ctx.getRepoView();
} catch (IOException ex) {
throw new StorageException(
String.format("Repository %s not found", ctx.getProject().get()), ex);
}
email
.create(notify, changeNotes, ps, user, message, comments, null, labelDelta, repoView)
.sendAsync();
}
commentAdded.fire(
changeNotes.getChange(),
ps,
ctx.getAccount(),
message.getMessage(),
ImmutableMap.of(),
ImmutableMap.of(),
ctx.getWhen());
}
private boolean insertMessage(ChangeContext ctx, ChangeUpdate changeUpdate) {
StringBuilder buf = new StringBuilder();
if (comments.size() == 1) {
buf.append("\n\n(1 comment)");
} else if (comments.size() > 1) {
buf.append(String.format("\n\n(%d comments)", comments.size()));
}
if (buf.length() == 0) {
return false;
}
message =
ChangeMessagesUtil.newMessage(
psId, user, ctx.getWhen(), "Patch Set " + psId.get() + ":" + buf, null);
cmUtil.addChangeMessage(changeUpdate, message);
return true;
}
}