加载叠加层永远不会在Jenkins插件的配置中消失

时间:2014-07-22 19:30:10

标签: groovy configuration jenkins jenkins-plugins

我想为我正在开发的Jenkins插件添加可重复的属性,并创建了一个测试插件,以确保我正确使用它们。我的插件似乎工作正常,我可以在最初编辑配置时添加任意数量的属性,并保存和构建。但是,当我尝试第二次编辑配置时,配置屏幕会无限地显示加载叠加层。如果我向下滚动,我可以看到我之前保存的属性仍然存在,但我无法编辑任何内容。

我的班级看起来像这样:

public class RepeatableTest extends Builder {

    private List<Prop> property = new ArrayList<Prop>();


    @DataBoundConstructor
    public RepeatableTest(List<Prop> property) {
        this.property = property;
    }

    public List<Prop> getProperty() {
        return property;
    }

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException {
        listener.getLogger().println(property.get(0).name);
        listener.getLogger().println(property.size());
        return true;
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl)super.getDescriptor();
    }

    public static class Prop extends AbstractDescribableImpl<Prop> {
        public String name;

        public String getName(){
            return name;
        }

        @DataBoundConstructor
        public Prop(String name) {
            this.name = name;
        }

        @Extension
        public static class DescriptorImpl extends Descriptor<Prop> {
            @Override
            public String getDisplayName() {
                return "";
            }
        }
    }

    @Extension // This indicates to Jenkins that this is an implementation of an extension point.
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {

        private String phpLoc;

        public DescriptorImpl() {
            load();
        }

        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            // Indicates that this builder can be used with all kinds of project types 
            return true;
        }

        public String getDisplayName() {
            return "Repeatable Test";
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
            phpLoc = formData.getString("phpLoc");
            save();
            return super.configure(req,formData);
        }

        public String getPhpLoc() {
            return phpLoc;
        }
    }
}

我的config.groovy看起来像这样:

package uitestplugin.uitest.RepeatableTest;

import lib.JenkinsTagLib
import lib.FormTagLib

def f = namespace(lib.FormTagLib)
t=namespace(JenkinsTagLib.class)

f.form{
    f.entry(title:"Properties"){
        f.repeatableProperty(field:"property")
    }
}

我的prop / config.groovy看起来像这样:

package uitestplugin.uitest.RepeatableTest.Prop;

def f = namespace(lib.FormTagLib)

f.entry(title:"Name", field:"name") {
    f.textbox()
}

config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description></description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <uitestplugin.uitest.RepeatableTest plugin="ui-test@1.0-SNAPSHOT">
      <property>
        <uitestplugin.uitest.RepeatableTest_-Prop>
          <name>Prop1</name>
        </uitestplugin.uitest.RepeatableTest_-Prop>
        <uitestplugin.uitest.RepeatableTest_-Prop>
          <name>Prop2</name>
        </uitestplugin.uitest.RepeatableTest_-Prop>
      </property>
    </uitestplugin.uitest.RepeatableTest>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>

