Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
47ec730
feat: Displays plugin healthScore for available plugins
alecharp Feb 27, 2025
f8842f9
Does not remove all score when one is missing
alecharp Mar 3, 2025
ec6d676
Adds health score column header
alecharp Mar 3, 2025
a0a62bf
Displays health score on plugin manage updates page
alecharp Mar 5, 2025
7dac82d
Displays scores on installed plugins
alecharp Mar 10, 2025
3370920
Fixes available update testing health score presence
alecharp Mar 10, 2025
ca01c75
Provides link to plugin health score from update tab
alecharp Mar 11, 2025
a2a04af
Creates score badge
alecharp Mar 11, 2025
c510645
Changes score badge color based on value for updates and installed
alecharp Mar 11, 2025
21b6bbd
Simplify health score css class selection
alecharp Mar 12, 2025
40b47ee
Computes health score css class in java code
alecharp Mar 12, 2025
6220b73
Fixes installed plugin score gathering
alecharp Mar 12, 2025
1831672
Reduces code duplication and coupling
alecharp Mar 12, 2025
e8ec45d
Reduces class name duplication
alecharp Mar 12, 2025
461be6f
Stops using deprecated SCSS variables
alecharp Mar 12, 2025
1112615
Moves health score column before enabling plugin
alecharp Mar 14, 2025
c5b03b6
Fixes java code formatting
alecharp Mar 14, 2025
7ecd37e
Fixes URL used to get to plugin health score report
alecharp Mar 14, 2025
d8398e2
:construction: Uses icons from plugins without scores in updates and …
alecharp Mar 14, 2025
ca19cea
Inline status-aborted symbol in available template
alecharp Mar 17, 2025
19aba65
Merge branch 'master' into feat/display-plugin-health-score-in-plugin…
alecharp Mar 17, 2025
85b2dde
Fixes health score scss syntax
alecharp Mar 17, 2025
ac4eaed
Reverts invalid removal from 111261546b8a891492237fbfbe34af2d1c0b38b3
alecharp Mar 17, 2025
9a13ad2
Restores installed downgradable cell
alecharp Mar 18, 2025
5a487e0
Enabled input was moved to the right
alecharp Mar 18, 2025
920efd4
Adds french translation for health score table headers
alecharp Mar 18, 2025
6c4204f
Merge branch 'master' into feat/display-plugin-health-score-in-plugin…
alecharp Mar 28, 2025
acbb0f6
Makes 'not applicable' icon less eye catching
alecharp Apr 3, 2025
07d1a08
Merge branch 'master' into feat/display-plugin-health-score-in-plugin…
alecharp Apr 3, 2025
8575976
Merge branch 'master' into feat/display-plugin-health-score-in-plugin…
alecharp Apr 3, 2025
1c4ac23
Only show health column if any plugin has it
daniel-beck Apr 9, 2025
31d4bbe
No needs for the administer permission check
alecharp Apr 9, 2025
3ac4013
Changes health key in update-center
alecharp Apr 11, 2025
118e263
Improves tooltip when no score available
alecharp Apr 11, 2025
c693c4f
CSS class for health score doesn't require Java naming trick
alecharp Apr 11, 2025
5b328e4
Shouldn't allow usage of health score class method
alecharp Apr 11, 2025
e10a3bb
Adds link to documentation on headers
alecharp Apr 11, 2025
d4402fb
Changes health header description and uses tooltip
alecharp Apr 14, 2025
2b40dde
Explains health score within tooltip
alecharp Apr 14, 2025
4ad31ee
Revert cell count when no score is available
alecharp Apr 15, 2025
f070756
Do not expose health to API
alecharp Apr 15, 2025
6f2d8e9
Correctly count cells based on health availability
alecharp Apr 15, 2025
ea7d6ec
Merge branch 'master' into feat/display-plugin-health-score-in-plugin…
alecharp Apr 15, 2025
c1b63c3
Merge branch 'master' into feat/display-plugin-health-score-in-plugin…
alecharp Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/main/java/hudson/PluginManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,10 @@
releaseTimestamp.put("displayValue", Messages.PluginManager_ago(Functions.getTimeSpanString(plugin.releaseTimestamp)));
jsonObject.put("releaseTimestamp", releaseTimestamp);
}
if (plugin.healthScore != null) {

Check warning on line 1560 in core/src/main/java/hudson/PluginManager.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1560 is only partially covered, one branch is missing
jsonObject.put("healthScore", plugin.healthScore);
jsonObject.put("healthScoreClass", plugin.healthScoreClass);

Check warning on line 1562 in core/src/main/java/hudson/PluginManager.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 1561-1562 are not covered by tests
}
return jsonObject;
})
.collect(toList());
Expand Down
28 changes: 28 additions & 0 deletions core/src/main/java/hudson/PluginWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@
*/
private static Set<String> CORE_ONLY_DEPENDANT = Set.of("jenkins-core");

