Skip to content

Commit

Permalink
Merge pull request #2117 from recena/JENKINS-31162
Browse files Browse the repository at this point in the history
[JENKINS-31162] New item categorization
  • Loading branch information
daniel-beck committed Mar 24, 2016
2 parents 6790c3f + c011124 commit 0de3068
Show file tree
Hide file tree
Showing 80 changed files with 1,299 additions and 506 deletions.
18 changes: 18 additions & 0 deletions core/src/main/java/hudson/model/FreeStyleProject.java
Expand Up @@ -25,6 +25,7 @@

import hudson.Extension;
import jenkins.model.Jenkins;
import jenkins.model.item_category.StandaloneProjectsCategory;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

Expand Down Expand Up @@ -65,12 +66,29 @@ public DescriptorImpl getDescriptor() {
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

public static final class DescriptorImpl extends AbstractProjectDescriptor {

public String getDisplayName() {
return Messages.FreeStyleProject_DisplayName();
}

public FreeStyleProject newInstance(ItemGroup parent, String name) {
return new FreeStyleProject(parent,name);
}

@Override
public String getDescription() {
return Messages.FreeStyleProject_Description();
}

@Override
public String getCategoryId() {
return StandaloneProjectsCategory.ID;
}

@Override
public String getIconFilePathPattern() {
return (Jenkins.RESOURCE_PATH + "/images/:size/freestyleproject.png").replaceFirst("^/", "");
}

}
}
99 changes: 99 additions & 0 deletions core/src/main/java/hudson/model/TopLevelItemDescriptor.java
Expand Up @@ -25,15 +25,33 @@

import hudson.ExtensionList;
import jenkins.model.Jenkins;
import jenkins.model.item_category.ItemCategory;
import org.acegisecurity.AccessDeniedException;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.jelly.DefaultScriptInvoker;
import org.kohsuke.stapler.jelly.JellyClassTearOff;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.StringWriter;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* {@link Descriptor} for {@link TopLevelItem}s.
*
* @author Kohsuke Kawaguchi
*/
public abstract class TopLevelItemDescriptor extends Descriptor<TopLevelItem> {

static final Logger LOGGER = Logger.getLogger(TopLevelItemDescriptor.class.getName());

protected TopLevelItemDescriptor(Class<? extends TopLevelItem> clazz) {
super(clazz);
}
Expand Down Expand Up @@ -113,6 +131,87 @@ public String getDisplayName() {
return super.getDisplayName();
}

/**
* A description of this kind of item type. This description can contain HTML code but it is recommend to use text plain
* in order to avoid how it should be represented.
*
* This method should be called in a thread where Stapler is associated, but it will return an empty string.
*
* @return A string, by default the value from newInstanceDetail view is taken.
*
* @since TODO
*/
@Nonnull
public String getDescription() {
Stapler stapler = Stapler.getCurrent();
if (stapler != null) {
try {
WebApp webapp = WebApp.getCurrent();
MetaClass meta = webapp.getMetaClass(this);
Script s = meta.loadTearOff(JellyClassTearOff.class).findScript("newInstanceDetail");
if (s == null) {
return "";
}
DefaultScriptInvoker dsi = new DefaultScriptInvoker();
StringWriter sw = new StringWriter();
XMLOutput xml = dsi.createXMLOutput(sw, true);
dsi.invokeScript(Stapler.getCurrentRequest(), Stapler.getCurrentResponse(), s, this, xml);
return sw.toString();
} catch (Exception e) {
LOGGER.log(Level.WARNING, null, e);
return "";
}
} else {
return "";
}
}

/**
* Used to categorize this kind of item type. @see {@link ItemCategory}
*
* @return A string with the category identifier, {@link ItemCategory.UncategorizedCategory#getId()} by default.
*
* @since TODO
*/
@Nonnull
public String getCategoryId() {
return ItemCategory.UncategorizedCategory.ID;
}

/**
* Represents a file path pattern to get the Item icon in different sizes.
*
* For example: plugin/plugin-shortname/images/:size/item.png, where {@code :size} represents the different
* icon sizes used commonly in Jenkins project: 16x16, 24x24, 32x32 or 48x48
*
* @see {@link FreeStyleProject.DescriptorImpl#getIconFilePathPattern()}
*
* @return A string or null if it is not defined.
*
* @since TODO
*/
@CheckForNull
public String getIconFilePathPattern() {
return null;
}

/**
* An icon file path associated to a specific size.
*
* @param size A string with values that represent the common sizes: 16x16, 24x24, 32x32 or 48x48
*
* @return A string or null if it is not defined.
*
* @since TODO
*/
@CheckForNull
public String getIconFilePath(String size) {
if (!StringUtils.isBlank(getIconFilePathPattern())) {
return getIconFilePathPattern().replace(":size", size);
}
return null;
}

/**
* @deprecated since 2007-01-19.
* This is not a valid operation for {@link Item}s.
Expand Down
40 changes: 39 additions & 1 deletion core/src/main/java/hudson/model/View.java
Expand Up @@ -54,15 +54,19 @@
import hudson.util.XStream2;
import hudson.views.ListViewColumn;
import hudson.widgets.Widget;
import jenkins.model.item_category.Categories;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.item_category.Category;
import jenkins.model.item_category.ItemCategory;
import jenkins.util.ProgressiveRendering;
import jenkins.util.xml.XMLUtils;

import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.tools.ant.filters.StringInputStream;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.Stapler;
Expand All @@ -84,6 +88,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -917,7 +922,7 @@ public SearchIndexBuilder makeSearchIndex() {
SearchIndexBuilder sib = super.makeSearchIndex();
sib.add(new CollectionSearchIndex<TopLevelItem>() {// for jobs in the view
protected TopLevelItem get(String key) { return getItem(key); }
protected Collection<TopLevelItem> all() { return getItems(); }
protected Collection<TopLevelItem> all() { return getItems(); }
@Override
protected String getName(TopLevelItem o) {
// return the name instead of the display for suggestion searching
Expand Down Expand Up @@ -1000,6 +1005,39 @@ public synchronized void doDoDelete(StaplerRequest req, StaplerResponse rsp) thr
*/
public abstract Item doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;

/**
* An API REST method to get the allowed {$link TopLevelItem}s and its categories.
*
* @return A {@link Categories} entity that is shown as JSON file.
*/
@Restricted(DoNotUse.class)
public Categories doCategories(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
Categories categories = new Categories();
int order = 0;
for (TopLevelItemDescriptor descriptor : DescriptorVisibilityFilter.apply(getOwnerItemGroup(), Items.all(Jenkins.getAuthentication(), getOwnerItemGroup()))) {
ItemCategory ic = ItemCategory.getCategory(descriptor);
Map<String, Serializable> metadata = new HashMap<String, Serializable>();

// Information about Item.
metadata.put("class", descriptor.getId());
metadata.put("order", ++order);
metadata.put("displayName", descriptor.getDisplayName());
metadata.put("description", descriptor.getDescription());
metadata.put("iconFilePathPattern", descriptor.getIconFilePathPattern());

Category category = categories.getItem(ic.getId());
if (category != null) {
category.getItems().add(metadata);
} else {
List<Map<String, Serializable>> temp = new ArrayList<Map<String, Serializable>>();
temp.add(metadata);
category = new Category(ic.getId(), ic.getDisplayName(), ic.getDescription(), ic.getOrder(), ic.getMinToShow(), temp);
categories.getItems().add(category);
}
}
return categories;
}

public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
rss(req, rsp, " all builds", getBuilds());
}
Expand Down
55 changes: 55 additions & 0 deletions core/src/main/java/jenkins/model/item_category/Categories.java
@@ -0,0 +1,55 @@
package jenkins.model.item_category;

