package com.groupon.jenkins.dotci.plugins;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.groupon.jenkins.buildtype.plugins.DotCiPluginAdapter;
import com.groupon.jenkins.dotci.patch.PatchFile;
import com.groupon.jenkins.dotci.patch.PatchHunk;
import com.groupon.jenkins.dotci.patch.PatchLine;
import com.groupon.jenkins.dotci.patch.PatchParser;
import com.groupon.jenkins.dynamic.build.DynamicBuild;
import com.groupon.jenkins.github.services.GithubRepositoryService;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.plugins.analysis.collector.AnalysisPublisher;
import hudson.plugins.analysis.collector.AnalysisResultAction;
import hudson.plugins.analysis.core.AbstractResultAction;
import hudson.plugins.analysis.core.BuildResult;
import hudson.plugins.analysis.util.model.FileAnnotation;
import hudson.plugins.cobertura.CoberturaBuildAction;
import hudson.plugins.cobertura.targets.CoveragePaint;
import hudson.plugins.cobertura.targets.CoverageResult;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHPullRequestReviewComment;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.PagedIterable;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Extension
public class AnalysisCollectorPluginAdapter extends DotCiPluginAdapter {
public AnalysisCollectorPluginAdapter() {
super("review_line_comments", null);
}
@Override
public boolean perform(final DynamicBuild dynamicBuild, final Launcher launcher, final BuildListener listener) {
try {
new AnalysisPublisher().perform(((AbstractBuild) dynamicBuild), launcher, listener);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
} catch (final IOException e) {
throw new RuntimeException(e);
}
if (dynamicBuild.isPullRequest()) {
try {
final int prNumber = Integer.parseInt(dynamicBuild.getCause().getPullRequestNumber());
final List<PatchFile> patchFiles = new PatchParser(listener).getLines(dynamicBuild.getGithubRepoUrl(), prNumber);
final List<LineComment> lineComments = new ArrayList<>();
final AbstractResultAction action = dynamicBuild.getAction(AnalysisResultAction.class);
if (action == null) return true;
final BuildResult anaylsisResult = action.getResult();
final CoberturaBuildAction cobeturaAction = dynamicBuild.getAction(CoberturaBuildAction.class);
final CoverageResult coberturaResult = cobeturaAction == null ? null : cobeturaAction.getResult();
final PrintStream logger = listener.getLogger();
for (final PatchFile file : patchFiles) {
final String fileName = file.getFilename();
for (final PatchHunk hunk : file.getHunks()) {
if (coberturaResult != null) {
final LineComment coverageComment = coverageComment(hunk, fileName, coberturaResult);
if (coverageComment != null) {
lineComments.add(coverageComment);
}
}
final List<LineComment> analysisComments = getAnalysisComments(anaylsisResult, fileName, hunk);
lineComments.addAll(analysisComments);
}
}
final GHRepository repo = new GithubRepositoryService(dynamicBuild.getGithubRepoUrl()).getGithubRepository();
final GHPullRequest pullRequest = repo.getPullRequest(prNumber);
final PagedIterable<GHPullRequestReviewComment> allReviewComments = pullRequest.listReviewComments();
for (final LineComment comment : lineComments) {
makeComment(allReviewComments, logger, pullRequest, comment);
}
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
return true;
}
private void makeComment(final PagedIterable<GHPullRequestReviewComment> allReviewComments, final PrintStream logger, final GHPullRequest pullRequest, final LineComment comment) throws IOException {
final GHPullRequestReviewComment existingComment = Iterables.find(allReviewComments, new Predicate<GHPullRequestReviewComment>() {
@Override
public boolean apply(final GHPullRequestReviewComment reviewComment) {
return comment.isSameAs(reviewComment);
}
}, null);
if (existingComment == null) {
logger.println("Commenting on " + comment.line.getLineNo() + " at Pos: " + comment.line.getPos());
pullRequest.createReviewComment(comment.comment, pullRequest.getHead().getSha(), comment.fileName, comment.line.getPos());
}
}
private List<LineComment> getAnalysisComments(final BuildResult anaylsisResult, final String fileName, final PatchHunk hunk) {
final List<LineComment> lineComments = new ArrayList<>();
for (final PatchLine line : hunk.getLines()) {
final FileAnnotation annotation = findAnnotation(anaylsisResult, fileName, line.getLineNo());
if (annotation != null) {
final String message = annotation.getMessage();
lineComments.add(new LineComment(line, message, fileName));
}
}
return lineComments;
}
private LineComment coverageComment(final PatchHunk hunk, final String fileName, final CoverageResult coverageResult) {
final Map<String, CoveragePaint> paintedSources = coverageResult.getPaintedSources();
for (final String coverageFileName : paintedSources.keySet()) {
if (fileName.contains(coverageFileName)) {
final CoveragePaint coverage = paintedSources.get(coverageFileName);
final List<String> unCoveredLines = new ArrayList<>();
for (final PatchLine line : hunk.getLines()) {
if (coverage.getHits(line.getLineNo()) == 0)
unCoveredLines.add(line.getLineNo() + "");
}
if (unCoveredLines.size() > 0) {
final String message = "Missing coverage for line(s) : ```" + Joiner.on(" ").join(unCoveredLines) + "```";
return new LineComment(Iterables.getLast(hunk.getLines()), message, fileName);
}
}
}
return null;
}
private FileAnnotation findAnnotation(final BuildResult buildResult, final String fileName, final int lineNo) {
for (final FileAnnotation annotation : buildResult.getAnnotations()) {
if (annotation.getFileName().endsWith(fileName)) {
if (annotation.getPrimaryLineNumber() == lineNo) {
return annotation;
}
// for(LineRange range : annotation.getLineRanges()){
//// if(range.getStart() <= lineNo && range.getEnd() < lineNo){
//// return annotation;
//// }
// }
}
}
return null;
}
private static class LineComment {
public PatchLine line;
public String comment;
public String fileName;
public LineComment(final PatchLine line, final String comment, final String fileName) {
this.line = line;
this.comment = comment;
this.fileName = fileName;
}
public boolean isSameAs(final GHPullRequestReviewComment reviewComment) {
return reviewComment.getPosition() == this.line.getPos()
&& this.comment.equals(reviewComment.getBody())
&& this.fileName.equals(reviewComment.getPath());
}
}
}