package pl.touk.sputnik.connector.stash;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.jayway.jsonpath.JsonPath;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.jetbrains.annotations.NotNull;
import pl.touk.sputnik.configuration.Configuration;
import pl.touk.sputnik.configuration.GeneralOption;
import pl.touk.sputnik.configuration.GeneralOptionNotSupportedException;
import pl.touk.sputnik.connector.ConnectorFacade;
import pl.touk.sputnik.connector.Connectors;
import pl.touk.sputnik.connector.stash.json.Anchor;
import pl.touk.sputnik.connector.stash.json.Comment;
import pl.touk.sputnik.connector.stash.json.DiffSegment;
import pl.touk.sputnik.connector.stash.json.FileComment;
import pl.touk.sputnik.connector.stash.json.LineComment;
import pl.touk.sputnik.connector.stash.json.LineSegment;
import pl.touk.sputnik.connector.stash.json.ReviewElement;
import pl.touk.sputnik.review.Review;
import pl.touk.sputnik.review.ReviewFile;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class StashFacade implements ConnectorFacade {
private StashConnector stashConnector;
private ObjectMapper objectMapper = new ObjectMapper();
private final Configuration configuration;
public StashFacade(@NotNull StashConnector stashConnector, Configuration configuration) {
this.stashConnector = stashConnector;
this.configuration = configuration;
}
@Override
public Connectors name() {
return Connectors.STASH;
}
@NotNull
@Override
public List<ReviewFile> listFiles() {
try {
String response = stashConnector.listFiles();
List<JSONObject> jsonList = JsonPath.read(response, "$.values[?(@.type != 'DELETE')].path");
List<ReviewElement> containers = transform(jsonList, ReviewElement.class);
List<ReviewFile> files = new ArrayList<>();
for (ReviewElement container : containers) {
String filePath = getFilePath(container);
files.add(new ReviewFile(filePath));
}
return files;
} catch (URISyntaxException | IOException e) {
throw new StashException("Error when listing files", e);
}
}
private String getFilePath(ReviewElement container) {
String filePath;
if (container.parent.isEmpty()) {
filePath = container.name;
} else {
filePath = String.format("%s/%s", container.parent, container.name);
}
return filePath;
}
@Override
public void publish(@NotNull Review review) {
sendFileComments(review);
try {
String json = objectMapper.writeValueAsString(new Comment(Joiner.on(". ").join(review.getMessages())));
stashConnector.sendReview(json);
} catch (URISyntaxException | IOException e) {
throw new StashException("Error while publishing review", e);
}
}
private void sendFileComments(Review review) {
boolean commentOnlyChangedLines = Boolean.parseBoolean(configuration.getProperty(GeneralOption.COMMENT_ONLY_CHANGED_LINES));
for (ReviewFile reviewFile : review.getFiles()) {
SingleFileChanges changes = changesForSingleFile(reviewFile.getReviewFilename());
for (pl.touk.sputnik.review.Comment comment : reviewFile.getComments()) {
if (noCommentExists(changes, comment)) {
ChangeType changeType = getChangeType(changes, comment.getLine());
if (changeType.equals(ChangeType.NONE) && commentOnlyChangedLines) {
log.info("Not posting out of context warning: {}", comment.getMessage());
continue;
}
try {
String json = objectMapper.writeValueAsString(toFileComment(reviewFile.getReviewFilename(), comment, changeType));
stashConnector.sendReview(json);
} catch (URISyntaxException | IOException e) {
throw new StashException("Error setting review", e);
}
}
}
}
}
private boolean noCommentExists(SingleFileChanges changes, pl.touk.sputnik.review.Comment comment) {
return !changes.containsComment(comment.getLine(), comment.getMessage());
}
private ChangeType getChangeType(SingleFileChanges changes, Integer line) {
if (changes.containsChange(line)) {
return changes.getChangeType(line);
}
return ChangeType.NONE;
}
private FileComment toFileComment(String key, pl.touk.sputnik.review.Comment comment, ChangeType changeType) {
FileComment fileComment = new FileComment();
fileComment.setText(comment.getMessage());
Anchor anchor = Anchor.builder().
path(key).
srcPath(key).
line(comment.getLine()).
lineType(changeType.getNameForStash()).
build();
fileComment.setAnchor(anchor);
return fileComment;
}
private <T> List<T> transform(List<JSONObject> jsonList, Class<T> someClass) {
List<T> result = Lists.newArrayList();
try {
for (JSONObject jsonObject : jsonList) {
result.add(objectMapper.readValue(jsonObject.toJSONString(), someClass));
}
} catch (IOException e) {
throw new StashException("Error parsing json strings to objects", e);
}
return result;
}
SingleFileChanges changesForSingleFile(String filename) {
try {
String response = stashConnector.getDiffByLine(filename);
List<JSONObject> diffJsonList = JsonPath.read(response, "$.diffs[*].hunks[*].segments[*]");
List<JSONObject> lineCommentJsonList = JsonPath.read(response, "$.diffs[*].lineComments[*]['text', 'id']");
List<DiffSegment> segments = transform(diffJsonList, DiffSegment.class);
List<LineComment> lineComments = transform(lineCommentJsonList, LineComment.class);
SingleFileChanges changes = SingleFileChanges.builder().filename(filename).build();
for (DiffSegment segment : segments) {
for (LineSegment line : segment.lines) {
changes.addChange(line.destination, ChangeType.valueOf(segment.type), getComment(lineComments, line.commentIds));
}
}
return changes;
} catch (URISyntaxException | IOException e) {
throw new StashException("Error parsing json strings to objects", e);
}
}
private List<String> getComment(List<LineComment> lineComments, List<Integer> commentIds) {
List<String> comments = new ArrayList<>();
if (commentIds != null) {
for (LineComment lineComment : lineComments) {
if (commentIds.contains(lineComment.id)) {
comments.add(lineComment.text);
}
}
}
return comments;
}
@Override
public void validate(Configuration configuration) throws GeneralOptionNotSupportedException {
// all features are supported by Stash
}
@Override
public void setReview(@NotNull Review review) {
publish(review);
}
}