Skip to content
Browse files
[FIXED JENKINS-17575] Redirecting properly after deleting jobs in fol…

Expanded MockFolder to be a ViewGroup so that it is possible to test views inside folders,
and generally fixed it to service Stapler hierarchies properly so that WebClient-based tests can work.
(cherry picked from commit 1205e9f)
  • Loading branch information
jglick authored and olivergondza committed Sep 18, 2013
1 parent adcdf61 commit 134e01e5be84dbff193d7166b95534ed3b0d897e
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 6 deletions.
@@ -95,6 +95,7 @@
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
@@ -117,6 +118,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
@@ -1892,11 +1894,20 @@ public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOExcepti
if (req == null || rsp == null)
View view = req.findAncestorObject(View.class);
if (view == null)
rsp.sendRedirect2(req.getContextPath() + '/' + getParent().getUrl());
rsp.sendRedirect2(req.getContextPath() + '/' + view.getUrl());
List<Ancestor> ancestors = req.getAncestors();
ListIterator<Ancestor> it = ancestors.listIterator(ancestors.size());
String url = getParent().getUrl(); // fallback but we ought to get to Jenkins.instance at the root
while (it.hasPrevious()) {
Object a = it.previous().getObject();
if (a instanceof View) {
url = ((View) a).getUrl();
} else if (a instanceof ViewGroup) {
url = ((ViewGroup) a).getUrl();
rsp.sendRedirect2(req.getContextPath() + '/' + url);

@@ -26,37 +26,53 @@

import hudson.Extension;
import hudson.model.AbstractItem;
import hudson.model.Action;
import hudson.model.AllView;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.ItemGroupMixIn;
import hudson.model.Job;
import hudson.model.TopLevelItem;
import hudson.model.TopLevelItemDescriptor;
import hudson.model.View;
import hudson.model.ViewGroup;
import hudson.model.ViewGroupMixIn;
import hudson.util.Function1;
import hudson.views.DefaultViewsTabBar;
import hudson.views.ViewsTabBar;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import jenkins.model.ModifiableTopLevelItemGroup;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;

* Minimal implementation of a modifiable item group akin to the CloudBees Folders plugin.
* No UI, just enough implementation to test functionality of code which should deal with item full names, etc.
* @since 1.494
@SuppressWarnings({"unchecked", "rawtypes"}) // the usual API mistakes
public class MockFolder extends AbstractItem implements ModifiableTopLevelItemGroup, TopLevelItem { // could be a ViewGroup too
public class MockFolder extends AbstractItem implements ModifiableTopLevelItemGroup, TopLevelItem, ViewGroup, StaplerFallback {

private transient Map<String,TopLevelItem> items = new TreeMap<String,TopLevelItem>();
private final List<View> views = new ArrayList<View>(Collections.singleton(new AllView("All", this)));
private String primaryView;
private ViewsTabBar viewsTabBar;

private MockFolder(ItemGroup parent, String name) {
super(parent, name);
@@ -102,6 +118,20 @@ private ItemGroupMixIn mixin() {

private ViewGroupMixIn vgmixin() {
return new ViewGroupMixIn(this) {
@Override protected List<View> views() {
return views;
@Override protected String primaryView() {
return primaryView != null ? primaryView : views.get(0).getViewName();
@Override protected void primaryView(String newName) {
primaryView = newName;

@Override public <T extends TopLevelItem> T copy(T src, String name) throws IOException {
return mixin().copy(src, name);
@@ -156,6 +186,63 @@ public <T extends TopLevelItem> T createProject(Class<T> type, String name) thro
return Jenkins.getInstance().getDescriptorByType(DescriptorImpl.class);

public void addView(View view) throws IOException {

@Override public boolean canDelete(View view) {
return vgmixin().canDelete(view);

@Override public void deleteView(View view) throws IOException {

@Override public Collection<View> getViews() {
return vgmixin().getViews();

@Override public View getView(String name) {
return vgmixin().getView(name);

@Override public View getPrimaryView() {
return vgmixin().getPrimaryView();

@Override public void onViewRenamed(View view, String oldName, String newName) {
vgmixin().onViewRenamed(view, oldName, newName);

@Override public ViewsTabBar getViewsTabBar() {
if (viewsTabBar == null) {
viewsTabBar = new DefaultViewsTabBar();
return viewsTabBar;

@Override public ItemGroup<? extends TopLevelItem> getItemGroup() {
return this;

@Override public List<Action> getViewActions() {
// XXX what should the default be? View.getOwnerViewActions uses Jenkins.actions; Jenkins.viewActions would make more sense as a default;
// or should it be empty by default since non-top-level folders probably do not need the same actions as root?
return Collections.emptyList();

@Override public Object getStaplerFallback() {
return getPrimaryView();

* Same as {@link #getItem} but named this way as a {@link WebMethod}.
* @see Hudson#getJob
public TopLevelItem getJob(String name) {
return getItem(name);

@Extension public static class DescriptorImpl extends TopLevelItemDescriptor {

@Override public String getDisplayName() {
@@ -379,12 +379,21 @@ public void testExternalBuildDirectoryRenameDelete() throws Exception {

public void testDeleteRedirect() throws Exception {
assertEquals("", deleteRedirectTarget("job/j1"));
Jenkins.getInstance().addView(new AllView("v1"));
assertEquals("view/v1/", deleteRedirectTarget("view/v1/job/j2"));
MockFolder d = Jenkins.getInstance().createProject(MockFolder.class, "d");
d.addView(new AllView("v2"));
d.createProject(FreeStyleProject.class, "j3");
d.createProject(FreeStyleProject.class, "j4");
d.createProject(FreeStyleProject.class, "j5");
assertEquals("job/d/", deleteRedirectTarget("job/d/job/j3"));
assertEquals("job/d/view/v2/", deleteRedirectTarget("job/d/view/v2/job/j4"));
assertEquals("view/v1/job/d/", deleteRedirectTarget("view/v1/job/d/job/j5"));
private String deleteRedirectTarget(String job) throws Exception {
WebClient wc = new WebClient();

0 comments on commit 134e01e

Please sign in to comment.