package hudson.plugins.jwsdp_sqe;
import hudson.model.ModelObject;
import hudson.model.AbstractBuild;
import hudson.util.ChartUtil;
import hudson.util.DataSetBuilder;
import hudson.util.ShiftedCategoryAxis;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.renderer.category.AreaRenderer;
import org.jfree.chart.renderer.AreaRendererEndType;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleInsets;
import java.io.IOException;
import java.awt.*;
/**
* Common data applicable to all test model objects.
*
* <p>
* Setter methods are for Digester, and once created the test objects
* are immutable.
*
* @param <S>
* The derived type of {@link TestCollection} (the same design pattern as you seen in {@link Enum})
*
* @author Kohsuke Kawaguchi
*/
public abstract class TestObject<S extends TestObject<S>>
implements ModelObject {
/**
* Unique identifier.
*/
private String id;
/**
* Optional human-readable name.
*/
private String name;
/**
* Optional description that possibly includes HTML.
*/
private String description;
protected Status status;
/**
* Optional message that complements status.
*/
private String statusMessage;
// set by the TestCollection when this is added to it.
TestCollection parent;
/*package*/ TestObject() {
}
/**
* Returns true only if all the mandatory fields are populated. This is used to make sure
* that we are parsing the right report.
*/
public boolean isFilled() {
return id!=null;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = mangleId(id);
}
/**
* Removes the characters in the string that are reserved in a URI
* @param id
* @return
*/
private String mangleId(String id) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < id.length(); i++) {
if (!ignoreChar(id.charAt(i))) {
sb.append(id.charAt(i));
}
}
return sb.toString();
}
private static char[] reserved = {'!','*','\'','(',')',';',':','@','&','=','+','$',',','/','?','%','#','[',']'};
private static boolean ignoreChar(char c) {
for (char rch: reserved) {
if (c == rch) {
return true;
}
}
return false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public final String getDisplayName() {
if(name!=null)
return name;
else
return id;
}
public AbstractBuild getOwner() {
return parent.getOwner();
}
/**
* Gets the counter part of this {@link TestObject} in the previous run.
*
* @return null
* if no such counter part exists.
*/
public S getPreviousResult() {
TestCollection p = (TestCollection)parent.getPreviousResult();
if(p!=null) return (S)p.get(getId());
else return null;
}
public Status getStatus() {
return status;
}
// Digester don't understand enum
public void setStatusString(String status) {
if(status.equalsIgnoreCase("pass"))
this.status = Status.PASS;
else
if(status.equalsIgnoreCase("did_not_run"))
this.status = Status.SKIP;
else
this.status = Status.FAIL;
}
public String getStatusMessage() {
return statusMessage;
}
public void setStatusMessage(String statusMessage) {
this.statusMessage = statusMessage;
}
public abstract int getTotalCount();
public abstract int getFailCount();
/**
* Generates a PNG image for the test result trend.
*/
public void doTestTrendGraph( StaplerRequest req, StaplerResponse rsp) throws IOException {
if(ChartUtil.awtProblemCause != null) {
// not available. send out error message
rsp.sendRedirect2(req.getContextPath()+"/images/headless.png");
return;
}
if(req.checkIfModified(getOwner().getTimestamp(),rsp))
return;
class BuildLabel implements Comparable<BuildLabel> {
private final AbstractBuild build;
public BuildLabel(AbstractBuild build) {
this.build = build;
}
public int compareTo(BuildLabel that) {
return this.build.number-that.build.number;
}
@Override
public boolean equals(Object o) {
BuildLabel that = (BuildLabel) o;
return build==that.build;
}
@Override
public int hashCode() {
return build.hashCode();
}
@Override
public String toString() {
return build.getDisplayName();
}
}
boolean failureOnly = Boolean.valueOf(req.getParameter("failureOnly"));
DataSetBuilder<String,BuildLabel> dsb = new DataSetBuilder<String,BuildLabel>();
for(TestObject a=this; a!=null; a=a.getPreviousResult() ) {
dsb.add( a.getFailCount(), "failed", new BuildLabel(a.getOwner()));
if(!failureOnly)
dsb.add( a.getTotalCount()-a.getFailCount(),"total", new BuildLabel(a.getOwner()));
}
ChartUtil.generateGraph(req,rsp,createChart(dsb.build()),500,200);
}
private JFreeChart createChart(CategoryDataset dataset) {
final JFreeChart chart = ChartFactory.createStackedAreaChart(
null, // chart title
null, // unused
"count", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
false, // include legend
true, // tooltips
false // urls
);
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
// set the background color for the chart...
// final StandardLegend legend = (StandardLegend) chart.getLegend();
// legend.setAnchor(StandardLegend.SOUTH);
chart.setBackgroundPaint(Color.white);
final CategoryPlot plot = chart.getCategoryPlot();
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlinePaint(null);
plot.setForegroundAlpha(0.8f);
// plot.setDomainGridlinesVisible(true);
// plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.black);
CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
plot.setDomainAxis(domainAxis);
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setCategoryMargin(0.0);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
AreaRenderer ar = (AreaRenderer) plot.getRenderer();
ar.setEndType(AreaRendererEndType.TRUNCATE);
ar.setSeriesPaint(0,new Color(0xEF,0x29,0x29));
ar.setSeriesPaint(1,new Color(0x72,0x9F,0xCF));
// crop extra space around the graph
plot.setInsets(new RectangleInsets(0,0,0,5.0));
return chart;
}
}