Add support for diff web links

Plugins can now add web links to the diff screen that will be
displayed next to the navigation icons.

Change-Id: Ifa01103054c3e81e3121c219ee5b69db2fefb3bf
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin 2014-12-06 10:55:38 +01:00
parent 91c473af8d
commit 8cdce50b43
16 changed files with 325 additions and 45 deletions

View File

@ -1772,6 +1772,9 @@ public class MyWeblinkPlugin implements PatchSetWebLink {
FileWebLinks will appear in the side-by-side diff screen on the right
side of the patch selection on each side.
DiffWebLinks will appear in the side-by-side and unified diff screen in
the header next to the navigation icons.
ProjectWebLinks will appear in the project list in the
`Repository Browser` column.

View File

@ -2972,6 +2972,9 @@ If the `intraline` parameter is specified, intraline differences are included in
The `base` parameter can be specified to control the base patch set from which the diff should
be generated.
[[weblinks-only]]
If the `weblinks-only` parameter is specified, only the diff web links are returned.
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/b6b9c10649b9041884046119ab794374470a1b45/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/diff?base=2 HTTP/1.0
@ -3589,6 +3592,9 @@ link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
The `DiffInfo` entity contains information about the diff of a file
in a revision.
If the link:#weblinks-only[weblinks-only] parameter is specified, only
the `web_links` field is set.
[options="header",cols="1,^1,5"]
|==========================
|Field Name ||Description
@ -3605,6 +3611,9 @@ Intraline status (`OK`, `ERROR`, `TIMEOUT`).
|`diff_header` ||A list of strings representing the patch set diff header.
|`content` ||The content differences in the file as a list of
link:#diff-content[DiffContent] entities.
|`web_links` |optional|
Links to the file diff in external sites as a list of
link:rest-api-changes.html#diff-web-link-info[DiffWebLinkInfo] entries.
|==========================
[[diff-intraline-info]]
@ -3621,6 +3630,23 @@ diff content lines.
Note that the implied newline character at the end of each line is included in
the length calculation, and thus it is possible for the edits to span newlines.
[[diff-web-link-info]]
=== DiffWebLinkInfo
The `DiffWebLinkInfo` entity describes a link on a diff screen to an
external site.
[options="header",cols="1,6"]
|=======================
|Field Name|Description
|`name` |The link name.
|`url` |The link URL.
|`image_url`|URL to the icon of the link.
|show_on_side_by_side_diff_view|
Whether the web link should be shown on the side-by-side diff screen.
|show_on_unified_diff_view|
Whether the web link should be shown on the unified diff screen.
|=======================
[[fetch-info]]
=== FetchInfo
The `FetchInfo` entity contains information about how to fetch a patch

View File

@ -0,0 +1,43 @@
// Copyright (C) 2014 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.extensions.common;
public class DiffWebLinkInfo extends WebLinkInfo {
public Boolean showOnSideBySideDiffView;
public Boolean showOnUnifiedDiffView;
public static DiffWebLinkInfo forSideBySideDiffView(String name,
String imageUrl, String url, String target) {
return new DiffWebLinkInfo(name, imageUrl, url, target, true, false);
}
public static DiffWebLinkInfo forUnifiedDiffView(String name,
String imageUrl, String url, String target) {
return new DiffWebLinkInfo(name, imageUrl, url, target, false, true);
}
public static DiffWebLinkInfo forSideBySideAndUnifiedDiffView(String name,
String imageUrl, String url, String target) {
return new DiffWebLinkInfo(name, imageUrl, url, target, true, true);
}
private DiffWebLinkInfo(String name, String imageUrl, String url,
String target, boolean showOnSideBySideDiffView,
boolean showOnUnifiedDiffView) {
super(name, imageUrl, url, target);
this.showOnSideBySideDiffView = showOnSideBySideDiffView ? true : null;
this.showOnUnifiedDiffView = showOnUnifiedDiffView ? true : null;
}
}

View File

@ -0,0 +1,47 @@
// Copyright (C) 2014 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.extensions.webui;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
@ExtensionPoint
public interface DiffWebLink extends WebLink {
/**
* {@link com.google.gerrit.extensions.common.DiffWebLinkInfo}
* describing a link from a file diff to an external service.
*
* <p>In order for the web link to be visible
* {@link com.google.gerrit.extensions.common.WebLinkInfo#url}
* and {@link com.google.gerrit.extensions.common.WebLinkInfo#name}
* must be set.<p>
*
* @param projectName Name of the project
* @param changeId ID of the change
* @param patchSetIdA Patch set ID of side A, <code>null</code> if no base
* patch set was selected
* @param revisionA Name of the revision of side A (e.g. branch or commit ID)
* @param fileNameA Name of the file of side A
* @param patchSetIdB Patch set ID of side B
* @param revisionB Name of the revision of side B (e.g. branch or commit ID)
* @param fileNameB Name of the file of side B
* @return WebLinkInfo that links to file diff in external service,
* null if there should be no link.
*/
DiffWebLinkInfo getDiffLink(String projectName, int changeId,
Integer patchSetIdA, String revisionA, String fileNameA,
int patchSetIdB, String revisionB, String fileNameB);
}

View File

@ -0,0 +1,26 @@
// Copyright (C) 2014 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;
public class DiffWebLinkInfo extends WebLinkInfo {
public final native boolean showOnSideBySideDiffView()
/*-{ return this.show_on_side_by_side_diff_view || false; }-*/;
public final native boolean showOnUnifiedDiffView()
/*-{ return this.show_on_unified_diff_view || false; }-*/;
protected DiffWebLinkInfo() {
}
}

View File

@ -161,6 +161,7 @@ public interface GerritCss extends CssResource {
String link();
String linkMenuBar();
String linkMenuItemNotLast();
String linkPanel();
String maxObjectSizeLimitEffectiveLabel();
String menuBarUserName();
String menuBarUserNameAvatar();

View File

@ -63,6 +63,11 @@ public class DiffApi {
return this;
}
public DiffApi webLinksOnly() {
call.addParameter("weblinks-only", true);
return this;
}
public DiffApi ignoreWhitespace(AccountDiffPreference.Whitespace w) {
switch (w) {
default:

View File

@ -14,12 +14,18 @@
package com.google.gerrit.client.diff;
import com.google.gerrit.client.DiffWebLinkInfo;
import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import java.util.LinkedList;
import java.util.List;
public class DiffInfo extends JavaScriptObject {
public static final String GITLINK = "x-git/gitlink";
public static final String SYMLINK = "x-git/symlink";
@ -28,6 +34,33 @@ public class DiffInfo extends JavaScriptObject {
public final native FileMeta meta_b() /*-{ return this.meta_b; }-*/;
public final native JsArrayString diff_header() /*-{ return this.diff_header; }-*/;
public final native JsArray<Region> content() /*-{ return this.content; }-*/;
public final native JsArray<DiffWebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
public final List<WebLinkInfo> side_by_side_web_links() {
return filterWebLinks(DiffView.SIDE_BY_SIDE);
}
public final List<WebLinkInfo> unified_web_links() {
return filterWebLinks(DiffView.UNIFIED_DIFF);
}
private final List<WebLinkInfo> filterWebLinks(DiffView diffView) {
List<WebLinkInfo> filteredDiffWebLinks = new LinkedList<>();
List<DiffWebLinkInfo> allDiffWebLinks = Natives.asList(web_links());
if (allDiffWebLinks != null) {
for (DiffWebLinkInfo webLink : allDiffWebLinks) {
if (diffView == DiffView.SIDE_BY_SIDE
&& webLink.showOnSideBySideDiffView()) {
filteredDiffWebLinks.add(webLink);
}
if (diffView == DiffView.UNIFIED_DIFF
&& webLink.showOnUnifiedDiffView()) {
filteredDiffWebLinks.add(webLink);
}
}
}
return filteredDiffWebLinks;
}
public final ChangeType change_type() {
return ChangeType.valueOf(change_typeRaw());

View File

@ -17,6 +17,7 @@ package com.google.gerrit.client.diff;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
@ -211,13 +212,17 @@ class Header extends Composite {
project.setInnerText(info.project());
}
void init(PreferencesAction pa, List<InlineHyperlink> links) {
void init(PreferencesAction pa, List<InlineHyperlink> links,
List<WebLinkInfo> webLinks) {
prefsAction = pa;
prefsAction.setPartner(preferences);
for (InlineHyperlink link : links) {
linkPanel.add(link);
}
for (WebLinkInfo webLink : webLinks) {
linkPanel.add(webLink.toAnchor());
}
}
@UiHandler("reviewed")

View File

@ -617,7 +617,7 @@ public class SideBySide2 extends Screen {
chunkManager.getLineMapper());
prefsAction = new PreferencesAction(this, prefs);
header.init(prefsAction, getLinks());
header.init(prefsAction, getLinks(), diff.side_by_side_web_links());
if (prefs.syntaxHighlighting() && fileSize.compareTo(FileSize.SMALL) > 0) {
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {

View File

@ -684,6 +684,10 @@ a:hover {
font-size: small;
}
.linkPanel img {
padding-right: 3px;
}
/** PatchContentTable **/
.patchContentTable {

View File

@ -15,6 +15,7 @@
package com.google.gerrit.client.patches;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.ui.ChangeLink;
@ -75,7 +76,7 @@ class NavLinks extends Composite {
}
void display(int patchIndex, PatchScreen.Type type, PatchTable fileList,
List<InlineHyperlink> links) {
List<InlineHyperlink> links, List<WebLinkInfo> webLinks) {
if (fileList != null) {
setupNav(Nav.PREV, fileList.getPreviousPatchLink(patchIndex, type));
setupNav(Nav.NEXT, fileList.getNextPatchLink(patchIndex, type));
@ -83,10 +84,15 @@ class NavLinks extends Composite {
setupNav(Nav.PREV, null);
setupNav(Nav.NEXT, null);
}
FlowPanel linkPanel = new FlowPanel();
linkPanel.setStyleName(Gerrit.RESOURCES.css().linkPanel());
for (InlineHyperlink link : links) {
linkPanel.add(link);
}
for (WebLinkInfo webLink : webLinks) {
linkPanel.add(webLink.toAnchor());
}
table.setWidget(0, 2, linkPanel);
}

View File

@ -18,9 +18,12 @@ import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.changes.CommitMessageBlock;
import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.diff.DiffApi;
import com.google.gerrit.client.diff.DiffInfo;
import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
@ -276,11 +279,25 @@ public abstract class PatchScreen extends Screen implements
}
if (fileList != null) {
topNav.display(patchIndex, getPatchScreenType(), fileList, getLinks());
bottomNav.display(patchIndex, getPatchScreenType(), fileList, getLinks());
displayNav();
}
}
private void displayNav() {
DiffApi.diff(idSideB, patchKey.getFileName())
.base(idSideA)
.webLinksOnly()
.get(new GerritCallback<DiffInfo>() {
@Override
public void onSuccess(DiffInfo diffInfo) {
topNav.display(patchIndex, getPatchScreenType(), fileList,
getLinks(), getWebLinks(diffInfo));
bottomNav.display(patchIndex, getPatchScreenType(), fileList,
getLinks(), getWebLinks(diffInfo));
}
});
}
private List<InlineHyperlink> getLinks() {
if (contentTable instanceof SideBySideTable) {
InlineHyperlink toUnifiedDiffLink = new InlineHyperlink();
@ -300,6 +317,17 @@ public abstract class PatchScreen extends Screen implements
}
}
private List<WebLinkInfo> getWebLinks(DiffInfo diffInfo) {
if (contentTable instanceof SideBySideTable) {
return diffInfo.side_by_side_web_links();
} else if (contentTable instanceof UnifiedDiffTable) {
return diffInfo.unified_web_links();
} else {
throw new IllegalStateException("unknown table type: "
+ contentTable.getClass().getSimpleName());
}
}
private String getSideBySideDiffUrl() {
StringBuilder url = new StringBuilder();
url.append("/c/");
@ -528,8 +556,7 @@ public abstract class PatchScreen extends Screen implements
lastScript = script;
if (fileList != null) {
topNav.display(patchIndex, getPatchScreenType(), fileList, getLinks());
bottomNav.display(patchIndex, getPatchScreenType(), fileList, getLinks());
displayNav();
}
if (Gerrit.isSignedIn()) {

View File

@ -18,9 +18,11 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.BranchWebLink;
import com.google.gerrit.extensions.webui.DiffWebLink;
import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
@ -53,16 +55,19 @@ public class WebLinks {
private final DynamicSet<PatchSetWebLink> patchSetLinks;
private final DynamicSet<FileWebLink> fileLinks;
private final DynamicSet<DiffWebLink> diffLinks;
private final DynamicSet<ProjectWebLink> projectLinks;
private final DynamicSet<BranchWebLink> branchLinks;
@Inject
public WebLinks(DynamicSet<PatchSetWebLink> patchSetLinks,
DynamicSet<FileWebLink> fileLinks,
DynamicSet<DiffWebLink> diffLinks,
DynamicSet<ProjectWebLink> projectLinks,
DynamicSet<BranchWebLink> branchLinks) {
this.patchSetLinks = patchSetLinks;
this.fileLinks = fileLinks;
this.diffLinks = diffLinks;
this.projectLinks = projectLinks;
this.branchLinks = branchLinks;
}
@ -102,6 +107,34 @@ public class WebLinks {
});
}
/**
*
* @param project Project name.
* @param patchSetIdA Patch set ID of side A, <code>null</code> if no base
* patch set was selected.
* @param revisionA SHA1 of revision of side A.
* @param fileA File name of side A.
* @param patchSetIdB Patch set ID of side B.
* @param revisionB SHA1 of revision of side B.
* @param fileB File name of side B.
* @return Links for file diffs.
*/
public FluentIterable<DiffWebLinkInfo> getDiffLinks(final String project, final int changeId,
final Integer patchSetIdA, final String revisionA, final String fileA,
final int patchSetIdB, final String revisionB, final String fileB) {
return FluentIterable
.from(diffLinks)
.transform(new Function<WebLink, DiffWebLinkInfo>() {
@Override
public DiffWebLinkInfo apply(WebLink webLink) {
return ((DiffWebLink) webLink).getDiffLink(project, changeId,
patchSetIdA, revisionA, fileA,
patchSetIdB, revisionB, fileB);
}
})
.filter(INVALID_WEBLINK);
}
/**
*
* @param project Project name.

View File

@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
import com.google.gerrit.common.data.PatchScript.FileMode;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.CacheControl;
@ -81,6 +82,9 @@ public class GetDiff implements RestReadView<FileResource> {
@Option(name = "--intraline")
boolean intraline;
@Option(name = "--weblinks-only")
boolean webLinksOnly;
@Inject
GetDiff(ProjectCache projectCache,
PatchScriptFactory.Factory patchScriptFactoryFactory,
@ -148,20 +152,35 @@ public class GetDiff implements RestReadView<FileResource> {
projectCache.get(resource.getRevision().getChange().getProject());
Result result = new Result();
// TODO referring to the parent commit by refs/changes/12/60012/1^1
// will likely not work for inline edits
String revA = basePatchSet != null
? basePatchSet.getRefName()
: resource.getRevision().getPatchSet().getRefName() + "^1";
String revB = resource.getRevision().getEdit().isPresent()
? resource.getRevision().getEdit().get().getRefName()
: resource.getRevision().getPatchSet().getRefName();
FluentIterable<DiffWebLinkInfo> links =
webLinks.getDiffLinks(state.getProject().getName(),
resource.getPatchKey().getParentKey().getParentKey().get(),
basePatchSet != null ? basePatchSet.getId().get() : null,
revA,
MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
resource.getPatchKey().getParentKey().get(),
revB,
ps.getNewName());
result.webLinks = links.isEmpty() ? null : links.toList();
if (!webLinksOnly) {
if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
result.metaA = new FileMeta();
result.metaA.name = MoreObjects.firstNonNull(ps.getOldName(),
ps.getNewName());
setContentType(result.metaA, state, ps.getFileModeA(), ps.getMimeTypeA());
result.metaA.lines = ps.getA().size();
// TODO referring to the parent commit by refs/changes/12/60012/1^1
// will likely not work for inline edits
String rev = basePatchSet != null
? basePatchSet.getRefName()
: resource.getRevision().getPatchSet().getRefName() + "^1";
result.metaA.webLinks =
getFileWebLinks(state.getProject(), rev, result.metaA.name);
getFileWebLinks(state.getProject(), revA, result.metaA.name);
}
if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
@ -169,11 +188,8 @@ public class GetDiff implements RestReadView<FileResource> {
result.metaB.name = ps.getNewName();
setContentType(result.metaB, state, ps.getFileModeB(), ps.getMimeTypeB());
result.metaB.lines = ps.getB().size();
String rev = resource.getRevision().getEdit().isPresent()
? resource.getRevision().getEdit().get().getRefName()
: resource.getRevision().getPatchSet().getRefName();
result.metaB.webLinks =
getFileWebLinks(state.getProject(), rev, result.metaB.name);
getFileWebLinks(state.getProject(), revB, result.metaB.name);
}
if (intraline) {
@ -191,6 +207,8 @@ public class GetDiff implements RestReadView<FileResource> {
result.diffHeader = ps.getPatchHeader();
}
result.content = content.lines;
}
Response<Result> r = Response.ok(result);
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
@ -217,6 +235,7 @@ public class GetDiff implements RestReadView<FileResource> {
ChangeType changeType;
List<String> diffHeader;
List<ContentEntry> content;
List<DiffWebLinkInfo> webLinks;
}
static class FileMeta {

View File

@ -33,6 +33,7 @@ import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
import com.google.gerrit.extensions.webui.BranchWebLink;
import com.google.gerrit.extensions.webui.DiffWebLink;
import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
@ -287,6 +288,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
DynamicSet.setOf(binder(), PatchSetWebLink.class);
DynamicSet.setOf(binder(), FileWebLink.class);
DynamicSet.setOf(binder(), DiffWebLink.class);
DynamicSet.setOf(binder(), ProjectWebLink.class);
DynamicSet.setOf(binder(), BranchWebLink.class);