Add support for monitoring issues that are waiting for feedback
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -24,6 +24,10 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package io.spring.issuebot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -54,6 +56,11 @@ public class GitHubProperties {
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The names of the repository's collaborators.
|
||||
*/
|
||||
private List<String> collaborators;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ 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 org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
import io.spring.issuebot.github.GitHubTemplate;
|
||||
@@ -33,6 +34,7 @@ import io.spring.issuebot.github.RegexLinkParser;
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@EnableConfigurationProperties(GitHubProperties.class)
|
||||
public class IssueBotApplication {
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.feedback;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import io.spring.issuebot.GitHubProperties;
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
|
||||
/**
|
||||
* Central configuration for the beans involved in managing issues that are waiting for
|
||||
* feedback.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(FeedbackProperties.class)
|
||||
class FeedbackConfiguration {
|
||||
|
||||
@Bean
|
||||
FeedbackIssueListener feedbackIssueListener(GitHubOperations gitHub,
|
||||
GitHubProperties githubProperties, FeedbackProperties feedbackProperties) {
|
||||
return new FeedbackIssueListener(gitHub, feedbackProperties.getRequiredLabel(),
|
||||
githubProperties.getRepository().getCollaborators(),
|
||||
new StandardFeedbackListener(gitHub,
|
||||
feedbackProperties.getProvidedLabel(),
|
||||
feedbackProperties.getRequiredLabel(),
|
||||
feedbackProperties.getReminderComment(),
|
||||
feedbackProperties.getCloseComment()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.feedback;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.issuebot.IssueListener;
|
||||
import io.spring.issuebot.github.Comment;
|
||||
import io.spring.issuebot.github.Event;
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
import io.spring.issuebot.github.Issue;
|
||||
import io.spring.issuebot.github.Label;
|
||||
import io.spring.issuebot.github.Page;
|
||||
|
||||
/**
|
||||
* An {@link IssueListener} that processes issues that are waiting for feedback.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class FeedbackIssueListener implements IssueListener {
|
||||
|
||||
private final GitHubOperations gitHub;
|
||||
|
||||
private final String labelName;
|
||||
|
||||
private final List<String> collaborators;
|
||||
|
||||
private final FeedbackListener feedbackListener;
|
||||
|
||||
FeedbackIssueListener(GitHubOperations gitHub, String labelName,
|
||||
List<String> collaborators, FeedbackListener feedbackListener) {
|
||||
this.gitHub = gitHub;
|
||||
this.labelName = labelName;
|
||||
this.collaborators = collaborators;
|
||||
this.feedbackListener = feedbackListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpenIssue(Issue issue) {
|
||||
if (waitingForFeedback(issue)) {
|
||||
OffsetDateTime waitingSince = getWaitingSince(issue);
|
||||
if (waitingSince != null) {
|
||||
processWaitingIssue(issue, waitingSince);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processWaitingIssue(Issue issue, OffsetDateTime waitingSince) {
|
||||
if (commentedSince(waitingSince, issue)) {
|
||||
this.feedbackListener.feedbackProvided(issue);
|
||||
}
|
||||
else {
|
||||
this.feedbackListener.feedbackRequired(issue, waitingSince);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean waitingForFeedback(Issue issue) {
|
||||
return issue.getPullRequest() == null && labelledAsWaitingForFeedback(issue);
|
||||
}
|
||||
|
||||
private boolean labelledAsWaitingForFeedback(Issue issue) {
|
||||
for (Label label : issue.getLabels()) {
|
||||
if (this.labelName.equals(label.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private OffsetDateTime getWaitingSince(Issue issue) {
|
||||
OffsetDateTime createdAt = null;
|
||||
Page<Event> page = this.gitHub.getEvents(issue);
|
||||
while (page != null) {
|
||||
for (Event event : page.getContent()) {
|
||||
if (Event.Type.LABELED.equals(event.getType())
|
||||
&& this.labelName.equals(event.getLabel().getName())) {
|
||||
createdAt = event.getCreationTime();
|
||||
}
|
||||
}
|
||||
page = page.next();
|
||||
}
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
private boolean commentedSince(OffsetDateTime waitingForFeedbackSince, Issue issue) {
|
||||
Page<Comment> page = this.gitHub.getComments(issue);
|
||||
while (page != null) {
|
||||
for (Comment comment : page.getContent()) {
|
||||
if (!this.collaborators.contains(comment.getUser().getLogin())
|
||||
&& comment.getCreationTime().isAfter(waitingForFeedbackSince)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
page = page.next();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.feedback;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import io.spring.issuebot.github.Issue;
|
||||
|
||||
/**
|
||||
* A {@code FeedbackListener} is notified when feedback has been provided or is still
|
||||
* required for an {@link Issue}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public interface FeedbackListener {
|
||||
|
||||
/**
|
||||
* Notification that feedback has been provided for the given {@code issue}.
|
||||
*
|
||||
* @param issue the issue
|
||||
*/
|
||||
void feedbackProvided(Issue issue);
|
||||
|
||||
/**
|
||||
* Notification that feedback is still required for the given {@code issue} having
|
||||
* been requested at the given {@code requestTime}.
|
||||
*
|
||||
* @param issue the issue
|
||||
* @param requestTime the time when feedback was requested
|
||||
*/
|
||||
void feedbackRequired(Issue issue, OffsetDateTime requestTime);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.feedback;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
/**
|
||||
* {@link EnableConfigurationProperties Configuration properties} for configuring the
|
||||
* monitoring of issues that require user feedback.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties(prefix = "issuebot.feedback")
|
||||
final class FeedbackProperties {
|
||||
|
||||
/**
|
||||
* Name of the label that is applied when feedback is required.
|
||||
*/
|
||||
private String requiredLabel;
|
||||
|
||||
/**
|
||||
* Name of the label that is applied when feedback has been provided.
|
||||
*/
|
||||
private String providedLabel;
|
||||
|
||||
/**
|
||||
* The text of the comment that is added as a reminder that feedback is required.
|
||||
*/
|
||||
private String reminderComment;
|
||||
|
||||
/**
|
||||
* The text of the comment that is added when an issue is clsed as feedback has not
|
||||
* been provided.
|
||||
*/
|
||||
private String closeComment;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.feedback;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
import io.spring.issuebot.github.Issue;
|
||||
|
||||
/**
|
||||
* Standard implementation of {@link FeedbackListener}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class StandardFeedbackListener implements FeedbackListener {
|
||||
|
||||
private final GitHubOperations gitHub;
|
||||
|
||||
private final String providedLabel;
|
||||
|
||||
private final String requiredLabel;
|
||||
|
||||
private final String reminderComment;
|
||||
|
||||
private final String closeComment;
|
||||
|
||||
StandardFeedbackListener(GitHubOperations gitHub, String providedLabel,
|
||||
String requiredLabel, String reminderComment, String closeComment) {
|
||||
this.gitHub = gitHub;
|
||||
this.providedLabel = providedLabel;
|
||||
this.requiredLabel = requiredLabel;
|
||||
this.reminderComment = reminderComment;
|
||||
this.closeComment = closeComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackProvided(Issue issue) {
|
||||
this.gitHub.addLabel(issue, this.providedLabel);
|
||||
this.gitHub.removeLabel(issue, this.requiredLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackRequired(Issue issue, OffsetDateTime requestTime) {
|
||||
OffsetDateTime now = OffsetDateTime.now();
|
||||
if (requestTime.plusDays(14).isBefore(now)) {
|
||||
close(issue);
|
||||
}
|
||||
else if (requestTime.plusDays(7).isBefore(now)) {
|
||||
remind(issue);
|
||||
}
|
||||
}
|
||||
|
||||
private void close(Issue issue) {
|
||||
this.gitHub.addComment(issue, this.closeComment);
|
||||
this.gitHub.close(issue);
|
||||
}
|
||||
|
||||
private void remind(Issue issue) {
|
||||
this.gitHub.addComment(issue, this.reminderComment);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package io.spring.issuebot.github;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
@@ -30,14 +32,20 @@ public final class Comment {
|
||||
|
||||
private final User user;
|
||||
|
||||
private final OffsetDateTime creationTime;
|
||||
|
||||
/**
|
||||
* Creates a new comment that was authored by the given {@code user}.
|
||||
* Creates a new comment that was authored by the given {@code user} at the given
|
||||
* {@code creationTime}.
|
||||
*
|
||||
* @param user the user
|
||||
* @param creationTime the creation time
|
||||
*/
|
||||
@JsonCreator
|
||||
public Comment(@JsonProperty("user") User user) {
|
||||
public Comment(@JsonProperty("user") User user,
|
||||
@JsonProperty("created_at") OffsetDateTime creationTime) {
|
||||
this.user = user;
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
164
src/main/java/io/spring/issuebot/github/Event.java
Normal file
164
src/main/java/io/spring/issuebot/github/Event.java
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.github;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* An event that has been performed on an {@link Issue}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Getter
|
||||
public class Event {
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final OffsetDateTime creationTime;
|
||||
|
||||
private final Label label;
|
||||
|
||||
/**
|
||||
* Creates a new {@code Event}.
|
||||
*
|
||||
* @param type the type of the event
|
||||
* @param creationTime the timestamp of when the event was created
|
||||
* @param label the label associated with the event
|
||||
*/
|
||||
@JsonCreator
|
||||
public Event(@JsonProperty("event") String type,
|
||||
@JsonProperty("created_at") OffsetDateTime creationTime,
|
||||
@JsonProperty("label") Label label) {
|
||||
this.type = Type.valueFrom(type);
|
||||
this.creationTime = creationTime;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of an {@link Event}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
/**
|
||||
* The issue was closed by the actor.
|
||||
*/
|
||||
CLOSED("closed"),
|
||||
|
||||
/**
|
||||
* The issue was reopened by the actor.
|
||||
*/
|
||||
REOPENED("reopened"),
|
||||
|
||||
/**
|
||||
* The actor subscribed to receive notifications for an issue.
|
||||
*/
|
||||
SUBSCRIBED("subscribed"),
|
||||
|
||||
/**
|
||||
* The issue was merged by the actor.
|
||||
*/
|
||||
MERGED("merged"),
|
||||
|
||||
/**
|
||||
* The issue was referenced from a commit message.
|
||||
*/
|
||||
REFERENCED("referenced"),
|
||||
|
||||
/**
|
||||
* The actor was {@code @mentioned} in an issue body.
|
||||
*/
|
||||
MENTIONED("mentioned"),
|
||||
|
||||
/**
|
||||
* The issue was assigned to the actor.
|
||||
*/
|
||||
ASSIGNED("assigned"),
|
||||
|
||||
/**
|
||||
* The actor was unassigned from the issue.
|
||||
*/
|
||||
UNASSIGNED("unassigned"),
|
||||
|
||||
/**
|
||||
* A label was added to the issue.
|
||||
*/
|
||||
LABELED("labeled"),
|
||||
|
||||
/**
|
||||
* A label was removed from the issue.
|
||||
*/
|
||||
UNLABELED("unlabeled"),
|
||||
|
||||
/**
|
||||
* The issue was added to a milestone.
|
||||
*/
|
||||
MILESTONED("milestoned"),
|
||||
|
||||
/**
|
||||
* The issue was removed from a milestone.
|
||||
*/
|
||||
DEMILESTONED("demilestoned"),
|
||||
|
||||
/**
|
||||
* The issue title was changed.
|
||||
*/
|
||||
RENAMED("renamed"),
|
||||
|
||||
/**
|
||||
* The issue was locked by the actor.
|
||||
*/
|
||||
LOCKED("locked"),
|
||||
|
||||
/**
|
||||
* The issue was unlocked by the actor.
|
||||
*/
|
||||
UNLOCKED("unlocked"),
|
||||
|
||||
/**
|
||||
* The pull request's branch was deleted.
|
||||
*/
|
||||
HEAD_REF_DELETED("head_ref_deleted"),
|
||||
|
||||
/**
|
||||
* The pull request's branch was restored.
|
||||
*/
|
||||
HEAD_REF_RESTORED("head_ref_restored");
|
||||
|
||||
private String type;
|
||||
|
||||
Type(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
static Type valueFrom(String type) {
|
||||
for (Type value : values()) {
|
||||
if (type.equals(value.type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"'" + type + "' is not a valid event type");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,9 +45,43 @@ public interface GitHubOperations {
|
||||
* Adds the given {@code label} to the given {@code issue}.
|
||||
*
|
||||
* @param issue the issue
|
||||
* @param label the label;
|
||||
* @return the updated issue
|
||||
* @param label the label
|
||||
* @return the modified issue
|
||||
*/
|
||||
Issue addLabel(Issue issue, String label);
|
||||
|
||||
/**
|
||||
* Removes the given {@code label} from the given {@code issue}.
|
||||
*
|
||||
* @param issue the issue
|
||||
* @param label the label
|
||||
* @return the modified issue
|
||||
*/
|
||||
Issue removeLabel(Issue issue, String label);
|
||||
|
||||
/**
|
||||
* Adds the given {@code comment} to the given {@code issue}.
|
||||
*
|
||||
* @param issue the issue
|
||||
* @param comment the comment
|
||||
* @return the added comment
|
||||
*/
|
||||
Comment addComment(Issue issue, String comment);
|
||||
|
||||
/**
|
||||
* Closes the given {@code issue}.
|
||||
*
|
||||
* @param issue the issue
|
||||
* @return the modified issue
|
||||
*/
|
||||
Issue close(Issue issue);
|
||||
|
||||
/**
|
||||
* Returns the events that have occurred on the given {@code issue}.
|
||||
*
|
||||
* @param issue the issue
|
||||
* @return the events
|
||||
*/
|
||||
Page<Event> getEvents(Issue issue);
|
||||
|
||||
}
|
||||
|
||||
@@ -73,10 +73,6 @@ public class GitHubTemplate implements GitHubOperations {
|
||||
this.linkParser = linkParser;
|
||||
}
|
||||
|
||||
RestOperations getRestOperations() {
|
||||
return this.rest;
|
||||
}
|
||||
|
||||
static RestTemplate createDefaultRestTemplate(String username, String password) {
|
||||
RestTemplate rest = new RestTemplate();
|
||||
rest.setErrorHandler(new DefaultResponseErrorHandler() {
|
||||
@@ -103,48 +99,83 @@ public class GitHubTemplate implements GitHubOperations {
|
||||
public Page<Issue> getIssues(String organization, String repository) {
|
||||
String url = "https://api.github.com/repos/" + organization + "/" + repository
|
||||
+ "/issues";
|
||||
return getIssues(url);
|
||||
return getPage(url, Issue[].class);
|
||||
}
|
||||
|
||||
private Page<Issue> getIssues(String url) {
|
||||
@Override
|
||||
public Page<Comment> getComments(Issue issue) {
|
||||
return getPage(issue.getCommentsUrl(), Comment[].class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Event> getEvents(Issue issue) {
|
||||
return getPage(issue.getEventsUrl(), Event[].class);
|
||||
}
|
||||
|
||||
private <T> Page<T> getPage(String url, Class<T[]> type) {
|
||||
if (!StringUtils.hasText(url)) {
|
||||
return null;
|
||||
}
|
||||
ResponseEntity<Issue[]> issues = this.rest.getForEntity(url, Issue[].class);
|
||||
return new StandardPage<Issue>(Arrays.asList(issues.getBody()),
|
||||
() -> getIssues(getNextUrl(issues)));
|
||||
ResponseEntity<T[]> contents = this.rest.getForEntity(url, type);
|
||||
return new StandardPage<T>(Arrays.asList(contents.getBody()),
|
||||
() -> getPage(getNextUrl(contents), type));
|
||||
}
|
||||
|
||||
private String getNextUrl(ResponseEntity<?> response) {
|
||||
return this.linkParser.parse(response.getHeaders().getFirst("Link")).get("next");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Comment> getComments(Issue issue) {
|
||||
return getComments(issue.getCommentsUrl());
|
||||
}
|
||||
|
||||
private Page<Comment> getComments(String url) {
|
||||
if (!StringUtils.hasText(url)) {
|
||||
return null;
|
||||
}
|
||||
ResponseEntity<Comment[]> comments = this.rest.getForEntity(url, Comment[].class);
|
||||
return new StandardPage<Comment>(Arrays.asList(comments.getBody()),
|
||||
() -> getComments(getNextUrl(comments)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Issue addLabel(Issue issue, String labelName) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("labels", Arrays.asList(labelName));
|
||||
ResponseEntity<Issue> exchange = this.rest.exchange(
|
||||
ResponseEntity<Label[]> response = this.rest.exchange(
|
||||
new RequestEntity<>(body, HttpMethod.POST, URI.create(issue.getUrl())),
|
||||
Issue.class);
|
||||
if (exchange.getStatusCode() != HttpStatus.OK) {
|
||||
Label[].class);
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
log.warn("Failed to add label to issue. Response status: "
|
||||
+ exchange.getStatusCode());
|
||||
+ response.getStatusCode());
|
||||
}
|
||||
return exchange.getBody();
|
||||
return new Issue(issue.getUrl(), issue.getCommentsUrl(), issue.getEventsUrl(),
|
||||
issue.getLabelsUrl(), issue.getUser(), Arrays.asList(response.getBody()),
|
||||
issue.getMilestone(), issue.getPullRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Issue removeLabel(Issue issue, String labelName) {
|
||||
ResponseEntity<Label[]> response = this.rest.exchange(
|
||||
new RequestEntity<Void>(HttpMethod.DELETE, URI.create(
|
||||
issue.getLabelsUrl().replace("{/name}", "/" + labelName))),
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comment addComment(Issue issue, String comment) {
|
||||
Map<String, String> body = new HashMap<>();
|
||||
body.put("body", comment);
|
||||
return this.rest.postForEntity(issue.getCommentsUrl(), body, Comment.class)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Issue close(Issue issue) {
|
||||
Map<String, String> body = new HashMap<>();
|
||||
body.put("state", "closed");
|
||||
ResponseEntity<Issue> response = this.rest.exchange(
|
||||
new RequestEntity<>(body, HttpMethod.PATCH, URI.create(issue.getUrl())),
|
||||
Issue.class);
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
log.warn("Failed to close issue. Response status: "
|
||||
+ response.getStatusCode());
|
||||
}
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
private static class BasicAuthorizationInterceptor
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -27,38 +28,65 @@ import lombok.Getter;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Getter
|
||||
public class Issue {
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final String url;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final String commentsUrl;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final String eventsUrl;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final String labelsUrl;
|
||||
|
||||
@Getter
|
||||
private final User user;
|
||||
|
||||
private final List<Label> labels;
|
||||
@Getter
|
||||
private List<Label> labels;
|
||||
|
||||
@Getter
|
||||
private final Milestone milestone;
|
||||
|
||||
@Getter
|
||||
private final PullRequest pullRequest;
|
||||
|
||||
/**
|
||||
* Creates a new {@code Issue}.
|
||||
*
|
||||
* @param url the url of the issue in the GitHub API
|
||||
* @param commentsUrl the url of the comments on the issue in the GitHub API
|
||||
* @param eventsUrl the url of the events on the issue in the GitHub API
|
||||
* @param labelsUrl the url of the labels on the issue in the GitHub API
|
||||
* @param user the user that created the 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)
|
||||
*/
|
||||
@JsonCreator
|
||||
public Issue(@JsonProperty("url") String url,
|
||||
@JsonProperty("comments_url") String commentsUrl,
|
||||
@JsonProperty("user") User user, @JsonProperty("labels") List<Label> labels,
|
||||
@JsonProperty("milestone") Milestone milestone) {
|
||||
@JsonProperty("events_url") String eventsUrl,
|
||||
@JsonProperty("labels_url") String labelsUrl, @JsonProperty("user") User user,
|
||||
@JsonProperty("labels") List<Label> labels,
|
||||
@JsonProperty("milestone") Milestone milestone,
|
||||
@JsonProperty("pull_request") PullRequest pullRequest) {
|
||||
this.url = url;
|
||||
this.commentsUrl = commentsUrl;
|
||||
this.eventsUrl = eventsUrl;
|
||||
this.labelsUrl = labelsUrl;
|
||||
this.user = user;
|
||||
this.labels = labels;
|
||||
this.milestone = milestone;
|
||||
this.pullRequest = pullRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
42
src/main/java/io/spring/issuebot/github/PullRequest.java
Normal file
42
src/main/java/io/spring/issuebot/github/PullRequest.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Details of a GitHub pull request.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Getter
|
||||
public class PullRequest {
|
||||
|
||||
private final String url;
|
||||
|
||||
/**
|
||||
* Creates a new {@code PullRequest} that has the given {@code url} in the GitHub API.
|
||||
* @param url the url
|
||||
*/
|
||||
@JsonCreator
|
||||
public PullRequest(@JsonProperty("url") String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,4 +40,9 @@ public class User {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.login;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ final class CommentedByCollaboratorTriageFilter implements TriageFilter {
|
||||
while (page != null) {
|
||||
for (Comment comment : page.getContent()) {
|
||||
if (this.collaborators.contains(comment.getUser().getLogin())) {
|
||||
log.debug("{} has been triaged. It was commented on by {}",
|
||||
issue.getUrl(), comment.getUser().getLogin());
|
||||
log.debug("{} has been triaged. It was commented on by {}", issue,
|
||||
comment.getUser());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ final class LabelledTriageFilter implements TriageFilter {
|
||||
@Override
|
||||
public boolean triaged(Issue issue) {
|
||||
if (issue.getLabels() != null && !issue.getLabels().isEmpty()) {
|
||||
log.debug("{} has been triaged. It has been labelled.", issue.getUrl());
|
||||
log.debug("{} has been triaged. It has been labelled.", issue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -45,8 +45,7 @@ final class OpenedByCollaboratorTriageFilter implements TriageFilter {
|
||||
@Override
|
||||
public boolean triaged(Issue issue) {
|
||||
if (this.collaborators.contains(issue.getUser().getLogin())) {
|
||||
log.debug("{} has been triaged. It was opened by {}", issue.getUrl(),
|
||||
issue.getUser().getLogin());
|
||||
log.debug("{} has been triaged. It was opened by {}", issue, issue.getUser());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -21,8 +21,8 @@ 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.GitHubProperties;
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
|
||||
/**
|
||||
@@ -31,20 +31,20 @@ import io.spring.issuebot.github.GitHubOperations;
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@EnableConfigurationProperties(TriageProperties.class)
|
||||
class TriageConfiguration {
|
||||
|
||||
@Bean
|
||||
TriageIssueListener triageIssueListener(GitHubOperations gitHubOperations,
|
||||
TriageProperties triageProperties) {
|
||||
TriageProperties triageProperties, GitHubProperties gitHubProperties) {
|
||||
return new TriageIssueListener(
|
||||
Arrays.asList(
|
||||
new OpenedByCollaboratorTriageFilter(
|
||||
triageProperties.getCollaborators()),
|
||||
gitHubProperties.getRepository().getCollaborators()),
|
||||
new LabelledTriageFilter(), new MilestoneAppliedTriageFilter(),
|
||||
new CommentedByCollaboratorTriageFilter(
|
||||
triageProperties.getCollaborators(), gitHubOperations)),
|
||||
gitHubProperties.getRepository().getCollaborators(),
|
||||
gitHubOperations)),
|
||||
new LabelApplyingTriageListener(gitHubOperations,
|
||||
triageProperties.getLabel()));
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package io.spring.issuebot.triage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -34,11 +32,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "issuebot.triage")
|
||||
class TriageProperties {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -3,10 +3,20 @@ issuebot:
|
||||
repository:
|
||||
organization: spring-projects
|
||||
name: spring-boot
|
||||
collaborators:
|
||||
- dsyer
|
||||
- philwebb
|
||||
- snicoll
|
||||
- wilkinsona
|
||||
triage:
|
||||
label: waiting-for-triage
|
||||
collaborators:
|
||||
- dsyer
|
||||
- philwebb
|
||||
- snicoll
|
||||
- wilkinsona
|
||||
feedback:
|
||||
required_label: waiting-for-feedback
|
||||
provided_label: feedback-provided
|
||||
reminder_comment: >
|
||||
If you would like us to look at this issue, please provide the requested
|
||||
information. If the information is not provided within the next 7 days this issue
|
||||
will be closed.
|
||||
close_comment: >
|
||||
Closing due to lack of requested feedback. If you would like us to look at this
|
||||
issue, please provide the requested information and we will re-open the issue.
|
||||
|
||||
@@ -57,8 +57,8 @@ public class RepositoryMonitorTests {
|
||||
public void repositoryWithOpenIssues() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<Issue> page = mock(Page.class);
|
||||
Issue issueOne = new Issue(null, null, null, null, null);
|
||||
Issue issueTwo = new Issue(null, null, null, null, null);
|
||||
Issue issueOne = new Issue(null, null, null, null, null, null, null, null);
|
||||
Issue issueTwo = new Issue(null, null, null, null, null, null, null, null);
|
||||
given(page.getContent()).willReturn(Arrays.asList(issueOne, issueTwo));
|
||||
given(this.gitHub.getIssues("test", "test")).willReturn(page);
|
||||
this.repositoryMonitor.monitor();
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.feedback;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import io.spring.issuebot.IssueListener;
|
||||
import io.spring.issuebot.github.Comment;
|
||||
import io.spring.issuebot.github.Event;
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
import io.spring.issuebot.github.Issue;
|
||||
import io.spring.issuebot.github.Label;
|
||||
import io.spring.issuebot.github.PullRequest;
|
||||
import io.spring.issuebot.github.StandardPage;
|
||||
import io.spring.issuebot.github.User;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link FeedbackIssueListener}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FeedbackIssueListenerTests {
|
||||
|
||||
private final GitHubOperations gitHub = mock(GitHubOperations.class);
|
||||
|
||||
private final FeedbackListener feedbackListener = mock(FeedbackListener.class);
|
||||
|
||||
private final IssueListener listener = new FeedbackIssueListener(this.gitHub,
|
||||
"required", Arrays.asList("Amy", "Brian"), this.feedbackListener);
|
||||
|
||||
@Test
|
||||
public void pullRequestsAreIgnored() {
|
||||
Issue issue = new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, new PullRequest("url"));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verifyNoMoreInteractions(this.gitHub, this.feedbackListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuesWithFeedbackRequiredLabelAreIgnored() {
|
||||
Issue issue = new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("something-else")), null, null);
|
||||
this.listener.onOpenIssue(issue);
|
||||
verifyNoMoreInteractions(this.gitHub, this.feedbackListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackRequiredForLabeledIssueWithEvent() {
|
||||
Issue issue = new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, null);
|
||||
OffsetDateTime requestTime = OffsetDateTime.now();
|
||||
given(this.gitHub.getEvents(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Event("labeled", requestTime, new Label("required"))),
|
||||
() -> null));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verify(this.feedbackListener).feedbackRequired(issue, requestTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackProvidedAfterCommentFromNonCollaborator() {
|
||||
Issue issue = new Issue("issue_url", null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, null);
|
||||
OffsetDateTime requestTime = OffsetDateTime.now().minusDays(1);
|
||||
given(this.gitHub.getEvents(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Event("labeled", requestTime, new Label("required"))),
|
||||
() -> null));
|
||||
given(this.gitHub.getComments(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Comment(new User("Charlie"), OffsetDateTime.now())),
|
||||
() -> null));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verify(this.feedbackListener).feedbackProvided(issue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackRequiredAfterCommentFromNonCollaboratorBeforeRequest() {
|
||||
Issue issue = new Issue("issue_url", null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, null);
|
||||
OffsetDateTime requestTime = OffsetDateTime.now().minusDays(1);
|
||||
given(this.gitHub.getEvents(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Event("labeled", requestTime, new Label("required"))),
|
||||
() -> null));
|
||||
given(this.gitHub.getComments(issue)).willReturn(new StandardPage<>(Arrays.asList(
|
||||
new Comment(new User("Charlie"), OffsetDateTime.now().minusDays(2))),
|
||||
() -> null));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verify(this.feedbackListener).feedbackRequired(issue, requestTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackRequiredAfterCommentFromCollaborator() {
|
||||
Issue issue = new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, null);
|
||||
OffsetDateTime requestTime = OffsetDateTime.now().minusDays(1);
|
||||
given(this.gitHub.getEvents(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Event("labeled", requestTime, new Label("required"))),
|
||||
() -> null));
|
||||
given(this.gitHub.getComments(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Comment(new User("Amy"), OffsetDateTime.now())),
|
||||
() -> null));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verify(this.feedbackListener).feedbackRequired(issue, requestTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issueWithNoMatchingLabeledEventIsIgnored() {
|
||||
Issue issue = new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, null);
|
||||
OffsetDateTime requestTime = OffsetDateTime.now();
|
||||
given(this.gitHub.getEvents(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(
|
||||
new Event("labeled", requestTime, new Label("something-else"))),
|
||||
() -> null));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verifyNoMoreInteractions(this.feedbackListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventsWithWrongTypeAreIgnored() {
|
||||
Issue issue = new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("required")), null, null);
|
||||
OffsetDateTime requestTime = OffsetDateTime.now();
|
||||
given(this.gitHub.getEvents(issue)).willReturn(new StandardPage<>(
|
||||
Arrays.asList(new Event("milestoned", requestTime, null)), () -> null));
|
||||
this.listener.onOpenIssue(issue);
|
||||
verifyNoMoreInteractions(this.feedbackListener);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.feedback;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import io.spring.issuebot.github.GitHubOperations;
|
||||
import io.spring.issuebot.github.Issue;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link StandardFeedbackListener}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class StandardFeedbackListenerTests {
|
||||
|
||||
private final GitHubOperations gitHub = mock(GitHubOperations.class);
|
||||
|
||||
private final FeedbackListener listener = new StandardFeedbackListener(this.gitHub,
|
||||
"provided", "required", "reminder", "closing");
|
||||
|
||||
private final Issue issue = new Issue(null, null, null, null, null, null, null, null);
|
||||
|
||||
@Test
|
||||
public void feedbackProvided() {
|
||||
this.listener.feedbackProvided(this.issue);
|
||||
verify(this.gitHub).addLabel(this.issue, "provided");
|
||||
verify(this.gitHub).removeLabel(this.issue, "required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackRequiredButReminderNotYetDue() {
|
||||
this.listener.feedbackRequired(this.issue, OffsetDateTime.now());
|
||||
verifyNoMoreInteractions(this.gitHub);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackRequiredAndReminderDue() {
|
||||
this.listener.feedbackRequired(this.issue, OffsetDateTime.now().minusDays(8));
|
||||
verify(this.gitHub).addComment(this.issue, "reminder");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void feedbackRequiredAndOverdue() {
|
||||
this.listener.feedbackRequired(this.issue, OffsetDateTime.now().minusDays(15));
|
||||
verify(this.gitHub).addComment(this.issue, "closing");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package io.spring.issuebot.github;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Rule;
|
||||
@@ -33,6 +32,7 @@ import org.springframework.test.web.client.response.DefaultResponseCreator;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.equalToIgnoringCase;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
@@ -121,8 +121,8 @@ public class GitHubTemplateTests {
|
||||
this.server.expect(requestTo("commentsUrl")).andExpect(method(HttpMethod.GET))
|
||||
.andExpect(basicAuth())
|
||||
.andRespond(withSuccess("[]", MediaType.APPLICATION_JSON));
|
||||
Page<Comment> comments = this.gitHub
|
||||
.getComments(new Issue(null, "commentsUrl", null, null, null));
|
||||
Page<Comment> comments = this.gitHub.getComments(
|
||||
new Issue(null, "commentsUrl", null, null, null, null, null, null));
|
||||
assertThat(comments.getContent().size(), is(0));
|
||||
assertThat(comments.next(), is(nullValue()));
|
||||
}
|
||||
@@ -132,8 +132,8 @@ public class GitHubTemplateTests {
|
||||
this.server.expect(requestTo("commentsUrl")).andExpect(method(HttpMethod.GET))
|
||||
.andExpect(basicAuth())
|
||||
.andRespond(withResource("comments-page-one.json"));
|
||||
Page<Comment> comments = this.gitHub
|
||||
.getComments(new Issue(null, "commentsUrl", null, null, null));
|
||||
Page<Comment> comments = this.gitHub.getComments(
|
||||
new Issue(null, "commentsUrl", null, null, null, null, null, null));
|
||||
assertThat(comments.getContent().size(), is(17));
|
||||
assertThat(comments.next(), is(nullValue()));
|
||||
}
|
||||
@@ -148,8 +148,8 @@ public class GitHubTemplateTests {
|
||||
this.server.expect(requestTo("page-two")).andExpect(method(HttpMethod.GET))
|
||||
.andExpect(basicAuth())
|
||||
.andRespond(withResource("comments-page-two.json"));
|
||||
Page<Comment> pageOne = this.gitHub
|
||||
.getComments(new Issue(null, "commentsUrl", null, null, null));
|
||||
Page<Comment> pageOne = this.gitHub.getComments(
|
||||
new Issue(null, "commentsUrl", null, null, null, null, null, null));
|
||||
assertThat(pageOne.getContent().size(), is(17));
|
||||
Page<Comment> pageTwo = pageOne.next();
|
||||
assertThat(pageTwo, is(not(nullValue())));
|
||||
@@ -157,25 +157,75 @@ public class GitHubTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addLabelToUnlabelledIssue() {
|
||||
public void addLabelToIssue() {
|
||||
this.server.expect(requestTo("issueUrl")).andExpect(method(HttpMethod.POST))
|
||||
.andExpect(basicAuth())
|
||||
.andExpect(content().string("{\"labels\":[\"test\"]}"))
|
||||
.andRespond(withResource("issue-single-label.json"));
|
||||
Issue issue = new Issue("issueUrl", null, null, Arrays.asList(), null);
|
||||
Issue labelledIssue = this.gitHub.addLabel(issue, "test");
|
||||
assertThat(labelledIssue.getLabels(), hasSize(1));
|
||||
.andExpect(content().string("{\"labels\":[\"test\"]}")).andRespond(
|
||||
withSuccess("[{\"name\":\"test\"}]", MediaType.APPLICATION_JSON));
|
||||
Issue issue = new Issue("issueUrl", null, null, null, null, null, null, null);
|
||||
Issue modifiedIssue = this.gitHub.addLabel(issue, "test");
|
||||
assertThat(modifiedIssue.getLabels(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAdditionalLabelToIssue() {
|
||||
this.server.expect(requestTo("issueUrl")).andExpect(method(HttpMethod.POST))
|
||||
public void removeLabelFromIssue() {
|
||||
this.server.expect(requestTo("labels/test")).andExpect(method(HttpMethod.DELETE))
|
||||
.andExpect(basicAuth())
|
||||
.andExpect(content().string("{\"labels\":[\"test\"]}"))
|
||||
.andRespond(withResource("issue-two-labels.json"));
|
||||
Issue issue = new Issue("issueUrl", null, null, Arrays.asList(), null);
|
||||
Issue labelledIssue = this.gitHub.addLabel(issue, "test");
|
||||
assertThat(labelledIssue.getLabels(), hasSize(2));
|
||||
.andRespond(withSuccess("[]", MediaType.APPLICATION_JSON));
|
||||
Issue issue = new Issue(null, null, null, "labels{/name}", null, null, null,
|
||||
null);
|
||||
Issue modifiedIssue = this.gitHub.removeLabel(issue, "test");
|
||||
assertThat(modifiedIssue.getLabels(), hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCommentToIssue() {
|
||||
this.server.expect(requestTo("commentsUrl")).andExpect(method(HttpMethod.POST))
|
||||
.andExpect(basicAuth())
|
||||
.andExpect(content().string("{\"body\":\"A test comment\"}"))
|
||||
.andRespond(withResource("new-comment.json"));
|
||||
Issue issue = new Issue(null, "commentsUrl", null, null, null, null, null, null);
|
||||
Comment comment = this.gitHub.addComment(issue, "A test comment");
|
||||
assertThat(comment, is(not(nullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePageOfEvents() {
|
||||
this.server.expect(requestTo("eventsUrl")).andExpect(method(HttpMethod.GET))
|
||||
.andExpect(basicAuth()).andRespond(withResource("events-page-one.json"));
|
||||
Page<Event> events = this.gitHub.getEvents(
|
||||
new Issue(null, null, "eventsUrl", null, null, null, null, null));
|
||||
assertThat(events.getContent().size(), is(12));
|
||||
assertThat(events.next(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiplePagesOfEvents() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Link", "<page-two>; rel=\"next\"");
|
||||
this.server.expect(requestTo("eventsUrl")).andExpect(method(HttpMethod.GET))
|
||||
.andExpect(basicAuth()).andRespond(withResource("events-page-one.json",
|
||||
"Link:<page-two>; rel=\"next\""));
|
||||
this.server.expect(requestTo("page-two")).andExpect(method(HttpMethod.GET))
|
||||
.andExpect(basicAuth()).andRespond(withResource("events-page-two.json"));
|
||||
Page<Event> pageOne = this.gitHub.getEvents(
|
||||
new Issue(null, null, "eventsUrl", null, null, null, null, null));
|
||||
assertThat(pageOne.getContent().size(), is(12));
|
||||
Page<Event> pageTwo = pageOne.next();
|
||||
assertThat(pageTwo, is(not(nullValue())));
|
||||
assertThat(pageTwo.getContent().size(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeIssue() {
|
||||
this.server.expect(requestTo("issueUrl")).andExpect(method(HttpMethod.PATCH))
|
||||
.andExpect(basicAuth())
|
||||
.andExpect(content().string("{\"state\":\"closed\"}"))
|
||||
.andRespond(withSuccess("{\"url\":\"updatedIssueUrl\"}",
|
||||
MediaType.APPLICATION_JSON));
|
||||
Issue closedIssue = this.gitHub
|
||||
.close(new Issue("issueUrl", null, null, null, null, null, null, null));
|
||||
assertThat(closedIssue.getUrl(), is(equalTo("updatedIssueUrl")));
|
||||
}
|
||||
|
||||
private DefaultResponseCreator withResource(String resource, String... headers) {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class CommentedByCollaboratorTriageFilterTests {
|
||||
private final TriageFilter filter = new CommentedByCollaboratorTriageFilter(
|
||||
Arrays.asList("Adam", "Brenda", "Charlie"), this.gitHub);
|
||||
|
||||
private final Issue issue = new Issue(null, null, null, null, null);
|
||||
private final Issue issue = new Issue(null, null, null, null, null, null, null, null);
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -61,7 +61,7 @@ public class CommentedByCollaboratorTriageFilterTests {
|
||||
public void noCommentsByCollaborators() {
|
||||
Page<Comment> pageOne = mock(Page.class);
|
||||
given(pageOne.getContent())
|
||||
.willReturn(Arrays.asList(new Comment(new User("Debbie"))));
|
||||
.willReturn(Arrays.asList(new Comment(new User("Debbie"), null)));
|
||||
given(this.gitHub.getComments(this.issue)).willReturn(pageOne);
|
||||
assertThat(this.filter.triaged(this.issue), is(false));
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class CommentedByCollaboratorTriageFilterTests {
|
||||
public void commentByCollaboratorOnFirstPage() {
|
||||
Page<Comment> pageOne = mock(Page.class);
|
||||
given(pageOne.getContent())
|
||||
.willReturn(Arrays.asList(new Comment(new User("Brenda"))));
|
||||
.willReturn(Arrays.asList(new Comment(new User("Brenda"), null)));
|
||||
given(this.gitHub.getComments(this.issue)).willReturn(pageOne);
|
||||
assertThat(this.filter.triaged(this.issue), is(true));
|
||||
}
|
||||
@@ -81,10 +81,10 @@ public class CommentedByCollaboratorTriageFilterTests {
|
||||
public void commentByCollaboratorOnLaterPage() {
|
||||
Page<Comment> pageOne = mock(Page.class);
|
||||
given(pageOne.getContent())
|
||||
.willReturn(Arrays.asList(new Comment(new User("Debbie"))));
|
||||
.willReturn(Arrays.asList(new Comment(new User("Debbie"), null)));
|
||||
Page<Comment> pageTwo = mock(Page.class);
|
||||
given(pageTwo.getContent())
|
||||
.willReturn(Arrays.asList(new Comment(new User("Brenda"))));
|
||||
.willReturn(Arrays.asList(new Comment(new User("Brenda"), null)));
|
||||
given(pageOne.next()).willReturn(pageTwo);
|
||||
given(this.gitHub.getComments(this.issue)).willReturn(pageOne);
|
||||
assertThat(this.filter.triaged(this.issue), is(true));
|
||||
|
||||
@@ -38,7 +38,7 @@ public class LabelApplyingTriageListenerTests {
|
||||
|
||||
@Test
|
||||
public void requiresTriage() {
|
||||
Issue issue = new Issue(null, null, null, null, null);
|
||||
Issue issue = new Issue(null, null, null, null, null, null, null, null);
|
||||
this.listener.requiresTriage(issue);
|
||||
verify(this.gitHub).addLabel(issue, "test");
|
||||
}
|
||||
|
||||
@@ -38,23 +38,22 @@ public class LabelledTriageFilterTests {
|
||||
|
||||
@Test
|
||||
public void issueWithLabels() {
|
||||
assertThat(this.filter.triaged(
|
||||
new Issue(null, null, null, Arrays.asList(new Label("test")), null)),
|
||||
is(true));
|
||||
assertThat(this.filter.triaged(new Issue(null, null, null, null, null,
|
||||
Arrays.asList(new Label("test")), null, null)), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issueWithNullLabels() {
|
||||
assertThat(this.filter.triaged(new Issue(null, null, null, null, null)),
|
||||
assertThat(
|
||||
this.filter.triaged(
|
||||
new Issue(null, null, null, null, null, null, null, null)),
|
||||
is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issueWithNoLabels() {
|
||||
assertThat(
|
||||
this.filter.triaged(
|
||||
new Issue(null, null, null, Collections.emptyList(), null)),
|
||||
is(false));
|
||||
assertThat(this.filter.triaged(new Issue(null, null, null, null, null,
|
||||
Collections.emptyList(), null, null)), is(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,15 +36,15 @@ public class MilestoneAppliedTriageFilterTests {
|
||||
|
||||
@Test
|
||||
public void issueWithMilestoneApplied() {
|
||||
assertThat(
|
||||
this.filter.triaged(
|
||||
new Issue(null, null, null, null, new Milestone("test"))),
|
||||
is(true));
|
||||
assertThat(this.filter.triaged(new Issue(null, null, null, null, null, null,
|
||||
new Milestone("test"), null)), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issueWithNoMilestoneApplied() {
|
||||
assertThat(this.filter.triaged(new Issue(null, null, null, null, null)),
|
||||
assertThat(
|
||||
this.filter.triaged(
|
||||
new Issue(null, null, null, null, null, null, null, null)),
|
||||
is(false));
|
||||
}
|
||||
|
||||
|
||||
@@ -38,16 +38,15 @@ public class OpenedByCollaboratorTriageFilterTests {
|
||||
|
||||
@Test
|
||||
public void openedByCollaborator() {
|
||||
assertThat(
|
||||
this.filter.triaged(new Issue(null, null, new User("Adam"), null, null)),
|
||||
assertThat(this.filter.triaged(
|
||||
new Issue(null, null, null, null, new User("Adam"), null, null, null)),
|
||||
is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void openedByAnotherUser() {
|
||||
assertThat(
|
||||
this.filter
|
||||
.triaged(new Issue(null, null, new User("Debbie"), null, null)),
|
||||
assertThat(this.filter.triaged(
|
||||
new Issue(null, null, null, null, new User("Debbie"), null, null, null)),
|
||||
is(false));
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class TriageIssueListenerTests {
|
||||
|
||||
@Test
|
||||
public void listenerIsCalledWhenIssueRequiresTriage() {
|
||||
Issue issue = new Issue(null, null, null, null, null);
|
||||
Issue issue = new Issue(null, null, null, null, null, null, null, null);
|
||||
given(this.triageFilterOne.triaged(issue)).willReturn(false);
|
||||
given(this.triageFilterTwo.triaged(issue)).willReturn(false);
|
||||
this.issueListener.onOpenIssue(issue);
|
||||
@@ -57,7 +57,7 @@ public class TriageIssueListenerTests {
|
||||
|
||||
@Test
|
||||
public void listenerIsNotCalledWhenIssueHasAlreadyBeenTriaged() {
|
||||
Issue issue = new Issue(null, null, null, null, null);
|
||||
Issue issue = new Issue(null, null, null, null, null, null, null, null);
|
||||
given(this.triageFilterOne.triaged(issue)).willReturn(true);
|
||||
this.issueListener.onOpenIssue(issue);
|
||||
verify(this.triageFilterOne).triaged(issue);
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
[
|
||||
{
|
||||
"id": 465099546,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/465099546",
|
||||
"actor": {
|
||||
"login": "rwinch",
|
||||
"id": 362503,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/362503?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/rwinch",
|
||||
"html_url": "https://github.com/rwinch",
|
||||
"followers_url": "https://api.github.com/users/rwinch/followers",
|
||||
"following_url": "https://api.github.com/users/rwinch/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/rwinch/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/rwinch/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/rwinch/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/rwinch/orgs",
|
||||
"repos_url": "https://api.github.com/users/rwinch/repos",
|
||||
"events_url": "https://api.github.com/users/rwinch/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/rwinch/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "mentioned",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-16T06:53:18Z"
|
||||
},
|
||||
{
|
||||
"id": 465099547,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/465099547",
|
||||
"actor": {
|
||||
"login": "rwinch",
|
||||
"id": 362503,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/362503?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/rwinch",
|
||||
"html_url": "https://github.com/rwinch",
|
||||
"followers_url": "https://api.github.com/users/rwinch/followers",
|
||||
"following_url": "https://api.github.com/users/rwinch/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/rwinch/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/rwinch/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/rwinch/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/rwinch/orgs",
|
||||
"repos_url": "https://api.github.com/users/rwinch/repos",
|
||||
"events_url": "https://api.github.com/users/rwinch/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/rwinch/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "subscribed",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-16T06:53:18Z"
|
||||
},
|
||||
{
|
||||
"id": 465559518,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/465559518",
|
||||
"actor": {
|
||||
"login": "cemo",
|
||||
"id": 44156,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44156?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/cemo",
|
||||
"html_url": "https://github.com/cemo",
|
||||
"followers_url": "https://api.github.com/users/cemo/followers",
|
||||
"following_url": "https://api.github.com/users/cemo/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/cemo/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/cemo/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/cemo/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/cemo/orgs",
|
||||
"repos_url": "https://api.github.com/users/cemo/repos",
|
||||
"events_url": "https://api.github.com/users/cemo/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/cemo/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "mentioned",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-16T15:39:55Z"
|
||||
},
|
||||
{
|
||||
"id": 465559520,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/465559520",
|
||||
"actor": {
|
||||
"login": "cemo",
|
||||
"id": 44156,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44156?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/cemo",
|
||||
"html_url": "https://github.com/cemo",
|
||||
"followers_url": "https://api.github.com/users/cemo/followers",
|
||||
"following_url": "https://api.github.com/users/cemo/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/cemo/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/cemo/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/cemo/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/cemo/orgs",
|
||||
"repos_url": "https://api.github.com/users/cemo/repos",
|
||||
"events_url": "https://api.github.com/users/cemo/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/cemo/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "subscribed",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-16T15:39:55Z"
|
||||
},
|
||||
{
|
||||
"id": 466343988,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/466343988",
|
||||
"actor": {
|
||||
"login": "rwinch",
|
||||
"id": 362503,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/362503?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/rwinch",
|
||||
"html_url": "https://github.com/rwinch",
|
||||
"followers_url": "https://api.github.com/users/rwinch/followers",
|
||||
"following_url": "https://api.github.com/users/rwinch/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/rwinch/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/rwinch/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/rwinch/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/rwinch/orgs",
|
||||
"repos_url": "https://api.github.com/users/rwinch/repos",
|
||||
"events_url": "https://api.github.com/users/rwinch/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/rwinch/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "mentioned",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-17T06:35:03Z"
|
||||
},
|
||||
{
|
||||
"id": 466343989,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/466343989",
|
||||
"actor": {
|
||||
"login": "rwinch",
|
||||
"id": 362503,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/362503?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/rwinch",
|
||||
"html_url": "https://github.com/rwinch",
|
||||
"followers_url": "https://api.github.com/users/rwinch/followers",
|
||||
"following_url": "https://api.github.com/users/rwinch/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/rwinch/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/rwinch/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/rwinch/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/rwinch/orgs",
|
||||
"repos_url": "https://api.github.com/users/rwinch/repos",
|
||||
"events_url": "https://api.github.com/users/rwinch/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/rwinch/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "subscribed",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-17T06:35:03Z"
|
||||
},
|
||||
{
|
||||
"id": 473947325,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/473947325",
|
||||
"actor": {
|
||||
"login": "philwebb",
|
||||
"id": 519772,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/519772?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/philwebb",
|
||||
"html_url": "https://github.com/philwebb",
|
||||
"followers_url": "https://api.github.com/users/philwebb/followers",
|
||||
"following_url": "https://api.github.com/users/philwebb/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/philwebb/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/philwebb/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/philwebb/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/philwebb/orgs",
|
||||
"repos_url": "https://api.github.com/users/philwebb/repos",
|
||||
"events_url": "https://api.github.com/users/philwebb/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/philwebb/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "milestoned",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-25T00:07:11Z",
|
||||
"milestone": {
|
||||
"title": "1.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 473947414,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/473947414",
|
||||
"actor": {
|
||||
"login": "philwebb",
|
||||
"id": 519772,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/519772?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/philwebb",
|
||||
"html_url": "https://github.com/philwebb",
|
||||
"followers_url": "https://api.github.com/users/philwebb/followers",
|
||||
"following_url": "https://api.github.com/users/philwebb/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/philwebb/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/philwebb/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/philwebb/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/philwebb/orgs",
|
||||
"repos_url": "https://api.github.com/users/philwebb/repos",
|
||||
"events_url": "https://api.github.com/users/philwebb/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/philwebb/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "labeled",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-25T00:07:21Z",
|
||||
"label": {
|
||||
"name": "for team discussion",
|
||||
"color": "006b75"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 474642336,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/474642336",
|
||||
"actor": {
|
||||
"login": "philwebb",
|
||||
"id": 519772,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/519772?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/philwebb",
|
||||
"html_url": "https://github.com/philwebb",
|
||||
"followers_url": "https://api.github.com/users/philwebb/followers",
|
||||
"following_url": "https://api.github.com/users/philwebb/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/philwebb/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/philwebb/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/philwebb/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/philwebb/orgs",
|
||||
"repos_url": "https://api.github.com/users/philwebb/repos",
|
||||
"events_url": "https://api.github.com/users/philwebb/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/philwebb/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "unlabeled",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-25T15:41:39Z",
|
||||
"label": {
|
||||
"name": "for team discussion",
|
||||
"color": "006b75"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 474642710,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/474642710",
|
||||
"actor": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "labeled",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-11-25T15:41:57Z",
|
||||
"label": {
|
||||
"name": "enhancement",
|
||||
"color": "84b6eb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 479204419,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/479204419",
|
||||
"actor": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "assigned",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-12-01T17:13:26Z",
|
||||
"assignee": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 479248577,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/479248577",
|
||||
"actor": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "closed",
|
||||
"commit_id": "524a32879fea9179e9ba51c2c3a4ce031cdf8a13",
|
||||
"commit_url": "https://api.github.com/repos/spring-projects/spring-boot/commits/524a32879fea9179e9ba51c2c3a4ce031cdf8a13",
|
||||
"created_at": "2015-12-01T17:53:40Z"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,83 @@
|
||||
[
|
||||
{
|
||||
"id": 480011364,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/480011364",
|
||||
"actor": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "reopened",
|
||||
"commit_id": null,
|
||||
"commit_url": null,
|
||||
"created_at": "2015-12-02T10:27:17Z"
|
||||
},
|
||||
{
|
||||
"id": 480034760,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/480034760",
|
||||
"actor": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "closed",
|
||||
"commit_id": "f47449c800011fbd6844774be12d0a381d1bd020",
|
||||
"commit_url": "https://api.github.com/repos/spring-projects/spring-boot/commits/f47449c800011fbd6844774be12d0a381d1bd020",
|
||||
"created_at": "2015-12-02T10:52:07Z"
|
||||
},
|
||||
{
|
||||
"id": 480202795,
|
||||
"url": "https://api.github.com/repos/spring-projects/spring-boot/issues/events/480202795",
|
||||
"actor": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"event": "referenced",
|
||||
"commit_id": "ffd6e8d7eb944d8a3613e40e6aa1f6aa57127046",
|
||||
"commit_url": "https://api.github.com/repos/spring-projects/spring-boot/commits/ffd6e8d7eb944d8a3613e40e6aa1f6aa57127046",
|
||||
"created_at": "2015-12-02T14:02:24Z"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"url": "https://api.github.com/repos/wilkinsona/issue-bot-test/issues/comments/162885552",
|
||||
"html_url": "https://github.com/wilkinsona/issue-bot-test/issues/1#issuecomment-162885552",
|
||||
"issue_url": "https://api.github.com/repos/wilkinsona/issue-bot-test/issues/1",
|
||||
"id": 162885552,
|
||||
"user": {
|
||||
"login": "wilkinsona",
|
||||
"id": 914682,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/wilkinsona",
|
||||
"html_url": "https://github.com/wilkinsona",
|
||||
"followers_url": "https://api.github.com/users/wilkinsona/followers",
|
||||
"following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/wilkinsona/orgs",
|
||||
"repos_url": "https://api.github.com/users/wilkinsona/repos",
|
||||
"events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/wilkinsona/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"created_at": "2015-12-08T13:53:45Z",
|
||||
"updated_at": "2015-12-08T13:53:45Z",
|
||||
"body": "A test comment"
|
||||
}
|
||||
Reference in New Issue
Block a user