Skip to content

Commit

Permalink
Merge pull request #1524 from yrodiere/issue-querycomments
Browse files Browse the repository at this point in the history
Add GHIssue#queryComments, with a since() filter
  • Loading branch information
bitwiseman committed Sep 27, 2022
2 parents 644a1cc + a628b93 commit ea9f4b1
Show file tree
Hide file tree
Showing 201 changed files with 11,655 additions and 1,526 deletions.
14 changes: 13 additions & 1 deletion src/main/java/org/kohsuke/github/GHIssue.java
Expand Up @@ -479,18 +479,30 @@ public List<GHIssueComment> getComments() throws IOException {
}

/**
* Obtains all the comments associated with this issue.
* Obtains all the comments associated with this issue, witout any filter.
*
* @return the paged iterable
* @throws IOException
* the io exception
* @see <a href="https://docs.github.com/en/rest/issues/comments#list-issue-comments">List issue comments</a>
* @see #queryComments() queryComments to apply filters.
*/
public PagedIterable<GHIssueComment> listComments() throws IOException {
return root().createRequest()
.withUrlPath(getIssuesApiRoute() + "/comments")
.toIterable(GHIssueComment[].class, item -> item.wrapUp(this));
}

/**
* Search comments on this issue by specifying filters through a builder pattern.
*
* @return the query builder
* @see <a href="https://docs.github.com/en/rest/issues/comments#list-issue-comments">List issue comments</a>
*/
public GHIssueCommentQueryBuilder queryComments() {
return new GHIssueCommentQueryBuilder(this);
}

