Restructure code to make it less triage-specific

This commit is contained in:
Andy Wilkinson
2015-12-07 15:38:16 +00:00
parent c0c4e2d901
commit d19f043c41
36 changed files with 414 additions and 438 deletions

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.issuebot;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
* {@link ConfigurationProperties} for connecting to GitHub.
*
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "issuebot.github")
@Getter
@Setter
public class GitHubProperties {
@NestedConfigurationProperty
private Repository repository = new Repository();
@NestedConfigurationProperty
private Credentials credentials = new Credentials();
/**
* Configuration for a GitHub repository.
*/
@Getter
@Setter
public static class Repository {
/**
* The name of the organization that owns the repository.
*/
private String organization;
/**
* The name of the repository.
*/
private String name;
}
/**
* Configuration for the credentials used to authenticate with GitHub.
*/
@Getter
@Setter
public static class Credentials {
/**
* The username used for authentication with GitHub.
*/
private String username;
/**
* The password used for authentication with GitHub.
*/
private String password;
}
}

View File

@@ -16,8 +16,16 @@
package io.spring.issuebot;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import io.spring.issuebot.github.GitHubOperations;
import io.spring.issuebot.github.GitHubTemplate;
import io.spring.issuebot.github.RegexLinkParser;
/**
* Main class for launching Issue Bot.
@@ -25,10 +33,27 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author Andy Wilkinson
*/
@SpringBootApplication
@EnableConfigurationProperties(GitHubProperties.class)
public class IssueBotApplication {
public static void main(String[] args) {
SpringApplication.run(IssueBotApplication.class, args);
}
@Bean
GitHubTemplate gitHubTemplate(GitHubProperties gitHubProperties) {
return new GitHubTemplate(gitHubProperties.getCredentials().getUsername(),
gitHubProperties.getCredentials().getPassword(), new RegexLinkParser());
}
@Bean
RepositoryMonitor repositoryMonitor(GitHubOperations gitHub,
GitHubProperties gitHubProperties, List<IssueListener> issueListeners) {
return new RepositoryMonitor(gitHub,
new MonitoredRepository(
gitHubProperties.getRepository().getOrganization(),
gitHubProperties.getRepository().getName()),
issueListeners);
}
}

View File

