Add push cert status to change screen

Add a status icon next to the uploader with a check/warning/X
depending on the push cert status. Include messages in the title on
hover. If the uploader is the same as the change owner, place the
status icon next to the owner.

PushCertificateInfo and GpgKeyInfo are moved to gerrit-gwtui-common so
they can be referenced from ChangeInfo.

The new question mark icon is the help-browser icon from the
public-domain Tango collection:
http://tango.freedesktop.org/Tango_Icon_Library

Change-Id: Iec666c668342b00fd92609dd28feb082d5a560a8
This commit is contained in:
Dave Borowitz
2015-09-15 11:06:47 -04:00
parent 8f10d51c0d
commit ed1523094d
11 changed files with 143 additions and 17 deletions

View File

@@ -104,4 +104,7 @@ public interface Resources extends ClientBundle {
@Source("warning.png") @Source("warning.png")
public ImageResource warning(); public ImageResource warning();
@Source("question.png")
public ImageResource question();
} }

View File

@@ -311,6 +311,9 @@ public class ChangeInfo extends JavaScriptObject {
public final native boolean hasFetch() /*-{ return this.hasOwnProperty('fetch') }-*/; public final native boolean hasFetch() /*-{ return this.hasOwnProperty('fetch') }-*/;
public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/; public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/;
public final native boolean hasPushCertificate() /*-{ return this.hasOwnProperty('push_certificate'); }-*/;
public final native PushCertificateInfo pushCertificate() /*-{ return this.push_certificate; }-*/;
public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) { public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
final int editParent = findEditParent(list); final int editParent = findEditParent(list);
Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() { Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {

View File

@@ -12,17 +12,34 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package com.google.gerrit.client.account; package com.google.gerrit.client.info;
import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.JsArrayString;
public class GpgKeyInfo extends JavaScriptObject { public class GpgKeyInfo extends JavaScriptObject {
public enum Status {
BAD, OK, TRUSTED;
}
public final native String id() /*-{ return this.id; }-*/; public final native String id() /*-{ return this.id; }-*/;
public final native String fingerprint() /*-{ return this.fingerprint; }-*/; public final native String fingerprint() /*-{ return this.fingerprint; }-*/;
public final native JsArrayString userIds() /*-{ return this.user_ids; }-*/; public final native JsArrayString userIds() /*-{ return this.user_ids; }-*/;
public final native String key() /*-{ return this.key; }-*/; public final native String key() /*-{ return this.key; }-*/;
private final native String statusRaw() /*-{ return this.status; }-*/;
public final Status status() {
String s = statusRaw();
if (s == null) {
return null;
}
return Status.valueOf(s);
}
public final native boolean hasProblems()
/*-{ return this.hasOwnProperty('problems'); }-*/;
public final native JsArrayString problems() /*-{ return this.problems; }-*/;
protected GpgKeyInfo() { protected GpgKeyInfo() {
} }
} }

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2015 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.info;
import com.google.gwt.core.client.JavaScriptObject;
public class PushCertificateInfo extends JavaScriptObject {
public final native String certificate() /*-{ return this.certificate; }-*/;
public final native GpgKeyInfo key() /*-{ return this.key; }-*/;
protected PushCertificateInfo() {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.client.account;
import com.google.gerrit.client.VoidResult; import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.info.AccountInfo; import com.google.gerrit.client.info.AccountInfo;
import com.google.gerrit.client.info.GpgKeyInfo;
import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.NativeMap; import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString; import com.google.gerrit.client.rpc.NativeString;

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.client.account; package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.info.GpgKeyInfo;
import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap; import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.rpc.Natives;

View File

@@ -38,6 +38,8 @@ import com.google.gerrit.client.info.ChangeInfo.LabelInfo;
import com.google.gerrit.client.info.ChangeInfo.MessageInfo; import com.google.gerrit.client.info.ChangeInfo.MessageInfo;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo; import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.info.FileInfo; import com.google.gerrit.client.info.FileInfo;
import com.google.gerrit.client.info.GpgKeyInfo;
import com.google.gerrit.client.info.PushCertificateInfo;
import com.google.gerrit.client.projects.ConfigInfoCache; import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.projects.ConfigInfoCache.Entry; import com.google.gerrit.client.projects.ConfigInfoCache.Entry;
import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.CallbackGroup;
@@ -112,17 +114,18 @@ public class ChangeScreen extends Screen {
private static final Binder uiBinder = GWT.create(Binder.class); private static final Binder uiBinder = GWT.create(Binder.class);
interface Style extends CssResource { interface Style extends CssResource {
String labelName();
String avatar(); String avatar();
String label_user(); String hashtagName();
String label_ok(); String highlight();
String label_reject(); String labelName();
String label_may(); String label_may();
String label_need(); String label_need();
String label_ok();
String label_reject();
String label_user();
String pushCertStatus();
String replyBox(); String replyBox();
String selected(); String selected();
String highlight();
String hashtagName();
} }
static ChangeScreen get(NativeEvent in) { static ChangeScreen get(NativeEvent in) {
@@ -164,11 +167,14 @@ public class ChangeScreen extends Screen {
@UiField Reviewers reviewers; @UiField Reviewers reviewers;
@UiField Hashtags hashtags; @UiField Hashtags hashtags;
@UiField Element hashtagTableRow; @UiField Element hashtagTableRow;
@UiField FlowPanel ownerPanel; @UiField FlowPanel ownerPanel;
@UiField InlineHyperlink ownerLink; @UiField InlineHyperlink ownerLink;
@UiField Element uploaderRow; @UiField Element uploaderRow;
@UiField FlowPanel uploaderPanel; @UiField FlowPanel uploaderPanel;
@UiField InlineLabel uploaderName; @UiField InlineLabel uploaderName;
@UiField Element statusText; @UiField Element statusText;
@UiField Image projectSettings; @UiField Image projectSettings;
@UiField AnchorElement projectSettingsLink; @UiField AnchorElement projectSettingsLink;
@@ -292,11 +298,19 @@ public class ChangeScreen extends Screen {
p.add(extensionPanel); p.add(extensionPanel);
} }
private boolean enableSignedPush() {
return Gerrit.info().receive().enableSignedPush();
}
void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) { void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) {
RestApi call = ChangeApi.detail(changeId.get()); RestApi call = ChangeApi.detail(changeId.get());
ChangeList.addOptions(call, EnumSet.of( EnumSet<ListChangesOption> opts = EnumSet.of(
ListChangesOption.CHANGE_ACTIONS, ListChangesOption.ALL_REVISIONS,
ListChangesOption.ALL_REVISIONS)); ListChangesOption.CHANGE_ACTIONS);
if (enableSignedPush()) {
opts.add(ListChangesOption.PUSH_CERTIFICATES);
}
ChangeList.addOptions(call, opts);
if (!fg) { if (!fg) {
call.background(); call.background();
} }
@@ -1146,13 +1160,14 @@ public class ChangeScreen extends Screen {
} }
private void renderChangeInfo(ChangeInfo info) { private void renderChangeInfo(ChangeInfo info) {
RevisionInfo revisionInfo = info.revision(revision);
changeInfo = info; changeInfo = info;
lastDisplayedUpdate = info.updated(); lastDisplayedUpdate = info.updated();
labels.set(info); labels.set(info);
renderOwner(info); renderOwner(info);
renderUploader(info, revision); renderUploader(info, revisionInfo);
renderActionTextDate(info); renderActionTextDate(info);
renderDiffBaseListBox(info); renderDiffBaseListBox(info);
initReplyButton(info, revision); initReplyButton(info, revision);
@@ -1189,7 +1204,7 @@ public class ChangeScreen extends Screen {
// render it faster. // render it faster.
if (!info.status().isOpen() if (!info.status().isOpen()
|| !revision.equals(info.currentRevision()) || !revision.equals(info.currentRevision())
|| info.revision(revision).isEdit()) { || revisionInfo.isEdit()) {
setVisible(strategy, false); setVisible(strategy, false);
} }
@@ -1200,7 +1215,6 @@ public class ChangeScreen extends Screen {
quickApprove.setVisible(false); quickApprove.setVisible(false);
actions.reloadRevisionActions(emptyMap); actions.reloadRevisionActions(emptyMap);
RevisionInfo revisionInfo = info.revision(revision);
boolean current = revision.equals(info.currentRevision()) boolean current = revision.equals(info.currentRevision())
&& !revisionInfo.isEdit(); && !revisionInfo.isEdit();
@@ -1252,10 +1266,12 @@ public class ChangeScreen extends Screen {
: String.valueOf(info.owner()._accountId()), Change.Status.NEW)); : String.valueOf(info.owner()._accountId()), Change.Status.NEW));
} }
private void renderUploader(ChangeInfo info, String revision) { private void renderUploader(ChangeInfo changeInfo, RevisionInfo revInfo) {
AccountInfo uploader = info.revision(revision).uploader(); AccountInfo uploader = revInfo.uploader();
if (uploader == null boolean isOwner = uploader == null
|| uploader._accountId() == info.owner()._accountId()) { || uploader._accountId() == changeInfo.owner()._accountId();
renderPushCertificate(revInfo, isOwner ? ownerPanel : uploaderPanel);
if (isOwner) {
uploaderRow.getStyle().setDisplay(Display.NONE); uploaderRow.getStyle().setDisplay(Display.NONE);
return; return;
} }
@@ -1269,6 +1285,37 @@ public class ChangeScreen extends Screen {
uploaderName.setTitle(email(uploader, name)); uploaderName.setTitle(email(uploader, name));
} }
private void renderPushCertificate(RevisionInfo revInfo, FlowPanel panel) {
if (!enableSignedPush()) {
return;
}
Image status = new Image();
panel.add(status);
status.setStyleName(style.pushCertStatus());
if (!revInfo.hasPushCertificate()
|| revInfo.pushCertificate().key() == null) {
status.setResource(Gerrit.RESOURCES.question());
status.setTitle(Util.C.pushCertMissing());
return;
}
PushCertificateInfo certInfo = revInfo.pushCertificate();
GpgKeyInfo.Status s = certInfo.key().status();
switch (s) {
case BAD:
status.setResource(Gerrit.RESOURCES.redNot());
status.setTitle(problems(Util.C.pushCertBad(), certInfo));
break;
case OK:
status.setResource(Gerrit.RESOURCES.warning());
status.setTitle(problems(Util.C.pushCertOk(), certInfo));
break;
case TRUSTED:
status.setResource(Gerrit.RESOURCES.greenCheck());
status.setTitle(Util.C.pushCertTrusted());
break;
}
}
private static String name(AccountInfo info) { private static String name(AccountInfo info) {
return info.name() != null return info.name() != null
? info.name() ? info.name()
@@ -1279,6 +1326,21 @@ public class ChangeScreen extends Screen {
return info.email() != null ? info.email() : name; return info.email() != null ? info.email() : name;
} }
private static String problems(String msg, PushCertificateInfo info) {
if (info.key() == null
|| !info.key().hasProblems()
|| info.key().problems().length() == 0) {
return msg;
}
StringBuilder sb = new StringBuilder();
sb.append(msg).append(':');
for (String problem : Natives.asList(info.key().problems())) {
sb.append('\n').append(problem);
}
return sb.toString();
}
private void renderSubmitType(String action) { private void renderSubmitType(String action) {
try { try {
SubmitType type = SubmitType.valueOf(action); SubmitType type = SubmitType.valueOf(action);

View File

@@ -334,6 +334,10 @@ limitations under the License.
.changeExtension { .changeExtension {
padding-top: 5px; padding-top: 5px;
} }
.pushCertStatus {
padding-left: 5px;
}
</ui:style> </ui:style>
<g:HTMLPanel styleName='{style.cs2}'> <g:HTMLPanel styleName='{style.cs2}'>

View File

@@ -202,4 +202,9 @@ public interface ChangeConstants extends Constants {
String diffAllUnified(); String diffAllUnified();
String votable(); String votable();
String pushCertMissing();
String pushCertBad();
String pushCertOk();
String pushCertTrusted();
} }

View File

@@ -184,3 +184,8 @@ diffAllSideBySide = All Side-by-Side
diffAllUnified = All Unified diffAllUnified = All Unified
votable = Votable: votable = Votable:
pushCertMissing = This patch set was created without a push certificate
pushCertBad = Push certificate is invalid
pushCertOk = Push certificate is valid, but key is not trusted
pushCertTrusted = Push certificate is valid and key is trusted