diff --git a/action.yml b/action.yml
index 9afdbfa..ab1a8c2 100644
--- a/action.yml
+++ b/action.yml
@@ -20,7 +20,7 @@ inputs:
runs:
using: 'docker'
- image: 'docker://uhafner/quality-monitor:1.2.0-SNAPSHOT'
+ image: 'docker://uhafner/quality-monitor:1.2.0'
env:
CONFIG: ${{ inputs.config }}
CHECKS_NAME: ${{ inputs.checks-name }}
diff --git a/doc/dependency-graph.puml b/doc/dependency-graph.puml
index 62e5683..6fb841b 100644
--- a/doc/dependency-graph.puml
+++ b/doc/dependency-graph.puml
@@ -49,7 +49,7 @@ rectangle "spotbugs-annotations\n\n4.8.2" as com_github_spotbugs_spotbugs_annota
rectangle "error_prone_annotations\n\n2.23.0" as com_google_errorprone_error_prone_annotations_jar
rectangle "streamex\n\n0.8.2" as one_util_streamex_jar
rectangle "codingstyle\n\n3.30.0" as edu_hm_hafner_codingstyle_jar
-rectangle "quality-monitor\n\n1.2.0-SNAPSHOT" as edu_hm_hafner_quality_monitor_jar
+rectangle "quality-monitor\n\n1.2.0" as edu_hm_hafner_quality_monitor_jar
edu_hm_hafner_analysis_model_jar -[#000000]-> org_jsoup_jsoup_jar
org_apache_commons_commons_digester3_jar -[#000000]-> cglib_cglib_jar
org_apache_commons_commons_digester3_jar -[#000000]-> commons_logging_commons_logging_jar
diff --git a/pom.xml b/pom.xml
index 3e32e0b..d2fcede 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
edu.hm.hafner
quality-monitor
- 1.2.0-SNAPSHOT
+ 1.2.0
jar
@@ -29,16 +29,65 @@
17
- 3.4.1
+ 3.22.0
+ 1.319
1.19.6
+
+ 3.4.1
edu.hm.hafner
- autograding-github-action
- 3.14.0
+ autograding-model
+ ${autograding-model.version}
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ commons-io
+ commons-io
+
+
+ codingstyle
+ edu.hm.hafner
+
+
+ streamex
+ one.util
+
+
+
+
+
+ org.kohsuke
+ github-api
+ ${github-api.version}
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ commons-io
+ commons-io
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
diff --git a/src/main/java/edu/hm/hafner/grading/github/GitHubAnnotationsBuilder.java b/src/main/java/edu/hm/hafner/grading/github/GitHubAnnotationsBuilder.java
new file mode 100644
index 0000000..ef4c197
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/grading/github/GitHubAnnotationsBuilder.java
@@ -0,0 +1,48 @@
+package edu.hm.hafner.grading.github;
+
+import org.apache.commons.lang3.StringUtils;
+
+import edu.hm.hafner.grading.CommentBuilder;
+
+import org.kohsuke.github.GHCheckRun.AnnotationLevel;
+import org.kohsuke.github.GHCheckRunBuilder.Annotation;
+import org.kohsuke.github.GHCheckRunBuilder.Output;
+
+/**
+ * Creates GitHub annotations for static analysis warnings, for lines with missing coverage, and for lines with
+ * survived mutations.
+ *
+ * @author Ullrich Hafner
+ */
+class GitHubAnnotationsBuilder extends CommentBuilder {
+ private static final String GITHUB_WORKSPACE_REL = "/github/workspace/./";
+ private static final String GITHUB_WORKSPACE_ABS = "/github/workspace/";
+
+ private final Output output;
+
+ GitHubAnnotationsBuilder(final Output output, final String prefix) {
+ super(prefix, GITHUB_WORKSPACE_REL, GITHUB_WORKSPACE_ABS);
+
+ this.output = output;
+ }
+
+ @Override
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ protected void createComment(final CommentType commentType, final String relativePath,
+ final int lineStart, final int lineEnd,
+ final String message, final String title,
+ final int columnStart, final int columnEnd,
+ final String details) {
+ Annotation annotation = new Annotation(relativePath,
+ lineStart, lineEnd, AnnotationLevel.WARNING, message).withTitle(title);
+
+ if (lineStart == lineEnd) {
+ annotation.withStartColumn(columnStart).withEndColumn(columnEnd);
+ }
+ if (StringUtils.isNotBlank(details)) {
+ annotation.withRawDetails(details);
+ }
+
+ output.add(annotation);
+ }
+}
diff --git a/src/main/java/edu/hm/hafner/grading/github/QualityMonitor.java b/src/main/java/edu/hm/hafner/grading/github/QualityMonitor.java
index 3826e6d..3a1d03a 100644
--- a/src/main/java/edu/hm/hafner/grading/github/QualityMonitor.java
+++ b/src/main/java/edu/hm/hafner/grading/github/QualityMonitor.java
@@ -1,23 +1,42 @@
package edu.hm.hafner.grading.github;
+import java.io.IOException;
import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.Date;
+import org.apache.commons.lang3.StringUtils;
+
+import edu.hm.hafner.grading.AggregatedScore;
+import edu.hm.hafner.grading.AutoGradingRunner;
+import edu.hm.hafner.grading.GradingReport;
+import edu.hm.hafner.util.FilteredLog;
import edu.hm.hafner.util.VisibleForTesting;
+import org.kohsuke.github.GHCheckRun;
+import org.kohsuke.github.GHCheckRun.Conclusion;
+import org.kohsuke.github.GHCheckRun.Status;
+import org.kohsuke.github.GHCheckRunBuilder;
+import org.kohsuke.github.GHCheckRunBuilder.Output;
+import org.kohsuke.github.GitHub;
+import org.kohsuke.github.GitHubBuilder;
+
/**
* GitHub action entrypoint for the quality monitor action.
*
* @author Ullrich Hafner
*/
-public class QualityMonitor extends GitHubAutoGradingRunner {
+public class QualityMonitor extends AutoGradingRunner {
static final String QUALITY_MONITOR = "Quality Monitor";
/**
- * Public entry point for the GitHub action in the docker container, simply calls the action.
- *
- * @param unused
- * not used
- */
+ * Public entry point for the GitHub action in the docker container, simply calls the action.
+ *
+ * @param unused
+ * not used
+ */
public static void main(final String... unused) {
new QualityMonitor().run();
}
@@ -43,4 +62,117 @@ protected String getDisplayName() {
protected String getDefaultConfigurationPath() {
return "/default-no-score-config.json";
}
+
+ @Override
+ protected void publishGradingResult(final AggregatedScore score, final FilteredLog log) {
+ var errors = createErrorMessageMarkdown(log);
+
+ var results = new GradingReport();
+ addComment(score,
+ results.getTextSummary(score, getChecksName()),
+ results.getMarkdownDetails(score, getChecksName()) + errors,
+ results.getSubScoreDetails(score) + errors,
+ results.getMarkdownSummary(score, getChecksName()) + errors,
+ errors.isBlank() ? Conclusion.SUCCESS : Conclusion.FAILURE, log);
+
+ try {
+ var environmentVariables = createEnvironmentVariables(score, log);
+ Files.writeString(Paths.get("metrics.env"), environmentVariables);
+ }
+ catch (IOException exception) {
+ log.logException(exception, "Can't write environment variables to 'metrics.env'");
+ }
+
+ log.logInfo("GitHub Action has finished");
+ }
+
+ @Override
+ protected void publishError(final AggregatedScore score, final FilteredLog log, final Throwable exception) {
+ var results = new GradingReport();
+
+ var markdownErrors = results.getMarkdownErrors(score, exception);
+ addComment(score, results.getTextSummary(score, getChecksName()),
+ markdownErrors, markdownErrors, markdownErrors, Conclusion.FAILURE, log);
+ }
+
+ private void addComment(final AggregatedScore score, final String textSummary,
+ final String markdownDetails, final String markdownSummary, final String prSummary,
+ final Conclusion conclusion, final FilteredLog log) {
+ try {
+ var repository = getEnv("GITHUB_REPOSITORY", log);
+ if (repository.isBlank()) {
+ log.logError("No GITHUB_REPOSITORY defined - skipping");
+
+ return;
+ }
+ String oAuthToken = getEnv("GITHUB_TOKEN", log);
+ if (oAuthToken.isBlank()) {
+ log.logError("No valid GITHUB_TOKEN found - skipping");
+
+ return;
+ }
+
+ String sha = getEnv("GITHUB_SHA", log);
+
+ GitHub github = new GitHubBuilder().withAppInstallationToken(oAuthToken).build();
+ GHCheckRunBuilder check = github.getRepository(repository)
+ .createCheckRun(getChecksName(), sha)
+ .withStatus(Status.COMPLETED)
+ .withStartedAt(Date.from(Instant.now()))
+ .withConclusion(conclusion);
+
+ Output output = new Output(textSummary, markdownSummary).withText(markdownDetails);
+
+ if (getEnv("SKIP_ANNOTATIONS", log).isEmpty()) {
+ var annotationBuilder = new GitHubAnnotationsBuilder(output, computeAbsolutePathPrefixToRemove(log));
+ annotationBuilder.createAnnotations(score);
+ }
+
+ check.add(output);
+
+ GHCheckRun run = check.create();
+ log.logInfo("Successfully created check " + run);
+
+ var prNumber = getEnv("PR_NUMBER", log);
+ if (!prNumber.isBlank()) { // optional PR comment
+ github.getRepository(repository)
+ .getPullRequest(Integer.parseInt(prNumber))
+ .comment(prSummary + addCheckLink(run));
+ log.logInfo("Successfully commented PR#" + prNumber);
+ }
+ }
+ catch (IOException exception) {
+ log.logException(exception, "Could not create check");
+ }
+ }
+
+ String createEnvironmentVariables(final AggregatedScore score, final FilteredLog log) {
+ var metrics = new StringBuilder();
+ score.getMetrics().forEach((metric, value) -> metrics.append(String.format("%s=%d%n", metric, value)));
+ log.logInfo("---------------");
+ log.logInfo("Metrics Summary");
+ log.logInfo("---------------");
+ log.logInfo(metrics.toString());
+ return metrics.toString();
+ }
+
+ private String getChecksName() {
+ return StringUtils.defaultIfBlank(System.getenv("CHECKS_NAME"), getDisplayName());
+ }
+
+ private String computeAbsolutePathPrefixToRemove(final FilteredLog log) {
+ return String.format("%s/%s/", getEnv("RUNNER_WORKSPACE", log),
+ StringUtils.substringAfter(getEnv("GITHUB_REPOSITORY", log), "/"));
+ }
+
+ private String addCheckLink(final GHCheckRun run) {
+ return String.format("%n%n More details are available in the [GitHub Checks Result](%s).%n",
+ run.getDetailsUrl().toString());
+ }
+
+ private String getEnv(final String key, final FilteredLog log) {
+ String value = StringUtils.defaultString(System.getenv(key));
+ log.logInfo(">>>> " + key + ": " + value);
+ return value;
+ }
}
diff --git a/src/test/java/edu/hm/hafner/grading/github/QualityMonitorDockerITest.java b/src/test/java/edu/hm/hafner/grading/github/QualityMonitorDockerITest.java
index 8a8c2f1..94120ad 100644
--- a/src/test/java/edu/hm/hafner/grading/github/QualityMonitorDockerITest.java
+++ b/src/test/java/edu/hm/hafner/grading/github/QualityMonitorDockerITest.java
@@ -195,7 +195,7 @@ void shouldShowErrors() throws TimeoutException {
}
private GenericContainer> createContainer() {
- return new GenericContainer<>(DockerImageName.parse("uhafner/quality-monitor:1.2.0-SNAPSHOT"));
+ return new GenericContainer<>(DockerImageName.parse("uhafner/quality-monitor:1.2.0"));
}
private String readStandardOut(final GenericContainer extends GenericContainer>> container) throws TimeoutException {