private Integer healthScore;

/**
* Set the list of components that depend on this plugin.
* @param dependents The list of components that depend on this plugin.
Expand Down Expand Up @@ -423,6 +425,32 @@

}

@Restricted(NoExternalUse.class) // Jelly use only
public Integer getHealthScore() {
if (this.healthScore == null) {
this.healthScore = getInfoFromAllSites().stream()
.filter(Objects::nonNull)
.filter(p -> p.healthScore != null)
.findFirst()
.map(plugin -> plugin.healthScore)
.orElse(null);
}
return this.healthScore;

Check warning on line 438 in core/src/main/java/hudson/PluginWrapper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 430-438 are not covered by tests
}

@Restricted(NoExternalUse.class)
public static String getHealthScoreClassForScore(int score) {
if (score > 80) return "top";
if (score > 60) return "middle";
return "bottom";
}

@Restricted(NoExternalUse.class) // Jelly use only
public String getHealthScoreClass() {
if (this.healthScore == null) return null;
return getHealthScoreClassForScore(this.healthScore);

Check warning on line 451 in core/src/main/java/hudson/PluginWrapper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 450-451 are not covered by tests
}

@ExportedBean
public static final class Dependency {
@Exported
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/java/hudson/model/UpdateCenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,20 @@
return result;
}

@Restricted(NoExternalUse.class)
public boolean isHealthScoresAvailable() {
for (UpdateSite site : sites) {
final Data data = site.getData();
if (data == null) {
continue;
}
if (data.healthScoresAvailable) {

Check warning on line 775 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 775 is only partially covered, one branch is missing
return true;

Check warning on line 776 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 776 is not covered by tests
}
}
return false;
}

private boolean checkMinVersion(@CheckForNull Plugin p, @CheckForNull VersionNumber minVersion) {
return p != null
&& (minVersion == null || !minVersion.isNewerThan(new VersionNumber(p.version)));
Expand Down
23 changes: 23 additions & 0 deletions core/src/main/java/hudson/model/UpdateSite.java
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,9 @@ public final class Data {
*/
public final String connectionCheckUrl;

@Restricted(NoExternalUse.class)
public final boolean healthScoresAvailable;

Data(JSONObject o) {
this.sourceId = Util.intern((String) o.get("id"));
JSONObject c = o.optJSONObject("core");
Expand Down Expand Up @@ -649,6 +652,8 @@ public final class Data {
}
}

boolean healthScoresAvailable = false;

for (Map.Entry<String, JSONObject> e : (Set<Map.Entry<String, JSONObject>>) o.getJSONObject("plugins").entrySet()) {
Plugin p = new Plugin(sourceId, e.getValue());
// JENKINS-33308 - include implied dependencies for older plugins that may need them
Expand All @@ -662,6 +667,10 @@ public final class Data {
}
plugins.put(Util.intern(e.getKey()), p);

if (p.healthScore != null) {
healthScoresAvailable = true;
}

// compatibility with update sites that have no separate 'deprecated' top-level entry.
// Also do this even if there are deprecations to potentially allow limiting the top-level entry to overridden URLs.
if (p.hasCategory("deprecated")) {
Expand All @@ -671,6 +680,8 @@ public final class Data {
}
}

this.healthScoresAvailable = healthScoresAvailable;

connectionCheckUrl = (String) o.get("connectionCheckUrl");
}

