-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
In this PR: - Add a github client to retrieve pull request status from a repository. - Add unit test.
- Loading branch information
1 parent
d63af40
commit 80cc1f7
Showing
8 changed files
with
251 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...ependencies/dependency-analyzer/src/main/java/com/google/cloud/external/GitHubClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package com.google.cloud.external; | ||
|
||
import com.google.cloud.model.Interval; | ||
import com.google.cloud.model.PullRequest; | ||
import com.google.cloud.model.PullRequestStatistics; | ||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.google.gson.reflect.TypeToken; | ||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
import java.net.http.HttpResponse.BodyHandlers; | ||
import java.time.Instant; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
/** | ||
* GitHubClient is a class that sends HTTP requests to the GitHub RESTful API. It provides methods | ||
* for interacting with various GitHub resources such as repositories, issues, users, etc. | ||
* | ||
* <p>This class simplifies the process of making API calls by handling authentication, request | ||
* construction, and response parsing. It uses the {@link java.net.http.HttpClient} for sending | ||
* requests and {@link com.google.gson.Gson} for handling JSON serialization/deserialization. | ||
*/ | ||
public class GitHubClient { | ||
private final HttpClient client; | ||
private final Gson gson; | ||
private static final String PULL_REQUESTS_BASE = | ||
"https://api.github.com/repos/%s/%s/pulls?state=all&per_page=100&page=%s"; | ||
private static final int MAX_PULL_REQUEST_NUM = 1000; | ||
private static final String OPEN_STATE = "open"; | ||
|
||
public GitHubClient(HttpClient client) { | ||
this.client = client; | ||
this.gson = new GsonBuilder().create(); | ||
} | ||
|
||
public PullRequestStatistics listMonthlyPullRequestStatusOf(String organization, String repo) | ||
throws URISyntaxException, IOException, InterruptedException { | ||
return listPullRequestStatus(organization, repo, Interval.MONTHLY); | ||
} | ||
|
||
private PullRequestStatistics listPullRequestStatus( | ||
String organization, String repo, Interval interval) | ||
throws URISyntaxException, IOException, InterruptedException { | ||
List<PullRequest> pullRequests = listPullRequests(organization, repo); | ||
ZonedDateTime now = ZonedDateTime.now(); | ||
long created = | ||
pullRequests.stream() | ||
.distinct() | ||
.filter(pullRequest -> pullRequest.state().equals(OPEN_STATE)) | ||
.filter( | ||
pullRequest -> { | ||
ZonedDateTime createdAt = utcTimeFrom(pullRequest.createdAt()); | ||
return now.minusDays(interval.getDays()).isBefore(createdAt); | ||
}) | ||
.count(); | ||
|
||
long merged = | ||
pullRequests.stream() | ||
.distinct() | ||
.filter(pullRequest -> Objects.nonNull(pullRequest.mergedAt())) | ||
.filter( | ||
pullRequest -> { | ||
ZonedDateTime createdAt = utcTimeFrom(pullRequest.mergedAt()); | ||
return now.minusDays(interval.getDays()).isBefore(createdAt); | ||
}) | ||
.count(); | ||
|
||
return new PullRequestStatistics(created, merged, interval); | ||
} | ||
|
||
private List<PullRequest> listPullRequests(String organization, String repo) | ||
throws URISyntaxException, IOException, InterruptedException { | ||
List<PullRequest> pullRequests = new ArrayList<>(); | ||
int page = 1; | ||
while (pullRequests.size() < MAX_PULL_REQUEST_NUM) { | ||
HttpResponse<String> response = getResponse(getPullRequestsUrl(organization, repo, page)); | ||
pullRequests.addAll( | ||
gson.fromJson(response.body(), new TypeToken<List<PullRequest>>() {}.getType())); | ||
page++; | ||
} | ||
|
||
return pullRequests; | ||
} | ||
|
||
private String getPullRequestsUrl(String organization, String repo, int page) { | ||
return String.format(PULL_REQUESTS_BASE, organization, repo, page); | ||
} | ||
|
||
private ZonedDateTime utcTimeFrom(String time) { | ||
ZoneId zoneIdUTC = ZoneId.of("UTC"); | ||
Instant instant = Instant.parse(time); | ||
return instant.atZone(zoneIdUTC); | ||
} | ||
|
||
private HttpResponse<String> getResponse(String endpoint) | ||
throws URISyntaxException, IOException, InterruptedException { | ||
HttpRequest request = | ||
HttpRequest.newBuilder() | ||
.header("Authorization", System.getenv("GITHUB_TOKEN")) | ||
.uri(new URI(endpoint)) | ||
.GET() | ||
.build(); | ||
return client.send(request, BodyHandlers.ofString()); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...hared-dependencies/dependency-analyzer/src/main/java/com/google/cloud/model/Interval.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.google.cloud.model; | ||
|
||
public enum Interval { | ||
WEEKLY(7), | ||
MONTHLY(30); | ||
|
||
private final int days; | ||
|
||
Interval(int days) { | ||
this.days = days; | ||
} | ||
|
||
public int getDays() { | ||
return days; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ed-dependencies/dependency-analyzer/src/main/java/com/google/cloud/model/PullRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.google.cloud.model; | ||
|
||
import com.google.gson.annotations.SerializedName; | ||
|
||
/** | ||
* A record that represents a GitHub pull request. | ||
* | ||
* @param url The url of the pull request. | ||
* @param state The state of the pull request, e.g., open, merged. | ||
* @param createdAt The creation time of the pull request. | ||
* @param mergedAt The merged time of the pull request; null if not merged. | ||
*/ | ||
public record PullRequest( | ||
String url, | ||
String state, | ||
@SerializedName("created_at") String createdAt, | ||
@SerializedName("merged_at") String mergedAt) {} |
15 changes: 15 additions & 0 deletions
15
...ncies/dependency-analyzer/src/main/java/com/google/cloud/model/PullRequestStatistics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.google.cloud.model; | ||
|
||
/** | ||
* A record that represents statistics about pull requests within a specified time interval. | ||
* | ||
* <p>The pull request statistics is used to show pull request freshness in the package information | ||
* report. | ||
* | ||
* <p>For example, x pull requests are created and y pull requests are merged in the last 30 days. | ||
* | ||
* @param created The number of pull requests created within the interval. | ||
* @param merged The number of pull requests merged within the interval. | ||
* @param interval The time interval over which the statistics were collected. | ||
*/ | ||
public record PullRequestStatistics(long created, long merged, Interval interval) {} |
65 changes: 65 additions & 0 deletions
65
...dencies/dependency-analyzer/src/test/java/com/google/cloud/external/GitHubClientTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.google.cloud.external; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
import com.google.cloud.model.Interval; | ||
import com.google.cloud.model.PullRequestStatistics; | ||
import java.io.IOException; | ||
import java.net.URISyntaxException; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
import java.net.http.HttpResponse.BodyHandler; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.time.Instant; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.mockito.MockedStatic; | ||
import org.mockito.Mockito; | ||
|
||
public class GitHubClientTest { | ||
|
||
private HttpResponse<String> response; | ||
private GitHubClient client; | ||
|
||
@Before | ||
public void setUp() throws IOException, InterruptedException { | ||
HttpClient httpClient = mock(HttpClient.class); | ||
client = new GitHubClient(httpClient); | ||
response = mock(HttpResponse.class); | ||
when(httpClient.send(any(HttpRequest.class), any(BodyHandler.class))).thenReturn(response); | ||
} | ||
|
||
@Test | ||
public void testListMonthlyPullRequestStatusSucceeds() | ||
throws URISyntaxException, IOException, InterruptedException { | ||
ZonedDateTime fixedNow = ZonedDateTime.parse("2024-05-22T09:33:52Z"); | ||
ZonedDateTime lastMonth = ZonedDateTime.parse("2024-04-22T09:33:52Z"); | ||
Instant prInstant = Instant.parse("2024-05-10T09:33:52Z"); | ||
ZonedDateTime prTime = ZonedDateTime.parse("2024-05-10T09:33:52Z"); | ||
String responseBody = | ||
Files.readString(Path.of("src/test/resources/pull_request_sample_response.txt")); | ||
|
||
try (MockedStatic<ZonedDateTime> mockedLocalDateTime = Mockito.mockStatic(ZonedDateTime.class); | ||
MockedStatic<Instant> mockedInstant = Mockito.mockStatic(Instant.class)) { | ||
mockedLocalDateTime.when(ZonedDateTime::now).thenReturn(fixedNow); | ||
mockedInstant.when(() -> Instant.parse(Mockito.anyString())).thenReturn(prInstant); | ||
when(fixedNow.minusDays(30)).thenReturn(lastMonth); | ||
when(prInstant.atZone(ZoneId.of("UTC"))).thenReturn(prTime); | ||
when(response.body()).thenReturn(responseBody); | ||
String org = ""; | ||
String repo = ""; | ||
PullRequestStatistics status = client.listMonthlyPullRequestStatusOf(org, repo); | ||
|
||
assertEquals(Interval.MONTHLY, status.interval()); | ||
assertEquals(3, status.created()); | ||
assertEquals(7, status.merged()); | ||
} | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
...ared-dependencies/dependency-analyzer/src/test/resources/pull_request_sample_response.txt
Large diffs are not rendered by default.
Oops, something went wrong.