Skip to content

Commit b759660

Browse files
seatenyatda0116bitwiseman
authored
FEAT: Implement enabling auto merge for PR by GraphQL (#2056)
* feat: implement support for general GraphQL Request and Response * feat: implement get PR id of GraphQL feature and enabling auto merge for PR feature * test: implement test for enable pull request auto merge * Tidy up GraphQL methods and classes * Additional cleanup and code coverage * test: update test for tidy up * Update src/main/java/org/kohsuke/github/GitHubResponse.java * Update src/main/java/org/kohsuke/github/GitHub.java * Update src/main/java/org/kohsuke/github/GitHub.java * Update src/main/java/org/kohsuke/github/GitHub.java --------- Co-authored-by: siwoo <k74415834@gmail.com> Co-authored-by: Liam Newman <bitwiseman@gmail.com>
1 parent 98cf5eb commit b759660

File tree

29 files changed

+2523
-1
lines changed

29 files changed

+2523
-1
lines changed

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,66 @@ public enum MergeMethod {
629629
REBASE
630630
}
631631

632+
/**
633+
* Request to enable auto merge for a pull request.
634+
*
635+
* @param authorEmail
636+
* The email address to associate with this merge.
637+
* @param clientMutationId
638+
* A unique identifier for the client performing the mutation.
639+
* @param commitBody
640+
* Commit body to use for the commit when the PR is mergable; if omitted, a default message will be used.
641+
* NOTE: when merging with a merge queue any input value for commit message is ignored.
642+
* @param commitHeadline
643+
* Commit headline to use for the commit when the PR is mergable; if omitted, a default message will be
644+
* used. NOTE: when merging with a merge queue any input value for commit headline is ignored.
645+
* @param expectedHeadOid
646+
* The expected head OID of the pull request.
647+
* @param mergeMethod
648+
* The merge method to use. If omitted, defaults to `MERGE`. NOTE: when merging with a merge queue any
649+
* input value for merge method is ignored.
650+
* @throws IOException
651+
* the io exception
652+
*/
653+
public void enablePullRequestAutoMerge(String authorEmail,
654+
String clientMutationId,
655+
String commitBody,
656+
String commitHeadline,
657+
String expectedHeadOid,
658+
MergeMethod mergeMethod) throws IOException {
659+
660+
StringBuilder inputBuilder = new StringBuilder();
661+
addParameter(inputBuilder, "pullRequestId", this.getNodeId());
662+
addOptionalParameter(inputBuilder, "authorEmail", authorEmail);
663+
addOptionalParameter(inputBuilder, "clientMutationId", clientMutationId);
664+
addOptionalParameter(inputBuilder, "commitBody", commitBody);
665+
addOptionalParameter(inputBuilder, "commitHeadline", commitHeadline);
666+
addOptionalParameter(inputBuilder, "expectedHeadOid", expectedHeadOid);
667+
addOptionalParameter(inputBuilder, "mergeMethod", mergeMethod);
668+
669+
String graphqlBody = "mutation EnableAutoMerge { enablePullRequestAutoMerge(input: {" + inputBuilder + "}) { "
670+
+ "pullRequest { id } } }";
671+
672+
root().createGraphQLRequest(graphqlBody).sendGraphQL();
673+
674+
refresh();
675+
}
676+
677+
private void addOptionalParameter(StringBuilder inputBuilder, String name, Object value) {
678+
if (value != null) {
679+
addParameter(inputBuilder, name, value);
680+
}
681+
}
682+
683+
private void addParameter(StringBuilder inputBuilder, String name, Object value) {
684+
Objects.requireNonNull(value);
685+
String formatString = " %s: \"%s\"";
686+
if (value instanceof Enum) {
687+
formatString = " %s: %s";
688+
}
689+
690+
inputBuilder.append(String.format(formatString, name, value));
691+
}
632692
/**
633693
* The status of auto merging a {@linkplain GHPullRequest}.
634694
*

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,21 @@ Requester createRequest() {
12991299
return requester;
13001300
}
13011301

1302+
/**
1303+
* Creates a request to GitHub GraphQL API.
1304+
*
1305+
* @param query
1306+
* the query for the GraphQL
1307+
* @return the requester
1308+
*/
1309+
@Nonnull
1310+
Requester createGraphQLRequest(String query) {
1311+
return createRequest().method("POST")
1312+
.rateLimit(RateLimitTarget.GRAPHQL)
1313+
.with("query", query)
1314+
.withUrlPath("/graphql");
1315+
}
1316+
13021317
/**
13031318
* Intern.
13041319
*

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.commons.io.IOUtils;
2828
import org.kohsuke.github.connector.GitHubConnectorResponse;
2929
import org.kohsuke.github.function.InputStreamFunction;
30+
import org.kohsuke.github.internal.graphql.response.GHGraphQLResponse;
3031

3132
import java.io.ByteArrayInputStream;
3233
import java.io.IOException;
@@ -103,6 +104,37 @@ public <T> T fetchInto(@Nonnull T existingInstance) throws IOException {
103104
.body();
104105
}
105106

107+
/**
108+
* Sends a GraphQL request with no response
109+
*
110+
* @throws IOException
111+
* the io exception
112+
*/
113+
public void sendGraphQL() throws IOException {
114+
fetchGraphQL(GHGraphQLResponse.ObjectResponse.class);
115+
}
116+
117+
/**
118+
* Sends a request and parses the response into the given type via databinding in GraphQL response.
119+
*
120+
* @param <T>
121+
* the type parameter
122+
* @param type
123+
* the type
124+
* @return an instance of {@code GHGraphQLResponse<T>}
125+
* @throws IOException
126+
* if the server returns 4xx/5xx responses.
127+
*/
128+
public <T extends GHGraphQLResponse<S>, S> S fetchGraphQL(@Nonnull Class<T> type) throws IOException {
129+
T response = fetch(type);
130+
131+
if (!response.isSuccessful()) {
132+
throw new IOException("GraphQL request failed by:" + response.getErrorMessages());
133+
}
134+
135+
return response.getData();
136+
}
137+
106138
/**
107139
* Makes a request and just obtains the HTTP status code. Method does not throw exceptions for many status codes
108140
* that would otherwise throw.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.kohsuke.github.internal.graphql.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6+
7+
import java.util.Collections;
8+
import java.util.List;
9+
import java.util.stream.Collectors;
10+
11+
/**
12+
* A response of GraphQL.
13+
* <p>
14+
* This class is used to parse the response of GraphQL.
15+
* </p>
16+
*
17+
* @param <T>
18+
* the type of data
19+
*/
20+
public class GHGraphQLResponse<T> {
21+
22+
private final T data;
23+
24+
private final List<GraphQLError> errors;
25+
26+
/**
27+
* @param data
28+
* GraphQL success response
29+
* @param errors
30+
* GraphQL failure response, This will be empty if not fail
31+
*/
32+
@JsonCreator
33+
@SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this")
34+
public GHGraphQLResponse(@JsonProperty("data") T data, @JsonProperty("errors") List<GraphQLError> errors) {
35+
if (errors == null) {
36+
errors = Collections.emptyList();
37+
}
38+
this.data = data;
39+
this.errors = Collections.unmodifiableList(errors);
40+
}
41+
42+
/**
43+
* @return request is succeeded. True when error list is empty.
44+
*/
45+
public boolean isSuccessful() {
46+
return errors.isEmpty();
47+
}
48+
49+
/**
50+
* @return GraphQL success response
51+
*/
52+
public T getData() {
53+
if (!isSuccessful()) {
54+
throw new RuntimeException("Response not successful, data invalid");
55+
}
56+
57+
return data;
58+
}
59+
60+
/**
61+
* @return GraphQL error messages from Github Response. Empty list when no errors occurred.
62+
*/
63+
public List<String> getErrorMessages() {
64+
return errors.stream().map(GraphQLError::getMessage).collect(Collectors.toList());
65+
}
66+
67+
/**
68+
* A error of GraphQL response. Minimum implementation for GraphQL error.
69+
*/
70+
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" },
71+
justification = "JSON API")
72+
private static class GraphQLError {
73+
private String message;
74+
75+
public String getMessage() {
76+
return message;
77+
}
78+
}
79+
80+
/**
81+
* A GraphQL response with basic Object data type.
82+
*/
83+
public static class ObjectResponse extends GHGraphQLResponse<Object> {
84+
/**
85+
* {@inheritDoc}
86+
*/
87+
@JsonCreator
88+
@SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this")
89+
public ObjectResponse(@JsonProperty("data") Object data, @JsonProperty("errors") List<GraphQLError> errors) {
90+
super(data, errors);
91+
}
92+
}
93+
}