Expand Down Expand Up @@ -1256,6 +1267,12 @@ public final class Plugin extends Entry {
@Restricted(NoExternalUse.class)
public IssueTracker[] issueTrackers;

@Restricted(NoExternalUse.class)
public final Integer healthScore;

@Restricted(NoExternalUse.class)
public final String healthScoreClass;

@DataBoundConstructor
public Plugin(String sourceId, JSONObject o) {
super(sourceId, o, UpdateSite.this.url);
Expand Down Expand Up @@ -1292,6 +1309,12 @@ public Plugin(String sourceId, JSONObject o) {
int optionalDepCount = (int) ja.stream().filter(IS_DEP_PREDICATE.and(IS_NOT_OPTIONAL.negate())).count();
dependencies = getPresizedMutableMap(depCount);
optionalDependencies = getPresizedMutableMap(optionalDepCount);
this.healthScore = o.has("health") ? o.getInt("health") : null;
if (healthScore != null) {
this.healthScoreClass = PluginWrapper.getHealthScoreClassForScore(healthScore);
} else {
this.healthScoreClass = null;
}

for (Object jo : o.getJSONArray("dependencies")) {
JSONObject depObj = (JSONObject) jo;
Expand Down
11 changes: 10 additions & 1 deletion core/src/main/resources/hudson/PluginManager/available.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ THE SOFTWARE.

<form id="form" method="post" action="install">
<table id="plugins" class="jenkins-table sortable"
data-hasAdmin="${h.hasPermission(app.ADMINISTER)}">
data-hasAdmin="${h.hasPermission(app.ADMINISTER)}"
data-health="${app.updateCenter.healthScoresAvailable}">
<thead>
<tr>
<l:isAdmin>
Expand All @@ -82,6 +83,14 @@ THE SOFTWARE.
</l:isAdmin>
<th initialSortDir="down">${%Name}</th>
<th>${%Released}</th>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<th>
<j:set var="healthTooltip">${%healthTooltip}</j:set>
<span data-html-tooltip="${healthTooltip}" data-tooltip-interactive="true">
${%Health}
</span>
</th>
</j:if>
</tr>
</thead>
<tbody/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
healthTooltip=\
A score evaluating the health of a plugin, using metrics such as actively maintained, recommended repository configuration applied and documentation provided. \
<a href="https://jenkins.io/redirect/plugin-health-score" target="_blank">Learn more</a>
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ Install\ after\ restart=Installer après le redémarrage
Install=Installer
Name=Nom
Released=Publié
Health=Santé
24 changes: 24 additions & 0 deletions core/src/main/resources/hudson/PluginManager/installed.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ THE SOFTWARE.
<thead>
<tr>
<th initialSortDir="down">${%Name}</th>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<th>
<j:set var="healthTooltip">${%healthTooltip}</j:set>
<span data-html-tooltip="${healthTooltip}" data-tooltip-interactive="true">
${%Health}
</span>
</th>
</j:if>
<th class="jenkins-table__cell--tight">${%Enabled}</th>
<l:isAdmin>
<th data-sort-disable="true"/>
Expand Down Expand Up @@ -161,6 +169,22 @@ THE SOFTWARE.
</div>
</j:if>
</td>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<td class="jenkins-table__cell">
<span>
<j:choose>
<j:when test="${p.healthScore != null}">
<a href="${p.url}/healthScore" class="jenkins-healthScore--badge jenkins-healthScore--${p.healthScoreClass}" target="_blank" rel="noopener noreferrer">
<st:out value="${p.healthScore}"/>
</a>
</j:when>
<j:otherwise>
<l:icon src="symbol-status-aborted" class="icon-lg" tooltip="${%No health score available}"/>
</j:otherwise>
</j:choose>
</span>
</td>
</j:if>
<j:set var="state" value="${p.enabled?'true':null}"/>
<td class="jenkins-table__cell--tight enable" data="${state}">
<div class="jenkins-table__cell__button-wrapper">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ adoptThisPlugin=\
reportIssue=Report an issue with this plugin
uninstall-title=Are you sure you want to uninstall {0}?
uninstall-description=This will remove the plugin binary from your $JENKINS_HOME, but it will leave the configuration files of the plugin untouched
healthTooltip=\
A score evaluating the health of a plugin, using metrics such as actively maintained, recommended repository configuration applied and documentation provided. \
<a href="https://jenkins.io/redirect/plugin-health-score" target="_blank">Learn more</a>
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ Uninstall=Désinstaller
Version=Version
Previously\ installed\ version=Version précédente
downgradeTo=Rétrograder à {0}
Health=Santé
22 changes: 22 additions & 0 deletions core/src/main/resources/hudson/PluginManager/updates.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ THE SOFTWARE.
<th initialSortDir="down">${%Name}</th>
<th>${%Released}</th>
<th>${%Installed}</th>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<th>
<j:set var="healthTooltip">${%healthTooltip}</j:set>
<span data-html-tooltip="${healthTooltip}" data-tooltip-interactive="true">
${%Health}
</span>
</th>
</j:if>
</tr>
</thead>
<j:forEach var="p" items="${list}">
Expand Down Expand Up @@ -225,6 +233,20 @@ THE SOFTWARE.
</j:otherwise>
</j:choose>
</td>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<td>
<j:choose>
<j:when test="${p.healthScore != null}">
<a href="${p.wiki}/healthScore" class="jenkins-healthScore--badge jenkins-healthScore--${p.healthScoreClass}" target="_blank" rel="noopener noreferrer">
<st:out value="${p.healthScore}"/>
</a>
</j:when>
<j:otherwise>
<l:icon src="symbol-status-aborted" class="icon-lg" tooltip="${%No health score available}"/>
</j:otherwise>
</j:choose>
</td>
</j:if>
</tr>
</j:forEach>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ adoptThisPlugin=\
deprecationWarning=<strong>This plugin is deprecated.</strong> In general, this means that it is either obsolete, no longer being developed, or may no longer work. <a href="{0}" rel="noopener noreferrer" target="_blank">Learn more.</a>
loading=Loading
CompatibleTooltip=Selects plugin updates that are compatible with their current version
healthTooltip=\
A score evaluating the health of a plugin, using metrics such as actively maintained, recommended repository configuration applied and documentation provided. \
<a href="https://jenkins.io/redirect/plugin-health-score" target="_blank">Learn more</a>
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ No\ updates\ available=Aucune mise à jour disponible
Version=Version
compatWarning=Avertissement: Cette nouvelle version n''est pas compatible avec la version installée. Il peut être nécessaire de reconfigurer les tâches utilisant ce plugin.
coreWarning=Ce plugin est conçu pour Jenkins {0} ou une version plus récente. Il pourrait ne pas fonctionner avec votre version de Jenkins.
Health=Santé
2 changes: 2 additions & 0 deletions src/main/js/plugin-manager-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function applyFilter(searchQuery) {
var pluginsTable = document.getElementById("plugins");
var tbody = pluginsTable.querySelector("tbody");
var admin = pluginsTable.dataset.hasadmin === "true";
var includeHealthScores = pluginsTable.dataset.health === "true";
var selectedPlugins = [];

var filterInput = document.getElementById("filter-box");
Expand Down Expand Up @@ -43,6 +44,7 @@ function applyFilter(searchQuery) {
(plugin) => selectedPlugins.indexOf(plugin.name) === -1,
),
admin,
includeHealthScores,
});

tbody.insertAdjacentHTML("beforeend", rows);
Expand Down
22 changes: 20 additions & 2 deletions src/main/js/templates/plugin-manager/available.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,31 @@
{{/if}}
</td>
{{#if this.releaseTimestamp }}
<td style="width: 25%" data="{{ this.releaseTimestamp.iso8601 }}">
<td style="width: 20%" data="{{ this.releaseTimestamp.iso8601 }}">
<time datetime="{{ this.releaseTimestamp.iso8601 }}">
{{ this.releaseTimestamp.displayValue }}
</time>
</td>
{{else}}
<td style="width: 25%"></td>
<td style="width: 20%"></td>
{{/if}}
{{#if @root/includeHealthScores }}
<td style="width: 15%">
<div>
{{#if this.healthScore }}
<a href="{{ this.wiki }}/healthScore"
class="jenkins-healthScore--badge jenkins-healthScore--{{ this.healthScoreClass }}"
target="_blank" rel="noopener noreferrer">
{{ this.healthScore }}
</a>
{{else}}
<div class="icon-lg">
<!-- symbol: status-aborted -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><ellipse cx="256" cy="256" rx="210" ry="210" fill="none" stroke="var(--text-color-secondary)" stroke-linecap="round" stroke-miterlimit="10" stroke-width="36" /><path fill="none" stroke="var(--text-color-secondary)" stroke-linecap="round" stroke-linejoin="round" stroke-width="36" d="M192 320l128-128" /></svg>
</div>
{{/if}}
</div>
</td>
{{/if}}
</tr>
{{/each}}
33 changes: 33 additions & 0 deletions src/main/scss/components/_healthScore.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.jenkins-healthScore {
&--badge {
--healthscore-badge-border-width: 4px;
--healthscore-badge-size: 32px;

width: var(--healthscore-badge-size);
height: var(--healthscore-badge-size);
border-radius: 50%;
border-width: var(--badge-border-width);
border-style: solid;
border-color: var(--accent-color);
display: table-cell;
vertical-align: middle;
text-align: center;
background-color: var(--background);

&:hover {
background-color: var(--light-grey);
}
}

&--top {
border-color: var(--success-color);
}

&--middle {
border-color: var(--warning-color);
}

&--bottom {
border-color: var(--destructive-color);
}
}
1 change: 1 addition & 0 deletions src/main/scss/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@use "content-blocks";
@use "dialogs";
@use "dropdowns";
@use "healthScore";
@use "icons";
@use "notice";
@use "notifications";
Expand Down
Loading
Loading