com.piketec.jenkins.plugins.tpt.publisher.TrendGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.piketec.jenkins.plugins.tpt.publisher.TrendGraph.java

Source

/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2018 PikeTec GmbH
 * 
 * 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 com.piketec.jenkins.plugins.tpt.publisher;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import com.piketec.jenkins.plugins.tpt.Utils;

import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.DirectoryBrowserSupport;
import hudson.model.Result;
import hudson.model.Run;
import hudson.util.HttpResponses;
import jenkins.model.RunAction2;

/**
 * Generates the trend graph on the main page.
 * 
 * @author FInfantino, PikeTec GmbH
 *
 */
public class TrendGraph implements RunAction2, StaplerProxy {

    private static final String INDENT = "\t";

    private static final String LF = "\n";

    private AbstractProject<?, ?> project;

    private AbstractBuild<?, ?> actualBuild;

    private transient Run<?, ?> run;

    private ArrayList<Integer> failedBuilds = new ArrayList<Integer>();

    private int passed;

    private int inconclusive;

    private int error;

    private int failed;

    private ArrayList<ResultData> historyData;

    /**
     * Creates a new TrendGraph
     * 
     * @param project
     *          The Jenkins project this Trendgraph belongs to.
     */
    public TrendGraph(final AbstractProject<?, ?> project) {
        this.historyData = new ArrayList<>();
        this.project = project;
        initBuildAndTestCaseResultCounts();
        setHistoryIterativ();
    }

    /**
     * @return true if the security has been set. (By security is meant the "trust slaves and user to
     *         modify the jenkins workspace option).
     */
    public boolean isTrustSlavesAndUsers() {
        return TPTGlobalConfiguration.DescriptorImpl.trustSlavesAndUsers;
    }

    /**
     * Gets the last successful build and gets the data (passed, inconclusive, error and failed tests)
     * from there. We need this method although we have the setHistoryIterativ method because in the
     * setHistoryIterativ method the call actualBuild.getPreviousBuildsOverThreshold doesnt get the
     * data from actual build, thats why we have a method for the history and this one for the actual
     * build.
     */
    private void initBuildAndTestCaseResultCounts() {
        actualBuild = this.project.getLastSuccessfulBuild();
        if (actualBuild == null) {
            return;
        }
        Action tptAction = actualBuild.getAction(TPTReportPage.class);
        if (tptAction == null) {
            return;
        }
        this.passed = ((TPTReportPage) tptAction).getPassedCount();
        this.inconclusive = ((TPTReportPage) tptAction).getInconclusiveCount();
        this.error = ((TPTReportPage) tptAction).getErrorCount();
        this.failed = ((TPTReportPage) tptAction).getFailedCount();
    }

    /**
     * Regenerates the trend graph. It is called everytime the page is refreshed, this is useful
     * because when a new build is created we want the graph to update on runtime.
     */
    private void refreshTrendGraph() {
        historyData.clear();
        setHistoryIterativ();
        initBuildAndTestCaseResultCounts();
    }

    /**
     * Fills the history Data with the data from the last 20 builds.
     */
    private void setHistoryIterativ() {
        if (actualBuild == null) {
            return;
        }
        Result result = actualBuild.getResult();
        if (result == null) {
            return;
        }
        @SuppressWarnings("unchecked")
        List<Run> builds = (List<Run>) actualBuild.getPreviousBuildsOverThreshold(20, Result.UNSTABLE);
        if (result.isBetterOrEqualTo(Result.UNSTABLE)) {
            builds.add(0, actualBuild);
        }
        for (Run run : builds) {
            ResultData toAdd = new ResultData();
            TPTReportPage tptAction = run.getAction(TPTReportPage.class); // is always unique
            if (tptAction == null) {
                continue;
            }
            toAdd.buildNummer = run.getNumber();
            toAdd.error = tptAction.getErrorCount();
            toAdd.passed = tptAction.getPassedCount();
            toAdd.inconclusive = tptAction.getInconclusiveCount();
            toAdd.failed = tptAction.getFailedCount();
            toAdd.total = toAdd.error + toAdd.failed + toAdd.passed + toAdd.inconclusive;
            historyData.add(toAdd);
        }
    }

    @Override
    public String getIconFileName() {
        return "/plugin/piketec-tpt/tpt.ico";
    }

    @Override
    public String getDisplayName() {
        return "TPT Trend Results";
    }

    @Override
    public String getUrlName() {
        return "TPTtrendResults";
    }

    /**
     * @return The Jenkins project this Trendgraph belongs to
     */
    public AbstractProject<?, ?> getProject() {
        return project;
    }

    /**
     * Set the Jenkins project this Trendgraph belongs to
     * 
     * @param project
     *          The Jenkins project this Trendgraph belongs to
     */
    public void setProject(AbstractProject<?, ?> project) {
        this.project = project;
    }

