From 60a4b2f29f5e310d7968d6d6c3c53b3c5636ddc3 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 21 Feb 2025 16:20:27 +0100 Subject: [PATCH 1/4] Add metric customization and refactor trend chart logic Update trend chart implementations to allow metric visibility customization by the user. Improve separation of coverage and metrics handling. Introduce a configuration system for charts with support for lines vs. filled areas. --- plugin/pom.xml | 4 + .../metrics/charts/CoverageSeriesBuilder.java | 31 +----- .../metrics/charts/CoverageTrendChart.java | 63 ++++++----- .../metrics/charts/MetricsTrendChart.java | 19 +++- .../coverage/metrics/charts/TrendChart.java | 30 ++++- .../metrics/steps/CoverageBuildAction.java | 11 ++ .../metrics/steps/CoverageJobAction.java | 103 +++++++++++++++++- .../CoverageJobAction/configure-trend.js | 34 ++++++ .../steps/CoverageJobAction/floatingBox.jelly | 38 ++++++- .../charts/CoverageSeriesBuilderTest.java | 21 ++-- .../metrics/steps/CoverageJobActionTest.java | 73 ++++++++++++- 11 files changed, 347 insertions(+), 80 deletions(-) create mode 100644 plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js diff --git a/plugin/pom.xml b/plugin/pom.xml index cb47417b..121db571 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -85,6 +85,10 @@ javax.annotation javax.annotation-api + + commons-io + commons-io + diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java index 079eaed9..220f29ee 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java @@ -3,10 +3,10 @@ import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.echarts.line.SeriesBuilder; -import java.util.HashMap; +import java.util.Arrays; import java.util.Map; +import java.util.stream.Collectors; -import io.jenkins.plugins.coverage.metrics.model.Baseline; import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; /** @@ -25,28 +25,9 @@ public class CoverageSeriesBuilder extends SeriesBuilder { @Override protected Map computeSeries(final CoverageStatistics statistics) { - Map series = new HashMap<>(); - - add(statistics, Metric.LINE, LINE_COVERAGE, series); - add(statistics, Metric.BRANCH, BRANCH_COVERAGE, series); - add(statistics, Metric.MUTATION, MUTATION_COVERAGE, series); - add(statistics, Metric.TEST_STRENGTH, TEST_STRENGTH, series); - add(statistics, Metric.MCDC_PAIR, MCDC_PAIR_COVERAGE, series); - add(statistics, Metric.FUNCTION_CALL, FUNCTION_CALL_COVERAGE, series); - - if (statistics.containsValue(Metric.MCDC_PAIR, Baseline.PROJECT) - || statistics.containsValue(Metric.FUNCTION_CALL, Baseline.PROJECT)) { - // Method coverage is only relevant if MC/DC pair or function call coverage is available - add(statistics, Metric.METHOD, METHOD_COVERAGE, series); - } - - return series; - } - - private void add(final CoverageStatistics statistics, final Metric metric, final String chartId, - final Map series) { - if (statistics.containsValue(metric, Baseline.PROJECT)) { - series.put(chartId, statistics.roundValue(Baseline.PROJECT, metric)); - } + return Arrays.stream(Metric.values()) + .filter(Metric::isCoverage) + .filter(statistics::containsValue) + .collect(Collectors.toMap(Metric::toTagName, statistics::roundValue)); } } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java index 0bff6a9f..107a5a31 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java @@ -4,9 +4,11 @@ import edu.hm.hafner.echarts.BuildResult; import edu.hm.hafner.echarts.ChartModelConfiguration; import edu.hm.hafner.echarts.JacksonFacade; -import edu.hm.hafner.echarts.line.LineSeries.FilledMode; import edu.hm.hafner.echarts.line.LinesChartModel; -import edu.hm.hafner.echarts.line.LinesDataSet; +import edu.hm.hafner.util.VisibleForTesting; + +import java.util.List; +import java.util.Set; import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import io.jenkins.plugins.echarts.JenkinsPalette; @@ -21,6 +23,23 @@ * @see JacksonFacade */ public class CoverageTrendChart extends TrendChart { + @VisibleForTesting + CoverageTrendChart() { + super(Set.of(), false); + } + + /** + * Creates a new {@link CoverageTrendChart}. + * + * @param visibleMetrics + * the metrics to render in the trend chart + * @param useLines + * determines if the chart should use lines or filled areas + */ + public CoverageTrendChart(final Set visibleMetrics, final boolean useLines) { + super(visibleMetrics, useLines); + } + @Override public LinesChartModel create(final Iterable> results, final ChartModelConfiguration configuration) { @@ -29,36 +48,24 @@ public LinesChartModel create(final Iterable> re LinesChartModel model = new LinesChartModel(dataSet); if (dataSet.isNotEmpty()) { model.useContinuousRangeAxis(); - model.setRangeMax(100); + model.setRangeMax(100); // Restrict the range to 100% model.setRangeMin(dataSet.getMinimumValue()); - var filledMode = computeFilledMode(dataSet); - addSeriesIfAvailable(dataSet, model, Metric.LINE, JenkinsPalette.GREEN.normal(), filledMode); - addSeriesIfAvailable(dataSet, model, Metric.BRANCH, JenkinsPalette.GREEN.dark(), filledMode); - addSeriesIfAvailable(dataSet, model, Metric.MUTATION, JenkinsPalette.GREEN.dark(), filledMode); - addSeriesIfAvailable(dataSet, model, Metric.TEST_STRENGTH, JenkinsPalette.GREEN.light(), filledMode); + int colorIndex = 0; + for (Metric metric : List.of(Metric.MODULE, Metric.PACKAGE, Metric.FILE, Metric.CLASS, Metric.METHOD)) { + addSeriesIfAvailable(dataSet, model, metric, JenkinsPalette.chartColor(colorIndex++).normal()); + } - addSeriesIfAvailable(dataSet, model, Metric.MCDC_PAIR, JenkinsPalette.RED.light(), filledMode); - addSeriesIfAvailable(dataSet, model, Metric.METHOD, JenkinsPalette.RED.normal(), filledMode); - addSeriesIfAvailable(dataSet, model, Metric.FUNCTION_CALL, JenkinsPalette.RED.dark(), filledMode); - } - return model; - } + addSeriesIfAvailable(dataSet, model, Metric.LINE, JenkinsPalette.GREEN.normal()); + addSeriesIfAvailable(dataSet, model, Metric.BRANCH, JenkinsPalette.GREEN.dark()); + addSeriesIfAvailable(dataSet, model, Metric.INSTRUCTION, JenkinsPalette.GREEN.light()); - /** - * Returns the filled mode based on the contained coverage values. If the dataset contains MCDC or Function Call - * coverage, then the filled mode is set to LINES, otherwise FILLED. - * - * @param dataSet - * the dataset to check - * - * @return the filled mode - */ - private FilledMode computeFilledMode(final LinesDataSet dataSet) { - if (dataSet.containsSeries(CoverageSeriesBuilder.MCDC_PAIR_COVERAGE) - || dataSet.containsSeries(CoverageSeriesBuilder.FUNCTION_CALL_COVERAGE)) { - return FilledMode.LINES; + addSeriesIfAvailable(dataSet, model, Metric.MUTATION, JenkinsPalette.GREEN.dark()); + addSeriesIfAvailable(dataSet, model, Metric.TEST_STRENGTH, JenkinsPalette.GREEN.light()); + + addSeriesIfAvailable(dataSet, model, Metric.MCDC_PAIR, JenkinsPalette.RED.light()); + addSeriesIfAvailable(dataSet, model, Metric.FUNCTION_CALL, JenkinsPalette.RED.dark()); } - return FilledMode.FILLED; + return model; } } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java index 6eeecc9a..b155fee3 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java @@ -4,10 +4,11 @@ import edu.hm.hafner.echarts.BuildResult; import edu.hm.hafner.echarts.ChartModelConfiguration; import edu.hm.hafner.echarts.JacksonFacade; -import edu.hm.hafner.echarts.line.LineSeries.FilledMode; import edu.hm.hafner.echarts.line.LinesChartModel; import edu.hm.hafner.echarts.line.LinesDataSet; +import java.util.Set; + import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import io.jenkins.plugins.echarts.JenkinsPalette; @@ -21,6 +22,18 @@ * @see JacksonFacade */ public class MetricsTrendChart extends TrendChart { + /** + * Creates a new {@link MetricsTrendChart}. + * + * @param visibleMetrics + * the metrics to render in the trend chart + * @param useLines + * determines if the chart should use lines or filled areas + */ + public MetricsTrendChart(final Set visibleMetrics, final boolean useLines) { + super(visibleMetrics, useLines); + } + @Override public LinesChartModel create(final Iterable> results, final ChartModelConfiguration configuration) { @@ -36,8 +49,8 @@ public LinesChartModel create(final Iterable> re for (var tag : dataSet.getDataSetIds()) { Metric metric = Metric.fromTag(tag); addSeriesIfAvailable(dataSet, model, metric.getDisplayName(), - tag, JenkinsPalette.chartColor(colorIndex++).normal(), - FilledMode.LINES); + tag, JenkinsPalette.chartColor(colorIndex++).normal() + ); } } return model; diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/TrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/TrendChart.java index 45f9f141..023af585 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/TrendChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/TrendChart.java @@ -10,6 +10,10 @@ import edu.hm.hafner.echarts.line.LinesChartModel; import edu.hm.hafner.echarts.line.LinesDataSet; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; /** @@ -21,15 +25,25 @@ * @see JacksonFacade */ public abstract class TrendChart { + private static final Set ALL_METRICS = Arrays.stream(Metric.values()).collect(Collectors.toSet()); + /** - * Create a Trend Chart Instance that is either for Coverage or Metrics. + * Create a trend chart instance for coverage or software metrics. * * @param metrics if the instance should be the metrics * * @return the created Trend Chart Instance */ public static TrendChart createTrendChart(final boolean metrics) { - return metrics ? new MetricsTrendChart() : new CoverageTrendChart(); + return metrics ? new MetricsTrendChart(ALL_METRICS, true) : new CoverageTrendChart(ALL_METRICS, false); + } + + private final Set visibleMetrics; + private final FilledMode filledMode; + + TrendChart(final Set visibleMetrics, final boolean useLines) { + this.visibleMetrics = visibleMetrics; + filledMode = useLines ? FilledMode.LINES : FilledMode.FILLED; } /** @@ -47,8 +61,8 @@ public abstract LinesChartModel create(Iterable> ChartModelConfiguration configuration); void addSeriesIfAvailable(final LinesDataSet dataSet, final LinesChartModel model, - final String name, final String seriesId, final String color, final FilledMode filledMode) { - if (dataSet.containsSeries(seriesId)) { + final String name, final String seriesId, final String color) { + if (dataSet.containsSeries(seriesId) && isVisible(seriesId)) { LineSeries branchSeries = new LineSeries(name, color, StackedMode.SEPARATE_LINES, filledMode, dataSet.getSeries(seriesId)); @@ -57,13 +71,17 @@ void addSeriesIfAvailable(final LinesDataSet dataSet, final LinesChartModel mode } void addSeriesIfAvailable(final LinesDataSet dataSet, final LinesChartModel model, - final Metric metric, final String color, final FilledMode filledMode) { + final Metric metric, final String color) { var tagName = metric.toTagName(); - if (dataSet.containsSeries(tagName)) { + if (dataSet.containsSeries(tagName) && isVisible(tagName)) { LineSeries branchSeries = new LineSeries(metric.getDisplayName(), color, StackedMode.SEPARATE_LINES, filledMode, dataSet.getSeries(tagName)); model.addSeries(branchSeries); } } + + private boolean isVisible(final String seriesId) { + return visibleMetrics.contains(Metric.fromTag(seriesId)); + } } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java index f135cc98..59bba932 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java @@ -405,6 +405,17 @@ public List getValues(final Baseline baseline) { return filterImportantMetrics(getValueStream(baseline)); } + /** + * Returns whether this action represents results with coverage metrics. Otherwise, this action represents software + * metrics. + * + * @return {@code true} if this action represents coverage metrics, {@code false} if this action represents software + * metrics + */ + public boolean hasCoverage() { + return getAllValues(Baseline.PROJECT).stream().map(Value::getMetric).anyMatch(Metric::isCoverage); + } + /** * Returns the value for the specified metric, if available. * diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java index 0c0af8b3..21d8b1f5 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java @@ -1,18 +1,34 @@ package io.jenkins.plugins.coverage.metrics.steps; -import java.util.Optional; -import java.util.function.Predicate; - import org.apache.commons.lang3.StringUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.coverage.Node; +import edu.hm.hafner.coverage.Value; import edu.hm.hafner.echarts.ChartModelConfiguration; +import edu.hm.hafner.echarts.JacksonFacade; import edu.hm.hafner.echarts.line.LinesChartModel; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + import hudson.model.Job; import io.jenkins.plugins.coverage.metrics.charts.CoverageTrendChart; +import io.jenkins.plugins.coverage.metrics.charts.MetricsTrendChart; +import io.jenkins.plugins.coverage.metrics.charts.TrendChart; +import io.jenkins.plugins.coverage.metrics.model.Baseline; import io.jenkins.plugins.echarts.ActionSelector; import io.jenkins.plugins.echarts.GenericBuildActionIterator.BuildActionIterable; import io.jenkins.plugins.echarts.TrendChartJobAction; @@ -25,6 +41,18 @@ * @author Ullrich Hafner */ public class CoverageJobAction extends TrendChartJobAction { + static final Set DEFAULT_TREND_METRICS = Set.of( + Metric.LINE, Metric.BRANCH, + Metric.MUTATION, Metric.TEST_STRENGTH, + Metric.NCSS, Metric.LOC, Metric.CYCLOMATIC_COMPLEXITY, Metric.COGNITIVE_COMPLEXITY); + private static final Set IGNORED_TREND_METRICS = Set.of( + Metric.ACCESS_TO_FOREIGN_DATA, Metric.WEIGHED_METHOD_COUNT, + Metric.NUMBER_OF_ACCESSORS, + Metric.WEIGHT_OF_CLASS, Metric.COHESION, Metric.CONTAINER, + Metric.FAN_OUT, Metric.MODULE); + + private static final JacksonFacade JACKSON = new JacksonFacade(); + private final String id; private final String name; private final String icon; @@ -72,12 +100,77 @@ public String getSearchUrl() { return getUrlName(); } + /** + * Returns the metrics that are available for the trend chart. + * + * @return the available metrics + */ + @SuppressWarnings("unused") // Used in trend chart configuration + public List getTrendMetrics() { + var latestAction = getLatestAction(); + + return latestAction.stream() + .map(a -> a.getAllValues(Baseline.PROJECT)) + .flatMap(Collection::stream) + .map(Value::getMetric) + .filter(m -> !IGNORED_TREND_METRICS.contains(m)) + .filter(m -> m.isCoverage() || latestAction.map(a -> !a.hasCoverage()).orElse(true)) + .toList(); + } + @Override protected LinesChartModel createChartModel(final String configuration) { - var iterable = new BuildActionIterable<>(CoverageBuildAction.class, getLatestAction(), + var latestAction = getLatestAction(); + var buildActions = new BuildActionIterable<>(CoverageBuildAction.class, latestAction, selectByUrl(), CoverageBuildAction::getStatistics); - return new CoverageTrendChart().create(iterable, ChartModelConfiguration.fromJson(configuration)); + Set actualValues = latestAction.map(a -> + a.getAllValues(Baseline.PROJECT).stream() + .map(Value::getMetric) + .collect(Collectors.toSet())) + .orElseGet(() -> Arrays.stream(Metric.values()).collect(Collectors.toSet())); + actualValues.retainAll(getVisibleMetrics(configuration)); + return getTrendChartType(latestAction, actualValues, useLines(configuration)) + .create(buildActions, ChartModelConfiguration.fromJson(configuration)); + } + + private boolean useLines(final String configuration) { + return JACKSON.getBoolean(configuration, "useLines", false); + } + + Set getVisibleMetrics(final String configuration) { + try { + var objectMapper = new ObjectMapper(); + ObjectNode jsonNodes = objectMapper.readValue(configuration, ObjectNode.class); + var metrics = jsonNodes.get("metrics"); + @SuppressWarnings("unchecked") + Map metricMapping = objectMapper.convertValue(metrics, Map.class); + if (metricMapping != null && !metricMapping.isEmpty()) { + return metricMapping.entrySet().stream() + .filter(Map.Entry::getValue) + .map(Map.Entry::getKey) + .map(Metric::valueOf) + .collect(Collectors.toSet()); + } + } + catch (JsonProcessingException | ClassCastException | IllegalArgumentException ignored) { + // ignore and return default values + } + + return DEFAULT_TREND_METRICS; + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private TrendChart getTrendChartType(final Optional latestAction, + final Set visibleMetrics, final boolean useLines) { + if (latestAction.isPresent()) { + var hasCoverage = latestAction.get().getAllValues(Baseline.PROJECT).stream() + .map(Value::getMetric).anyMatch(Metric::isCoverage); + if (!hasCoverage) { + return new MetricsTrendChart(visibleMetrics, useLines); + } + } + return new CoverageTrendChart(visibleMetrics, useLines); } @Override diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js new file mode 100644 index 00000000..2985202a --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js @@ -0,0 +1,34 @@ +/* global jQuery3, proxy, echartsJenkinsApi, bootstrap5 */ +window.addEventListener("DOMContentLoaded", () => { + const dataHolders = document.querySelectorAll(".coverage-trend-data-holder"); + + dataHolders.forEach(dataHolder => { + function fillCoverage(trendConfiguration, jsonConfiguration) { + const metrics = jsonConfiguration['metrics']; + if (metrics) { + Object.entries(metrics).forEach(([metric, isChecked]) => { + trendConfiguration.find(`input[type="checkbox"][name="${metric}"]`).prop('checked', isChecked); + }); + } + const useLines = jsonConfiguration['useLines']; + if (useLines) { + trendConfiguration.find('input[type="checkbox"][name="lines"]').prop('checked', useLines); + } + } + + function saveCoverage(trendConfiguration) { + const metrics = {}; + + trendConfiguration.find('input[type="checkbox"][id^="coverage-"]').each(function () { + metrics[jQuery3(this).attr('name')] = jQuery3(this).prop('checked'); + }); + return { + 'metrics': metrics, + 'useLines': trendConfiguration.find('input[name=lines]').prop('checked') + }; + } + + const url = dataHolder.getAttribute("data-url"); + echartsJenkinsApi.configureTrend('coverage-' + url, fillCoverage, saveCoverage); + }); +}); diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly index c5ac20a2..d1a920e1 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly @@ -1,4 +1,38 @@ - - + + + +
+ +
+ +
+ + +
+
+
+ +
+
+ + +
+
+ + + +
diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java index c637dc2b..bbea1022 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java @@ -1,9 +1,5 @@ package io.jenkins.plugins.coverage.metrics.charts; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -20,6 +16,11 @@ import edu.hm.hafner.util.ResourceTest; import edu.hm.hafner.util.VisibleForTesting; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*; @@ -44,7 +45,7 @@ void shouldHaveEmptyDataSetForEmptyIterator() { @Test void shouldCreateChart() { - TrendChart trendChart = new CoverageTrendChart(); + TrendChart trendChart = createTrend(); BuildResult smallLineCoverage = createResult(1, new CoverageBuilder().withMetric(Metric.LINE).withCovered(1).withMissed(1).build(), @@ -65,7 +66,7 @@ void shouldCreateChart() { @Test void shouldCreateStackedChartByDefault() { - TrendChart trendChart = new CoverageTrendChart(); + TrendChart trendChart = createTrend(); BuildResult smallLineCoverage = createResult(1, new CoverageBuilder().withMetric(Metric.LINE).withCovered(1).withMissed(1).build(), @@ -81,9 +82,13 @@ void shouldCreateStackedChartByDefault() { assertThat(lineCoverage.getRangeMin()).isEqualTo(50.0); } + private CoverageTrendChart createTrend() { + return new CoverageTrendChart(Set.of(Metric.LINE, Metric.BRANCH), false); + } + @ParameterizedTest @EnumSource(value = Metric.class, names = {"MCDC_PAIR", "FUNCTION_CALL"}) void shouldCreateLineChartForVectorCoverage(final Metric vector) { - TrendChart trendChart = new CoverageTrendChart(); + TrendChart trendChart = new CoverageTrendChart(Set.of(Metric.LINE, Metric.BRANCH, vector), true); BuildResult smallLineCoverage = createResult(1, new CoverageBuilder().withMetric(Metric.LINE).withCovered(1).withMissed(1).build(), @@ -163,7 +168,7 @@ void shouldHaveTwoValuesForTwoBuilds() { assertThat(dataSet.getSeries(CoverageSeriesBuilder.BRANCH_COVERAGE)) .containsExactly(75.0, 25.0); - TrendChart trendChart = new CoverageTrendChart(); + TrendChart trendChart = createTrend(); var model = trendChart.create(List.of(first, second), createConfiguration()); assertThatJson(model).isEqualTo(toString("chart.json")); diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java index ef3b87d2..aa946acc 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java @@ -1,13 +1,15 @@ package io.jenkins.plugins.coverage.metrics.steps; -import java.io.IOException; -import java.util.List; - import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; +import edu.hm.hafner.coverage.Metric; +import edu.hm.hafner.coverage.Value; import edu.hm.hafner.echarts.line.LinesChartModel; +import java.io.IOException; +import java.util.List; + import org.kohsuke.stapler.StaplerRequest2; import org.kohsuke.stapler.StaplerResponse2; import hudson.model.FreeStyleBuild; @@ -26,6 +28,68 @@ class CoverageJobActionTest { private static final String URL = "coverage"; + @Test + void shouldSelectMetrics() { + var jobAction = new CoverageJobAction(mock(FreeStyleProject.class), URL, "Coverage Results", "icon"); + + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": false, + "BRANCH": true + } + } + """)).containsExactly(Metric.BRANCH); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": true, + "BRANCH": false + } + } + """)).containsExactly(Metric.LINE); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": true, + "BRANCH": true + } + } + """)).containsExactlyInAnyOrder(Metric.LINE, Metric.BRANCH); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": false, + "BRANCH": false + } + } + """)).isEmpty(); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + } + } + """)).isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": 1.0 + } + } + """)).isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "WRONG-METRIC": true + } + } + """)).isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics("{}")) + .isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics("broken")) + .isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); + } + @Test void shouldIgnoreIndexIfNoActionFound() throws IOException { FreeStyleProject job = mock(FreeStyleProject.class); @@ -102,6 +166,9 @@ private CoverageBuildAction createBuildAction(final FreeStyleBuild build) { CoverageBuildAction action = mock(CoverageBuildAction.class); when(action.getOwner()).thenAnswer(i -> build); when(action.getUrlName()).thenReturn(URL); + when(action.getAllValues(any())).thenReturn(List.of( + Value.nullObject(Metric.LINE), + Value.nullObject(Metric.BRANCH))); return action; } } From 251be8ef8c28e05984e427e82ec91b239fd0965b Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Mon, 24 Feb 2025 21:12:55 +0100 Subject: [PATCH 2/4] Add configuration of trend charts Now metric and coverage charts can be customized. Users can decide whether the charts are using a lines or stacked lines chart. Additionally, the visible metrics for each chart can be selected. --- .../metrics/charts/CoverageSeriesBuilder.java | 8 -- .../metrics/charts/CoverageTrendChart.java | 9 +- .../metrics/charts/MetricsTrendChart.java | 31 ++++-- .../metrics/steps/CoverageBuildAction.java | 16 ++- .../metrics/steps/CoverageJobAction.java | 89 ++--------------- .../metrics/steps/CoverageViewModel.java | 56 ++++++++--- .../metrics/steps/TrendChartFactory.java | 99 +++++++++++++++++++ .../CoverageJobAction/configure-trend.js | 2 +- .../steps/CoverageJobAction/floatingBox.jelly | 4 +- .../steps/CoverageViewModel/index.jelly | 83 ++++++++++++++-- .../steps/CoverageViewModel/index.properties | 1 + .../CoverageViewModel/init-view-model.js | 28 ++++++ plugin/src/main/webapp/js/view-model.js | 43 +++++--- .../charts/CoverageSeriesBuilderTest.java | 19 ++-- .../metrics/steps/CoverageJobActionTest.java | 62 ------------ .../metrics/steps/CoverageViewModelTest.java | 3 +- .../metrics/steps/TrendChartFactoryTest.java | 71 +++++++++++++ 17 files changed, 401 insertions(+), 223 deletions(-) create mode 100644 plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactory.java create mode 100644 plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactoryTest.java diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java index 220f29ee..849cd317 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java @@ -15,14 +15,6 @@ * @author Ullrich Hafner */ public class CoverageSeriesBuilder extends SeriesBuilder { - static final String LINE_COVERAGE = Metric.LINE.toTagName(); - static final String BRANCH_COVERAGE = Metric.BRANCH.toTagName(); - static final String MUTATION_COVERAGE = Metric.MUTATION.toTagName(); - static final String TEST_STRENGTH = Metric.TEST_STRENGTH.toTagName(); - static final String MCDC_PAIR_COVERAGE = Metric.MCDC_PAIR.toTagName(); - static final String FUNCTION_CALL_COVERAGE = Metric.FUNCTION_CALL.toTagName(); - static final String METHOD_COVERAGE = Metric.METHOD.toTagName(); - @Override protected Map computeSeries(final CoverageStatistics statistics) { return Arrays.stream(Metric.values()) diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java index 107a5a31..804ca988 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java @@ -4,6 +4,7 @@ import edu.hm.hafner.echarts.BuildResult; import edu.hm.hafner.echarts.ChartModelConfiguration; import edu.hm.hafner.echarts.JacksonFacade; +import edu.hm.hafner.echarts.line.LineSeries; import edu.hm.hafner.echarts.line.LinesChartModel; import edu.hm.hafner.util.VisibleForTesting; @@ -47,10 +48,6 @@ public LinesChartModel create(final Iterable> re LinesChartModel model = new LinesChartModel(dataSet); if (dataSet.isNotEmpty()) { - model.useContinuousRangeAxis(); - model.setRangeMax(100); // Restrict the range to 100% - model.setRangeMin(dataSet.getMinimumValue()); - int colorIndex = 0; for (Metric metric : List.of(Metric.MODULE, Metric.PACKAGE, Metric.FILE, Metric.CLASS, Metric.METHOD)) { addSeriesIfAvailable(dataSet, model, metric, JenkinsPalette.chartColor(colorIndex++).normal()); @@ -65,6 +62,10 @@ public LinesChartModel create(final Iterable> re addSeriesIfAvailable(dataSet, model, Metric.MCDC_PAIR, JenkinsPalette.RED.light()); addSeriesIfAvailable(dataSet, model, Metric.FUNCTION_CALL, JenkinsPalette.RED.dark()); + + model.useContinuousRangeAxis(); + model.setRangeMax(100); // Restrict the range to 100% + model.setRangeMin(model.getSeries().stream().map(LineSeries::getData).flatMap(List::stream).mapToDouble(Number::doubleValue).min().orElse(0)); } return model; } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java index b155fee3..312b5aa2 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java @@ -4,19 +4,20 @@ import edu.hm.hafner.echarts.BuildResult; import edu.hm.hafner.echarts.ChartModelConfiguration; import edu.hm.hafner.echarts.JacksonFacade; +import edu.hm.hafner.echarts.line.LineSeries; import edu.hm.hafner.echarts.line.LinesChartModel; import edu.hm.hafner.echarts.line.LinesDataSet; +import java.util.List; import java.util.Set; import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import io.jenkins.plugins.echarts.JenkinsPalette; /** - * Builds the Java side model for a trend chart showing the metrics of a project. The number of builds - * to consider is controlled by a {@link ChartModelConfiguration} instance. The created model object can be serialized - * to JSON (e.g., using the {@link JacksonFacade}) and can be used 1:1 as ECharts configuration object in the - * corresponding JS file. + * Builds the Java side model for a trend chart showing the metrics of a project. The number of builds to consider is + * controlled by a {@link ChartModelConfiguration} instance. The created model object can be serialized to JSON (e.g., + * using the {@link JacksonFacade}) and can be used 1:1 as ECharts configuration object in the corresponding JS file. * * @author Ullrich Hafner * @see JacksonFacade @@ -41,17 +42,29 @@ public LinesChartModel create(final Iterable> re LinesChartModel model = new LinesChartModel(dataSet); if (dataSet.isNotEmpty()) { - model.useContinuousRangeAxis(); - model.setRangeMax(dataSet.getMaximumValue()); - model.setRangeMin(dataSet.getMinimumValue()); int colorIndex = 0; for (var tag : dataSet.getDataSetIds()) { Metric metric = Metric.fromTag(tag); addSeriesIfAvailable(dataSet, model, metric.getDisplayName(), - tag, JenkinsPalette.chartColor(colorIndex++).normal() - ); + tag, JenkinsPalette.chartColor(colorIndex++).normal()); } + + model.useContinuousRangeAxis(); + model.setRangeMax(model.getSeries() + .stream() + .map(LineSeries::getData) + .flatMap(List::stream) + .mapToDouble(Number::doubleValue) + .max() + .orElse(0)); + model.setRangeMin(model.getSeries() + .stream() + .map(LineSeries::getData) + .flatMap(List::stream) + .mapToDouble(Number::doubleValue) + .min() + .orElse(0)); } return model; } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java index 59bba932..6f151d52 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java @@ -7,7 +7,6 @@ import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.coverage.Node; import edu.hm.hafner.coverage.Value; -import edu.hm.hafner.echarts.ChartModelConfiguration; import edu.hm.hafner.echarts.JacksonFacade; import edu.hm.hafner.util.FilteredLog; import edu.hm.hafner.util.VisibleForTesting; @@ -32,7 +31,6 @@ import hudson.model.Run; import hudson.util.XStream2; -import io.jenkins.plugins.coverage.metrics.charts.TrendChart; import io.jenkins.plugins.coverage.metrics.model.Baseline; import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import io.jenkins.plugins.coverage.metrics.model.ElementFormatter; @@ -679,15 +677,15 @@ protected String getBuildResultBaseName() { public CoverageViewModel getTarget() { return new CoverageViewModel(getOwner(), getUrlName(), name, getResult(), getStatistics(), getQualityGateResult(), getReferenceBuildLink(), log, - this::createChartModel, this::checkForCoverageData); + this::createCoverageModel, this::createMetricsModel); } - private String createChartModel(final String configuration, final boolean metrics) { - // TODO: add without optional - var iterable = new BuildActionIterable<>(CoverageBuildAction.class, Optional.of(this), - action -> getUrlName().equals(action.getUrlName()), CoverageBuildAction::getStatistics); - return new JacksonFacade().toJson(TrendChart.createTrendChart(metrics) - .create(iterable, ChartModelConfiguration.fromJson(configuration))); + private String createCoverageModel(final String configuration) { + return new JacksonFacade().toJson(new TrendChartFactory().createChartModel(configuration, this)); + } + + private String createMetricsModel(final String configuration) { + return new JacksonFacade().toJson(new TrendChartFactory().createMetricsModel(configuration, this)); } private boolean checkForCoverageData() { diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java index 21d8b1f5..2de921e1 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction.java @@ -2,35 +2,20 @@ import org.apache.commons.lang3.StringUtils; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; - import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.coverage.Node; import edu.hm.hafner.coverage.Value; -import edu.hm.hafner.echarts.ChartModelConfiguration; -import edu.hm.hafner.echarts.JacksonFacade; import edu.hm.hafner.echarts.line.LinesChartModel; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; import hudson.model.Job; -import io.jenkins.plugins.coverage.metrics.charts.CoverageTrendChart; -import io.jenkins.plugins.coverage.metrics.charts.MetricsTrendChart; -import io.jenkins.plugins.coverage.metrics.charts.TrendChart; import io.jenkins.plugins.coverage.metrics.model.Baseline; import io.jenkins.plugins.echarts.ActionSelector; -import io.jenkins.plugins.echarts.GenericBuildActionIterator.BuildActionIterable; import io.jenkins.plugins.echarts.TrendChartJobAction; /** @@ -41,17 +26,7 @@ * @author Ullrich Hafner */ public class CoverageJobAction extends TrendChartJobAction { - static final Set DEFAULT_TREND_METRICS = Set.of( - Metric.LINE, Metric.BRANCH, - Metric.MUTATION, Metric.TEST_STRENGTH, - Metric.NCSS, Metric.LOC, Metric.CYCLOMATIC_COMPLEXITY, Metric.COGNITIVE_COMPLEXITY); - private static final Set IGNORED_TREND_METRICS = Set.of( - Metric.ACCESS_TO_FOREIGN_DATA, Metric.WEIGHED_METHOD_COUNT, - Metric.NUMBER_OF_ACCESSORS, - Metric.WEIGHT_OF_CLASS, Metric.COHESION, Metric.CONTAINER, - Metric.FAN_OUT, Metric.MODULE); - - private static final JacksonFacade JACKSON = new JacksonFacade(); + private static final LinesChartModel EMPTY_CHART = new LinesChartModel(); private final String id; private final String name; @@ -113,7 +88,7 @@ public List getTrendMetrics() { .map(a -> a.getAllValues(Baseline.PROJECT)) .flatMap(Collection::stream) .map(Value::getMetric) - .filter(m -> !IGNORED_TREND_METRICS.contains(m)) + .filter(m -> !TrendChartFactory.IGNORED_TREND_METRICS.contains(m)) .filter(m -> m.isCoverage() || latestAction.map(a -> !a.hasCoverage()).orElse(true)) .toList(); } @@ -121,64 +96,16 @@ public List getTrendMetrics() { @Override protected LinesChartModel createChartModel(final String configuration) { var latestAction = getLatestAction(); - var buildActions = new BuildActionIterable<>(CoverageBuildAction.class, latestAction, - selectByUrl(), CoverageBuildAction::getStatistics); - - Set actualValues = latestAction.map(a -> - a.getAllValues(Baseline.PROJECT).stream() - .map(Value::getMetric) - .collect(Collectors.toSet())) - .orElseGet(() -> Arrays.stream(Metric.values()).collect(Collectors.toSet())); - actualValues.retainAll(getVisibleMetrics(configuration)); - return getTrendChartType(latestAction, actualValues, useLines(configuration)) - .create(buildActions, ChartModelConfiguration.fromJson(configuration)); - } - - private boolean useLines(final String configuration) { - return JACKSON.getBoolean(configuration, "useLines", false); - } - - Set getVisibleMetrics(final String configuration) { - try { - var objectMapper = new ObjectMapper(); - ObjectNode jsonNodes = objectMapper.readValue(configuration, ObjectNode.class); - var metrics = jsonNodes.get("metrics"); - @SuppressWarnings("unchecked") - Map metricMapping = objectMapper.convertValue(metrics, Map.class); - if (metricMapping != null && !metricMapping.isEmpty()) { - return metricMapping.entrySet().stream() - .filter(Map.Entry::getValue) - .map(Map.Entry::getKey) - .map(Metric::valueOf) - .collect(Collectors.toSet()); - } - } - catch (JsonProcessingException | ClassCastException | IllegalArgumentException ignored) { - // ignore and return default values - } - - return DEFAULT_TREND_METRICS; - } - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private TrendChart getTrendChartType(final Optional latestAction, - final Set visibleMetrics, final boolean useLines) { - if (latestAction.isPresent()) { - var hasCoverage = latestAction.get().getAllValues(Baseline.PROJECT).stream() - .map(Value::getMetric).anyMatch(Metric::isCoverage); - if (!hasCoverage) { - return new MetricsTrendChart(visibleMetrics, useLines); - } - } - return new CoverageTrendChart(visibleMetrics, useLines); + return latestAction + .map(a -> new TrendChartFactory().createChartModel(configuration, a)) + .orElse(EMPTY_CHART); } @Override public Optional getLatestAction() { - return new ActionSelector<>(CoverageBuildAction.class, selectByUrl()).findFirst(getOwner().getLastBuild()); - } - - private Predicate selectByUrl() { - return action -> getUrlName().equals(action.getUrlName()); + return new ActionSelector<>(CoverageBuildAction.class, + action -> getUrlName().equals(action.getUrlName())) + .findFirst(getOwner().getLastBuild()); } } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java index 4b851218..25132abe 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java @@ -12,6 +12,7 @@ import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.coverage.Node; import edu.hm.hafner.coverage.Percentage; +import edu.hm.hafner.coverage.Value; import edu.hm.hafner.echarts.LabeledTreeMapNode; import edu.hm.hafner.util.FilteredLog; import edu.hm.hafner.util.VisibleForTesting; @@ -26,10 +27,8 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -95,8 +94,8 @@ public class CoverageViewModel extends DefaultAsyncTableContentProvider implemen private final Node modifiedLinesCoverageTreeRoot; private final Node indirectCoverageChangesTreeRoot; - private final BiFunction trendChartFunction; - private final Supplier coverageSupplier; + private final Function trendChartFunction; + private final Function metricsTrendFunction; private ColorProvider colorProvider = ColorProviderFactory.createDefaultColorProvider(); @@ -104,7 +103,8 @@ public class CoverageViewModel extends DefaultAsyncTableContentProvider implemen CoverageViewModel(final Run owner, final String id, final String displayName, final Node node, final CoverageStatistics statistics, final QualityGateResult qualityGateResult, final String referenceBuild, final FilteredLog log, - final BiFunction trendChartFunction, final Supplier coverageSupplier) { + final Function trendChartFunction, + final Function metricsTrendFunction) { super(); this.owner = owner; @@ -122,7 +122,7 @@ public class CoverageViewModel extends DefaultAsyncTableContentProvider implemen modifiedLinesCoverageTreeRoot = node.filterByModifiedLines(); indirectCoverageChangesTreeRoot = node.filterByIndirectChanges(); this.trendChartFunction = trendChartFunction; - this.coverageSupplier = coverageSupplier; + this.metricsTrendFunction = metricsTrendFunction; } @VisibleForTesting @@ -158,6 +158,34 @@ public NavigableSet getTreeMetrics() { return valueMetrics; } + /** + * Returns the metrics that are available for the trend chart. + * + * @return the available metrics + */ + @SuppressWarnings("unused") // Used in trend chart configuration + public List getCoverageMetrics() { + return node.aggregateValues().stream() + .map(Value::getMetric) + .filter(Metric::isCoverage) + .filter(m -> !TrendChartFactory.IGNORED_TREND_METRICS.contains(m)) + .toList(); + } + + /** + * Returns the metrics that are available for the trend chart. + * + * @return the available metrics + */ + @SuppressWarnings("unused") // Used in trend chart configuration + public List getSoftwareMetrics() { + return node.aggregateValues().stream() + .map(Value::getMetric) + .filter(Predicate.not(Metric::isCoverage)) + .filter(m -> !TrendChartFactory.IGNORED_TREND_METRICS.contains(m)) + .toList(); + } + @Override public String getDisplayName() { if (StringUtils.isBlank(displayName)) { @@ -187,10 +215,10 @@ public Set getJenkinsColorIDs() { } /** - * Creates a new {@link ColorProvider} based on the passed color json string which contains the set Jenkins colors. + * Creates a new {@link ColorProvider} based on the passed color JSON string which contains the set Jenkins colors. * * @param colors - * The dynamically loaded Jenkins colors to be used for highlighting the coverage tree as json string + * The dynamically loaded Jenkins colors to be used for highlighting the coverage tree as JSON string */ @JavaScriptMethod @SuppressWarnings("unused") @@ -199,10 +227,10 @@ public void setJenkinsColors(final String colors) { } /** - * Parses the passed color json string to a {@link ColorProvider}. + * Parses the passed color JSON string to a {@link ColorProvider}. * * @param json - * The color json + * The color JSON * * @return the created color provider */ @@ -233,7 +261,7 @@ public CoverageOverview getOverview() { @JavaScriptMethod @SuppressWarnings("unused") public String getTrendChart(final String configuration) { - return trendChartFunction.apply(configuration, false); + return trendChartFunction.apply(configuration); } /** @@ -247,16 +275,16 @@ public String getTrendChart(final String configuration) { @JavaScriptMethod @SuppressWarnings("unused") public String getMetricsTrendChart(final String configuration) { - return trendChartFunction.apply(configuration, true); + return metricsTrendFunction.apply(configuration); } /** - * Returns if the last job had any coverage data. + * Returns if the model has any coverage data. * * @return if the last job has coverage data */ public boolean hasCoverage() { - return coverageSupplier.get(); + return node.aggregateValues().stream().map(Value::getMetric).anyMatch(Metric::isCoverage); } /** diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactory.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactory.java new file mode 100644 index 00000000..5cf9d445 --- /dev/null +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactory.java @@ -0,0 +1,99 @@ +package io.jenkins.plugins.coverage.metrics.steps; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import edu.hm.hafner.coverage.Metric; +import edu.hm.hafner.coverage.Value; +import edu.hm.hafner.echarts.ChartModelConfiguration; +import edu.hm.hafner.echarts.JacksonFacade; +import edu.hm.hafner.echarts.line.LinesChartModel; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import io.jenkins.plugins.coverage.metrics.charts.CoverageTrendChart; +import io.jenkins.plugins.coverage.metrics.charts.MetricsTrendChart; +import io.jenkins.plugins.coverage.metrics.charts.TrendChart; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.jenkins.plugins.echarts.GenericBuildActionIterator.BuildActionIterable; + +/** + * Creates trend charts for coverage results. + * + * @author Ullrich Hafner + */ +class TrendChartFactory { + private static final JacksonFacade JACKSON = new JacksonFacade(); + static final Set DEFAULT_TREND_METRICS = Set.of( + Metric.LINE, Metric.BRANCH, + Metric.MUTATION, Metric.TEST_STRENGTH, + Metric.NCSS, Metric.LOC, Metric.CYCLOMATIC_COMPLEXITY, Metric.COGNITIVE_COMPLEXITY); + static final Set IGNORED_TREND_METRICS = Set.of( + Metric.ACCESS_TO_FOREIGN_DATA, Metric.WEIGHED_METHOD_COUNT, + Metric.NUMBER_OF_ACCESSORS, + Metric.WEIGHT_OF_CLASS, Metric.COHESION, Metric.CONTAINER, + Metric.FAN_OUT, Metric.MODULE); + + LinesChartModel createMetricsModel(final String configuration, final CoverageBuildAction latestAction) { + return getLinesChartModel(configuration, latestAction, true); + } + + LinesChartModel createChartModel(final String configuration, final CoverageBuildAction latestAction) { + return getLinesChartModel(configuration, latestAction, false); + } + + private LinesChartModel getLinesChartModel(final String configuration, final CoverageBuildAction latestAction, + final boolean isMetric) { + var buildActions = new BuildActionIterable<>(CoverageBuildAction.class, Optional.of(latestAction), + action -> latestAction.getUrlName().equals(action.getUrlName()), + CoverageBuildAction::getStatistics); + + Set actualValues = latestAction.getAllValues(Baseline.PROJECT).stream() + .map(Value::getMetric) + .collect(Collectors.toSet()); + actualValues.retainAll(getVisibleMetrics(configuration)); + + return getTrendChartType(latestAction, actualValues, useLines(configuration), isMetric) + .create(buildActions, ChartModelConfiguration.fromJson(configuration)); + } + + private boolean useLines(final String configuration) { + return JACKSON.getBoolean(configuration, "useLines", false); + } + + Set getVisibleMetrics(final String configuration) { + try { + var objectMapper = new ObjectMapper(); + ObjectNode jsonNodes = objectMapper.readValue(configuration, ObjectNode.class); + var metrics = jsonNodes.get("metrics"); + @SuppressWarnings("unchecked") + Map metricMapping = objectMapper.convertValue(metrics, Map.class); + if (metricMapping != null && !metricMapping.isEmpty()) { + return metricMapping.entrySet().stream() + .filter(Map.Entry::getValue) + .map(Map.Entry::getKey) + .map(Metric::valueOf) + .collect(Collectors.toSet()); + } + } + catch (JsonProcessingException | ClassCastException | IllegalArgumentException ignored) { + // ignore and return default values + } + + return DEFAULT_TREND_METRICS; + } + + private TrendChart getTrendChartType(final CoverageBuildAction latestAction, + final Set visibleMetrics, final boolean useLines, final boolean isMetric) { + var hasCoverage = latestAction.getAllValues(Baseline.PROJECT).stream() + .map(Value::getMetric).anyMatch(Metric::isCoverage); + if (isMetric || !hasCoverage) { + return new MetricsTrendChart(visibleMetrics, useLines); + } + return new CoverageTrendChart(visibleMetrics, useLines); + } +} diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js index 2985202a..a0cc5c73 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/configure-trend.js @@ -19,7 +19,7 @@ window.addEventListener("DOMContentLoaded", () => { function saveCoverage(trendConfiguration) { const metrics = {}; - trendConfiguration.find('input[type="checkbox"][id^="coverage-"]').each(function () { + trendConfiguration.find('input[type="checkbox"][id^="coverage-"][id*="-metric-"]').each(function () { metrics[jQuery3(this).attr('name')] = jQuery3(this).prop('checked'); }); return { diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly index d1a920e1..579094ad 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageJobAction/floatingBox.jelly @@ -10,8 +10,8 @@
-
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.jelly b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.jelly index 0debf669..4eb61059 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.jelly @@ -22,12 +22,21 @@ - + + +
@@ -71,8 +82,8 @@
- -
+ +
@@ -101,7 +112,63 @@ - + +
+ +
+ +
+ + +
+
+
+ +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+
+ +
+
+ + +
+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.properties b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.properties index 4709a14f..e8897654 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.properties +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/index.properties @@ -1,4 +1,5 @@ tab.name.overview=Overview +tab.name.metrics=Metrics tab.name.files=Files tab.name.lines=Modified Lines tab.name.indirect=Indirect Changes diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/init-view-model.js b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/init-view-model.js index bd6a3879..c6d7a107 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/init-view-model.js +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel/init-view-model.js @@ -1,6 +1,34 @@ /* global jQuery3, proxy, echartsJenkinsApi, bootstrap5 */ (function () { + function fillDialog(trendConfiguration, jsonConfiguration) { + const metrics = jsonConfiguration['metrics']; + if (metrics) { + Object.entries(metrics).forEach(([metric, isChecked]) => { + trendConfiguration.find(`input[type="checkbox"][name="${metric}"]`).prop('checked', isChecked); + }); + } + const useLines = jsonConfiguration['useLines']; + if (useLines) { + trendConfiguration.find('input[type="checkbox"][name="lines"]').prop('checked', useLines); + } + } + + function saveDialog(trendConfiguration) { + const metrics = {}; + + trendConfiguration.find('input[type="checkbox"][id*="-history-metric"]').each(function () { + metrics[jQuery3(this).attr('name')] = jQuery3(this).prop('checked'); + }); + return { + 'metrics': metrics, + 'useLines': trendConfiguration.find('input[name=lines]').prop('checked') + }; + } + + echartsJenkinsApi.configureChart('coverage-history', fillDialog, saveDialog); + echartsJenkinsApi.configureChart('metrics-history', fillDialog, saveDialog); + proxy.getJenkinsColorIDs(function (colors) { const jenkinsColors = getJenkinsColors(colors.responseObject()); const colorJson = JSON.stringify(Object.fromEntries(jenkinsColors)); diff --git a/plugin/src/main/webapp/js/view-model.js b/plugin/src/main/webapp/js/view-model.js index aed4deb9..a9ac99c7 100644 --- a/plugin/src/main/webapp/js/view-model.js +++ b/plugin/src/main/webapp/js/view-model.js @@ -346,9 +346,8 @@ const CoverageChartGenerator = function ($, proxy) { // NOPMD /** * Loads all chart JSON models via AJAX calls from the server and renders the corresponding echarts. */ - // TODO: maybe it would make sense to render only visible charts function initializeCharts() { - renderTrendChart(); + renderCoverageTrendChart(); renderMetricsTrendChart(); proxy.getOverview(function (t) { @@ -370,27 +369,31 @@ const CoverageChartGenerator = function ($, proxy) { // NOPMD } } - function renderTrendChart() { - const configuration = JSON.stringify(echartsJenkinsApi.readFromLocalStorage('jenkins-echarts-chart-configuration-coverage-history')); - proxy.getTrendChart(configuration, function (t) { - echartsJenkinsApi.renderConfigurableZoomableTrendChart('coverage-trend', t.responseJSON, 'chart-configuration-coverage-history', openBuild); - resizeChartOf('#coverage-trend'); - }); + function renderCoverageTrendChart() { + if ($('#coverage-trend').is(':visible')) { + const configuration = JSON.stringify(echartsJenkinsApi.readFromLocalStorage('jenkins-echarts-chart-configuration-coverage-history')); + proxy.getTrendChart(configuration, function (t) { + echartsJenkinsApi.renderConfigurableZoomableTrendChart('coverage-trend', t.responseJSON, 'chart-configuration-coverage-history', openBuild); + resizeChartOf('#coverage-trend'); + }); + } } function renderMetricsTrendChart() { - const configuration = JSON.stringify(echartsJenkinsApi.readFromLocalStorage('jenkins-echarts-chart-configuration-metrics-history')); - proxy.getMetricsTrendChart(configuration, function (t) { - echartsJenkinsApi.renderConfigurableZoomableTrendChart('metrics-trend', t.responseJSON, 'chart-configuration-metrics-history', openBuild); - resizeChartOf('#metrics-trend'); - }); + if ($('#metrics-trend').is(':visible')) { + const configuration = JSON.stringify(echartsJenkinsApi.readFromLocalStorage('jenkins-echarts-chart-configuration-metrics-history')); + proxy.getMetricsTrendChart(configuration, function (t) { + echartsJenkinsApi.renderConfigurableZoomableTrendChart('metrics-trend', t.responseJSON, 'chart-configuration-metrics-history', openBuild); + resizeChartOf('#metrics-trend'); + }); + } } /** * Event handler to resize all charts. */ function redrawCharts() { - renderTrendChart(); // re-render since the configuration might have changed + renderCoverageTrendChart(); renderMetricsTrendChart(); resizeChartOf('#coverage-overview'); @@ -400,13 +403,20 @@ const CoverageChartGenerator = function ($, proxy) { // NOPMD }); } - function registerTrendChartConfiguration() { + function registerCoverageChartConfiguration() { const trendConfigurationDialogId = 'chart-configuration-coverage-history'; $('#' + trendConfigurationDialogId).on('hidden.bs.modal', function () { redrawCharts(); }); } + function registerMetricsChartConfiguration() { + const trendConfigurationDialogId = 'chart-configuration-metrics-history'; + $('#' + trendConfigurationDialogId).on('hidden.bs.modal', function () { + redrawCharts(); + }); + } + /** * Initializes a selection listener for a datatable which loads the selected source code. * @@ -461,7 +471,8 @@ const CoverageChartGenerator = function ($, proxy) { // NOPMD }); } - registerTrendChartConfiguration(); + registerCoverageChartConfiguration(); + registerMetricsChartConfiguration(); registerTabEvents(); initializeCharts(); diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java index bbea1022..dd7254d0 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilderTest.java @@ -33,6 +33,9 @@ * @author Ullrich Hafner */ class CoverageSeriesBuilderTest extends ResourceTest { + private static final String LINE_COVERAGE = Metric.LINE.toTagName(); + private static final String BRANCH_COVERAGE = Metric.BRANCH.toTagName(); + @Test void shouldHaveEmptyDataSetForEmptyIterator() { CoverageSeriesBuilder builder = new CoverageSeriesBuilder(); @@ -136,11 +139,11 @@ void shouldHaveTwoValuesForSingleBuild() { assertThat(dataSet.getDomainAxisLabels()).containsExactly("#1"); assertThat(dataSet.getDataSetIds()).containsExactlyInAnyOrder( - CoverageSeriesBuilder.LINE_COVERAGE, - CoverageSeriesBuilder.BRANCH_COVERAGE); + LINE_COVERAGE, + BRANCH_COVERAGE); - assertThat(dataSet.getSeries(CoverageSeriesBuilder.LINE_COVERAGE)).containsExactly(50.0); - assertThat(dataSet.getSeries(CoverageSeriesBuilder.BRANCH_COVERAGE)).containsExactly(75.0); + assertThat(dataSet.getSeries(LINE_COVERAGE)).containsExactly(50.0); + assertThat(dataSet.getSeries(BRANCH_COVERAGE)).containsExactly(75.0); } @Test @@ -160,12 +163,12 @@ void shouldHaveTwoValuesForTwoBuilds() { assertThat(dataSet.getDomainAxisLabels()).containsExactly("#1", "#2"); assertThat(dataSet.getDataSetIds()).containsExactlyInAnyOrder( - CoverageSeriesBuilder.LINE_COVERAGE, - CoverageSeriesBuilder.BRANCH_COVERAGE); + LINE_COVERAGE, + BRANCH_COVERAGE); - assertThat(dataSet.getSeries(CoverageSeriesBuilder.LINE_COVERAGE)) + assertThat(dataSet.getSeries(LINE_COVERAGE)) .containsExactly(50.0, 25.0); - assertThat(dataSet.getSeries(CoverageSeriesBuilder.BRANCH_COVERAGE)) + assertThat(dataSet.getSeries(BRANCH_COVERAGE)) .containsExactly(75.0, 25.0); TrendChart trendChart = createTrend(); diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java index aa946acc..855698ab 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageJobActionTest.java @@ -28,68 +28,6 @@ class CoverageJobActionTest { private static final String URL = "coverage"; - @Test - void shouldSelectMetrics() { - var jobAction = new CoverageJobAction(mock(FreeStyleProject.class), URL, "Coverage Results", "icon"); - - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - "LINE": false, - "BRANCH": true - } - } - """)).containsExactly(Metric.BRANCH); - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - "LINE": true, - "BRANCH": false - } - } - """)).containsExactly(Metric.LINE); - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - "LINE": true, - "BRANCH": true - } - } - """)).containsExactlyInAnyOrder(Metric.LINE, Metric.BRANCH); - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - "LINE": false, - "BRANCH": false - } - } - """)).isEmpty(); - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - } - } - """)).isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - "LINE": 1.0 - } - } - """)).isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); - assertThat(jobAction.getVisibleMetrics(""" - { - "metrics": { - "WRONG-METRIC": true - } - } - """)).isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); - assertThat(jobAction.getVisibleMetrics("{}")) - .isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); - assertThat(jobAction.getVisibleMetrics("broken")) - .isEqualTo(CoverageJobAction.DEFAULT_TREND_METRICS); - } - @Test void shouldIgnoreIndexIfNoActionFound() throws IOException { FreeStyleProject job = mock(FreeStyleProject.class); diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModelTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModelTest.java index 301f8ba1..a9d5f8ba 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModelTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModelTest.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Function; import hudson.model.Run; @@ -117,6 +118,6 @@ private CoverageViewModel createModelFromCodingStyleReport() { private CoverageViewModel createModel(final Node node) { return new CoverageViewModel(mock(Run.class), "id", StringUtils.EMPTY, node, createStatistics(), new QualityGateResult(), "-", new FilteredLog("Errors"), - (i, b) -> i, () -> true); + Function.identity(), Function.identity()); } } diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactoryTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactoryTest.java new file mode 100644 index 00000000..4436d03a --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/TrendChartFactoryTest.java @@ -0,0 +1,71 @@ +package io.jenkins.plugins.coverage.metrics.steps; + +import org.junit.jupiter.api.Test; + +import edu.hm.hafner.coverage.Metric; + +import static org.assertj.core.api.Assertions.*; + +class TrendChartFactoryTest { + @Test + void shouldSelectMetrics() { + var jobAction = new TrendChartFactory(); + + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": false, + "BRANCH": true + } + } + """)).containsExactly(Metric.BRANCH); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": true, + "BRANCH": false + } + } + """)).containsExactly(Metric.LINE); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": true, + "BRANCH": true + } + } + """)).containsExactlyInAnyOrder(Metric.LINE, Metric.BRANCH); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": false, + "BRANCH": false + } + } + """)).isEmpty(); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + } + } + """)).isEqualTo(TrendChartFactory.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "LINE": 1.0 + } + } + """)).isEqualTo(TrendChartFactory.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics(""" + { + "metrics": { + "WRONG-METRIC": true + } + } + """)).isEqualTo(TrendChartFactory.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics("{}")) + .isEqualTo(TrendChartFactory.DEFAULT_TREND_METRICS); + assertThat(jobAction.getVisibleMetrics("broken")) + .isEqualTo(TrendChartFactory.DEFAULT_TREND_METRICS); + } +} From 10b82a6732b4dc5942c3eff3ab608c7c70854745 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Mon, 24 Feb 2025 21:41:37 +0100 Subject: [PATCH 3/4] Remove obsolete method --- .../metrics/steps/CoverageBuildAction.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java index 6f151d52..a4f39eaa 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java @@ -2,7 +2,6 @@ import org.apache.commons.lang3.StringUtils; -import edu.hm.hafner.coverage.Coverage; import edu.hm.hafner.coverage.Difference; import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.coverage.Node; @@ -35,7 +34,6 @@ import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import io.jenkins.plugins.coverage.metrics.model.ElementFormatter; import io.jenkins.plugins.coverage.metrics.steps.CoverageXmlStream.MetricFractionMapConverter; -import io.jenkins.plugins.echarts.GenericBuildActionIterator.BuildActionIterable; import io.jenkins.plugins.forensics.reference.ReferenceBuild; import io.jenkins.plugins.util.AbstractXmlStream; import io.jenkins.plugins.util.BuildAction; @@ -688,22 +686,6 @@ private String createMetricsModel(final String configuration) { return new JacksonFacade().toJson(new TrendChartFactory().createMetricsModel(configuration, this)); } - private boolean checkForCoverageData() { - var iterator = new BuildActionIterable<>(CoverageBuildAction.class, Optional.of(this), - action -> getUrlName().equals(action.getUrlName()), CoverageBuildAction::getStatistics).iterator(); - if (iterator.hasNext()) { - var lastResult = iterator.next().getResult(); - return lastResult.getValue(Baseline.PROJECT, Metric.MODULE) - .or(() -> lastResult.getValue(Baseline.PROJECT, Metric.PACKAGE)) - .or(() -> lastResult.getValue(Baseline.PROJECT, Metric.FILE)) - .or(() -> lastResult.getValue(Baseline.PROJECT, Metric.CLASS)) - .or(() -> lastResult.getValue(Baseline.PROJECT, Metric.METHOD)) - .map(value -> value instanceof Coverage && ((Coverage) value).isSet()) - .orElse(false); - } - return false; - } - @NonNull @Override public String getIconFileName() { From ecebb0ac244653f8739535d874e7c6608bf0980c Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Mon, 24 Feb 2025 22:04:07 +0100 Subject: [PATCH 4/4] Simplify the series builders Since the series are filtered by the user now we do not need to generate different series for coverage and software metrics. --- .../metrics/charts/CoverageSeriesBuilder.java | 1 - .../metrics/charts/MetricSeriesBuilder.java | 26 ------------------- .../metrics/charts/MetricsTrendChart.java | 4 +-- 3 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricSeriesBuilder.java diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java index 849cd317..99c5a4a3 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java @@ -18,7 +18,6 @@ public class CoverageSeriesBuilder extends SeriesBuilder { @Override protected Map computeSeries(final CoverageStatistics statistics) { return Arrays.stream(Metric.values()) - .filter(Metric::isCoverage) .filter(statistics::containsValue) .collect(Collectors.toMap(Metric::toTagName, statistics::roundValue)); } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricSeriesBuilder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricSeriesBuilder.java deleted file mode 100644 index 6425d21e..00000000 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricSeriesBuilder.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.jenkins.plugins.coverage.metrics.charts; - -import java.util.Arrays; -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import edu.hm.hafner.coverage.Metric; -import edu.hm.hafner.echarts.line.SeriesBuilder; - -import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; - -/** - * Builds one x-axis point for the series of a line chart showing the line and branch coverage of a project. - * - * @author Ullrich Hafner - */ -public class MetricSeriesBuilder extends SeriesBuilder { - @Override - protected Map computeSeries(final CoverageStatistics statistics) { - return Arrays.stream(Metric.values()) - .filter(Predicate.not(Metric::isCoverage)) - .filter(statistics::containsValue) - .collect(Collectors.toMap(Metric::toTagName, statistics::roundValue)); - } -} diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java index 312b5aa2..05736f60 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java @@ -38,11 +38,10 @@ public MetricsTrendChart(final Set visibleMetrics, final boolean useLine @Override public LinesChartModel create(final Iterable> results, final ChartModelConfiguration configuration) { - LinesDataSet dataSet = new MetricSeriesBuilder().createDataSet(configuration, results); + LinesDataSet dataSet = new CoverageSeriesBuilder().createDataSet(configuration, results); LinesChartModel model = new LinesChartModel(dataSet); if (dataSet.isNotEmpty()) { - int colorIndex = 0; for (var tag : dataSet.getDataSetIds()) { Metric metric = Metric.fromTag(tag); @@ -51,6 +50,7 @@ public LinesChartModel create(final Iterable> re } model.useContinuousRangeAxis(); + // FIXME: once part of ECharts we should remove this code model.setRangeMax(model.getSeries() .stream() .map(LineSeries::getData)