提交 6ab4691e 编写于 作者: K Kohsuke Kawaguchi

Merge remote-tracking branch 'origin/master'

......@@ -63,6 +63,13 @@ Upcoming changes</a>
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.468>What's new in 1.468</a> <!--=DATE=--></h3>
<ul class=image>
<li class=bug>
Fixed: XML API Logs Too Much Information When Invalid Char is Present
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-13378">issue 13378</a>)
<li class=rfe>
Reduce the total height of items shown in the view configuration page.
(<a href="https://github.com/jenkinsci/jenkins/pull/488">pull 488</a>)
<li class=bug>
Added more MIME type mapping for Winstone.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-13496">issue 13496</a>)
......
......@@ -14,8 +14,23 @@ import static java.lang.annotation.RetentionPolicy.*;
* nearby parameters that belong to different parents.
*
* <p>
* Currently, "..", "../..", etc. are supported to indicate
* parameters that belong to the ancestors.
* ".." refers
* to values in the parent object, and "foo" refers to the child
* object of the current object named "foo". They can be combined
* with '/' like path, such as "../foo/bar", "../..", and etc.
*
* <p>
* A good way to think about this is the file system structure.
* {@code @RelativePath} is like the dirname, and {@code QueryParameter}
* is like the basename. Together they form a relative path.
* And because of the structured form submissions,
* form elements are organized in a tree structure of JSON objects,
* which is akin to directories and files.
*
* <p>
* The relative path then points from the current input element
* (for which you are doing form validation, for example) to the target
* input element that you want to obtain the value.
*
* @author Kohsuke Kawaguchi
* @since 1.376
......
......@@ -23,6 +23,7 @@
*/
package hudson.model;
import hudson.search.SearchFactory;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.Stapler;
......@@ -99,6 +100,11 @@ public abstract class AbstractModelObject implements SearchableModelObject {
}
public Search getSearch() {
for (SearchFactory sf : SearchFactory.all()) {
Search s = sf.createFor(this);
if (s!=null)
return s;
}
return new Search();
}
......
......@@ -45,6 +45,8 @@ import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Used to expose remote access API for ".../api/"
......@@ -142,7 +144,8 @@ public class Api extends AbstractModelObject {
}
} catch (DocumentException e) {
throw new IOException2("Failed to do XPath/wrapper handling. XML is as follows:"+sw,e);
LOGGER.log(Level.FINER, "Failed to do XPath/wrapper handling. XML is as follows:"+sw, e);
throw new IOException2("Failed to do XPath/wrapper handling. Turn on FINER logging to view XML.",e);
}
OutputStream o = rsp.getCompressedOutputStream(req);
......@@ -191,5 +194,6 @@ public class Api extends AbstractModelObject {
rsp.serveExposedBean(req,bean, Flavor.PYTHON);
}
private static final Logger LOGGER = Logger.getLogger(Api.class.getName());
private static final ModelBuilder MODEL_BUILDER = new ModelBuilder();
}
......@@ -49,9 +49,14 @@ import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Flavor;
/**
* Web-bound object that serves QuickSilver-like search requests.
* Web-bound object that provides search/navigation capability.
*
* <p>
* This object is bound to "./search" of a model object via {@link SearchableModelObject} and serves
* HTTP requests coming from JavaScript to provide search result and auto-completion.
*
* @author Kohsuke Kawaguchi
* @see SearchableModelObject
*/
public class Search {
public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
......
package hudson.search;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import jenkins.model.Jenkins;
/**
* Creates a {@link Search} instance for a {@link SearchableModelObject}.
*
* <p>
* This allows you to plug in different backends to the search, such as full-text search,
* or more intelligent user-sensitive search, etc. Puts @{@link Extension} annotation
* on your implementation to have it registered.
*
* <p>
* Right now, there's no user control over which {@link SearchFactory} takes priority,
* but we may do so later.
*
* @author Kohsuke Kawaguchi
* @since 1.469
*/
public abstract class SearchFactory implements ExtensionPoint {
/**
* Creates a {@link Search} object.
*
* This method needs to execute quickly (without actually executing any search),
* since it is created per incoming HTTP response.
*
* @param owner
* The {@link SearchableModelObject} object for which we are creating the search.
* The returned object will provide the search for this object.
* @return
* null if your factory isn't interested in creating a {@link Search} object.
* The next factory will get a chance to act on it.
*/
public abstract Search createFor(SearchableModelObject owner);
/**
* Returns all the registered {@link SearchFactory} instances.
*/
public static ExtensionList<SearchFactory> all() {
return Jenkins.getInstance().getExtensionList(SearchFactory.class);
}
}
......@@ -38,10 +38,12 @@ THE SOFTWARE.
</f:entry>
<f:entry title="${%Jobs}">
<j:forEach var="job" items="${it.ownerItemGroup.items}">
<f:checkbox name="${job.name}" checked="${it.contains(job)}" title="${job.name}" />
<br/>
</j:forEach>
<div class="listview-jobs">
<j:forEach var="job" items="${it.ownerItemGroup.items}">
<f:checkbox name="${job.name}" checked="${it.contains(job)}" title="${job.name}" />
<br/>
</j:forEach>
</div>
</f:entry>
<f:optionalBlock name="useincluderegex" title="${%Use a regular expression to include jobs into the view}"
......
package jenkins.plugins.ui_samples;
import hudson.Extension;
import hudson.RelativePath;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.util.FormValidation;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* How to access values of the nearby input fields when you do form field validation.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class FormFieldValidationWithContext extends UISample {
private List<State> states = new ArrayList<State>(Arrays.asList(
new State("California",new City("Sacramento"), Arrays.asList(new City("San Francisco"),new City("Los Angeles"))),
new State("New York",new City("New York"), Arrays.asList(new City("Albany"),new City("Ithaca")))
));
public FormFieldValidationWithContext() {
}
@DataBoundConstructor
public FormFieldValidationWithContext(List<State> states) {
this.states = states;
}
@Override
public String getDescription() {
return "How to access values of the nearby input fields when you do form field validation";
}
public List<State> getStates() {
return states;
}
@Override
public List<SourceFile> getSourceFiles() {
List<SourceFile> r = super.getSourceFiles();
r.add(new SourceFile("City/config.groovy"));
r.add(new SourceFile("State/config.groovy"));
return r;
}
public static class State extends AbstractDescribableImpl<State> {
/*
I'm lazy and just exposing fields as opposed to getter/setter.
Jenkins doesn't care and works correctly either way.
*/
public String name;
public City capital;
public List<City> cities;
@DataBoundConstructor
public State(String name, City capital, List<City> cities) {
this.name = name;
this.capital = capital;
this.cities = cities;
}
@Extension
public static class DescriptorImpl extends Descriptor<State> {
@Override
public String getDisplayName() {
return "";
}
public FormValidation doCheckName(@QueryParameter String value,
@RelativePath("capital") @QueryParameter String name) {
/*
@RelativePath("capital") @QueryParameter
... is short for
@RelativePath("capital") @QueryParameter("name")
... and thus can be thought of "capital/name"
so this matches the current city name entered as the capital of this state
*/
return FormValidation.ok("Are you sure " + name + " is a capital of " + value + "?");
}
}
}
public static class City extends AbstractDescribableImpl<City> {
public String name;
@DataBoundConstructor
public City(String name) {
this.name = name;
}
@Extension
public static class DescriptorImpl extends Descriptor<City> {
@Override
public String getDisplayName() {
return "";
}
public FormValidation doCheckName(@QueryParameter String value,
@RelativePath("..") @QueryParameter String name) {
/*
@RelativePath("..") @QueryParameter
... is short for
@RelativePath("..") @QueryParameter("name")
... and thus can be thought of "../name"
in the UI, fields for city is wrapped inside those of state, so "../name" binds
to the name field in the state.
*/
if (name==null || value==null || value.contains(name)) return FormValidation.ok();
return FormValidation.warning("City name doesn't contain "+name);
}
}
}
@Extension
public static class DescriptorImpl extends UISampleDescriptor {
}
}
package jenkins.plugins.ui_samples.FormFieldValidationWithContext.City;
def f = namespace(lib.FormTagLib)
f.entry(title:"City Name", field:"name") {
f.textbox()
}
package jenkins.plugins.ui_samples.FormFieldValidationWithContext.State;
def f = namespace(lib.FormTagLib)
f.entry(title:"State Name", field:"name") {
f.textbox()
}
f.nested {
table {
f.section(title:"Capital city") {
f.property(field:"capital")
}
f.entry(title:"Other cities") {
f.repeatableProperty(field:"cities")
}
}
}
package jenkins.plugins.ui_samples.FormFieldValidationWithContext;
import lib.JenkinsTagLib
import lib.FormTagLib
def f=namespace(FormTagLib.class)
t=namespace(JenkinsTagLib.class)
namespace("/lib/samples").sample(title:_("Context-sensitive form validation")) {
p {
raw(_("blurb.context"))
raw(_("blurb.otheruse"))
raw(_("blurb.contrived"))
}
f.form {
f.entry(title:"States") {
f.repeatableProperty(field:"states")
}
}
}
blurb.context=\
Form field validation can access values of the nearby input controls, which is useful for performing \
complex context sensitive form validation.
blurb.otheruse=\
The same technique can be also used for auto-completion, populating combobox/listbox, and so on.
blurb.contrived=\
The example below is bit contrived, but all the input elements are named 'name' (for city name and state name), \
and we use <code>@RelativePath</code> so that the validation of the state name refers to the capital name, \
and the validation of the city name refers to the state name.
\ No newline at end of file
......@@ -613,6 +613,12 @@ LABEL.attach-previous {
text-align: left;
}
/* ============================ list view entries ======================== */
div.listview-jobs {
max-height:300px;
overflow:auto;
}
/* ============================ parameters form ========================== */
table.parameters {
......
......@@ -144,6 +144,9 @@ var FormChecker = {
* @param {string} name
* Name of the control to find. Can include "../../" etc in the prefix.
* See @RelativePath.
*
* We assume that the name is normalized and doesn't contain any redundant component.
* That is, ".." can only appear as prefix, and "foo/../bar" is not OK (because it can be reduced to "bar")
*/
function findNearBy(e,name) {
while (name.startsWith("../")) {
......@@ -151,25 +154,38 @@ function findNearBy(e,name) {
e = findFormParent(e,null,true);
}
// name="foo/bar/zot" -> prefixes=["bar","foo"] & name="zot"
var prefixes = name.split("/");
name = prefixes.pop();
prefixes = prefixes.reverse();
// does 'e' itself match the criteria?
// as some plugins use the field name as a parameter value, instead of 'value'
var p = findFormItem(e,name,function(e,filter) {
if (filter(e)) return e;
return null;
return filter(e) ? e : null;
});
if (p!=null) return p;
if (p!=null && prefixes.length==0) return p;
var owner = findFormParent(e,null,true);
p = findPreviousFormItem(e,name);
if (p!=null && findFormParent(p,null,true)==owner)
return p;
var n = findNextFormItem(e,name);
if (n!=null && findFormParent(n,null,true)==owner)
return n;
function locate(iterator,e) {// keep finding elements until we find the good match
while (true) {
e = iterator(e,name);
if (e==null) return null;
// make sure this candidate element 'e' is in the right point in the hierarchy
var p = e;
for (var i=0; i<prefixes.length; i++) {
p = findFormParent(p,null,true);
if (p.getAttribute("name")!=prefixes[i])
return null;
}
if (findFormParent(p,null,true)==owner)
return e;
}
}
return null; // not found
return locate(findPreviousFormItem,e) || locate(findNextFormItem,e);
}
function controlValue(e) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册