Skip to content

Commit e01dd64

Browse files
jtnordCopilotMarkEWaite
authored
[JENKINS-75727] Distinguish between primary and secondary actions in the header (#10729)
* Distinguish between primary and secondary actions in the header This PR distinguishes between primary and secondary Actions for the header. Primary actions are always shown, and secondary actions are by default in the Hamburger menu. When the current page is the page for the secondary action (or a parent thereof) the action is put in the main header action list and not in the hamburger menu. * remove actions-overflow * Fix Typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix inverted badgeClass Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Javadoc comment for isPrimaryAction Co-authored-by: Mark Waite <mark.earl.waite@gmail.com> * add ID to header "More Actions" button --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Mark Waite <mark.earl.waite@gmail.com>
1 parent 6a6714a commit e01dd64

12 files changed

Lines changed: 175 additions & 192 deletions

File tree

core/src/main/java/hudson/model/ManageJenkinsAction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public String getUrlName() {
6868
return "/manage";
6969
}
7070

71+
@Override
72+
public boolean isPrimaryAction() {
73+
return true;
74+
}
75+
7176
@Override
7277
public Object getStaplerFallback() {
7378
return Jenkins.get();

core/src/main/java/hudson/model/RootAction.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,15 @@ public interface RootAction extends Action, ExtensionPoint {
5050
default @CheckForNull Badge getBadge() {
5151
return null;
5252
}
53+
54+
/**
55+
* Identifies if the action is a primary action.
56+
* Primary actions may be handled differently in the UI (for example, by always showing on the header rather than in an actions dropdown).
57+
* In almost all cases this should return {@code false} which is the default
58+
* @return {@code true} iff this action should be considered primary.
59+
* @since TODO
60+
*/
61+
default boolean isPrimaryAction() {
62+
return false;
63+
}
5364
}

core/src/main/java/jenkins/model/navigation/SearchAction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,9 @@ public String getDisplayName() {
4747
public String getUrlName() {
4848
return null;
4949
}
50+
51+
@Override
52+
public boolean isPrimaryAction() {
53+
return true;
54+
}
5055
}

core/src/main/java/jenkins/model/navigation/UserAction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public User getUser() {
8080
return User.current();
8181
}
8282

83+
@Override
84+
public boolean isPrimaryAction() {
85+
return true;
86+
}
87+
8388
@Restricted(NoExternalUse.class)
8489
public List<Action> getActions() {
8590
User current = User.current();

core/src/main/java/jenkins/views/Header.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public static Header get() {
6464
}
6565

6666
/**
67-
* @return a list of {@link Action} to show in the header, defaults to {@link hudson.model.RootAction} extensions
67+
* @return a list of {@link Action} to show in the header.
68+
* The default implementation returns an {@link Jenkins#getActions()} that should be displayed (ie have an icon).
6869
*/
6970
@Restricted(NoExternalUse.class)
7071
public List<Action> getActions() {

core/src/main/resources/lib/layout/header/actions.jelly

Lines changed: 32 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,68 +3,44 @@
33
<div class="jenkins-header__actions">
44
<st:include page="suffix" optional="true" />
55

6-
<j:forEach var="action" items="${it.actions}">
7-
<j:set var="isCurrent" value="${h.hyperlinkMatchesCurrentPage(action.urlName)}" />
8-
9-
<j:set var="jumplist">
10-
<st:include it="${action}" page="jumplist.jelly" optional="true" />
11-
</j:set>
6+
<j:set var="allActions" value="${it.actions}"/>
127

13-
<j:if test="${jumplist.length() == 0}">
14-
<j:set var="tooltip">
15-
<st:include it="${action}" page="tooltip.jelly" optional="true" />
16-
</j:set>
17-
</j:if>
8+
<!-- first render all primary actions (except the user login )-->
9+
<j:forEach var="action" items="${allActions}">
10+
<j:set var="isCurrent" value="${h.hyperlinkMatchesCurrentPage(action.urlName)}" />
11+
<j:set var="isPrimary" value="${action.isPrimaryAction()}"/>
1812

19-
<j:set var="badge" value="${action.badge}" />
20-
<j:if test="${jumplist.length() == 0 and tooltip.length() == 0}">
21-
<j:set var="tooltip">
22-
<div style="text-align: center;">${action.displayName}</div>
23-
<j:if test="${badge != null}">
24-
<div style="text-align: center; color: var(--text-color-secondary)">${badge.tooltip}</div>
25-
</j:if>
26-
</j:set>
13+
<j:if test="${isCurrent or isPrimary}">
14+
<!-- do not show the user avatar -->
15+
<j:if test="${action.class.name != 'jenkins.model.navigation.UserAction'}">
16+
<h:primaryAction action="${action}" isCurrent="${isCurrent}"/>
17+
</j:if>
2718
</j:if>
19+
</j:forEach>
2820

29-
<j:set var="interactive" value="${jumplist.length() gt 0}" />
30-
<j:set var="icon" value="${action.iconClassName != null ? action.iconClassName : action.iconFileName}"/>
31-
<x:element name="${action.urlName == null ? 'button' : 'a'}">
32-
<x:attribute name="data-dropdown">${interactive}</x:attribute>
33-
<x:attribute name="id">root-action-${action.class.simpleName}</x:attribute>
34-
<x:attribute name="href">${h.getActionUrl(app.url, action)}</x:attribute>
35-
<j:if test="${interactive}">
36-
<x:attribute name="data-tippy-offset">[0, 10]</x:attribute>
21+
<!-- we only want the hamburger menu if there is something to display -->
22+
<!-- render all non primary non current actions -->
23+
<j:set var="hamburgerEntries">
24+
<j:forEach var="action" items="${allActions}">
25+
<j:set var="isCurrent" value="${h.hyperlinkMatchesCurrentPage(action.urlName)}" />
26+
<j:set var="isPrimary" value="${action.isPrimaryAction()}"/>
27+
<j:if test="${!isCurrent and !isPrimary}">
28+
<h:secondaryAction action="${action}"/>
3729
</j:if>
38-
<j:if test="${!interactive}">
39-
<x:attribute name="data-html-tooltip" escapeText="false"><j:out value="${tooltip}" /></x:attribute>
40-
</j:if>
41-
<x:attribute name="data-tooltip-interactive">${interactive}</x:attribute>
42-
<x:attribute name="data-tippy-animation">tooltip</x:attribute>
43-
<x:attribute name="data-tippy-theme">${interactive ? 'dropdown' : 'tooltip'}</x:attribute>
44-
<x:attribute name="data-tippy-trigger">mouseenter focus</x:attribute>
45-
<x:attribute name="data-tippy-touch">${interactive}</x:attribute>
46-
<x:attribute name="data-type">header-action</x:attribute>
47-
<x:attribute name="draggable">false</x:attribute>
48-
<x:attribute name="class">jenkins-button ${isCurrent ? '' : 'jenkins-button--tertiary'}</x:attribute>
49-
<l:icon src="${icon}" class="${action.class.name == 'jenkins.model.navigation.UserAction' ? 'jenkins-avatar' : ''}"/>
50-
<span class="jenkins-visually-hidden" data-type="action-label">${action.displayName}</span>
51-
<j:if test="${badge != null}">
52-
<span class="jenkins-badge jenkins-!-${badge.severity}-color" />
53-
</j:if>
54-
</x:element>
55-
56-
<j:if test="${interactive}">
57-
<template>
58-
<div class="jenkins-dropdown">
59-
<j:out value="${jumplist}" />
60-
</div>
61-
</template>
30+
</j:forEach>
31+
</j:set>
32+
<j:if test="${hamburgerEntries.length() gt 0}">
33+
<l:overflowButton icon="symbol-menu-hamburger" id="header-more-actions">
34+
<j:out value="${hamburgerEntries}" />
35+
</l:overflowButton>
36+
</j:if>
37+
38+
<!-- finally the user avatar or login button (if required) -->
39+
<j:forEach var="action" items="${allActions}">
40+
<j:if test="${action.class.name == 'jenkins.model.navigation.UserAction'}">
41+
<h:primaryAction action="${action}" isCurrent="${h.hyperlinkMatchesCurrentPage(action.urlName)}"/>
6242
</j:if>
63-
64-
<j:set var="jumplist" />
65-
<j:set var="tooltip" />
6643
</j:forEach>
67-
6844
<h:login/>
6945
</div>
70-
</j:jelly>
46+
</j:jelly>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:h="/lib/layout/header" xmlns:l="/lib/layout" xmlns:st="jelly:stapler" xmlns:x="jelly:xml">
3+
4+
5+
<st:documentation> <![CDATA[
6+
Used inside <h:actions> to render primaryActions.
7+
]]>
8+
<st:attribute name="action" use="required">
9+
The action to render
10+
</st:attribute>
11+
<st:attribute name="isCurrent" use="required">
12+
true if the action is the action for the current page
13+
</st:attribute>
14+
15+
</st:documentation>
16+
17+
<j:set var="jumplist">
18+
<st:include it="${action}" page="jumplist.jelly" optional="true" />
19+
</j:set>
20+
<x:comment>rendering primary action: ${action.class.name}</x:comment>
21+
<j:if test="${jumplist.length() == 0}">
22+
<j:set var="tooltip">
23+
<st:include it="${action}" page="tooltip.jelly" optional="true" />
24+
</j:set>
25+
</j:if>
26+
27+
<j:set var="badge" value="${action.badge}" />
28+
<j:if test="${jumplist.length() == 0 and tooltip.length() == 0}">
29+
<j:set var="tooltip">
30+
<div style="text-align: center;">${action.displayName}</div>
31+
<j:if test="${badge != null}">
32+
<div style="text-align: center; color: var(--text-color-secondary)">${badge.tooltip}</div>
33+
</j:if>
34+
</j:set>
35+
</j:if>
36+
37+
<j:set var="interactive" value="${jumplist.length() gt 0}" />
38+
<j:set var="icon" value="${action.iconClassName != null ? action.iconClassName : action.iconFileName}"/>
39+
<x:element name="${action.urlName == null ? 'button' : 'a'}">
40+
<x:attribute name="data-dropdown">${interactive}</x:attribute>
41+
<x:attribute name="id">root-action-${action.class.simpleName}</x:attribute>
42+
<x:attribute name="href">${h.getActionUrl(app.url, action)}</x:attribute>
43+
<j:if test="${interactive}">
44+
<x:attribute name="data-tippy-offset">[0, 10]</x:attribute>
45+
</j:if>
46+
<j:if test="${!interactive}">
47+
<x:attribute name="data-html-tooltip" escapeText="false"><j:out value="${tooltip}" /></x:attribute>
48+
</j:if>
49+
<x:attribute name="data-tooltip-interactive">${interactive}</x:attribute>
50+
<x:attribute name="data-tippy-animation">tooltip</x:attribute>
51+
<x:attribute name="data-tippy-theme">${interactive ? 'dropdown' : 'tooltip'}</x:attribute>
52+
<x:attribute name="data-tippy-trigger">mouseenter focus</x:attribute>
53+
<x:attribute name="data-tippy-touch">${interactive}</x:attribute>
54+
<x:attribute name="data-type">header-action</x:attribute>
55+
<x:attribute name="draggable">false</x:attribute>
56+
<x:attribute name="class">jenkins-button ${isCurrent ? '' : 'jenkins-button--tertiary'}</x:attribute>
57+
<l:icon src="${icon}" class="${action.class.name == 'jenkins.model.navigation.UserAction' ? 'jenkins-avatar' : ''}"/>
58+
<span class="jenkins-visually-hidden" data-type="action-label">${action.displayName}</span>
59+
<j:if test="${badge != null}">
60+
<span class="jenkins-badge jenkins-!-${badge.severity}-color" />
61+
</j:if>
62+
</x:element>
63+
64+
<j:if test="${interactive}">
65+
<template>
66+
<div class="jenkins-dropdown">
67+
<j:out value="${jumplist}" />
68+
</div>
69+
</template>
70+
</j:if>
71+
72+
</j:jelly>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:h="/lib/layout/header" xmlns:l="/lib/layout" xmlns:dd="/lib/layout/dropdowns" xmlns:st="jelly:stapler" xmlns:x="jelly:xml">
3+
4+
5+
<st:documentation> <![CDATA[
6+
Used inside <h:actions> to render primaryActions.
7+
]]>
8+
<st:attribute name="action" use="required">
9+
The action to render
10+
</st:attribute>
11+
</st:documentation>
12+
13+
<j:set var="badge" value="${action.badge}"/>
14+
<j:set var="badgeClazz" value="${badge != null ? 'jenkins-badge jenkins-!-${badge.severity}-color' : ''}"/>
15+
16+
<dd:item icon="${action.iconClassName != null ? action.iconClassName : action.iconFileName}"
17+
text="${action.displayName}"
18+
href="${h.getActionUrl(app.url, action)}"
19+
clazz="${badgeClazz}">
20+
</dd:item>
21+
22+
</j:jelly>

src/main/js/components/header/actions-overflow.js

Lines changed: 0 additions & 133 deletions
This file was deleted.

0 commit comments

Comments
 (0)