@Preview(SQUIRREL_GIRL)
public GHReaction createReaction(ReactionContent content) throws IOException {
return root().createRequest()
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/org/kohsuke/github/GHIssueCommentQueryBuilder.java
@@ -0,0 +1,61 @@
package org.kohsuke.github;

import java.util.Date;

/**
* Builds a query for listing comments on an issue.
* <p>
* Call various methods that set the filter criteria, then the {@link #list()} method to actually retrieve the comments.
*
* <pre>
* GHIssue issue = ...;
* for (GHIssueComment comment : issue.queryComments().since(x).list()) {
* ...
* }
* </pre>
*
* @author Yoann Rodiere
* @see GHIssue#queryComments() GHIssue#queryComments()
* @see <a href="https://docs.github.com/en/rest/issues/comments#list-issue-comments">List issue comments</a>
*/
public class GHIssueCommentQueryBuilder {
private final Requester req;
private final GHIssue issue;

GHIssueCommentQueryBuilder(GHIssue issue) {
this.issue = issue;
this.req = issue.root().createRequest().withUrlPath(issue.getIssuesApiRoute() + "/comments");
}

/**
* Only comments created/updated after this date will be returned.
*
* @param date
* the date
* @return the query builder
*/
public GHIssueCommentQueryBuilder since(Date date) {
req.with("since", GitHubClient.printDate(date));
return this;
}

/**
* Only comments created/updated after this timestamp will be returned.
*
* @param timestamp
* the timestamp
* @return the query builder
*/
public GHIssueCommentQueryBuilder since(long timestamp) {
return since(new Date(timestamp));
}

/**
* Lists up the comments with the criteria added so far.
*
* @return the paged iterable
*/
public PagedIterable<GHIssueComment> list() {
return req.toIterable(GHIssueComment[].class, item -> item.wrapUp(issue));
}
}
266 changes: 266 additions & 0 deletions src/test/java/org/kohsuke/github/GHIssueTest.java
@@ -0,0 +1,266 @@
package org.kohsuke.github;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

/**
* @author Kohsuke Kawaguchi
* @author Yoann Rodiere
*/
public class GHIssueTest extends AbstractGitHubWireMockTest {

@Before
@After
public void cleanUp() throws Exception {
// Cleanup is only needed when proxying
if (!mockGitHub.isUseProxy()) {
return;
}

for (GHIssue issue : getRepository(this.getNonRecordingGitHub()).getIssues(GHIssueState.OPEN)) {
issue.close();
}
}

@Test
public void createIssue() throws Exception {
String name = "createIssue";
GHRepository repo = getRepository();
GHIssue issue = repo.createIssue(name).body("## test").create();
assertThat(issue.getTitle(), equalTo(name));
}

@Test
public void issueComment() throws Exception {
String name = "createIssueComment";
GHIssue issue = getRepository().createIssue(name).body("## test").create();

List<GHIssueComment> comments;
comments = issue.listComments().toList();
assertThat(comments, hasSize(0));
comments = issue.queryComments().list().toList();
assertThat(comments, hasSize(0));

GHIssueComment firstComment = issue.comment("First comment");
Date firstCommentCreatedAt = firstComment.getCreatedAt();
Date firstCommentCreatedAtPlus1Second = Date
.from(firstComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS));

comments = issue.listComments().toList();
assertThat(comments, hasSize(1));
assertThat(comments, contains(hasProperty("body", equalTo("First comment"))));

comments = issue.queryComments().list().toList();
assertThat(comments, hasSize(1));
assertThat(comments, contains(hasProperty("body", equalTo("First comment"))));

// Test "since"
comments = issue.queryComments().since(firstCommentCreatedAt).list().toList();
assertThat(comments, hasSize(1));
assertThat(comments, contains(hasProperty("body", equalTo("First comment"))));
comments = issue.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList();
assertThat(comments, hasSize(0));

// "since" is only precise up to the second,
// so if we want to differentiate comments, we need to be completely sure they're created
// at least 1 second from each other.
// Waiting 2 seconds to avoid edge cases.
Thread.sleep(2000);

GHIssueComment secondComment = issue.comment("Second comment");
Date secondCommentCreatedAt = secondComment.getCreatedAt();
Date secondCommentCreatedAtPlus1Second = Date
.from(secondComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS));
assertThat(
"There's an error in the setup of this test; please fix it."
+ " The second comment should be created at least one second after the first one.",
firstCommentCreatedAtPlus1Second.getTime() <= secondCommentCreatedAt.getTime());

comments = issue.listComments().toList();
assertThat(comments, hasSize(2));
assertThat(comments,
contains(hasProperty("body", equalTo("First comment")),
hasProperty("body", equalTo("Second comment"))));
comments = issue.queryComments().list().toList();
assertThat(comments, hasSize(2));
assertThat(comments,
contains(hasProperty("body", equalTo("First comment")),
hasProperty("body", equalTo("Second comment"))));

// Test "since"
comments = issue.queryComments().since(firstCommentCreatedAt).list().toList();
assertThat(comments, hasSize(2));
assertThat(comments,
contains(hasProperty("body", equalTo("First comment")),
hasProperty("body", equalTo("Second comment"))));
comments = issue.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList();
assertThat(comments, hasSize(1));
assertThat(comments, contains(hasProperty("body", equalTo("Second comment"))));
comments = issue.queryComments().since(secondCommentCreatedAt).list().toList();
assertThat(comments, hasSize(1));
assertThat(comments, contains(hasProperty("body", equalTo("Second comment"))));
comments = issue.queryComments().since(secondCommentCreatedAtPlus1Second).list().toList();
assertThat(comments, hasSize(0));

// Test "since" with timestamp instead of Date
comments = issue.queryComments().since(secondCommentCreatedAt.getTime()).list().toList();
assertThat(comments, hasSize(1));
assertThat(comments, contains(hasProperty("body", equalTo("Second comment"))));
}

@Test
public void closeIssue() throws Exception {
String name = "closeIssue";
GHIssue issue = getRepository().createIssue(name).body("## test").create();
assertThat(issue.getTitle(), equalTo(name));
assertThat(getRepository().getIssue(issue.getNumber()).getState(), equalTo(GHIssueState.OPEN));
issue.close();
assertThat(getRepository().getIssue(issue.getNumber()).getState(), equalTo(GHIssueState.CLOSED));
}

@Test
// Requires push access to the test repo to pass
public void setLabels() throws Exception {
GHIssue issue = getRepository().createIssue("setLabels").body("## test").create();
String label = "setLabels_label_name";
issue.setLabels(label);

Collection<GHLabel> labels = getRepository().getIssue(issue.getNumber()).getLabels();
assertThat(labels.size(), equalTo(1));
GHLabel savedLabel = labels.iterator().next();
assertThat(savedLabel.getName(), equalTo(label));
assertThat(savedLabel.getId(), notNullValue());
assertThat(savedLabel.getNodeId(), notNullValue());
assertThat(savedLabel.isDefault(), is(false));
}

