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:
		| @@ -104,4 +104,7 @@ public interface Resources extends ClientBundle { | ||||
|  | ||||
|   @Source("warning.png") | ||||
|   public ImageResource warning(); | ||||
|  | ||||
|   @Source("question.png") | ||||
|   public ImageResource question(); | ||||
| } | ||||
|   | ||||
| @@ -311,6 +311,9 @@ public class ChangeInfo extends JavaScriptObject { | ||||
|     public final native boolean hasFetch() /*-{ return this.hasOwnProperty('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) { | ||||
|       final int editParent = findEditParent(list); | ||||
|       Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() { | ||||
|   | ||||
| @@ -12,17 +12,34 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // 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.JsArrayString; | ||||
| 
 | ||||
| public class GpgKeyInfo extends JavaScriptObject { | ||||
|   public enum Status { | ||||
|     BAD, OK, TRUSTED; | ||||
|   } | ||||
| 
 | ||||
|   public final native String id() /*-{ return this.id; }-*/; | ||||
|   public final native String fingerprint() /*-{ return this.fingerprint; }-*/; | ||||
|   public final native JsArrayString userIds() /*-{ return this.user_ids; }-*/; | ||||
|   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() { | ||||
|   } | ||||
| } | ||||
| @@ -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 | 
| @@ -16,6 +16,7 @@ package com.google.gerrit.client.account; | ||||
|  | ||||
| import com.google.gerrit.client.VoidResult; | ||||
| 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.NativeMap; | ||||
| import com.google.gerrit.client.rpc.NativeString; | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
| package com.google.gerrit.client.account; | ||||
|  | ||||
| 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.NativeMap; | ||||
| import com.google.gerrit.client.rpc.Natives; | ||||
|   | ||||
| @@ -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.RevisionInfo; | ||||
| 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.Entry; | ||||
| 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); | ||||
|  | ||||
|   interface Style extends CssResource { | ||||
|     String labelName(); | ||||
|     String avatar(); | ||||
|     String label_user(); | ||||
|     String label_ok(); | ||||
|     String label_reject(); | ||||
|     String hashtagName(); | ||||
|     String highlight(); | ||||
|     String labelName(); | ||||
|     String label_may(); | ||||
|     String label_need(); | ||||
|     String label_ok(); | ||||
|     String label_reject(); | ||||
|     String label_user(); | ||||
|     String pushCertStatus(); | ||||
|     String replyBox(); | ||||
|     String selected(); | ||||
|     String highlight(); | ||||
|     String hashtagName(); | ||||
|   } | ||||
|  | ||||
|   static ChangeScreen get(NativeEvent in) { | ||||
| @@ -164,11 +167,14 @@ public class ChangeScreen extends Screen { | ||||
|   @UiField Reviewers reviewers; | ||||
|   @UiField Hashtags hashtags; | ||||
|   @UiField Element hashtagTableRow; | ||||
|  | ||||
|   @UiField FlowPanel ownerPanel; | ||||
|   @UiField InlineHyperlink ownerLink; | ||||
|  | ||||
|   @UiField Element uploaderRow; | ||||
|   @UiField FlowPanel uploaderPanel; | ||||
|   @UiField InlineLabel uploaderName; | ||||
|  | ||||
|   @UiField Element statusText; | ||||
|   @UiField Image projectSettings; | ||||
|   @UiField AnchorElement projectSettingsLink; | ||||
| @@ -292,11 +298,19 @@ public class ChangeScreen extends Screen { | ||||
|     p.add(extensionPanel); | ||||
|   } | ||||
|  | ||||
|   private boolean enableSignedPush() { | ||||
|     return Gerrit.info().receive().enableSignedPush(); | ||||
|   } | ||||
|  | ||||
|   void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) { | ||||
|     RestApi call = ChangeApi.detail(changeId.get()); | ||||
|     ChangeList.addOptions(call, EnumSet.of( | ||||
|       ListChangesOption.CHANGE_ACTIONS, | ||||
|       ListChangesOption.ALL_REVISIONS)); | ||||
|     EnumSet<ListChangesOption> opts = EnumSet.of( | ||||
|       ListChangesOption.ALL_REVISIONS, | ||||
|       ListChangesOption.CHANGE_ACTIONS); | ||||
|     if (enableSignedPush()) { | ||||
|       opts.add(ListChangesOption.PUSH_CERTIFICATES); | ||||
|     } | ||||
|     ChangeList.addOptions(call, opts); | ||||
|     if (!fg) { | ||||
|       call.background(); | ||||
|     } | ||||
| @@ -1146,13 +1160,14 @@ public class ChangeScreen extends Screen { | ||||
|   } | ||||
|  | ||||
|   private void renderChangeInfo(ChangeInfo info) { | ||||
|     RevisionInfo revisionInfo = info.revision(revision); | ||||
|     changeInfo = info; | ||||
|     lastDisplayedUpdate = info.updated(); | ||||
|  | ||||
|     labels.set(info); | ||||
|  | ||||
|     renderOwner(info); | ||||
|     renderUploader(info, revision); | ||||
|     renderUploader(info, revisionInfo); | ||||
|     renderActionTextDate(info); | ||||
|     renderDiffBaseListBox(info); | ||||
|     initReplyButton(info, revision); | ||||
| @@ -1189,7 +1204,7 @@ public class ChangeScreen extends Screen { | ||||
|     // render it faster. | ||||
|     if (!info.status().isOpen() | ||||
|         || !revision.equals(info.currentRevision()) | ||||
|         || info.revision(revision).isEdit()) { | ||||
|         || revisionInfo.isEdit()) { | ||||
|       setVisible(strategy, false); | ||||
|     } | ||||
|  | ||||
| @@ -1200,7 +1215,6 @@ public class ChangeScreen extends Screen { | ||||
|     quickApprove.setVisible(false); | ||||
|     actions.reloadRevisionActions(emptyMap); | ||||
|  | ||||
|     RevisionInfo revisionInfo = info.revision(revision); | ||||
|     boolean current = revision.equals(info.currentRevision()) | ||||
|         && !revisionInfo.isEdit(); | ||||
|  | ||||
| @@ -1252,10 +1266,12 @@ public class ChangeScreen extends Screen { | ||||
|         : String.valueOf(info.owner()._accountId()), Change.Status.NEW)); | ||||
|   } | ||||
|  | ||||
|   private void renderUploader(ChangeInfo info, String revision) { | ||||
|     AccountInfo uploader = info.revision(revision).uploader(); | ||||
|     if (uploader == null | ||||
|         || uploader._accountId() == info.owner()._accountId()) { | ||||
|   private void renderUploader(ChangeInfo changeInfo, RevisionInfo revInfo) { | ||||
|     AccountInfo uploader = revInfo.uploader(); | ||||
|     boolean isOwner = uploader == null | ||||
|         || uploader._accountId() == changeInfo.owner()._accountId(); | ||||
|     renderPushCertificate(revInfo, isOwner ? ownerPanel : uploaderPanel); | ||||
|     if (isOwner) { | ||||
|       uploaderRow.getStyle().setDisplay(Display.NONE); | ||||
|       return; | ||||
|     } | ||||
| @@ -1269,6 +1285,37 @@ public class ChangeScreen extends Screen { | ||||
|     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) { | ||||
|     return info.name() != null | ||||
|         ? info.name() | ||||
| @@ -1279,6 +1326,21 @@ public class ChangeScreen extends Screen { | ||||
|     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) { | ||||
|     try { | ||||
|       SubmitType type = SubmitType.valueOf(action); | ||||
|   | ||||
| @@ -334,6 +334,10 @@ limitations under the License. | ||||
|     .changeExtension { | ||||
|       padding-top: 5px; | ||||
|     } | ||||
|  | ||||
|     .pushCertStatus { | ||||
|       padding-left: 5px; | ||||
|     } | ||||
|   </ui:style> | ||||
|  | ||||
|   <g:HTMLPanel styleName='{style.cs2}'> | ||||
|   | ||||
| @@ -202,4 +202,9 @@ public interface ChangeConstants extends Constants { | ||||
|   String diffAllUnified(); | ||||
|  | ||||
|   String votable(); | ||||
|  | ||||
|   String pushCertMissing(); | ||||
|   String pushCertBad(); | ||||
|   String pushCertOk(); | ||||
|   String pushCertTrusted(); | ||||
| } | ||||
|   | ||||
| @@ -184,3 +184,8 @@ diffAllSideBySide = All Side-by-Side | ||||
| diffAllUnified = All Unified | ||||
|  | ||||
| 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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dave Borowitz
					Dave Borowitz