package hudson.plugins.violations.hudson; import java.io.IOException; import java.util.Calendar; import java.awt.Color; import java.awt.BasicStroke; import org.jfree.chart.JFreeChart; import org.jfree.chart.ChartFactory; import org.jfree.chart.renderer.category.LineAndShapeRenderer; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.CategoryLabelPositions; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.title.LegendTitle; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.CategoryPlot; import org.jfree.data.category.CategoryDataset; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerProxy; import hudson.model.Action; import hudson.model.Actionable; import hudson.model.HealthReportingAction; import hudson.model.HealthReport; import hudson.model.AbstractBuild; import hudson.util.ChartUtil; import hudson.util.DataSetBuilder; import hudson.util.ShiftedCategoryAxis; import hudson.util.ColorPalette; import hudson.plugins.violations.graph.SeverityTypeDataSet; import hudson.plugins.violations.util.StringUtil; import hudson.plugins.violations.MagicNames; import hudson.plugins.violations.ViolationsReport; /** * Common base class for recording violations results. */ public abstract class AbstractViolationsBuildAction <T extends AbstractViolationsBuildAction> extends Actionable implements Action, HealthReportingAction, StaplerProxy { protected final AbstractBuild<?, ?> owner; private static final double LOG_VALUE_FOR_ZERO = 0.5; private boolean useLog = false; private static final int X_SIZE = 400; private static final int Y_SIZE = 200; private static final double PADDING = 5.0; /** * Constructor setting the owner build. */ protected AbstractViolationsBuildAction(AbstractBuild<?, ?> owner) { this.owner = owner; } /** * Get the report. * @return the report. */ abstract public ViolationsReport getReport(); /** * The object to use then the URL is pressed * This handles the url's from "${buildpage}/violations/" except * for any overridded by do methods in this class (in this * instance doGraph). * @return the violations report. */ public Object getTarget() { return getReport(); } /** * Get the build that owns this action. * @return the build. */ public AbstractBuild<?, ?> getBuild() { return owner; } /** * Get the build report. * @return the build report (null if not available). */ @Override public HealthReport getBuildHealth() { return getReport().getBuildHealth(); } /** * The display name for the url to be displayed * on the build page right hand side. * @return the display name. */ public String getDisplayName() { return "Violations"; } // ------------------------------------------- // These are the magic methods to tell hudson // the url, the object to go to when the url // is pressed. // ------------------------------------------- /** * The url to get to the "target" object. * @return the url */ public String getUrlName() { return MagicNames.VIOLATIONS; } /** * Get the search url. * @return the url. */ public String getSearchUrl() { return getUrlName(); } /** * Get the icon to display. * FIXME: MAY NEED TO SPECIFY ROOT DIR. * @return the violations 24x24 icon. */ public String getIconFileName() { return MagicNames.ICON_24; } /** * Gets the first valid violations report. * @return the first violations report found for this build. */ public ViolationsReport findReport() { return ViolationsReport.findViolationsReport(owner); } /** * Gets the test result of the previous build, if it's recorded, or null. */ public T getPreviousResult() { ViolationsReport report = ViolationsReport.findViolationsReport(owner); return report == null ? null : report.getBuild().getAction((Class<T>) getClass()); } /** * This corresponds to the url ./graph. * @param req the request parameters. * @param rsp the response. * @throws IOException if there is an error writing the graph */ public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException { String type = req.getParameter("type"); if (ChartUtil.awtProblemCause != null) { // not available. send out error message rsp.sendRedirect2(req.getContextPath() + "/images/headless.png"); return; } Calendar t = getBuild().getTimestamp(); if (!StringUtil.isBlank(type)) { ChartUtil.generateGraph( req, rsp, new SeverityTypeDataSet( getReport(), type).createChart(), X_SIZE, Y_SIZE); return; } DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dsb = new DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel>(); for (ViolationsReport r: ViolationsReport.iteration(getBuild())) { ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(r.getBuild()); for (String ty: r.getViolations().keySet()) { dsb.add(roundUp(r.getViolations().get(ty)), ty, label); } } ChartUtil.generateGraph( req, rsp, createChart(dsb.build()), X_SIZE, Y_SIZE); } private double roundUp(int val) { if (!useLog) { return 1.0 * val; } if (val < 1) { return LOG_VALUE_FOR_ZERO; } else { return 1.0 * val; } } // FIXME this should be in a utility class protected JFreeChart createChart(CategoryDataset dataset) { final JFreeChart chart = ChartFactory.createLineChart( null, // chart title null, // unused "count", // 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(); if (useLog) { final NumberAxis rangeAxis2 = new LogarithmicAxis("Log(y)"); plot.setRangeAxis(rangeAxis2); } // 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.setStroke(new BasicStroke(2.0f)); ColorPalette.apply(renderer); // crop extra space around the graph plot.setInsets(new RectangleInsets(PADDING, 0, 0, PADDING)); return chart; } }