Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #124 from bluesliverx/master
JENKINS-44524 Support configuring a site on a folder
  • Loading branch information
artkoshelev committed Aug 2, 2017
2 parents 6ec3f7e + 0869169 commit 85cab2b
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 37 deletions.
30 changes: 30 additions & 0 deletions src/main/java/hudson/plugins/jira/EmptyFriendlyURLConverter.java
@@ -0,0 +1,30 @@
package hudson.plugins.jira;

import org.apache.commons.beanutils.Converter;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* It's little hackish.
*/
@Restricted(NoExternalUse.class)
public class EmptyFriendlyURLConverter implements Converter {
private static final Logger LOGGER = Logger
.getLogger(JiraProjectProperty.class.getName());

public Object convert(Class aClass, Object o) {
if (o == null || "".equals(o) || "null".equals(o)) {
return null;
}
try {
return new URL(o.toString());
} catch (MalformedURLException e) {
LOGGER.log(Level.WARNING, "{0} is not a valid URL", o);
return null;
}
}
}
82 changes: 82 additions & 0 deletions src/main/java/hudson/plugins/jira/JiraFolderProperty.java
@@ -0,0 +1,82 @@
package hudson.plugins.jira;

import com.cloudbees.hudson.plugins.folder.AbstractFolder;
import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty;
import com.cloudbees.hudson.plugins.folder.AbstractFolderPropertyDescriptor;
import hudson.Extension;
import hudson.model.Descriptor;
import hudson.util.CopyOnWriteList;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;

import javax.annotation.Nonnull;
import java.util.List;

/**
* Provides folder level JIRA configuration.
*/
public class JiraFolderProperty extends AbstractFolderProperty<AbstractFolder<?>> {
/**
* Hold the JIRA sites configuration.
*/
private final CopyOnWriteList<JiraSite> sites = new CopyOnWriteList<JiraSite>();

/**
* Constructor.
*/
@DataBoundConstructor
public JiraFolderProperty() {
}

@Override
public AbstractFolderProperty<?> reconfigure(StaplerRequest req, JSONObject formData)
throws Descriptor.FormException {
if (formData == null) {
return null;
}
//Fix^H^H^HDirty hack for empty string to URL conversion error
//Should check for existing handler etc, but since this is a dirty hack,
//we won't
Stapler.CONVERT_UTILS.deregister(java.net.URL.class);
Stapler.CONVERT_UTILS.register(new EmptyFriendlyURLConverter(), java.net.URL.class);
//End hack

sites.replaceBy(req.bindJSONToList(JiraSite.class, formData.get("sites")));
return this;
}

/**
* Return the JIRA sites.
*
* @return the JIRA sites
*/
public JiraSite[] getSites() {
return sites.toArray(new JiraSite[0]);
}

/**
* Adds a JIRA site.
*
* @param site the JIRA site
*/
@DataBoundSetter
public void setSites(JiraSite site) {
sites.add(site);
}

/**
* Descriptor class.
*/
@Extension
public static class DescriptorImpl extends AbstractFolderPropertyDescriptor {

@Nonnull
@Override
public String getDisplayName() {
return Messages.JiraFolderProperty_DisplayName();
}
}
}
27 changes: 0 additions & 27 deletions src/main/java/hudson/plugins/jira/JiraProjectProperty.java
Expand Up @@ -6,16 +6,10 @@
import hudson.model.JobPropertyDescriptor;
import hudson.util.CopyOnWriteList;
import net.sf.json.JSONObject;
import org.apache.commons.beanutils.Converter;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
Expand Down Expand Up @@ -122,26 +116,5 @@ public boolean configure(StaplerRequest req, JSONObject formData) {
save();
return true;
}

