All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.capitalone.dashboard.evaluator.CodeReviewEvaluatorLegacy Maven / Gradle / Ivy

package com.capitalone.dashboard.evaluator;

import com.capitalone.dashboard.ApiSettings;
import com.capitalone.dashboard.common.CommonCodeReview;
import com.capitalone.dashboard.model.CollectorItem;
import com.capitalone.dashboard.model.Commit;
import com.capitalone.dashboard.model.CommitType;
import com.capitalone.dashboard.model.GitRequest;
import com.capitalone.dashboard.model.SCM;
import com.capitalone.dashboard.model.ServiceAccount;
import com.capitalone.dashboard.repository.CommitRepository;
import com.capitalone.dashboard.repository.GitRequestRepository;
import com.capitalone.dashboard.repository.ServiceAccountRepository;
import com.capitalone.dashboard.response.CodeReviewAuditResponse;
import com.capitalone.dashboard.status.CodeReviewAuditStatus;
import com.capitalone.dashboard.util.GitHubParsedUrl;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.capitalone.dashboard.status.CodeReviewAuditStatus.COLLECTOR_ITEM_ERROR;

@Component
public class CodeReviewEvaluatorLegacy extends LegacyEvaluator {

    private final CommitRepository commitRepository;
    private final GitRequestRepository gitRequestRepository;
    protected final ApiSettings settings;
    private final ServiceAccountRepository serviceAccountRepository;

    @Autowired
    public CodeReviewEvaluatorLegacy(CommitRepository commitRepository, GitRequestRepository gitRequestRepository, ServiceAccountRepository serviceAccountRepository, ApiSettings settings) {
        this.commitRepository = commitRepository;
        this.gitRequestRepository = gitRequestRepository;
        this.settings = settings;
        this.serviceAccountRepository = serviceAccountRepository;
    }

    @Override
    public List evaluate(CollectorItem collectorItem, long beginDate, long endDate, Collection data) {
        return getPeerReviewResponses(collectorItem, new ArrayList<>(), beginDate, endDate);
    }

    @Override
    public List evaluate(CollectorItem collectorItem, List collectorItemList,
                                                  long beginDate, long endDate, Collection data) {
        return getPeerReviewResponses(collectorItem, collectorItemList, beginDate, endDate);
    }

    /**
     * Return an empty response in error situation
     *
     * @param repoItem  the repo item
     * @param scmBranch the scrm branch
     * @param scmUrl    the scm url
     * @return code review audit response
     */
    protected CodeReviewAuditResponse getErrorResponse(CollectorItem repoItem, String scmBranch, String scmUrl) {
        CodeReviewAuditResponse noPRsCodeReviewAuditResponse = new CodeReviewAuditResponse();
        noPRsCodeReviewAuditResponse.addAuditStatus(COLLECTOR_ITEM_ERROR);
        noPRsCodeReviewAuditResponse.setAuditEntity(repoItem.getOptions());
        noPRsCodeReviewAuditResponse.setLastUpdated(repoItem.getLastUpdated());
        noPRsCodeReviewAuditResponse.setScmBranch(scmBranch);
        noPRsCodeReviewAuditResponse.setScmUrl(scmUrl);
        noPRsCodeReviewAuditResponse.setErrorMessage(repoItem.getErrors().get(0).getErrorMessage());
        return noPRsCodeReviewAuditResponse;
    }

    private List getPeerReviewResponses(CollectorItem repoItem,
                                                                 List collectorItemList,
                                                                 long beginDt, long endDt) {
        List allPeerReviews = new ArrayList<>();

        if (repoItem == null) {
            CodeReviewAuditResponse codeReviewAuditResponse = new CodeReviewAuditResponse();
            codeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.REPO_NOT_CONFIGURED);

            allPeerReviews.add(codeReviewAuditResponse);
            return allPeerReviews;
        }

        String scmUrl = (String) repoItem.getOptions().get("url");
        String scmBranch = (String) repoItem.getOptions().get("branch");

        GitHubParsedUrl gitHubParsed = new GitHubParsedUrl(scmUrl);

        String parsedUrl = gitHubParsed.getUrl(); //making sure we have a goot url

        if (StringUtils.isEmpty(scmBranch) || StringUtils.isEmpty(scmUrl)) {
            CodeReviewAuditResponse noPRsCodeReviewAuditResponse = getErrorResponse(repoItem, scmBranch, parsedUrl);
            allPeerReviews.add(noPRsCodeReviewAuditResponse);
            return allPeerReviews;
        }

