diff options
author | Android Code Review <code-review@android.com> | 2009-03-09 12:54:40 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2009-03-09 12:54:40 -0700 |
commit | 1afce7ff1683d5879d2844963386d789d701c923 (patch) | |
tree | aaf618d6425aa543d1fbcd49a0959b9d394712a2 | |
parent | dce0ee9ad37f3b96f955dfb781f83dde15e3f940 (diff) | |
parent | f6a57ac50379a7e68c4af5bc2a491ace74ddc66b (diff) |
Merge
-rw-r--r-- | src/main/java/com/google/gerrit/Gerrit.gwt.xml | 2 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java | 105 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/changes/ChangeConstants.java | 7 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties | 7 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java | 27 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java | 22 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/data/ChangeDetail.java | 9 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/client/patches/PatchDetailService.java | 4 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/public/gerrit3.cache.css (renamed from src/main/java/com/google/gerrit/public/gerrit2.cache.css) | 28 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/server/ChangeMail.java | 35 | ||||
-rw-r--r-- | src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java | 93 |
11 files changed, 334 insertions, 5 deletions
diff --git a/src/main/java/com/google/gerrit/Gerrit.gwt.xml b/src/main/java/com/google/gerrit/Gerrit.gwt.xml index 8d73d5cb44..2f03f9b62b 100644 --- a/src/main/java/com/google/gerrit/Gerrit.gwt.xml +++ b/src/main/java/com/google/gerrit/Gerrit.gwt.xml @@ -12,7 +12,7 @@ <entry-point class='com.google.gerrit.client.Gerrit'/> - <stylesheet src='gerrit2.cache.css' /> + <stylesheet src='gerrit3.cache.css' /> <servlet path='/Gerrit' class='com.google.gerrit.server.HostPageServlet'/> diff --git a/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java b/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java new file mode 100644 index 0000000000..b0d419bfde --- /dev/null +++ b/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java @@ -0,0 +1,105 @@ +// Copyright (C) 2009 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.client.changes; + +import com.google.gerrit.client.patches.PatchUtil; +import com.google.gerrit.client.reviewdb.PatchSet; +import com.google.gerrit.client.rpc.GerritCallback; +import com.google.gerrit.client.ui.SmallHeading; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.TextArea; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwtexpui.user.client.AutoCenterDialogBox; +import com.google.gwtjsonrpc.client.VoidResult; + +public class AbandonChangeDialog extends AutoCenterDialogBox { + + private final FlowPanel panel; + private final TextArea message; + private final Button sendButton; + private final Button cancelButton; + private final PatchSet.Id psid; + private final AsyncCallback<?> appCallback; + + public AbandonChangeDialog(final PatchSet.Id psi, + final AsyncCallback<?> callback) { + super(/* auto hide */true, /* modal */true); + + psid = psi; + appCallback = callback; + addStyleName("gerrit-AbandonChangeDialog"); + setText(Util.C.abandonChangeTitle()); + + panel = new FlowPanel(); + add(panel); + + panel.add(new SmallHeading(Util.C.headingAbandonMessage())); + + final FlowPanel mwrap = new FlowPanel(); + mwrap.setStyleName("gerrit-AbandonMessage"); + panel.add(mwrap); + + message = new TextArea(); + message.setCharacterWidth(60); + message.setVisibleLines(10); + DOM.setElementPropertyBoolean(message.getElement(), "spellcheck", true); + mwrap.add(message); + + final FlowPanel buttonPanel = new FlowPanel(); + buttonPanel.setStyleName("gerrit-CommentEditor-Buttons"); + panel.add(buttonPanel); + + sendButton = new Button(Util.C.buttonAbandonChangeSend()); + sendButton.addClickListener(new ClickListener() { + public void onClick(Widget sender) { + sendButton.setEnabled(false); + PatchUtil.DETAIL_SVC.abandonChange(psid, message.getText().trim(), + new GerritCallback<VoidResult>() { + public void onSuccess(VoidResult result) { + if (appCallback != null) { + appCallback.onSuccess(null); + } + hide(); + } + + @Override + public void onFailure(Throwable caught) { + sendButton.setEnabled(true); + super.onFailure(caught); + } + }); + } + }); + buttonPanel.add(sendButton); + + cancelButton = new Button(Util.C.buttonAbandonChangeCancel()); + cancelButton.addClickListener(new ClickListener() { + public void onClick(Widget sender) { + hide(); + } + }); + buttonPanel.add(cancelButton); + } + + @Override + protected void onLoad() { + super.onLoad(); + message.setFocus(true); + } +} diff --git a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java index afc1555b9b..a69d85adc4 100644 --- a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java +++ b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java @@ -70,6 +70,13 @@ public interface ChangeConstants extends Constants { String patchSetInfoAuthor(); String patchSetInfoCommitter(); String patchSetInfoDownload(); + + String buttonAbandonChangeBegin(); + String buttonAbandonChangeSend(); + String buttonAbandonChangeCancel(); + String headingAbandonMessage(); + String abandonChangeTitle(); + String buttonPublishCommentsBegin(); String buttonPublishCommentsSend(); String buttonPublishCommentsCancel(); diff --git a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties index 38546e923c..d0662c42e8 100644 --- a/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties +++ b/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties @@ -50,6 +50,13 @@ messageCollapseAll = Collapse All patchSetInfoAuthor = Author patchSetInfoCommitter = Committer patchSetInfoDownload = Download + +buttonAbandonChangeBegin = Abandon Change +buttonAbandonChangeSend = Abandon Change +buttonAbandonChangeCancel = Cancel +headingAbandonMessage = Abandon Message: +abandonChangeTitle = Code Review - Abandon Change + buttonPublishCommentsBegin = Publish Comments buttonPublishCommentsSend = Publish Comments buttonPublishCommentsCancel = Cancel diff --git a/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java b/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java index 959ced2deb..52263ddb92 100644 --- a/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java +++ b/src/main/java/com/google/gerrit/client/changes/ChangeDetailServiceImpl.java @@ -17,8 +17,11 @@ package com.google.gerrit.client.changes; import com.google.gerrit.client.data.AccountInfoCacheFactory; import com.google.gerrit.client.data.ChangeDetail; import com.google.gerrit.client.data.PatchSetDetail; +import com.google.gerrit.client.data.ProjectCache; +import com.google.gerrit.client.reviewdb.Account; import com.google.gerrit.client.reviewdb.Change; import com.google.gerrit.client.reviewdb.PatchSet; +import com.google.gerrit.client.reviewdb.Project; import com.google.gerrit.client.reviewdb.ReviewDb; import com.google.gerrit.client.rpc.BaseServiceImplementation; import com.google.gerrit.client.rpc.Common; @@ -32,14 +35,23 @@ public class ChangeDetailServiceImpl extends BaseServiceImplementation final AsyncCallback<ChangeDetail> callback) { run(callback, new Action<ChangeDetail>() { public ChangeDetail run(final ReviewDb db) throws OrmException, Failure { + final Account.Id me = Common.getAccountId(); final Change change = db.changes().get(id); if (change == null) { throw new Failure(new NoSuchEntityException()); } + final PatchSet patch = db.patchSets().get(change.currentPatchSetId()); + final ProjectCache.Entry projEnt = + Common.getProjectCache().get(change.getDest().getParentKey()); + if (patch == null || projEnt == null) { + throw new Failure(new NoSuchEntityException()); + } + final Project proj = projEnt.getProject(); assertCanRead(change); final boolean anon; - if (Common.getAccountId() == null) { + boolean canAbandon = false; + if (me == null) { // Safe assumption, this wouldn't be allowed if it wasn't. // anon = true; @@ -48,9 +60,20 @@ public class ChangeDetailServiceImpl extends BaseServiceImplementation // we can that doesn't mean the anonymous user could. // anon = canRead(null, change.getDest().getParentKey()); + + // The change owner, current patchset uploader, Gerrit administrator, + // and project administrator can mark the change as abandoned. + // + canAbandon = + me.equals(change.getOwner()) + || me.equals(patch.getUploader()) + || Common.getGroupCache().isAdministrator(me) + || Common.getGroupCache().isInGroup(me, + proj.getOwnerGroupId()); } final ChangeDetail d = new ChangeDetail(); - d.load(db, new AccountInfoCacheFactory(db), change, anon); + + d.load(db, new AccountInfoCacheFactory(db), change, anon, canAbandon); return d; } }); diff --git a/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java b/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java index 0bf1c3fa4d..4295f27e76 100644 --- a/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java +++ b/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java @@ -33,6 +33,7 @@ import com.google.gerrit.client.rpc.Common; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.RefreshListener; import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.ClickListener; import com.google.gwt.user.client.ui.Composite; @@ -161,6 +162,9 @@ class PatchSetPanel extends Composite implements DisclosureHandler { if (Gerrit.isSignedIn() && changeDetail.isCurrentPatchSet(detail)) { populateCommentAction(); populateActions(detail); + if (changeDetail.canAbandon()) { + populateAbandonAction(); + } } body.add(patchTable); } @@ -311,6 +315,24 @@ class PatchSetPanel extends Composite implements DisclosureHandler { } } + private void populateAbandonAction() { + final Button b = new Button(Util.C.buttonAbandonChangeBegin()); + b.addClickListener(new ClickListener() { + public void onClick(Widget sender) { + new AbandonChangeDialog(patchSet.getId(), new AsyncCallback<Object>() { + public void onSuccess(Object result) { + actionsPanel.remove(b); + fireOnSuggestRefresh(); + } + + public void onFailure(Throwable caught) { + } + }).center(); + } + }); + actionsPanel.add(b); + } + private void populateCommentAction() { final Button b = new Button(Util.C.buttonPublishCommentsBegin()); b.addClickListener(new ClickListener() { diff --git a/src/main/java/com/google/gerrit/client/data/ChangeDetail.java b/src/main/java/com/google/gerrit/client/data/ChangeDetail.java index 036de1e977..9677789409 100644 --- a/src/main/java/com/google/gerrit/client/data/ChangeDetail.java +++ b/src/main/java/com/google/gerrit/client/data/ChangeDetail.java @@ -41,6 +41,7 @@ import java.util.Set; public class ChangeDetail { protected AccountInfoCache accounts; protected boolean allowsAnonymous; + protected boolean canAbandon; protected Change change; protected List<ChangeInfo> dependsOn; protected List<ChangeInfo> neededBy; @@ -56,12 +57,14 @@ public class ChangeDetail { } public void load(final ReviewDb db, final AccountInfoCacheFactory acc, - final Change c, final boolean allowAnon) throws OrmException { + final Change c, final boolean allowAnon, final boolean canAbdn) + throws OrmException { change = c; final Account.Id owner = change.getOwner(); acc.want(owner); allowsAnonymous = allowAnon; + canAbandon = canAbdn; patchSets = db.patchSets().byChange(change.getId()).toList(); messages = db.changeMessages().byChange(change.getId()).toList(); for (final ChangeMessage m : messages) { @@ -177,6 +180,10 @@ public class ChangeDetail { return allowsAnonymous; } + public boolean canAbandon() { + return canAbandon; + } + public Change getChange() { return change; } diff --git a/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java b/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java index 7d00318975..04d506baba 100644 --- a/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java +++ b/src/main/java/com/google/gerrit/client/patches/PatchDetailService.java @@ -54,4 +54,8 @@ public interface PatchDetailService extends RemoteJsonService { @SignInRequired void addReviewers(Change.Id id, List<String> reviewers, AsyncCallback<VoidResult> callback); + + @SignInRequired + void abandonChange(PatchSet.Id patchSetId, String message, + AsyncCallback<VoidResult> callback); } diff --git a/src/main/java/com/google/gerrit/public/gerrit2.cache.css b/src/main/java/com/google/gerrit/public/gerrit3.cache.css index 69512fe775..66c735e6f2 100644 --- a/src/main/java/com/google/gerrit/public/gerrit2.cache.css +++ b/src/main/java/com/google/gerrit/public/gerrit3.cache.css @@ -288,7 +288,7 @@ border-collapse: separate; border-spacing: 0; } -.gerrit-PatchContentTable td { +.gerrit-PatchContentTable td { font-size: 8pt; font-family: monospace; } @@ -777,6 +777,32 @@ } +/** AbandonChangeDialog **/ + +.gerrit-AbandonChangeDialog .gwt-DisclosurePanel .header td { + font-weight: bold; + white-space: nowrap; +} + +.gerrit-AbandonChangeDialog .gerrit-SmallHeading { + font-size: small; + font-weight: bold; + white-space: nowrap; +} +.gerrit-AbandonChangeDialog .gerrit-AbandonMessage { + margin-left: 10px; + background: #d4e9a9; + padding: 5px 5px 5px 5px; +} +.gerrit-AbandonChangeDialog .gerrit-AbandonMessage textarea { + font-size: small; +} +.gerrit-AbandonChangeDialog .gwt-Hyperlink { + white-space: nowrap; + font-size: small; +} + + /** OpenIdLoginPanel **/ .gerrit-OpenID-loginform { margin-left: 10px; diff --git a/src/main/java/com/google/gerrit/server/ChangeMail.java b/src/main/java/com/google/gerrit/server/ChangeMail.java index 4c77fd9af5..b0515e96e3 100644 --- a/src/main/java/com/google/gerrit/server/ChangeMail.java +++ b/src/main/java/com/google/gerrit/server/ChangeMail.java @@ -295,6 +295,41 @@ public class ChangeMail { } } + public void sendAbandoned() throws MessagingException { + if (begin("abandon")) { + final Account a = Common.getAccountCache().get(fromId); + if (a == null || a.getFullName() == null || a.getFullName().length() == 0) { + body.append("A Gerrit user"); + } else { + body.append(a.getFullName()); + } + + body.append(" has abandoned a change:\n\n"); + body.append(change.getChangeId()); + body.append(" - "); + body.append(change.getSubject()); + body.append("\n\n"); + + if (message != null) { + body.append(message.getMessage().trim()); + if (body.length() > 0) { + body.append("\n\n"); + } + } + + if (changeUrl() != null) { + openFooter(); + body.append("To view visit "); + body.append(changeUrl()); + body.append("\n"); + } + + initInReplyToChange(); + commentTo(); + send(); + } + } + private void newChangeTo() throws MessagingException { add(RecipientType.TO, reviewers); add(RecipientType.CC, extraCC); diff --git a/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java b/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java index db835cef62..d95bc139dd 100644 --- a/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java +++ b/src/main/java/com/google/gerrit/server/patch/PatchDetailServiceImpl.java @@ -15,6 +15,7 @@ package com.google.gerrit.server.patch; import com.google.gerrit.client.data.ApprovalType; +import com.google.gerrit.client.data.ProjectCache; import com.google.gerrit.client.data.SideBySidePatchDetail; import com.google.gerrit.client.data.UnifiedPatchDetail; import com.google.gerrit.client.patches.PatchDetailService; @@ -28,6 +29,7 @@ import com.google.gerrit.client.reviewdb.Patch; import com.google.gerrit.client.reviewdb.PatchLineComment; import com.google.gerrit.client.reviewdb.PatchSet; import com.google.gerrit.client.reviewdb.PatchSetInfo; +import com.google.gerrit.client.reviewdb.Project; import com.google.gerrit.client.reviewdb.ReviewDb; import com.google.gerrit.client.reviewdb.Account.Id; import com.google.gerrit.client.rpc.BaseServiceImplementation; @@ -355,4 +357,95 @@ public class PatchDetailServiceImpl extends BaseServiceImplementation implements } return VoidResult.INSTANCE; } + + public void abandonChange(final PatchSet.Id patchSetId, final String message, + final AsyncCallback<VoidResult> callback) { + run(callback, new Action<VoidResult>() { + public VoidResult run(final ReviewDb db) throws OrmException, Failure { + final Account.Id me = Common.getAccountId(); + final Change change = db.changes().get(patchSetId.getParentKey()); + if (change == null) { + throw new Failure(new NoSuchEntityException()); + } + final PatchSet patch = db.patchSets().get(patchSetId); + final ProjectCache.Entry projEnt = + Common.getProjectCache().get(change.getDest().getParentKey()); + if (me == null || patch == null || projEnt == null) { + throw new Failure(new NoSuchEntityException()); + } + final Project proj = projEnt.getProject(); + + if (!me.equals(change.getOwner()) && !me.equals(patch.getUploader()) + && !Common.getGroupCache().isAdministrator(me) + && !Common.getGroupCache().isInGroup(me, proj.getOwnerGroupId())) { + // The user doesn't have permission to abandon the change + throw new Failure(new NoSuchEntityException()); + } + + final ChangeMessage cmsg = + new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil + .messageUUID(db)), me); + final StringBuilder msgBuf = + new StringBuilder("Patch Set " + change.currentPatchSetId().get() + + ": Abandoned"); + if (message != null && message.length() > 0) { + msgBuf.append("\n\n"); + msgBuf.append(message); + } + cmsg.setMessage(msgBuf.toString()); + + Boolean dbSuccess = db.run(new OrmRunnable<Boolean, ReviewDb>() { + public Boolean run(ReviewDb db, Transaction txn, boolean retry) + throws OrmException { + return doAbandonChange(message, change, patchSetId, cmsg, db, txn); + } + }); + + if (dbSuccess) { + // Email the reviewers + try { + final ChangeMail cm = new ChangeMail(server, change); + cm.setFrom(me); + cm.setReviewDb(db); + cm.setChangeMessage(cmsg); + cm.setHttpServletRequest(GerritJsonServlet.getCurrentCall() + .getHttpServletRequest()); + cm.sendAbandoned(); + } catch (MessagingException e) { + log.error("Cannot send abandon change email for change " + + change.getChangeId(), e); + throw new Failure(e); + } + } + + return VoidResult.INSTANCE; + } + }); + } + + private Boolean doAbandonChange(final String message, final Change change, + final PatchSet.Id psid, final ChangeMessage cm, final ReviewDb db, + final Transaction txn) throws OrmException { + + // Check to make sure the change status and current patchset ID haven't + // changed while the user was typing an abandon message + if (change.getStatus() == Change.Status.NEW + && change.currentPatchSetId().equals(psid)) { + change.setStatus(Change.Status.ABANDONED); + ChangeUtil.updated(change); + + final List<ChangeApproval> approvals = + db.changeApprovals().byChange(change.getId()).toList(); + for (ChangeApproval a : approvals) { + a.cache(change); + } + db.changeApprovals().update(approvals, txn); + + db.changeMessages().insert(Collections.singleton(cm), txn); + db.changes().update(Collections.singleton(change), txn); + return Boolean.TRUE; + } + + return Boolean.FALSE; + } } |