Skip to content

Commit

Permalink
remove idle executors from widget (#9177)
Browse files Browse the repository at this point in the history
* remove idle executors from widget

showing idle executors brings no value. They only blow up the widget.
Instead the busy/total number of executors are added behind the agent
name when the agent is online.
The executor name is kept intentionally to distinguish regular executors
from oneoffexecutors

* feedback

use an icon for oneoffexecutors (FlyweightTask), instead of using a
number for normal executors (which is also not really interesting).

When there are no agents and no builds running use the same text as when
collapsed to show that nothing is running.

* busy in widget header

* fine tuning styling

* icon-xs symbols

* show offline

* show collapsed with no executors

* fix css

* append tooltip to parent

---------

Co-authored-by: Daniel Beck <1831569+daniel-beck@users.noreply.github.com>
  • Loading branch information
mawinter69 and daniel-beck authored Jun 18, 2024
1 parent 16b5d21 commit 92ac1d8
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 112 deletions.
286 changes: 176 additions & 110 deletions core/src/main/resources/lib/hudson/executors.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -33,130 +33,196 @@ THE SOFTWARE.
</st:documentation>
<d:taglib uri="local">
<d:tag name="computerCaption">

<a href="${rootURL}/${c.url}" class="jenkins-link--with-icon model-link inside"><l:icon src="${c.iconClassName}" alt="${altText}"/>
<span style="margin-left: 1ch;">${title}</span>
</a>
<span style="float: right; font-weight: normal;">
<j:if test="${c.offline}">
<j:choose>
<j:when test="${c.offlineCause!=null and !c.connecting}">
(<img src="${imagesURL}/svgs/error.svg" width="16" height="16" title="${c.offlineCause}"/> ${%offline})
</j:when>
<j:when test="${c.connecting}">
(<l:icon src="symbol-hourglass" class="icon-sm"/>${%launching})
</j:when>
<j:otherwise>
(${%offline})
</j:otherwise>
</j:choose>
</j:if>
<j:if test="${!c.acceptingTasks}"> <st:nbsp/> (${%suspended})</j:if>
</span>
</d:tag>
<d:tag name="executor">
<j:if test="${!c.offline or (c.offline and !e.idle)}">
<tr>
<td class="pane" align="right" style="vertical-align: top">
${name}
</td>
<j:choose>
<j:when test="${c.offline}">
<j:choose>
<j:when test="${e.idle}">
<td class="pane">
<j:choose>
<j:when test="${c.offline}">
<a href="${rootURL}/${c.url}" class="model-link inside">${%Offline}</a>
</j:when>
<j:otherwise>
${%Idle}
</j:otherwise>
</j:choose>
</td>
<td class="pane"/>
<td class="pane"/>
<j:when test="${c.offlineCause!=null and !c.connecting}">
<div>
(<l:icon src="symbol-error" class="icon-xs jenkins-!-error-color" title="${c.offlineCause}"/> ${%offline})
</div>
</j:when>
<j:when test="${c.connecting}">
<div>(<l:icon src="symbol-hourglass" class="icon-xs"/>${%launching})</div>
</j:when>
<j:otherwise>
<!-- not actually optional, but it helps with backward compatibility -->
<j:set var="executor" value="${e}" />
<st:include it="${e.currentExecutable}" page="executorCell.jelly" optional="true">
<td class="pane">
<div style="white-space: normal">
<j:set var="exe" value="${e.currentExecutable}" />
<j:set var="wu" value="${e.currentWorkUnit}" />
<j:choose>
<j:when test="${exe == null and wu != null}">
<j:set var="exeparent" value="${wu.work}"/>
<j:choose>
<j:when test="${h.hasPermission(exeparent,exeparent.READ)}">
<a href="${rootURL}/${exeparent.url}"><l:breakable value="${exeparent.fullDisplayName}"/></a>
<t:progressBar tooltip="${%Pending}" pos="-1" href="${rootURL}/${exeparent.url}"/>
</j:when>
<j:otherwise>
<span>${%Unknown Task}</span>
</j:otherwise>
</j:choose>
</j:when>
<j:when test="${exe == null and wu == null}">
<!-- went idle concurrent with testing for idle -->
${%Idle}
</j:when>
<j:otherwise>
<j:invokeStatic var="exeparent"
className="hudson.model.queue.Executables" method="getParentOf">
<j:arg type="hudson.model.Queue$Executable" value="${exe}" />
</j:invokeStatic>
<j:choose>
<j:when test="${h.hasPermission(exeparent,exeparent.READ)}">
<a href="${rootURL}/${exeparent.url}"><l:breakable value="${exeparent.fullDisplayName}"/></a>
<t:buildProgressBar build="${exe}" executor="${executor}"/>
</j:when>
<j:otherwise>
<span>${%Unknown Task}</span>
</j:otherwise>
</j:choose>
</j:otherwise>
</j:choose>
</div>
</td>
<td class="pane">
<j:if test="${h.hasPermission(exeparent,exeparent.READ)}">
<a href="${rootURL}/${exe.url}" class="model-link inside" style="display: block;"><l:breakable value="${exe.displayName}"/></a>
</j:if>
</td>
</st:include>
<td class="pane" align="center" valign="middle">
<j:if test="${h.hasPermission(exeparent,exeparent.READ) and e.hasStopPermission()}">
<l:stopButton href="${rootURL}/${c.url}${url}/stopBuild?runExtId=${h.urlEncode(exe.externalizableId)}" confirm="${%confirm(exe.fullDisplayName)}" alt="${%terminate this build}" />
</j:if>
</td>
<div>(${%offline})</div>
</j:otherwise>
</j:choose>
</tr>
</j:when>
<j:otherwise>
<div tooltip="${%busy(c.countBusy(), c.countExecutors())}" data-tooltip-append-to-parent="true">${c.countBusy()}/${c.countExecutors()}</div>
</j:otherwise>
</j:choose>
<j:if test="${!c.acceptingTasks}"> <st:nbsp/> (${%suspended})</j:if>
</d:tag>
<d:tag name="executor">
<j:if test="${!e.idle}">
<div class="executor-row">
<div class="executor-type">
<j:if test="${name == ''}">
<span tooltip="${%Light weight executor}" data-tooltip-append-to-parent="true">
<l:icon src="symbol-paper-plane-outline" class="icon-sm"/>
</span>
</j:if>
</div>
<div class="executor-cell">
<table class="executor-cell-table">
<tr>
<!-- not actually optional, but it helps with backward compatibility -->
<j:set var="executor" value="${e}" />
<st:include it="${e.currentExecutable}" page="executorCell.jelly" optional="true">
<td class="pane">
<div style="white-space: normal">
<j:set var="exe" value="${e.currentExecutable}" />
<j:set var="wu" value="${e.currentWorkUnit}" />
<j:choose>
<j:when test="${exe == null and wu != null}">
<j:set var="exeparent" value="${wu.work}"/>
<j:choose>
<j:when test="${h.hasPermission(exeparent,exeparent.READ)}">
<a href="${rootURL}/${exeparent.url}"><l:breakable value="${exeparent.fullDisplayName}"/></a>
<t:progressBar tooltip="${%Pending}" pos="-1" href="${rootURL}/${exeparent.url}"/>
</j:when>
<j:otherwise>
<span>${%Unknown Task}</span>
</j:otherwise>
</j:choose>
</j:when>
<j:when test="${exe == null and wu == null}">
<!-- went idle concurrent with testing for idle -->
${%Idle}
</j:when>
<j:otherwise>
<j:invokeStatic var="exeparent"
className="hudson.model.queue.Executables" method="getParentOf">
<j:arg type="hudson.model.Queue$Executable" value="${exe}" />
</j:invokeStatic>
<j:choose>
<j:when test="${h.hasPermission(exeparent,exeparent.READ)}">
<a href="${rootURL}/${exeparent.url}"><l:breakable value="${exeparent.fullDisplayName}"/></a>
<t:buildProgressBar build="${exe}" executor="${executor}"/>
</j:when>
<j:otherwise>
<span>${%Unknown Task}</span>
</j:otherwise>
</j:choose>
</j:otherwise>
</j:choose>
</div>
</td>
<td class="pane">
<j:if test="${h.hasPermission(exeparent,exeparent.READ)}">
<a href="${rootURL}/${exe.url}" class="model-link inside" style="display: block;"><l:breakable value="${exe.displayName}"/></a>
</j:if>
</td>
</st:include>
</tr>
</table>
</div>
<div class="executor-stop">
<j:if test="${h.hasPermission(exeparent,exeparent.READ) and e.hasStopPermission()}">
<l:stopButton href="${rootURL}/${c.url}${url}/stopBuild?runExtId=${h.urlEncode(exe.externalizableId)}" confirm="${%confirm(exe.fullDisplayName)}" alt="${%terminate this build}" />
</j:if>
</div>
</div>
</j:if>
</d:tag>
</d:taglib>
<j:set var="paneIsCollapsed" value="${h.isCollapsed('executors')}" />
<j:set var="computers" value="${attrs.computers?:app.computers}" />
<j:set var="origComputersSize" value="${computers.size()}"/>

<!-- Check if the built-in has to be shown, this is the case when it has either executors configured or is running
an asynchronous execution that doesn't want to hide the displaycell -->
<j:set var="builtInHasExecutors" value="${!app.toComputer().displayExecutors.isEmpty()}"/>
<j:set var="singleComputer" value="${computers.size() == 1
or (computers.size() == 2 and computers.get(0).node == app
and !builtInHasExecutors)}"/>
<!-- Remove the built-in from the list when it is included but has no executors to show -->
<j:choose>
<j:when test="${!builtInHasExecutors and computers.get(0).node == app and computers.size() gt 1}">
<j:set var="computers" value="${computers.subList(1, computers.size())}"/>
</j:when>
</j:choose>

