diff --git a/changelog.html b/changelog.html index 6c6870070e43c4149f011e4bdada624f29d9d5d5..43ffa1730945a40828a1d52d025a7380024de137 100644 --- a/changelog.html +++ b/changelog.html @@ -55,6 +55,8 @@ Upcoming changes diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index a330a042bfba8edad253fa7ec7269e3ba901e92a..fc263f5efad7df4d1c23769c08599eed4a9f23fc 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -903,7 +903,7 @@ public class Functions { ItemGroup ig = i.getParent(); url = i.getShortUrl()+url; - if(ig== Jenkins.getInstance()) { + if(ig== Jenkins.getInstance() || (view != null && ig == view.getOwner())) { assert i instanceof TopLevelItem; if(view!=null && view.contains((TopLevelItem)i)) { // if p and the current page belongs to the same view, then return a relative path diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 07cb6825df0fae267f457732796b1fa64e34759d..ca00e912c4f0e967c20c0bb74d18ecbbf4147b36 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -43,6 +43,8 @@ import hudson.util.AtomicFileWriter; import hudson.util.IOException2; import hudson.util.IOUtils; import jenkins.model.Jenkins; + +import org.apache.commons.lang.StringUtils; import org.apache.tools.ant.taskdefs.Copy; import org.apache.tools.ant.types.FileSet; import org.kohsuke.stapler.WebMethod; @@ -54,6 +56,7 @@ import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.StringTokenizer; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -326,6 +329,21 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet if(n.length()==0) return getDisplayName(); else return n+" \u00BB "+getDisplayName(); } + + public String getRelativeDisplayNameFrom(ItemGroup p) { + String relativeName = getRelativeNameFrom(p); + if (relativeName == null) return null; + return relativeName.replace("/", " \u00BB "); + } + + /** + * This method only exists to disambiguate getRelativeNameFrom(Itemgroup) and getRelativeNameFrom(Item) + * @param p + * @return + */ + public String getRelativeNameFromGroup(ItemGroup p) { + return getRelativeNameFrom(p); + } public String getRelativeNameFrom(ItemGroup p) { // first list up all the parents @@ -367,7 +385,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet /** * Called right after when a {@link Item} is loaded from disk. - * This is an opporunity to do a post load processing. + * This is an opportunity to do a post load processing. */ public void onLoad(ItemGroup parent, String name) throws IOException { this.parent = parent; @@ -407,7 +425,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet public String getShortUrl() { return getParent().getUrlChildPrefix()+'/'+Util.rawEncode(getName())+'/'; } - + public String getSearchUrl() { return getShortUrl(); } diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java index b9092facfa2920a2637b678479c4ed0e4afeab4c..003c9e1a19ec25b7241aed5c85ec4ee128cdfecb 100644 --- a/core/src/main/java/hudson/model/Item.java +++ b/core/src/main/java/hudson/model/Item.java @@ -124,6 +124,14 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont * of the ancestors. */ String getFullDisplayName(); + + /** + * Gets the relative display name to this item from the specified group + * + * @since 1.512 + * @return + */ + String getRelativeDisplayNameFrom(ItemGroup g); /** * Gets the relative name to this item from the specified group. @@ -159,7 +167,7 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont * URL that ends with '/'. */ String getShortUrl(); - + /** * Returns the absolute URL of this item. This relies on the current * {@link StaplerRequest} to figure out what the host name is, diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index f58306ebc1b4bb795181ec0aa29b8c6308291539..e4cb36b69287779f3a0eead534461146e8c9a51f 100644 --- a/core/src/main/java/hudson/model/Items.java +++ b/core/src/main/java/hudson/model/Items.java @@ -235,6 +235,31 @@ public class Items { public static XmlFile getConfigFile(Item item) { return getConfigFile(item.getRootDir()); } + + /** + * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree + * and filter them by the given type. + */ + public static List getAllItems(ItemGroup root, Class type) { + List r = new ArrayList(); + + Stack q = new Stack(); + q.push(root); + + while(!q.isEmpty()) { + ItemGroup parent = q.pop(); + for (Item i : parent.getItems()) { + if(type.isInstance(i)) { + if (i.hasPermission(Item.READ)) + r.add(type.cast(i)); + } + if(i instanceof ItemGroup) + q.push((ItemGroup)i); + } + } + + return r; + } /** * Used to load/save job configuration. diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java index cda589ee47a926bfd1b3dc0c337639a3ef1031aa..f406582ca07315afa96c6c12f72b9455e349fa01 100644 --- a/core/src/main/java/hudson/model/ListView.java +++ b/core/src/main/java/hudson/model/ListView.java @@ -33,24 +33,29 @@ import hudson.util.FormValidation; import hudson.util.HttpResponses; import hudson.views.ListViewColumn; import hudson.views.ViewJobFilter; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.interceptor.RequirePOST; -import javax.annotation.concurrent.GuardedBy; -import javax.servlet.ServletException; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.SortedSet; +import java.util.StringTokenizer; import java.util.TreeSet; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import javax.annotation.concurrent.GuardedBy; +import javax.servlet.ServletException; + +import net.sf.json.JSONObject; + +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.interceptor.RequirePOST; + /** * Displays {@link Job}s in a flat list view. * @@ -63,16 +68,25 @@ public class ListView extends View implements Saveable { */ @GuardedBy("this") /*package*/ final SortedSet jobNames = new TreeSet(CaseInsensitiveComparator.INSTANCE); + + @GuardedBy("this") + private transient SortedSet allJobNames; private DescribableList> jobFilters; private DescribableList> columns; + /** * Include regex string. */ private String includeRegex; + /** + * Whether to recurse in ItemGroups + */ + private boolean recurse = true; + /** * Compiled include pattern from the includeRegex string. */ @@ -138,33 +152,31 @@ public class ListView extends View implements Saveable { */ public List getItems() { SortedSet names; + List items = new ArrayList(); + SortedSet allNames; synchronized (this) { names = new TreeSet(jobNames); + allNames = allJobNames = new TreeSet(CaseInsensitiveComparator.INSTANCE); } - if (includePattern != null) { - for (Item item : getOwnerItemGroup().getItems()) { - String itemName = item.getName(); - if (includePattern.matcher(itemName).matches()) { - names.add(itemName); - } - } - } + ItemGroup parent = getOwnerItemGroup(); + includeItems(parent, names); Boolean statusFilter = this.statusFilter; // capture the value to isolate us from concurrent update - List items = new ArrayList(names.size()); - for (TopLevelItem item : getOwnerItemGroup().getItems()) { - if (!names.contains(item.getName())) continue; + for (TopLevelItem item : Items.getAllItems(getOwnerItemGroup(), TopLevelItem.class)) { + if (!names.contains(item.getRelativeNameFrom(getOwnerItemGroup()))) continue; // Add if no status filter or filter matches enabled/disabled status: if(statusFilter == null || !(item instanceof AbstractProject) - || ((AbstractProject)item).isDisabled() ^ statusFilter) + || ((AbstractProject)item).isDisabled() ^ statusFilter) { items.add(item); + allNames.add(item.getName()); + } } // check the filters Iterable jobFilters = getJobFilters(); - List allItems = new ArrayList(getOwnerItemGroup().getItems()); + List allItems = new ArrayList(parent.getItems()); for (ViewJobFilter jobFilter: jobFilters) { items = jobFilter.filter(items, allItems, this); } @@ -173,9 +185,51 @@ public class ListView extends View implements Saveable { return items; } + + private TopLevelItem getItem(ItemGroup parent, String name) { + if (!name.contains("/")) { // same namespace + return parent.getItem(name); + } + StringTokenizer stringTokenizer = new StringTokenizer(name, "/"); + TopLevelItem leaf = null; + ItemGroup node = parent; + // Navigate down to the item using / as separator + while(stringTokenizer.hasMoreTokens()) { + leaf = node.getItem(stringTokenizer.nextToken()); + if (stringTokenizer.hasMoreTokens()) { + if (leaf instanceof ItemGroup) { + node = (ItemGroup)leaf; + } else { + return null; + } + } + } + return leaf; + } + + private void includeItems(ItemGroup parent, SortedSet names) { + includeItems(parent, parent, names); + } + + private void includeItems(ItemGroup root, ItemGroup parent, SortedSet names) { + if (includePattern != null) { + for (Item item : parent.getItems()) { + if (recurse && item instanceof ItemGroup) { + includeItems(root, (ItemGroup)item, names); + } + String itemName = item.getRelativeNameFrom(root); + if (includePattern.matcher(itemName).matches()) { + names.add(itemName); + } + } + } + } public synchronized boolean contains(TopLevelItem item) { - return jobNames.contains(item.getName()); + if(allJobNames == null) { + getItems(); + } + return allJobNames.contains(item.getName()); } /** @@ -193,6 +247,10 @@ public class ListView extends View implements Saveable { public String getIncludeRegex() { return includeRegex; } + + public boolean isRecurse() { + return recurse; + } /** * Filter by enabled/disabled status of jobs. @@ -257,6 +315,7 @@ public class ListView extends View implements Saveable { */ @Override protected void submit(StaplerRequest req) throws ServletException, FormException, IOException { + JSONObject json = req.getSubmittedForm(); synchronized (this) { jobNames.clear(); for (TopLevelItem item : getOwnerItemGroup().getItems()) { @@ -275,16 +334,17 @@ public class ListView extends View implements Saveable { includeRegex = null; includePattern = null; } + recurse = json.optBoolean("recurse", true); if (columns == null) { columns = new DescribableList>(this); } - columns.rebuildHetero(req, req.getSubmittedForm(), ListViewColumn.all(), "columns"); + columns.rebuildHetero(req, json, ListViewColumn.all(), "columns"); if (jobFilters == null) { jobFilters = new DescribableList>(this); } - jobFilters.rebuildHetero(req, req.getSubmittedForm(), ViewJobFilter.all(), "jobFilters"); + jobFilters.rebuildHetero(req, json, ViewJobFilter.all(), "jobFilters"); String filter = Util.fixEmpty(req.getParameter("statusFilter")); statusFilter = filter != null ? "1".equals(filter) : null; diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index e5241ba8f14cb7d731da0299502ebc175c49a333..42e61eaf5a2551c0c22e499763dc702dc3cd8243 100755 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -1385,24 +1385,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro * and filter them by the given type. */ public List getAllItems(Class type) { - List r = new ArrayList(); - - Stack q = new Stack(); - q.push(this); - - while(!q.isEmpty()) { - ItemGroup parent = q.pop(); - for (Item i : parent.getItems()) { - if(type.isInstance(i)) { - if (i.hasPermission(Item.READ)) - r.add(type.cast(i)); - } - if(i instanceof ItemGroup) - q.push((ItemGroup)i); - } - } - - return r; + return Items.getAllItems(this, type); } /** diff --git a/core/src/main/resources/hudson/model/ListView/configure-entries.jelly b/core/src/main/resources/hudson/model/ListView/configure-entries.jelly index ceb721adfc3a8b0908c5bb827b0fb3e2f7cdd363..26e59290d4e467010ead140543722faf624148c6 100644 --- a/core/src/main/resources/hudson/model/ListView/configure-entries.jelly +++ b/core/src/main/resources/hudson/model/ListView/configure-entries.jelly @@ -40,17 +40,20 @@ THE SOFTWARE.
- +
+ checked="${it.includeRegex != null}" help="/help/view-config/includeregex.html" inline="true"> + + + diff --git a/core/src/main/resources/hudson/model/View/main.groovy b/core/src/main/resources/hudson/model/View/main.groovy index 8f51aab6f96d25910aaff56267b1761bbd8c1d19..fdf1b73fced3b635cb6f6a3fb0958f9b6aafcc04 100644 --- a/core/src/main/resources/hudson/model/View/main.groovy +++ b/core/src/main/resources/hudson/model/View/main.groovy @@ -11,7 +11,7 @@ if (items.isEmpty()) { } include(my,"noJob.jelly"); } else { - t.projectView(jobs: items, jobBaseUrl: "", showViewTabs: true, columnExtensions: my.columns, indenter: my.indenter) { + t.projectView(jobs: items, jobBaseUrl: "", showViewTabs: true, columnExtensions: my.columns, indenter: my.indenter, useFullName: true) { set("views",my.owner.views); set("currentView",my); if (my.owner.class == hudson.model.MyViewsProperty.class) { diff --git a/core/src/main/resources/hudson/views/JobColumn/column.jelly b/core/src/main/resources/hudson/views/JobColumn/column.jelly index 4d30d67d596e4b357defab1bcfd907f29ca36ec0..1b6bbe17ae2df01a63d39b2b37fd4327a9714eaa 100644 --- a/core/src/main/resources/hudson/views/JobColumn/column.jelly +++ b/core/src/main/resources/hudson/views/JobColumn/column.jelly @@ -25,6 +25,6 @@ THE SOFTWARE. - ${useFullName ? job.fullDisplayName : job.displayName} + ${useFullName ? job.getRelativeDisplayNameFrom(currentView.owner.itemGroup) : job.displayName} \ No newline at end of file diff --git a/core/src/main/resources/lib/hudson/projectView.jelly b/core/src/main/resources/lib/hudson/projectView.jelly index 895329966c3ee156feae7860f68142b465ad05e2..fb257a70ab353cca8b8ef1ef896e6324cd8ff6d0 100644 --- a/core/src/main/resources/lib/hudson/projectView.jelly +++ b/core/src/main/resources/lib/hudson/projectView.jelly @@ -79,7 +79,7 @@ THE SOFTWARE. - +