    @Override
    public void onAttached(Run<?, ?> run) {
        this.run = run;
    }

    @Override
    public void onLoad(Run<?, ?> run) {
        this.run = run;
    }

    /**
     * @return The concrete run this trend graph belongs to
     */
    public Run<?, ?> getRun() {
        return run;
    }

    /**
     * Set the concrete run this trend graph belongs to
     * 
     * @param run
     *          The concrete run this trend graph belongs to
     */
    public void setRun(Run<?, ?> run) {
        this.run = run;
    }

    /**
     * @return The number of failed builds of the jenkins project this trend graph belongs to
     */
    public ArrayList<Integer> getFailedBuilds() {
        return failedBuilds;
    }

    /**
     * Set the number of failed builds of the jenkins project this trend graph belongs to
     * 
     * @param failedBuilds
     *          The number of failed builds of the jenkins project this trend graph belongs to
     */
    public void setFailedBuilds(ArrayList<Integer> failedBuilds) {
        this.failedBuilds = failedBuilds;
    }

    /**
     * @return The number of passed tests of the last build of the jenkins project this trend graph
     *         belongs to.
     */
    public int getPassed() {
        return passed;
    }

    /**
     * Set the number of passed tests of the last build of the jenkins project this trend graph
     * belongs to.
     * 
     * @param passed
     *          TThe number of passed tests of the last build of the jenkins project this trend graph
     *          belongs to.
     */
    public void setPassed(int passed) {
        this.passed = passed;
    }

    /**
     * @return The number of failed tests of the last build of the jenkins project this trend graph
     *         belongs to.
     */
    public int getFailed() {
        return failed;
    }

    /**
     * Set the number of failed tests of the last build of the jenkins project this trend graph
     * belongs to.
     * 
     * @param failed
     *          The number of failed tests of the last build of the jenkins project this trend graph
     *          belongs to.
     */
    public void setFailed(int failed) {
        this.failed = failed;
    }

    /**
     * @return The number of tests with execution error of the last build of the jenkins project this
     *         trend graph belongs to.
     */
    public int getError() {
        return error;
    }

    /**
     * Set the number of tests with execution error of the last build of the jenkins project this
     * trend graph belongs to.
     * 
     * @param error
     *          The number of tests with execution error of the last build of the jenkins project this
     *          trend graph belongs to.
     */
    public void setError(int error) {
        this.error = error;
    }

    /**
     * @return The number of inconclusive tests of the last build of the jenkins project this trend
     *         graph belongs to.
     */
    public int getInconclusive() {
        return inconclusive;
    }

    /**
     * Set the number of inconclusive tests of the last build of the jenkins project this trend graph
     * belongs to.
     * 
     * @param inconclusive
     *          Set the number of inconclusive tests of the last build of the jenkins project this
     *          trend graph belongs to.
     */
    public void setInconclusive(int inconclusive) {
        this.inconclusive = inconclusive;
    }

    /**
     * @return A List of Data containing the numer off passed, failed, inconcluive etc. TPT tests of
     *         previous builds.
     */
    public ArrayList<ResultData> getHistoryData() {
        return this.historyData;
    }

    @Override
    public Object getTarget() {
        return this;
    }