/**
* It's little hackish
*/
@Restricted(NoExternalUse.class)
public static class EmptyFriendlyURLConverter implements Converter {
public Object convert(Class aClass, Object o) {
if (o == null || "".equals(o) || "null".equals(o)) {
return null;
}
try {
return new URL(o.toString());
} catch (MalformedURLException e) {
LOGGER.log(Level.WARNING, "{0} is not a valid URL", o);
return null;
}
}
}
}

private static final Logger LOGGER = Logger
.getLogger(JiraProjectProperty.class.getName());
}
29 changes: 25 additions & 4 deletions src/main/java/hudson/plugins/jira/JiraSite.java
Expand Up @@ -5,15 +5,13 @@
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.Version;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
import com.google.common.base.*;
import com.cloudbees.hudson.plugins.folder.AbstractFolder;
import com.google.common.base.Objects;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.*;
import hudson.plugins.jira.model.JiraIssue;
import hudson.plugins.jira.model.JiraVersion;
import hudson.util.FormValidation;
Expand Down Expand Up @@ -384,12 +382,35 @@ public Set<String> getProjectKeys() {
public static JiraSite get(Job<?, ?> p) {
JiraProjectProperty jpp = p.getProperty(JiraProjectProperty.class);
if (jpp != null) {
// Looks in global configuration for the site configured
JiraSite site = jpp.getSite();
if (site != null) {
return site;
}
}

// Check up the folder chain if a site is defined there
// This only supports one site per folder
ItemGroup parent = p.getParent();
while (parent != null) {
if (parent instanceof AbstractFolder) {
AbstractFolder folder = (AbstractFolder) parent;
JiraFolderProperty jfp = (JiraFolderProperty) folder.getProperties().get(JiraFolderProperty.class);
if (jfp != null) {
JiraSite[] sites = jfp.getSites();
if (sites != null && sites.length > 0) {
return sites[0];
}
}
}

if (parent instanceof Item) {
parent = ((Item) parent).getParent();
} else {
parent = null;
}
}

// none is explicitly configured. try the default ---
// if only one is configured, that must be it.
JiraSite[] sites = JiraProjectProperty.DESCRIPTOR.getSites();
Expand Down
@@ -0,0 +1,7 @@
<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">
<f:section title="JIRA">
<f:entry title="${%JIRA sites}" description="">
<f:repeatableProperty field="sites"/>
</f:entry>
</f:section>
</j:jelly>
1 change: 1 addition & 0 deletions src/main/resources/hudson/plugins/jira/Messages.properties
@@ -1,4 +1,5 @@
JiraIssueUpdater.DisplayName=JIRA: Update relevant issues
JiraFolderProperty.DisplayName=Associated JIRA
JiraProjectProperty.DisplayName=Associated JIRA
JiraProjectProperty.JiraUrlMandatory=JIRA URL is a mandatory field
JiraProjectProperty.NotAJiraUrl=This is a valid URL but it doesn''t look like JIRA
Expand Down
43 changes: 43 additions & 0 deletions src/test/groovy/hudson/plugins/jira/JiraFolderPropertyTest.groovy
@@ -0,0 +1,43 @@
package hudson.plugins.jira

import net.sf.json.JSONObject
import org.kohsuke.stapler.Stapler
import org.kohsuke.stapler.StaplerRequest
import spock.lang.Specification
import spock.lang.Unroll

/**
* @author saville
*/
@Unroll
class JiraFolderPropertyTest extends Specification {
def "reconfigure"() {
given:
JiraFolderProperty subject = new JiraFolderProperty()
JiraSite site = Mock()
StaplerRequest req = Mock()
JSONObject formData = new JSONObject()
JSONObject sites = new JSONObject()
formData.put("sites", sites)

expect:
subject.sites.length==0

when:
def result = subject.reconfigure(req, null)

then:
0 * _._
result==null

when:
subject.reconfigure(req, formData)

then:
1 * req.bindJSONToList(JiraSite, sites) >> [site]
0 * _._
Stapler.CONVERT_UTILS.converters[URL.class] instanceof EmptyFriendlyURLConverter
subject.sites.length==1
subject.sites[0]==site
}
}
109 changes: 109 additions & 0 deletions src/test/groovy/hudson/plugins/jira/JiraSiteTest.groovy
@@ -0,0 +1,109 @@
package hudson.plugins.jira

import com.cloudbees.hudson.plugins.folder.AbstractFolder
import com.cloudbees.hudson.plugins.folder.AbstractFolderProperty
import hudson.model.ItemGroup
import hudson.model.Job
import hudson.util.DescribableList
import jenkins.model.Jenkins
import org.junit.runner.RunWith
import org.powermock.core.PowerMockUtils
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import spock.lang.Specification
import spock.lang.Unroll

/**
* @author saville
*/
@Unroll
class JiraSiteTest extends Specification {
def "get"() {
given:
Jenkins jenkins = Mock()
Jenkins.theInstance = jenkins
Job<?, ?> job = Mock()
JiraProjectProperty jpp = Spy(JiraProjectProperty)
JiraFolderProperty jfp = Mock()
ItemGroup nonFolderParent = Mock()
AbstractFolder folder1 = Mock()
AbstractFolder folder2 = Mock()
AbstractFolder folder3 = Mock()
DescribableList<AbstractFolderProperty> folder1Properties = Mock()
DescribableList<AbstractFolderProperty> folder2Properties = Mock()
DescribableList<AbstractFolderProperty> folder3Properties = Mock()
JiraSite site1 = Mock()
JiraSite site2 = Mock()

when: "site is configured on project property"
def result = JiraSite.get(job)

then:
1 * job.getProperty(JiraProjectProperty) >> jpp
1 * jpp.getSite() >> site1
0 * _._
result==site1

when: "project property site and parent are both null"
result = JiraSite.get(job)

then:
1 * job.getProperty(JiraProjectProperty) >> jpp
1 * jpp.getSite() >> null
1 * job.getParent() >> null
0 * _._
result==null

when: "no project property, parent is not a folder and is not an item"
result = JiraSite.get(job)

then:
1 * job.getProperty(JiraProjectProperty) >> null
1 * job.getParent() >> nonFolderParent
0 * _._
result==null

when: "no project property, go up folders with no property"
result = JiraSite.get(job)

then:
1 * job.getProperty(JiraProjectProperty.class) >> null
1 * job.getParent() >> folder1
1 * folder1.getProperties() >> folder1Properties
1 * folder1Properties.get(JiraFolderProperty) >> null
1 * folder1.getParent() >> folder2
1 * folder2.getProperties() >> folder2Properties
1 * folder2Properties.get(JiraFolderProperty) >> null
1 * folder2.getParent() >> nonFolderParent
0 * _._
result==null

when: "no project property, find folder property with null, 0 length, and valid sites"
result = JiraSite.get(job)

then:
1 * job.getProperty(JiraProjectProperty.class) >> null
1 * job.getParent() >> folder1
1 * folder1.getProperties() >> folder1Properties
1 * folder1Properties.get(JiraFolderProperty) >> jfp
1 * folder1.getParent() >> folder2
1 * folder2.getProperties() >> folder2Properties
1 * folder2Properties.get(JiraFolderProperty) >> jfp
1 * folder2.getParent() >> folder3
1 * folder3.getProperties() >> folder3Properties
1 * folder3Properties.get(JiraFolderProperty) >> jfp
3 * jfp.getSites() >>> [null, [] as JiraSite[], [site2, site1] as JiraSite[]]
0 * _._
result==site2

when: "site is configured globally"
JiraProjectProperty.DESCRIPTOR.setSites(site2)
result = JiraSite.get(job)

then:
1 * job.getProperty(JiraProjectProperty.class) >> null
1 * job.getParent() >> nonFolderParent
0 * _._
result==site2
}
}

0 comments on commit 85cab2b

Please sign in to comment.