All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dk.hlyh.ciplugins.projecthealth.ProjectHealthProjectAction Maven / Gradle / Ivy

The newest version!
package dk.hlyh.ciplugins.projecthealth;

import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.model.Run;
import hudson.tasks.junit.CaseResult;
import hudson.tasks.test.AbstractTestResultAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.kohsuke.stapler.Stapler;

/**
 * Shows statistics about project health and which test cases fail the most.
 * 
 * @author Henrik Lynggaard Hansen 
 */
public final class ProjectHealthProjectAction implements Action {

    private static final Logger LOG = Logger.getLogger(ProjectHealthProjectAction.class.getName());

    private static class CountComparator implements Comparator {

        public CountComparator() {
        }

        public int compare(TestFailure o1, TestFailure o2) {
            return o2.getCount() - o1.getCount();
        }
    }
    // instance members
    private final AbstractProject project;
    private final Hudson hudson;

    public ProjectHealthProjectAction(AbstractProject project) {
        super();
        this.project = project;
        this.hudson = Hudson.getInstance();
    }

    public String getIconFileName() {
        return "graph.gif";
    }

    public String getDisplayName() {
        return "Project Health";
    }

    public String getUrlName() {
        return "project-health";
    }

    public final AbstractProject getProject() {
        return project;
    }

    public ProjectHealthState getState() {
        ProjectHealthState state = new ProjectHealthState();
        state.setAllBuilds(project.getBuilds());
        
        parseInput(state);
        setBuildLimit(state);
        calculateTestFailures(state);
        calculateGraphs(state);

        return state;
    }

    /**
     * Parses the input from the form and sets the parsed information in the state object.
     */
    private void parseInput(ProjectHealthState state) {
        // set options
        String optionParam = Stapler.getCurrentRequest().getParameter("option");
        String numOfBuildsParam = Stapler.getCurrentRequest().getParameter("num");
        String firstBuildParam = Stapler.getCurrentRequest().getParameter("firstBuild");
        String oldestBuildParam = Stapler.getCurrentRequest().getParameter("oldestBuild");
        String groupOnErrorMessageParam = Stapler.getCurrentRequest().getParameter("groupOnErrorMsg");
        
        LOG.fine("Project Health plugin called with: optionParam" + optionParam + 
                ", numOfBuildsParam=" + numOfBuildsParam + ", firstBuildParam=" + firstBuildParam +
                ", oldestBuildParam" + oldestBuildParam + ", groupOnErrorMessageParam=" + groupOnErrorMessageParam);


        // option
        ProjectHealthState.OptionValue option = null;
        try {
            option = ProjectHealthState.OptionValue.valueOf(optionParam);
        } catch (IllegalArgumentException ex) {
            option = ProjectHealthState.OptionValue.All;
        } catch (NullPointerException ex) {
            option = ProjectHealthState.OptionValue.All;
        }
        state.setOptionParameter(option);

        //recent build
        if (numOfBuildsParam != null && !numOfBuildsParam.trim().isEmpty()) {
            try {
                state.setRecentBuildsParameter(Integer.parseInt(numOfBuildsParam));
            } catch (NumberFormatException e) {
                state.setOptionParameter(ProjectHealthState.OptionValue.All);
            }
        }

        //period
        if (firstBuildParam != null && !firstBuildParam.trim().isEmpty() && oldestBuildParam != null && !oldestBuildParam.trim().isEmpty()) {
            try {
                long first = Long.parseLong(firstBuildParam);
                long last = Long.parseLong(oldestBuildParam);

                if (last > first) {
                    long tmp = last;
                    last = first;
                    first = tmp;
                }
                //make search inclusive
                first += 1;
                last -= 1;
                state.setFirstBuildParameter(first);
                state.setLastBuildParameter(last);
            } catch (NumberFormatException e) {
                state.setOptionParameter(ProjectHealthState.OptionValue.All);
            }
        }
        state.setGroupOnErrorMessageParameter(Boolean.parseBoolean(groupOnErrorMessageParam));
        
        LOG.fine("Project Health plugin called with (parsed): optionParam"+state.getOptionParameter() +
                ", numOfBuildsParam=" + state.getRecentBuildsParameter() + ", firstBuildParam=" + state.getFirstBuildParameter() +
                ", oldestBuildParam" + state.getLastBuildParameter() + ", groupOnErrorMessageParam="+state.isGroupOnErrorMessageParameter());        
    }