<j:set var="computersSize" value="${computers.size()}"/>
<l:pane width="3" id="executors"
title="&lt;a href='${rootURL}/computer/'>${%Build Executor Status}&lt;/a>"
collapsedText="${%Computers(computersSize - 1, app.unlabeledLoad.computeTotalExecutors() - app.unlabeledLoad.computeIdleExecutors(), app.unlabeledLoad.computeTotalExecutors())}">
<colgroup><col width="30"/><col width="200*"/><col width="24"/></colgroup>
<j:choose>
<j:when test="${!builtInHasExecutors and app.clouds.isEmpty() and app.nodes.isEmpty()}">
<j:set var="collapsedText" value="${%noExecutors}"/>
<j:set var="showCollapsedText" value="true" />
</j:when>
<j:when test="${singleComputer}">
<j:set var="sc" value="${computers.get(0)}"/>
<j:set var="executorDetails" value="${sc.countBusy()}/${sc.countExecutors()}"/>
<j:set var="collapsedText" value="${%CollapsedSingle(sc.countBusy(), sc.countExecutors())}"/>
<j:set var="singleToolTip" value="${%busy(sc.countBusy(), sc.countExecutors())}"/>
</j:when>
<j:when test="${computersSize > 1 and !builtInHasExecutors}">
<j:set var="collapsedText" value="${%CollapsedMulti(computersSize, app.unlabeledLoad.computeTotalExecutors() - app.unlabeledLoad.computeIdleExecutors(), app.unlabeledLoad.computeTotalExecutors())}"/>
</j:when>
<j:when test="${computersSize > 1 and builtInHasExecutors}">
<j:set var="collapsedText" value="${%Computers(computersSize - 1, app.unlabeledLoad.computeTotalExecutors() - app.unlabeledLoad.computeIdleExecutors(), app.unlabeledLoad.computeTotalExecutors())}"/>
</j:when>
</j:choose>
<div class="pane-frame ${paneIsCollapsed ? 'collapsed' : 'expanded'}" id="executors">
<div class="pane-header">
<span class="pane-header-title">
<a href="${rootURL}/computer/">${%Build Executor Status}</a>
</span>
<j:if test="${origComputersSize == 1 and !paneIsCollapsed}">
<span class="pane-header-details" tooltip="${singleToolTip}" data-tooltip-append-to-parent="true">
${executorDetails}
</span>
</j:if>
<a class="collapse" href="${rootURL}/toggleCollapse?paneId=executors"
tooltip="${paneIsCollapsed ? '%Expand' : '%Collapse'}" data-tooltip-append-to-parent="true">
<j:set var="svgIconId" value="${paneIsCollapsed ? 'chevron-up' : 'chevron-down'}" />
<l:icon src="symbol-${svgIconId}" />
</a>
</div>
<j:choose>
<div class="pane-content">
<j:when test="${paneIsCollapsed or showCollapsedText}">
<div class="executors-collapsed">
${collapsedText}
</div>
</j:when>
<j:otherwise>
<j:forEach var="c" items="${computers}">
<div class="computer-row">
<j:set var="cDisplayExecutors" value="${c.displayExecutors}"/>
<j:choose>
<j:when test="${!singleComputer or origComputersSize > 1 or c.offline}">
<j:if test="${!cDisplayExecutors.isEmpty()}">
<div class="computer-caption">
<local:computerCaption title="${c.displayName}" />
</div>
</j:if>
</j:when>
</j:choose>
<j:if test="${!cDisplayExecutors.isEmpty()}">
<div class="executors-cell">
<j:forEach var="de" items="${cDisplayExecutors}" varStatus="eloop">
<j:set var="e" value="${de.executor}"/>
<local:executor name="${de.displayName}" url="${de.url}" />
</j:forEach>
</div>
</j:if>
</div>
</j:forEach>
</j:otherwise>
</div>
</j:choose>
<j:if test="${attrs.footer != null}">
<div class="pane-footer">
<j:out value="${attrs.footer}"/>
</div>
</j:if>
</div>

