package hudson.plugins.cobertura;
import hudson.model.AbstractBuild;
import hudson.model.HealthReport;
import hudson.model.HealthReportingAction;
import hudson.model.Result;
import hudson.plugins.cobertura.targets.CoverageMetric;
import hudson.plugins.cobertura.targets.CoverageResult;
import hudson.plugins.cobertura.targets.CoverageTarget;
import hudson.util.ChartUtil;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.DescribableList;
import hudson.util.ShiftedCategoryAxis;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Calendar;
import java.util.EnumMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Created by IntelliJ IDEA.
*
* @author connollys
* @since 03-Jul-2007 08:43:08
*/
public class CoberturaBuildAction implements HealthReportingAction, StaplerProxy {
private final AbstractBuild<?, ?> owner;
private CoverageTarget healthyTarget;
private CoverageTarget unhealthyTarget;
/**
* Overall coverage result.
*/
private Map<CoverageMetric, Ratio> result;
private HealthReport health = null;
private transient WeakReference<CoverageResult> report;
private boolean onlyStable;
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public HealthReport getBuildHealth() {
if (health != null) {
return health;
}
//try to get targets from root project (for maven modules targets are null)
DescribableList rootpublishers = owner.getProject().getRootProject().getPublishersList();
if (rootpublishers != null) {
CoberturaPublisher publisher = (CoberturaPublisher) rootpublishers.get(CoberturaPublisher.class);
if (publisher != null) {
healthyTarget = publisher.getHealthyTarget();
unhealthyTarget = publisher.getUnhealthyTarget();
}
}
if(healthyTarget == null || unhealthyTarget == null){
return null;
}
if (result == null) {
CoverageResult projectCoverage = getResult();
result = new EnumMap<CoverageMetric, Ratio>(CoverageMetric.class);
result.putAll(projectCoverage.getResults());
}
Map<CoverageMetric, Integer> scores = healthyTarget.getRangeScores(unhealthyTarget, result);
int minValue = 100;
CoverageMetric minKey = null;
for (Map.Entry<CoverageMetric, Integer> e : scores.entrySet()) {
if (e.getValue() < minValue) {
minKey = e.getKey();
minValue = e.getValue();
}
}
if (minKey == null) {
if (result == null || result.size() == 0) {
return null;
} else {
for (Map.Entry<CoverageMetric, Integer> e : scores.entrySet()) {
minKey = e.getKey();
}
if (minKey != null) {
Localizable localizedDescription = Messages._CoberturaBuildAction_description(result.get(minKey).getPercentage(), result.get(minKey).toString(), minKey.getName());
health = new HealthReport(minValue, localizedDescription);
return health;
}
return null;
}
} else {
Localizable localizedDescription = Messages._CoberturaBuildAction_description(result.get(minKey).getPercentage(), result.get(minKey).toString(), minKey.getName());
health = new HealthReport(minValue, localizedDescription);
return health;
}
}
/**
* {@inheritDoc}
*/
public String getIconFileName() {
return "graph.gif"; //To change body of implemented methods use File | Settings | File Templates.
}
/**
* {@inheritDoc}
*/
public String getDisplayName() {
return Messages.CoberturaBuildAction_displayName(); //To change body of implemented methods use File | Settings | File Templates.
}
/**
* {@inheritDoc}
*/
public String getUrlName() {
return "cobertura"; //To change body of implemented methods use File | Settings | File Templates.
}
/**
* {@inheritDoc}
*/
public Object getTarget() {
return getResult(); //To change body of implemented methods use File | Settings | File Templates.
}
/**
* Getter for property 'previousResult'.
*
* @return Value for property 'previousResult'.
*/
public CoberturaBuildAction getPreviousResult() {
return getPreviousResult(owner);
}
/**
* Gets the previous {@link CoberturaBuildAction} of the given build.
*/
/*package*/
static CoberturaBuildAction getPreviousResult(AbstractBuild<?,?> start) {
AbstractBuild<?, ?> b = start;
while (true) {
b = b.getPreviousNotFailedBuild();
if (b == null)
return null;
assert b.getResult() != Result.FAILURE : "We asked for the previous not failed build";
CoberturaBuildAction r = b.getAction(CoberturaBuildAction.class);
if(r != null && r.includeOnlyStable() && b.getResult() != Result.SUCCESS){
r = null;
}
if (r != null)
return r;
}
}
private boolean includeOnlyStable() {
return onlyStable;
}
CoberturaBuildAction(AbstractBuild<?, ?> owner, CoverageResult r, CoverageTarget healthyTarget,
CoverageTarget unhealthyTarget, boolean onlyStable) {
this.owner = owner;
this.report = new WeakReference<CoverageResult>(r);
this.healthyTarget = healthyTarget;
this.unhealthyTarget = unhealthyTarget;
this.onlyStable = onlyStable;
r.setOwner(owner);
if (result == null) {
result = new EnumMap<CoverageMetric,Ratio>(CoverageMetric.class);
result.putAll(r.getResults());
}
getBuildHealth(); // populate the health field so we don't have to parse everything all the time
}
/**
* Obtains the detailed {@link hudson.plugins.cobertura.targets.CoverageResult} instance.
*/
public synchronized CoverageResult getResult() {
if (report != null) {
CoverageResult r = report.get();
if (r != null) return r;
}
CoverageResult r = null;
for (File reportFile : CoberturaPublisher.getCoberturaReports(owner)) {
try {
r = CoberturaCoverageParser.parse(reportFile, r);
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to load " + reportFile, e);
}
}
if (r != null) {
r.setOwner(owner);
report = new WeakReference<CoverageResult>(r);
return r;
} else {
return null;
}
}
private static final Logger logger = Logger.getLogger(CoberturaBuildAction.class.getName());
public static CoberturaBuildAction load(AbstractBuild<?, ?> build, CoverageResult result, CoverageTarget healthyTarget,
CoverageTarget unhealthyTarget, boolean onlyStable) {
return new CoberturaBuildAction(build, result, healthyTarget, unhealthyTarget, onlyStable);
}
/**
* Generates the graph that shows the coverage trend up to this report.
*/
public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException {
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
rsp.sendRedirect2(req.getContextPath() + "/images/headless.png");
return;
}
Calendar t = owner.getTimestamp();
if (req.checkIfModified(t, rsp))
return; // up to date
DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dsb = new DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel>();
for (CoberturaBuildAction a = this; a != null; a = a.getPreviousResult()) {
ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(a.owner);
for (Map.Entry<CoverageMetric, Ratio> value : a.result.entrySet()) {
dsb.add(value.getValue().getPercentageFloat(), value.getKey().getName(), label);
}
}
ChartUtil.generateGraph(req, rsp, createChart(dsb.build()), 500, 200);
}
private JFreeChart createChart(CategoryDataset dataset) {
final JFreeChart chart = ChartFactory.createLineChart(
null, // chart title
null, // unused
"%", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
final LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.RIGHT);
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.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());
rangeAxis.setUpperBound(100);
rangeAxis.setLowerBound(0);
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setBaseStroke(new BasicStroke(2.0f));
ColorPalette.apply(renderer);
// crop extra space around the graph
plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0));
return chart;
}
}