org.jenkinsci.plugins.githubissues.GitHubIssueNotifier.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.plugins.githubissues.GitHubIssueNotifier.java

Source

/**
 * Copyright (c) 2016-present, Daniel Lo Nigro (Daniel15)
 * All rights reserved.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 */
package org.jenkinsci.plugins.githubissues;

import com.cloudbees.jenkins.GitHubRepositoryName;
import com.coravy.hudson.plugins.github.GithubProjectProperty;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.*;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.githubissues.exceptions.GitHubRepositoryException;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHRepository;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.PrintStream;

/**
 * Notifier that creates GitHub issues when builds fail, and automatically closes the issue once the build starts
 * passing again.
 */
public class GitHubIssueNotifier extends Notifier implements SimpleBuildStep {
    private String issueTitle;
    private String issueBody;
    private String issueLabel;
    private String issueRepo;
    private boolean issueReopen = true;
    private boolean issueAppend = true;

    /**
     * Initialises the {@link GitHubIssueNotifier} instance.
     * @param issueTitle the issue title
     * @param issueBody  the issue body
     * @param issueLabel the issue label
     * @param issueRepo the issue repo
     * @param issueReopen reopen the issue
     * @param issueAppend append to existing issue
     */
    @DataBoundConstructor
    public GitHubIssueNotifier(String issueTitle, String issueBody, String issueLabel, String issueRepo,
            boolean issueReopen, boolean issueAppend) {
        this.issueTitle = issueTitle;
        this.issueBody = issueBody;
        this.issueLabel = issueLabel;
        this.issueRepo = issueRepo;
        this.issueReopen = issueReopen;
        this.issueAppend = issueAppend;
    }

