From 2f2d5d86fad40f40968fe969032816e8d2ef0fd3 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 3 Jan 2025 23:46:11 +0100 Subject: [PATCH 1/5] Add a property to specify the icon of the result. --- .../analysis/core/model/ResultAction.java | 38 ++--- .../plugins/analysis/core/model/Tool.java | 30 +++- .../core/steps/AnalysisStepDescriptor.java | 15 +- .../analysis/core/steps/AnnotatedReport.java | 3 + .../analysis/core/steps/IssuesAggregator.java | 2 +- .../analysis/core/steps/IssuesPublisher.java | 8 +- .../analysis/core/steps/IssuesRecorder.java | 132 ++++++++++++++---- .../core/steps/PublishIssuesStep.java | 19 ++- .../analysis/core/steps/RecordIssuesStep.java | 18 +++ .../model/ReportScanningTool/help-icon.html | 9 ++ .../model/ReportScanningTool/help-id.html | 8 +- .../model/ReportScanningTool/help-name.html | 4 +- .../core/steps/IssuesRecorder/help-icon.html | 9 ++ .../core/steps/IssuesRecorder/help-id.html | 6 +- .../core/steps/IssuesRecorder/help-name.html | 4 +- .../steps/PublishIssuesStep/help-icon.html | 9 ++ .../core/steps/PublishIssuesStep/help-id.html | 6 +- .../steps/PublishIssuesStep/help-name.html | 4 +- .../steps/RecordIssuesStep/help-icon.html | 9 ++ .../core/steps/RecordIssuesStep/help-id.html | 6 +- .../steps/RecordIssuesStep/help-name.html | 4 +- .../core/steps/ToolProxy/help-tool.html | 8 +- .../help-highThreshold.html | 4 + .../help-normalThreshold.html | 4 + .../resources/issues/publish-parameters.jelly | 12 ++ .../issues/publish-parameters.properties | 9 +- .../main/resources/issues/tool-defaults.jelly | 4 + .../resources/issues/tool-defaults.properties | 2 + .../analysis/core/model/ResultActionTest.java | 7 +- .../core/steps/IssuesAggregatorTest.java | 10 +- .../steps/MiscIssuesRecorderITest.java | 113 ++++++++++----- .../analysis/warnings/steps/StepsITest.java | 65 +++++++-- 32 files changed, 434 insertions(+), 147 deletions(-) create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-highThreshold.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-normalThreshold.html diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java index 2056e7ac51..aa317d3329 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java @@ -45,29 +45,9 @@ public class ResultAction implements HealthReportingAction, LastBuildAction, Run private final HealthDescriptor healthDescriptor; private final String id; private final String name; + private /* almost final */ String icon; private final String charset; - private TrendChartType trendChartType; - - /** - * Creates a new instance of {@link ResultAction}. - * - * @param owner - * the associated build/run that created the static analysis result - * @param result - * the result of the static analysis run - * @param healthDescriptor - * the health descriptor of the static analysis run - * @param id - * the ID of the results - * @param name - * the optional name of the results - * @param charset - * the charset to use to display source files - */ - public ResultAction(final Run owner, final AnalysisResult result, final HealthDescriptor healthDescriptor, - final String id, final String name, final Charset charset) { - this(owner, result, healthDescriptor, id, name, charset, TrendChartType.AGGREGATION_TOOLS); - } + private /* almost final */ TrendChartType trendChartType; /** * Creates a new instance of {@link ResultAction}. @@ -82,18 +62,23 @@ public ResultAction(final Run owner, final AnalysisResult result, final He * the ID of the results * @param name * the optional name of the results + * @param icon + * the optional icon of the results * @param charset * the charset to use to display source files * @param trendChartType * determines if the trend chart will be shown */ + @SuppressWarnings("checkstyle:ParameterNumber") public ResultAction(final Run owner, final AnalysisResult result, final HealthDescriptor healthDescriptor, - final String id, final String name, final Charset charset, final TrendChartType trendChartType) { + final String id, final String name, final String icon, + final Charset charset, final TrendChartType trendChartType) { this.owner = owner; this.result = result; this.healthDescriptor = healthDescriptor; this.id = id; this.name = name; + this.icon = icon; this.charset = charset.name(); this.trendChartType = trendChartType; } @@ -107,6 +92,9 @@ protected Object readResolve() { if (trendChartType == null) { trendChartType = TrendChartType.TOOLS_ONLY; } + if (icon == null) { + icon = StringUtils.EMPTY; + } return this; } @@ -276,8 +264,10 @@ public String getSmallImageName() { * * @return the URL of the image */ - @SuppressWarnings({"unused", "WeakerAccess"}) // Called by jelly view public String getSmallImage() { + if (StringUtils.isNotBlank(icon)) { + return icon; + } return getLabelProvider().getSmallIconUrl(); } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java index 6a31b7aa0f..450f72b917 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java @@ -41,6 +41,7 @@ public abstract class Tool extends AbstractDescribableImpl implements Seri private String id = StringUtils.EMPTY; private String name = StringUtils.EMPTY; + private String icon = StringUtils.EMPTY; // @since 12.0.0: by default no custom icon is set private JenkinsFacade jenkins = new JenkinsFacade(); @@ -57,6 +58,10 @@ public void setJenkinsFacade(final JenkinsFacade jenkinsFacade) { protected Object readResolve() { jenkins = new JenkinsFacade(); + if (icon == null) { + icon = StringUtils.EMPTY; + } + return this; } @@ -120,6 +125,21 @@ public String getActualName() { return StringUtils.defaultIfBlank(getName(), getDescriptor().getDisplayName()); } + /** + * Defines the custom icon of the tool. If no icon is given, then the default icon of the tool is used. + * + * @param icon + * the icon of the tool + */ + @DataBoundSetter + public void setIcon(final String icon) { + this.icon = icon; + } + + public String getIcon() { + return icon; + } + /** * Returns the {@link Symbol} name of this tool. * @@ -149,7 +169,7 @@ public ToolDescriptor getDescriptor() { /** * Scans the results of a build for issues. This method is invoked on Jenkins master. I.e., if a tool wants to - * process some build results it is required to run a {@link MasterToSlaveCallable}. + * process some build results, it is required to run a {@link MasterToSlaveCallable}. * * @param run * the build @@ -162,9 +182,9 @@ public ToolDescriptor getDescriptor() { * * @return the created report * @throws ParsingException - * Signals that during parsing a non recoverable error has been occurred + * signals that during parsing a non-recoverable error has been occurred * @throws ParsingCanceledException - * Signals that the parsing has been aborted by the user + * signals that the user has aborted the parsing */ public abstract Report scan(Run run, FilePath workspace, Charset sourceCodeEncoding, LogHandler logger) throws ParsingException, ParsingCanceledException; @@ -246,6 +266,10 @@ public StaticAnalysisLabelProvider getLabelProvider() { return new StaticAnalysisLabelProvider(getId(), getDisplayName()); } + public String getIcon() { + return getLabelProvider().getSmallIconUrl(); + } + /** * Returns an optional help text that can provide useful hints on how to configure the static analysis tool so * that the report files could be parsed by Jenkins. This help can be a plain text message or an HTML snippet. diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java index 403cc69afb..e219417bc0 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java @@ -15,6 +15,7 @@ import hudson.util.ListBoxModel; import jenkins.model.Jenkins; +import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider; import io.jenkins.plugins.analysis.core.steps.WarningChecksPublisher.ChecksAnnotationScope; import io.jenkins.plugins.analysis.core.util.ModelValidation; import io.jenkins.plugins.prism.SourceCodeRetention; @@ -22,7 +23,7 @@ import io.jenkins.plugins.util.ValidationUtilities; /** - * Descriptor base class for all analysis steps. Provides generic validation methods, and list box models for UI select + * Descriptor base class for all analysis steps. Provides generic validation methods and list box models for UI select * elements. * * @author Ullrich Hafner @@ -32,6 +33,18 @@ public abstract class AnalysisStepDescriptor extends StepDescriptor { private static final JenkinsFacade JENKINS = new JenkinsFacade(); private final ModelValidation model = new ModelValidation(); + public String getDefaultId() { + return IssuesRecorder.DEFAULT_ID; + } + + public String getDefaultName() { + return Messages.Tool_Default_Name(); + } + + public String getDefaultIcon() { + return StaticAnalysisLabelProvider.ANALYSIS_SVG_ICON; + } + /** * Returns a model with all available charsets. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnnotatedReport.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnnotatedReport.java index 5d13648ed8..efe826a599 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnnotatedReport.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnnotatedReport.java @@ -9,6 +9,7 @@ import com.google.errorprone.annotations.FormatMethod; import edu.hm.hafner.analysis.Report; +import edu.hm.hafner.util.Ensure; import edu.umd.cs.findbugs.annotations.CheckForNull; import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; @@ -40,6 +41,8 @@ public class AnnotatedReport implements Serializable { * the ID of the report */ public AnnotatedReport(final String id) { + Ensure.that(id).isNotBlank("The ID of the report must not be empty"); + this.id = id; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java index f3bcda2e9c..6bfecae061 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java @@ -89,7 +89,7 @@ public boolean endBuild() { resultsPerTool.forEachKeyMultiValues((tool, reports) -> { AnnotatedReport aggregatedReport = new AnnotatedReport(tool, reports); recorder.publishResult(build, build.getWorkspace(), listener, Messages.Tool_Default_Name(), - aggregatedReport, StringUtils.EMPTY, new RunResultHandler(build)); + aggregatedReport, StringUtils.EMPTY, recorder.getIcon(), new RunResultHandler(build)); }); return true; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java index dd46995ff3..5fe0fbc467 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java @@ -49,6 +49,7 @@ class IssuesPublisher { private final DeltaCalculator deltaCalculator; private final HealthDescriptor healthDescriptor; private final String name; + private final String icon; private final Charset sourceCodeEncoding; private final List qualityGates; private final QualityGateEvaluationMode qualityGateEvaluationMode; @@ -59,13 +60,14 @@ class IssuesPublisher { @SuppressWarnings("ParameterNumber") IssuesPublisher(final Run run, final AnnotatedReport report, final DeltaCalculator deltaCalculator, final HealthDescriptor healthDescriptor, final List qualityGates, - final String name, final boolean ignoreQualityGate, final Charset sourceCodeEncoding, + final String name, final String icon, final boolean ignoreQualityGate, final Charset sourceCodeEncoding, final LogHandler logger, final ResultHandler notifier, final boolean failOnErrors) { this.report = report; this.run = run; this.deltaCalculator = deltaCalculator; this.healthDescriptor = healthDescriptor; this.name = name; + this.icon = icon; this.sourceCodeEncoding = sourceCodeEncoding; this.qualityGates = qualityGates; qualityGateEvaluationMode = ignoreQualityGate ? IGNORE_QUALITY_GATE : SUCCESSFUL_QUALITY_GATE; @@ -120,8 +122,8 @@ ResultAction attachAction(final TrendChartType trendChartType) { previous)) .orElseGet(() -> new AnalysisResult(run, getId(), deltaReport, report.getBlames(), report.getStatistics(), qualityGateResult, report.getSizeOfOrigin())); - ResultAction action - = new ResultAction(run, result, healthDescriptor, getId(), name, sourceCodeEncoding, trendChartType); + ResultAction action = new ResultAction(run, result, healthDescriptor, getId(), name, icon, + sourceCodeEncoding, trendChartType); run.addAction(action); if (trendChartType == TrendChartType.TOOLS_AGGREGATION || trendChartType == TrendChartType.AGGREGATION_ONLY) { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java index 8bc2c62fb2..ff4de26832 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java @@ -125,8 +125,9 @@ public class IssuesRecorder extends Recorder { @CheckForNull private ChecksInfo checksInfo; - private String id; - private String name; + private String id = StringUtils.EMPTY; + private String name = StringUtils.EMPTY; + private String icon = StringUtils.EMPTY; // @since 12.0.0: by default no custom icon is set private List qualityGates = new ArrayList<>(); @@ -172,6 +173,9 @@ protected Object readResolve() { if (checksAnnotationScope == null) { checksAnnotationScope = publishAllIssues ? ChecksAnnotationScope.ALL : ChecksAnnotationScope.NEW; } + if (icon == null) { + icon = StringUtils.EMPTY; + } return this; } @@ -233,6 +237,7 @@ public String getId() { /** * Defines the name of the results. The name is used for all labels in the UI. If no name is given, then the name of * the associated {@link StaticAnalysisLabelProvider} is used. + * *

* Note: this property is not used if {@link #isAggregatingResults} is {@code false}. It is also not visible in the * UI in order to simplify the user interface. @@ -250,6 +255,22 @@ public String getName() { return name; } + /** + * Defines the custom icon of the results. If no icon is given, then the default icon of + * the associated {@link StaticAnalysisLabelProvider} is used. + * + * @param icon + * the icon of the results + */ + @DataBoundSetter + public void setIcon(final String icon) { + this.icon = icon; + } + + public String getIcon() { + return icon; + } + /** * Gets the static analysis tools that will scan files and create issues. * @@ -632,52 +653,86 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, List perform(final Run run, final FilePath workspace, final TaskListener listener, final ResultHandler resultHandler) throws InterruptedException, IOException { + LogHandler logHandler = new LogHandler(listener, DEFAULT_ID); + logHandler.setQuiet(quiet); + Result overallResult = run.getResult(); if (isEnabledForFailure || overallResult == null || overallResult.isBetterOrEqualTo(Result.UNSTABLE)) { - return record(run, workspace, listener, resultHandler); + return record(run, workspace, listener, resultHandler, logHandler); } else { - LogHandler logHandler = new LogHandler(listener, createLoggerPrefix()); - logHandler.setQuiet(quiet); logHandler.log("Skipping execution of recorder since overall result is '%s'", overallResult); return Collections.emptyList(); } } - private String createLoggerPrefix() { - return analysisTools.stream().map(Tool::getActualName).collect(Collectors.joining()); - } - private List record(final Run run, final FilePath workspace, final TaskListener listener, - final ResultHandler resultHandler) throws IOException, InterruptedException { + final ResultHandler resultHandler, final LogHandler logHandler) throws IOException, InterruptedException { + if (analysisTools.isEmpty()) { + throw new IllegalStateException("No tools configured to record issues"); + } + List results = new ArrayList<>(); - if (isAggregatingResults && analysisTools.size() > 1) { - AnnotatedReport totalIssues = new AnnotatedReport(StringUtils.defaultIfEmpty(id, DEFAULT_ID)); - for (Tool tool : analysisTools) { - totalIssues.add(scanWithTool(run, workspace, listener, tool), tool.getActualId()); + if (analysisTools.size() == 1) { + Tool tool = analysisTools.get(0); + + var customId = StringUtils.defaultIfBlank(getId(), tool.getActualId()); + AnnotatedReport report = new AnnotatedReport(customId); + report.add(scanWithTool(run, workspace, listener, tool), tool.getActualId()); + + var customName = StringUtils.defaultIfBlank(getName(), tool.getActualName()); + var customIcon = StringUtils.defaultIfBlank(getIcon(), tool.getIcon()); + + results.add(publishResult(run, workspace, listener, customName, + report, customName, customIcon, resultHandler)); + + if (isAggregatingResults) { + logHandler.log("Ignoring property 'aggregatingResults' since only a single tool is defined."); + } + if (isNotUnique(tool)) { + logHandler.log("Do not set id, name, or icon for both the tool and the recorder"); } - String toolName = StringUtils.defaultIfEmpty(getName(), Messages.Tool_Default_Name()); - results.add(publishResult(run, workspace, listener, toolName, totalIssues, toolName, resultHandler)); } else { - for (Tool tool : analysisTools) { - AnnotatedReport report = new AnnotatedReport(tool.getActualId()); - if (isAggregatingResults) { - report.logInfo("Ignoring 'aggregatingResults' and ID '%s' since only a single tool is defined.", - id); + if (isAggregatingResults) { + AnnotatedReport report = new AnnotatedReport(StringUtils.defaultIfBlank(getId(), DEFAULT_ID)); + for (Tool tool : analysisTools) { + report.add(scanWithTool(run, workspace, listener, tool), tool.getActualId()); } - report.add(scanWithTool(run, workspace, listener, tool)); - if (StringUtils.isNotBlank(id) || StringUtils.isNotBlank(name)) { - report.logInfo("Ignoring name='%s' and id='%s' when publishing non-aggregating reports", - name, id); + + results.add(publishResult(run, workspace, listener, getCustomName(), + report, getCustomName(), getIcon(), resultHandler)); + } + else { + for (Tool tool : analysisTools) { + AnnotatedReport report = new AnnotatedReport(tool.getActualId()); + report.add(scanWithTool(run, workspace, listener, tool)); + + results.add(publishResult(run, workspace, listener, tool.getActualName(), + report, getReportName(tool), tool.getIcon(), resultHandler)); } - results.add( - publishResult(run, workspace, listener, tool.getActualName(), report, getReportName(tool), resultHandler)); + } + if (StringUtils.isNotBlank(getId()) || !StringUtils.isNotBlank(getName()) || !StringUtils.isNotBlank(getIcon())) { + logHandler.log("Do not set id, name, or icon for both the tool and the recorder"); } } return results; } + private String getCustomName() { + return StringUtils.defaultIfBlank(getName(), Messages.Tool_Default_Name()); + } + + private boolean isNotUnique(final Tool tool) { + if (StringUtils.isNotBlank(getId()) && StringUtils.isNotBlank(tool.getId())) { + return true; + } + if (StringUtils.isNotBlank(getName()) && StringUtils.isNotBlank(tool.getName())) { + return true; + } + return StringUtils.isNotBlank(getIcon()) && StringUtils.isNotBlank(tool.getIcon()); + } + /** * Returns the name of the tool. If no name has been set, then an empty string is returned so that the default name * will be used. @@ -733,15 +788,18 @@ private Charset getCharset(final String encoding) { * the name of the logger * @param annotatedReport * the analysis report to publish - * @param reportName + * @param customName * the name of the report (might be empty) + * @param customIcon + * the icon to use for the report (might be empty) * @param resultHandler * the status handler to use * * @return the created results */ - AnalysisResult publishResult(final Run run, final FilePath workspace, final TaskListener listener, final String loggerName, - final AnnotatedReport annotatedReport, final String reportName, final ResultHandler resultHandler) { + AnalysisResult publishResult(final Run run, final FilePath workspace, final TaskListener listener, + final String loggerName, final AnnotatedReport annotatedReport, final String customName, final String customIcon, + final ResultHandler resultHandler) { var logHandler = new LogHandler(listener, loggerName); logHandler.setQuiet(quiet); @@ -755,7 +813,7 @@ AnalysisResult publishResult(final Run run, final FilePath workspace, fina IssuesPublisher publisher = new IssuesPublisher(run, annotatedReport, deltaCalculator, new HealthDescriptor(healthy, unhealthy, minimumSeverity), qualityGates, - reportName, ignoreQualityGate, getSourceCodeCharset(), logHandler, resultHandler, failOnError); + customName, customIcon, ignoreQualityGate, getSourceCodeCharset(), logHandler, resultHandler, failOnError); ResultAction action = publisher.attachAction(trendChartType); if (!skipPublishingChecks) { @@ -784,6 +842,18 @@ public static void addAliases() { private final ModelValidation model = new ModelValidation(); + public String getDefaultId() { + return DEFAULT_ID; + } + + public String getDefaultName() { + return Messages.Tool_Default_Name(); + } + + public String getDefaultIcon() { + return StaticAnalysisLabelProvider.ANALYSIS_SVG_ICON; + } + @NonNull @Override public String getDisplayName() { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java index 6168a1122c..bd110a4cf6 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java @@ -75,6 +75,7 @@ public class PublishIssuesStep extends Step implements Serializable { private String id = StringUtils.EMPTY; private String name = StringUtils.EMPTY; + private String icon = StringUtils.EMPTY; // @since 12.0.0: by default no custom icon is set private String scm = StringUtils.EMPTY; /** @@ -136,6 +137,22 @@ public String getName() { return name; } + /** + * Defines the custom icon of the results. If no icon is given, then the default icon of + * the associated {@link StaticAnalysisLabelProvider} is used. + * + * @param icon + * the icon of the results + */ + @DataBoundSetter + public void setIcon(final String icon) { + this.icon = icon; + } + + public String getIcon() { + return icon; + } + /** * Sets the SCM that should be used to find the reference build for. The reference recorder will select the SCM * based on a substring comparison, there is no need to specify the full name. @@ -436,7 +453,7 @@ protected ResultAction run() throws IOException, InterruptedException, IllegalSt IssuesPublisher publisher = new IssuesPublisher(getRun(), report, deltaCalculator, new HealthDescriptor(step.getHealthy(), step.getUnhealthy(), step.getMinimumSeverityAsSeverity()), step.getQualityGates(), - StringUtils.defaultString(step.getName()), step.getIgnoreQualityGate(), + StringUtils.defaultString(step.getName()), step.getIcon(), step.getIgnoreQualityGate(), getCharset(step.getSourceCodeEncoding()), getLogger(report), createResultHandler(), step.getFailOnError()); ResultAction action = publisher.attachAction(step.getTrendChartType()); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java index a7cb8a45a7..0acdb49d89 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java @@ -92,6 +92,7 @@ public class RecordIssuesStep extends Step implements Serializable { private String id; private String name; + private String icon = StringUtils.EMPTY; // @since 12.0.0: by default no custom icon is set private List qualityGates = new ArrayList<>(); @@ -187,6 +188,22 @@ public String getName() { return name; } + /** + * Defines the custom icon of the results. If no icon is given, then the default icon of + * the associated {@link StaticAnalysisLabelProvider} is used. + * + * @param icon + * the icon of the results + */ + @DataBoundSetter + public void setIcon(final String icon) { + this.icon = icon; + } + + public String getIcon() { + return icon; + } + /** * Gets the static analysis tools that will scan files and create issues. * @@ -635,6 +652,7 @@ protected List run() throws IOException, InterruptedException { recorder.setChecksAnnotationScope(step.getChecksAnnotationScope()); recorder.setId(step.getId()); recorder.setName(step.getName()); + recorder.setIcon(step.getIcon()); recorder.setQualityGates(step.getQualityGates()); recorder.setFailOnError(step.getFailOnError()); recorder.setTrendChartType(step.getTrendChartType()); diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html new file mode 100644 index 0000000000..1048184d0d --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html @@ -0,0 +1,9 @@ +

+ You can override the icon of the tool. This icon is used in the overview, in details views, and + hyperlinks. If you leave the icon empty, then the built-in default icon of the registered tool will be used. + +

+ Icons are specified using one of the existing + Jenkins symbols. +

+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-id.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-id.html index e8c20b6161..be09b620e1 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-id.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-id.html @@ -1,7 +1,7 @@
- The results of the selected tool are published using a unique ID (i.e. URL) which must not be already used by - another tool in this job. This ID is used as link to the results, so choose a short and meaningful name. + The results of the selected tool are published using a unique ID (i.e., URL) which must not be already used by + another tool in this job. This ID is used as a link to the results, so choose a short and meaningful name. Allowed elements are characters, digits, dashes and underscores (more precisely, - the ID must match the regular expression `\p{Alnum}[\p{Alnum}-_]*`). - If you leave the ID field empty, then the built-in default ID of the registered tool will be used. + the ID must match the regular expression \p{Alnum}[\p{Alnum}-_]*). + If you leave the ID field empty, then the built-in default ID of the registered tool w will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-name.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-name.html index 6acc58fd72..55c6c3a548 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-name.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-name.html @@ -1,4 +1,4 @@
- You can override the display name of the tool. This name is used in details views, trend captions, and hyper - links. If you leave the name field empty, then the built-in default name of the registered tool will be used. + You can override the display name of the results. This name is used in details views, trend captions, and + hyperlinks. If you leave the name field empty, then the built-in default name of the registered tool will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html new file mode 100644 index 0000000000..feb5007907 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html @@ -0,0 +1,9 @@ +
+ You can override the icon of the results. This icon is used in the overview, in details views, and + hyperlinks. If you leave the icon empty, then the built-in default icon will be used. + +

+ Icons are specified using one of the existing + Jenkins symbols. +

+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-id.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-id.html index 280bf5d52c..2d5abe34dd 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-id.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-id.html @@ -1,7 +1,7 @@
- The results of the selected tool are published using a unique ID (i.e. URL) which must not be already used by - another tool in this job. This ID is used as link to the results, so choose a short and meaningful name. + The results of the selected tool are published using a unique ID (i.e., URL) which must not be already used by + another tool in this job. This ID is used as a link to the results, so choose a short and meaningful name. Allowed elements are characters, digits, dashes and underscores (more precisely, the ID must match the regular expression \p{Alnum}[\p{Alnum}-_]*). - If you leave the ID field empty, then the built-in default ID of the registered tool will be used. + If you leave the ID field empty, then the built-in default ID will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-name.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-name.html index 6acc58fd72..f622737517 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-name.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-name.html @@ -1,4 +1,4 @@
- You can override the display name of the tool. This name is used in details views, trend captions, and hyper - links. If you leave the name field empty, then the built-in default name of the registered tool will be used. + You can override the display name of the results. This name is used in details views, trend captions, and + hyperlinks. If you leave the name field empty, then the built-in default name will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html new file mode 100644 index 0000000000..feb5007907 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html @@ -0,0 +1,9 @@ +
+ You can override the icon of the results. This icon is used in the overview, in details views, and + hyperlinks. If you leave the icon empty, then the built-in default icon will be used. + +

+ Icons are specified using one of the existing + Jenkins symbols. +

+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-id.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-id.html index 280bf5d52c..2d5abe34dd 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-id.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-id.html @@ -1,7 +1,7 @@
- The results of the selected tool are published using a unique ID (i.e. URL) which must not be already used by - another tool in this job. This ID is used as link to the results, so choose a short and meaningful name. + The results of the selected tool are published using a unique ID (i.e., URL) which must not be already used by + another tool in this job. This ID is used as a link to the results, so choose a short and meaningful name. Allowed elements are characters, digits, dashes and underscores (more precisely, the ID must match the regular expression \p{Alnum}[\p{Alnum}-_]*). - If you leave the ID field empty, then the built-in default ID of the registered tool will be used. + If you leave the ID field empty, then the built-in default ID will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-name.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-name.html index 6acc58fd72..f622737517 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-name.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-name.html @@ -1,4 +1,4 @@
- You can override the display name of the tool. This name is used in details views, trend captions, and hyper - links. If you leave the name field empty, then the built-in default name of the registered tool will be used. + You can override the display name of the results. This name is used in details views, trend captions, and + hyperlinks. If you leave the name field empty, then the built-in default name will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html new file mode 100644 index 0000000000..feb5007907 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html @@ -0,0 +1,9 @@ +
+ You can override the icon of the results. This icon is used in the overview, in details views, and + hyperlinks. If you leave the icon empty, then the built-in default icon will be used. + +

+ Icons are specified using one of the existing + Jenkins symbols. +

+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-id.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-id.html index 280bf5d52c..2d5abe34dd 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-id.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-id.html @@ -1,7 +1,7 @@
- The results of the selected tool are published using a unique ID (i.e. URL) which must not be already used by - another tool in this job. This ID is used as link to the results, so choose a short and meaningful name. + The results of the selected tool are published using a unique ID (i.e., URL) which must not be already used by + another tool in this job. This ID is used as a link to the results, so choose a short and meaningful name. Allowed elements are characters, digits, dashes and underscores (more precisely, the ID must match the regular expression \p{Alnum}[\p{Alnum}-_]*). - If you leave the ID field empty, then the built-in default ID of the registered tool will be used. + If you leave the ID field empty, then the built-in default ID will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-name.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-name.html index 6acc58fd72..4df529699c 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-name.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-name.html @@ -1,4 +1,4 @@
- You can override the display name of the tool. This name is used in details views, trend captions, and hyper - links. If you leave the name field empty, then the built-in default name of the registered tool will be used. + You can override the display name of the tool. This name is used in details views, trend captions, and + hyperlinks. If you leave the name field empty, then the built-in default name will be used.
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ToolProxy/help-tool.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ToolProxy/help-tool.html index 211e3a645b..32a3744be3 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ToolProxy/help-tool.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ToolProxy/help-tool.html @@ -1,9 +1,9 @@

- For each static analysis tool a dedicated parser or scanner will be used to read report files or produce issues in - any other way. If your tool is not yet supported you can define a new Groovy based parser in Jenkins - system configuration. You can reference this new parser afterwards when you select the tool 'Groovy Parser'. + For each static analysis tool, a dedicated parser or scanner will be used to read report files or produce issues in + any other way. If your tool is not yet supported, you can define a new Groovy based parser in Jenkins + system configuration. You can reference this new parser afterward when you select the tool 'Groovy Parser'. Additionally, you provide a new parser within a new small plug-in. If the parser is useful for other teams - as well please share it and provide pull requests for the + as well, please share it and provide pull requests for the Warnings Next Generation Plug-in and the Analysis Parsers Library.

diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-highThreshold.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-highThreshold.html new file mode 100644 index 0000000000..9487052333 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-highThreshold.html @@ -0,0 +1,4 @@ +

+ All warnings that have more duplicated lines than this number (or equal to this number) are considered + as high severity warnings. +

diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-normalThreshold.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-normalThreshold.html new file mode 100644 index 0000000000..16619a1f5b --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/warnings/DuplicateCodeScanner/help-normalThreshold.html @@ -0,0 +1,4 @@ +

+ All warnings that have more duplicated lines than this number (or equal to this number) are considered + as normal severity warnings. All warnings with less duplicated lines are considered as low severity warnings. +

diff --git a/plugin/src/main/resources/issues/publish-parameters.jelly b/plugin/src/main/resources/issues/publish-parameters.jelly index 37785f5145..7df22c4c44 100644 --- a/plugin/src/main/resources/issues/publish-parameters.jelly +++ b/plugin/src/main/resources/issues/publish-parameters.jelly @@ -7,6 +7,18 @@ + + + + + + + + + + + + diff --git a/plugin/src/main/resources/issues/publish-parameters.properties b/plugin/src/main/resources/issues/publish-parameters.properties index 9b3a9f0de8..80209fc0cd 100644 --- a/plugin/src/main/resources/issues/publish-parameters.properties +++ b/plugin/src/main/resources/issues/publish-parameters.properties @@ -22,5 +22,12 @@ threshold.0.percent=0% description.health.limit=Configure the thresholds for the build health. If left empty, then no health report is created. \ If the actual number of warnings is between the provided thresholds, then the build health is interpolated. title.health.severities=Health Report Severities -description.healthy=Report health as 100% when the number of issues is less than this value. +description.healthy=Report health as 100% when the number of issues is lower than this value. description.unhealthy=Report health as 0% when the number of issues is greater than this value. + +title.name=Custom Name +description.name=Optional custom display name of the result action, overwrites the built-in name ''{0}''. +title.id=Custom ID +description.id=Optional custom ID (URL) of the result action, overwrites the built-in ID ''{0}''. +title.icon=Custom Icon +description.icon=Optional custom icon of the result action, overwrites the built-in icon ''{0}''. diff --git a/plugin/src/main/resources/issues/tool-defaults.jelly b/plugin/src/main/resources/issues/tool-defaults.jelly index d923aa1495..f4a3b137c6 100644 --- a/plugin/src/main/resources/issues/tool-defaults.jelly +++ b/plugin/src/main/resources/issues/tool-defaults.jelly @@ -13,4 +13,8 @@ + + + + diff --git a/plugin/src/main/resources/issues/tool-defaults.properties b/plugin/src/main/resources/issues/tool-defaults.properties index 31e0e56880..15e7a5bc2c 100644 --- a/plugin/src/main/resources/issues/tool-defaults.properties +++ b/plugin/src/main/resources/issues/tool-defaults.properties @@ -2,3 +2,5 @@ title.name=Custom Name description.name=Optional custom display name of the tool, overwrites the built-in name ''{0}''. title.id=Custom ID description.id=Optional custom ID (URL) of this tool, overwrites the built-in ID ''{0}''. +title.icon=Custom Icon +description.icon=Optional custom icon of this tool, overwrites the built-in icon ''{0}''. diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResultActionTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResultActionTest.java index 7b95769e43..e80f881073 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResultActionTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResultActionTest.java @@ -9,6 +9,7 @@ import hudson.model.Run; import io.jenkins.plugins.analysis.core.util.HealthDescriptor; +import io.jenkins.plugins.analysis.core.util.TrendChartType; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @@ -22,13 +23,15 @@ class ResultActionTest { @Test void shouldRestoreRun() { ResultAction action = new ResultAction(null, mock(AnalysisResult.class), - new HealthDescriptor(0, 0, Severity.WARNING_HIGH), - "ID", "Name", StandardCharsets.UTF_8); + new HealthDescriptor(0, 0, Severity.WARNING_HIGH), "ID", "Name", + "icon", StandardCharsets.UTF_8, TrendChartType.AGGREGATION_TOOLS); assertThat(action.getOwner()).isNull(); Run run = mock(Run.class); action.onAttached(run); assertThat(action.getOwner()).isSameAs(run); + + assertThat(action.getIconFileName()).isEqualTo("icon"); } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java index 581d8086d6..8b9c88b559 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java @@ -50,7 +50,7 @@ void shouldHandleBuildWithoutActions() { aggregator.endBuild(); - verify(recorder, never()).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); + verify(recorder, never()).publishResult(any(), any(), any(), anyString(), any(), anyString(), anyString(), any()); } @Test @@ -71,7 +71,7 @@ void shouldCollectSingleResultForSingleAxis() { aggregator.endBuild(); - verify(recorder).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); + verify(recorder).publishResult(any(), any(), any(), anyString(), any(), anyString(), anyString(), any()); } @Test @org.junitpioneer.jupiter.Issue("JENKINS-59178") @@ -96,7 +96,7 @@ void shouldCollectDifferentResultsForTwoAxes() { aggregator.endBuild(); - verify(recorder, times(2)).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); + verify(recorder, times(2)).publishResult(any(), any(), any(), anyString(), any(), anyString(), anyString(), any()); } @Test @@ -120,7 +120,7 @@ void shouldCollectMultipleToolsOneAxis() { aggregator.endBuild(); - verify(recorder, times(2)).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); + verify(recorder, times(2)).publishResult(any(), any(), any(), anyString(), any(), anyString(), anyString(), any()); } @Test @@ -148,7 +148,7 @@ void shouldCollectOneToolMultipleAxes() { aggregator.endBuild(); - verify(recorder).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); + verify(recorder).publishResult(any(), any(), any(), anyString(), any(), anyString(), anyString(), any()); } private Issue createIssue(final String pmd) { diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java index a1d89a577b..eb376e0ccc 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java @@ -56,6 +56,10 @@ class MiscIssuesRecorderITest extends IntegrationTestWithJenkinsPerSuite { private static final Pattern TAG_REGEX = Pattern.compile(">(.+?) build = buildWithResult(project, Result.SUCCESS); + var tool = configurePattern(new Eclipse()); + tool.setId(CUSTOM_ID); + tool.setName(CUSTOM_NAME); - ResultAction action = getResultAction(build); - assertThat(action.getId()).isEqualTo(id); - assertThat(action.getDisplayName()).startsWith(name); + enableGenericWarnings(project, tool); + + var action = getResultAction(buildWithResult(project, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo(CUSTOM_ID); + assertThat(action.getDisplayName()).startsWith(CUSTOM_NAME); + assertThat(action.getIconFileName()).contains("triangle-exclamation"); + } + + @Test + @org.junitpioneer.jupiter.Issue("JENKINS-73636, JENKINS-72777") + void shouldCreateResultWithDefaultIcon() { + var project = createFreestyleJob("checkstyle.xml"); + + var tool = configurePattern(new CheckStyle()); + enableGenericWarnings(project, tool); + + var action = getResultAction(buildWithResult(project, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo("checkstyle"); + assertThat(action.getDisplayName()).startsWith("CheckStyle"); + assertThat(action.getIconFileName()).endsWith(CHECKSTYLE_ICON); + } + + @Test + @org.junitpioneer.jupiter.Issue("JENKINS-73636, JENKINS-72777") + void shouldCreateResultWithCorrectIconAndCustomId() { + var project = createFreestyleJob("checkstyle.xml"); + + var tool = configurePattern(new CheckStyle()); + tool.setId(CUSTOM_ID); + tool.setName(CUSTOM_NAME); + + enableGenericWarnings(project, tool); + + var action = getResultAction(buildWithResult(project, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo(CUSTOM_ID); + assertThat(action.getDisplayName()).startsWith(CUSTOM_NAME); + assertThat(action.getIconFileName()).endsWith(CHECKSTYLE_ICON); + } + + @Test + @org.junitpioneer.jupiter.Issue("JENKINS-73636, JENKINS-72777") + void shouldCreateResultWithCorrectIconInTool() { + var project = createFreestyleJob("checkstyle.xml"); + + var tool = configurePattern(new CheckStyle()); + tool.setId(CUSTOM_ID); + tool.setName(CUSTOM_NAME); + tool.setIcon(CUSTOM_ICON); + + enableGenericWarnings(project, tool); + + var action = getResultAction(buildWithResult(project, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo(CUSTOM_ID); + assertThat(action.getDisplayName()).startsWith(CUSTOM_NAME); + assertThat(action.getIconFileName()).isEqualTo(CUSTOM_ICON); } - /** - * Runs the CheckStyle parser and changes name and ID and icon. - */ @Test @org.junitpioneer.jupiter.Issue("JENKINS-73636, JENKINS-72777") - void shouldCreateResultWithCorrectIcon() { - var checkstyleImage = "checkstyle.svg"; - - FreeStyleProject project = createFreestyleJob("checkstyle.xml"); - ReportScanningTool configuration = configurePattern(new CheckStyle()); - enableGenericWarnings(project, configuration); - - ResultAction checkstyle = getResultAction(buildWithResult(project, Result.SUCCESS)); - assertThat(checkstyle.getId()).isEqualTo("checkstyle"); - assertThat(checkstyle.getDisplayName()).startsWith("CheckStyle"); - assertThat(checkstyle.getIconFileName()).endsWith(checkstyleImage); - - project.getPublishersList().clear(); - - String changedId = "new-id"; - configuration.setId(changedId); - String changedName = "new-name"; - configuration.setName(changedName); - enableGenericWarnings(project, configuration); - - ResultAction changedProperties = getResultAction(buildWithResult(project, Result.SUCCESS)); - assertThat(changedProperties.getId()).isEqualTo(changedId); - assertThat(changedProperties.getDisplayName()).startsWith(changedName); - assertThat(checkstyle.getIconFileName()).endsWith(checkstyleImage); + void shouldCreateResultWithCorrectIconInRecorder() { + var project = createFreestyleJob("checkstyle.xml"); + + var recorder = enableGenericWarnings(project, configurePattern(new CheckStyle())); + recorder.setId(CUSTOM_ID); + recorder.setName(CUSTOM_NAME); + recorder.setIcon(CUSTOM_ICON); + + ResultAction action = getResultAction(buildWithResult(project, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo(CUSTOM_ID); + assertThat(action.getDisplayName()).startsWith(CUSTOM_NAME); + assertThat(action.getIconFileName()).isEqualTo(CUSTOM_ICON); } /** diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java index 8a76d9366d..ac821f8815 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java @@ -495,7 +495,7 @@ private void assertThatConsoleNotesAreRemoved(final String fileName, final int e void shouldCombineIssuesOfSeveralFiles() { publishResultsWithIdAndName( "publishIssues issues:[java, eclipse, javadoc]", - "analysis", "Static Analysis"); + "analysis", "Static Analysis", "triangle-exclamation"); } /** Runs the JavaDoc parser and uses a message filter to change the number of recorded warnings. */ @@ -580,6 +580,34 @@ void shouldFailBuildWhenFailBuildOnErrorsIsSet() { scheduleBuildAndAssertStatus(job, Result.FAILURE); } + @Test + void shouldReportResultWithDifferentIdNameAndIconInStep() { + WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("emptyFile.txt"); + + job.setDefinition(asStage( + "recordIssues id: 'custom-id', name: 'custom-name', icon: 'custom-icon', " + + "tool: javaDoc(pattern:'**/*issues.txt', reportEncoding:'UTF-8')")); + + var action = getResultAction(buildWithResult(job, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo("custom-id"); + assertThat(action.getDisplayName()).startsWith("custom-name"); + assertThat(action.getIconFileName()).isEqualTo("custom-icon"); + } + + @Test + void shouldReportResultWithDifferentIdNameAndIconInTool() { + WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("emptyFile.txt"); + + job.setDefinition(asStage( + "recordIssues tool: javaDoc(pattern:'**/*issues.txt', reportEncoding:'UTF-8'," + + "id: 'custom-id', name: 'custom-name', icon: 'custom-icon')")); + + var action = getResultAction(buildWithResult(job, Result.SUCCESS)); + assertThat(action.getId()).isEqualTo("custom-id"); + assertThat(action.getDisplayName()).startsWith("custom-name"); + assertThat(action.getIconFileName()).isEqualTo("custom-icon"); + } + /** * Runs the all Java parsers on three output files: the build should report issues of all tools. The results should * be aggregated into a new action with the specified ID. Since no name is given the default name is used. @@ -588,7 +616,7 @@ void shouldFailBuildWhenFailBuildOnErrorsIsSet() { void shouldProvideADefaultNameIfNoOneIsGiven() { publishResultsWithIdAndName( "publishIssues issues:[java, eclipse, javadoc], id:'my-id'", - "my-id", "Static Analysis Warnings"); + "my-id", "Static Analysis Warnings", "triangle-exclamation"); } /** @@ -599,11 +627,22 @@ void shouldProvideADefaultNameIfNoOneIsGiven() { void shouldUseSpecifiedName() { publishResultsWithIdAndName( "publishIssues issues:[java, eclipse, javadoc], id:'my-id', name:'my-name'", - "my-id", "my-name"); + "my-id", "my-name", "triangle-exclamation"); + } + + /** + * Runs the all Java parsers on three output files: the build should report issues of all tools. The results should + * be aggregated into a new action with the specified ID and the specified name. + */ + @Test + void shouldUseSpecifiedIcon() { + publishResultsWithIdAndName( + "publishIssues issues:[java, eclipse, javadoc], id:'my-id', name:'my-name', icon:'my-icon'", + "my-id", "my-name", "my-icon"); } private void publishResultsWithIdAndName(final String publishStep, final String expectedId, - final String expectedName) { + final String expectedName, final String expectedIcon) { WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("eclipse.txt", "javadoc.txt", "javac.txt"); job.setDefinition(asStage(createScanForIssuesStep(new Java(), "java"), createScanForIssuesStep(new Eclipse(), "eclipse"), @@ -615,6 +654,7 @@ private void publishResultsWithIdAndName(final String publishStep, final String ResultAction action = getResultAction(run); assertThat(action.getId()).isEqualTo(expectedId); assertThat(action.getDisplayName()).contains(expectedName); + assertThat(action.getIconFileName()).contains(expectedIcon); assertThatJavaIssuesArePublished(action.getResult()); } @@ -878,16 +918,15 @@ void shouldUseGroovyParserTwice() { */ @Test void shouldLogWarningIfNameIsSetWhenNotAggregating() { - List results = getAnalysisResults(runWith2GroovyParsers(false, - "name: 'name'", "id: 'id'")); + Run build = runWith2GroovyParsers(false, + "name: 'name'", "id: 'id'"); + List results = getAnalysisResults(build); assertThat(results).hasSize(2); - for (AnalysisResult result : results) { - assertThat(result.getInfoMessages()) - .contains("Ignoring name='name' and id='id' when publishing non-aggregating reports"); - } - Set ids = results.stream().map(AnalysisResult::getId).collect(Collectors.toSet()); assertThat(ids).containsExactly("groovy-1", "groovy-2"); + + assertThat(getConsoleLog(build)) + .contains("Do not set id, name, or icon for both the tool and the recorder"); } /** @@ -909,11 +948,13 @@ void shouldUseGroovyParserTwiceAndAggregateIntoSingleResult() { */ @Test void shouldUseGroovyParserTwiceAndAggregateIntoSingleResultWithCustomizableIdAndName() { - Run build = runWith2GroovyParsers(true, "name: 'Custom Name'", "id: 'custom-id'"); + Run build = runWith2GroovyParsers(true, + "name: 'Custom Name'", "id: 'custom-id'", "icon: 'custom-icon.png'"); ResultAction action = getResultAction(build); assertThat(action.getId()).isEqualTo("custom-id"); assertThat(action.getDisplayName()).isEqualTo("Custom Name Warnings"); + assertThat(action.getIconFileName()).isEqualTo("custom-icon.png"); } /** From babdc4815a0141d6956281939c710141084f1f10 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 4 Jan 2025 22:47:07 +0100 Subject: [PATCH 2/5] Add description on how to use custom icon. --- .../core/model/ReportScanningTool/help-icon.html | 12 +++++++++--- .../core/steps/IssuesRecorder/help-icon.html | 6 +++++- .../core/steps/PublishIssuesStep/help-icon.html | 6 +++++- .../core/steps/RecordIssuesStep/help-icon.html | 6 +++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html index 1048184d0d..3ec2bafc38 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ReportScanningTool/help-icon.html @@ -1,9 +1,15 @@
- You can override the icon of the tool. This icon is used in the overview, in details views, and - hyperlinks. If you leave the icon empty, then the built-in default icon of the registered tool will be used. +

+ You can override the icon of the tool. This icon is used in the overview, in details views, and + hyperlinks. If you leave the icon empty, then the built-in default icon of the registered tool will be used. +

Icons are specified using one of the existing - Jenkins symbols. + Jenkins symbols. Additional icons are + available in the ionicons and + font-awesome plugins. + If none of these icons fit your needs, you can also use a custom icon that can be deployed to the + Jenkins userContent directory.

diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html index feb5007907..f1ed590b8d 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-icon.html @@ -4,6 +4,10 @@

Icons are specified using one of the existing - Jenkins symbols. + Jenkins symbols. Additional icons are + available in the ionicons and + font-awesome plugins. + If none of these icons fit your needs, you can also use a custom icon that can be deployed to the + Jenkins userContent directory.

diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html index feb5007907..f1ed590b8d 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep/help-icon.html @@ -4,6 +4,10 @@

Icons are specified using one of the existing - Jenkins symbols. + Jenkins symbols. Additional icons are + available in the ionicons and + font-awesome plugins. + If none of these icons fit your needs, you can also use a custom icon that can be deployed to the + Jenkins userContent directory.

diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html index feb5007907..f1ed590b8d 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-icon.html @@ -4,6 +4,10 @@

Icons are specified using one of the existing - Jenkins symbols. + Jenkins symbols. Additional icons are + available in the ionicons and + font-awesome plugins. + If none of these icons fit your needs, you can also use a custom icon that can be deployed to the + Jenkins userContent directory.

From a0c9984f58180718b4e0a92fff284f89517b9b07 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 4 Jan 2025 22:48:45 +0100 Subject: [PATCH 3/5] Decorate the existing label provider with the icon. --- .../analysis/core/model/ResultAction.java | 109 +++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java index aa317d3329..845747da10 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java @@ -7,12 +7,17 @@ import org.apache.commons.lang3.StringUtils; +import edu.hm.hafner.analysis.Issue; +import edu.hm.hafner.analysis.Report; +import edu.hm.hafner.util.Generated; +import edu.hm.hafner.util.VisibleForTesting; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.stapler.StaplerProxy; import org.kohsuke.stapler.bind.JavaScriptMethod; import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; +import org.jvnet.localizer.Localizable; import hudson.model.Action; import hudson.model.HealthReport; import hudson.model.HealthReportingAction; @@ -265,9 +270,6 @@ public String getSmallImageName() { * @return the URL of the image */ public String getSmallImage() { - if (StringUtils.isNotBlank(icon)) { - return icon; - } return getLabelProvider().getSmallIconUrl(); } @@ -304,7 +306,12 @@ public String toString() { * @return the label provider for this tool */ public StaticAnalysisLabelProvider getLabelProvider() { - return new LabelProviderFactory().create(getParserId(), name); + var registeredLabelProvider = new LabelProviderFactory().create(getParserId(), name); + if (StringUtils.isBlank(icon)) { + return registeredLabelProvider; + } + + return new CustomIconLabelProvider(registeredLabelProvider, icon); } private String getParserId() { @@ -336,4 +343,98 @@ public String resetReference() { // Empty method as workaround for Stapler bug that does not find JavaScript proxy methods in target object IssueDetail return "{}"; } + + private static class CustomIconLabelProvider extends StaticAnalysisLabelProvider { + @Override + public DetailsTableModel getIssuesModel(final Run build, final String url, final Report report) { + return decorated.getIssuesModel(build, url, report); + } + + @Override + public DefaultAgeBuilder getAgeBuilder(final Run owner, final String url) { + return decorated.getAgeBuilder(owner, url); + } + + @Override + public FileNameRenderer getFileNameRenderer(final Run owner) { + return decorated.getFileNameRenderer(owner); + } + + @VisibleForTesting + @Override + public String getDefaultName() { + return decorated.getDefaultName(); + } + + @Override + public String getId() { + return decorated.getId(); + } + + @Override + public String getName() { + return decorated.getName(); + } + + @Override + public StaticAnalysisLabelProvider setName(@CheckForNull final String name) { + return decorated.setName(name); + } + + @Override + @Generated + public String toString() { + return decorated.toString(); + } + + @Override + public String getLinkName() { + return decorated.getLinkName(); + } + + @Override + public String getTrendName() { + return decorated.getTrendName(); + } + + @Override + public String getToolTip(final int numberOfItems) { + return decorated.getToolTip(numberOfItems); + } + + @Override + public Localizable getToolTipLocalizable(final int numberOfItems) { + return decorated.getToolTipLocalizable(numberOfItems); + } + + @Override + public String getDescription(final Issue issue) { + return decorated.getDescription(issue); + } + + @Override + public String getSourceCodeDescription(final Run build, final Issue issue) { + return decorated.getSourceCodeDescription(build, issue); + } + + private final StaticAnalysisLabelProvider decorated; + private final String icon; + + CustomIconLabelProvider(final StaticAnalysisLabelProvider decorated, final String icon) { + super(decorated.getId(), decorated.getName()); + this.decorated = decorated; + + this.icon = icon; + } + + @Override + public String getSmallIconUrl() { + return icon; + } + + @Override + public String getLargeIconUrl() { + return icon; + } + } } From 8f0835f878493a01b310c71109372f67812d328c Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Tue, 7 Jan 2025 18:37:44 +0100 Subject: [PATCH 4/5] Move parsers into parser top-level package. --- .../analysis/warnings/steps/MiscIssuesRecorderITest.java | 2 +- .../jenkins/plugins/analysis/warnings/integrations/spotbugs.xml | 2 +- .../plugins/analysis/warnings/integrations/spotbugsXml.xml | 2 +- .../io/jenkins/plugins/analysis/warnings/steps/spotbugsXml.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java index eb376e0ccc..9b8a0f2d03 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java @@ -15,7 +15,7 @@ import edu.hm.hafner.analysis.Issue; import edu.hm.hafner.analysis.Report; import edu.hm.hafner.analysis.Severity; -import edu.hm.hafner.analysis.parser.findbugs.FindBugsParser.PriorityProperty; +import edu.hm.hafner.analysis.parser.FindBugsParser.PriorityProperty; import hudson.model.FreeStyleProject; import hudson.model.Result; diff --git a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugs.xml b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugs.xml index 19f37e7e58..5574cd1115 100644 --- a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugs.xml +++ b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugs.xml @@ -16,4 +16,4 @@ Common false-positive cases include:</p> to instruct SpotBugs that ignoring the return value of this method is acceptable. </p> - Bad use of return value from method \ No newline at end of file + Bad use of return value from method diff --git a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugsXml.xml b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugsXml.xml index 19f37e7e58..5574cd1115 100644 --- a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugsXml.xml +++ b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/spotbugsXml.xml @@ -16,4 +16,4 @@ Common false-positive cases include:</p> to instruct SpotBugs that ignoring the return value of this method is acceptable. </p> - Bad use of return value from method \ No newline at end of file + Bad use of return value from method diff --git a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/steps/spotbugsXml.xml b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/steps/spotbugsXml.xml index 19f37e7e58..5574cd1115 100644 --- a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/steps/spotbugsXml.xml +++ b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/steps/spotbugsXml.xml @@ -16,4 +16,4 @@ Common false-positive cases include:</p> to instruct SpotBugs that ignoring the return value of this method is acceptable. </p> - Bad use of return value from method \ No newline at end of file + Bad use of return value from method From ee575bff169f881d242256652ad1fac32f7eca51 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Wed, 8 Jan 2025 15:42:10 +0100 Subject: [PATCH 5/5] Improve API of module detector. --- .../analysis/core/steps/IssuesScanner.java | 16 +++++++++------- .../warnings/steps/ModuleDetectorITest.java | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java index e49b1f37d7..e360817595 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -20,8 +21,8 @@ import edu.hm.hafner.analysis.FileNameResolver; import edu.hm.hafner.analysis.FingerprintGenerator; import edu.hm.hafner.analysis.FullTextFingerprint; -import edu.hm.hafner.analysis.ModuleDetector; -import edu.hm.hafner.analysis.ModuleDetector.FileSystem; +import edu.hm.hafner.analysis.ModuleDetectorRunner; +import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade; import edu.hm.hafner.analysis.ModuleResolver; import edu.hm.hafner.analysis.PackageNameResolver; import edu.hm.hafner.analysis.Report; @@ -335,8 +336,9 @@ private void resolveModuleNames(final Report report, final File workspace) { report.logInfo("Resolving module names from module definitions (build.xml, pom.xml, or Manifest.mf files)"); try { - ModuleResolver resolver = new ModuleResolver(); - resolver.run(report, new ModuleDetector(workspace.toPath(), new DefaultFileSystem())); + var runner = new ModuleDetectorRunner(workspace.toPath(), new DefaultFileSystem()); + var resolver = new ModuleResolver(runner); + resolver.run(report); } catch (InvalidPathException exception) { report.logException(exception, "Resolving of modul names aborted"); @@ -370,7 +372,7 @@ private void createFingerprints(final Report report) { /** * Provides file system operations using real IO. */ - private static final class DefaultFileSystem implements FileSystem { + private static final class DefaultFileSystem implements FileSystemFacade { @MustBeClosed @Override public InputStream open(final String fileName) throws IOException { @@ -378,8 +380,8 @@ public InputStream open(final String fileName) throws IOException { } @Override - public String[] find(final Path root, final String pattern) { - return new FileFinder(pattern).find(root.toFile()); + public List find(final Path root, final String pattern) { + return Arrays.stream(new FileFinder(pattern).find(root.toFile())).toList(); } } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ModuleDetectorITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ModuleDetectorITest.java index 20c4a7a089..24bb11c9fb 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ModuleDetectorITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ModuleDetectorITest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -import edu.hm.hafner.analysis.ModuleDetector; +import edu.hm.hafner.analysis.ModuleDetectorRunner; import hudson.FilePath; import hudson.model.FreeStyleProject; @@ -23,7 +23,7 @@ import static org.assertj.core.api.Assertions.*; /** - * Integration test for the {@link ModuleDetector}. + * Integration test for the {@link ModuleDetectorRunner}. * *

* These tests work on several pom.xml, build.xml and MANIFEST.MF files that will be copied to the workspace for each