From eafdb0594182d9e99a49e45b826c21149dfcee39 Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Thu, 20 May 2021 21:56:27 +0200 Subject: [PATCH 1/7] implement coverage portlet for pull-request-monitoring plugin --- pom.xml | 6 + .../CoveragePullRequestMonitoringPortlet.java | 122 ++++++++++++++++++ .../monitor.jelly | 48 +++++++ src/main/webapp/scripts/coverage-portlet.js | 119 +++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java create mode 100644 src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly create mode 100644 src/main/webapp/scripts/coverage-portlet.js diff --git a/pom.xml b/pom.xml index 739b2d5e5..0883c836b 100644 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,12 @@ org.jenkins-ci.plugins display-url-api + + io.jenkins.plugins + pull-request-monitoring + 1.4.0 + true + org.mockito mockito-core diff --git a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java new file mode 100644 index 000000000..52707b488 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java @@ -0,0 +1,122 @@ +package io.jenkins.plugins.coverage; + +import hudson.Extension; +import hudson.model.Run; +import io.jenkins.plugins.coverage.targets.CoverageElement; +import io.jenkins.plugins.coverage.targets.CoverageResult; +import io.jenkins.plugins.coverage.targets.Ratio; +import io.jenkins.plugins.monitoring.MonitorPortlet; +import io.jenkins.plugins.monitoring.MonitorPortletFactory; +import org.kohsuke.stapler.bind.JavaScriptMethod; + +import java.util.*; + +/** + * A portlet that can be used for the pull-request-monitoring dashboard + * (https://github.com/jenkinsci/pull-request-monitoring-plugin). + * + * It renders the aggregated line, method and instruction coverage in a stacked bar chart. + * + * @author Simon Symhoven + */ +public class CoveragePullRequestMonitoringPortlet implements MonitorPortlet { + private final CoverageAction action; + + /** + * Creates a new {@link CoveragePullRequestMonitoringPortlet}. + * + * @param action + * the {@link CoverageAction} of corresponding run. + */ + public CoveragePullRequestMonitoringPortlet(final CoverageAction action) { + this.action = action; + } + + @Override + public String getTitle() { + return action.getDisplayName(); + } + + @Override + public String getId() { + return "code-coverage"; + } + + @Override + public int getPreferredWidth() { + return 600; + } + + @Override + public int getPreferredHeight() { + return 400; + } + + @Override + public String getIconUrl() { + return null; + } + + @Override + public Optional getDetailViewUrl() { + return Optional.ofNullable(action.getUrlName()); + } + + /** + * Transform the result map of {@link CoverageResult} to a list of {@link CoverageResult.JSCoverageResult}. + * + * @return + * the transformed list. + */ + @JavaScriptMethod + public List getResults() { + List results = new LinkedList<>(); + + for (Map.Entry c : action.getResult().getResults().entrySet()) { + String name = c.getKey().getName(); + if ("Conditional".equals(name) || "Line".equals(name) || "Instruction".equals(name)) { + results.add(new CoverageResult.JSCoverageResult(c.getKey().getName(), c.getValue())); + } + } + + return results; + } + + /** + * 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 getComparedBuildLink() { + return Optional.ofNullable(action.getResult().getLinkToBuildThatWasUsedForComparison()); + } + + /** + * Get the diff to the target branch. + * + * @return + * the diff as float. + */ + public float getDiff() { + return action.getResult().getChangeRequestCoverageDiffWithTargetBranch(); + } + + /** + * The factory for the {@link CoveragePullRequestMonitoringPortlet}. + */ + @Extension(optional = true) + public static class PortletFactory implements MonitorPortletFactory { + + @Override + public Collection getPortlets(Run build) { + CoverageAction action = build.getAction(CoverageAction.class); + return Collections.singleton(new CoveragePullRequestMonitoringPortlet(action)); + } + + @Override + public String getDisplayName() { + return "Code Coverage API"; + } + } +} diff --git a/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly b/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly new file mode 100644 index 000000000..9647c4dd5 --- /dev/null +++ b/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly @@ -0,0 +1,48 @@ + + + + + + + + + + + + +

+ 🎉 Code coverage compared to reference build increased! +

+${coverageDiff}%

+

+
+ +

+ 🚫 Code coverage compared to reference build decreased! +

-${coverageDiff}%

+

+
+ +

+ 👍 Code coverage compared to reference build has not changed! +

+
+
+
+ +