    @Override
    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }

    @Override
    public GitHubIssueNotifier.DescriptorImpl getDescriptor() {
        return (GitHubIssueNotifier.DescriptorImpl) super.getDescriptor();
    }

    /**
     * Gets the GitHub repository for the specified job.
     * @param job The job
     * @return The GitHub repository
     * @throws GitHubRepositoryException when the GitHub repository can not be loaded
     */
    public GHRepository getRepoForJob(Job<?, ?> job) throws GitHubRepositoryException {
        final String repoUrl;
        if (StringUtils.isNotBlank(this.issueRepo)) {
            repoUrl = this.issueRepo;
        } else {
            GithubProjectProperty foo = job.getProperty(GithubProjectProperty.class);
            repoUrl = foo.getProjectUrlStr();
        }
        GitHubRepositoryName repoName = GitHubRepositoryName.create(repoUrl);
        if (repoName == null) {
            throw new GitHubRepositoryException("GitHub project not configured");
        }
        GHRepository repo = repoName.resolveOne();
        if (repo == null) {
            throw new GitHubRepositoryException(
                    "Could not connect to GitHub repository. Please double-check that you have correctly configured a "
                            + "GitHub API key.");
        }
        return repo;
    }

    @Override
    public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher,
            @Nonnull TaskListener listener) throws InterruptedException, IOException {
        PrintStream logger = listener.getLogger();

        // If we got here, we need to grab the repo to create an issue (or close an existing issue)
        GHRepository repo;
        try {
            repo = getRepoForJob(run.getParent());
        } catch (GitHubRepositoryException ex) {
            logger.println(
                    "WARNING: No GitHub config available for this job, GitHub Issue Notifier will not run! Error: "
                            + ex.getMessage());
            return;
        }

        Result result = run.getResult();
        final GitHubIssueAction previousGitHubIssueAction = getLatestIssueAction((Build) run.getPreviousBuild());
        GHIssue issue = null;
        if (previousGitHubIssueAction != null) {
            issue = repo.getIssue(previousGitHubIssueAction.getIssueNumber());
        }

        if (result == Result.FAILURE) {
            if (issue != null) {
                String issueBody = this.getIssueBody();
                if (StringUtils.isBlank(issueBody)) {
                    issueBody = this.getDescriptor().getIssueBody();
                }
                if (issue.getState() == GHIssueState.OPEN) {
                    if (issueAppend) {
                        //CONTINUE
                        issue.comment(IssueCreator.formatText(issueBody, run, listener, workspace));
                        logger.format("GitHub Issue Notifier: Build is still failing and issue #%s already exists. "
                                + "Not sending anything to GitHub issues%n", issue.getNumber());
                    }
                    run.addAction(new GitHubIssueAction(issue, GitHubIssueAction.TransitionAction.CONTINUE));
                } else if (issue.getState() == GHIssueState.CLOSED) {
                    if (issueReopen) {
                        // REOPEN
                        logger.format(
                                "GitHub Issue Notifier: Build has started failing again, reopend GitHub issue #%s%n",
                                issue.getNumber());
                        issue.reopen();
                        issue.comment(IssueCreator.formatText(issueBody, run, listener, workspace));
                        //set new labels
                        if (issueLabel != null && !issueLabel.isEmpty()) {
                            issue.setLabels(issueLabel.split(",| "));
                        }
                        run.addAction(new GitHubIssueAction(issue, GitHubIssueAction.TransitionAction.REOPEN));
                    } else {
                        //CREATE NEW
                        issue = IssueCreator.createIssue(run, this, repo, listener, workspace);
                        logger.format("GitHub Issue Notifier: Build has started failing, filed GitHub issue #%s%n",
                                issue.getNumber());
                        run.addAction(new GitHubIssueAction(issue, GitHubIssueAction.TransitionAction.OPEN));
                    }
                }
            } else {
                // CREATE NEW
                issue = IssueCreator.createIssue(run, this, repo, listener, workspace);
                logger.format("GitHub Issue Notifier: Build has started failing, filed GitHub issue #%s%n",
                        issue.getNumber());
                run.addAction(new GitHubIssueAction(issue, GitHubIssueAction.TransitionAction.OPEN));
            }
        } else if (result == Result.SUCCESS && issue != null && issue.getState() == GHIssueState.OPEN) {
            issue.comment("Build was fixed!");
            issue.close();
            logger.format("GitHub Issue Notifier: Build was fixed, closing GitHub issue #%s%n", issue.getNumber());
            run.addAction(new GitHubIssueAction(issue, GitHubIssueAction.TransitionAction.CLOSE));
        }
    }

    private GitHubIssueAction getLatestIssueAction(Build previousBuild) {
        if (previousBuild != null) {
            GitHubIssueAction previousGitHubIssueAction = previousBuild.getAction(GitHubIssueAction.class);
            if (previousGitHubIssueAction != null) {
                return previousGitHubIssueAction;
            } else {
                return this.getLatestIssueAction((Build) previousBuild.getPreviousBuild());
            }
        }
        return null;
    }

    /**
     * Returns the issue title.
     *
     * @return the issue title
     */
    public String getIssueTitle() {
        return issueTitle;
    }

    /**
     * Returns the issue body.
     *
     * @return the issue body
     */
    public String getIssueBody() {
        return issueBody;
    }

    /**
     * Returns the issue label.
     *
     * @return the issue label
     */
    public String getIssueLabel() {
        return issueLabel;
    }

    public boolean isIssueReopen() {
        return issueReopen;
    }

    public boolean isIssueAppend() {
        return issueAppend;
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
        private String issueTitle = "$JOB_NAME $BUILD_DISPLAY_NAME failed";
        private String issueBody = "Build '$JOB_NAME' is failing!\n\n" + "Last 50 lines of build output:\n\n"
                + "```\n" + "${BUILD_LOG, maxLines=50}\n" + "```\n\n" + "Changes since last successful build:\n"
                + "${CHANGES_SINCE_LAST_SUCCESS, format=\"%c\", changesFormat=\"- [%a] %r - %m\\n\"}\n\n"
                + "[View full output]($BUILD_URL)";
        private String issueLabel;

        public DescriptorImpl() {
            load();
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
            issueTitle = formData.getString("issueTitle");
            issueBody = formData.getString("issueBody");
            issueLabel = formData.getString("issueLabel");
            save();
            return super.configure(req, formData);
        }

        /**
         * Title of the issue to create on GitHub
         *
         * @return issueTitle
         */
        public String getIssueTitle() {
            return issueTitle;
        }

        /**
         * Body of the issue to create on GitHub
         *
         * @return issueBody
         */
        public String getIssueBody() {
            return issueBody;
        }

        /**
         * Label to use for the issues created on GitHub.
         *
         * @return issueLabel
         */
        public String getIssueLabel() {
            return issueLabel;
        }

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            return true;
        }

        @Override
        public String getDisplayName() {
            return "Create GitHub issue on failure";
        }
    }
}