    /**
     * Calculate the set of selected builds.
     */
    private void setBuildLimit(ProjectHealthState state) {

        List> selectedBuilds;
        switch (state.getOptionParameter()) {
            case Recent:
                if (state.getRecentBuildsParameter() > 0 && state.getRecentBuildsParameter() < state.getAllBuilds().size()) {
                    selectedBuilds = (List>) state.getAllBuilds().subList(0, state.getRecentBuildsParameter());
                } else {
                    selectedBuilds = (List>) state.getAllBuilds();
                    state.setOptionParameter(ProjectHealthState.OptionValue.All);
                }
                break;
            case Period:
                selectedBuilds = (List>) state.getAllBuilds().byTimestamp(state.getLastBuildParameter(), state.getFirstBuildParameter());
                break;
            case All:
                selectedBuilds = (List>) state.getAllBuilds();
                break;
            default:
                selectedBuilds = (List>) state.getAllBuilds();
                break;
        }
        state.setTotalBuilds(selectedBuilds.size());
        state.setSelectedBuilds(selectedBuilds);
        state.setFirstSelectedBuild(selectedBuilds.get(0));
        state.setLastSelectedBuild(selectedBuilds.get(selectedBuilds.size() - 1));
    }

    private void calculateTestFailures(ProjectHealthState state) {
        Map resultMap = new HashMap();
        int failureCount = 0;
        int successCount = 0;
        int testcaseFailures = 0;
        boolean groupByErrorMessage = state.isGroupOnErrorMessageParameter();


        for (Run build : state.getSelectedBuilds()) {
            // skip building jobs
            if (build.isBuilding()) {
                continue;
            }

            if (build.getResult().isWorseThan(Result.SUCCESS)) {
                failureCount++;
                testcaseFailures = testcaseFailures + getFailuresForRun(groupByErrorMessage, resultMap, build);
            } else {
                successCount++;
            }

        }
        state.setFailedBuilds(failureCount);
        state.setGoodBuilds(successCount);
        state.setTotalFailures(testcaseFailures);
        List failures = new ArrayList(resultMap.values());
        Collections.sort(failures, new CountComparator());
        state.setFailures(failures);
    }

    private int getFailuresForRun(boolean groupByErrorMessage, Map failures, Run build) {
        AbstractTestResultAction tests = build.getAction(AbstractTestResultAction.class);
        int testFailures = 0;
        if (tests == null) {
            return 0;
        }
        List failedTests = tests.getFailedTests();

        for (CaseResult result : failedTests) {

            String name = result.getFullName();
            if (groupByErrorMessage) {
                name = name + ": " + result.getErrorDetails();
            }

            TestFailure failure = failures.get(name);
            if (failure == null) {
                failure = new TestFailure();
                failure.setTestcase(name);
                failure.setCount(0);
                failures.put(name, failure);
            }
            failure.setCount(failure.getCount() + 1);
            testFailures += 1;
        }
        return testFailures;
    }

    private void calculateGraphs(ProjectHealthState state) {
        state.setOverviewPie(getOverviewPie(state));
        state.setFailuresPie(getFailurePie(state));
    }    
    
    public String getOverviewPie(ProjectHealthState state) {
        long successPct = (state.getGoodBuilds() * 100) / state.getSelectedBuilds().size();
        long failedPct = (state.getFailedBuilds() * 100) / state.getSelectedBuilds().size();

        String result = "https://chart.googleapis.com/chart?cht=p&chs=250x100";
        result += "&chd=t:" + successPct + "," + failedPct;
        result += "&chl=Success|Failure";
        result += "&chco=729FCF,EF2929";
        result += "&chtt=Project%20health";
        return result;
    }

    public String getFailurePie(ProjectHealthState state) {
        StringBuilder result = new StringBuilder(1024);
        StringBuilder legends = new StringBuilder(1024);
        StringBuilder datas = new StringBuilder(1024);


        boolean first = true;
        for (TestFailure failure : state.getFailures()) {
            long failurePct = (failure.getCount() * 100) / state.getTotalFailures();
            String shortName = failure.getTestcase().substring(failure.getTestcase().lastIndexOf(".") + 1);
            if (first) {
                legends.append(shortName);
                datas.append(failurePct);
            } else {
                legends.append("|").append(shortName);
                datas.append(",").append(failurePct);
            }
            first = false;
        }
        result.append("https://chart.googleapis.com/chart?cht=p&chs=850x300");
        result.append("&chd=t:").append(datas);
        result.append("&chl=").append(legends);
        result.append("&chtt=Test+Failures&chco=EF2929");
        return result.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy