/* * The MIT License * * Copyright (c) 2010 Bruno P. Kinoshita <http://www.kinoshita.eti.br> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.testlink; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Run; import hudson.util.ChartUtil; import hudson.util.DataSetBuilder; import java.io.IOException; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import org.jfree.chart.JFreeChart; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; /** * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br * @since 1.0 */ public class TestLinkProjectAction extends AbstractTestLinkProjectAction { private static final int DEFAULT_GRAPH_WIDTH = 500; private static final int DEFAULT_GRAPH_HEIGHT = 200; private AbstractProject<?, ?> project; /** * Used to figure out if we need to regenerate the graphs or not. Only used in newGraphNotNeeded() method. Key is * the request URI and value is the number of builds for the project. */ private transient Map<String, Integer> requestMap = new HashMap<String, Integer>(); public TestLinkProjectAction(AbstractProject<?, ?> project) { this.project = project; } public AbstractProject<?, ?> getProject() { return this.project; } protected Class<TestLinkBuildAction> getBuildActionClass() { return TestLinkBuildAction.class; } /** * Returns the last build action. * * @return the last build action or <code>null</code> if there is no such build action. */ public TestLinkBuildAction getLastBuildAction() { AbstractBuild<?, ?> lastBuild = getLastBuildWithTestLink(); TestLinkBuildAction action = null; if (lastBuild != null) { action = lastBuild.getAction(getBuildActionClass()); } return action; } /** * Retrieves the last build with TestLink in the project. * * @return Last build with TestLink in the project or <code>null</code>, if there is no build with TestLink in the * project. */ private AbstractBuild<?, ?> getLastBuildWithTestLink() { AbstractBuild<?, ?> lastBuild = (AbstractBuild<?, ?>) project.getLastBuild(); while (lastBuild != null && lastBuild.getAction(getBuildActionClass()) == null) { lastBuild = lastBuild.getPreviousBuild(); } return lastBuild; } /** * * Show CCM html report f the latest build. If no builds are associated with CCM , returns info page. * * @param req Stapler request * @param res Stapler response * @throws IOException in case of an error */ public void doIndex(final StaplerRequest req, final StaplerResponse res) throws IOException { AbstractBuild<?, ?> lastBuild = getLastBuildWithTestLink(); if (lastBuild == null) { res.sendRedirect2("nodata"); } else { int buildNumber = lastBuild.getNumber(); res.sendRedirect2(String.format("../%d/%s", buildNumber, TestLinkBuildAction.URL_NAME)); } } public void doGraph(final StaplerRequest req, StaplerResponse res) throws IOException { if (ChartUtil.awtProblemCause != null) { res.sendRedirect2(req.getContextPath() + "/images/headless.png"); return; } if (newGraphNotNeeded(req, res)) { return; } final DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dataSetBuilder = new DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel>(); populateDataSetBuilder(dataSetBuilder); new hudson.util.Graph(-1, getGraphWidth(), getGraphHeight()) { protected JFreeChart createGraph() { return GraphHelper.createChart(req, dataSetBuilder.build()); } }.doPng(req, res); } public void doGraphMap(final StaplerRequest req, StaplerResponse res) throws IOException { if (newGraphNotNeeded(req, res)) { return; } final DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dataSetBuilder = new DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel>(); populateDataSetBuilder(dataSetBuilder); new hudson.util.Graph(-1, getGraphWidth(), getGraphHeight()) { protected JFreeChart createGraph() { return GraphHelper.createChart(req, dataSetBuilder.build()); } }.doMap(req, res); } /** * Checks if it should display graph. * * @return <code>true</code> if it should display graph and <code>false</code> otherwise. */ public final boolean isDisplayGraph() { return project.getBuilds().size() > 0; } /** * If number of builds hasn't changed and if checkIfModified() returns true, no need to regenerate the graph. * Browser should reuse it's cached image * * @param req * @param res * @return true, if new image does NOT need to be generated, false otherwise */ private boolean newGraphNotNeeded(final StaplerRequest req, StaplerResponse res) { boolean newGraphNotNeeded = false; Calendar t = getProject().getLastCompletedBuild().getTimestamp(); Integer prevNumBuilds = requestMap.get(req.getRequestURI()); int numBuilds = getProject().getBuilds().size(); // change null to 0 prevNumBuilds = prevNumBuilds == null ? 0 : prevNumBuilds; if (prevNumBuilds != numBuilds) { requestMap.put(req.getRequestURI(), numBuilds); } if (requestMap.keySet().size() > 10) { // keep map size in check requestMap.clear(); } if (prevNumBuilds == numBuilds && req.checkIfModified(t, res)) { /* * checkIfModified() is after '&&' because we want it evaluated only if number of builds is different */ newGraphNotNeeded = true; } return newGraphNotNeeded; } protected void populateDataSetBuilder(DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dataset) { for (Run<?, ?> build = getProject().getLastBuild(); build != null; build = build.getPreviousBuild()) { ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel((Run<?, ?>) build); TestLinkBuildAction action = build.getAction(getBuildActionClass()); if (action != null) { final TestLinkResult result = action.getResult(); final Report report = result.getReport(); dataset.add(report.getBlocked(), "Blocked", label); dataset.add(report.getFailed(), "Failed", label); dataset.add(report.getNotRun(), "Not Run", label); dataset.add(report.getPassed(), "Passed", label); } } } /** * Getter for property 'graphWidth'. * * @return Value for property 'graphWidth'. */ public int getGraphWidth() { return DEFAULT_GRAPH_WIDTH; } /** * Getter for property 'graphHeight'. * * @return Value for property 'graphHeight'. */ public int getGraphHeight() { return DEFAULT_GRAPH_HEIGHT; } }