-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #550 from XiongKezhi/publish-checks
[JENKINS-54072] Publish warnings in GitHub pull requests using Checks API
- Loading branch information
Showing
18 changed files
with
692 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package io.jenkins.plugins.analysis.core.steps; | ||
|
||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
import org.jsoup.Jsoup; | ||
import org.jsoup.nodes.Element; | ||
import org.jsoup.nodes.TextNode; | ||
|
||
import edu.hm.hafner.analysis.Report; | ||
import edu.hm.hafner.util.VisibleForTesting; | ||
|
||
import io.jenkins.plugins.analysis.core.model.AnalysisResult; | ||
import io.jenkins.plugins.analysis.core.model.ResultAction; | ||
import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider; | ||
import io.jenkins.plugins.analysis.core.util.IssuesStatistics; | ||
import io.jenkins.plugins.analysis.core.util.QualityGateStatus; | ||
import io.jenkins.plugins.checks.api.ChecksAnnotation; | ||
import io.jenkins.plugins.checks.api.ChecksAnnotation.ChecksAnnotationBuilder; | ||
import io.jenkins.plugins.checks.api.ChecksAnnotation.ChecksAnnotationLevel; | ||
import io.jenkins.plugins.checks.api.ChecksConclusion; | ||
import io.jenkins.plugins.checks.api.ChecksDetails; | ||
import io.jenkins.plugins.checks.api.ChecksDetails.ChecksDetailsBuilder; | ||
import io.jenkins.plugins.checks.api.ChecksOutput.ChecksOutputBuilder; | ||
import io.jenkins.plugins.checks.api.ChecksPublisher; | ||
import io.jenkins.plugins.checks.api.ChecksPublisherFactory; | ||
import io.jenkins.plugins.checks.api.ChecksStatus; | ||
|
||
/** | ||
* Publishes warnings as checks to scm platforms. | ||
* | ||
* @author Kezhi Xiong | ||
*/ | ||
class WarningChecksPublisher { | ||
private final ResultAction action; | ||
|
||
WarningChecksPublisher(final ResultAction action) { | ||
this.action = action; | ||
} | ||
|
||
/** | ||
* Publishes checks to platforms. Afterwards, all warnings are available in corresponding platform's UI, e.g. GitHub | ||
* checks. | ||
*/ | ||
void publishChecks() { | ||
ChecksPublisher publisher = ChecksPublisherFactory.fromRun(action.getOwner()); | ||
publisher.publish(extractChecksDetails()); | ||
} | ||
|
||
@VisibleForTesting | ||
ChecksDetails extractChecksDetails() { | ||
AnalysisResult result = action.getResult(); | ||
IssuesStatistics totals = result.getTotals(); | ||
|
||
StaticAnalysisLabelProvider labelProvider = action.getLabelProvider(); | ||
|
||
return new ChecksDetailsBuilder() | ||
.withName(labelProvider.getName()) | ||
.withStatus(ChecksStatus.COMPLETED) | ||
.withConclusion(extractChecksConclusion(result.getQualityGateStatus())) | ||
.withOutput(new ChecksOutputBuilder() | ||
.withTitle(extractChecksTitle(totals)) | ||
.withSummary(extractChecksSummary(totals)) | ||
.withText(extractChecksText(totals)) | ||
.withAnnotations(extractChecksAnnotations(result.getNewIssues(), labelProvider)) | ||
.build()) | ||
.withDetailsURL(action.getAbsoluteUrl()) | ||
.build(); | ||
} | ||
|
||
private String extractChecksTitle(final IssuesStatistics statistics) { | ||
if (statistics.getTotalSize() == 0) { | ||
return "No issues."; | ||
} | ||
else if (statistics.getNewSize() == 0) { | ||
return String.format("No new issues, %d total.", statistics.getTotalSize()); | ||
} | ||
else if (statistics.getNewSize() == statistics.getTotalSize()) { | ||
if (statistics.getNewSize() == 1) { | ||
return "1 new issue."; | ||
} | ||
return String.format("%d new issues.", statistics.getNewSize()); | ||
} | ||
else { | ||
if (statistics.getNewSize() == 1) { | ||
return String.format("1 new issue, %d total.", statistics.getTotalSize()); | ||
} | ||
return String.format("%d new issues, %d total.", statistics.getNewSize(), statistics.getTotalSize()); | ||
} | ||
} | ||
|
||
private String extractChecksSummary(final IssuesStatistics statistics) { | ||
return String.format("## %d issues in total:\n" | ||
+ "- ### %d new issues\n" | ||
+ "- ### %d outstanding issues\n" | ||
+ "- ### %d delta issues\n" | ||
+ "- ### %d fixed issues", | ||
statistics.getTotalSize(), statistics.getNewSize(), statistics.getTotalSize() - statistics.getNewSize(), | ||
statistics.getDeltaSize(), statistics.getFixedSize()); | ||
} | ||
|
||
private String extractChecksText(final IssuesStatistics statistics) { | ||
return "## Total Issue Statistics:\n" | ||
+ generateSeverityText(statistics.getTotalLowSize(), statistics.getTotalNormalSize(), | ||
statistics.getTotalHighSize(), statistics.getTotalErrorSize()) | ||
+ "## New Issue Statistics:\n" | ||
+ generateSeverityText(statistics.getNewLowSize(), statistics.getNewNormalSize(), | ||
statistics.getNewHighSize(), statistics.getNewErrorSize()) | ||
+ "## Delta Issue Statistics:\n" | ||
+ generateSeverityText(statistics.getDeltaLowSize(), statistics.getDeltaNormalSize(), | ||
statistics.getDeltaHighSize(), statistics.getDeltaErrorSize()); | ||
} | ||
|
||
private String generateSeverityText(final int low, final int normal, final int high, final int error) { | ||
return "* Error: " + error + "\n" | ||
+ "* High: " + high + "\n" | ||
+ "* Normal: " + normal + "\n" | ||
+ "* Low: " + low + "\n"; | ||
} | ||
|
||
private ChecksConclusion extractChecksConclusion(final QualityGateStatus status) { | ||
switch (status) { | ||
case INACTIVE: | ||
case PASSED: | ||
return ChecksConclusion.SUCCESS; | ||
case FAILED: | ||
case WARNING: | ||
return ChecksConclusion.FAILURE; | ||
default: | ||
throw new IllegalArgumentException("Unsupported quality gate status: " + status); | ||
} | ||
} | ||
|
||
private List<ChecksAnnotation> extractChecksAnnotations(final Report issues, | ||
final StaticAnalysisLabelProvider labelProvider) { | ||
return issues.stream() | ||
.map(issue -> new ChecksAnnotationBuilder() | ||
.withPath(issue.getFileName()) | ||
.withTitle(issue.getType()) | ||
.withAnnotationLevel(ChecksAnnotationLevel.WARNING) | ||
.withMessage(issue.getSeverity() + ":\n" + parseHtml(issue.getMessage())) | ||
.withStartLine(issue.getLineStart()) | ||
.withEndLine(issue.getLineEnd()) | ||
.withStartColumn(issue.getColumnStart()) | ||
.withEndColumn(issue.getColumnEnd()) | ||
.withRawDetails(StringUtils.normalizeSpace(labelProvider.getDescription(issue))) | ||
.build()) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private String parseHtml(final String html) { | ||
Set<String> contents = new HashSet<>(); | ||
parseHtml(Jsoup.parse(html), contents); | ||
return String.join("\n", contents); | ||
} | ||
|
||
private void parseHtml(final Element html, final Set<String> contents) { | ||
for (TextNode node : html.textNodes()) { | ||
contents.add(node.text().trim()); | ||
} | ||
|
||
for (Element child : html.children()) { | ||
if (child.hasAttr("href")) { | ||
contents.add(child.text().trim() + ":" + child.attr("href").trim()); | ||
} | ||
else { | ||
parseHtml(child, contents); | ||
} | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...rces/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-skipPublishingChecks.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<div> | ||
If this option is unchecked, then the plugin automatically publishes the issues to corresponding SCM hosting platforms. | ||
For example, if you are using this feature for a GitHub organization project, the warnings will be published to | ||
GitHub through the Checks API. If this operation slows down your build or you don't want to publish the warnings to | ||
SCM platforms, you can use this option to deactivate this feature. | ||
</div> |
6 changes: 6 additions & 0 deletions
6
...s/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-skipPublishingChecks.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<div> | ||
If this option is unchecked, then the plugin automatically publishes the issues to corresponding SCM hosting platforms. | ||
For example, if you are using this feature for a GitHub organization project, the warnings will be published to | ||
GitHub through the Checks API. If this operation slows down your build or you don't want to publish the warnings to | ||
SCM platforms, you can use this option to deactivate this feature. | ||
</div> |
6 changes: 6 additions & 0 deletions
6
...es/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-skipPublishingChecks.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<div> | ||
If this option is unchecked, then the plugin automatically publishes the issues to corresponding SCM hosting platforms. | ||
For example, if you are using this feature for a GitHub organization project, the warnings will be published to | ||
GitHub through the Checks API. If this operation slows down your build or you don't want to publish the warnings to | ||
SCM platforms, you can use this option to deactivate this feature. | ||
</div> |
Oops, something went wrong.