❓Calculate diff for change request is not enabled!

+
+ +
+ + + + \ No newline at end of file diff --git a/src/main/webapp/scripts/coverage-portlet.js b/src/main/webapp/scripts/coverage-portlet.js new file mode 100644 index 000000000..c5545de4c --- /dev/null +++ b/src/main/webapp/scripts/coverage-portlet.js @@ -0,0 +1,119 @@ + +var CoveragePortletChart = function (results, id) { + + var metrics = []; + var covered = []; + var missed = []; + var coveredPercentage = []; + var missedPercentage = []; + for (var i = 0; i < results.length; i++) { + metrics[i] = results[i].name; + covered[i] = results[i].ratio.numerator; + missed[i] = results[i].ratio.denominator - covered[i]; + + coveredPercentage[i] = 100 * (covered[i] / results[i].ratio.denominator); + missedPercentage[i] = 100 - coveredPercentage[i]; + + if (results[i].ratio.denominator === 0) { + coveredPercentage[i] = 0; + } + + } + + const chartDom = document.getElementById(id); + const portletChart = echarts.init(chartDom); + + const option = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + }, + formatter: function (obj) { + if (Array.isArray(obj)) { + if (obj.length === 2) { + return '
' + obj[0].name + '
' + + obj[0].marker + ' ' + obj[0].seriesName + '  ' + covered[obj[0].dataIndex] + '
' + + obj[1].marker + ' ' + obj[1].seriesName + '    ' + missed[obj[1].dataIndex] + '
'; + + } else if (obj.length === 1) { + return '
' + obj[0].name + '
' + + obj[0].marker + ' ' + obj[0].seriesName + '  ' + + (obj[0].seriesName === 'Covered' ? covered[obj[0].dataIndex] : missed[obj[0].dataIndex]) + '
'; + } + } + } + }, + legend: { + data: ['Covered', 'Missed'] + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true + }, + xAxis: { + type: 'value', + name: 'in %', + }, + yAxis: [{ + name: 'Metric', + type: 'category', + data: metrics, + axisLine: { + show: false + }, + axisTick: { + show: false + } + }, { + type: 'category', + data: coveredPercentage, + position: 'right', + axisLabel: { + formatter: function (value, index) { + return coveredPercentage[index].toFixed(2) + "%"; + } + }, + axisLine: { + show: false + }, + axisTick: { + show: false + } + }], + series: [ + { + name: 'Covered', + type: 'bar', + stack: 'sum', + itemStyle: { + normal: { + color: '#A5D6A7' + } + }, + emphasis: { + focus: 'series' + }, + data: coveredPercentage + }, + { + name: 'Missed', + type: 'bar', + stack: 'sum', + itemStyle: { + normal: { + color: '#EF9A9A' + } + }, + emphasis: { + focus: 'series' + }, + data: missedPercentage + } + ] + }; + + option && portletChart.setOption(option) +} \ No newline at end of file From b96450be530479ad276fc3d29a98e9af4441a92e Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Wed, 26 May 2021 13:35:18 +0200 Subject: [PATCH 2/7] integrate delta coverage values into bar chart --- pom.xml | 20 ++++- .../CoveragePullRequestMonitoringPortlet.java | 89 +++++++++++++------ .../monitor.jelly | 35 +------- src/main/webapp/scripts/coverage-portlet.js | 39 ++------ 4 files changed, 91 insertions(+), 92 deletions(-) diff --git a/pom.xml b/pom.xml index 286c3fe23..33faa77bb 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,10 @@ 3.16.1 5.1.0-2 1.0.0 - 1.4.0 + 1.5.3 + 2.2.0 + 5.15.3-2 + 2.8.6 diff --git a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java index 52707b488..326e04133 100644 --- a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java +++ b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java @@ -1,5 +1,7 @@ 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; @@ -53,8 +55,8 @@ public int getPreferredHeight() { } @Override - public String getIconUrl() { - return null; + public Optional getIconUrl() { + return Optional.empty(); } @Override @@ -63,23 +65,58 @@ public Optional getDetailViewUrl() { } /** - * Transform the result map of {@link CoverageResult} to a list of {@link CoverageResult.JSCoverageResult}. + * Get the json data for the stacked bar chart. (used by jelly view) * * @return - * the transformed list. + * the data as json string. */ - @JavaScriptMethod - public List getResults() { - List results = new LinkedList<>(); - - for (Map.Entry c : action.getResult().getResults().entrySet()) { - String name = c.getKey().getName(); - if ("Conditional".equals(name) || "Line".equals(name) || "Instruction".equals(name)) { - results.add(new CoverageResult.JSCoverageResult(c.getKey().getName(), c.getValue())); - } - } - - return results; + 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(); } /** @@ -88,18 +125,8 @@ public List getResults() { * @return * optional of the link to the build or empty optional. */ - public Optional getComparedBuildLink() { - return Optional.ofNullable(action.getResult().getLinkToBuildThatWasUsedForComparison()); - } - - /** - * Get the diff to the target branch. - * - * @return - * the diff as float. - */ - public float getDiff() { - return action.getResult().getChangeRequestCoverageDiffWithTargetBranch(); + public Optional getReferenceBuildUrl() { + return Optional.ofNullable(action.getResult().getReferenceBuildUrl()); } /** @@ -111,6 +138,11 @@ public static class PortletFactory implements MonitorPortletFactory { @Override public Collection getPortlets(Run build) { CoverageAction action = build.getAction(CoverageAction.class); + + if (action == null) { + return Collections.emptyList(); + } + return Collections.singleton(new CoveragePullRequestMonitoringPortlet(action)); } @@ -119,4 +151,5 @@ public String getDisplayName() { return "Code Coverage API"; } } + } diff --git a/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly b/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly index 9647c4dd5..516ed6ab5 100644 --- a/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly +++ b/src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly @@ -5,44 +5,13 @@ - - - - - -

- 🎉 Code coverage compared to reference build increased! -

+${coverageDiff}%

-

-
- -

- 🚫 Code coverage compared to reference build decreased! -

-${coverageDiff}%

-

-
- -

- 👍 Code coverage compared to reference build has not changed! -

-
-
-
- -

❓Calculate diff for change request is not enabled!

-
- -
\ No newline at end of file diff --git a/src/main/webapp/scripts/coverage-portlet.js b/src/main/webapp/scripts/coverage-portlet.js index c5545de4c..89f73d0c9 100644 --- a/src/main/webapp/scripts/coverage-portlet.js +++ b/src/main/webapp/scripts/coverage-portlet.js @@ -1,27 +1,12 @@ -var CoveragePortletChart = function (results, id) { - - var metrics = []; - var covered = []; - var missed = []; - var coveredPercentage = []; - var missedPercentage = []; - for (var i = 0; i < results.length; i++) { - metrics[i] = results[i].name; - covered[i] = results[i].ratio.numerator; - missed[i] = results[i].ratio.denominator - covered[i]; - - coveredPercentage[i] = 100 * (covered[i] / results[i].ratio.denominator); - missedPercentage[i] = 100 - coveredPercentage[i]; - - if (results[i].ratio.denominator === 0) { - coveredPercentage[i] = 0; - } - - } +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: { @@ -58,9 +43,8 @@ var CoveragePortletChart = function (results, id) { name: 'in %', }, yAxis: [{ - name: 'Metric', type: 'category', - data: metrics, + data: data.metrics, axisLine: { show: false }, @@ -69,13 +53,8 @@ var CoveragePortletChart = function (results, id) { } }, { type: 'category', - data: coveredPercentage, + data: data.coveredPercentageLabels, position: 'right', - axisLabel: { - formatter: function (value, index) { - return coveredPercentage[index].toFixed(2) + "%"; - } - }, axisLine: { show: false }, @@ -96,7 +75,7 @@ var CoveragePortletChart = function (results, id) { emphasis: { focus: 'series' }, - data: coveredPercentage + data: data.coveredPercentage }, { name: 'Missed', @@ -110,7 +89,7 @@ var CoveragePortletChart = function (results, id) { emphasis: { focus: 'series' }, - data: missedPercentage + data: data.missedPercentage } ] }; From c432c55f203dcf569af241e29e95bf11b76111e2 Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Fri, 28 May 2021 10:51:15 +0200 Subject: [PATCH 3/7] bump pull-request-monitoring from 1.5.3 to 1.6.0 --- pom.xml | 12 ++++++++++- .../CoveragePullRequestMonitoringPortlet.java | 20 +++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 33faa77bb..c21355fe8 100644 --- a/pom.xml +++ b/pom.xml @@ -22,10 +22,12 @@ 3.16.1 5.1.0-2 1.0.0 - 1.5.3 + 1.6.0 2.2.0 5.15.3-2 2.8.6 + 2.92 + 2.24 + + org.jenkins-ci.plugins.workflow + workflow-multibranch + ${workflow-multibranch.version} + + org.jenkins-ci.plugins @@ -124,6 +133,7 @@ org.jenkins-ci.plugins.workflow workflow-cps + ${workflow-cps.version} test diff --git a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java index 326e04133..d4a365d83 100644 --- a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java +++ b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java @@ -5,23 +5,22 @@ import hudson.Extension; import hudson.model.Run; import io.jenkins.plugins.coverage.targets.CoverageElement; -import io.jenkins.plugins.coverage.targets.CoverageResult; import io.jenkins.plugins.coverage.targets.Ratio; import io.jenkins.plugins.monitoring.MonitorPortlet; import io.jenkins.plugins.monitoring.MonitorPortletFactory; -import org.kohsuke.stapler.bind.JavaScriptMethod; import java.util.*; /** - * A portlet that can be used for the pull-request-monitoring dashboard - * (https://github.com/jenkinsci/pull-request-monitoring-plugin). + * A portlet that can be used for the + * pull-request-monitoring dashboard. * - * It renders the aggregated line, method and instruction coverage in a stacked bar chart. + * 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 implements MonitorPortlet { +public class CoveragePullRequestMonitoringPortlet extends MonitorPortlet { private final CoverageAction action; /** @@ -44,6 +43,11 @@ public String getId() { return "code-coverage"; } + @Override + public boolean isDefault() { + return true; + } + @Override public int getPreferredWidth() { return 600; @@ -56,7 +60,7 @@ public int getPreferredHeight() { @Override public Optional getIconUrl() { - return Optional.empty(); + return Optional.of("/images/48x48/graph.png"); } @Override @@ -133,7 +137,7 @@ public Optional getReferenceBuildUrl() { * The factory for the {@link CoveragePullRequestMonitoringPortlet}. */ @Extension(optional = true) - public static class PortletFactory implements MonitorPortletFactory { + public static class PortletFactory extends MonitorPortletFactory { @Override public Collection getPortlets(Run build) { From 1b0dd2f11343cbe48633e97f0a2f36dd0f815ef6 Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Sat, 29 May 2021 14:29:05 +0200 Subject: [PATCH 4/7] bump pull-request-monitoring from 1.6.0 to 1.6.1 --- pom.xml | 2 +- .../plugins/coverage/CoveragePullRequestMonitoringPortlet.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c21355fe8..d2b7aa891 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 3.16.1 5.1.0-2 1.0.0 - 1.6.0 + 1.6.1 2.2.0 5.15.3-2 2.8.6 diff --git a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java index d4a365d83..c7d2f9b4b 100644 --- a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java +++ b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java @@ -30,6 +30,7 @@ public class CoveragePullRequestMonitoringPortlet extends MonitorPortlet { * the {@link CoverageAction} of corresponding run. */ public CoveragePullRequestMonitoringPortlet(final CoverageAction action) { + super(); this.action = action; } From 832297457d34225475eac48e464d615a1ea952e8 Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Mon, 31 May 2021 11:21:40 +0200 Subject: [PATCH 5/7] bump pull-request-monitoring from 1.6.1 to 1.7.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d2b7aa891..4e7582182 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 3.16.1 5.1.0-2 1.0.0 - 1.6.1 + 1.7.0 2.2.0 5.15.3-2 2.8.6 From 05722c34c6c5ab6ad964160c1233708b25955125 Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Mon, 31 May 2021 22:13:17 +0200 Subject: [PATCH 6/7] change height to 350 instead of 400 px --- .../plugins/coverage/CoveragePullRequestMonitoringPortlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java index c7d2f9b4b..26ab3452f 100644 --- a/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java +++ b/src/main/java/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet.java @@ -56,7 +56,7 @@ public int getPreferredWidth() { @Override public int getPreferredHeight() { - return 400; + return 350; } @Override From 5a89f786cd0732c2d970c37be377c0c018a0d038 Mon Sep 17 00:00:00 2001 From: simonsymhoven Date: Tue, 1 Jun 2021 15:53:46 +0200 Subject: [PATCH 7/7] bump pull-request-monitoring from 1.7.0 to 1.7.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4e7582182..1cbb17060 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 3.16.1 5.1.0-2 1.0.0 - 1.7.0 + 1.7.1 2.2.0 5.15.3-2 2.8.6