Skip to content

Commit

Permalink
[FIXED JENKINS-22967] Introduce directly modifiable views
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergondza committed May 11, 2014
1 parent db98ee0 commit a5ee2d1
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 30 deletions.
107 changes: 107 additions & 0 deletions core/src/main/java/hudson/model/DirectlyModifiableView.java
@@ -0,0 +1,107 @@
/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;

import hudson.util.HttpResponses;

import java.io.IOException;

import javax.servlet.ServletException;

import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;

/**
* View its items can be modified.
*
* @author ogondza
* @since TODO
*/
public abstract class DirectlyModifiableView extends View {

public DirectlyModifiableView(String name) {
super(name);
}

public DirectlyModifiableView(String name, ViewGroup owner) {
super(name, owner);
}

/**
* Remove item from this view.
*
* @return false if item not present in view, true if removed.
* @throws IOException Removal failed.
*/
public abstract boolean remove(TopLevelItem item) throws IOException;

/**
* Add item to this view.
*
* @throws IOException Adding failed.
*/
public abstract void add(TopLevelItem item) throws IOException;

/**
* Handle addJobToView web method.
*
* @param name Item name.
*/
@RequirePOST
public HttpResponse doAddJobToView(@QueryParameter String name) throws IOException, ServletException {
checkPermission(View.CONFIGURE);
if(name==null)
throw new Failure("Query parameter 'name' is required");

TopLevelItem item = getOwnerItemGroup().getItem(name);
if (item == null)
throw new Failure("Query parameter 'name' does not correspond to a known item");

if (contains(item)) return HttpResponses.ok();

add(item);
owner.save();

return HttpResponses.ok();
}

/**
* Handle removeJobFromView web method.
*
* @param name Item name.
*/
@RequirePOST
public HttpResponse doRemoveJobFromView(@QueryParameter String name) throws IOException, ServletException {
checkPermission(View.CONFIGURE);
if(name==null)
throw new Failure("Query parameter 'name' is required");

TopLevelItem item = getOwnerItemGroup().getItem(name);
if (remove(item))
owner.save();

return HttpResponses.ok();
}
}
46 changes: 16 additions & 30 deletions core/src/main/java/hudson/model/ListView.java
Expand Up @@ -63,7 +63,7 @@
*
* @author Kohsuke Kawaguchi
*/
public class ListView extends View {
public class ListView extends DirectlyModifiableView {

/**
* List of job names. This is what gets serialized.
Expand Down Expand Up @@ -237,8 +237,6 @@ public synchronized boolean jobNamesContains(TopLevelItem item) {
return jobNames.contains(item.getRelativeNameFrom(getOwnerItemGroup()));
}



/**
* Adds the given item to this view.
*
Expand All @@ -252,6 +250,21 @@ public void add(TopLevelItem item) throws IOException {
save();
}

/**
* Removes given item from this view.
*
* @since TODO
*/
@Override
public boolean remove(TopLevelItem item) throws IOException {
synchronized (this) {
String name = item.getRelativeNameFrom(getOwnerItemGroup());
if (!jobNames.remove(name)) return false;
}
save();
return true;
}

public String getIncludeRegex() {
return includeRegex;
}
Expand Down Expand Up @@ -292,33 +305,6 @@ public Item doCreateItem(StaplerRequest req, StaplerResponse rsp) throws IOExcep
return null;
}

@RequirePOST
public HttpResponse doAddJobToView(@QueryParameter String name) throws IOException, ServletException {
checkPermission(View.CONFIGURE);
if(name==null)
throw new Failure("Query parameter 'name' is required");

if (getOwnerItemGroup().getItem(name) == null)
throw new Failure("Query parameter 'name' does not correspond to a known item");

if (jobNames.add(name))
owner.save();

return HttpResponses.ok();
}

@RequirePOST
public HttpResponse doRemoveJobFromView(@QueryParameter String name) throws IOException, ServletException {
checkPermission(View.CONFIGURE);
if(name==null)
throw new Failure("Query parameter 'name' is required");

if (jobNames.remove(name))
owner.save();

return HttpResponses.ok();
}

/**
* Handles the configuration submission.
*
Expand Down
145 changes: 145 additions & 0 deletions test/src/test/java/hudson/model/DirectlyModifiableViewTest.java
@@ -0,0 +1,145 @@
/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;

import static org.junit.Assert.*;

import java.io.IOException;
import java.net.URL;

import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;

import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.WebResponse;

public class DirectlyModifiableViewTest {

@Rule public JenkinsRule j = new JenkinsRule();

@Test
public void manipulateViewContent() throws IOException {
FreeStyleProject projectA = j.createFreeStyleProject("projectA");
FreeStyleProject projectB = j.createFreeStyleProject("projectB");

ListView view = new ListView("a_view", j.jenkins);
j.jenkins.addView(view);

assertFalse(view.contains(projectA));
assertFalse(view.contains(projectB));

view.add(projectA);
assertTrue(view.contains(projectA));
assertFalse(view.contains(projectB));

view.add(projectB);
assertTrue(view.contains(projectA));
assertTrue(view.contains(projectB));

assertTrue(view.remove(projectA));
assertFalse(view.contains(projectA));
assertTrue(view.contains(projectB));

assertTrue(view.remove(projectB));
assertFalse(view.contains(projectA));
assertFalse(view.contains(projectB));

assertFalse(view.remove(projectB));
}

@Test
public void doAddJobToView() throws Exception {
FreeStyleProject project = j.createFreeStyleProject("a_project");
ListView view = new ListView("a_view", j.jenkins);
j.jenkins.addView(view);

assertFalse(view.contains(project));

Page page = doPost(view, "addJobToView?name=a_project");
j.assertGoodStatus(page);
assertTrue(view.contains(project));

page = doPost(view, "addJobToView?name=a_project");
j.assertGoodStatus(page);
assertTrue(view.contains(project));
}

@Test
public void doRemoveJobFromView() throws Exception {
FreeStyleProject project = j.createFreeStyleProject("a_project");
ListView view = new ListView("a_view", j.jenkins);
j.jenkins.addView(view);

Page page = doPost(view, "addJobToView?name=a_project");
assertTrue(view.contains(project));

page = doPost(view, "removeJobFromView?name=a_project");
j.assertGoodStatus(page);
assertFalse(view.contains(project));

page = doPost(view, "removeJobFromView?name=a_project");
j.assertGoodStatus(page);
assertFalse(view.contains(project));
}

@Test
public void failWebMethodForIllegalRequest() throws Exception {
ListView view = new ListView("a_view", j.jenkins);
j.jenkins.addView(view);

assertBadStatus(
doPost(view, "addJobToView"),
"Query parameter 'name' is required"
);
assertBadStatus(
doPost(view, "addJobToView?name=no_project"),
"Query parameter 'name' does not correspond to a known item"
);
assertBadStatus(
doPost(view, "removeJobFromView"),
"Query parameter 'name' is required"
);
}

private Page doPost(View view, String path) throws Exception {
WebClient wc = j.createWebClient();
wc.setThrowExceptionOnFailingStatusCode(false);
WebRequestSettings req = new WebRequestSettings(
new URL(j.jenkins.getRootUrl() + view.getUrl() + path),
HttpMethod.POST
);

return wc.getPage(wc.addCrumb(req));
}

private void assertBadStatus(Page page, String message) {
WebResponse rsp = page.getWebResponse();
assertFalse("Status: " + rsp.getStatusCode(), j.isGoodHttpStatus(rsp.getStatusCode()));
assertTrue(rsp.getContentAsString().contains(message));
}
}

0 comments on commit a5ee2d1

Please sign in to comment.