diff --git a/src/main/java/io/spring/issuebot/IssueBotApplication.java b/src/main/java/io/spring/issuebot/IssueBotApplication.java index 1320697..d28f46a 100644 --- a/src/main/java/io/spring/issuebot/IssueBotApplication.java +++ b/src/main/java/io/spring/issuebot/IssueBotApplication.java @@ -18,7 +18,6 @@ import java.util.List; -import io.spring.issuebot.github.GitHubOperations; import io.spring.issuebot.github.GitHubTemplate; import io.spring.issuebot.github.RegexLinkParser; @@ -49,11 +48,10 @@ GitHubTemplate gitHubTemplate(GitHubProperties gitHubProperties) { } @Bean - RepositoryMonitor repositoryMonitor(GitHubOperations gitHub, - MonitoringProperties monitoringProperties, - List issueListeners) { - return new RepositoryMonitor(gitHub, monitoringProperties.getRepositories(), - issueListeners); + RepositoryMonitor repositoryMonitor(MonitoringProperties monitoringProperties, + List repositoryListeners) { + return new RepositoryMonitor(monitoringProperties.getRepositories(), + repositoryListeners); } } diff --git a/src/main/java/io/spring/issuebot/IssueMonitor.java b/src/main/java/io/spring/issuebot/IssueMonitor.java new file mode 100644 index 0000000..315f1cc --- /dev/null +++ b/src/main/java/io/spring/issuebot/IssueMonitor.java @@ -0,0 +1,45 @@ +package io.spring.issuebot; + +import io.spring.issuebot.github.Issue; +import io.spring.issuebot.github.Page; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class IssueMonitor { + + private static final Logger log = LoggerFactory.getLogger(IssueMonitor.class); + + private final List issueListeners; + + public IssueMonitor(List issueListeners) { + this.issueListeners = issueListeners; + } + + public void monitorIssue(Page page, IssueOperation issueOperation) { + while (page != null) { + for (Issue issue : page.getContent()) { + for (IssueListener issueListener : this.issueListeners) { + try { + issueOperation.run(issue, issueListener); + } + catch (Exception ex) { + log.warn("Listener '{}' failed when handling issue '{}'", + issueListener, issue, ex); + } + } + } + page = page.next(); + } + } + + @FunctionalInterface + public interface IssueOperation { + + void run(Issue issue, IssueListener issueListener); + + } + +} diff --git a/src/main/java/io/spring/issuebot/RepositoryListener.java b/src/main/java/io/spring/issuebot/RepositoryListener.java new file mode 100644 index 0000000..7ab043b --- /dev/null +++ b/src/main/java/io/spring/issuebot/RepositoryListener.java @@ -0,0 +1,14 @@ +package io.spring.issuebot; + +/** + * A {@code RepositoryListener} is called when monitoring a repository + */ +public interface RepositoryListener { + + /** + * Handle the specific repository + * @param repository the repository + */ + void handle(Repository repository); + +} diff --git a/src/main/java/io/spring/issuebot/RepositoryMonitor.java b/src/main/java/io/spring/issuebot/RepositoryMonitor.java index 290c6e4..117675a 100644 --- a/src/main/java/io/spring/issuebot/RepositoryMonitor.java +++ b/src/main/java/io/spring/issuebot/RepositoryMonitor.java @@ -17,13 +17,8 @@ package io.spring.issuebot; import java.util.List; - -import io.spring.issuebot.github.GitHubOperations; -import io.spring.issuebot.github.Issue; -import io.spring.issuebot.github.Page; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.scheduling.annotation.Scheduled; /** @@ -35,52 +30,27 @@ class RepositoryMonitor { private static final Logger log = LoggerFactory.getLogger(RepositoryMonitor.class); - private final GitHubOperations gitHub; - private final List repositories; - private final List issueListeners; + private final List repositoryListeners; - RepositoryMonitor(GitHubOperations gitHub, List repositories, - List issueListeners) { - this.gitHub = gitHub; + RepositoryMonitor(List repositories, + List repositoryListeners) { this.repositories = repositories; - this.issueListeners = issueListeners; + this.repositoryListeners = repositoryListeners; } @Scheduled(fixedRate = 5 * 60 * 1000) void monitor() { for (Repository repository : this.repositories) { - monitor(repository); - } - } - - private void monitor(Repository repository) { - log.info("Monitoring {}/{}", repository.getOrganization(), repository.getName()); - try { - Page page = this.gitHub.getIssues(repository.getOrganization(), + log.info("Monitoring {}/{}", repository.getOrganization(), repository.getName()); - while (page != null) { - for (Issue issue : page.getContent()) { - for (IssueListener issueListener : this.issueListeners) { - try { - issueListener.onOpenIssue(repository, issue); - } - catch (Exception ex) { - log.warn("Listener '{}' failed when handling issue '{}'", - issueListener, issue, ex); - } - } - } - page = page.next(); + for (RepositoryListener repositoryListener : repositoryListeners) { + repositoryListener.handle(repository); } + log.info("Monitoring of {}/{} completed", repository.getOrganization(), + repository.getName()); } - catch (Exception ex) { - log.warn("A failure occurred during monitoring of {}/{}", - repository.getOrganization(), repository.getName(), ex); - } - log.info("Monitoring of {}/{} completed", repository.getOrganization(), - repository.getName()); } } diff --git a/src/main/java/io/spring/issuebot/github/ClosedIssuesListener.java b/src/main/java/io/spring/issuebot/github/ClosedIssuesListener.java new file mode 100644 index 0000000..1c739cf --- /dev/null +++ b/src/main/java/io/spring/issuebot/github/ClosedIssuesListener.java @@ -0,0 +1,39 @@ +package io.spring.issuebot.github; + +import io.spring.issuebot.IssueMonitor; +import io.spring.issuebot.Repository; +import io.spring.issuebot.RepositoryListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class ClosedIssuesListener implements RepositoryListener { + + private static final Logger log = LoggerFactory.getLogger(ClosedIssuesListener.class); + + private final GitHubOperations gitHub; + + private final IssueMonitor issueMonitor; + + public ClosedIssuesListener(GitHubOperations gitHub, IssueMonitor issueMonitor) { + this.issueMonitor = issueMonitor; + this.gitHub = gitHub; + } + + @Override + public void handle(Repository repository) { + try { + Page page = this.gitHub.getClosedIssuesWithLabel( + repository.getOrganization(), repository.getName(), + "status: waiting-for-triage"); + issueMonitor.monitorIssue(page, (issue, issueListener) -> issueListener + .onIssueClosure(repository, issue)); + } + catch (Exception ex) { + log.warn("A failure occurred during monitoring of {}/{}", + repository.getOrganization(), repository.getName(), ex); + } + } + +} diff --git a/src/main/java/io/spring/issuebot/github/GitHubOperations.java b/src/main/java/io/spring/issuebot/github/GitHubOperations.java index b7e19d7..f38ae8d 100644 --- a/src/main/java/io/spring/issuebot/github/GitHubOperations.java +++ b/src/main/java/io/spring/issuebot/github/GitHubOperations.java @@ -32,6 +32,17 @@ public interface GitHubOperations { */ Page getIssues(String organization, String repository); + /** + * Returns the open closed issues in the {@code repository} owned by the given + * {@code organization} with the given label. + * @param organization the name of the organization + * @param repository the name of the repository + * @param label the label the issues need to have + * @return the issues + */ + Page getClosedIssuesWithLabel(String organization, String repository, + String label); + /** * Returns the comments that have been made on the given {@code issue}. * @param issue the issue diff --git a/src/main/java/io/spring/issuebot/github/GitHubTemplate.java b/src/main/java/io/spring/issuebot/github/GitHubTemplate.java index acd5e19..0ec8cee 100644 --- a/src/main/java/io/spring/issuebot/github/GitHubTemplate.java +++ b/src/main/java/io/spring/issuebot/github/GitHubTemplate.java @@ -113,6 +113,14 @@ public Page getIssues(String organization, String repository) { return getPage(url, Issue[].class); } + @Override + public Page getClosedIssuesWithLabel(String organization, String repository, + String label) { + String url = "https://api.github.com/repos/" + organization + "/" + repository + + "/issues?state=closed&labels=" + label; + return getPage(url, Issue[].class); + } + @Override public Page getComments(Issue issue) { return getPage(issue.getCommentsUrl(), Comment[].class); @@ -149,7 +157,7 @@ public Issue addLabel(Issue issue, String labelName) { } return new Issue(issue.getUrl(), issue.getCommentsUrl(), issue.getEventsUrl(), issue.getLabelsUrl(), issue.getUser(), Arrays.asList(response.getBody()), - issue.getMilestone(), issue.getPullRequest()); + issue.getMilestone(), issue.getPullRequest(), issue.getState()); } @Override @@ -161,17 +169,17 @@ public Issue removeLabel(Issue issue, String labelName) { catch (URISyntaxException ex) { throw new RuntimeException(ex); } - ResponseEntity response = this.rest.exchange( - new RequestEntity(HttpMethod.DELETE, URI.create( - issue.getLabelsUrl().replace("{/name}", "/" + encodedName))), - Label[].class); + URI uri = URI.create(issue.getLabelsUrl().replace("{/name}", "/" + encodedName)); + log.info("Removing label {} on {}", labelName, uri); + ResponseEntity response = this.rest + .exchange(new RequestEntity(HttpMethod.DELETE, uri), Label[].class); if (response.getStatusCode() != HttpStatus.OK) { log.warn("Failed to remove label from issue. Response status: " + response.getStatusCode()); } return new Issue(issue.getUrl(), issue.getCommentsUrl(), issue.getEventsUrl(), issue.getLabelsUrl(), issue.getUser(), Arrays.asList(response.getBody()), - issue.getMilestone(), issue.getPullRequest()); + issue.getMilestone(), issue.getPullRequest(), issue.getState()); } @Override diff --git a/src/main/java/io/spring/issuebot/github/Issue.java b/src/main/java/io/spring/issuebot/github/Issue.java index f81d11a..58b4256 100644 --- a/src/main/java/io/spring/issuebot/github/Issue.java +++ b/src/main/java/io/spring/issuebot/github/Issue.java @@ -44,6 +44,8 @@ public class Issue { private final PullRequest pullRequest; + private final String state; + /** * Creates a new {@code Issue}. * @param url the url of the issue in the GitHub API @@ -54,6 +56,7 @@ public class Issue { * @param labels the labels applied to the issue * @param milestone the milestone applied to the issue * @param pullRequest details of the pull request (if this issue is a pull request) + * @param state the state of the issue, open or closed */ @JsonCreator public Issue(@JsonProperty("url") String url, @@ -62,7 +65,8 @@ public Issue(@JsonProperty("url") String url, @JsonProperty("labels_url") String labelsUrl, @JsonProperty("user") User user, @JsonProperty("labels") List