        //if there are errors on the collector item
        if (!CollectionUtils.isEmpty(repoItem.getErrors())) {
            CodeReviewAuditResponse noPRsCodeReviewAuditResponse = getErrorResponse(repoItem, scmBranch, parsedUrl);
            allPeerReviews.add(noPRsCodeReviewAuditResponse);
            return allPeerReviews;
        }

        //if the collector item is pending data collection
        if (repoItem.getLastUpdated() == 0) {
            CodeReviewAuditResponse noPRsCodeReviewAuditResponse = new CodeReviewAuditResponse();
            noPRsCodeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.PENDING_DATA_COLLECTION);
            noPRsCodeReviewAuditResponse.setAuditEntity(repoItem.getOptions());
            noPRsCodeReviewAuditResponse.setLastUpdated(repoItem.getLastUpdated());
            noPRsCodeReviewAuditResponse.setScmBranch(scmBranch);
            noPRsCodeReviewAuditResponse.setScmUrl(scmUrl);
            allPeerReviews.add(noPRsCodeReviewAuditResponse);
            return allPeerReviews;
        }

        List pullRequests = gitRequestRepository.findByCollectorItemIdAndMergedAtIsBetween(repoItem.getId(), beginDt-1, endDt+1);
        List allCommits = commitRepository.findByCollectorItemIdAndScmCommitTimestampIsBetween(repoItem.getId(), beginDt-1, endDt+1);
        //Filter empty commits
        List commits = allCommits.stream().filter(commit -> commit.getNumberOfChanges()>0).collect(Collectors.toList());
        commits.sort(Comparator.comparing(Commit::getScmCommitTimestamp).reversed());
        if (CollectionUtils.isEmpty(pullRequests)) {
            CodeReviewAuditResponse noPRsCodeReviewAuditResponse = new CodeReviewAuditResponse();
            noPRsCodeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.NO_PULL_REQ_FOR_DATE_RANGE);
            noPRsCodeReviewAuditResponse.setAuditEntity(repoItem.getOptions());
            allPeerReviews.add(noPRsCodeReviewAuditResponse);
        }

        //check for pr author <> pr merger
        //check to see if pr was reviewed
        //type of branching strategy
        List allPrCommitShas = new ArrayList<>();
        List mergeCommitShas = new ArrayList<>();
        pullRequests.stream().filter(pr -> "merged".equalsIgnoreCase(pr.getState())).forEach(pr -> {
            auditPullRequest(repoItem, pr, commits, mergeCommitShas, allPrCommitShas, allPeerReviews);
        });

        //check any commits not directly tied to pr
        CodeReviewAuditResponse codeReviewAuditResponse = new CodeReviewAuditResponse();
        codeReviewAuditResponse.setAuditEntity(repoItem.getOptions());
        List commitsNotDirectlyTiedToPr = new ArrayList<>();
        commits.forEach(commit -> {

            if (!checkPrCommitsAndMergeCommits(allPrCommitShas, commit, mergeCommitShas)) { return; }

            if (isCommitEligibleForDirectCommitsForPushedRepo(repoItem, commit, collectorItemList, beginDt, endDt)
                 || isCommitEligibleForDirectCommitsForPulledRepo(repoItem, commit)) {
                commitsNotDirectlyTiedToPr.add(commit);
                // auditServiceAccountChecks includes - check for service account and increment version tag for service account on direct commits.
                auditServiceAccountChecks(codeReviewAuditResponse, commit);
            }
        });

        if (!commitsNotDirectlyTiedToPr.isEmpty()) {
            codeReviewAuditResponse.setCommits(commitsNotDirectlyTiedToPr);
            allPeerReviews.add(codeReviewAuditResponse);
        }

        //pull requests in date range, but merged prior to 14 days so no commits available in hygieia
        if (!CollectionUtils.isEmpty(pullRequests)) {
            if (allPeerReviews.isEmpty()) {
                CodeReviewAuditResponse prsButNoCommitsInRangeCodeReviewAuditResponse = new CodeReviewAuditResponse();
                prsButNoCommitsInRangeCodeReviewAuditResponse.setAuditEntity(repoItem.getOptions());
                prsButNoCommitsInRangeCodeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.NO_PULL_REQ_FOR_DATE_RANGE);
                allPeerReviews.add(prsButNoCommitsInRangeCodeReviewAuditResponse);
            }
        }

        allPeerReviews.forEach(peerReviewResponseList -> {
            peerReviewResponseList.setLastUpdated(repoItem.getLastUpdated());
            peerReviewResponseList.setScmBranch(scmBranch);
            peerReviewResponseList.setScmUrl(parsedUrl);
        });
        return allPeerReviews;
    }

    private boolean checkPrCommitsAndMergeCommits(List allPrCommitShas, Commit commit, List mergeCommitShas) {
        if ( (!allPrCommitShas.contains(commit.getScmRevisionNumber()))
                && (commit.getType() == CommitType.New)
                && (!mergeCommitShas.contains(commit.getScmRevisionNumber())) ) {
            return true;
        }

        return false;
    }

    private boolean isCommitEligibleForDirectCommitsForPushedRepo(CollectorItem repoItem, Commit commit,
                                                                  List collectorItemList,
                                                                  long beginDt, long endDt) {
        if (repoItem.isPushed()
                && !existsApprovedPROnAnotherBranch(repoItem, commit, collectorItemList, beginDt, endDt)) {
            return true;
        }
        return false;
    }

    private boolean isCommitEligibleForDirectCommitsForPulledRepo(CollectorItem repoItem, Commit commit) {
        if (!repoItem.isPushed() && StringUtils.isEmpty(commit.getPullNumber())) {
            return true;
        }
        return false;
    }

    protected void auditPullRequest(CollectorItem repoItem, GitRequest pr, List commits,
                                    List mergeCommitShas, List allPrCommitShas,
                                    List allPeerReviews) {
        CodeReviewAuditResponse codeReviewAuditResponse = new CodeReviewAuditResponse();
        codeReviewAuditResponse.setAuditEntity(repoItem.getOptions());
        codeReviewAuditResponse.setPullRequest(pr);
        String mergeSha = pr.getScmRevisionNumber();

        Commit mergeCommit = Optional.ofNullable(commits)
                                .orElseGet(Collections::emptyList).stream()
                                .filter(c -> Objects.equals(c.getScmRevisionNumber(), mergeSha))
                                .findFirst().orElse(null);

        if (mergeCommit == null) {
            mergeCommit = Optional.ofNullable(commits)
                            .orElseGet(Collections::emptyList).stream()
                            .filter(c -> Objects.equals(c.getScmRevisionNumber(), pr.getScmMergeEventRevisionNumber()))
                            .findFirst().orElse(null);
        }

        List allCommitsRelatedToPr = pr.getCommits();
        List commitsRelatedToPr = allCommitsRelatedToPr.stream().filter(commit -> commit.getNumberOfChanges()>0).collect(Collectors.toList());
        commitsRelatedToPr.sort(Comparator.comparing(e -> (e.getScmCommitTimestamp())));
        if (mergeCommit == null) {
            codeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.MERGECOMMITER_NOT_FOUND);
        } else {
            mergeCommitShas.add(mergeCommit.getScmRevisionNumber());
            if (repoItem.isPushed()) {
                codeReviewAuditResponse.addAuditStatus(pr.getUserId().equalsIgnoreCase(mergeCommit.getScmCommitterLogin()) ? CodeReviewAuditStatus.COMMITAUTHOR_EQ_MERGECOMMITER : CodeReviewAuditStatus.COMMITAUTHOR_NE_MERGECOMMITER);
            } else {
                codeReviewAuditResponse.addAuditStatus(pr.getUserId().equalsIgnoreCase(mergeCommit.getScmAuthorLogin()) ? CodeReviewAuditStatus.COMMITAUTHOR_EQ_MERGECOMMITER : CodeReviewAuditStatus.COMMITAUTHOR_NE_MERGECOMMITER);
            }
        }
        codeReviewAuditResponse.setCommits(commitsRelatedToPr);

        allPrCommitShas.addAll(commitsRelatedToPr.stream().map(SCM::getScmRevisionNumber).collect(Collectors.toList()));

        boolean peerReviewed = CommonCodeReview.computePeerReviewStatus(pr, settings, codeReviewAuditResponse, commits, commitRepository, serviceAccountRepository);
        codeReviewAuditResponse.addAuditStatus(peerReviewed ? CodeReviewAuditStatus.PULLREQ_REVIEWED_BY_PEER : CodeReviewAuditStatus.PULLREQ_NOT_PEER_REVIEWED);
        String sourceRepo = pr.getSourceRepo();
        String targetRepo = pr.getTargetRepo();
        codeReviewAuditResponse.addAuditStatus(sourceRepo == null ? CodeReviewAuditStatus.GIT_FORK_STRATEGY : sourceRepo.equalsIgnoreCase(targetRepo) ? CodeReviewAuditStatus.GIT_BRANCH_STRATEGY : CodeReviewAuditStatus.GIT_FORK_STRATEGY);
        if (!StringUtils.isEmpty(pr.getMergeAuthorLDAPDN()) && (CommonCodeReview.checkForServiceAccount(pr.getMergeAuthorLDAPDN(), settings, getAllServiceAccounts(),pr.getMergeAuthor(),null,false,codeReviewAuditResponse))) {
            codeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.MERGECOMMITER_EQ_SERVICEACCOUNT);
        }
        allPeerReviews.add(codeReviewAuditResponse);
    }

    protected boolean existsApprovedPROnAnotherBranch(CollectorItem repoItem, Commit commit, List collectorItemList,
                                                      long beginDt, long endDt) {
        CollectorItem collectorItem = Optional.ofNullable(collectorItemList)
                                        .orElseGet(Collections::emptyList).stream()
                                        .filter(ci -> existsApprovedPRForCollectorItem(repoItem, commit, ci, beginDt, endDt))
                                        .findFirst().orElse(null);
        return (collectorItem != null);
    }

    protected boolean existsApprovedPRForCollectorItem(CollectorItem repoItem, Commit commit, CollectorItem collectorItem,
                                                       long beginDt, long endDt) {
        List mergedPullRequests
                = gitRequestRepository.findByCollectorItemIdAndMergedAtIsBetween(collectorItem.getId(), beginDt-1, endDt+1);

        if (CollectionUtils.isEmpty(mergedPullRequests)) { return false; }

        List commits
                = commitRepository.findByCollectorItemIdAndScmCommitTimestampIsBetween(collectorItem.getId(), beginDt-1, endDt+1);

        GitRequest mergedPullRequestFound
                = Optional.ofNullable(mergedPullRequests)
                    .orElseGet(Collections::emptyList).stream()
                    .filter(mergedPullRequest -> evaluateMergedPullRequest(repoItem, mergedPullRequest, commit, commits))
                    .findFirst().orElse(null);

        return (mergedPullRequestFound != null);
    }

    private boolean evaluateMergedPullRequest (CollectorItem repoItem, GitRequest mergedPullRequest,
                                               Commit commit, List commits) {
        Commit matchingCommit = findAMatchingCommit(mergedPullRequest, commit, commits);
        if (matchingCommit == null) { return false; }

        List allPrCommitShas = new ArrayList<>();
        List mergeCommitShas = new ArrayList<>();
        List allPeerReviews = new ArrayList<>();
        auditPullRequest(repoItem, mergedPullRequest, commits, mergeCommitShas, allPrCommitShas, allPeerReviews);
        CodeReviewAuditResponse codeReviewAuditResponse = allPeerReviews.get(0);

        if ((codeReviewAuditResponse != null)
                && codeReviewAuditResponseCheck(codeReviewAuditResponse)) { return true; }

        return false;
    }

    protected boolean codeReviewAuditResponseCheck(CodeReviewAuditResponse codeReviewAuditResponse) {
        for (CodeReviewAuditStatus status : codeReviewAuditResponse.getAuditStatuses()) {
            if ((status == CodeReviewAuditStatus.COMMITAUTHOR_EQ_MERGECOMMITER)
                    || (status == CodeReviewAuditStatus.PULLREQ_NOT_PEER_REVIEWED)) {
                return false;
            }
        }
        return true;
    }

    protected Commit findAMatchingCommit(GitRequest mergedPullRequest, Commit commitToBeFound, List commitsOnTheRepo) {
        List commitsRelatedToPr = mergedPullRequest.getCommits();

        // So, will find the matching commit based on the criteria below for "Merge Only" case.
        Commit matchingCommit
                = Optional.ofNullable(commitsRelatedToPr)
                    .orElseGet(Collections::emptyList).stream()
                    .filter(commitRelatedToPr -> checkIfCommitsMatch(commitRelatedToPr, commitToBeFound))
                    .findFirst().orElse(null);

        // For "Squash and Merge", or a "Rebase and Merge":
        // The merged commit will not be part of the commits in the PR.
        // The PR will only have the original commits when the PR was opened.
        // Search for the commit in the list of commits on the repo in the db
        if (matchingCommit == null) {
            String pullNumber = mergedPullRequest.getNumber();
            matchingCommit = Optional.ofNullable(commitsOnTheRepo)
                            .orElseGet(Collections::emptyList).stream()
                            .filter(commitOnRepo -> Objects.equals(pullNumber, commitToBeFound.getPullNumber())
                                                    && checkIfCommitsMatch(commitOnRepo, commitToBeFound))
                            .findFirst().orElse(null);
        }

        return matchingCommit;
    }

    protected boolean checkIfCommitsMatch(Commit commit1, Commit commit2) {
        if (Objects.equals(commit1.getScmRevisionNumber(), commit2.getScmRevisionNumber())
                && Objects.equals(commit1.getScmAuthor(), commit2.getScmAuthor())
                && Objects.equals(commit1.getScmCommitTimestamp(), commit2.getScmCommitTimestamp())
                && Objects.equals(commit1.getScmCommitLog(), commit2.getScmCommitLog())) {
            return true;
        }
        return false;
    }

    private void auditServiceAccountChecks(CodeReviewAuditResponse codeReviewAuditResponse, Commit commit) {
        // Check for improper login info and add appropriate status.
        if (StringUtils.isEmpty(commit.getScmAuthorLDAPDN())) {
            codeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.SCM_AUTHOR_LOGIN_INVALID);
        }

        auditDirectCommits(codeReviewAuditResponse, commit);
    }

    @SuppressWarnings("Duplicates")
    private void auditDirectCommits(CodeReviewAuditResponse codeReviewAuditResponse, Commit commit) {

        Stream combinedStream = Stream.of(commit.getFilesAdded(), commit.getFilesModified(),commit.getFilesRemoved()).filter(Objects::nonNull).flatMap(Collection::stream);
        List collectionCombined = combinedStream.collect(Collectors.toList());
        if (CommonCodeReview.checkForServiceAccount(commit.getScmAuthorLDAPDN(), settings,getAllServiceAccounts(),commit.getScmAuthor(),collectionCombined,true,codeReviewAuditResponse)) {
            codeReviewAuditResponse.addAuditStatus(CodeReviewAuditStatus.COMMITAUTHOR_EQ_SERVICEACCOUNT);
            // check for increment version tag and flag Direct commit by Service Account.
            auditIncrementVersionTag(codeReviewAuditResponse, commit, CodeReviewAuditStatus.DIRECT_COMMIT_NONCODE_CHANGE_SERVICE_ACCOUNT);
        }else if (StringUtils.isBlank(commit.getScmAuthorLDAPDN())) {
            // Status for commits without login information.
            auditIncrementVersionTag(codeReviewAuditResponse, commit, CodeReviewAuditStatus.DIRECT_COMMIT_NONCODE_CHANGE);
        }  else {
            // check for increment version tag and flag Direct commit by User Account.
            auditIncrementVersionTag(codeReviewAuditResponse, commit, CodeReviewAuditStatus.DIRECT_COMMIT_NONCODE_CHANGE_USER_ACCOUNT);
        }
    }

    private void auditIncrementVersionTag(CodeReviewAuditResponse codeReviewAuditResponse, Commit commit, CodeReviewAuditStatus directCommitIncrementVersionTagStatus) {
        if (CommonCodeReview.matchIncrementVersionTag(commit.getScmCommitLog(), settings)) {
            codeReviewAuditResponse.addAuditStatus(directCommitIncrementVersionTagStatus);
        } else {
            codeReviewAuditResponse.addAuditStatus(commit.isFirstEverCommit() ? CodeReviewAuditStatus.DIRECT_COMMITS_TO_BASE_FIRST_COMMIT : CodeReviewAuditStatus.DIRECT_COMMITS_TO_BASE);
        }
    }

    public Map getAllServiceAccounts(){
        List serviceAccounts = (List) serviceAccountRepository.findAll();
        return serviceAccounts.stream().collect(Collectors.toMap(ServiceAccount :: getServiceAccountName, ServiceAccount::getFileNames));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy