From a154e5b13dd7d900829d1d951efff50c41f25738 Mon Sep 17 00:00:00 2001 From: Michael Osipov Date: Sun, 11 Jun 2023 16:07:27 +0200 Subject: [PATCH] [MINVOKER-344] Move reporting rendering logic into a ReportRenderer class This closes #193 --- .../maven/plugins/invoker/InvokerReport.java | 254 ++---------------- .../invoker/InvokerReportRenderer.java | 210 +++++++++++++++ src/main/resources/invoker-report.properties | 37 ++- .../resources/invoker-report_de.properties | 33 ++- .../resources/invoker-report_fr.properties | 35 ++- 5 files changed, 281 insertions(+), 288 deletions(-) create mode 100644 src/main/java/org/apache/maven/plugins/invoker/InvokerReportRenderer.java diff --git a/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java b/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java index 40cc33bd..339705a4 100644 --- a/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java +++ b/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java @@ -20,15 +20,10 @@ import java.io.File; import java.io.IOException; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.MessageFormat; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import org.apache.maven.doxia.sink.Sink; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -52,12 +47,6 @@ @Mojo(name = "report", threadSafe = true) public class InvokerReport extends AbstractMavenReport { - /** - * Internationalization component. - */ - @Component - protected I18N i18n; - /** * Base directory where all build reports have been written to. */ @@ -65,58 +54,14 @@ public class InvokerReport extends AbstractMavenReport { private File reportsDirectory; /** - * The number format used to print percent values in the report locale. - */ - private NumberFormat percentFormat; - - /** - * The number format used to print time values in the report locale. - */ - private NumberFormat secondsFormat; - - /** - * The format used to print build name and description. + * Internationalization component */ - private MessageFormat nameAndDescriptionFormat; + @Component + protected I18N i18n; protected void executeReport(Locale locale) throws MavenReportException { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); - percentFormat = new DecimalFormat(getText(locale, "report.invoker.format.percent"), symbols); - secondsFormat = new DecimalFormat(getText(locale, "report.invoker.format.seconds"), symbols); - nameAndDescriptionFormat = new MessageFormat(getText(locale, "report.invoker.format.name_with_description")); - - Sink sink = getSink(); - - sink.head(); - - sink.title(); - sink.text(getText(locale, "report.invoker.result.title")); - sink.title_(); - - sink.head_(); - - sink.body(); - - sink.section1(); - sink.sectionTitle1(); - sink.text(getText(locale, "report.invoker.result.title")); - sink.sectionTitle1_(); - sink.paragraph(); - sink.text(getText(locale, "report.invoker.result.description")); - sink.paragraph_(); - sink.section1_(); - - // ---------------------------------- - // build buildJob beans - // ---------------------------------- - File[] reportFiles = ReportUtils.getReportFiles(reportsDirectory); - if (reportFiles.length <= 0) { - getLog().info("no invoker report files found, skip report generation"); - return; - } - + File[] reportFiles = getReportFiles(); BuildJobXpp3Reader buildJobReader = new BuildJobXpp3Reader(); - List buildJobs = new ArrayList<>(reportFiles.length); for (File reportFile : reportFiles) { try (XmlStreamReader xmlReader = ReaderFactory.newXmlReader(reportFile)) { @@ -127,193 +72,38 @@ protected void executeReport(Locale locale) throws MavenReportException { throw new MavenReportException("Failed to read report file: " + reportFile, e); } } - - // ---------------------------------- - // summary - // ---------------------------------- - - constructSummarySection(buildJobs, locale); - - // ---------------------------------- - // per file/it detail - // ---------------------------------- - - sink.section2(); - sink.sectionTitle2(); - - sink.text(getText(locale, "report.invoker.detail.title")); - - sink.sectionTitle2_(); - - sink.section2_(); - - // detail tests table header - sink.table(); - sink.tableRows(null, false); - - sink.tableRow(); - // ------------------------------------------- - // name | Result | time | message - // ------------------------------------------- - sinkTableHeader(sink, getText(locale, "report.invoker.detail.name")); - sinkTableHeader(sink, getText(locale, "report.invoker.detail.result")); - sinkTableHeader(sink, getText(locale, "report.invoker.detail.time")); - sinkTableHeader(sink, getText(locale, "report.invoker.detail.message")); - - sink.tableRow_(); - - for (BuildJob buildJob : buildJobs) { - renderBuildJob(buildJob); - } - - sink.tableRows_(); - sink.table_(); - - sink.body_(); - - sink.flush(); - sink.close(); + InvokerReportRenderer r = new InvokerReportRenderer(getSink(), i18n, locale, getLog(), buildJobs); + r.render(); } - private void constructSummarySection(List buildJobs, Locale locale) { - Sink sink = getSink(); - - sink.section2(); - sink.sectionTitle2(); - - sink.text(getText(locale, "report.invoker.summary.title")); - - sink.sectionTitle2_(); - sink.section2_(); - - // ------------------------------------------------------------------------ - // Building a table with - // it number | succes nb | failed nb | Success rate | total time | avg time - // ------------------------------------------------------------------------ - - sink.table(); - sink.tableRows(null, false); - - sink.tableRow(); - - sinkTableHeader(sink, getText(locale, "report.invoker.summary.number")); - sinkTableHeader(sink, getText(locale, "report.invoker.summary.success")); - sinkTableHeader(sink, getText(locale, "report.invoker.summary.failed")); - sinkTableHeader(sink, getText(locale, "report.invoker.summary.skipped")); - sinkTableHeader(sink, getText(locale, "report.invoker.summary.success.rate")); - sinkTableHeader(sink, getText(locale, "report.invoker.summary.time.total")); - sinkTableHeader(sink, getText(locale, "report.invoker.summary.time.avg")); - - int number = buildJobs.size(); - int success = 0; - int failed = 0; - int skipped = 0; - double totalTime = 0; - - for (BuildJob buildJob : buildJobs) { - if (BuildJob.Result.SUCCESS.equals(buildJob.getResult())) { - success++; - } else if (BuildJob.Result.SKIPPED.equals(buildJob.getResult())) { - skipped++; - } else { - failed++; - } - totalTime += buildJob.getTime(); - } - - sink.tableRow_(); - sink.tableRow(); - - sinkCell(sink, Integer.toString(number)); - sinkCell(sink, Integer.toString(success)); - sinkCell(sink, Integer.toString(failed)); - sinkCell(sink, Integer.toString(skipped)); - - if (success + failed > 0) { - sinkCell(sink, percentFormat.format((double) success / (success + failed))); - } else { - sinkCell(sink, ""); - } - - sinkCell(sink, secondsFormat.format(totalTime)); - - sinkCell(sink, secondsFormat.format(totalTime / number)); - - sink.tableRow_(); - - sink.tableRows_(); - sink.table_(); - } - - private void renderBuildJob(BuildJob buildJob) { - Sink sink = getSink(); - sink.tableRow(); - sinkCell(sink, getBuildJobReportName(buildJob)); - // FIXME image - sinkCell(sink, buildJob.getResult()); - sinkCell(sink, secondsFormat.format(buildJob.getTime())); - sinkCell(sink, buildJob.getFailureMessage()); - sink.tableRow_(); - } - - private String getBuildJobReportName(BuildJob buildJob) { - String buildJobName = buildJob.getName(); - String buildJobDescription = buildJob.getDescription(); - boolean emptyJobName = buildJobName == null || buildJobName.isEmpty(); - boolean emptyJobDescription = buildJobDescription == null || buildJobDescription.isEmpty(); - boolean isReportJobNameComplete = !emptyJobName && !emptyJobDescription; - if (isReportJobNameComplete) { - return getFormattedName(buildJobName, buildJobDescription); - } else { - String buildJobProject = buildJob.getProject(); - if (!emptyJobName) { - getLog().warn(incompleteNameWarning("description", buildJobProject)); - } else if (!emptyJobDescription) { - getLog().warn(incompleteNameWarning("name", buildJobProject)); - } - return buildJobProject; - } - } - - private static String incompleteNameWarning(String missing, String pom) { - return String.format( - "Incomplete job name-description: %s is missing. " + "POM (%s) will be used in place of job name.", - missing, pom); + /** + * @param locale The locale + * @param key The key to search for + * @return The text appropriate for the locale. + */ + private String getI18nString(Locale locale, String key) { + return i18n.getString("invoker-report", locale, "report.invoker." + key); } - private String getFormattedName(String name, String description) { - return nameAndDescriptionFormat.format(new Object[] {name, description}); + /** {@inheritDoc} */ + public String getName(Locale locale) { + return getI18nString(locale, "name"); } + /** {@inheritDoc} */ public String getDescription(Locale locale) { - return getText(locale, "report.invoker.result.description"); - } - - public String getName(Locale locale) { - return getText(locale, "report.invoker.result.name"); + return getI18nString(locale, "description"); } public String getOutputName() { return "invoker-report"; } - public boolean canGenerateReport() { - return ReportUtils.getReportFiles(reportsDirectory).length > 0; + private File[] getReportFiles() { + return ReportUtils.getReportFiles(reportsDirectory); } - private String getText(Locale locale, String key) { - return i18n.getString("invoker-report", locale, key); - } - - private void sinkTableHeader(Sink sink, String header) { - sink.tableHeaderCell(); - sink.text(header); - sink.tableHeaderCell_(); - } - - private void sinkCell(Sink sink, String text) { - sink.tableCell(); - sink.text(text); - sink.tableCell_(); + public boolean canGenerateReport() { + return getReportFiles().length > 0; } } diff --git a/src/main/java/org/apache/maven/plugins/invoker/InvokerReportRenderer.java b/src/main/java/org/apache/maven/plugins/invoker/InvokerReportRenderer.java new file mode 100644 index 00000000..eae9739b --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/invoker/InvokerReportRenderer.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.plugins.invoker; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.invoker.model.BuildJob; +import org.apache.maven.reporting.AbstractMavenReportRenderer; +import org.codehaus.plexus.i18n.I18N; + +public class InvokerReportRenderer extends AbstractMavenReportRenderer { + private final I18N i18n; + private final Locale locale; + private final Log log; + private final List buildJobs; + + /** + * The number format used to print percent values in the report locale. + */ + private NumberFormat percentFormat; + + /** + * The number format used to print time values in the report locale. + */ + private NumberFormat secondsFormat; + + /** + * The format used to print build name and description. + */ + private MessageFormat nameAndDescriptionFormat; + + public InvokerReportRenderer(Sink sink, I18N i18n, Locale locale, Log log, List buildJobs) { + super(sink); + this.i18n = i18n; + this.locale = locale; + this.log = log; + this.buildJobs = buildJobs; + } + + @Override + public String getTitle() { + return getI18nString("title"); + } + + /** + * @param key The key to translate. + * @return the translated key. + */ + private String getI18nString(String key) { + return i18n.getString("invoker-report", locale, "report.invoker." + key); + } + + /** + * @param key The key to translate. + * @param args The args to pass to translated string. + * @return the translated key. + */ + private String formatI18nString(String key, Object... args) { + return i18n.format("invoker-report", locale, "report.invoker." + key, args); + } + + @Override + protected void renderBody() { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); + percentFormat = new DecimalFormat(getI18nString("format.percent"), symbols); + secondsFormat = new DecimalFormat(getI18nString("format.seconds"), symbols); + nameAndDescriptionFormat = new MessageFormat(getI18nString("format.name_with_description")); + + startSection(getTitle()); + paragraph(getI18nString("description")); + + renderSectionSummary(); + + renderSectionDetails(); + + endSection(); + } + + private void renderSectionSummary() { + startSection(getI18nString("summary.title")); + + startTable(); + + tableHeader(new String[] { + getI18nString("summary.builds"), + getI18nString("summary.success"), + getI18nString("summary.failures"), + getI18nString("summary.skipped"), + getI18nString("summary.successrate"), + getI18nString("summary.time") + }); + + int totalBuilds = buildJobs.size(); + int totalSuccess = 0; + int totalFailures = 0; + int totalSkipped = 0; + float totalTime = 0.0f; + + for (BuildJob buildJob : buildJobs) { + switch (buildJob.getResult()) { + case BuildJob.Result.SUCCESS: + totalSuccess++; + break; + case BuildJob.Result.SKIPPED: + totalSkipped++; + break; + default: + totalFailures++; + } + totalTime += buildJob.getTime(); + } + + tableRow(new String[] { + Integer.toString(totalBuilds), + Integer.toString(totalSuccess), + Integer.toString(totalFailures), + Integer.toString(totalSkipped), + (totalSuccess + totalFailures > 0) + ? percentFormat.format(totalSuccess / (float) (totalSuccess + totalFailures)) + : "", + secondsFormat.format(totalTime) + }); + + endTable(); + + endSection(); + } + + private void renderSectionDetails() { + startSection(getI18nString("detail.title")); + + startTable(); + + tableHeader(new String[] { + getI18nString("detail.name"), + getI18nString("detail.result"), + getI18nString("detail.time"), + getI18nString("detail.message") + }); + + for (BuildJob buildJob : buildJobs) { + renderBuildJob(buildJob); + } + + endTable(); + + endSection(); + } + + private void renderBuildJob(BuildJob buildJob) { + tableRow(new String[] { + getBuildJobReportName(buildJob), + // FIXME image + buildJob.getResult(), + secondsFormat.format(buildJob.getTime()), + buildJob.getFailureMessage() + }); + } + + private String getBuildJobReportName(BuildJob buildJob) { + String buildJobName = buildJob.getName(); + String buildJobDescription = buildJob.getDescription(); + boolean emptyJobName = buildJobName == null || buildJobName.isEmpty(); + boolean emptyJobDescription = buildJobDescription == null || buildJobDescription.isEmpty(); + boolean isReportJobNameComplete = !emptyJobName && !emptyJobDescription; + if (isReportJobNameComplete) { + return getFormattedName(buildJobName, buildJobDescription); + } else { + String buildJobProject = buildJob.getProject(); + if (!emptyJobName) { + log.warn(incompleteNameWarning("description", buildJobProject)); + } else if (!emptyJobDescription) { + log.warn(incompleteNameWarning("name", buildJobProject)); + } + return buildJobProject; + } + } + + private static String incompleteNameWarning(String missing, String pom) { + return "Incomplete job name-description: " + missing + " is missing. POM (" + pom + + ") will be used in place of job name!"; + } + + private String getFormattedName(String name, String description) { + return nameAndDescriptionFormat.format(new Object[] {name, description}); + } +} diff --git a/src/main/resources/invoker-report.properties b/src/main/resources/invoker-report.properties index 5fe569d2..e746c3ef 100644 --- a/src/main/resources/invoker-report.properties +++ b/src/main/resources/invoker-report.properties @@ -15,22 +15,21 @@ # specific language governing permissions and limitations # under the License. -report.invoker.result.description = The results of the Maven invocations. -report.invoker.result.name = Invoker Build Results -report.invoker.result.title = Invoker Report -report.invoker.summary.title = Summary -report.invoker.summary.number = Builds -report.invoker.summary.success = Success -report.invoker.summary.failed = Failures -report.invoker.summary.skipped = Skipped -report.invoker.summary.success.rate = Success Rate -report.invoker.summary.time.total = Total Time -report.invoker.summary.time.avg = Avg Time -report.invoker.detail.title = Build Details -report.invoker.detail.name = Name -report.invoker.detail.result = Result -report.invoker.detail.time = Time -report.invoker.detail.message = Message -report.invoker.format.percent = 0.0% -report.invoker.format.seconds = 0.0\u00A0s -report.invoker.format.name_with_description = {0}: {1} +report.invoker.name=Invoker +report.invoker.description=Report on the build results of the Maven invocations. +report.invoker.title=Invoker Report +report.invoker.summary.title=Summary +report.invoker.summary.builds=Builds +report.invoker.summary.success=Success +report.invoker.summary.failures=Failures +report.invoker.summary.skipped=Skipped +report.invoker.summary.successrate=Success Rate +report.invoker.summary.time=Time +report.invoker.detail.title=Build Details +report.invoker.detail.name=Name +report.invoker.detail.result=Result +report.invoker.detail.time=Time +report.invoker.detail.message=Message +report.invoker.format.percent=0.0% +report.invoker.format.seconds=0.0\u00A0s +report.invoker.format.name_with_description={0}: {1} diff --git a/src/main/resources/invoker-report_de.properties b/src/main/resources/invoker-report_de.properties index d1016e30..41e030be 100644 --- a/src/main/resources/invoker-report_de.properties +++ b/src/main/resources/invoker-report_de.properties @@ -15,21 +15,18 @@ # specific language governing permissions and limitations # under the License. -report.invoker.result.description = Die Ergebnisse der Maven-Ausf\u00FChrungen. -report.invoker.result.name = Invoker-Build-Ergebnisse -report.invoker.result.title = Invoker-Bericht -report.invoker.summary.title = Zusammenfassungen -report.invoker.summary.number = Builds -report.invoker.summary.success = Erfolge -report.invoker.summary.failed = Fehlschl\u00E4ge -report.invoker.summary.skipped = Ausgelassen -report.invoker.summary.success.rate = Erfolgsrate -report.invoker.summary.time.total = Gesamtzeit -report.invoker.summary.time.avg = Durchschnittszeit -report.invoker.detail.title = Build-Details -report.invoker.detail.name = Name -report.invoker.detail.result = Ergebnis -report.invoker.detail.time = Zeit -report.invoker.detail.message = Meldung -report.invoker.format.percent = 0.0\u00A0% -report.invoker.format.seconds = 0.0\u00A0s +report.invoker.description=Bericht \u00FCber die Build-Ergebnisse der Maven-Ausf\u00FChrungen. +report.invoker.title=Invoker-Bericht +report.invoker.summary.title=Zusammenfassung +report.invoker.summary.builds=Builds +report.invoker.summary.success=Erfolge +report.invoker.summary.failures=Fehlschl\u00E4ge +report.invoker.summary.skipped=Ausgelassen +report.invoker.summary.successrate=Erfolgsrate +report.invoker.summary.time=Zeit +report.invoker.detail.title=Build-Details +report.invoker.detail.name=Name +report.invoker.detail.result=Ergebnis +report.invoker.detail.time=Zeit +report.invoker.detail.message=Meldung +report.invoker.format.percent=0.0\u00A0% diff --git a/src/main/resources/invoker-report_fr.properties b/src/main/resources/invoker-report_fr.properties index 1d3393ab..7da545ad 100644 --- a/src/main/resources/invoker-report_fr.properties +++ b/src/main/resources/invoker-report_fr.properties @@ -15,22 +15,19 @@ # specific language governing permissions and limitations # under the License. -report.invoker.result.description = Résultat des invocations de Maven -report.invoker.result.name = Invoker Résultat de builds -report.invoker.result.title = Invoker Rapport -report.invoker.summary.title = Sommaire -report.invoker.summary.number = Builds -report.invoker.summary.success = Réussis -report.invoker.summary.failed = Echecs -report.invoker.summary.skipped = Ignorés -report.invoker.summary.success.rate = Taux de réussite -report.invoker.summary.time.total = Durée totale -report.invoker.summary.time.avg = Durée moyenne -report.invoker.detail.title = Détails de Build -report.invoker.detail.name = Nom -report.invoker.detail.result = Résultat -report.invoker.detail.time = Durée -report.invoker.detail.message = Message -report.invoker.format.percent = 0.0% -report.invoker.format.seconds = 0.0\u00A0s -report.invoker.format.name_with_description = {0} : {1} +report.invoker.description=Résultat des invocations de Maven +report.invoker.title=Invoker Rapport +report.invoker.summary.title=Sommaire +report.invoker.summary.builds=Builds +report.invoker.summary.success=Réussis +report.invoker.summary.failures=Echecs +report.invoker.summary.skipped=Ignorés +report.invoker.summary.successrate=Taux de réussite +report.invoker.summary.time=Durée +report.invoker.detail.title=Détails de Build +report.invoker.detail.name=Nom +report.invoker.detail.result=Résultat +report.invoker.detail.time=Durée +report.invoker.detail.message=Message +report.invoker.format.percent=0.0\u00A0% +report.invoker.format.name_with_description={0} : {1}