Skip to content

Commit

Permalink
Add metric customization and refactor trend chart logic
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
uhafner committed Feb 21, 2025
1 parent fa78065 commit 60a4b2f
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 80 deletions.
4 changes: 4 additions & 0 deletions plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -25,28 +25,9 @@ public class CoverageSeriesBuilder extends SeriesBuilder<CoverageStatistics> {

@Override
protected Map<String, Double> computeSeries(final CoverageStatistics statistics) {
Map<String, Double> 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<String, Double> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +23,23 @@
* @see JacksonFacade
*/
public class CoverageTrendChart extends TrendChart {
@VisibleForTesting
CoverageTrendChart() {
super(Set.of(), false);
}

Check warning on line 29 in plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 28-29 are not covered by tests

/**
* 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<Metric> visibleMetrics, final boolean useLines) {
super(visibleMetrics, useLines);
}

@Override
public LinesChartModel create(final Iterable<BuildResult<CoverageStatistics>> results,
final ChartModelConfiguration configuration) {
Expand All @@ -29,36 +48,24 @@ public LinesChartModel create(final Iterable<BuildResult<CoverageStatistics>> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<Metric> visibleMetrics, final boolean useLines) {
super(visibleMetrics, useLines);
}

@Override
public LinesChartModel create(final Iterable<BuildResult<CoverageStatistics>> results,
final ChartModelConfiguration configuration) {
Expand All @@ -36,8 +49,8 @@ public LinesChartModel create(final Iterable<BuildResult<CoverageStatistics>> 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()

Check warning on line 52 in plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/MetricsTrendChart.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 34-52 are not covered by tests
);
}
}
return model;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -21,15 +25,25 @@
* @see JacksonFacade
*/
public abstract class TrendChart {
private static final Set<Metric> 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);

Check warning on line 38 in plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/TrendChart.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 38 is not covered by tests
}

private final Set<Metric> visibleMetrics;
private final FilledMode filledMode;

TrendChart(final Set<Metric> visibleMetrics, final boolean useLines) {
this.visibleMetrics = visibleMetrics;
filledMode = useLines ? FilledMode.LINES : FilledMode.FILLED;
}

/**
Expand All @@ -47,8 +61,8 @@ public abstract LinesChartModel create(Iterable<BuildResult<CoverageStatistics>>
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)) {

Check warning on line 65 in plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/TrendChart.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 65 is not covered by tests
LineSeries branchSeries = new LineSeries(name,
color, StackedMode.SEPARATE_LINES, filledMode, dataSet.getSeries(seriesId));

Expand All @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,17 @@ public List<Value> 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);

Check warning on line 416 in plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 416 is not covered by tests
}

/**
* Returns the value for the specified metric, if available.
*
Expand Down
Loading

0 comments on commit 60a4b2f

Please sign in to comment.