<j:forEach var="c" items="${computers}">
<j:set var="cDisplayExecutors" value="${c.displayExecutors}"/>
<tr>
<j:if test="${(computersSize gt 1 and !cDisplayExecutors.isEmpty()) or (c.node == app and c.offline)}">
<th class="pane" colspan="4">
<local:computerCaption title="${c.displayName}" />
</th>
</j:if>
</tr>
<j:forEach var="de" items="${cDisplayExecutors}" varStatus="eloop">
<j:set var="e" value="${de.executor}"/>
<local:executor name="${de.displayName}" url="${de.url}" />
</j:forEach>
</j:forEach>
</l:pane>
<!-- schedule updates only for the full page reload -->
<j:if test="${ajax==null and h.hasPermission(app.READ)}">
<div class="widget-refresh-reference" data-id="executors" data-url="${rootURL}/${it.url}ajax"/>
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/resources/lib/hudson/executors.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ Computers=built-in node{0,choice,0# ({1} of {2} executors busy)|1# + {0,number}
confirm=Are you sure you want to abort {0}?
launching=launching...
terminate\ this\ build=Cancel
busy={1,choice,1# {0} of {1} executor busy|1< {0} of {1} executors busy}
CollapsedSingle={1,choice,1# ({0} of {1} executor busy)|1< ({0} of {1} executors busy)}
CollapsedMulti={0} agents ({1} of {2} executors busy)}
noExecutors=No executors, agents or clouds are configured.
4 changes: 4 additions & 0 deletions core/src/main/resources/lib/hudson/executors_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ Unknown\ Task=Unbekannter Task
Pending=Wartend
Computers=Master{0,choice,0#|1# + {0,number} Agent ({1} von {2} Build-Prozessoren)|1< + {0,number} Agenten ({1} von {2} Build-Prozessoren)}
confirm=Möchten Sie {0} wirklich abbrechen?
busy={0} von {1} Build-Prozessoren belegt
CollapsedSingle={1,choice,1# ({0} von {1} Prozessor belegt)|1< ({0} von {1} Prozessoren belegt)}
CollapsedMulti={0} Agenten ({1} von {2} Build-Prozessoren belegt)}
noExecutors=Es sind keine Prozessoren, Agenten oder Clouds konfiguriert.
1 change: 1 addition & 0 deletions war/src/main/resources/images/symbols/error.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions war/src/main/scss/base/_style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,18 @@ body.no-sticker #bottom-sticker {
}

/* see the Icon class for the definition of these CSS classes */
.icon-xs,
svg.icon-xs {
width: 12px;
height: 12px;
vertical-align: middle;

svg {
width: 12px;
height: 12px;
}
}

.icon-sm,
svg.icon-sm {
width: 16px;
Expand Down
Loading

0 comments on commit 92ac1d8

Please sign in to comment.