    /**
     * This method is called everytime the page is refreshed. It regenerates the json file and
     * refreshes the graph.
     * 
     * 
     * @param req
     *          The request
     * @param rsp
     *          The response
     * @throws IOException
     *           if the response could not be generated
     * @throws ServletException
     *           if the response could not be generated
     * @throws InterruptedException
     *           if the job is cancelled
     */
    public void doDynamic(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException, InterruptedException {
        // For every refresh the actual build will be updated. If actual build equals null, nothing to
        // show
        actualBuild = this.project.getLastSuccessfulBuild();
        refreshTrendGraph();
        if (actualBuild == null) {
            return;
        }
        TPTGlobalConfiguration.setSecurity();
        generateJson();
        File buildDir = actualBuild.getRootDir();
        DirectoryBrowserSupport dbs = new DirectoryBrowserSupport(this,
                new FilePath(new File(buildDir.getAbsolutePath() + File.separator + "TrendGraph")), "TPT Report",
                "/plugin/piketec-tpt/tpt.ico", false);

        if (req.getRestOfPath().equals("")) {
            throw HttpResponses.forwardToView(this, "index.jelly");
        }
        dbs.generateResponse(req, rsp, this);
    }

    /**
     * Generates the json file with the historyData.
     * 
     * @throws IOException
     *           if files could not be created
     * @throws InterruptedException
     *           if the job is cancelled
     */
    private void generateJson() throws IOException, InterruptedException {
        File oldIndexHTML = new File(Utils.getTptPluginRootDir(), "TrendGraph" + File.separator + "index.html");
        File utilsJs = new File(Utils.getTptPluginRootDir(), "TrendGraph" + File.separator + "utils.js");

        File buildDir = actualBuild.getRootDir();

        File trendGraph = new File(buildDir.getAbsolutePath() + File.separator + "TrendGraph");

        if (!trendGraph.isDirectory() && !trendGraph.mkdirs()) {
            throw new IOException("Could not create directory \"" + trendGraph.getAbsolutePath() + "\"");
        }
        if (utilsJs.exists()) {
            FileUtils.copyFileToDirectory(utilsJs, trendGraph);
            FileUtils.copyFileToDirectory(oldIndexHTML, trendGraph);
        }
        // replace the place holder "toReplace" by actual json script
        String jsonScript = getResultArray(historyData);
        String newIndexHTMLWithJson = FileUtils.readFileToString(oldIndexHTML);
        newIndexHTMLWithJson = newIndexHTMLWithJson.replace("toReplace", jsonScript);
        File newIndexHTML = new File(
                buildDir.getAbsolutePath() + File.separator + "TrendGraph" + File.separator + "index.html");
        FileUtils.writeStringToFile(newIndexHTML, newIndexHTMLWithJson);

    }

    /**
     * Minor function from generateJson()
     * 
     * @param data
     *          The data to create the graph from
     * @return
     */
    private static String getResultArray(ArrayList<ResultData> data) {
        StringBuffer buf = new StringBuffer();
        int indent = 1;
        buf.append(StringUtils.repeat(INDENT, indent + 1) + " { \"data\" : [" + LF);
        for (int i = 0; i < data.size(); i++) {
            ResultData currentData = data.get(i);
            if (i == data.size() - 1) {
                buf.append(getResultStruct(currentData.total, currentData.failed, currentData.inconclusive,
                        currentData.error, currentData.passed, currentData.buildNummer, indent + 1, false));
            } else {
                buf.append(getResultStruct(currentData.total, currentData.failed, currentData.inconclusive,
                        currentData.error, currentData.passed, currentData.buildNummer, indent + 1, true));
            }
        }
        buf.append(StringUtils.repeat(INDENT, indent + 1) + "]" + LF);
        buf.append("}");
        return buf.toString();
    }

    /**
     * Minor function from generateJson().
     * 
     * @param total
     *          The total number of test cases
     * @param failed
     *          The number of failed test cases
     * @param inconclusive
     *          The number of inconclusive test cases
     * @param error
     *          The number of test cases with execution errors
     * @param passed
     *          the number of passed test cases
     * @param buildNummer
     *          the build number of the Jenkins build
     * @param indent
     *          indentation. Just for formating the json output
     * @param withComma
     *          should the last line end with a comma
     * @return The part of the json output containing the numbers of test cases foreach result and teh
     *         build number
     */
    private static String getResultStruct(int total, int failed, int inconclusive, int error, int passed,
            int buildNummer, int indent, boolean withComma) {
        StringBuffer buf = new StringBuffer();
        buf.append(StringUtils.repeat(INDENT, indent + 1) + "[" + LF);
        buf.append(getJSONIntEntry("buildNummer", buildNummer, indent + 1, true));
        buf.append(getJSONIntEntry("total", total, indent + 1, true));
        buf.append(getJSONIntEntry("failed", failed, indent + 1, true));
        buf.append(getJSONIntEntry("inconclusive", inconclusive, indent + 1, true));
        buf.append(getJSONIntEntry("error", error, indent + 1, true));
        buf.append(getJSONIntEntry("passed", passed, indent + 1));
        // append comma if needed
        if (withComma) {
            buf.append(StringUtils.repeat(INDENT, indent + 1) + "]," + LF);
        } else {
            buf.append(StringUtils.repeat(INDENT, indent + 1) + "]" + LF);
        }
        return buf.toString();
    }

    private static String getJSONIntEntry(String name, int value, int indent, boolean withComma) {
        if (withComma) {
            return StringUtils.repeat(INDENT, indent + 1) + "{\"" + name + "\" : " + value + "}," + LF;
        } else {
            return StringUtils.repeat(INDENT, indent + 1) + "{\"" + name + "\" : " + value + "}" + LF;
        }
    }

    private static String getJSONIntEntry(String name, int value, int indent) {
        return getJSONIntEntry(name, value, indent, false);
    }

    /**
     * Data container to collect numbers of test results of TPT test execuiton for build previous
     * builds.
     * 
     * @author FInfantino, PikeTec GmbH
     *
     */
    public static class ResultData {

        /**
         * The total number of TPT test cases
         */
        public int total;

        /**
         * The number of TPT test cases with execution error
         */
        public int error;

        /**
         * The number of failed TPT test cases
         */
        public int failed;

        /**
         * The number of passed TPT test cases
         * 
         */
        public int passed;

        /**
         * The number of inconclusive TPT test cases
         */
        public int inconclusive;

        /**
         * The number of the Jenkins build
         */
        public int buildNummer;
    }

}