Refine experimental Run overflow menu (#26524)

This commit is contained in:
Jan Faracik authored and GitHub committed 2026-03-26 07:45:13 +00:00
1 parent 210527646d
commit 6f384824f4
6 files changed
+60 -39

No files matched your search

+6 -1
View File
@@ -170,6 +170,7 @@ import jenkins.model.SimplePageDecorator;
import jenkins.model.details.Detail;
import jenkins.model.details.DetailFactory;
import jenkins.model.details.DetailGroup;
import jenkins.model.menu.Group;
import jenkins.telemetry.impl.PasswordMasking;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
@@ -181,6 +182,7 @@ import org.apache.commons.jexl.parser.ASTSizeFunction;
import org.apache.commons.jexl.util.Introspector;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.jenkins.ui.icon.IconSpec;
import org.jvnet.tiger_types.Types;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -2669,7 +2671,10 @@ public class Functions {
ModelObjectWithContextMenu.ContextMenu contextMenu = new ModelObjectWithContextMenu.ContextMenu();
contextMenu.addAll(actions
.stream()
.filter(action -> action.getIconFileName() != null)
.filter(action ->
action.getIconFileName() != null
|| (action instanceof IconSpec iconSpec && iconSpec.getIconClassName() != null)
).filter(action -> action.getGroup().getOrder() < Group.FIRST_IN_MENU.getOrder())
.toList());
JSONObject jsonObject = JSONObject.fromObject(contextMenu);
jsonObject.put("url", Util.ensureEndsWith(baseUrl, "/"));
@@ -292,8 +292,17 @@ public interface ModelObjectWithContextMenu extends ModelObject {
public ContextMenu from(ModelObjectWithContextMenu self, StaplerRequest2 request, StaplerResponse2 response, String view) throws JellyException, IOException {
// Only Runs support getAppBarActions currently
if (self instanceof Run) {
this.addAll(((Actionable) self).getAppBarActions());
return this;
boolean menuOnly = Boolean.parseBoolean(request.getParameter("menu-only"));
List<Action> actions = ((Actionable) self).getAppBarActions().stream()
.filter(action ->
action.getIconFileName() != null
|| (action instanceof IconSpec iconSpec && iconSpec.getIconClassName() != null)
)
.filter(action -> !menuOnly || action.getGroup().getOrder() >= Group.FIRST_IN_MENU.getOrder())
.toList();
return new ModelObjectWithContextMenu.ContextMenu().addAll(actions);
}
WebApp webApp = WebApp.getCurrent();
@@ -25,17 +25,22 @@ THE SOFTWARE.
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<st:documentation>
Generates a strip of controls based on the given actions parameter.
Generates a strip of actions based on the given Actionable.
<st:attribute name="actions">
The actions to show.
<st:attribute name="it" use="required" type="hudson.model.Actionable">
The object to display actions for.
</st:attribute>
</st:documentation>
<j:set var="url" value="${h.getNearestAncestorUrl(request2, it)}/" />
<j:set var="actions" value="${attrs.it.appBarActions}" />
<j:if test="${actions != null}">
<button id="auto-overflow"
class="jenkins-button"
<button data-type="auto-overflow"
class="jenkins-button jenkins-jumplist-link"
tooltip="${%More actions}"
data-href="${url}"
data-jumplist-type="menu"
data-testid="app-bar-overflow-button"
type="button">
<div class="jenkins-overflow-button__ellipsis">
@@ -44,9 +49,9 @@ THE SOFTWARE.
<span />
</div>
</button>
<j:set var="url" value="${h.getNearestAncestorUrl(request2,it.object)}"/>
<template>
${h.convertActionsToJson(url, actions)}
${h.convertActionsToJson(url, attrs.it.appBarActions)}
</template>
</j:if>
</j:jelly>
@@ -68,7 +68,7 @@ THE SOFTWARE.
</div>
<div class="app-build-bar__controls">
<l:app-bar-controls actions="${it.object.appBarActions}" />
<l:actions it="${it.object}" />
</div>
</div>
+23 -27
View File
@@ -2,38 +2,34 @@ import behaviorShim from "@/util/behavior-shim";
import Utils from "@/components/dropdowns/utils";
import Templates from "@/components/dropdowns/templates";
/**
* Generates inline actions and an overflow menu if necessary.
*/
function init() {
behaviorShim.specify("#auto-overflow", "-dropdowns-", 1000, (element) => {
const template = JSON.parse(element.nextSibling.content.textContent);
const appBarItems = Utils.mapChildrenItemsToDropdownItems(
template.items.filter((e) => e.group.order <= 2),
);
const overflowItems = Utils.mapChildrenItemsToDropdownItems(
template.items.filter((e) => e.group.order > 2),
);
// Append top-level items to the app bar
appBarItems.forEach((item, index) => {
// Only the first button in an app bar should have an icon
if (index > 0) {
item.icon = null;
item.iconXml = null;
}
element.parentNode.insertBefore(
Templates.menuItem(item, "jenkins-button", template.url),
element,
behaviorShim.specify(
"[data-type='auto-overflow']",
"-dropdowns-",
1000,
(element) => {
const template = JSON.parse(element.nextSibling.content.textContent);
const topLevelActions = Utils.mapChildrenItemsToDropdownItems(
template.items.filter((e) => e.group.order <= 2),
);
});
// Add any additional items as an overflow menu
if (overflowItems.length > 0) {
Utils.generateDropdown(element, (instance) => {
instance.setContent(
Utils.generateDropdownItems(overflowItems, false, template.url),
// Append top-level items next to the overflow menu
topLevelActions.forEach((item, index) => {
// Only the first button in an app bar should have an icon
if (index > 0) {
item.icon = null;
item.iconXml = null;
}
element.parentNode.insertBefore(
Templates.menuItem(item, "jenkins-button", template.url),
element,
);
});
}
});
},
);
}
export default { init };
@@ -95,7 +95,13 @@ function generateDropdowns() {
return;
}
fetch(Path.combinePath(href, jumplistType))
// If requested, only return menu actions rather than top-level app bar actions
let query = "";
if (element.dataset.jumplistType === "menu") {
query = "?menu-only=true";
}
fetch(Path.combinePath(href, jumplistType) + query)
.then((response) => response.json())
.then((json) =>
instance.setContent(