Skip to content

Commit

Permalink
Cache contributor stats
Browse files Browse the repository at this point in the history
  • Loading branch information
holly-cummins committed Aug 25, 2022
1 parent d2654e3 commit 2c3bbae
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 15 deletions.
54 changes: 40 additions & 14 deletions src/main/java/io/quarkus/bot/ApproveWorkflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
import io.quarkus.bot.config.QuarkusGitHubBotConfig;
import io.quarkus.bot.config.QuarkusGitHubBotConfigFile;
import io.quarkus.bot.util.PullRequestFilesMatcher;
import io.quarkus.cache.CacheKey;
import io.quarkus.cache.CacheResult;
import org.jboss.logging.Logger;
import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHRepositoryStatistics;
import org.kohsuke.github.GHWorkflowRun;
import org.kohsuke.github.PagedIterable;

import javax.inject.Inject;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

class ApproveWorkflow {

Expand Down Expand Up @@ -179,20 +186,39 @@ private boolean matchRuleForUser(GHRepositoryStatistics.ContributorStats stats,
}

private GHRepositoryStatistics.ContributorStats getStatsForUser(GHEventPayload.WorkflowRun workflowPayload) {
if (workflowPayload.getSender().getLogin() != null) {
try {
GHRepositoryStatistics statistics = workflowPayload.getRepository().getStatistics();
if (statistics != null) {
PagedIterable<GHRepositoryStatistics.ContributorStats> contributors = statistics.getContributorStats();
for (GHRepositoryStatistics.ContributorStats contributor : contributors) {
if (workflowPayload.getSender().getLogin().equals(contributor.getAuthor().getLogin())) {
return contributor;
}
}
}
} catch (InterruptedException | IOException e) {
LOG.error("Could not get repository contributor statistics", e);
}

String login = workflowPayload.getSender().getLogin();
if (login != null) {
return getStatsForUser(workflowPayload.getRepository(), login);
}
return null;
}

@CacheResult(cacheName = "contributor-cache")
GHRepositoryStatistics.ContributorStats getStatsForUser(GHRepository repository, @CacheKey String login) {
try {
Map<String, GHRepositoryStatistics.ContributorStats> contributorStats = getContributorStats(repository);
return contributorStats.get(login);
} catch (IOException | InterruptedException e) {
LOG.error("Could not get repository contributor statistics", e);
}

return null;
}

@CacheResult(cacheName = "stats-cache")
Map<String, GHRepositoryStatistics.ContributorStats> getContributorStats(GHRepository repository)
throws IOException, InterruptedException {
GHRepositoryStatistics statistics = repository.getStatistics();
if (statistics != null) {
PagedIterable<GHRepositoryStatistics.ContributorStats> contributors = statistics.getContributorStats();
// Pull the iterable into a list object to force the traversal of the entire list,
// since then we get a fully-warmed cache on our first request
// Convert to a map for convenience of retrieval
List<GHRepositoryStatistics.ContributorStats> statsList = contributors.toList();
return statsList.stream()
.collect(
Collectors.toMap(contributorStats -> contributorStats.getAuthor().getLogin(), Function.identity()));
}
return null;
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ quarkus.cache.caffeine."PushToProject.getStatusFieldValue".initial-capacity=10
quarkus.cache.caffeine."PushToProject.getStatusFieldValue".maximum-size=100
quarkus.cache.caffeine."PushToProject.getStatusFieldValue".expire-after-write=2H

quarkus.cache.caffeine."contributor-cache".expire-after-write=P2D
quarkus.cache.caffeine."stats-cache".expire-after-write=P2D

quarkus.openshift.labels."app"=quarkus-bot
quarkus.openshift.annotations."kubernetes.io/tls-acme"=true
quarkus.openshift.env.vars.QUARKUS_GITHUB_APP_APP_ID=90234
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/io/quarkus/bot/it/MockHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.kohsuke.github.PagedIterable;
import org.kohsuke.github.PagedIterator;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

Expand All @@ -24,11 +25,19 @@ public static GHPullRequestFileDetail mockGHPullRequestFileDetail(String filenam
@SuppressWarnings("unchecked")
public static <T> PagedIterable<T> mockPagedIterable(T... contentMocks) {
PagedIterable<T> iterableMock = mock(PagedIterable.class);
try {
lenient().when(iterableMock.toList()).thenAnswer(ignored2 -> List.of(contentMocks));
} catch (IOException e) {
// This should never happen
// That's a classic unwise comment, but it's a mock, so surely we're safe? :)
throw new RuntimeException(e);
}
lenient().when(iterableMock.iterator()).thenAnswer(ignored -> {
PagedIterator<T> iteratorMock = mock(PagedIterator.class);
Iterator<T> actualIterator = List.of(contentMocks).iterator();
when(iteratorMock.next()).thenAnswer(ignored2 -> actualIterator.next());
lenient().when(iteratorMock.hasNext()).thenAnswer(ignored2 -> actualIterator.hasNext());

return iteratorMock;
});
return iterableMock;
Expand Down
6 changes: 5 additions & 1 deletion src/test/java/io/quarkus/bot/it/WorkflowApprovalTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.quarkiverse.githubapp.testing.GitHubAppTest;
import io.quarkiverse.githubapp.testing.dsl.GitHubMockSetupContext;
import io.quarkiverse.githubapp.testing.dsl.GitHubMockVerificationContext;
import io.quarkus.cache.CacheInvalidateAll;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -36,7 +37,10 @@
@ExtendWith(MockitoExtension.class)
public class WorkflowApprovalTest {

private void setupMockQueriesAndCommits(GitHubMockSetupContext mocks) {
// We may change our user stats in individual tests, so wipe caches before each test
@CacheInvalidateAll(cacheName = "contributor-cache")
@CacheInvalidateAll(cacheName = "stats-cache")
void setupMockQueriesAndCommits(GitHubMockSetupContext mocks) {
GHRepository repoMock = mocks.repository("bot-playground");
GHPullRequestQueryBuilder workflowRunQueryBuilderMock = mock(GHPullRequestQueryBuilder.class,
withSettings().defaultAnswer(Answers.RETURNS_SELF));
Expand Down

0 comments on commit 2c3bbae

Please sign in to comment.