Skip to content

Commit ea9f4b1

Browse files
authored
Merge pull request #1524 from yrodiere/issue-querycomments
Add GHIssue#queryComments, with a since() filter
2 parents 644a1cc + a628b93 commit ea9f4b1

File tree

201 files changed

+11655
-1526
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

201 files changed

+11655
-1526
lines changed

src/main/java/org/kohsuke/github/GHIssue.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,18 +479,30 @@ public List<GHIssueComment> getComments() throws IOException {
479479
}
480480

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

496+
/**
497+
* Search comments on this issue by specifying filters through a builder pattern.
498+
*
499+
* @return the query builder
500+
* @see <a href="https://docs.github.com/en/rest/issues/comments#list-issue-comments">List issue comments</a>
501+
*/
502+
public GHIssueCommentQueryBuilder queryComments() {
503+
return new GHIssueCommentQueryBuilder(this);
504+
}
505+
494506
@Preview(SQUIRREL_GIRL)
495507
public GHReaction createReaction(ReactionContent content) throws IOException {
496508
return root().createRequest()
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.kohsuke.github;
2+
3+
import java.util.Date;
4+
5+
/**
6+
* Builds a query for listing comments on an issue.
7+
* <p>
8+
* Call various methods that set the filter criteria, then the {@link #list()} method to actually retrieve the comments.
9+
*
10+
* <pre>
11+
* GHIssue issue = ...;
12+
* for (GHIssueComment comment : issue.queryComments().since(x).list()) {
13+
* ...
14+
* }
15+
* </pre>
16+
*
17+
* @author Yoann Rodiere
18+
* @see GHIssue#queryComments() GHIssue#queryComments()
19+
* @see <a href="https://docs.github.com/en/rest/issues/comments#list-issue-comments">List issue comments</a>
20+
*/
21+
public class GHIssueCommentQueryBuilder {
22+
private final Requester req;
23+
private final GHIssue issue;
24+
25+
GHIssueCommentQueryBuilder(GHIssue issue) {
26+
this.issue = issue;
27+
this.req = issue.root().createRequest().withUrlPath(issue.getIssuesApiRoute() + "/comments");
28+
}
29+
30+
/**
31+
* Only comments created/updated after this date will be returned.
32+
*
33+
* @param date
34+
* the date
35+
* @return the query builder
36+
*/
37+
public GHIssueCommentQueryBuilder since(Date date) {
38+
req.with("since", GitHubClient.printDate(date));
39+
return this;
40+
}
41+
42+
/**
43+
* Only comments created/updated after this timestamp will be returned.
44+
*
45+
* @param timestamp
46+
* the timestamp
47+
* @return the query builder
48+
*/
49+
public GHIssueCommentQueryBuilder since(long timestamp) {
50+
return since(new Date(timestamp));
51+
}
52+
53+
/**
54+
* Lists up the comments with the criteria added so far.
55+
*
56+
* @return the paged iterable
57+
*/
58+
public PagedIterable<GHIssueComment> list() {
59+
return req.toIterable(GHIssueComment[].class, item -> item.wrapUp(issue));
60+
}
61+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
package org.kohsuke.github;
2+
3+
import org.junit.After;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
7+
import java.io.IOException;
8+
import java.time.temporal.ChronoUnit;
9+
import java.util.Collection;
10+
import java.util.Date;
11+
import java.util.List;
12+
13+
import static org.hamcrest.Matchers.contains;
14+
import static org.hamcrest.Matchers.containsInAnyOrder;
15+
import static org.hamcrest.Matchers.containsString;
16+
import static org.hamcrest.Matchers.equalTo;
17+
import static org.hamcrest.Matchers.hasProperty;
18+
import static org.hamcrest.Matchers.hasSize;
19+
import static org.hamcrest.Matchers.is;
20+
import static org.hamcrest.Matchers.notNullValue;
21+
22+
/**
23+
* @author Kohsuke Kawaguchi
24+
* @author Yoann Rodiere
25+
*/
26+
public class GHIssueTest extends AbstractGitHubWireMockTest {
27+
28+
@Before
29+
@After
30+
public void cleanUp() throws Exception {
31+
// Cleanup is only needed when proxying
32+
if (!mockGitHub.isUseProxy()) {
33+
return;
34+
}
35+
36+
for (GHIssue issue : getRepository(this.getNonRecordingGitHub()).getIssues(GHIssueState.OPEN)) {
37+
issue.close();
38+
}
39+
}
40+
41+
@Test
42+
public void createIssue() throws Exception {
43+
String name = "createIssue";
44+
GHRepository repo = getRepository();
45+
GHIssue issue = repo.createIssue(name).body("## test").create();
46+
assertThat(issue.getTitle(), equalTo(name));
47+
}
48+
49+
@Test
50+
public void issueComment() throws Exception {
51+
String name = "createIssueComment";
52+
GHIssue issue = getRepository().createIssue(name).body("## test").create();
53+
54+
List<GHIssueComment> comments;
55+
comments = issue.listComments().toList();
56+
assertThat(comments, hasSize(0));
57+
comments = issue.queryComments().list().toList();
58+
assertThat(comments, hasSize(0));
59+
60+
GHIssueComment firstComment = issue.comment("First comment");
61+
Date firstCommentCreatedAt = firstComment.getCreatedAt();
62+
Date firstCommentCreatedAtPlus1Second = Date
63+
.from(firstComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS));
64+
65+
comments = issue.listComments().toList();
66+
assertThat(comments, hasSize(1));
67+
assertThat(comments, contains(hasProperty("body", equalTo("First comment"))));
68+
69+
comments = issue.queryComments().list().toList();
70+
assertThat(comments, hasSize(1));
71+
assertThat(comments, contains(hasProperty("body", equalTo("First comment"))));
72+
73+
// Test "since"
74+
comments = issue.queryComments().since(firstCommentCreatedAt).list().toList();
75+
assertThat(comments, hasSize(1));
76+
assertThat(comments, contains(hasProperty("body", equalTo("First comment"))));
77+
comments = issue.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList();
78+
assertThat(comments, hasSize(0));
79+
80+
// "since" is only precise up to the second,
81+
// so if we want to differentiate comments, we need to be completely sure they're created
82+
// at least 1 second from each other.
83+
// Waiting 2 seconds to avoid edge cases.
84+
Thread.sleep(2000);
85+
86+
GHIssueComment secondComment = issue.comment("Second comment");
87+
Date secondCommentCreatedAt = secondComment.getCreatedAt();
88+
Date secondCommentCreatedAtPlus1Second = Date
89+
.from(secondComment.getCreatedAt().toInstant().plus(1, ChronoUnit.SECONDS));
90+
assertThat(
91+
"There's an error in the setup of this test; please fix it."
92+
+ " The second comment should be created at least one second after the first one.",
93+
firstCommentCreatedAtPlus1Second.getTime() <= secondCommentCreatedAt.getTime());
94+
95+
comments = issue.listComments().toList();
96+
assertThat(comments, hasSize(2));
97+
assertThat(comments,
98+
contains(hasProperty("body", equalTo("First comment")),
99+
hasProperty("body", equalTo("Second comment"))));
100+
comments = issue.queryComments().list().toList();
101+
assertThat(comments, hasSize(2));
102+
assertThat(comments,
103+
contains(hasProperty("body", equalTo("First comment")),
104+
hasProperty("body", equalTo("Second comment"))));
105+
106+
// Test "since"
107+
comments = issue.queryComments().since(firstCommentCreatedAt).list().toList();
108+
assertThat(comments, hasSize(2));
109+
assertThat(comments,
110+
contains(hasProperty("body", equalTo("First comment")),
111+
hasProperty("body", equalTo("Second comment"))));
112+
comments = issue.queryComments().since(firstCommentCreatedAtPlus1Second).list().toList();
113+
assertThat(comments, hasSize(1));
114+
assertThat(comments, contains(hasProperty("body", equalTo("Second comment"))));
115+
comments = issue.queryComments().since(secondCommentCreatedAt).list().toList();
116+
assertThat(comments, hasSize(1));
117+
assertThat(comments, contains(hasProperty("body", equalTo("Second comment"))));
118+
comments = issue.queryComments().since(secondCommentCreatedAtPlus1Second).list().toList();
119+
assertThat(comments, hasSize(0));
120+
121+
// Test "since" with timestamp instead of Date
122+
comments = issue.queryComments().since(secondCommentCreatedAt.getTime()).list().toList();
123+
assertThat(comments, hasSize(1));
124+
assertThat(comments, contains(hasProperty("body", equalTo("Second comment"))));
125+
}
126+
127+
@Test
128+
public void closeIssue() throws Exception {
129+
String name = "closeIssue";
130+
GHIssue issue = getRepository().createIssue(name).body("## test").create();
131+
assertThat(issue.getTitle(), equalTo(name));
132+
assertThat(getRepository().getIssue(issue.getNumber()).getState(), equalTo(GHIssueState.OPEN));
133+
issue.close();
134+
assertThat(getRepository().getIssue(issue.getNumber()).getState(), equalTo(GHIssueState.CLOSED));
135+
}
136+
137+
@Test
138+
// Requires push access to the test repo to pass
139+
public void setLabels() throws Exception {
140+
GHIssue issue = getRepository().createIssue("setLabels").body("## test").create();
141+
String label = "setLabels_label_name";
142+
issue.setLabels(label);
143+
144+
Collection<GHLabel> labels = getRepository().getIssue(issue.getNumber()).getLabels();
145+
assertThat(labels.size(), equalTo(1));
146+
GHLabel savedLabel = labels.iterator().next();
147+
assertThat(savedLabel.getName(), equalTo(label));
148+
assertThat(savedLabel.getId(), notNullValue());
149+
assertThat(savedLabel.getNodeId(), notNullValue());
150+
assertThat(savedLabel.isDefault(), is(false));
151+
}
152+
153+
@Test
154+
// Requires push access to the test repo to pass
155+
public void addLabels() throws Exception {
156+
GHIssue issue = getRepository().createIssue("addLabels").body("## test").create();
157+
String addedLabel1 = "addLabels_label_name_1";
158+
String addedLabel2 = "addLabels_label_name_2";
159+
String addedLabel3 = "addLabels_label_name_3";
160+
161+
List<GHLabel> resultingLabels = issue.addLabels(addedLabel1);
162+
assertThat(resultingLabels.size(), equalTo(1));
163+
GHLabel ghLabel = resultingLabels.get(0);
164+
assertThat(ghLabel.getName(), equalTo(addedLabel1));
165+
166+
int requestCount = mockGitHub.getRequestCount();
167+
resultingLabels = issue.addLabels(addedLabel2, addedLabel3);
168+
// multiple labels can be added with one api call
169+
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 1));
170+
171+
assertThat(resultingLabels.size(), equalTo(3));
172+
assertThat(resultingLabels,
173+
containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)),
174+
hasProperty("name", equalTo(addedLabel2)),
175+
hasProperty("name", equalTo(addedLabel3))));
176+
177+
// Adding a label which is already present does not throw an error
178+
resultingLabels = issue.addLabels(ghLabel);
179+
assertThat(resultingLabels.size(), equalTo(3));
180+
}
181+
182+
@Test
183+
// Requires push access to the test repo to pass
184+
public void addLabelsConcurrencyIssue() throws Exception {
185+
String addedLabel1 = "addLabelsConcurrencyIssue_label_name_1";
186+
String addedLabel2 = "addLabelsConcurrencyIssue_label_name_2";
187+
188+
GHIssue issue1 = getRepository().createIssue("addLabelsConcurrencyIssue").body("## test").create();
189+
issue1.getLabels();
190+
191+
GHIssue issue2 = getRepository().getIssue(issue1.getNumber());
192+
issue2.addLabels(addedLabel2);
193+
194+
Collection<GHLabel> labels = issue1.addLabels(addedLabel1);
195+
196+
assertThat(labels.size(), equalTo(2));
197+
assertThat(labels,
198+
containsInAnyOrder(hasProperty("name", equalTo(addedLabel1)),
199+
hasProperty("name", equalTo(addedLabel2))));
200+
}
201+
202+
@Test
203+
// Requires push access to the test repo to pass
204+
public void removeLabels() throws Exception {
205+
GHIssue issue = getRepository().createIssue("removeLabels").body("## test").create();
206+
String label1 = "removeLabels_label_name_1";
207+
String label2 = "removeLabels_label_name_2";
208+
String label3 = "removeLabels_label_name_3";
209+
issue.setLabels(label1, label2, label3);
210+
211+
Collection<GHLabel> labels = getRepository().getIssue(issue.getNumber()).getLabels();
212+
assertThat(labels.size(), equalTo(3));
213+
GHLabel ghLabel3 = labels.stream().filter(label -> label3.equals(label.getName())).findFirst().get();
214+
215+
int requestCount = mockGitHub.getRequestCount();
216+
List<GHLabel> resultingLabels = issue.removeLabels(label2, label3);
217+
// each label deleted is a separate api call
218+
assertThat(mockGitHub.getRequestCount(), equalTo(requestCount + 2));
219+
220+
assertThat(resultingLabels.size(), equalTo(1));
221+
assertThat(resultingLabels.get(0).getName(), equalTo(label1));
222+
223+
// Removing some labels that are not present does not throw
224+
// This is consistent with earlier behavior and with addLabels()
225+
issue.removeLabels(ghLabel3);
226+
227+
// Calling removeLabel() on label that is not present will throw
228+
try {
229+
issue.removeLabel(label3);
230+
fail("Expected GHFileNotFoundException");
231+
} catch (GHFileNotFoundException e) {
232+
assertThat(e.getMessage(), containsString("Label does not exist"));
233+
}
234+
}
235+
236+
@Test
237+
// Requires push access to the test repo to pass
238+
public void setAssignee() throws Exception {
239+
GHIssue issue = getRepository().createIssue("setAssignee").body("## test").create();
240+
GHMyself user = gitHub.getMyself();
241+
issue.assignTo(user);
242+
243+
assertThat(getRepository().getIssue(issue.getNumber()).getAssignee(), equalTo(user));
244+
}
245+
246+
@Test
247+
public void getUserTest() throws IOException {
248+
GHIssue issue = getRepository().createIssue("getUserTest").create();
249+
GHIssue issueSingle = getRepository().getIssue(issue.getNumber());
250+
assertThat(issueSingle.getUser().root(), notNullValue());
251+
252+
PagedIterable<GHIssue> ghIssues = getRepository().listIssues(GHIssueState.OPEN);
253+
for (GHIssue otherIssue : ghIssues) {
254+
assertThat(otherIssue.getUser().root(), notNullValue());
255+
}
256+
}
257+
258+
protected GHRepository getRepository() throws IOException {
259+
return getRepository(gitHub);
260+
}
261+
262+
private GHRepository getRepository(GitHub gitHub) throws IOException {
263+
return gitHub.getOrganization(GITHUB_API_TEST_ORG).getRepository("GHIssueTest");
264+
}
265+
266+
}

0 commit comments

Comments
 (0)