Until now we have been generating classes that collect the results we want to display. We have not hooked into Hudson’s methods of displaying reports. There are essentially four places that we could want to generate reports:In each individual build (we’ll call this a Single Build Report),In the project (we’ll call this a Single Project Report),In the summary of all the Maven 2 module builds associated with a multi-module Maven 2 build (we’ll call this an Aggregate Build Report), and finallyIn the Maven 2 project itself (we’ll call this an Aggregate Project Report).To make matters more confusing, each of these reports usually to implement different classes and need to implement different interfaces:Project Reports usually inherit from ActionableBuild Reports usually inherit from HealthReportingActionSingle Build Reports in Maven 2 projects need to implement AggregatableActionWhat we need is to put some common framework around these reports to ensure that we are not repeating ourselves all the time. In general, each of these four reports will be doing mostly the same things.The Build Reports will be displaying the results for a specific buildThe Project Reports will be displaying the results for the latest build (and possibly a trend chart if that makes sense)The Aggregate Reports will be displaying the aggregate of all the Single ReportsSound’s like a job for multiple inheritance! Fortune would have it that Java does not support multiple inheritance, so we will have to use some form of composition to get the same effect, and generics can help reduce the repetition too. But enough! On to the solution.AbstractBuildActionWe’ll start with a parent class for all our Build Reports. We’ll make this class generic, parameterised by the Build class that it operates on, so that we can use the same core code for all the Build Reports:package hudson.plugins.helpers;import hudson.model.AbstractBuild;import hudson.model.HealthReportingAction;public abstract class AbstractBuildAction<BUILD extends AbstractBuild> implements HealthReportingAction { private BUILD build = null; protected AbstractBuildAction() { } public synchronized BUILD getBuild() { return build; } public synchronized void setBuild(BUILD build) { if (this.build == null && this.build != build) { this.build = build; } }}We store a reference to the build in a local variable and provide a getter for the build. Ideally we would like to the build variable to be final and initialize it in the constructor. However, Hudson’s remoting interface will get in the way of this for any actions created on the slave. The solution is to use a setter to set the build. Additionally, we have some logic that makes this setter write-once. This is just a safety net to stop us from accidentally confusing Hudson. Strictly speaking, if you are careful the setter can be justa a simple setter and not write once.[Correction] We also need to modify BuildProxy so that it calls our setter for us for actions added from slave side executions:package hudson.plugsin.helpers;…public final class BuildProxy implements Serializable { … private final List<AbstractBuildAction<AbstractBuild» actions = new ArrayList<AbstractBuildAction»(); … public void updateBuild(AbstractBuild<,?> build) { for (AbstractBuildAction<AbstractBuild> action: actions) { if (!build.getActions().contains(action)) { action.setBuild(build); build.getActions().add(action); } } if (result != null && result.isWorseThan(build.getResult())) { build.setResult(result); } } … public List<AbstractBuildAction<AbstractBuild» getActions() { return actions; } …}[/Correction]In addition to these simple getters and setters, we want to provide a framework for displaying the report detail. Each different type of report has different ways of fitting into the Hudson UI. We are going to attempt to standardise these by using wrapper .jelly files to call a standard set which implementing classes can override. Jelly files are stored as resources in the hudson plugin, so with the Maven2 project structure for a Hudson plugin, the java source is /src/main/java/hudson/plugins/helpers/AbstractBuildAction.java and the jelly files for this java class are in /src/main/resources/hudson/plugins/helpers/AbstractBuildAction/. The basic things that all build reports want to do are as follows:A main report detail page (e.g. package level summary of the number of lines of code)A graph of some sort, with the option to enlarge it. (e.g. trend graph of the number of lines of code)A simple summary of the report for the main page (i.e. “17,345 lines of code (+1,534)")We will implement this functionality with some properties of the AbstractBuildAction, and some default .jelly files. First the additional properties:…public abstract class AbstractBuildAction<BUILD extends AbstractBuild> implements HealthReportingAction { … public boolean isFloatingBoxActive() { return false; } public boolean isGraphActive() { return false; } public String getGraphName() { return getDisplayName(); } public String getSummary() { return “”; }}These four properties will be used to control how the action appears. isFloatingBoxActive allows us to enable the floating box on the build summary page. isGraphActive allows us to activate the graph inside the floating box. getGraphName should return the title that will be displayed above the graph. And finally, getSummary will control the summary text to display beside the build report icon on the build summary page.Next we have some empty default place-holder jelly files. These will be the jelly files that we can override in our actual reports:reportDetail.jelly<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"></j:jelly>normalGraph.jelly<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"></j:jelly>largeGraph.jelly<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"></j:jelly>The reportDetail.jelly page will be used for the main report detail page, the normalGraph.jelly page will be used for the floating trend graph, while the largeGraph.jelly page will be used for the enlarged graph. To hook these pages into the Hudson framework, we need the following jelly pages:index.jelly<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 xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"> <st:include it="${it.build}" page=“sidepanel.jelly”/> <l:main-panel> ${it.displayName} <j:if test="${it.graphActive}"> ${it.graphName} <st:include page=“normalGraph.jelly”/> </j:if> <st:include page=“reportDetail.jelly”/> </l:main-panel> </l:layout></j:jelly>This page will also include the trend graph if it is activefloatingBox.jelly<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"> <j:if test="${it.graphActive}"> ${from.graphName} <st:include page=“normalGraph.jelly”/> enlarge </j:if></j:jelly>summary.jelly<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" xmlns:i=“jelly:fmt”> <t:summary icon="${it.iconFileName}"> ${it.displayName} ${it.summary} </t:summary> <j:if test="${it.floatingBoxVisible}}"> <st:include page=“floatingBox.jelly”/> </j:if></j:jelly>Note that the build main page does not support floating boxes, so we have to hack it in via the summary.jelly pageenlargedGraph.jelly<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 xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"> <st:include it="${it.build}" page=“sidepanel.jelly”/> <l:main-panel> <j:if test="${it.graphActive}"> ${it.graphName} <st:include page=“largeGraph.jelly”/> </j:if> </l:main-panel> </l:layout></j:jelly>AbstractProjectActionThis is similar to AbstractBuildAction, however, we don’t have to deal with the write-once setter problem.package hudson.plugins.helpers;import hudson.model.AbstractProject;import hudson.model.Actionable;abstract public class AbstractProjectAction<PROJECT extends AbstractProject> extends Actionable { private final PROJECT project; protected AbstractProjectAction(PROJECT project) { this.project = project; } public PROJECT getProject() { return project; } public boolean isFloatingBoxActive() { return true; } public boolean isGraphActive() { return false; } public String getGraphName() { return getDisplayName(); }}Again we have a set of jelly files, we’ll repeat the same placeholders:reportDetail.jelly<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"></j:jelly>normalGraph.jelly<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"></j:jelly>largeGraph.jelly<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"></j:jelly>The other link in pages are similar, only changing from a reference for ${it.build} to ${it.project}index.jelly<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 xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"> <st:include it="${it.project}" page=“sidepanel.jelly”/> <l:main-panel> ${it.displayName} <j:if test="${it.graphActive}"> ${it.graphName} <st:include page=“normalGraph.jelly”/> </j:if> <st:include page=“reportDetail.jelly”/> </l:main-panel> </l:layout></j:jelly>floatingBox.jelly<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" xmlns:i=“jelly:fmt” xmlns:local=“local”> <j:if test="${it.graphActive}"> ${from.graphName} <st:include page=“normalGraph.jelly”/> enlarge </j:if></j:jelly>The project page automatically includes the floating box, so we don;t require the summary hackenlargedGraph.jelly<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 xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"> <st:include it="${it.project}" page=“sidepanel.jelly”/> <l:main-panel> <j:if test="${it.graphActive}"> ${it.graphName} <st:include page=“largeGraph.jelly”/> </j:if> </l:main-panel> </l:layout></j:jelly>SummaryThat’s it for part 5, next time we will implement the javancss parser and get the results for a build. Part 7 will finish off the plugin with the reports based on these two base classes.
/ Writing a Hudson plug-in (Part 5 – Reporting)
Created Fri, 01 Feb 2008 00:00:00 +0000
Modified Fri, 01 Feb 2008 00:00:00 +0000