/******************************************************************************* * Copyright (c) 2017 Synopsys, Inc * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Synopsys, Inc - initial implementation and documentation *******************************************************************************/ package jenkins.plugins.coverity; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.util.ChartUtil; import hudson.util.DataSetBuilder; import hudson.util.Graph; import hudson.util.ShiftedCategoryAxis; import hudson.util.StackedAreaRenderer2; import jenkins.model.Jenkins; 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.StackedAreaRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.ui.RectangleInsets; import java.awt.*; import java.util.List; /** * Project-level action for Coverity. This is used to to display the history graph. */ public class CoverityProjectAction implements Action { private final AbstractProject<?, ?> project; public CoverityProjectAction(AbstractProject<?, ?> project) { this.project = project; } public AbstractProject<?, ?> getProject() { return project; } public String getIconFileName() { return null; } public String getDisplayName() { return null; } public String getUrlName() { return "coverity"; } public Graph getGraph() { return new GraphImpl(); } private class GraphImpl extends Graph { protected GraphImpl() { super(-1, 600, 300); // no caching, because it doesn't deal with deleted builds } protected DataSetBuilder<String, ChartLabel> createDataSet() { DataSetBuilder<String, ChartLabel> data = new DataSetBuilder<String, ChartLabel>(); AbstractBuild<?, ?> build = project.getLastCompletedBuild(); while(build != null) { final List<CoverityBuildAction> actions = build.getActions(CoverityBuildAction.class); for(CoverityBuildAction action : actions) { if(action != null && action.getDefects() != null && action.getGraphDisplayName() != null) { data.add(action.getDefects().size(), action.getGraphDisplayName(), new ChartLabel(build)); } } build = build.getPreviousBuild(); } return data; } protected JFreeChart createGraph() { final CategoryDataset dataset = createDataSet().build(); List rows = dataset.getColumnKeys(); for(int i = 0; i < rows.size(); i ++){ Object row = rows.get(i); if(row == null){ throw new NullPointerException(); } } final JFreeChart chart = ChartFactory.createStackedAreaChart(null, // chart // title null, // unused "Defect Count", // range axis label dataset, // data PlotOrientation.VERTICAL, // orientation true, // include legend true, // tooltips false // urls ); chart.setBackgroundPaint(Color.white); // Originally white final CategoryPlot plot = chart.getCategoryPlot(); // plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0)); plot.setBackgroundPaint(Color.white); // Originally white plot.setOutlinePaint(null); plot.setForegroundAlpha(0.8f); plot.setDomainGridlinesVisible(true); plot.setDomainGridlinePaint(Color.white); plot.setRangeGridlinesVisible(true); plot.setRangeGridlinePaint(Color.black); // Originally 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.setAutoRange(true); plot.setRenderer(new ChartItemRenderer(dataset)); // crop extra space around the graph plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); return chart; } } private static class ChartItemRenderer extends StackedAreaRenderer2 { private final CategoryDataset ds; public ChartItemRenderer(CategoryDataset ds) { this.ds = ds; } @Override public Paint getItemPaint(int row, int column) { ChartLabel key = (ChartLabel) ds.getColumnKey(column); if(key.getColor() != null) return key.getColor(); return super.getItemPaint(row, column); } @Override public String generateURL(CategoryDataset dataset, int row, int column) { ChartLabel label = (ChartLabel) dataset.getColumnKey(column); return label.getUrl() + CoverityBuildAction.BUILD_ACTION_IDENTIFIER; } @Override public String generateToolTip(CategoryDataset dataset, int row, int column) { ChartLabel label = (ChartLabel) dataset.getColumnKey(column); int defects = 0; for(CoverityBuildAction a : label.build.getActions(CoverityBuildAction.class)) { defects += a.getDefects().size(); } return label.build.getDisplayName() + " has " + defects + " total defects"; } @Override public boolean equals(Object obj) { if(obj == this) { return true; } else if(!(obj instanceof ChartItemRenderer)) { return false; } else { ChartItemRenderer that = (ChartItemRenderer)obj; return this.ds != that.ds ? false : super.equals(obj); } } @Override public int hashCode() { int result = ds.hashCode(); result = 31 * result + super.hashCode(); return result; } } private static class ChartLabel implements Comparable<ChartLabel> { private AbstractBuild build; public ChartLabel(AbstractBuild build) { this.build = build; } public String getUrl() { return Jenkins.getInstance().getRootUrl() + build.getUrl(); } public int compareTo(ChartLabel that) { return build.number - that.build.number; } @Override public boolean equals(Object o) { if(!(o instanceof ChartLabel)) { return false; } ChartLabel that = (ChartLabel) o; return this.build == that.build; } public Color getColor() { return null; } @Override public int hashCode() { return build.getDisplayName().hashCode(); } @Override public String toString() { String l = build.getDisplayName(); String s = build.getBuiltOnStr(); if(s != null) l += ' ' + s; return l; } } }