Skip to content

Commit

Permalink
feat: implement codecheck api
Browse files Browse the repository at this point in the history
  • Loading branch information
balrok committed Oct 27, 2023
1 parent 93743a3 commit f793e10
Show file tree
Hide file tree
Showing 18 changed files with 249 additions and 122 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ on: # yamllint disable-line rule:truthy
jobs:
build:
name: "build"
permissions:
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -90,7 +88,8 @@ jobs:
- name: Qualityannotate
if: success() && steps.findPr.outputs.number
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
# ${{ secrets.TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PULL_REQUEST: ${{ steps.findPr.outputs.pr }}
GITHUB_PROJECT: balrok/qualityannotate # ${{env.GITHUB_ACTION_REPOSITORY}}
SONARQUBE_PROJECT: quyt_qualityannotate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import java.util.List;

import org.qualityannotate.api.qualitytool.MetricsAndIssues;

public interface CodeRepository {
void createOrUpdateAnnotations(Comment globalComment, List<FileComment> fileComments) throws Exception;
void createOrUpdateAnnotations(Comment globalComment, List<FileComment> fileComments,
MetricsAndIssues metricsAndIssues) throws Exception;

String printConfigWithoutSecrets();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.qualityannotate.api.coderepository;

import org.qualityannotate.api.coderepository.api.CodeStructuredApi;
import org.qualityannotate.api.qualitytool.MetricsAndIssues;

public final class CodeStructuredExecutor {
private CodeStructuredExecutor() {

}

public static void run(CodeStructuredApi codeStructuredApi, MetricsAndIssues metricsAndIssues) throws Exception {
codeStructuredApi.update(metricsAndIssues);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,25 @@
import org.apache.commons.lang3.tuple.Pair;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;

import org.qualityannotate.api.coderepository.api.CodeApi;
import org.qualityannotate.api.coderepository.api.CodeFileComment;
import org.qualityannotate.api.coderepository.api.CodeMainComment;
import org.qualityannotate.api.coderepository.api.CodeStructuredApi;
import org.qualityannotate.api.coderepository.api.CodeTextApi;
import org.qualityannotate.api.coderepository.api.CodeTextFileComment;
import org.qualityannotate.api.coderepository.api.CodeTextMainComment;
import org.qualityannotate.api.qualitytool.MetricsAndIssues;

public abstract class AbstractCodeRepository implements CodeRepository {
public final class CodeTextExecutor {
private CodeTextExecutor() {

protected static Map<Pair<String, Integer>, String> convertFileCommentsToMap(List<FileComment> fileComments) {
Map<Pair<String, Integer>, List<Comment>> fileLineToCommentList = new HashMap<>();
for (FileComment fileComment : fileComments) {
List<Comment> comments = fileLineToCommentList
.computeIfAbsent(Pair.of(fileComment.fileName(), fileComment.linenumber()), k -> new ArrayList<>());
comments.add(fileComment.comment());
}
return fileLineToCommentList.entrySet()
.stream()
.map(e -> Map.entry(e.getKey(), e.getValue().stream().reduce((c1, c2) -> c1).orElse(Comment.EMPTY)))
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().markdown()));
}

protected abstract CodeApi getCodeApi();

@Override
public void createOrUpdateAnnotations(Comment globalComment, List<FileComment> fileComments) {
CodeApi codeApi = getCodeApi();
Optional<CodeMainComment> mainComment = Optional.empty();
public static void run(CodeTextApi codeTextApi, Comment globalComment, List<FileComment> fileComments)
throws Exception {
Optional<CodeTextMainComment> mainComment = Optional.empty();
try {
mainComment = codeApi.getMainComment();
mainComment = codeTextApi.getMainComment();
} catch (IOException e) {
Log.warn("Could not retrieve main comment");
}
Expand All @@ -50,20 +34,20 @@ public void createOrUpdateAnnotations(Comment globalComment, List<FileComment> f
}
}, () -> {
try {
codeApi.createMainComment(globalComment.markdown());
codeTextApi.createMainComment(globalComment.markdown());
} catch (IOException e) {
Log.warn("Could not create main comment", e);
}
});

Map<Pair<String, Integer>, String> fileLineToComment = convertFileCommentsToMap(fileComments);
List<CodeFileComment> githubFileComments = Collections.emptyList();
List<CodeTextFileComment> githubFileComments = Collections.emptyList();
try {
githubFileComments = codeApi.listFileComments();
githubFileComments = codeTextApi.listFileComments();
} catch (IOException e) {
Log.warn("Could not retrieve comments");
}
for (CodeFileComment review : githubFileComments) {
for (CodeTextFileComment review : githubFileComments) {
String comment = fileLineToComment.get(review.getFileLine());
Log.info("Found existing file comment");
if (comment != null) {
Expand All @@ -90,11 +74,29 @@ public void createOrUpdateAnnotations(Comment globalComment, List<FileComment> f
for (Map.Entry<Pair<String, Integer>, String> fileLineCommentEntry : fileLineToComment.entrySet()) {
try {
Log.infof("Creating %s", fileLineCommentEntry.getKey());
codeApi.createFileComment(fileLineCommentEntry.getKey().getLeft(),
codeTextApi.createFileComment(fileLineCommentEntry.getKey().getLeft(),
fileLineCommentEntry.getKey().getRight(), fileLineCommentEntry.getValue());
} catch (IOException e) {
Log.warn("Could not create comment", e);
}
}
}

private static Map<Pair<String, Integer>, String> convertFileCommentsToMap(List<FileComment> fileComments) {
Map<Pair<String, Integer>, List<Comment>> fileLineToCommentList = new HashMap<>();
for (FileComment fileComment : fileComments) {
List<Comment> comments = fileLineToCommentList
.computeIfAbsent(Pair.of(fileComment.fileName(), fileComment.linenumber()), k -> new ArrayList<>());
comments.add(fileComment.comment());
}
return fileLineToCommentList.entrySet()
.stream()
.map(e -> Map.entry(e.getKey(), e.getValue().stream().reduce((c1, c2) -> c1).orElse(Comment.EMPTY)))
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().markdown()));
}

private static void runCodeStructuredApi(CodeStructuredApi codeStructuredApi, MetricsAndIssues metricsAndIssues)
throws Exception {
codeStructuredApi.update(metricsAndIssues);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.qualityannotate.api.coderepository.api;

import java.io.IOException;

import org.qualityannotate.api.qualitytool.MetricsAndIssues;

/**
* Abstraction for the API of Code repositories like Gitlab, github, bitbucket, etc.
* Can be used best for APIs which are structured and know about issues with severity. So github-checks
* api and bitbucket-report api are well suited.
*/
public interface CodeStructuredApi {
void update(MetricsAndIssues metricsAndIssues) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.qualityannotate.api.coderepository.api;

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

/**
* Abstraction for the API of Code repositories like Gitlab, github, bitbucket, etc.
* Can be used best for APIs which are only text-based. So github-checks api and bitbucket-report api should use a
* different interface.
*/
public interface CodeTextApi {
Optional<CodeTextMainComment> getMainComment() throws IOException;

void createMainComment(String comment) throws IOException;

List<CodeTextFileComment> listFileComments() throws IOException;

void createFileComment(String file, Integer line, String comment) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import java.io.IOException;

public interface CodeFileComment {
public interface CodeTextFileComment {
void update(String comment) throws IOException;

void delete() throws IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

import java.io.IOException;

public interface CodeMainComment {
public interface CodeTextMainComment {
void update(String comment) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

import java.util.Map;

public record GlobalMetrics(Map<String, String> metrics) {
public record GlobalMetrics(Map<String, String> metrics, String url) {
}
7 changes: 6 additions & 1 deletion src/main/java/org/qualityannotate/api/qualitytool/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@

import jakarta.annotation.Nullable;

public record Issue(String fileName, Integer lineNumber, String comment, String severity, @Nullable String urlToIssue) {
public record Issue(String fileName, Integer lineNumber, String comment, String severity, String severityIcon,
Severity severityEnum, @Nullable String urlToIssue) {

public enum Severity {
LOW, MEDIUM, HIGH
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,38 @@

import jakarta.enterprise.context.ApplicationScoped;

import org.qualityannotate.api.coderepository.AbstractCodeRepository;
import java.util.List;

import org.qualityannotate.api.coderepository.CodeRepository;
import org.qualityannotate.api.coderepository.api.CodeApi;
import org.qualityannotate.coderepo.github.client.GithubApi;
import org.qualityannotate.api.coderepository.CodeStructuredExecutor;
import org.qualityannotate.api.coderepository.Comment;
import org.qualityannotate.api.coderepository.FileComment;
import org.qualityannotate.api.qualitytool.MetricsAndIssues;
import org.qualityannotate.coderepo.github.client.GithubStructuredApi;
import org.qualityannotate.coderepo.github.client.GithubTextApi;

@ApplicationScoped
public class GithubCodeRepository extends AbstractCodeRepository implements CodeRepository {
public class GithubCodeRepository implements CodeRepository {
private final GithubConfig config;
private final GithubApi githubApi;
private final GithubTextApi githubTextsApi;
private final GithubStructuredApi githubStructuredApi;

public GithubCodeRepository(GithubConfig config) {
this.config = config;
this.githubApi = new GithubApi(config.token(), config.project(), config.pullRequest());
this.githubTextsApi = new GithubTextApi(config);
this.githubStructuredApi = new GithubStructuredApi(config);
}

@Override
public String printConfigWithoutSecrets() {
return config.printWithoutSecrets();
public void createOrUpdateAnnotations(Comment globalComment, List<FileComment> fileComments,
MetricsAndIssues metricsAndIssues) throws Exception {
CodeStructuredExecutor.run(githubStructuredApi, metricsAndIssues);
// CodeTextExecutor.run(githubTextsApi, globalComment, fileComments);
}

@Override
protected CodeApi getCodeApi() {
return githubApi;
public String printConfigWithoutSecrets() {
return config.printWithoutSecrets();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.qualityannotate.coderepo.github.client;

import org.kohsuke.github.*;

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

import org.qualityannotate.api.coderepository.api.CodeStructuredApi;
import org.qualityannotate.api.qualitytool.Issue;
import org.qualityannotate.api.qualitytool.MetricsAndIssues;
import org.qualityannotate.coderepo.github.GithubConfig;
import org.qualityannotate.core.CommentProcessor;

public class GithubStructuredApi implements CodeStructuredApi {
public static final String NAME = "qualityannotate";
private final GitHubBuilder gitHubBuilder;
private final String project;
private final int pullRequestId;
/**
* Guaranteed to be not null by {@link #init()}
*/
private GitHub github = null;

public GithubStructuredApi(GithubConfig config) {
gitHubBuilder = new GitHubBuilder().withOAuthToken(config.token());
this.project = config.project();
this.pullRequestId = config.pullRequest();
}

private static Optional<GHCheckRun> findCheckRun(GHRepository repository, GHPullRequest pullRequest)
throws IOException {
PagedIterator<GHCheckRun> iterator = repository.getCheckRuns(pullRequest.getHead().getSha()).iterator();
while (iterator.hasNext()) {
GHCheckRun next = iterator.next();
if (next.getName().equals(NAME)) {
return Optional.of(next);
}
}
return Optional.empty();
}

private void init() throws IOException {
if (github == null) {
github = gitHubBuilder.build();
github.checkApiUrlValidity();
}
}

@Override
public void update(MetricsAndIssues metricsAndIssues) throws IOException {
init();
GHRepository repository = github.getRepository(project);
GHPullRequest pullRequest = repository.getPullRequest(pullRequestId);
Optional<GHCheckRun> checkRunOpt = findCheckRun(repository, pullRequest);
if (checkRunOpt.isPresent()) {
GHCheckRun checkRun = checkRunOpt.get();
GHCheckRunBuilder checkRunBuilder = checkRun.update();
updateCheckRun(checkRunBuilder, metricsAndIssues);
} else {
GHCheckRunBuilder checkRunBuilder = repository.createCheckRun(NAME, pullRequest.getHead().getSha());
updateCheckRun(checkRunBuilder, metricsAndIssues);
}
}

private void updateCheckRun(GHCheckRunBuilder checkRunBuilder, MetricsAndIssues metricsAndIssues)
throws IOException {
checkRunBuilder.withDetailsURL(metricsAndIssues.globalMetrics().url());
checkRunBuilder.withStatus(GHCheckRun.Status.COMPLETED);
checkRunBuilder.withConclusion(GHCheckRun.Conclusion.NEUTRAL);
GHCheckRunBuilder.Output output = new GHCheckRunBuilder.Output("Result",
CommentProcessor.createGlobalComment(metricsAndIssues.globalMetrics()).markdown());
List<Issue> issues = metricsAndIssues.issues();
for (Issue issue : issues) {
output.add(new GHCheckRunBuilder.Annotation(issue.fileName(), issue.lineNumber(),
mapSeverity(issue.severityEnum()), CommentProcessor.createComment(issue).comment().markdown()));
}
checkRunBuilder.add(output);
checkRunBuilder.create();
}

private GHCheckRun.AnnotationLevel mapSeverity(Issue.Severity severity) {
return switch (severity) {
case LOW -> GHCheckRun.AnnotationLevel.NOTICE;
case MEDIUM -> GHCheckRun.AnnotationLevel.WARNING;
case HIGH -> GHCheckRun.AnnotationLevel.FAILURE;
};
}
}
Loading

0 comments on commit f793e10

Please sign in to comment.