src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6703,5 +6703,95 @@
67036703
"allDeclaredMethods": true,
67046704
"allPublicClasses": true,
67056705
"allDeclaredClasses": true
6706+
},
6707+
{
6708+
"name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse",
6709+
"allPublicFields": true,
6710+
"allDeclaredFields": true,
6711+
"queryAllPublicConstructors": true,
6712+
"queryAllDeclaredConstructors": true,
6713+
"allPublicConstructors": true,
6714+
"allDeclaredConstructors": true,
6715+
"queryAllPublicMethods": true,
6716+
"queryAllDeclaredMethods": true,
6717+
"allPublicMethods": true,
6718+
"allDeclaredMethods": true,
6719+
"allPublicClasses": true,
6720+
"allDeclaredClasses": true
6721+
},
6722+
{
6723+
"name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge",
6724+
"allPublicFields": true,
6725+
"allDeclaredFields": true,
6726+
"queryAllPublicConstructors": true,
6727+
"queryAllDeclaredConstructors": true,
6728+
"allPublicConstructors": true,
6729+
"allDeclaredConstructors": true,
6730+
"queryAllPublicMethods": true,
6731+
"queryAllDeclaredMethods": true,
6732+
"allPublicMethods": true,
6733+
"allDeclaredMethods": true,
6734+
"allPublicClasses": true,
6735+
"allDeclaredClasses": true
6736+
},
6737+
{
6738+
"name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge$EnablePullRequestAutoMergePullRequest",
6739+
"allPublicFields": true,
6740+
"allDeclaredFields": true,
6741+
"queryAllPublicConstructors": true,
6742+
"queryAllDeclaredConstructors": true,
6743+
"allPublicConstructors": true,
6744+
"allDeclaredConstructors": true,
6745+
"queryAllPublicMethods": true,
6746+
"queryAllDeclaredMethods": true,
6747+
"allPublicMethods": true,
6748+
"allDeclaredMethods": true,
6749+
"allPublicClasses": true,
6750+
"allDeclaredClasses": true
6751+
},
6752+
{
6753+
"name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse",
6754+
"allPublicFields": true,
6755+
"allDeclaredFields": true,
6756+
"queryAllPublicConstructors": true,
6757+
"queryAllDeclaredConstructors": true,
6758+
"allPublicConstructors": true,
6759+
"allDeclaredConstructors": true,
6760+
"queryAllPublicMethods": true,
6761+
"queryAllDeclaredMethods": true,
6762+
"allPublicMethods": true,
6763+
"allDeclaredMethods": true,
6764+
"allPublicClasses": true,
6765+
"allDeclaredClasses": true
6766+
},
6767+
{
6768+
"name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$GraphQLError",
6769+
"allPublicFields": true,
6770+
"allDeclaredFields": true,
6771+
"queryAllPublicConstructors": true,
6772+
"queryAllDeclaredConstructors": true,
6773+
"allPublicConstructors": true,
6774+
"allDeclaredConstructors": true,
6775+
"queryAllPublicMethods": true,
6776+
"queryAllDeclaredMethods": true,
6777+
"allPublicMethods": true,
6778+
"allDeclaredMethods": true,
6779+
"allPublicClasses": true,
6780+
"allDeclaredClasses": true
6781+
},
6782+
{
6783+
"name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$ObjectResponse",
6784+
"allPublicFields": true,
6785+
"allDeclaredFields": true,
6786+
"queryAllPublicConstructors": true,
6787+
"queryAllDeclaredConstructors": true,
6788+
"allPublicConstructors": true,
6789+
"allDeclaredConstructors": true,
6790+
"queryAllPublicMethods": true,
6791+
"queryAllDeclaredMethods": true,
6792+
"allPublicMethods": true,
6793+
"allDeclaredMethods": true,
6794+
"allPublicClasses": true,
6795+
"allDeclaredClasses": true
67066796
}
67076797
]

src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,5 +1342,23 @@
13421342
},
13431343
{
13441344
"name": "org.kohsuke.github.SkipFromToString"
1345+
},
1346+
{
1347+
"name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse"
1348+
},
1349+
{
1350+
"name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge"
1351+
},
1352+
{
1353+
"name": "org.kohsuke.github.GHPullRequest$EnablePullRequestAutoMergeResponse$EnablePullRequestAutoMerge$EnablePullRequestAutoMergePullRequest"
1354+
},
1355+
{
1356+
"name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse"
1357+
},
1358+
{
1359+
"name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$GraphQLError"
1360+
},
1361+
{
1362+
"name": "org.kohsuke.github.internal.graphql.response.GHGraphQLResponse$ObjectResponse"
13451363
}
13461364
]

src/test/java/org/kohsuke/github/GHPullRequestMockTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,4 @@ public void shouldMockGHPullRequest() throws IOException {
3333

3434
assertThat("Mock should return true", pullRequest.isDraft());
3535
}
36-
3736
}

0 commit comments

Comments
 (0)