Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ea9f4b1

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

File tree

Expand file treeCollapse file tree

201 files changed

+11655
-1526
lines changed
Filter options

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner
Expand file treeCollapse file tree

201 files changed

+11655
-1526
lines changed

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

Copy file name to clipboardExpand all lines: src/main/java/org/kohsuke/github/GHIssue.java
+13-1Lines 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()
+61Lines 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+
}
+266Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.