有关可能导致此问题的任何想法?我基于ui-samples插件(https://wiki.jenkins-ci.org/display/JENKINS/UI+Samples+Plugin)的许多代码。

编辑:目前的情况是,我仍然没有想到它。我做了更多的研究并尝试了大量不同的例子,但我得到的最远的是我上面所描述的。几乎看起来你不能通过groovy使用可重复的。无论如何,我还要添加一条信息。使用Firefox的Web开发人员工具栏,我可以看到页面上有一个Javascript错误。错误是:

Timestamp: 10/3/2014 12:58:49 PM
Error: TypeError: prototypes is undefined
Source File: http://localhost:8080/adjuncts/e58fb488/lib/form/hetero-list/hetero-list.js
Line: 16

与此相关的代码是(我在第16行标记了行尾的注释):

// @include lib.form.dragdrop.dragdrop

// do the ones that extract innerHTML so that they can get their original HTML before
// other behavior rules change them (like YUI buttons.)
Behaviour.specify("DIV.hetero-list-container", 'hetero-list', -100, function(e) {
        e=$(e);
        if(isInsideRemovable(e))    return;

        // components for the add button
        var menu = document.createElement("SELECT");
        var btns = findElementsBySelector(e,"INPUT.hetero-list-add"),
            btn = btns[btns.length-1]; // In case nested content also uses hetero-list
        YAHOO.util.Dom.insertAfter(menu,btn);

        var prototypes = $(e.lastChild);
        while(!prototypes.hasClassName("prototypes")) //LINE 16, ERROR IS HERE
            prototypes = prototypes.previous();
        var insertionPoint = prototypes.previous();    // this is where the new item is inserted.

        // extract templates
        var templates = []; var i=0;
        $(prototypes).childElements().each(function (n) {
            var name = n.getAttribute("name");
            var tooltip = n.getAttribute("tooltip");
            var descriptorId = n.getAttribute("descriptorId");
            menu.options[i] = new Option(n.getAttribute("title"),""+i);
            templates.push({html:n.innerHTML, name:name, tooltip:tooltip,descriptorId:descriptorId});
            i++;
        });
        Element.remove(prototypes);

        var withDragDrop = initContainerDD(e);

        var menuAlign = (btn.getAttribute("menualign")||"tl-bl");

        var menuButton = new YAHOO.widget.Button(btn, { type: "menu", menu: menu, menualignment: menuAlign.split("-") });
        $(menuButton._button).addClassName(btn.className);    // copy class names
        $(menuButton._button).setAttribute("suffix",btn.getAttribute("suffix"));
        menuButton.getMenu().clickEvent.subscribe(function(type,args,value) {
            var item = args[1];
            if (item.cfg.getProperty("disabled"))   return;
            var t = templates[parseInt(item.value)];

            var nc = document.createElement("div");
            nc.className = "repeated-chunk";
            nc.setAttribute("name",t.name);
            nc.setAttribute("descriptorId",t.descriptorId);
            nc.innerHTML = t.html;
            $(nc).setOpacity(0);

            var scroll = document.body.scrollTop;

            renderOnDemand(findElementsBySelector(nc,"TR.config-page")[0],function() {
                function findInsertionPoint() {
                    // given the element to be inserted 'prospect',
                    // and the array of existing items 'current',
                    // and preferred ordering function, return the position in the array
                    // the prospect should be inserted.
                    // (for example 0 if it should be the first item)
                    function findBestPosition(prospect,current,order) {
                        function desirability(pos) {
                            var count=0;
                            for (var i=0; i<current.length; i++) {
                                if ((i<pos) == (order(current[i])<=order(prospect)))
                                    count++;
                            }
                            return count;
                        }

                        var bestScore = -1;
                        var bestPos = 0;
                        for (var i=0; i<=current.length; i++) {
                            var d = desirability(i);
                            if (bestScore<=d) {// prefer to insert them toward the end
                                bestScore = d;
                                bestPos = i;
                            }
                        }
                        return bestPos;
                    }

                    var current = e.childElements().findAll(function(e) {return e.match("DIV.repeated-chunk")});

                    function o(did) {
                        if (Object.isElement(did))
                            did = did.getAttribute("descriptorId");
                        for (var i=0; i<templates.length; i++)
                            if (templates[i].descriptorId==did)
                                return i;
                        return 0; // can't happen
                    }

                    var bestPos = findBestPosition(t.descriptorId, current, o);
                    if (bestPos<current.length)
                        return current[bestPos];
                    else
                        return insertionPoint;
                }
                (e.hasClassName("honor-order") ? findInsertionPoint() : insertionPoint).insert({before:nc});

                if(withDragDrop)    prepareDD(nc);

                new YAHOO.util.Anim(nc, {
                    opacity: { to:1 }
                }, 0.2, YAHOO.util.Easing.easeIn).animate();

                Behaviour.applySubtree(nc,true);
                ensureVisible(nc);
                layoutUpdateCallback.call();
            },true);
        });

        menuButton.getMenu().renderEvent.subscribe(function() {
            // hook up tooltip for menu items
            var items = menuButton.getMenu().getItems();
            for(i=0; i<items.length; i++) {
                var t = templates[i].tooltip;
                if(t!=null)
                    applyTooltip(items[i].element,t);
            }
        });

        if (e.hasClassName("one-each")) {
            // does this container already has a ocnfigured instance of the specified descriptor ID?
            function has(id) {
                return Prototype.Selector.find(e.childElements(),"DIV.repeated-chunk[descriptorId=\""+id+"\"]")!=null;
            }

            menuButton.getMenu().showEvent.subscribe(function() {
                var items = menuButton.getMenu().getItems();
                for(i=0; i<items.length; i++) {
                    items[i].cfg.setProperty("disabled",has(templates[i].descriptorId));
                }
            });
        }
    });

Behaviour.specify("DIV.dd-handle", 'hetero-list', -100, function(e) {
        e=$(e);
        e.on("mouseover",function() {
            $(this).up(".repeated-chunk").addClassName("hover");
        });
        e.on("mouseout",function() {
            $(this).up(".repeated-chunk").removeClassName("hover");
        });
});

我希望这是解决问题的足够信息。任何建议(即使它们不是完整的答案)都非常感谢。

1 个答案:

答案 0 :(得分:0)

虽然不是一个确切的答案,但我确实找到了一种方法来实现这一目标。出于某种原因,将repeatableProperty放在高级块中会阻止javascript错误的发生,所以一切都很好。

所以,我对RepeatableTest的config.groovy看起来像这样:

package uitestplugin.uitest.RepeatableTest;

f = namespace(lib.FormTagLib)

f.advanced{
    f.entry(title:"Properties"){
        f.repeatableProperty(field:"property", minimum:"1"){
        }
    }
}

Prop1的我的config.groovy看起来像这样:

package uitestplugin.uitest.Prop1;

def f = namespace(lib.FormTagLib)

f.entry(title:"Name",field:"name") {
    f.textbox()
}

f.entry {
    div(align:"left") {
        input(type:"button",value:"Delete",class:"repeatable-delete")
    }
}

我的道具1看起来像这样:

public class Prop1 extends AbstractDescribableImpl<Prop1> {
    private final String name;

    public String getName(){
        return name;
    }

    @DataBoundConstructor
    public Prop1( String name) {
        this.name = name;
    }

    @Extension
    public static class DescriptorImpl extends Descriptor<Prop1> {
        @Override
        public String getDisplayName() {
            return "";
        }
    }
}

我的RepeatableTest.java看起来像这样:

public class RepeatableTest extends Builder {

    private final List<Prop1> property;


    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public RepeatableTest(List<Prop1> property) {
        this.property = property;
    }

    public List<Prop1> getProperty() {
        return property;
    }

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException {
        //Doesn't matter
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl)super.getDescriptor();
    }

    @Extension // This indicates to Jenkins that this is an implementation of an extension point.
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {

        private String phpLoc;

        public DescriptorImpl() {
            load();
        }

        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            // Indicates that this builder can be used with all kinds of project types 
            return true;
        }

        public String getDisplayName() {
            return "Repeatable Test";
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
            phpLoc = formData.getString("phpLoc");
            save();
            return super.configure(req,formData);
        }

        public String getPhpLoc() {
            return phpLoc;
        }
    }
}