提交 26603ed4 编写于 作者: K kohsuke

Hudson no longer extends from View, so that it can use a different view...

Hudson no longer extends from View, so that it can use a different view implementation to render the top page.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@13987 71c3de6d-444a-0410-be80-ed276b4c234a
上级 6a911d73
......@@ -231,7 +231,7 @@
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler</artifactId>
<version>1.90</version>
<version>1.91</version>
</dependency>
<dependency>
<groupId>org.jvnet.localizer</groupId>
......
package hudson.model;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Collection;
/**
* {@link View} that contains everything.
*
* @author Kohsuke Kawaguchi
* @since 1.269
*/
public class AllView extends View {
@DataBoundConstructor
public AllView(String name) {
super(name);
}
@Override
public boolean contains(TopLevelItem item) {
return true;
}
@Override
public Item doCreateItem(StaplerRequest req, StaplerResponse rsp)
throws IOException, ServletException {
return Hudson.getInstance().doCreateItem(req, rsp);
}
@Override
public Collection<TopLevelItem> getItems() {
return Hudson.getInstance().getItems();
}
@Override
public String getPostConstructLandingPage() {
return ""; // there's no configuration page
}
@Override
public void onJobRenamed(Item item, String oldName, String newName) {
// noop
}
public ViewDescriptor getDescriptor() {
return DESCRIPTOR;
}
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
static {
LIST.add(DESCRIPTOR);
}
public static final class DescriptorImpl extends ViewDescriptor {
private DescriptorImpl() {
super(AllView.class);
}
@Override
public boolean isInstantiable() {
return false;
}
public String getDisplayName() {
return Messages.Hudson_ViewName();
}
}
}
......@@ -90,6 +90,7 @@ import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.export.Exported;
import javax.servlet.ServletContext;
......@@ -126,7 +127,6 @@ import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -148,7 +148,7 @@ import javax.servlet.RequestDispatcher;
*
* @author Kohsuke Kawaguchi
*/
public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node, StaplerProxy, ViewGroup {
public final class Hudson extends AbstractModelObject implements ItemGroup<TopLevelItem>, Node, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled {
private transient final Queue queue;
/**
......@@ -255,7 +255,12 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
/**
* {@link View}s.
*/
private List<View> views; // can't initialize it eagerly for backward compatibility
private final CopyOnWriteArrayList<View> views = new CopyOnWriteArrayList<View>();
/**
* Name of the primary view.
*/
private volatile String primaryView;
private transient final FingerprintMap fingerprintMap = new FingerprintMap();
......@@ -331,7 +336,6 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
private transient final LogRecorderManager log = new LogRecorderManager();
public Hudson(File root, ServletContext context) throws IOException {
super(null);
this.root = root;
this.servletContext = context;
if(theInstance!=null)
......@@ -460,6 +464,21 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return noUsageStatistics==null || !noUsageStatistics;
}
public View.People getPeople() {
return new View.People(this);
}
/**
* Does this {@link View} has any associated user information recorded?
*/
public final boolean hasPeople() {
return View.People.isApplicable(items.values());
}
public Api getApi() {
return new Api(this);
}
/**
* Returns a secret key that survives across container start/stop.
* <p>
......@@ -469,20 +488,6 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return secretKey;
}
/**
* @deprecated
* {@link Hudson} can't be renamed unlike a regular view.
*/
@Override
public void rename(String newName) throws ParseException {
throw new UnsupportedOperationException();
}
@Override
public String getViewName() {
return Messages.Hudson_ViewName();
}
/**
* Gets the SCM descriptor by name. Primarily used for making them web-visible.
*/
......@@ -534,17 +539,6 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return (JobPropertyDescriptor) d;
}
/**
* @deprecated
* This method is really just implementing {@link View#getDescriptor()}.
* It's unlikely that your program wants to invoke this method explicitly.
* Perhaps you meant {@link #getDescriptor(String)} ?
*/
public ViewDescriptor getDescriptor() {
// TODO
throw new UnsupportedOperationException();
}
/**
* Exposes {@link Descriptor} by its name to URL.
*
......@@ -835,26 +829,11 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return names;
}
/**
* Every job belongs to us.
*
* @deprecated
* why are you calling a method that always return true?
*/
@Deprecated
public boolean contains(TopLevelItem view) {
return true;
}
public synchronized View getView(String name) {
if(views!=null) {
for (View v : views) {
if(v.getViewName().equals(name))
return v;
}
for (View v : views) {
if(v.getViewName().equals(name))
return v;
}
if (this.getViewName().equals(name))
return this;
return null;
}
......@@ -863,21 +842,16 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
*/
@Exported
public synchronized Collection<View> getViews() {
if(views==null)
return Collections.<View>singletonList(this);
List<View> copy = new ArrayList<View>(views.size()+1);
copy.add(this);
copy.addAll(views);
List<View> copy = new ArrayList<View>(views);
Collections.sort(copy, View.SORTER);
return copy;
}
public synchronized void deleteView(View view) throws IOException {
if(views!=null) {
views.remove(view);
save();
}
if(views.size()<=1)
throw new IllegalStateException();
views.remove(view);
save();
}
/**
......@@ -1031,6 +1005,10 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return "";
}
public String getSearchUrl() {
return "";
}
public void onViewRenamed(View view, String oldName, String newName) {
// implementation of Hudson is immune to view name change.
}
......@@ -1041,6 +1019,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
.add("configure", "config","configure")
.add("manage")
.add("log")
.add(getPrimaryView().makeSearchIndex())
.add(new CollectionSearchIndex() {// for computers
protected Computer get(String key) { return getComputer(key); }
protected Collection<Computer> all() { return computers.values(); }
......@@ -1055,6 +1034,16 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
});
}
/**
* Returns the primary {@link View} that renders the top-page of Hudson.
*/
public View getPrimaryView() {
View v = getView(primaryView);
if(v==null) // fallback
v = views.get(0);
return v;
}
public String getUrlChildPrefix() {
return "job";
}
......@@ -1197,9 +1186,12 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return authorizationStrategy.getRootACL();
}
@Override
public void onJobRenamed(Item item, String oldName, String newName) {
// noop
public void checkPermission(Permission p) {
getACL().checkPermission(p);
}
public boolean hasPermission(Permission p) {
return getACL().hasPermission(p);
}
/**
......@@ -1259,7 +1251,6 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
*
* Note that the look up is case-insensitive.
*/
@Override
public TopLevelItem getItem(String name) {
return items.get(name);
}
......@@ -1345,11 +1336,9 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
l.onDeleted(item);
items.remove(item.getName());
if(views!=null) {
for (View v : views)
v.onJobRenamed(item, item.getName(), null);
save();
}
for (View v : views)
v.onJobRenamed(item, item.getName(), null);
save();
}
/**
......@@ -1360,11 +1349,9 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
items.remove(oldName);
items.put(newName,job);
if(views!=null) {
for (View v : views)
v.onJobRenamed(job, oldName, newName);
save();
}
for (View v : views)
v.onJobRenamed(job, oldName, newName);
save();
}
public FingerprintMap getFingerprintMap() {
......@@ -1515,6 +1502,15 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
slave.getAssignedLabels();
}
// initialize views by inserting the default view if necessary
// this is both for clean Hudson and for backward compatibility.
if(views.size()==0 || primaryView==null) {
View v = new AllView(Messages.Hudson_ViewName());
v.owner = this;
views.add(0,v);
primaryView = v.getViewName();
}
// read in old data that doesn't have the security field set
if(authorizationStrategy==null) {
if(useSecurity==null || !useSecurity)
......@@ -1908,8 +1904,6 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
// create a view
View v = View.LIST.findByName(mode).newInstance(req,req.getSubmittedForm());
v.owner = this;
if(views==null)
views = new Vector<View>();
views.add(v);
save();
......@@ -2386,7 +2380,6 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return;
}
error("No error or warning text was set for fieldCheck().");
return;
}
/**
......@@ -2598,6 +2591,13 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
return this;
}
/**
* Fallback to the primary view.
*/
public View getStaplerFallback() {
return getPrimaryView();
}
public static final class MasterComputer extends Computer {
private MasterComputer() {
super(Hudson.getInstance());
......
......@@ -79,14 +79,6 @@ public class ListView extends View {
return items;
}
public TopLevelItem getItem(String name) {
return Hudson.getInstance().getItem(name);
}
public TopLevelItem getJob(String name) {
return getItem(name);
}
public boolean contains(TopLevelItem item) {
return jobNames.contains(item.getName());
}
......
......@@ -26,7 +26,7 @@ public class MyView extends View {
@Override
public boolean contains(TopLevelItem item) {
return item.hasPermission(Hudson.ADMINISTER);
return item.hasPermission(Job.CONFIGURE);
}
@Override
......@@ -35,11 +35,6 @@ public class MyView extends View {
return Hudson.getInstance().doCreateItem(req, rsp);
}
@Override
public TopLevelItem getItem(String name) {
return Hudson.getInstance().getItem(name);
}
@Override
public Collection<TopLevelItem> getItems() {
List<TopLevelItem> items = new ArrayList<TopLevelItem>();
......@@ -61,10 +56,6 @@ public class MyView extends View {
// noop
}
public TopLevelItem getJob(String name) {
return getItem(name);
}
public ViewDescriptor getDescriptor() {
return DESCRIPTOR;
}
......
......@@ -81,7 +81,16 @@ public abstract class View extends AbstractModelObject implements AccessControll
/**
* Gets the {@link TopLevelItem} of the given name.
*/
public abstract TopLevelItem getItem(String name);
public TopLevelItem getItem(String name) {
return Hudson.getInstance().getItem(name);
}
/**
* Alias for {@link #getItem(String)}. This is the one used in the URL binding.
*/
public final TopLevelItem getJob(String name) {
return getItem(name);
}
/**
* Checks if the job is in this collection.
......@@ -129,6 +138,13 @@ public abstract class View extends AbstractModelObject implements AccessControll
return getViewName();
}
/**
* If true, this is a view that renders the top page of Hudson.
*/
public boolean isDefault() {
return Hudson.getInstance().getPrimaryView()==this;
}
/**
* Returns the path relative to the context root.
*
......@@ -136,6 +152,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
* Hudson,
*/
public String getUrl() {
if(isDefault()) return "";
return owner.getUrl()+"view/"+getViewName()+'/';
}
......@@ -274,38 +291,42 @@ public abstract class View extends AbstractModelObject implements AccessControll
* Does this {@link View} has any associated user information recorded?
*/
public final boolean hasPeople() {
for (Item item : getItems()) {
for (Job job : item.getAllJobs()) {
if (job instanceof AbstractProject) {
AbstractProject<?,?> p = (AbstractProject) job;
for (AbstractBuild<?,?> build : p.getBuilds()) {
for (Entry entry : build.getChangeSet()) {
User user = entry.getAuthor();
if(user!=null)
return true;
}
}
}
}
}
return false;
return People.isApplicable(getItems());
}
/**
* Gets the users that show up in the changelog of this job collection.
*/
public final People getPeople() {
return new People();
return new People(this);
}
@ExportedBean
public final class People {
public static final class People {
@Exported
public final List<UserInfo> users;
public People() {
public final Object parent;
public People(Hudson parent) {
this.parent = parent;
// for Hudson, really load all users
Map<User,UserInfo> users = new HashMap<User,UserInfo>();
User unknown = User.getUnknown();
for(User u : User.getAll()) {
if(u==unknown) continue; // skip the special 'unknown' user
UserInfo info = users.get(u);
if(info==null)
users.put(u,new UserInfo(u,null,null));
}
this.users = toList(users);
}
public People(View parent) {
this.parent = parent;
Map<User,UserInfo> users = new HashMap<User,UserInfo>();
for (Item item : getItems()) {
for (Item item : parent.getItems()) {
for (Job job : item.getAllJobs()) {
if (job instanceof AbstractProject) {
AbstractProject<?,?> p = (AbstractProject) job;
......@@ -327,30 +348,37 @@ public abstract class View extends AbstractModelObject implements AccessControll
}
}
if(View.this==Hudson.getInstance()) {
// for Hudson, really load all users
User unknown = User.getUnknown();
for(User u : User.getAll()) {
if(u==unknown) continue; // skip the special 'unknown' user
UserInfo info = users.get(u);
if(info==null)
users.put(u,new UserInfo(u,null,null));
}
}
this.users = toList(users);
}
private List<UserInfo> toList(Map<User,UserInfo> users) {
ArrayList<UserInfo> list = new ArrayList<UserInfo>();
list.addAll(users.values());
Collections.sort(list);
this.users = Collections.unmodifiableList(list);
}
public View getParent() {
return View.this;
return Collections.unmodifiableList(list);
}
public Api getApi() {
return new Api(this);
}
public static boolean isApplicable(Collection<? extends Item> items) {
for (Item item : items) {
for (Job job : item.getAllJobs()) {
if (job instanceof AbstractProject) {
AbstractProject<?,?> p = (AbstractProject) job;
for (AbstractBuild<?,?> build : p.getBuilds()) {
for (Entry entry : build.getChangeSet()) {
User user = entry.getAuthor();
if(user!=null)
return true;
}
}
}
}
}
return false;
}
}
......@@ -435,6 +463,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
static {
LIST.load(ListView.class);
LIST.load(AllView.class);
LIST.load(MyView.class);
}
......
......@@ -64,6 +64,10 @@ public final class SearchIndexBuilder {
return this;
}
public SearchIndexBuilder add(SearchIndexBuilder index) {
return add(index.make());
}
public SearchIndex make() {
SearchIndex r = new FixedSet(items);
for (SearchIndex index : indices)
......
<st:include it="${it.primaryView}" page="sidepanel.jelly" xmlns:st="jelly:stapler" />
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<j:if test="${app.fingerprintMap.ready}">
<l:task icon="images/24x24/search.gif" href="${rootURL}/projectRelationship" title="${%Project Relationship}" />
<l:task icon="images/24x24/fingerprint.gif" href="${rootURL}/fingerprintCheck" title="${%Check File Fingerprint}" />
</j:if>
</j:jelly>
\ No newline at end of file
Project\ Relationship=Projektbeziehungen
Check\ File\ Fingerprint=Fingerabdruck berprfen
Project\ Relationship=Relations entre les projets
Check\ File\ Fingerprint=Vérifier les empreintes numériques
Project\ Relationship=\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u76f8\u95a2\u95a2\u4fc2
Check\ File\ Fingerprint=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u30c1\u30a7\u30c3\u30af
\ No newline at end of file
Project\ Relationship=Relaties tussen projecten
Check\ File\ Fingerprint=Controleer vingerafdrukken
Project\ Relationship=Relacionamento entre Projetos
Check\ File\ Fingerprint=Checar Fingerprint de Arquivo
Project\ Relationship=\u041e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432
Check\ File\ Fingerprint=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0430
Project\ Relationship=Projelerin \u0130li\u015fkisi
Check\ File\ Fingerprint=Dosyadaki\ Parmakizini\ Kontrol\ Et
......@@ -8,25 +8,7 @@
<t:editableDescription permission="${app.ADMINISTER}"/>
</div>
<j:set var="items" value="${it.items}"/>
<j:choose>
<j:when test="${empty(items)}">
<st:include page="noJob.jelly" />
</j:when>
<j:otherwise>
<!-- set @jobBaseUrl="" so that links to jobs will be under this view. -->
<t:projectView jobs="${items}" jobBaseUrl="" showViewTabs="true">
<!-- view tab bar -->
<l:tabBar>
<j:forEach var="v" items="${app.views}">
<l:tab name="${v.viewName}" active="${v==it}" href="${rootURL}/${v.url}" />
</j:forEach>
<j:if test="${it.hasPermission(it.CREATE)}">
<l:tab name="+" href="${rootURL}/newView" active="false" />
</j:if>
</l:tabBar>
</t:projectView>
</j:otherwise>
</j:choose>
<st:include page="main.jelly" />
</l:main-panel>
<l:header>
<!-- for screen resolution detection -->
......
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<j:choose>
<j:when test="${empty(items)}">
<st:include page="noJob.jelly" />
</j:when>
<j:otherwise>
<!-- set @jobBaseUrl="" so that links to jobs will be under this view. -->
<t:projectView jobs="${items}" jobBaseUrl="" showViewTabs="true">
<!-- view tab bar -->
<l:tabBar>
<j:forEach var="v" items="${app.views}">
<l:tab name="${v.viewName}" active="${v==it}" href="${rootURL}/${v.url}" />
</j:forEach>
<j:if test="${it.hasPermission(it.CREATE)}">
<l:tab name="+" href="${rootURL}/newView" active="false" />
</j:if>
</l:tabBar>
</t:projectView>
</j:otherwise>
</j:choose>
</j:jelly>
\ No newline at end of file
......@@ -16,9 +16,14 @@
<l:task icon="images/24x24/user.gif" href="${rootURL}/people/" title="${%People}" />
</j:if>
<l:task icon="images/24x24/notepad.gif" href="${rootURL}/${it.url}builds" title="${%Build History}"/>
<j:if test="${it!=app}">
<j:if test="${!it.isDefault()}">
<!-- this is ugly, but Hudson delegates the rendering of its pages to its primary view -->
<l:task icon="images/24x24/gear.gif" href="configure" title="${%Edit View}" permission="${it.CONFIGURE}" />
<l:task icon="images/24x24/edit-delete.gif" href="delete" title="${%Delete View}" permission="${it.CONFIGURE}" />
<j:if test="${app.fingerprintMap.ready}">
<l:task icon="images/24x24/search.gif" href="${rootURL}/projectRelationship" title="${%Project Relationship}" />
<l:task icon="images/24x24/fingerprint.gif" href="${rootURL}/fingerprintCheck" title="${%Check File Fingerprint}" />
</j:if>
</j:if>
<!-- subtypes can put more stuff here -->
......
......@@ -4,3 +4,5 @@ People=Benutzer
Build\ History=Build-Verlauf
Edit\ View=Ansicht bearbeiten
Delete\ View=Ansicht l?schen
Project\ Relationship=Projektbeziehungen
Check\ File\ Fingerprint=Fingerabdruck ?berpr?fen
......@@ -4,3 +4,5 @@ People=Personnes
Build\ History=Historique des builds
Edit\ View=Editer la vue
Delete\ View=Supprimer la vue
Project\ Relationship=Relations entre les projets
Check\ File\ Fingerprint=V?rifier les empreintes num?riques
......@@ -4,3 +4,5 @@ People=\u4eba\u3005
Build\ History=\u30d3\u30eb\u30c9\u5c65\u6b74
Edit\ View=\u30d3\u30e5\u30fc\u306e\u5909\u66f4
Delete\ View=\u30d3\u30e5\u30fc\u306e\u524a\u9664
Project\ Relationship=\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u76f8\u95a2\u95a2\u4fc2
Check\ File\ Fingerprint=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u30c1\u30a7\u30c3\u30af
\ No newline at end of file
......@@ -4,3 +4,5 @@ People=Gebruikers
Build\ History=Overzicht bouwpogingen
Edit\ View=Overzichtsscherm bewerken
Delete\ View=Overzichtsscherm verwijderen
Project\ Relationship=Relaties tussen projecten
Check\ File\ Fingerprint=Controleer vingerafdrukken
......@@ -4,3 +4,5 @@ People=Pessoas
Build\ History=Hist\u00F3rico de Constru\u00E7\u00E3o
Edit\ View=Editar Vis\u00E3o
Delete\ View=Excluir Vis\u00E3o
Project\ Relationship=Relacionamento entre Projetos
Check\ File\ Fingerprint=Checar Fingerprint de Arquivo
......@@ -4,3 +4,5 @@ People=\u0410\u043a\u043a\u0430\u0443\u043d\u0442\u044b
Build\ History=\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u0441\u0431\u043e\u0440\u043e\u043a
Edit\ View=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434
Delete\ View=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0432\u0438\u0434
Project\ Relationship=\u041e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432
Check\ File\ Fingerprint=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0430
......@@ -4,3 +4,5 @@ People=\u0130nsanlar
Build\ History=Yap\u0131land\u0131rma Ge\u00e7mi\u015fi
Edit\ View=G\u00f6r\u00fcnt\u00fcy\u00fc D\u00fczenle
Delete\ View=G\u00f6r\u00fcnt\u00fcy\u00fc Sil
Project\ Relationship=Projelerin \u0130li\u015fkisi
Check\ File\ Fingerprint=Dosyadaki Parmakizini Kontrol Et
......@@ -135,12 +135,14 @@
<td id="left-top-nav">
<j:forEach var="anc" items="${request.ancestors}">
<j:if test="${h.isModel(anc.object)}">
<j:if test="${anc.prev!=null}">
<j:whitespace> &#187; </j:whitespace>
<j:if test="${anc.prev.url!=anc.url}">
<j:if test="${anc.prev!=null}">
<j:whitespace> &#187; </j:whitespace>
</j:if>
<a href="${anc.url}/">
${anc.object.displayName}
</a>
</j:if>
<a href="${anc.url}/">
${anc.object.displayName}
</a>
</j:if>
</j:forEach>
</td>
......
......@@ -72,7 +72,7 @@
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>htmlunit</artifactId>
<version>2.2-hudson-7</version>
<version>2.2-hudson-8</version>
</dependency>
<dependency>
<!-- for testing JNLP launch. -->
......
......@@ -2,6 +2,7 @@ package hudson.model;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.Node.Mode;
......@@ -75,4 +76,13 @@ public class HudsonTest extends HudsonTestCase {
System.out.println(url);
assertTrue(url.getPath().endsWith("/job/"+p.getName()+"/"));
}
/**
* Top page should only have one item in the breadcrumb.
*/
public void testBreadcrumb() throws Exception {
HtmlPage root = new WebClient().goTo("");
HtmlElement navbar = root.getElementById("left-top-nav");
assertEquals(1,navbar.selectNodes("a").size());
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册