import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Flavor;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
* It is a logic representation of a set of {@link Category}.
*
* This class is not thread-safe.
*/
@ExportedBean
@Restricted(NoExternalUse.class)
public class Categories implements HttpResponse, Serializable {

private List<Category> items;

public Categories() {
items = new ArrayList<Category>();
}

@Exported(name = "categories")
public List<Category> getItems() {
return items;
}

@Override
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
rsp.serveExposedBean(req, this, Flavor.JSON);
}

@CheckForNull
public Category getItem(@Nonnull String id) {
for (Category category : items) {
if (category.getId().equals(id)) {
return category;
}
}
return null;
}

}
73 changes: 73 additions & 0 deletions core/src/main/java/jenkins/model/item_category/Category.java
@@ -0,0 +1,73 @@
package jenkins.model.item_category;

import hudson.model.TopLevelItem;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
* Represents an {@link ItemCategory} and its {@link TopLevelItem}s.
*
* This class is not thread-safe.
*/
@ExportedBean
@Restricted(NoExternalUse.class)
public class Category implements Serializable {

private String id;

private String name;

private String description;

private int order;

private int minToShow;

private List<Map<String, Serializable>> items;

public Category(String id, String name, String description, int order, int minToShow, List<Map<String, Serializable>> items) {
this.id= id;
this.name = name;
this.description = description;
this.order = order;
this.minToShow = minToShow;
this.items = items;
}

@Exported
public String getId() {
return id;
}

@Exported
public String getName() {
return name;
}

@Exported
public String getDescription() {
return description;
}

@Exported
public int getOrder() {
return order;
}

@Exported
public int getMinToShow() {
return minToShow;
}

@Exported
public List<Map<String, Serializable>> getItems() {
return items;
}

}

0 comments on commit 0de3068

Please sign in to comment.