diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64f97e8..452cdd4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 @@ -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 diff --git a/src/main/java/org/qualityannotate/api/coderepository/CodeRepository.java b/src/main/java/org/qualityannotate/api/coderepository/CodeRepository.java index cfc7af5..e55246f 100644 --- a/src/main/java/org/qualityannotate/api/coderepository/CodeRepository.java +++ b/src/main/java/org/qualityannotate/api/coderepository/CodeRepository.java @@ -2,8 +2,11 @@ import java.util.List; +import org.qualityannotate.api.qualitytool.MetricsAndIssues; + public interface CodeRepository { - void createOrUpdateAnnotations(Comment globalComment, List fileComments) throws Exception; + void createOrUpdateAnnotations(Comment globalComment, List fileComments, + MetricsAndIssues metricsAndIssues) throws Exception; String printConfigWithoutSecrets(); } diff --git a/src/main/java/org/qualityannotate/api/coderepository/CodeStructuredExecutor.java b/src/main/java/org/qualityannotate/api/coderepository/CodeStructuredExecutor.java new file mode 100644 index 0000000..da26cd3 --- /dev/null +++ b/src/main/java/org/qualityannotate/api/coderepository/CodeStructuredExecutor.java @@ -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); + } +} diff --git a/src/main/java/org/qualityannotate/api/coderepository/AbstractCodeRepository.java b/src/main/java/org/qualityannotate/api/coderepository/CodeTextExecutor.java similarity index 67% rename from src/main/java/org/qualityannotate/api/coderepository/AbstractCodeRepository.java rename to src/main/java/org/qualityannotate/api/coderepository/CodeTextExecutor.java index 984ec9c..17c2af7 100644 --- a/src/main/java/org/qualityannotate/api/coderepository/AbstractCodeRepository.java +++ b/src/main/java/org/qualityannotate/api/coderepository/CodeTextExecutor.java @@ -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, String> convertFileCommentsToMap(List fileComments) { - Map, List> fileLineToCommentList = new HashMap<>(); - for (FileComment fileComment : fileComments) { - List 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 fileComments) { - CodeApi codeApi = getCodeApi(); - Optional mainComment = Optional.empty(); + public static void run(CodeTextApi codeTextApi, Comment globalComment, List fileComments) + throws Exception { + Optional mainComment = Optional.empty(); try { - mainComment = codeApi.getMainComment(); + mainComment = codeTextApi.getMainComment(); } catch (IOException e) { Log.warn("Could not retrieve main comment"); } @@ -50,20 +34,20 @@ public void createOrUpdateAnnotations(Comment globalComment, List f } }, () -> { try { - codeApi.createMainComment(globalComment.markdown()); + codeTextApi.createMainComment(globalComment.markdown()); } catch (IOException e) { Log.warn("Could not create main comment", e); } }); Map, String> fileLineToComment = convertFileCommentsToMap(fileComments); - List githubFileComments = Collections.emptyList(); + List 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) { @@ -90,11 +74,29 @@ public void createOrUpdateAnnotations(Comment globalComment, List f for (Map.Entry, 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, String> convertFileCommentsToMap(List fileComments) { + Map, List> fileLineToCommentList = new HashMap<>(); + for (FileComment fileComment : fileComments) { + List 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); + } } diff --git a/src/main/java/org/qualityannotate/api/coderepository/api/CodeApi.java b/src/main/java/org/qualityannotate/api/coderepository/api/CodeApi.java deleted file mode 100644 index 7e7337d..0000000 --- a/src/main/java/org/qualityannotate/api/coderepository/api/CodeApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.qualityannotate.api.coderepository.api; - -import java.io.IOException; -import java.util.List; -import java.util.Optional; - -public interface CodeApi { - Optional getMainComment() throws IOException; - - void createMainComment(String comment) throws IOException; - - List listFileComments() throws IOException; - - void createFileComment(String file, Integer line, String comment) throws IOException; -} diff --git a/src/main/java/org/qualityannotate/api/coderepository/api/CodeStructuredApi.java b/src/main/java/org/qualityannotate/api/coderepository/api/CodeStructuredApi.java new file mode 100644 index 0000000..3c3dccd --- /dev/null +++ b/src/main/java/org/qualityannotate/api/coderepository/api/CodeStructuredApi.java @@ -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; +} diff --git a/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextApi.java b/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextApi.java new file mode 100644 index 0000000..f5bed86 --- /dev/null +++ b/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextApi.java @@ -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 getMainComment() throws IOException; + + void createMainComment(String comment) throws IOException; + + List listFileComments() throws IOException; + + void createFileComment(String file, Integer line, String comment) throws IOException; +} diff --git a/src/main/java/org/qualityannotate/api/coderepository/api/CodeFileComment.java b/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextFileComment.java similarity index 88% rename from src/main/java/org/qualityannotate/api/coderepository/api/CodeFileComment.java rename to src/main/java/org/qualityannotate/api/coderepository/api/CodeTextFileComment.java index 13f2fc1..3abcc98 100644 --- a/src/main/java/org/qualityannotate/api/coderepository/api/CodeFileComment.java +++ b/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextFileComment.java @@ -4,7 +4,7 @@ import java.io.IOException; -public interface CodeFileComment { +public interface CodeTextFileComment { void update(String comment) throws IOException; void delete() throws IOException; diff --git a/src/main/java/org/qualityannotate/api/coderepository/api/CodeMainComment.java b/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextMainComment.java similarity index 77% rename from src/main/java/org/qualityannotate/api/coderepository/api/CodeMainComment.java rename to src/main/java/org/qualityannotate/api/coderepository/api/CodeTextMainComment.java index 960945c..c553e5e 100644 --- a/src/main/java/org/qualityannotate/api/coderepository/api/CodeMainComment.java +++ b/src/main/java/org/qualityannotate/api/coderepository/api/CodeTextMainComment.java @@ -2,6 +2,6 @@ import java.io.IOException; -public interface CodeMainComment { +public interface CodeTextMainComment { void update(String comment) throws IOException; } diff --git a/src/main/java/org/qualityannotate/api/qualitytool/GlobalMetrics.java b/src/main/java/org/qualityannotate/api/qualitytool/GlobalMetrics.java index 084d15f..eec570b 100644 --- a/src/main/java/org/qualityannotate/api/qualitytool/GlobalMetrics.java +++ b/src/main/java/org/qualityannotate/api/qualitytool/GlobalMetrics.java @@ -2,5 +2,5 @@ import java.util.Map; -public record GlobalMetrics(Map metrics) { +public record GlobalMetrics(Map metrics, String url) { } diff --git a/src/main/java/org/qualityannotate/api/qualitytool/Issue.java b/src/main/java/org/qualityannotate/api/qualitytool/Issue.java index 170233b..bc5e69c 100644 --- a/src/main/java/org/qualityannotate/api/qualitytool/Issue.java +++ b/src/main/java/org/qualityannotate/api/qualitytool/Issue.java @@ -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 + } } diff --git a/src/main/java/org/qualityannotate/coderepo/github/GithubCodeRepository.java b/src/main/java/org/qualityannotate/coderepo/github/GithubCodeRepository.java index fa581a8..f8c1602 100644 --- a/src/main/java/org/qualityannotate/coderepo/github/GithubCodeRepository.java +++ b/src/main/java/org/qualityannotate/coderepo/github/GithubCodeRepository.java @@ -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 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(); } + } diff --git a/src/main/java/org/qualityannotate/coderepo/github/client/GithubStructuredApi.java b/src/main/java/org/qualityannotate/coderepo/github/client/GithubStructuredApi.java new file mode 100644 index 0000000..2a112f7 --- /dev/null +++ b/src/main/java/org/qualityannotate/coderepo/github/client/GithubStructuredApi.java @@ -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 findCheckRun(GHRepository repository, GHPullRequest pullRequest) + throws IOException { + PagedIterator 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 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 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; + }; + } +} diff --git a/src/main/java/org/qualityannotate/coderepo/github/client/GithubApi.java b/src/main/java/org/qualityannotate/coderepo/github/client/GithubTextApi.java similarity index 75% rename from src/main/java/org/qualityannotate/coderepo/github/client/GithubApi.java rename to src/main/java/org/qualityannotate/coderepo/github/client/GithubTextApi.java index 759087d..f7859f7 100644 --- a/src/main/java/org/qualityannotate/coderepo/github/client/GithubApi.java +++ b/src/main/java/org/qualityannotate/coderepo/github/client/GithubTextApi.java @@ -2,24 +2,19 @@ import io.quarkus.logging.Log; import org.apache.commons.lang3.tuple.Pair; -import org.kohsuke.github.GHIssueComment; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GHPullRequestReviewComment; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GitHubBuilder; -import org.kohsuke.github.PagedIterator; +import org.kohsuke.github.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; -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.CodeTextApi; +import org.qualityannotate.api.coderepository.api.CodeTextFileComment; +import org.qualityannotate.api.coderepository.api.CodeTextMainComment; +import org.qualityannotate.coderepo.github.GithubConfig; -public class GithubApi implements CodeApi { +public class GithubTextApi implements CodeTextApi { private final GitHubBuilder gitHubBuilder; private final String project; private final int pullRequestId; @@ -33,10 +28,10 @@ public class GithubApi implements CodeApi { */ private long userId = 0L; - public GithubApi(String token, String project, int pullRequestId) { - gitHubBuilder = new GitHubBuilder().withOAuthToken(token); - this.project = project; - this.pullRequestId = pullRequestId; + public GithubTextApi(GithubConfig config) { + gitHubBuilder = new GitHubBuilder().withOAuthToken(config.token()); + this.project = config.project(); + this.pullRequestId = config.pullRequest(); } private void init() throws IOException { @@ -57,8 +52,7 @@ private GHPullRequest getPR() throws IOException { } @Override - public Optional getMainComment() throws IOException { - init(); + public Optional getMainComment() throws IOException { GHPullRequest pullRequest = getPR(); PagedIterator commentIterator = pullRequest.listComments().iterator(); while (commentIterator.hasNext()) { @@ -75,21 +69,19 @@ public Optional getMainComment() throws IOException { @Override public void createMainComment(String comment) throws IOException { - init(); GHPullRequest pullRequest = getPR(); System.out.println("Creating new global comment"); pullRequest.comment(comment); } @Override - public List listFileComments() throws IOException { - init(); + public List listFileComments() throws IOException { Log.info("Start creating file comments"); GHPullRequest pullRequest = getPR(); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (GHPullRequestReviewComment review : pullRequest.listReviewComments()) { if (review.getUser().getId() == userId) { - result.add(new CodeFileComment() { + result.add(new CodeTextFileComment() { @Override public void update(String comment) throws IOException { review.update(comment); @@ -117,7 +109,6 @@ public String getComment() { @Override public void createFileComment(String file, Integer line, String comment) throws IOException { - init(); GHPullRequest pullRequest = getPR(); String commitHash = pullRequest.getHead().getSha(); line = (line == null || line < 1) ? 1 : line; diff --git a/src/main/java/org/qualityannotate/core/CommentProcessor.java b/src/main/java/org/qualityannotate/core/CommentProcessor.java index 7d9911b..f76893e 100644 --- a/src/main/java/org/qualityannotate/core/CommentProcessor.java +++ b/src/main/java/org/qualityannotate/core/CommentProcessor.java @@ -7,19 +7,18 @@ import org.qualityannotate.api.coderepository.FileComment; import org.qualityannotate.api.qualitytool.GlobalMetrics; import org.qualityannotate.api.qualitytool.Issue; -import org.qualityannotate.api.qualitytool.QualityTool; public class CommentProcessor { private CommentProcessor() { } - public static Comment createGlobalComment(GlobalMetrics globalMetrics, QualityTool qualityTool) { + public static Comment createGlobalComment(GlobalMetrics globalMetrics) { String markdown = String.format(""" Code Quality Report for [SonarQube](%s) | Name | Value | |------|-------| %s - """, qualityTool.getUrl(), + """, globalMetrics.url(), globalMetrics.metrics() .entrySet() .stream() @@ -28,14 +27,14 @@ public static Comment createGlobalComment(GlobalMetrics globalMetrics, QualityTo return new Comment(markdown, markdown, "TODO-html"); } - public static List createFileComments(List issues, QualityTool qualityTool) { - return issues.stream().map(issue -> createComment(issue, qualityTool)).toList(); + public static List createFileComments(List issues) { + return issues.stream().map(issue -> createComment(issue)).toList(); } - private static FileComment createComment(Issue issue, QualityTool qualityTool) { + public static FileComment createComment(Issue issue) { String markdown = String.format(""" %s %s%s - """, qualityTool.getSeverityIcon(issue.severity()), issue.comment(), + """, issue.severityIcon(), issue.comment(), (issue.urlToIssue() == null ? "" : " [details](" + issue.urlToIssue() + ")")); Comment comment = new Comment(markdown, markdown, "TODO-html"); return new FileComment(issue.fileName(), issue.lineNumber(), comment); diff --git a/src/main/java/org/qualityannotate/core/MainCommand.java b/src/main/java/org/qualityannotate/core/MainCommand.java index d2abc9e..4101382 100644 --- a/src/main/java/org/qualityannotate/core/MainCommand.java +++ b/src/main/java/org/qualityannotate/core/MainCommand.java @@ -35,17 +35,16 @@ public class MainCommand implements Runnable { @Parameters(paramLabel = "", defaultValue = GithubConfig.NAME, description = "To which code-repository you want to upload the data. Possible Values: ${COMPLETION-CANDIDATES}; Default: ${DEFAULT-VALUE}", completionCandidates = CodeRepoProvider.CodeRepositoryCandidates.class) String codeRepositoryParam; - private static void updateAnnotations(QualityTool qualityTool, CodeRepository codeRepository, - MetricsAndIssues metricsAndIssues) { - Comment globalComment = CommentProcessor.createGlobalComment(metricsAndIssues.globalMetrics(), qualityTool); - List fileComments = CommentProcessor.createFileComments(metricsAndIssues.issues(), qualityTool); + private static void updateAnnotations(CodeRepository codeRepository, MetricsAndIssues metricsAndIssues) { + Comment globalComment = CommentProcessor.createGlobalComment(metricsAndIssues.globalMetrics()); + List fileComments = CommentProcessor.createFileComments(metricsAndIssues.issues()); Log.infof("Global comment:\n%s", globalComment.text()); Log.infof("Issues:\n%s", fileComments.stream() .map(c -> String.format("%s:%d\n%s", c.fileName(), c.linenumber(), c.comment().text())) .collect(Collectors.joining("\n\n"))); try { - codeRepository.createOrUpdateAnnotations(globalComment, fileComments); + codeRepository.createOrUpdateAnnotations(globalComment, fileComments, metricsAndIssues); } catch (Exception e) { Log.error("Updating the code-repository failed", e); System.exit(1); @@ -72,6 +71,6 @@ public void run() { Log.infof("Code-Repository tool configured with\n%s", codeRepository.printConfigWithoutSecrets()); MetricsAndIssues metricsAndIssues = getMetricsAndIssues(qualityTool); Log.info(metricsAndIssues); - updateAnnotations(qualityTool, codeRepository, metricsAndIssues); + updateAnnotations(codeRepository, metricsAndIssues); } } diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java b/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java index f5f4f1c..de6e4aa 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java @@ -5,24 +5,14 @@ import jakarta.inject.Inject; import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; import org.qualityannotate.api.qualitytool.GlobalMetrics; import org.qualityannotate.api.qualitytool.Issue; import org.qualityannotate.api.qualitytool.MetricsAndIssues; import org.qualityannotate.api.qualitytool.QualityTool; import org.qualityannotate.core.rest.BasicAuthRequestFilter; -import org.qualityannotate.quality.sonarqube.client.ComponentMeasures; -import org.qualityannotate.quality.sonarqube.client.IssueSearch; -import org.qualityannotate.quality.sonarqube.client.Measure; -import org.qualityannotate.quality.sonarqube.client.Metric; -import org.qualityannotate.quality.sonarqube.client.SonarqubeApiClient; -import org.qualityannotate.quality.sonarqube.client.SqIssue; +import org.qualityannotate.quality.sonarqube.client.*; @ApplicationScoped public class SonarqubeQualityTool implements QualityTool { @@ -45,6 +35,7 @@ List getIssues() { for (SqIssue sqIssue : issuesSearch.issues()) { issues.add(new Issue(sqIssue.getPath(config.project()), sqIssue.textRange().startLine(), sqIssue.getQualityType().map(q -> q + ": ").orElse("") + sqIssue.message(), sqIssue.getSeverity(), + getSeverityIcon(sqIssue.getSeverity()), getSeverityEnum(sqIssue.getSeverity()), getIssueUrl(sqIssue.rule()))); } return issues; @@ -79,7 +70,7 @@ GlobalMetrics getGlobalMetrics() { metrics.put(metric.getName(), value); } } - return new GlobalMetrics(metrics); + return new GlobalMetrics(metrics, getUrl()); } private String getIssueUrl(String rule) { @@ -111,6 +102,15 @@ public String getSeverityIcon(String severity) { }; } + public Issue.Severity getSeverityEnum(String severity) { + return switch (severity.toLowerCase(Locale.ENGLISH)) { + case "low" -> Issue.Severity.LOW; + case "medium" -> Issue.Severity.MEDIUM; + // case "high" -> Issue.Severity.HIGH; + default -> Issue.Severity.HIGH; + }; + } + @Override public String printConfigWithoutSecrets() { return config.printWithoutSecrets(); diff --git a/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java b/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java index 5e6c03c..0234679 100644 --- a/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java +++ b/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java @@ -1,9 +1,6 @@ package org.qualityannotate.quality.sonarqube; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.okJson; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.jknack.handlebars.internal.Files; @@ -56,7 +53,7 @@ void testGetGlobalMetrics() { .withQueryParam("metricKeys", equalTo("test-metrics-1,test-metrics-2")) .withQueryParam("additionalFields", equalTo("metrics")) .willReturn(okJson(content))); - assertEquals(new GlobalMetrics(Map.of("New issues", "25", "Lines of code", "114", "Complexity", "12")), + assertEquals(new GlobalMetrics(Map.of("New issues", "25", "Lines of code", "114", "Complexity", "12"), "TODO"), cut.getGlobalMetrics()); } @@ -77,8 +74,8 @@ void testGetIssues() { .willReturn(okJson(content))); assertEquals( List.of(new Issue("om.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest", 2, - "Remove this unused private \"getKee\" method.", "MAJOR", - "https://sonarcloud" + ".io/organizations/quyt/rules?open=java:S1144&rule_key=java:S1144")), + "Remove this unused private \"getKee\" method.", "MAJOR", "", Issue.Severity.HIGH, + "https://sonarcloud.io/organizations/quyt/rules?open=java:S1144&rule_key=java:S1144")), cut.getIssues()); }