@Test
// Requires push access to the test repo to pass
public void addLabels() throws Exception {
GHIssue issue = getRepository().createIssue("addLabels").body("## test").create();
String addedLabel1 = "addLabels_label_name_1";
String addedLabel2 = "addLabels_label_name_2";
String addedLabel3 = "addLabels_label_name_3";

List<GHLabel> resultingLabels = issue.addLabels(addedLabel1);
assertThat(resultingLabels.size(), equalTo(1));
GHLabel ghLabel = resultingLabels.get(0);
assertThat(ghLabel.getName(), equalTo(addedLabel1));

int requestCount = mockGitHub.getRequestCount();
resultingLabels = issue.addLabels(addedLabel2, addedLabel3);
// multiple labels can be added with one api call
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1));

assertThat(resultingLabels.size(), equalTo(3));
assertThat(resultingLabels,
containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)),
hasProperty("name", equalTo(addedLabel2)),
hasProperty("name", equalTo(addedLabel3))));

// Adding a label which is already present does not throw an error
resultingLabels = issue.addLabels(ghLabel);
assertThat(resultingLabels.size(), equalTo(3));
}

@Test
// Requires push access to the test repo to pass
public void addLabelsConcurrencyIssue() throws Exception {
String addedLabel1 = "addLabelsConcurrencyIssue_label_name_1";
String addedLabel2 = "addLabelsConcurrencyIssue_label_name_2";

GHIssue issue1 = getRepository().createIssue("addLabelsConcurrencyIssue").body("## test").create();
issue1.getLabels();

GHIssue issue2 = getRepository().getIssue(issue1.getNumber());
issue2.addLabels(addedLabel2);

Collection<GHLabel> labels = issue1.addLabels(addedLabel1);

assertThat(labels.size(), equalTo(2));
assertThat(labels,
containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)),
hasProperty("name", equalTo(addedLabel2))));
}

@Test
// Requires push access to the test repo to pass
public void removeLabels() throws Exception {
GHIssue issue = getRepository().createIssue("removeLabels").body("## test").create();
String label1 = "removeLabels_label_name_1";
String label2 = "removeLabels_label_name_2";
String label3 = "removeLabels_label_name_3";
issue.setLabels(label1, label2, label3);

Collection<GHLabel> labels = getRepository().getIssue(issue.getNumber()).getLabels();
assertThat(labels.size(), equalTo(3));
GHLabel ghLabel3 = labels.stream().filter(label -> label3.equals(label.getName())).findFirst().get();

int requestCount = mockGitHub.getRequestCount();
List<GHLabel> resultingLabels = issue.removeLabels(label2, label3);
// each label deleted is a separate api call
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 2));

assertThat(resultingLabels.size(), equalTo(1));
assertThat(resultingLabels.get(0).getName(), equalTo(label1));

// Removing some labels that are not present does not throw
// This is consistent with earlier behavior and with addLabels()
issue.removeLabels(ghLabel3);

// Calling removeLabel() on label that is not present will throw
try {
issue.removeLabel(label3);
fail("Expected GHFileNotFoundException");
} catch (GHFileNotFoundException e) {
assertThat(e.getMessage(), containsString("Label does not exist"));
}
}

@Test
// Requires push access to the test repo to pass
public void setAssignee() throws Exception {
GHIssue issue = getRepository().createIssue("setAssignee").body("## test").create();
GHMyself user = gitHub.getMyself();
issue.assignTo(user);

assertThat(getRepository().getIssue(issue.getNumber()).getAssignee(), equalTo(user));
}

@Test
public void getUserTest() throws IOException {
GHIssue issue = getRepository().createIssue("getUserTest").create();
GHIssue issueSingle = getRepository().getIssue(issue.getNumber());
assertThat(issueSingle.getUser().root(), notNullValue());

PagedIterable<GHIssue> ghIssues = getRepository().listIssues(GHIssueState.OPEN);
for (GHIssue otherIssue : ghIssues) {
assertThat(otherIssue.getUser().root(), notNullValue());
}
}

protected GHRepository getRepository() throws IOException {
return getRepository(gitHub);
}

private GHRepository getRepository(GitHub gitHub) throws IOException {
return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("GHIssueTest");
}

}

0 comments on commit ea9f4b1

Please sign in to comment.