package com.capitalone.dashboard.collector;
import com.capitalone.dashboard.misc.HygieiaException;
import com.capitalone.dashboard.model.CollectionMode;
import com.capitalone.dashboard.model.Comment;
import com.capitalone.dashboard.model.Commit;
import com.capitalone.dashboard.model.CommitStatus;
import com.capitalone.dashboard.model.CommitType;
import com.capitalone.dashboard.model.GitHubGraphQLQuery;
import com.capitalone.dashboard.model.GitHubPaging;
import com.capitalone.dashboard.model.GitHubParsed;
import com.capitalone.dashboard.model.GitHubRateLimit;
import com.capitalone.dashboard.model.GitHubRepo;
import com.capitalone.dashboard.model.GitRequest;
import com.capitalone.dashboard.model.MergeEvent;
import com.capitalone.dashboard.model.Review;
import com.capitalone.dashboard.util.Encryption;
import com.capitalone.dashboard.util.EncryptionException;
import com.capitalone.dashboard.util.Supplier;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* GitHubClient implementation that uses SVNKit to fetch information about
* Subversion repositories.
*/
@Component
@SuppressWarnings("PMD.ExcessiveClassLength")
public class DefaultGitHubClient implements GitHubClient {
private static final Log LOG = LogFactory.getLog(DefaultGitHubClient.class);
private final GitHubSettings settings;
private final RestOperations restOperations;
private List<Commit> commits;
private List<GitRequest> pullRequests;
private List<GitRequest> issues;
private Map<String, String> ldapMap;
private final List<Pattern> commitExclusionPatterns = new ArrayList<>();
private static final int FIRST_RUN_HISTORY_DEFAULT = 14;
public class RedirectedStatus {
private boolean isRedirected = false;
private String redirectedUrl = null;
RedirectedStatus() {}
RedirectedStatus(boolean isRedirected, String redirectedUrl) {
this.isRedirected = isRedirected;
this.redirectedUrl = redirectedUrl;
}
String getRedirectedUrl() {
return this.redirectedUrl;
}
boolean isRedirected() {
return this.isRedirected;
}
}
@Autowired
public DefaultGitHubClient(GitHubSettings settings,
Supplier<RestOperations> restOperationsSupplier) {
this.settings = settings;
this.restOperations = restOperationsSupplier.get();
if (!CollectionUtils.isEmpty(settings.getNotBuiltCommits())) {
settings.getNotBuiltCommits().stream().map(regExStr -> Pattern.compile(regExStr, Pattern.CASE_INSENSITIVE)).forEach(commitExclusionPatterns::add);
}
}
private int getFetchCount(boolean firstRun) {
if (firstRun) return 100;
return settings.getFetchCount();
}
@Override
public List<Commit> getCommits() {
return commits;
}
@Override
public List<GitRequest> getPulls() {
return pullRequests;
}
@Override
public List<GitRequest> getIssues() {
return issues;
}
protected void setLdapMap(Map<String, String> ldapMap) {
this.ldapMap = ldapMap;
}
protected Map<String, String> getLdapMap() {
return ldapMap;
}
@Override
@SuppressWarnings("PMD.ExcessiveMethodLength")
public void fireGraphQL(GitHubRepo repo, boolean firstRun, Map<Long, String> existingPRMap, Map<Long, String> existingIssueMap) throws RestClientException, MalformedURLException, HygieiaException {
// format URL
String repoUrl = (String) repo.getOptions().get("url");
GitHubParsed gitHubParsed = new GitHubParsed(repoUrl);
commits = new LinkedList<>();
pullRequests = new LinkedList<>();
issues = new LinkedList<>();
ldapMap = new HashMap<>();
long historyTimeStamp = getTimeStampMills(getRunDate(repo, firstRun, false));
String decryptedPassword = decryptString(repo.getPassword(), settings.getKey());
String personalAccessToken = (String) repo.getOptions().get("personalAccessToken");
String decryptPersonalAccessToken = decryptString(personalAccessToken, settings.getKey());
boolean alldone = false;
GitHubPaging dummyPRPaging = isThereNewPRorIssue(gitHubParsed, repo, decryptedPassword, decryptPersonalAccessToken, existingPRMap, "pull", firstRun);
GitHubPaging dummyIssuePaging = isThereNewPRorIssue(gitHubParsed, repo, decryptedPassword, decryptPersonalAccessToken, existingIssueMap, "issue", firstRun);
GitHubPaging dummyCommitPaging = new GitHubPaging();
dummyCommitPaging.setLastPage(false);
JSONObject query = buildQuery(true, firstRun, false, gitHubParsed, repo, dummyCommitPaging, dummyPRPaging, dummyIssuePaging);
int loopCount = 1;
while (!alldone) {
LOG.debug("Executing loop " + loopCount + " for " + gitHubParsed.getOrgName() + "/" + gitHubParsed.getRepoName());
JSONObject data = getDataFromRestCallPost(gitHubParsed, repo, decryptedPassword, decryptPersonalAccessToken, query);
if (data != null) {
JSONObject repository = (JSONObject) data.get("repository");
GitHubPaging pullPaging = processPullRequest((JSONObject) repository.get("pullRequests"), repo, existingPRMap, historyTimeStamp);
LOG.debug("--- Processed " + pullPaging.getCurrentCount() + " of total " + pullPaging.getTotalCount() + " pull requests");
GitHubPaging issuePaging = processIssues((JSONObject) repository.get("issues"), gitHubParsed, existingIssueMap, historyTimeStamp);
LOG.debug("--- Processed " + issuePaging.getCurrentCount() + " of total " + issuePaging.getTotalCount() + " issues");
GitHubPaging commitPaging = processCommits((JSONObject) repository.get("ref"), repo);
LOG.debug("--- Processed " + commitPaging.getCurrentCount() + " commits");
alldone = pullPaging.isLastPage() && commitPaging.isLastPage() && issuePaging.isLastPage();
query = buildQuery(false, firstRun, false, gitHubParsed, repo, commitPaging, pullPaging, issuePaging);
loopCount++;
}
}
LOG.info("-- Collected " + commits.size() + " Commits, " + pullRequests.size() + " Pull Requests, " + issues.size() + " Issues since " + getRunDate(repo, firstRun, false));
if (firstRun) {
connectCommitToPulls();
return;
}
List<GitRequest> allMergedPrs = pullRequests.stream().filter(pr -> "merged".equalsIgnoreCase(pr.getState())).collect(Collectors.toList());
if (CollectionUtils.isEmpty(allMergedPrs)) {
connectCommitToPulls();
return;
}