From e38695e618f6b43ebebcdf6c93eec7606d96c8d3 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 21 Feb 2025 16:20:27 +0100 Subject: [PATCH] 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 | 4 + .../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, 340 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..e59a2c8a 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,10 @@ public List getValues(final Baseline baseline) { return filterImportantMetrics(getValueStream(baseline)); } + 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; } }