Passing Dynamic Parameters: Part 3, Web Dashboard
The Story So Far
In my last two posts (here and here) I covered adding dynamic values to the server and CCTray. While this now allows some dynamic functionality, it doesn’t cover the full range – yes, I’ve still to cover the dashboard.
So, in this post I’ll finish the project and add dynamic values to the dashboard (including allow dynamic values via HTTP in CCTray).
Another Cunning Plan
There are two ways I could do this. First, when the user clicks on the force build button I could send them to another page. This page would check for parameters and if necessary send them to an editor page.
But that approach is so Web 1.0, it would be nice to have some dynamic Web 2.0 functionality (yes, I’m making fun of these terms). An alternate approach would be to display a little pop-up on the page with the parameter editor in it. This way the user still appears to be in the same context and more importantly, I can disable all other links.
Roll on AJAX
I’ve already used AJAX in one of the other little projects I did for CruiseControl.Net – the new security plug-ins. Therefore I’m just going to lever off these.
The JavaScript library I’m using is jQuery, plus some of the UI extensions (this is a nice little light-weight library).
My plan is to add a click handler to the force build button. When the user clicks on this it will send a request to the server asking if there are any parameters. If there are no parameters then the build will be triggered as per normal, otherwise it will display the parameters editor. When the user has finished with the parameters the build is then submitted as per normal (except with the extra parameters).
Starting backwards, the template that generates the force build button is ProjectGrid.vm. This is an nVelocity template that is called from VelocityProjectGridAction.
To this template I added the click handler, which calls a little JavaScript function (defined at the top of the template.) This function makes an AJAX call to the server asking if there are any parameters. This call will return ‘none’ if there are no parameters – otherwise it will return an HTML fragment containing the parameters.
The AJAX Call
This is where things get a little tricky. The actual grid is generated from VelocityProjectGridAction, but this isn’t an action. Instead the two actions that contain this class are ServerReportServerPlugin and FarmReportFarmPlugin. So I need to get each of these actions to return the new “naked” actions (initially defined in the security plug-ins here.)
This just means adding a new named action that is wrapped in an ImmutableNamedActionWithoutSiteTemplate. To do this the new action needs to inherit from ICruiseAction. So, my new action is called ProjectParametersAction. This calls ListBuildParameters() on the remote server (via IFarmService) to retrieve the list of parameters. If there are no parameters the request returns “NONE” (just the simple word), otherwise it calls nVelocity with a template to generate the parameters editor (this is just a table with the parameters as inputs in it).
This gets returned to the AJAX call in the page. The JavaScript function checks the outcome – if it is “NONE” it sends the build request off, otherwise it displays the editor in the pop-up. The editor has “Cancel” and “Build” in it. Clicking the “Cancel” button just closes the pop-up, while clicking “Build” does the “Submit” action.
To enable this I also had to modify ProjectGridRow and ProjectGrid to include the URL for requesting the parameters (since it’s a bit of fun working with the URL builders).
Processing the Parameters
To simplify the processing on the server-side, I prefixed each parameter name with “param_” and set it as the name of the input elements. When VelocityProjectGridAction receives the request it iterates through all the request parameters and extracts the ones that being with “param_” and puts them into a dictionary. This dictionary then gets passed onto the server.
I also had to modify IFarmService and ServerAggregatingCruiseManagerWrapper to pass the parameters through, but this was a reasonably simple change. I also modified CruiseServer in core to validate the parameters on their way in. Previously I was validating them as part of the Run() method, but with the integration queues this is far too late.
This now completes the changes required for the actual dashboard itself, but there is one other change that I need to make – exposing the parameters for HTTP transport in CCTray.
HTTP Transportation
There are two sides to the HTTP transport – sending the parameters to the client, and receiving the data values for the parameters from the client. The second part is easy – since it is already needed for force builds in the dashboard anyway (the HTTP transport sends a “PushButton” request to the server). However, sending the parameters will require a new plug-in.
Looking at CruiseObjectSourceInitializer there is a group of actions that are hard-coded. These actions provide some required plug-ins for CCTray, security and similar types of functionality. So I’ve just added a new action to this area called XmlProjectParametersReportAction.
This new action simply lists all the parameters for a project and stuffs them into an XML document. This XML document is then sent back via an XmlFragmentResponse. Simple, quick and easy.
Finishing Off CCTray
The final stage of the project is getting and setting parameters in CCTray. This involves modifying HttpCruiseProjectManager.
Setting the parameters is really easy – just iterate through the parameters and add them to the request (had to remember to prefix them with “param_”). This then works without any additional code changes.
To retrieve the parameters I needed to first call the action on the server (I extended WebDashboardUrl to return the correct URL), and then parse the results. This just involved the reverse of sending them down – load them into an XML document and select the nodes. The only minor change is parameters with values are returned as RangeParameter and all other parameters as TextParameter.
Dynamic Values Completed
This now completes the changes for adding dynamic parameters to CC.Net. People can now add projects to CC.Net that have dynamic parameters. This means one project can be built in different ways, depending on what the user specifies at build-time.
Hopefully this will add value to CC.Net
This is an excellent addition to CruiseControl. I have the security branch up and running here with no real issues. I really hope this makes it into a future release very soon!
The only issue I am having at the moment is when I try and pass a NAnt target as a parameter. The DynamicValueUtility.ChangeProperty method appears to have issue setting a value in the targetList array (not really surprising as I am not supplying an index into that array). I have tried using ‘targetList[0]‘ as a parameter, however, this appears to result in the property not being found.
Anyway, congratulations on a job well done!
Cheers,
Callum
p.s. I don’t know if this enhancement has already been requested, but if it was possible to access project names or build labels (akin to those passed to NAnt) within other areas of the project, I think it may be an extremely useful addition.
Hi Callum,
This is a bug in the code that I wrote
I have since gone through and (hopefully) fixed it. Thanks for your help on this.
Craig
Hi Craig,
I’ve been up and running with the security branch for a about a week and everything seems to be good so far. I’ve extended my local version to handle bool data type and display them as checkboxes on the webdashboard. Now I’m thinking the next thing that would be nice to have is a way to pass the parameters in on scheduled triggers. Thus far, both the cctray and webdashboard clients allow for manual triggering of the builds, so both rely on user interaction for the parameters to be set. However, at scheduled times, I might need a build to be made with a certain set of parameters. I don’t have much insight into the ccnet code, so I don’t know how difficult it is; have you looked into making that enhancement?
Phong
Hi Phong,
Sorry for the delay in replying – I’ve been busy in some of the other areas in CC.Net.
Your idea of passing parameters to a scheduled build is a good one. The best way to do this is a trigger that contains the scheduling trigger (similar to the way interval trigger works). I’ve put together this trigger and committed it to Subversion and I’ll make some time to put together a post on it.
Thanks again for your testing,
Craig
#if ($projectGridRow.allowForceBuild)
#if($projectGridRow.ForceAbortBuildButtonValue == “Force”)
>input type=”button”
name=”$projectGridRow.ForceAbortBuildButtonName”
value=”$projectGridRow.ForceAbortBuildButtonValue”
onclick=”checkForParams(this, ‘$projectGridRow.ParametersUrl’)”
/<
#else
>input type=”submit”
name=”$projectGridRow.ForceAbortBuildButtonName”
value=”$projectGridRow.ForceAbortBuildButtonValue”
/<
#end
#end
Thanks Phong,
I’ve made your suggested changes and checked it into Subversion.
(BTW, I think the issue with the greater than/less than signs is also annoying, but I guess that’s a limitation of WordPress)
Craig
grrr…seems I can’t put html code here directly. One more try:
#if ($projectGridRow.allowForceBuild)
#if($projectGridRow.ForceAbortBuildButtonValue == “Force”)
#else
#end
#end
Let’s try that again:
#if ($projectGridRow.allowForceBuild)
#if($projectGridRow.ForceAbortBuildButtonValue == “Force”)
l;
#else
l;
#end
#end
Hi Craig,
I had a bit of time to look at the abort problem and this bit of code change to the “Force” button in the ProjectGrid.vm seems to fix it:
#if ($projectGridRow.allowForceBuild)
#if($projectGridRow.ForceAbortBuildButtonValue == “Force”)
#else
#end
#end
The implementation seems to work great so far. I did find one small bug on the Web dashboard though; the Abort build seems to have stopped working. I think the problem is in ProjectGrid.vm but I haven’t had time to really dig into it.
-Phong
Thanks Phong,
I’ll take a look and fix it for you. I have a rough idea of what the problem will be
Craig
Adventurous is my middle name!
Thanks!
Hi Phong Do,
Yes this is completed, including with unit tests. Having after a discussion with the lead developer (Dave Cameron) we decided the best approach is to leave this for the 1.5 release.
I’m not sure on the timeframe yet, as we need to do a 1.4.3 release first – but if you feel adventurous all code is available in the security branch of Subversion.
Craig
Hi Craig,
I’m trying to implement the same exact enhancement! I went about it a different, less elegant way just to get it working. Have you finished your implemenation yet? I would love to see it in action.
-Phong Do