提交 69ef22f5 编写于 作者: J Jesse Glick

Merge branch 'master' of github.com:jenkinsci/jenkins

......@@ -55,7 +55,8 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=>
<li class=rfe>
Improved the auto-completion of job names to support hierarchy better.
</ul>
</div><!--=TRUNK-END=-->
......@@ -108,8 +109,6 @@ Upcoming changes</a>
<li class=bug>
Memory exhaustion parsing large test stdio from Surefire.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15382">issue 15382</a>)
<li class=rfe>
In celebration of the release number '486', background image will be 80486 for a week.
</ul>
<h3><a name=v1.485>What's new in 1.485</a> (2012/10/07)</h3>
<ul class=image>
......
......@@ -1537,12 +1537,4 @@ public class Functions {
DecimalFormat format = new DecimalFormat("##.00");
return format.format(number) + " " + measure;
}
/**
* 1.486 special
*/
public static String getBackgroundImage() {
// for one week.
return System.currentTimeMillis() < 1350696100409L ? "i486.jpg" : "jenkins.png";
}
}
......@@ -51,6 +51,7 @@ import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -690,6 +691,52 @@ public abstract class Launcher {
};
}
/**
* Returns a decorated {@link Launcher} that automatically adds the specified environment
* variables.
*
* Those that are specified in {@link ProcStarter#envs(String...)} will take precedence over
* what's specified here.
*
* @since 1.489
*/
public final Launcher decorateByEnv(EnvVars _env) {
final EnvVars env = new EnvVars(_env);
final Launcher outer = this;
return new Launcher(outer) {
@Override
public Proc launch(ProcStarter starter) throws IOException {
EnvVars e = new EnvVars(env);
if (starter.envs!=null) {
for (int i=0; i<starter.envs.length; i+=2)
e.put(starter.envs[i],starter.envs[i+1]);
}
String[] r = new String[e.size()*2];
int idx=0;
for (Entry<String, String> i : e.entrySet()) {
r[idx++] = i.getKey();
r[idx++] = i.getValue();
}
starter.envs = r;
return outer.launch(starter);
}
@Override
public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String, String> envVars) throws IOException, InterruptedException {
EnvVars e = new EnvVars(env);
e.putAll(envVars);
return outer.launchChannel(cmd,out,workDir,e);
}
@Override
public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
outer.kill(modelEnvVars);
}
};
}
/**
* {@link Launcher} that launches process locally.
*/
......
......@@ -931,19 +931,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
FilePath ws = getWorkspace();
if (ws!=null) // if this is done very early on in the build, workspace may not be decided yet. see HUDSON-3997
env.put("WORKSPACE", ws.getRemote());
// servlet container may have set CLASSPATH in its launch script,
// so don't let that inherit to the new child process.
// see http://www.nabble.com/Run-Job-with-JDK-1.4.2-tf4468601.html
env.put("CLASSPATH","");
JDK jdk = project.getJDK();
if (jdk != null) {
Computer computer = Computer.currentComputer();
if (computer != null) { // just in case were not in a build
jdk = jdk.forNode(computer.getNode(), log);
}
jdk.buildEnvVars(env);
}
project.getScm().buildEnvVars(this,env);
if (buildEnvironments!=null)
......
......@@ -28,6 +28,7 @@
package hudson.model;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.EnvVars;
import hudson.Functions;
import antlr.ANTLRException;
import hudson.AbortException;
......@@ -87,6 +88,7 @@ import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
......@@ -299,6 +301,21 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
updateTransientActions();
}
@Override
public EnvVars getEnvironment(Node node, TaskListener listener) throws IOException, InterruptedException {
EnvVars env = super.getEnvironment(node, listener);
JDK jdk = getJDK();
if (jdk != null) {
if (node != null) { // just in case were not in a build
jdk = jdk.forNode(node, listener);
}
jdk.buildEnvVars(env);
}
return env;
}
@Override
protected void performDelete() throws IOException, InterruptedException {
// prevent a new build while a delete operation is in progress
......@@ -1446,7 +1463,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
// so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
// by having multiple workspaces
WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
Launcher launcher = ws.createLauncher(listener);
Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(lb.getBuiltOn(),listener));
try {
LOGGER.fine("Polling SCM changes of " + getName());
if (pollingBaseline==null) // see NOTE-NO-BASELINE above
......@@ -1694,20 +1711,21 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
/**
* Schedules a new build command.
*/
public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
public void doBuild( StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay ) throws IOException, ServletException {
if (delay==null) delay=new TimeDuration(getQuietPeriod());
BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
// if a build is parameterized, let that take over
ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
if (pp != null) {
pp._doBuild(req,rsp);
pp._doBuild(req,rsp,delay);
return;
}
if (!isBuildable())
throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));
Jenkins.getInstance().getQueue().schedule(this, getDelay(req), getBuildCause(req));
Jenkins.getInstance().getQueue().schedule(this, (int)delay.getTime(), getBuildCause(req));
rsp.forwardToPreviousPage(req);
}
......@@ -1728,6 +1746,9 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
/**
* Computes the delay by taking the default value and the override in the request parameter into the account.
*
* @deprecated as of 1.488
* Inject {@link TimeDuration}.
*/
public int getDelay(StaplerRequest req) throws ServletException {
String delay = req.getParameter("delay");
......@@ -1747,12 +1768,12 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
* Supports build trigger with parameters via an HTTP GET or POST.
* Currently only String parameters are supported.
*/
public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
if (pp != null) {
pp.buildWithParameters(req,rsp);
pp.buildWithParameters(req,rsp,delay);
} else {
throw new IllegalStateException("This build is not parameterized!");
}
......
......@@ -25,6 +25,7 @@
package hudson.model;
import hudson.search.Search;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -71,4 +72,76 @@ public class AutoCompletionCandidates implements HttpResponse {
}
rsp.serveExposedBean(req,r, Flavor.JSON);
}
/**
* Auto-completes possible job names.
*
* @param type
* Limit the auto-completion to the subtype of this type.
* @param value
* The value the user has typed in. Matched as a prefix.
* @param self
* The contextual item for which the auto-completion is provided to.
* For example, if you are configuring a job, this is the job being configured.
* @param container
* The nearby contextual {@link ItemGroup} to resolve relative job names from.
*/
public static <T extends Item> AutoCompletionCandidates ofJobNames(final Class<T> type, final String value, Item self, ItemGroup container) {
if (self==container)
container = self.getParent();
final AutoCompletionCandidates candidates = new AutoCompletionCandidates();
class Visitor extends ItemVisitor {
String prefix;
Visitor(String prefix) {
this.prefix = prefix;
}
@Override
public void onItem(Item i) {
String n = contextualNameOf(i);
if ((n.startsWith(value) || value.startsWith(n))
// 'foobar' is a valid candidate if the current value is 'foo'.
// Also, we need to visit 'foo' if the current value is 'foo/bar'
&& (value.length()>n.length() || !n.substring(value.length()).contains("/"))
// but 'foobar/zot' isn't if the current value is 'foo'
// we'll first show 'foobar' and then wait for the user to type '/' to show the rest
&& i.hasPermission(Item.READ)
// and read permission required
) {
if (type.isInstance(i) && n.startsWith(value))
candidates.add(n);
// recurse
String oldPrefix = prefix;
prefix = n;
super.onItem(i);
prefix = oldPrefix;
}
}
private String contextualNameOf(Item i) {
if (prefix.endsWith("/") || prefix.length()==0)
return prefix+i.getName();
else
return prefix+'/'+i.getName();
}
}
if (container==null || container==Jenkins.getInstance()) {
new Visitor("").onItemGroup(Jenkins.getInstance());
} else {
new Visitor("").onItemGroup(container);
if (value.startsWith("/"))
new Visitor("/").onItemGroup(Jenkins.getInstance());
for ( String p="../"; value.startsWith(p); p+="../") {
container = ((Item)container).getParent();
new Visitor(p).onItemGroup(container);
}
}
return candidates;
}
}
......@@ -25,6 +25,9 @@
package hudson.model;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Launcher.ProcStarter;
import hudson.Util;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
......@@ -45,6 +48,7 @@ import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.ComputerListener;
import hudson.slaves.NodeProperty;
import hudson.slaves.RetentionStrategy;
import hudson.slaves.WorkspaceList;
import hudson.slaves.OfflineCause;
......@@ -77,6 +81,7 @@ import javax.servlet.ServletException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
......@@ -877,6 +882,36 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return EnvVars.getRemote(getChannel());
}
/**
* Creates an environment variable override to be used for launching processes on this node.
*
* @see ProcStarter#envs(Map)
* @since 1.489
*/
public EnvVars buildEnvironment(TaskListener listener) throws IOException, InterruptedException {
EnvVars env = new EnvVars();
Node node = getNode();
if (node==null) return env; // bail out
for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) {
nodeProperty.buildEnvVars(env,listener);
}
for (NodeProperty nodeProperty: node.getNodeProperties()) {
nodeProperty.buildEnvVars(env,listener);
}
// TODO: hmm, they don't really belong
String rootUrl = Hudson.getInstance().getRootUrl();
if(rootUrl!=null) {
env.put("HUDSON_URL", rootUrl); // Legacy.
env.put("JENKINS_URL", rootUrl);
}
return env;
}
/**
* Gets the thread dump of the slave JVM.
* @return
......
......@@ -26,6 +26,7 @@ package hudson.model;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.PermalinkList;
......@@ -51,6 +52,7 @@ import hudson.util.DataSetBuilder;
import hudson.util.DescribableList;
import hudson.util.FormApply;
import hudson.util.Graph;
import hudson.util.ProcessTree;
import hudson.util.RunList;
import hudson.util.ShiftedCategoryAxis;
import hudson.util.StackedAreaRenderer2;
......@@ -300,6 +302,47 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
return nextBuildNumber;
}
/**
* Builds up the environment variable map that's sufficient to identify a process
* as ours. This is used to kill run-away processes via {@link ProcessTree#killAll(Map)}.
*/
public EnvVars getCharacteristicEnvVars() {
EnvVars env = new EnvVars();
env.put("JENKINS_SERVER_COOKIE",Util.getDigestOf("ServerID:"+ Jenkins.getInstance().getSecretKey()));
env.put("HUDSON_SERVER_COOKIE",Util.getDigestOf("ServerID:"+ Jenkins.getInstance().getSecretKey())); // Legacy compatibility
env.put("JOB_NAME",getParent().getFullName());
return env;
}
/**
* Creates an environment variable override for launching processes for this project.
*
* <p>
* This is for process launching outside the build execution (such as polling, tagging, deployment, etc.)
* that happens in a context of a specific job.
*
* @param node
* Node to eventually run a process on. The implementation must cope with this parameter being null
* (in which case none of the node specific properties would be reflected in the resulting override.)
*/
public EnvVars getEnvironment(Node node, TaskListener listener) throws IOException, InterruptedException {
EnvVars env;
if (node!=null)
env = node.toComputer().buildEnvironment(listener);
else
env = new EnvVars();
env.putAll(getCharacteristicEnvVars());
// servlet container may have set CLASSPATH in its launch script,
// so don't let that inherit to the new child process.
// see http://www.nabble.com/Run-Job-with-JDK-1.4.2-tf4468601.html
env.put("CLASSPATH","");
return env;
}
/**
* Programatically updates the next build number.
*
......
......@@ -35,9 +35,11 @@ import java.util.AbstractList;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import jenkins.util.TimeDuration;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
......@@ -104,14 +106,16 @@ public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?,
* Interprets the form submission and schedules a build for a parameterized job.
*
* <p>
* This method is supposed to be invoked from {@link AbstractProject#doBuild(StaplerRequest, StaplerResponse)}.
* This method is supposed to be invoked from {@link AbstractProject#doBuild(StaplerRequest, StaplerResponse, TimeDuration)}.
*/
public void _doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
public void _doBuild(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
if(!req.getMethod().equals("POST")) {
// show the parameter entry form.
req.getView(this,"index.jelly").forward(req,rsp);
return;
}
if (delay==null) delay=new TimeDuration(owner.getQuietPeriod());
List<ParameterValue> values = new ArrayList<ParameterValue>();
......@@ -130,13 +134,13 @@ public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?,
}
Jenkins.getInstance().getQueue().schedule(
owner, owner.getDelay(req), new ParametersAction(values), new CauseAction(new Cause.UserIdCause()));
owner, delay.getTime(), new ParametersAction(values), new CauseAction(new Cause.UserIdCause()));
// send the user back to the job top page.
rsp.sendRedirect(".");
}
public void buildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
public void buildWithParameters(StaplerRequest req, StaplerResponse rsp, TimeDuration delay) throws IOException, ServletException {
List<ParameterValue> values = new ArrayList<ParameterValue>();
for (ParameterDefinition d: parameterDefinitions) {
ParameterValue value = d.createValue(req);
......@@ -144,9 +148,10 @@ public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?,
values.add(value);
}
}
if (delay==null) delay=new TimeDuration(owner.getQuietPeriod());
Jenkins.getInstance().getQueue().schedule(
owner, owner.getDelay(req), new ParametersAction(values), owner.getBuildCause(req));
owner, delay.getTime(), new ParametersAction(values), owner.getBuildCause(req));
if (requestWantsJson(req)) {
rsp.setContentType("application/json");
......
......@@ -1958,12 +1958,16 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* @return the map with the environmental variables. Never <code>null</code>.
* @since 1.305
*/
public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
EnvVars env = getCharacteristicEnvVars();
public EnvVars getEnvironment(TaskListener listener) throws IOException, InterruptedException {
Computer c = Computer.currentComputer();
Node n = c==null ? null : c.getNode();
EnvVars env = getParent().getEnvironment(n,listener);
env.putAll(getCharacteristicEnvVars());
// apply them in a reverse order so that higher ordinal ones can modify values added by lower ordinal ones
for (EnvironmentContributor ec : EnvironmentContributor.all().reverseView())
ec.buildEnvironmentFor(this,env,log);
ec.buildEnvironmentFor(this,env,listener);
return env;
}
......@@ -1973,13 +1977,10 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
* as ours. This is used to kill run-away processes via {@link ProcessTree#killAll(Map)}.
*/
public final EnvVars getCharacteristicEnvVars() {
EnvVars env = new EnvVars();
env.put("JENKINS_SERVER_COOKIE",Util.getDigestOf("ServerID:"+ Jenkins.getInstance().getSecretKey()));
env.put("HUDSON_SERVER_COOKIE",Util.getDigestOf("ServerID:"+ Jenkins.getInstance().getSecretKey())); // Legacy compatibility
EnvVars env = getParent().getCharacteristicEnvVars();
env.put("BUILD_NUMBER",String.valueOf(number));
env.put("BUILD_ID",getId());
env.put("BUILD_TAG","jenkins-"+getParent().getFullName().replace('/', '-')+"-"+number);
env.put("JOB_NAME",getParent().getFullName());
return env;
}
......
......@@ -31,6 +31,7 @@ import hudson.model.BuildListener;
import hudson.model.ComputerSet;
import hudson.model.Environment;
import hudson.model.Node;
import hudson.model.TaskListener;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Stapler;
......@@ -69,6 +70,11 @@ public class EnvironmentVariablesNodeProperty extends NodeProperty<Node> {
return Environment.create(envVars);
}
@Override
public void buildEnvVars(EnvVars env, TaskListener listener) throws IOException, InterruptedException {
env.putAll(envVars);
}
@Extension
public static class DescriptorImpl extends NodePropertyDescriptor {
......
......@@ -23,6 +23,7 @@
*/
package hudson.slaves;
import hudson.EnvVars;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.Launcher;
......@@ -30,6 +31,7 @@ import hudson.DescriptorExtensionList;
import hudson.model.Descriptor.FormException;
import hudson.model.Queue.BuildableItem;
import hudson.model.ReconfigurableDescribable;
import hudson.model.TaskListener;
import hudson.model.queue.CauseOfBlockage;
import hudson.scm.SCM;
import hudson.model.AbstractBuild;
......@@ -44,6 +46,7 @@ import org.kohsuke.stapler.StaplerRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Extensible property of {@link Node}.
......@@ -129,6 +132,39 @@ public abstract class NodeProperty<N extends Node> implements ReconfigurableDesc
return new Environment() {};
}
/**
* Creates environment variable override for launching child processes in this node.
*
* <p>
* Whereas {@link #setUp(AbstractBuild, Launcher, BuildListener)} is used specifically for
* executing builds, this method is used for other process launch activities that happens
* outside the context of a build, such as polling, one time action (tagging, deployment, etc.)
*
* <p>
* Starting 1.488, this method and {@link #setUp(AbstractBuild, Launcher, BuildListener)} are
* layered properly. That is, for launching processes for a build, this method
* is called first and then {@link Environment#buildEnvVars(Map)} will be added on top.
* This allows implementations to put node-scoped environment variables here, then
* build scoped variables to {@link #setUp(AbstractBuild, Launcher, BuildListener)}.
*
* <p>
* Unfortunately, Jenkins core earlier than 1.488 only calls {@link #setUp(AbstractBuild, Launcher, BuildListener)},
* so if the backward compatibility with these earlier versions is important, implementations
* should invoke this method from {@link Environment#buildEnvVars(Map)}.
*
* @param env
* Manipulate this variable (normally by adding more entries.)
* Note that this is an override, so it doesn't contain environment variables that are
* currently set for the slave process itself.
* @param listener
* Can be used to send messages.
*
* @since 1.488
*/
public void buildEnvVars(EnvVars env, TaskListener listener) throws IOException,InterruptedException {
// default is no-op
}
public NodeProperty<?> reconfigure(StaplerRequest req, JSONObject form) throws FormException {
return form==null ? null : getDescriptor().newInstance(req, form);
}
......
......@@ -343,17 +343,8 @@ public class BuildTrigger extends Recorder implements DependecyDeclarer {
return FormValidation.ok();
}
public AutoCompletionCandidates doAutoCompleteChildProjects(@QueryParameter String value) {
AutoCompletionCandidates candidates = new AutoCompletionCandidates();
List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
for (Job job: jobs) {
if (job.getFullName().startsWith(value)) {
if (job.hasPermission(Item.READ)) {
candidates.add(job.getFullName());
}
}
}
return candidates;
public AutoCompletionCandidates doAutoCompleteChildProjects(@QueryParameter String value, @AncestorInPath Item self, @AncestorInPath ItemGroup container) {
return AutoCompletionCandidates.ofJobNames(Job.class,value,self,container);
}
@Extension
......
......@@ -32,6 +32,7 @@ import hudson.Util;
import static hudson.Util.fixNull;
import hudson.model.BuildListener;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.ItemGroup;
import jenkins.model.Jenkins;
import hudson.model.Item;
import hudson.model.Job;
......@@ -351,19 +352,9 @@ public class AggregatedTestResultPublisher extends Recorder {
return new AggregatedTestResultPublisher(s.getString("jobs"), req.getParameter("includeFailedBuilds") != null);
}
public AutoCompletionCandidates doAutoCompleteJobs(@QueryParameter String value) {
AutoCompletionCandidates candidates = new AutoCompletionCandidates();
List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
for (Job job: jobs) {
if (job.getFullName().startsWith(value)) {
if (job.hasPermission(Item.READ)) {
candidates.add(job.getFullName());
}
}
}
return candidates;
public AutoCompletionCandidates doAutoCompleteJobs(@QueryParameter String value, @AncestorInPath Item self, @AncestorInPath ItemGroup container) {
return AutoCompletionCandidates.ofJobNames(Job.class,value,self,container);
}
}
}
package jenkins.util;
import org.apache.commons.beanutils.Converter;
import java.util.concurrent.TimeUnit;
/**
* Represents a length of something, like "3 seconds"
*
* This supports parameter injection, such as via {@link QueryParameter}.
*
* @author Kohsuke Kawaguchi
* @since 1.489
*/
public class TimeDuration {
private final long millis;
public TimeDuration(long millis) {
this.millis = millis;
}
public int getTime() {
return (int)millis;
}
public long getTimeInMillis() {
return millis;
}
public long as(TimeUnit t) {
return t.convert(millis,TimeUnit.MILLISECONDS);
}
public static class StaplerConverterImpl implements Converter {
public Object convert(Class type, Object value) {
if (value==null)
return null;
if (value instanceof String) {
String delay = (String)value;
try {
// TODO: more unit handling
if(delay.endsWith("sec")) delay=delay.substring(0,delay.length()-3);
if(delay.endsWith("secs")) delay=delay.substring(0,delay.length()-4);
return new TimeDuration(Long.parseLong(delay));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid time duration value: "+delay);
}
}
throw new UnsupportedOperationException();
}
}
}
......@@ -57,6 +57,8 @@ Behaviour.specify("SELECT.select", 'select', 0, function(e) {
}
}
fireEvent(e,"filled"); // let other interested parties know that the items have changed
// if the update changed the current selection, others listening to this control needs to be notified.
if (e.value!=value) fireEvent(e,"change");
}
......
......@@ -206,7 +206,7 @@ ${h.initPageVariables(context)}
</l:breadcrumbBar>
</table>
<table id="main-table" width="100%" height="70%" border="0"
style="background-image: url(${imagesURL}/${h.backgroundImage});
style="background-image: url(${imagesURL}/jenkins.png);
background-repeat: no-repeat; background-position: bottom left;">
<tr>
<td id="side-panel" width="20%">
......
package hudson.model;
import hudson.matrix.AxisList;
import hudson.matrix.MatrixConfiguration;
import hudson.matrix.MatrixProject;
import hudson.matrix.TextAxis;
import org.jvnet.hudson.test.HudsonTestCase;
import java.util.Arrays;
import java.util.TreeSet;
/**
* @author Kohsuke Kawaguchi
*/
public class AutoCompletionCandidatesTest extends HudsonTestCase {
public void testCompletion() throws Exception {
FreeStyleProject foo = jenkins.createProject(FreeStyleProject.class, "foo");
MatrixProject bar = jenkins.createProject(MatrixProject.class, "bar");
bar.setAxes(new AxisList(new TextAxis("x","1","2","3")));
MatrixConfiguration x3 = bar.getItem("x=3");
AutoCompletionCandidates c;
c = AutoCompletionCandidates.ofJobNames(Item.class, "", foo, jenkins);
assertContains(c, "foo", "bar");
c = AutoCompletionCandidates.ofJobNames(Item.class, "ba", foo, jenkins);
assertContains(c, "bar");
c = AutoCompletionCandidates.ofJobNames(Item.class, "bar/", foo, jenkins);
assertContains(c, "bar/x=1", "bar/x=2", "bar/x=3");
c = AutoCompletionCandidates.ofJobNames(FreeStyleProject.class, "", foo, jenkins);
assertContains(c, "foo");
c = AutoCompletionCandidates.ofJobNames(MatrixConfiguration.class, "bar/", foo, jenkins);
assertContains(c, "bar/x=1", "bar/x=2", "bar/x=3");
c = AutoCompletionCandidates.ofJobNames(Item.class, "", x3, x3.getParent());
assertContains(c, "x=1", "x=2", "x=3");
c = AutoCompletionCandidates.ofJobNames(Item.class, "/", x3, x3.getParent());
assertContains(c, "/foo", "/bar");
c = AutoCompletionCandidates.ofJobNames(Item.class, "/bar/", x3, x3.getParent());
assertContains(c, "/bar/x=1", "/bar/x=2", "/bar/x=3");
// relative path
c = AutoCompletionCandidates.ofJobNames(Item.class, "../", x3, x3.getParent());
assertContains(c, "../bar", "../foo");
}
private void assertContains(AutoCompletionCandidates c, String... values) {
assertEquals(new TreeSet<String>(Arrays.asList(values)), new TreeSet<String>(c.getValues()));
}
}
......@@ -46,4 +46,21 @@ public class RenderOnDemandTest extends HudsonTestCase {
ScriptResult r = p.executeJavaScript("var r=document.getElementsBySelector('DIV.a'); r[0].innerHTML+r[1].innerHTML+r[2].innerHTML");
assertEquals("AlphaBravoCharlie",r.getJavaScriptResult().toString());
}
/**
* Makes sure that scripts get evaluated.
*/
public void testScript() throws Exception {
HtmlPage p = createWebClient().goTo("self/testScript");
p.getElementById("button").click();
// all AJAX calls complete before the above method returns
ScriptResult r = p.executeJavaScript("x");
assertEquals("foo",r.getJavaScriptResult().toString());
// if you want to test this in the browser
System.out.println("Try http://localhost:"+localPort+"\"self/testScript");
interactiveBreak();
}
}
<!--
The MIT License
Copyright (c) 2011, CloudBees, 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.
-->
<!--
Config page
-->
<?jelly escape-by-default='true'?>
<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">
<l:layout title="">
<l:main-panel>
<input type="button" value="Test" id="button"/>
<script>
var x = null;
$("button").onclick = function() {
renderOnDemand(document.getElementsBySelector('.lazy')[0]);
}
</script>
<l:renderOnDemand clazz="lazy">
<div id="eye">hello</div>
<script>
x = "foo";
</script>
</l:renderOnDemand>
</l:main-panel>
</l:layout>
</j:jelly>
此差异由.gitattributes 抑制。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册