Using AJAX and Jelly to render a simple Widget

21 views
Skip to first unread message

Robert Smith

unread,
Jan 2, 2021, 9:45:21 PM1/2/21
to Jenkins Developers
I'm trying to create a Widget to display the status of an external server. I'd like the status information to refresh every few seconds. I'd like to render even the dynamic part of the UI using Jelly.

I searched the Jenkins core code and came across the pattern of using an "ajaxMyThing.jelly" file with a taglib "component" that includes a call to the JS function refreshPart(). For example, lib/hudson/queue.jelly calls refreshPart() and passes in the Ajax URL that will point to where hudson/model/View/ajaxBuildQueue.jelly is rendered.

I also read in the comments (inside ajax.jelly) that I should examine the Matrix Project to see how it is used. I did - and the pattern is the same as what I saw in the Jenkins core.

I followed this pattern. I created a taglib component in Jelly that rendered the UI and set up the AJAX call to refresh a section of the UI. I created an "ajaxMyThing.jelly" file (that included the <l:ajax> tag) and placed it in the Widget's resource directory. I included (using <st:include>) that file in the Widget's index.jelly file. 

The initial rendering and the Javascript side of things seemed to work just fine. The widget showed up and the status data was there. Every 5 seconds, a call was made to where I expected my ajaxMyThing.jelly file to be rendered. However, it always got a 404. It seems Jenkins was simply not binding the URL to that rendered Jelly content.

After many hours of headbanging and simplification, I concentrated solely on getting that ajaxMyThing.jelly file to be served up at some URL. I thought that it might have something to do with the object type - that Widgets didn't support this. So I created a simple view and placed the ajaxMyThing.jelly file into the matching package under resources. Nothing. I tried every combination of URL where I thought it might be lurking (adding /plugin/mything/ajaxMyThing, etc.) I tried adding @Symbol annotations and overriding getUrlName() and using those values in the URL. Nothing.

Finally, I deleted everything else and dropped the ajaxMyThing.jelly file into the hudson/model/View directory, and like magic, there it was. I could see my Jelly rendered at ${rootURL}/ajaxMyThing. However, when I tried to restore the rest of my code, it still didn't work as ${it} no longer pointed to my Widget object during the AJAX call - so the status update would work once, then, 5 seconds later, the refresh would wipe out the data.

My questions are:

1) What magic is happening to tell Jenkins to bind all of the hudson/model/View/ajax*.jelly files to a URL of the same name? I couldn't figure it out.

2) Can I accomplish this pattern in my own plugin using a Widget instead of a View and within my own package structure? If so, How?

Thanks so much!!!

Robert

Daniel Beck

unread,
Jan 3, 2021, 7:02:00 AM1/3/21
to Jenkins Developers


> On 2. Jan 2021, at 20:20, Robert Smith <robert...@nike.com> wrote:
>
> 1) What magic is happening to tell Jenkins to bind all of the hudson/model/View/ajax*.jelly files to a URL of the same name? I couldn't figure it out.

Stapler, the web framework used in Jenkins. For this specifically, see the section "View" in https://stapler.kohsuke.org/reference.html

If you run Jenkins/your plugin in debug mode (hpi:run or jetty:run), or set the system property `stapler.trace` on startup, or set the field `org.kohsuke.stapler.Dispatcher.TRACE=true` in the script console, HTTP response headers will tell you how the request was processed and, if it failed, 404 pages will tell you what else exists (with some complications if StaplerFallback objects are involved, but otherwise it is pretty useful). Given the dynamic nature of this specific dispatcher, 404 pages will not show a list of views, but you can at least see what type you're at in the chain (see below).

If you want to see the sources, JellyFacet and JellyDispatcher in Stapler are useful places to start, MetaClass is what adds them to the list.

> 2) Can I accomplish this pattern in my own plugin using a Widget instead of a View and within my own package structure? If so, How?

Yes. The URL needs to route to the object you want.

> After many hours of headbanging and simplification, I concentrated solely on getting that ajaxMyThing.jelly file to be served up at some URL. I thought that it might have something to do with the object type - that Widgets didn't support this. So I created a simple view and placed the ajaxMyThing.jelly file into the matching package under resources. Nothing. I tried every combination of URL where I thought it might be lurking (adding /plugin/mything/ajaxMyThing, etc.) I tried adding @Symbol annotations and overriding getUrlName() and using those values in the URL. Nothing.

/plugin/mything is a `hudson.Plugin` (as stapler.trace would tell you). It's an easy "default" location for plugins to put their stuff because it always exists at a known URL, but not a great choice IMO as it is deprecated for user code (see Javadoc).

@Symbol is completely unused by Stapler, it's much more recent and for a different purpose.

`getUrlName()` for Widgets may actually be unused, unsure. Widgets don't seem to have a really good URL binding: Jenkins#getWidgets() exists, but it returns a list, which can only be numerically indexed.

The safe fallback would be to make sure it's annotated with `@Extension` (~Jenkins creates and registers an instance on startup), because those are always available via /extensionList/hudson.widgets.Widget/org.acme.myWidget/

So the full URL to your view should be something like /extensionList/hudson.widgets.Widget/org.acme.MyWidget/ajaxMyThing

Robert Smith

unread,
Jan 3, 2021, 4:08:26 PM1/3/21
to Jenkins Developers
Yes! That URL pattern worked a treat! 

Thanks so much! The links are also very appreciated. I'll definitely take the time to work through what stapler is doing in this case.

Reply all
Reply to author
Forward
0 new messages