Merge "Add WebLink extension point for file history"

This commit is contained in:
David Pursehouse
2015-08-26 00:49:33 +00:00
committed by Gerrit Code Review
9 changed files with 208 additions and 15 deletions

View File

@@ -32,6 +32,7 @@ public class ProjectAccess {
protected LabelTypes labelTypes; protected LabelTypes labelTypes;
protected Map<String, String> capabilities; protected Map<String, String> capabilities;
protected Map<AccountGroup.UUID, GroupInfo> groupInfo; protected Map<AccountGroup.UUID, GroupInfo> groupInfo;
protected List<WebLinkInfoCommon> fileHistoryLinks;
public ProjectAccess() { public ProjectAccess() {
} }
@@ -132,4 +133,12 @@ public class ProjectAccess {
public void setGroupInfo(Map<AccountGroup.UUID, GroupInfo> m) { public void setGroupInfo(Map<AccountGroup.UUID, GroupInfo> m) {
groupInfo = m; groupInfo = m;
} }
public void setFileHistoryLinks(List<WebLinkInfoCommon> links) {
fileHistoryLinks = links;
}
public List<WebLinkInfoCommon> getFileHistoryLinks() {
return fileHistoryLinks;
}
} }

View File

@@ -0,0 +1,24 @@
// 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.common.data;
public class WebLinkInfoCommon {
public WebLinkInfoCommon() {}
public String name;
public String imageUrl;
public String url;
public String target;
}

View File

