This repository has been archived by the owner on Nov 19, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #195 from simonsymhoven/pull-request-monitoring-po…
…rtlet Add coverage portlet for pull-request-monitoring plugin
- Loading branch information
Showing
4 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package io.jenkins.plugins.coverage; | ||
|
||
import com.google.gson.JsonArray; | ||
import com.google.gson.JsonObject; | ||
import hudson.Extension; | ||
import hudson.model.Run; | ||
import io.jenkins.plugins.coverage.targets.CoverageElement; | ||
import io.jenkins.plugins.coverage.targets.Ratio; | ||
import io.jenkins.plugins.monitoring.MonitorPortlet; | ||
import io.jenkins.plugins.monitoring.MonitorPortletFactory; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* A portlet that can be used for the | ||
* <a href="https://github.com/jenkinsci/pull-request-monitoring-plugin">pull-request-monitoring</a> dashboard. | ||
* | ||
* It renders the aggregated line and conditional coverage in a stacked bar chart and displays the delta, | ||
* if a reference build is found. | ||
* | ||
* @author Simon Symhoven | ||
*/ | ||
public class CoveragePullRequestMonitoringPortlet extends MonitorPortlet { | ||
private final CoverageAction action; | ||
|
||
/** | ||
* Creates a new {@link CoveragePullRequestMonitoringPortlet}. | ||
* | ||
* @param action | ||
* the {@link CoverageAction} of corresponding run. | ||
*/ | ||
public CoveragePullRequestMonitoringPortlet(final CoverageAction action) { | ||
super(); | ||
this.action = action; | ||
} | ||
|
||
@Override | ||
public String getTitle() { | ||
return action.getDisplayName(); | ||
} | ||
|
||
@Override | ||
public String getId() { | ||
return "code-coverage"; | ||
} | ||
|
||
@Override | ||
public boolean isDefault() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public int getPreferredWidth() { | ||
return 600; | ||
} | ||
|
||
@Override | ||
public int getPreferredHeight() { | ||
return 350; | ||
} | ||
|
||
@Override | ||
public Optional<String> getIconUrl() { | ||
return Optional.of("/images/48x48/graph.png"); | ||
} | ||
|
||
@Override | ||
public Optional<String> getDetailViewUrl() { | ||
return Optional.ofNullable(action.getUrlName()); | ||
} | ||
|
||
/** | ||
* Get the json data for the stacked bar chart. (used by jelly view) | ||
* | ||
* @return | ||
* the data as json string. | ||
*/ | ||
public String getCoverageResultsAsJsonModel() { | ||
Ratio line = action.getResult().getResults().get(CoverageElement.LINE); | ||
Ratio conditional = action.getResult().getResults().get(CoverageElement.CONDITIONAL); | ||
|
||
JsonObject data = new JsonObject(); | ||
|
||
JsonArray metrics = new JsonArray(); | ||
metrics.add(CoverageElement.LINE.getName()); | ||
metrics.add(CoverageElement.CONDITIONAL.getName()); | ||
data.add("metrics", metrics); | ||
|
||
JsonArray covered = new JsonArray(); | ||
covered.add(line.numerator); | ||
covered.add(conditional.numerator); | ||
data.add("covered", covered); | ||
|
||
JsonArray missed = new JsonArray(); | ||
missed.add(line.denominator - line.numerator); | ||
missed.add(conditional.denominator - conditional.numerator); | ||
data.add("missed", missed); | ||
|
||
JsonArray coveredPercentage = new JsonArray(); | ||
coveredPercentage.add(line.denominator == 0 ? 0 : (double) (100 * (covered.get(0).getAsInt() / line.denominator))); | ||
coveredPercentage.add(conditional.denominator == 0 ? 0 : (double) (100 * (covered.get(1).getAsInt() / conditional.denominator))); | ||
data.add("coveredPercentage", coveredPercentage); | ||
|
||
JsonArray missedPercentage = new JsonArray(); | ||
missedPercentage.add(100 - coveredPercentage.get(0).getAsDouble()); | ||
missedPercentage.add(100 - coveredPercentage.get(1).getAsDouble()); | ||
data.add("missedPercentage", missedPercentage); | ||
|
||
String deltaLineLabel = getReferenceBuildUrl().isPresent() | ||
? String.format("%.2f%% (%s %.2f%%)", coveredPercentage.get(0).getAsDouble(), (char) 0x0394, | ||
action.getResult().getCoverageDelta(CoverageElement.LINE)) | ||
: String.format("%.2f%% (%s unknown)", coveredPercentage.get(0).getAsDouble(), (char) 0x0394); | ||
|
||
String deltaConditionalLabel = getReferenceBuildUrl().isPresent() | ||
? String.format("%.2f%% (%s %.2f%%)", coveredPercentage.get(1).getAsDouble(), (char) 0x0394, | ||
action.getResult().getCoverageDelta(CoverageElement.CONDITIONAL)) | ||
: String.format("%.2f%% (%s unknown)", coveredPercentage.get(1).getAsDouble(), (char) 0x0394); | ||
|
||
JsonArray coveredPercentageLabels = new JsonArray(); | ||
coveredPercentageLabels.add(deltaLineLabel); | ||
coveredPercentageLabels.add(deltaConditionalLabel); | ||
data.add("coveredPercentageLabels", coveredPercentageLabels); | ||
|
||
return data.toString(); | ||
} | ||
|
||
/** | ||
* Get the link to the build, that was used to compare the result with. | ||
* | ||
* @return | ||
* optional of the link to the build or empty optional. | ||
*/ | ||
public Optional<String> getReferenceBuildUrl() { | ||
return Optional.ofNullable(action.getResult().getReferenceBuildUrl()); | ||
} | ||
|
||
/** | ||
* The factory for the {@link CoveragePullRequestMonitoringPortlet}. | ||
*/ | ||
@Extension(optional = true) | ||
public static class PortletFactory extends MonitorPortletFactory { | ||
|
||
@Override | ||
public Collection<MonitorPortlet> getPortlets(Run<?, ?> build) { | ||
CoverageAction action = build.getAction(CoverageAction.class); | ||
|
||
if (action == null) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
return Collections.singleton(new CoveragePullRequestMonitoringPortlet(action)); | ||
} | ||
|
||
@Override | ||
public String getDisplayName() { | ||
return "Code Coverage API"; | ||
} | ||
} | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
.../resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?jelly escape-by-default='true'?> | ||
|
||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler"> | ||
|
||
<st:adjunct includes="io.jenkins.plugins.echarts"/> | ||
<st:adjunct includes="io.jenkins.plugins.bootstrap5"/> | ||
|
||
<div id="coverage-pr-portlet" data="${it.getCoverageResultsAsJsonModel()}" | ||
style="width: ${it.preferredWidth}px; height: ${it.preferredHeight - 100}px;"/> | ||
|
||
<script type="text/javascript" src="${rootURL}/plugin/code-coverage-api/scripts/coverage-portlet.js"/> | ||
|
||
<script type="text/javascript"> | ||
new CoveragePortletChart('coverage-pr-portlet'); | ||
</script> | ||
|
||
</j:jelly> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
|
||
var CoveragePortletChart = function (id) { | ||
|
||
const chartDom = document.getElementById(id); | ||
const portletChart = echarts.init(chartDom); | ||
const data = JSON.parse(chartDom.getAttribute('data')); | ||
|
||
const covered = data.covered; | ||
const missed = data.missed; | ||
|
||
const option = { | ||
tooltip: { | ||
trigger: 'axis', | ||
axisPointer: { | ||
type: 'shadow' | ||
}, | ||
formatter: function (obj) { | ||
if (Array.isArray(obj)) { | ||
if (obj.length === 2) { | ||
return '<div style="text-align: left"><b>' + obj[0].name + '</b><br/>' | ||
+ obj[0].marker + ' ' + obj[0].seriesName + ' ' + covered[obj[0].dataIndex] + '<br/>' | ||
+ obj[1].marker + ' ' + obj[1].seriesName + ' ' + missed[obj[1].dataIndex] + '</div>'; | ||
|
||
} else if (obj.length === 1) { | ||
return '<div style="text-align: left"><b>' + obj[0].name + '</b><br/>' | ||
+ obj[0].marker + ' ' + obj[0].seriesName + ' ' | ||
+ (obj[0].seriesName === 'Covered' ? covered[obj[0].dataIndex] : missed[obj[0].dataIndex]) + '</div>'; | ||
} | ||
} | ||
} | ||
}, | ||
legend: { | ||
data: ['Covered', 'Missed'] | ||
}, | ||
grid: { | ||
left: '3%', | ||
right: '4%', | ||
bottom: '3%', | ||
containLabel: true | ||
}, | ||
xAxis: { | ||
type: 'value', | ||
name: 'in %', | ||
}, | ||
yAxis: [{ | ||
type: 'category', | ||
data: data.metrics, | ||
axisLine: { | ||
show: false | ||
}, | ||
axisTick: { | ||
show: false | ||
} | ||
}, { | ||
type: 'category', | ||
data: data.coveredPercentageLabels, | ||
position: 'right', | ||
axisLine: { | ||
show: false | ||
}, | ||
axisTick: { | ||
show: false | ||
} | ||
}], | ||
series: [ | ||
{ | ||
name: 'Covered', | ||
type: 'bar', | ||
stack: 'sum', | ||
itemStyle: { | ||
normal: { | ||
color: '#A5D6A7' | ||
} | ||
}, | ||
emphasis: { | ||
focus: 'series' | ||
}, | ||
data: data.coveredPercentage | ||
}, | ||
{ | ||
name: 'Missed', | ||
type: 'bar', | ||
stack: 'sum', | ||
itemStyle: { | ||
normal: { | ||
color: '#EF9A9A' | ||
} | ||
}, | ||
emphasis: { | ||
focus: 'series' | ||
}, | ||
data: data.missedPercentage | ||
} | ||
] | ||
}; | ||
|
||
option && portletChart.setOption(option) | ||
} |