@@ -14,25 +14,22 @@
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
package io.spring.issuebot;
import io.spring.issuebot.triage.MonitoredRepository;
import io.spring.issuebot.github.Issue;
/**
* {@code TriageFilters} is used to retrieve a {@code TriageFilter} for a particular
* {@link MonitoredRepository repository}.
* An {@code IssueListener} is notified of issues found during repository monitoring.
*
* @author Andy Wilkinson
*/
public interface TriageFilters {
public interface IssueListener {
/**
* Returns the {@code TriageFilter} that should be used to process issues for the
* given {@code repository}.
* Notification that the given {@code issue} is open.
*
* @param repository the repository
* @return the filter for the repository
* @param issue the open issue
*/
TriageFilter filterForRepository(MonitoredRepository repository);
void onOpenIssue(Issue issue);
}

View File

@@ -14,40 +14,28 @@
* limitations under the License.
*/
package io.spring.issuebot.triage;
import java.util.List;
package io.spring.issuebot;
import lombok.Getter;
import lombok.Setter;
import lombok.RequiredArgsConstructor;
/**
* A repository that should be monitored.
*
* @author Andy Wilkinson
*/
@RequiredArgsConstructor
@Getter
@Setter
public class MonitoredRepository {
/**
* The name of the organization that owns the repository.
*/
private String organization;
private final String organization;
/**
* The name of the repository.
*/
private String name;
/**
* The names of the project collaborators whose issues do not require triage.
*/
private List<String> collaborators;
/**
* The name of the label that should be applied to issues that are waiting for triage.
*/
private String label;
private final String name;
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.spring.issuebot.triage;
package io.spring.issuebot;
import java.util.List;
@@ -25,12 +25,9 @@ import org.springframework.scheduling.annotation.Scheduled;
import io.spring.issuebot.github.GitHubOperations;
import io.spring.issuebot.github.Issue;
import io.spring.issuebot.github.Page;
import io.spring.issuebot.triage.filter.TriageFilter;
import io.spring.issuebot.triage.filter.TriageFilters;
/**
* Central class for monitoring the configured repositories and labeling issues as waiting
* for triage.
* Central class for monitoring the configured repository.
*
* @author Andy Wilkinson
*/
@@ -38,44 +35,35 @@ class RepositoryMonitor {
private static final Logger log = LoggerFactory.getLogger(RepositoryMonitor.class);
private final List<MonitoredRepository> repositoryConfigurations;
private final TriageFilters filters;
private final GitHubOperations gitHub;
private final TriageListener listener;
private final MonitoredRepository repository;
RepositoryMonitor(GitHubOperations gitHub, TriageFilters filters,
TriageListener listener, List<MonitoredRepository> repositoryConfigurations) {
private final List<IssueListener> issueListeners;
RepositoryMonitor(GitHubOperations gitHub, MonitoredRepository repository,
List<IssueListener> issueListeners) {
this.gitHub = gitHub;
this.filters = filters;
this.listener = listener;
this.repositoryConfigurations = repositoryConfigurations;
this.repository = repository;
this.issueListeners = issueListeners;
}
@Scheduled(fixedRate = 5 * 60 * 1000)
void monitor() {
for (MonitoredRepository configuration : this.repositoryConfigurations) {
monitor(configuration);
}
}
private void monitor(MonitoredRepository repository) {
log.info("Monitoring {}/{}", repository.getOrganization(), repository.getName());
TriageFilter filter = this.filters.filterForRepository(repository);
Page<Issue> page = this.gitHub.getIssues(repository.getOrganization(),
repository.getName());
log.info("Monitoring {}/{}", this.repository.getOrganization(),
this.repository.getName());
Page<Issue> page = this.gitHub.getIssues(this.repository.getOrganization(),
this.repository.getName());
while (page != null) {
for (Issue issue : page.getContent()) {
if (!filter.triaged(issue)) {
this.listener.requiresTriage(issue, repository);
for (IssueListener issueListener : this.issueListeners) {
issueListener.onOpenIssue(issue);
}
}
page = page.next();
}
log.info("Monitoring of {}/{} completed", repository.getOrganization(),
repository.getName());
log.info("Monitoring of {}/{} completed", this.repository.getOrganization(),
this.repository.getName());
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
package io.spring.issuebot.triage;
import java.util.Collections;
import java.util.List;
@@ -28,8 +28,8 @@ import io.spring.issuebot.github.Issue;
import io.spring.issuebot.github.Page;
/**
* A {@code TriageFilter} that considers an issue has having been triaged if a
* collaborator has commented on it.
* A {@code TriageFilter} that considers an issue as having been triaged if a collaborator
* has commented on it.
*
* @author Andy Wilkinson
*/

View File

@@ -24,23 +24,28 @@ import io.spring.issuebot.github.Issue;
*
* @author Andy Wilkinson
*/
public class LabelApplyingTriageListener implements TriageListener {
final class LabelApplyingTriageListener implements TriageListener {
private final GitHubOperations gitHub;
private final String label;
/**
* Creates a new {@code LabelApplyingTriageListener} that will use the given
* {@code gitHubOperations} to apply a label to any issues that require triage.
* {@code gitHubOperations} to apply the given {@code label} to any issues that
* require triage.
*
* @param gitHubOperations the GitHubOperations
* @param label the label
*/
public LabelApplyingTriageListener(GitHubOperations gitHubOperations) {
LabelApplyingTriageListener(GitHubOperations gitHubOperations, String label) {
this.gitHub = gitHubOperations;
this.label = label;
}
@Override
public void requiresTriage(Issue issue, MonitoredRepository repository) {
this.gitHub.addLabel(issue, repository.getLabel());
public void requiresTriage(Issue issue) {
this.gitHub.addLabel(issue, this.label);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
package io.spring.issuebot.triage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
package io.spring.issuebot.triage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,7 +27,7 @@ import io.spring.issuebot.github.Issue;
*
* @author Andy Wilkinson
*/
public class MilestoneAppliedTriageFilter implements TriageFilter {
final class MilestoneAppliedTriageFilter implements TriageFilter {
private static final Logger log = LoggerFactory.getLogger(LabelledTriageFilter.class);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
package io.spring.issuebot.triage;
import java.util.Collections;
import java.util.List;

View File

@@ -16,13 +16,14 @@
package io.spring.issuebot.triage;
import java.util.Arrays;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import io.spring.issuebot.github.GitHubTemplate;
import io.spring.issuebot.github.RegexLinkParser;
import io.spring.issuebot.triage.filter.StandardTriageFilters;
import io.spring.issuebot.github.GitHubOperations;
/**
* Central configuration for the beans involved in identifying issues that require triage.
@@ -31,39 +32,20 @@ import io.spring.issuebot.triage.filter.StandardTriageFilters;
*/
@Configuration
@EnableScheduling
@EnableConfigurationProperties(TriageProperties.class)
class TriageConfiguration {
@Bean
TriageProperties triageProperties() {
return new TriageProperties();
TriageIssueListener triageIssueListener(GitHubOperations gitHubOperations,
TriageProperties triageProperties) {
return new TriageIssueListener(
Arrays.asList(
new OpenedByCollaboratorTriageFilter(
triageProperties.getCollaborators()),
new LabelledTriageFilter(), new MilestoneAppliedTriageFilter(),
new CommentedByCollaboratorTriageFilter(
triageProperties.getCollaborators(), gitHubOperations)),
new LabelApplyingTriageListener(gitHubOperations,
triageProperties.getLabel()));
}
@Bean
LabelApplyingTriageListener triageListener() {
return new LabelApplyingTriageListener(gitHubTemplate());
}
@Bean
RepositoryMonitor repositoryMonitor() {
return new RepositoryMonitor(gitHubTemplate(), triageFilters(), triageListener(),
triageProperties().getRepositories());
}
@Bean
GitHubTemplate gitHubTemplate() {
TriageProperties triageProperties = triageProperties();
return new GitHubTemplate(triageProperties.getUsername(),
triageProperties.getPassword(), linkParser());
}
@Bean
StandardTriageFilters triageFilters() {
return new StandardTriageFilters(gitHubTemplate());
}
@Bean
RegexLinkParser linkParser() {
return new RegexLinkParser();
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
package io.spring.issuebot.triage;
import io.spring.issuebot.github.Issue;
@@ -23,7 +23,7 @@ import io.spring.issuebot.github.Issue;
*
* @author Andy Wilkinson
*/
public interface TriageFilter {
interface TriageFilter {
/**
* Returns {@code true} if the given issue has already been triaged, otherwise

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.issuebot.triage;
import java.util.List;
import io.spring.issuebot.IssueListener;
import io.spring.issuebot.github.Issue;
/**
* {@link IssueListener} that identifies open issues that require triage.
*
* @author Andy Wilkinson
*/
final class TriageIssueListener implements IssueListener {
private final List<TriageFilter> triageFilters;
private final TriageListener triageListener;
/**
* Creates a new {@code TriageIssueListener} that will use the given
* {@code triageFilters} to identify issues that require triage and notify the given
* {@code triageListener} of those that do.
*
* @param triageFilters the triage filters
* @param triageListener the triage listener
*/
TriageIssueListener(List<TriageFilter> triageFilters, TriageListener triageListener) {
this.triageFilters = triageFilters;
this.triageListener = triageListener;
}
/*
* (non-Javadoc)
*
* @see io.spring.issuebot.IssueListener#onOpenIssue(io.spring.issuebot.github.Issue)
*/
@Override
public void onOpenIssue(Issue issue) {
if (requiresTriage(issue)) {
this.triageListener.requiresTriage(issue);
}
}
private boolean requiresTriage(Issue issue) {
for (TriageFilter filter : this.triageFilters) {
if (filter.triaged(issue)) {
return false;
}
}
return true;
}
}

View File

@@ -23,13 +23,12 @@ import io.spring.issuebot.github.Issue;
*
* @author Andy Wilkinson
*/
public interface TriageListener {
interface TriageListener {
/**
* Notification that the given {@code issue} requires triage.
*
* @param issue the issue
* @param repository the monitored repository to which the issue belongs
*/
void requiresTriage(Issue issue, MonitoredRepository repository);
void requiresTriage(Issue issue);
}

View File

@@ -32,21 +32,16 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
@Getter
@Setter
@ConfigurationProperties(prefix = "issuebot.triage")
public class TriageProperties {
class TriageProperties {
/**
* Repositories that will be monitored.
* The names of the project collaborators whose issues do not require triage.
*/
private List<MonitoredRepository> repositories;
private List<String> collaborators;
/**
* GitHub username.
* The name of the label that should be applied to issues that are waiting for triage.
*/
private String username;
/**
* GitHub password.
*/
private String password;
private String label;
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.spring.issuebot.github.Issue;
/**
* A {@link TriageFilter} that delegates to one or more filters.
*
* @author Andy Wilkinson
*/
final class DelegatingTriageFilter implements TriageFilter {
private static final Logger log = LoggerFactory
.getLogger(DelegatingTriageFilter.class);
private final List<TriageFilter> filters;
DelegatingTriageFilter(List<TriageFilter> filters) {
this.filters = filters;
}
@Override
public boolean triaged(Issue issue) {
for (TriageFilter filter : this.filters) {
if (filter.triaged(issue)) {
return true;
}
}
log.info("{} is waiting for triage", issue.getUrl());
return false;
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.issuebot.triage.filter;
import java.util.Arrays;
import io.spring.issuebot.github.GitHubOperations;
import io.spring.issuebot.triage.MonitoredRepository;
/**
* Standard implementation of {@code TriageFilters}.
*
* @author Andy Wilkinson
*/
public class StandardTriageFilters implements TriageFilters {
private final GitHubOperations gitHub;
/**
* Creates a new {@code StandardTriageFilters} that will use the given
* {@code gitHubOperations} to interact with GitHub.
*
* @param gitHubOperations the GitHubOperations
*/
public StandardTriageFilters(GitHubOperations gitHubOperations) {
this.gitHub = gitHubOperations;
}
@Override
public TriageFilter filterForRepository(MonitoredRepository repository) {
return new DelegatingTriageFilter(Arrays.asList(
new OpenedByCollaboratorTriageFilter(repository.getCollaborators()),
new LabelledTriageFilter(), new MilestoneAppliedTriageFilter(),
new CommentedByCollaboratorTriageFilter(repository.getCollaborators(),
this.gitHub)));
}
}