@@ -0,0 +1,37 @@
// 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.extensions.webui;
import com.google.gerrit.extensions.common.WebLinkInfo;
public interface FileHistoryWebLink extends WebLink {
/**
* {@link com.google.gerrit.extensions.common.WebLinkInfo}
* describing a link from a file to an external service displaying
* a log for that file.
*
* <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 revision Name of the revision (e.g. branch or commit ID)
* @param fileName Name of the file
* @return WebLinkInfo that links to a log for the file in external
* service, null if there should be no link.
*/
WebLinkInfo getFileHistoryWebLink(String projectName, String revision, String fileName);
}

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.ParentProjectBox; import com.google.gerrit.client.ui.ParentProjectBox;
import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess; import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.WebLinkInfoCommon;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RefNames;
@@ -40,6 +41,7 @@ import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -65,7 +67,7 @@ public class ProjectAccessEditor extends Composite implements
DivElement history; DivElement history;
@UiField @UiField
Anchor gitweb; FlowPanel webLinkPanel;
@UiField @UiField
FlowPanel localContainer; FlowPanel localContainer;
@@ -120,16 +122,7 @@ public class ProjectAccessEditor extends Composite implements
} else { } else {
inheritsFrom.getStyle().setDisplay(Display.NONE); inheritsFrom.getStyle().setDisplay(Display.NONE);
} }
setUpWebLinks();
GitwebInfo c = Gerrit.info().gitweb();
if (value.isConfigVisible() && c != null) {
history.getStyle().setDisplay(Display.BLOCK);
gitweb.setText(c.getLinkName());
gitweb.setHref(c.toFileHistory(new Branch.NameKey(value.getProjectName(),
RefNames.REFS_CONFIG), "project.config"));
} else {
history.getStyle().setDisplay(Display.NONE);
}
addSection.setVisible(editing && (!value.getOwnerOf().isEmpty() || value.canUpload())); addSection.setVisible(editing && (!value.getOwnerOf().isEmpty() || value.canUpload()));
} }
@@ -162,6 +155,53 @@ public class ProjectAccessEditor extends Composite implements
addSection.setVisible(editing); addSection.setVisible(editing);
} }
private void setUpWebLinks() {
if (!value.isConfigVisible()) {
history.getStyle().setDisplay(Display.NONE);
} else {
GitwebInfo c = Gerrit.info().gitweb();
List<WebLinkInfoCommon> links = value.getFileHistoryLinks();
if (c == null && links == null) {
history.getStyle().setDisplay(Display.NONE);
}
if (c != null) {
webLinkPanel.add(toAnchor(c.toFileHistory(new Branch.NameKey(value.getProjectName(),
RefNames.REFS_CONFIG), "project.config"), c.getLinkName()));
}
if (links != null) {
for (WebLinkInfoCommon link : links) {
webLinkPanel.add(toAnchor(link));
}
}
}
}
private Anchor toAnchor(String href, String name) {
Anchor a = new Anchor();
a.setHref(href);
a.setText(name);
return a;
}
private static Anchor toAnchor(WebLinkInfoCommon info) {
Anchor a = new Anchor();
a.setHref(info.url);
if (info.target != null && !info.target.isEmpty()) {
a.setTarget(info.target);
}
if (info.imageUrl != null && !info.imageUrl.isEmpty()) {
Image img = new Image();
img.setAltText(info.name);
img.setUrl(info.imageUrl);
img.setTitle(info.name);
a.getElement().appendChild(img.getElement());
} else {
a.setText("(" + info.name + ")");
}
return a;
}
private class Source extends EditorSource<AccessSectionEditor> { private class Source extends EditorSource<AccessSectionEditor> {
private final FlowPanel container; private final FlowPanel container;

View File

@@ -39,9 +39,12 @@ limitations under the License.
.historyTitle { .historyTitle {
font-weight: bold; font-weight: bold;
} }
.gitwebLink { .webLinkPanel a {
display: inline; display: inline;
} }
.webLinkPanel>a {
margin-left:2px;
}
.addContainer { .addContainer {
margin-top: 5px; margin-top: 5px;
@@ -62,7 +65,9 @@ limitations under the License.
</div> </div>
<div ui:field='history' class='{style.history}'> <div ui:field='history' class='{style.history}'>
<span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span> <span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span>
<g:Anchor ui:field='gitweb' styleName='{style.gitwebLink}'></g:Anchor> <td>
<g:FlowPanel ui:field="webLinkPanel" styleName='{style.webLinkPanel}'/>
</td>
</div> </div>
<g:FlowPanel ui:field='localContainer'/> <g:FlowPanel ui:field='localContainer'/>

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc.project; package com.google.gerrit.httpd.rpc.project;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.common.data.GroupDescription;
@@ -23,11 +24,13 @@ import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.ProjectAccess; import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.RefConfigSection; import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.common.data.WebLinkInfoCommon;
import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler; import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.GroupBackend; import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
@@ -64,6 +67,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
private final Project.NameKey projectName; private final Project.NameKey projectName;
private ProjectControl pc; private ProjectControl pc;
private WebLinks webLinks;
@Inject @Inject
ProjectAccessFactory(final GroupBackend groupBackend, ProjectAccessFactory(final GroupBackend groupBackend,
@@ -72,6 +76,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
final GroupControl.Factory groupControlFactory, final GroupControl.Factory groupControlFactory,
final MetaDataUpdate.Server metaDataUpdateFactory, final MetaDataUpdate.Server metaDataUpdateFactory,
final AllProjectsName allProjectsName, final AllProjectsName allProjectsName,
final WebLinks webLinks,
@Assisted final Project.NameKey name) { @Assisted final Project.NameKey name) {
this.groupBackend = groupBackend; this.groupBackend = groupBackend;
@@ -80,6 +85,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
this.groupControlFactory = groupControlFactory; this.groupControlFactory = groupControlFactory;
this.metaDataUpdateFactory = metaDataUpdateFactory; this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allProjectsName = allProjectsName; this.allProjectsName = allProjectsName;
this.webLinks = webLinks;
this.projectName = name; this.projectName = name;
} }
@@ -209,9 +215,17 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible()); detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
detail.setGroupInfo(buildGroupInfo(local)); detail.setGroupInfo(buildGroupInfo(local));
detail.setLabelTypes(pc.getLabelTypes()); detail.setLabelTypes(pc.getLabelTypes());
detail.setFileHistoryLinks(getConfigFileLogLinks(projectName.get()));
return detail; return detail;
} }
private List<WebLinkInfoCommon> getConfigFileLogLinks(String projectName) {
FluentIterable<WebLinkInfoCommon> links =
webLinks.getFileHistoryLinksCommon(projectName, RefNames.REFS_CONFIG,
ProjectConfig.PROJECT_CONFIG);
return links.isEmpty() ? null : links.toList();
}
private Map<AccountGroup.UUID, GroupInfo> buildGroupInfo(List<AccessSection> local) { private Map<AccountGroup.UUID, GroupInfo> buildGroupInfo(List<AccessSection> local) {
Map<AccountGroup.UUID, GroupInfo> infos = new HashMap<>(); Map<AccountGroup.UUID, GroupInfo> infos = new HashMap<>();
for (AccessSection section : local) { for (AccessSection section : local) {

View File

@@ -18,11 +18,13 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.gerrit.common.data.WebLinkInfoCommon;
import com.google.gerrit.extensions.common.DiffWebLinkInfo; import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.WebLinkInfo; import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.BranchWebLink; import com.google.gerrit.extensions.webui.BranchWebLink;
import com.google.gerrit.extensions.webui.DiffWebLink; import com.google.gerrit.extensions.webui.DiffWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.FileWebLink; import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink; import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink; import com.google.gerrit.extensions.webui.ProjectWebLink;
@@ -53,9 +55,26 @@ public class WebLinks {
return true; return true;
} }
}; };
private static final Predicate<WebLinkInfoCommon> INVALID_WEBLINK_COMMON =
new Predicate<WebLinkInfoCommon>() {
@Override
public boolean apply(WebLinkInfoCommon link) {
if (link == null) {
return false;
} else if (Strings.isNullOrEmpty(link.name)
|| Strings.isNullOrEmpty(link.url)) {
log.warn(String.format("%s is missing name and/or url", link
.getClass().getName()));
return false;
}
return true;
}
};
private final DynamicSet<PatchSetWebLink> patchSetLinks; private final DynamicSet<PatchSetWebLink> patchSetLinks;
private final DynamicSet<FileWebLink> fileLinks; private final DynamicSet<FileWebLink> fileLinks;
private final DynamicSet<FileHistoryWebLink> fileHistoryLinks;
private final DynamicSet<DiffWebLink> diffLinks; private final DynamicSet<DiffWebLink> diffLinks;
private final DynamicSet<ProjectWebLink> projectLinks; private final DynamicSet<ProjectWebLink> projectLinks;
private final DynamicSet<BranchWebLink> branchLinks; private final DynamicSet<BranchWebLink> branchLinks;
@@ -63,11 +82,14 @@ public class WebLinks {
@Inject @Inject
public WebLinks(DynamicSet<PatchSetWebLink> patchSetLinks, public WebLinks(DynamicSet<PatchSetWebLink> patchSetLinks,
DynamicSet<FileWebLink> fileLinks, DynamicSet<FileWebLink> fileLinks,
DynamicSet<FileHistoryWebLink> fileLogLinks,
DynamicSet<DiffWebLink> diffLinks, DynamicSet<DiffWebLink> diffLinks,
DynamicSet<ProjectWebLink> projectLinks, DynamicSet<ProjectWebLink> projectLinks,
DynamicSet<BranchWebLink> branchLinks) { DynamicSet<BranchWebLink> branchLinks
) {
this.patchSetLinks = patchSetLinks; this.patchSetLinks = patchSetLinks;
this.fileLinks = fileLinks; this.fileLinks = fileLinks;
this.fileHistoryLinks = fileLogLinks;
this.diffLinks = diffLinks; this.diffLinks = diffLinks;
this.projectLinks = projectLinks; this.projectLinks = projectLinks;
this.branchLinks = branchLinks; this.branchLinks = branchLinks;
@@ -108,6 +130,46 @@ public class WebLinks {
}); });
} }
/**
*
* @param project Project name.
* @param revision SHA1 of revision.
* @param file File name.
* @return Links for file history
*/
public FluentIterable<WebLinkInfo> getFileHistoryLinks(final String project,
final String revision, final String file) {
return filterLinks(fileHistoryLinks, new Function<WebLink, WebLinkInfo>() {
@Override
public WebLinkInfo apply(WebLink webLink) {
return ((FileHistoryWebLink) webLink).getFileHistoryWebLink(project,
revision, file);
}
});
}
public FluentIterable<WebLinkInfoCommon> getFileHistoryLinksCommon(
final String project, final String revision, final String file) {
return FluentIterable
.from(fileHistoryLinks)
.transform(new Function<WebLink, WebLinkInfoCommon>() {
@Override
public WebLinkInfoCommon apply(WebLink webLink) {
WebLinkInfo info =
((FileHistoryWebLink) webLink).getFileHistoryWebLink(project,
revision, file);
WebLinkInfoCommon commonInfo = new WebLinkInfoCommon();
commonInfo.name = info.name;
commonInfo.imageUrl = info.imageUrl;
commonInfo.url = info.url;
commonInfo.target = info.target;
return commonInfo;
}
})
.filter(INVALID_WEBLINK_COMMON);
}
/** /**
* *
* @param project Project name. * @param project Project name.

View File

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