Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Ionicons (symbols) to <l:Icon> and <l:task> components #6186

Merged
merged 35 commits into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
efd57ff
Init
janfaracik Jan 13, 2022
2e6c2d7
Update Icon.java
janfaracik Jan 13, 2022
6994e51
Renamed SVGs folder from ionicons, add test icon
janfaracik Jan 13, 2022
ec2fb5b
Updated plugin manager
janfaracik Jan 14, 2022
2080aab
Getting there
janfaracik Jan 14, 2022
586c22a
Working (albeit very messy) build
janfaracik Jan 14, 2022
88b61aa
Working build
janfaracik Jan 15, 2022
dfc874d
Update Icon.java
janfaracik Jan 15, 2022
7ad92f6
Rename item
janfaracik Jan 15, 2022
e989919
Rename param to symbol
janfaracik Jan 15, 2022
e4bd510
Update icon.jelly
janfaracik Jan 15, 2022
e3d77ac
Working build
janfaracik Jan 15, 2022
c2e47cb
Move functions to Java
janfaracik Jan 15, 2022
d8169ad
Cleanup + fix menu
janfaracik Jan 15, 2022
b869976
Fix settings symbol size
janfaracik Jan 15, 2022
58936fe
Reset some files
janfaracik Jan 15, 2022
e3c1004
Address some comments
janfaracik Jan 16, 2022
f918424
Fix test + add case for symbol
janfaracik Jan 16, 2022
013f733
Add translation service
janfaracik Jan 16, 2022
9fed655
Remove suppresion
janfaracik Jan 16, 2022
839d823
Add suggestions by @timja
janfaracik Jan 17, 2022
f476b72
Fix breadcrumbs icon missing/incorrect size
janfaracik Jan 18, 2022
f8a0ece
Potentially fix test
janfaracik Jan 18, 2022
26ebfad
Update Functions.java
janfaracik Jan 18, 2022
cc2a014
Potential fix for plugins icons not showing
janfaracik Jan 18, 2022
f6de539
Fix missing slash in tests
janfaracik Jan 18, 2022
0987321
Restore icon requirement for tasks
janfaracik Jan 18, 2022
5241717
Icons are now blue in dropdown menus, icon color no longer changes on…
janfaracik Jan 18, 2022
4614b08
Merge branch 'master' into scalable-icon-support
janfaracik Jan 27, 2022
2aa2508
Merge branch 'master' into scalable-icon-support
janfaracik Feb 4, 2022
88699d6
Merge branch 'master' into scalable-icon-support
janfaracik Feb 7, 2022
8a55f17
Update repeatableDeleteButton.jelly
janfaracik Feb 7, 2022
c9bda44
Update Action javadoc
janfaracik Feb 7, 2022
e03f7d0
Update summary
janfaracik Feb 7, 2022
f4261b8
Remove icon loading logic from summary
janfaracik Feb 7, 2022
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
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/AboutJenkins.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
public class AboutJenkins extends ManagementLink {
@Override
public String getIconFileName() {
return "help.png";
return "symbol-help-circle";
}

@Override
Expand Down
50 changes: 50 additions & 0 deletions core/src/main/java/hudson/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
import org.apache.commons.jexl.parser.ASTSizeFunction;
import org.apache.commons.jexl.util.Introspector;
import org.apache.commons.lang.StringUtils;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.jvnet.tiger_types.Types;
import org.kohsuke.accmod.Restricted;
Expand Down Expand Up @@ -2289,4 +2290,53 @@ public static boolean isContextMenuVisible(Action a) {
return true;
}
}

public static Icon tryGetIcon(String iconGuess) {
janfaracik marked this conversation as resolved.
Show resolved Hide resolved
// Jenkins Symbols don't have metadata so return null
if (iconGuess == null || iconGuess.startsWith("symbol-")) {
return null;
}

StaplerRequest currentRequest = Stapler.getCurrentRequest();
currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caused SECURITY-2777

Icon iconMetadata = IconSet.icons.getIconByClassSpec(iconGuess);

if (iconMetadata == null) {
// Icon could be provided as a simple iconFileName e.g. "settings.png"
iconMetadata = IconSet.icons.getIconByClassSpec(IconSet.toNormalizedIconNameClass(iconGuess) + " icon-md");
}

if (iconMetadata == null) {
// Icon could be provided as an absolute iconFileName e.g. "/plugin/foo/abc.png"
iconMetadata = IconSet.icons.getIconByUrl(iconGuess);
}

return iconMetadata;
}

public static String tryGetIconPath(String iconGuess, JellyContext context) {
janfaracik marked this conversation as resolved.
Show resolved Hide resolved
if (iconGuess == null) {
return null;
}

if (iconGuess.startsWith("symbol-")) {
return iconGuess;
}

StaplerRequest currentRequest = Stapler.getCurrentRequest();
currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caused SECURITY-2777

String rootURL = currentRequest.getContextPath();
Icon iconMetadata = tryGetIcon(iconGuess);
String iconSource = null;

if (iconMetadata != null) {
iconSource = iconMetadata.getQualifiedUrl(context);
}

if (iconMetadata == null) {
iconSource = rootURL + (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : "") + getResourcePath() + iconGuess;
}

return iconSource;
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/model/ManageJenkinsAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class ManageJenkinsAction implements RootAction {
@Override
public String getIconFileName() {
if (Jenkins.get().hasAnyPermission(Jenkins.MANAGE, Jenkins.SYSTEM_READ))
return "gear.png";
return "symbol-settings";
else
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/jenkins/management/ConfigureLink.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class ConfigureLink extends ManagementLink {

@Override
public String getIconFileName() {
return "gear2.png";
return "symbol-settings";
}

@Override
Expand Down
23 changes: 23 additions & 0 deletions core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ public ContextMenu add(String url, String icon, String text, boolean post, boole
return this;
}

/** @since TODO */
public ContextMenu add(String url, String icon, String iconXml, String text, boolean post, boolean requiresConfirmation) {
if (text != null && icon != null && url != null) {
MenuItem item = new MenuItem(url, icon, text);
item.iconXml = iconXml;
item.post = post;
item.requiresConfirmation = requiresConfirmation;
items.add(item);
}
return this;
}

/**
* Add a header row (no icon, no URL, rendered in header style).
*
Expand Down Expand Up @@ -268,6 +280,13 @@ class MenuItem {
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "read by Stapler")
public String icon;

/**
* Optional icon XML, if set it's used instead of @icon for the menu item
*/
@Exported
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "read by Stapler")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the suppression now

private String iconXml;

/**
* True to make a POST request rather than GET.
* @since 1.504
Expand Down Expand Up @@ -300,6 +319,10 @@ class MenuItem {
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "read by Stapler")
public ContextMenu subMenu;

public String getIconXml() {
return iconXml;
}

public MenuItem(String url, String icon, String displayName) {
withUrl(url).withIcon(icon).withDisplayName(displayName);
}
Expand Down
49 changes: 20 additions & 29 deletions core/src/main/java/org/jenkins/ui/icon/IconSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* An icon set.
Expand All @@ -45,7 +47,7 @@ public class IconSet {


public static final IconSet icons = new IconSet();
private static final Map<String, String> IONICONS = new ConcurrentHashMap<>();
private static final Map<String, String> SYMBOLS = new ConcurrentHashMap<>();

private Map<String, Icon> iconsByCSSSelector = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByUrl = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -73,34 +75,36 @@ private static String prependTitleIfRequired(String icon, String title) {
return icon;
}

public static String getIonicon(String name, String title) {
timja marked this conversation as resolved.
Show resolved Hide resolved
if (IONICONS.containsKey(name)) {
String icon = IONICONS.get(name);
return prependTitleIfRequired(icon, title);
// for Jelly
@Restricted(NoExternalUse.class)
public static String getSymbol(String name, String title) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code I'm pretty sure this won't work for plugins wanting to add their own symbol.

Probably okay for this PR but should be addressed in a follow up

if (SYMBOLS.containsKey(name)) {
String symbol = SYMBOLS.get(name);
return prependTitleIfRequired(symbol, title);
}

// Load icon if it exists
InputStream inputStream = IconSet.class.getResourceAsStream("/images/ionicons/" + name + ".svg");
String ionicon = null;
// Load symbol if it exists
InputStream inputStream = IconSet.class.getResourceAsStream("/images/symbols/" + name + ".svg");
String symbol = null;

try {
if (inputStream != null) {
ionicon = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
symbol = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
} catch (IOException e) {
// ignored
}
if (ionicon == null) {
ionicon = PLACEHOLDER_SVG;
if (symbol == null) {
symbol = PLACEHOLDER_SVG;
}

ionicon = ionicon.replaceAll("(<title>)[^&]*(</title>)", "$1$2");
ionicon = ionicon.replaceAll("<svg", "<svg aria-hidden=\"true\"");
ionicon = ionicon.replace("stroke:#000", "stroke:currentColor");
symbol = symbol.replaceAll("(<title>)[^&]*(</title>)", "$1$2");
symbol = symbol.replaceAll("<svg", "<svg aria-hidden=\"true\"");
symbol = symbol.replace("stroke:#000", "stroke:currentColor");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that this replacement does not work for non ionicon icons? I tried with stroke:currentColor in my SVG sidebar links but they still appear in black. What is required to get the correct color automatically? It would be helpful if this could work out-of-the box here as well:

Bildschirmfoto 2022-01-30 um 13 01 56

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly there isn't any support for plugins yet. For adding Symbols to Jenkins, it should just work by adding them to the symbols folder - some SVGs may need a little tweaking (eg manually updating the fill attribute if it hasn't already been set) but for the most part they should just work automatically.


IONICONS.put(name, ionicon);
SYMBOLS.put(name, symbol);

return prependTitleIfRequired(ionicon, title);
return prependTitleIfRequired(symbol, title);
}

public IconSet addIcon(Icon icon) {
Expand Down Expand Up @@ -517,24 +521,11 @@ private static void initializeSVGs() {
images.add("warning");
images.add("document-properties");

Map<String, String> materialIcons = new HashMap<>();
materialIcons.put("help", "svg-sprite-action-symbol.svg#ic_help_24px");

for (Map.Entry<String, String> size : sizes.entrySet()) {
for (String image : images) {
icons.addIcon(new Icon("icon-" + image + " " + size.getKey(),
"svgs/" + image + ".svg", size.getValue()));
}

for (Map.Entry<String, String> imageEntry : materialIcons.entrySet()) {
icons.addIcon(new Icon(
"icon-" + imageEntry.getKey() + " " + size.getKey(),
"material-icons/" + imageEntry.getValue(),
size.getValue(),
IconFormat.EXTERNAL_SVG_SPRITE
)
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ THE SOFTWARE.
value="${request.getParameter('filter')}"
placeholder="${%Search}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</div>
</div>
</l:app-bar>
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/resources/hudson/PluginManager/installed.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ THE SOFTWARE.
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%Search' : '%Filter'}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</div>
</div>
</l:app-bar>
Expand Down Expand Up @@ -180,7 +180,7 @@ THE SOFTWARE.
<form class="jenkins-buttons-row" method="post"
action="${rootURL}/updateCenter/plugin/${p.shortName}/downgrade">
<button class="jenkins-table__button jenkins-table__button--orange" tooltip="${%downgradeTo(p.backupVersion)}">
<l:ionicon name="downgrade-circle-outline"/>
<l:icon src="symbol-downgrade-circle"/>
<span class="jenkins-!-margin-left-1"
style="font-size: 0.75rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 50px;">
${p.backupVersion}
Expand Down Expand Up @@ -226,7 +226,7 @@ THE SOFTWARE.
</j:if>
<form class="jenkins-buttons-row" method="post" action="plugin/${p.shortName}/uninstall">
<button class="jenkins-table__button jenkins-table__button--red uninstall" tooltip="${%Uninstall} ${p.updateInfo.displayName?:p.displayName}">
<l:ionicon name="close-circle-outline"/>
<l:icon src="symbol-close-circle"/>
</button>
</form>
</j:otherwise>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ l.header()
l.side_panel {
l.tasks {
l.task(icon:"icon-up icon-md", href:rootURL+'/', title:_("Back to Dashboard"))
l.task(icon:"icon-gear icon-md", href:"${rootURL}/manage", title:_("Manage Jenkins"))
l.task(icon:"symbol-settings", href:"${rootURL}/manage", title:_("Manage Jenkins"))
if (!app.updateCenter.jobs.isEmpty()) {
l.task(icon:"icon-plugin icon-md", href:"${rootURL}/updateCenter/", title:_("Update Center"))
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/resources/hudson/PluginManager/table.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ THE SOFTWARE.
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%Search' : '%Filter'}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</div>
</div>
</l:app-bar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ THE SOFTWARE.
<l:task href=".." icon="icon-up icon-md" title="${%Back to Loggers}"/>
<l:task href="." icon="icon-notepad icon-md" title="${%Log records}"/>
<l:isAdmin>
<l:task href="configure" icon="icon-gear icon-md" title="${%Configure}"/>
<l:task href="configure" icon="symbol-settings" title="${%Configure}"/>
<l:task href="delete" icon="icon-edit-delete icon-md" title="${%Delete}"/>
</l:isAdmin>
</l:tasks>
</l:side-panel>
</j:jelly>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ THE SOFTWARE.
<l:isAdmin>
<div class="jenkins-table__cell__button-wrapper">
<a href="${rootURL}/${c.url}configure" class="jenkins-table__button">
<l:ionicon name="settings-outline" />
<l:icon src="symbol-settings" />
</a>
</div>
</l:isAdmin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ THE SOFTWARE.
<l:side-panel>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/manage" icon="symbol-settings" title="${%Manage Jenkins}"/>
<l:task href="." icon="icon-clipboard icon-md" title="${%Log Recorders}">
<l:isAdmin>
<l:task href="new" icon="icon-new-package icon-md" title="${%New Log Recorder}"/>
</l:isAdmin>
</l:task>
<l:task href="all" icon="icon-notepad icon-md" title="${%All Log Messages}"/>
<l:task href="levels" icon="icon-gear icon-md" title="${%Log Levels}"/>
<l:task href="levels" icon="symbol-settings" title="${%Log Levels}"/>
</l:tasks>
</l:side-panel>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ THE SOFTWARE.
<l:task contextMenu="false" href="${buildUrl.baseUrl}/" icon="icon-search icon-md" title="${%Status}"/>
<l:task href="${buildUrl.baseUrl}/changes" icon="icon-notepad icon-md" title="${%Changes}"/>
<p:console-link/>
<l:task href="${buildUrl.baseUrl}/configure" icon="icon-notepad icon-md" title="${h.hasPermission(it,it.UPDATE)?'%Edit Build Information':'%View Build Information'}"/>
</j:jelly>
<l:task href="${buildUrl.baseUrl}/configure" icon="symbol-settings" title="${h.hasPermission(it,it.UPDATE)?'%Edit Build Information':'%View Build Information'}"/>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ THE SOFTWARE.
<j:if test="${c.hasPermission(c.EXTENDED_READ)}">
<div class="jenkins-table__cell__button-wrapper">
<a href="${rootURL}/${c.url}configure" class="jenkins-table__button">
<l:ionicon name="settings-outline" />
<l:icon src="symbol-settings" />
</a>
</div>
</j:if>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ THE SOFTWARE.
<j:getStatic var="createPermission" className="hudson.model.Computer" field="CREATE"/>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/manage" icon="symbol-settings" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Manage Jenkins}"/>
<l:task href="new" icon="icon-new-computer icon-md" permission="${createPermission}" title="${%New Node}"/>
<l:task href="${rootURL}/configureClouds" icon="icon-health-40to59 icon-md" permission="${app.SYSTEM_READ}"
title="${app.hasPermission(app.ADMINISTER) ? '%Configure Clouds' : '%View Clouds'}"/>
<l:task href="configure" icon="icon-gear icon-md" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Node Monitoring}"/>
<l:task href="configure" icon="symbol-settings" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Node Monitoring}"/>
</l:tasks>
<t:queue items="${app.queue.items}" />
<t:executors />
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/resources/hudson/model/Label/sidepanel.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ THE SOFTWARE.
<l:task contextMenu="false" href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task contextMenu="false" href="${url}" icon="icon-attribute icon-md" title="${%Overview}"/>
<j:if test="${it.atom}">
<l:task href="${url}/configure" icon="icon-gear icon-md" title="${%Configure}" permission="${app.ADMINISTER}" />
<l:task href="${url}/configure" icon="symbol-settings" title="${%Configure}" permission="${app.ADMINISTER}" />
</j:if>
<l:task href="${url}/load-statistics" icon="icon-monitor icon-md" title="${%Load Statistics}"/>
<st:include page="actions.jelly" />
</l:tasks>
</l:side-panel>
</j:jelly>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ THE SOFTWARE.
<l:side-panel>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/manage" icon="symbol-settings" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/pluginManager/" icon="icon-plugin icon-lg" title="${%Manage Plugins}"/>
</l:tasks>
</l:side-panel>
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/resources/hudson/model/User/sidepanel.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ THE SOFTWARE.
<l:task contextMenu="false" href="${rootURL}/asynchPeople/" icon="icon-up icon-md" title="${%People}"/>
<l:task contextMenu="false" href="${rootURL}/${it.url}/" icon="icon-search icon-md" title="${%Status}"/>
<l:task href="${rootURL}/${it.url}/builds" icon="icon-notepad icon-md" title="${%Builds}"/>
<l:task href="${rootURL}/${it.url}/configure" icon="icon-gear icon-md" permission="${app.ADMINISTER}" title="${%Configure}"/>
<l:task href="${rootURL}/${it.url}/configure" icon="symbol-settings" permission="${app.ADMINISTER}" title="${%Configure}"/>
<t:actions actions="${it.propertyActions}"/>
<t:actions actions="${it.transientActions}"/>
<j:if test="${it.canDelete()}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ THE SOFTWARE.
<l:progressiveRendering handler="${it}" callback="display"/>

<!-- cloned from JS -->
<span id="person-circle-outline" class="default-hidden">
<l:ionicon name="person-circle-outline" />
<span id="person-circle" class="default-hidden">
<l:icon src="symbol-person-circle" />
</span>

<table class="jenkins-table ${iconSize == '16x16' ? 'jenkins-table--small' : iconSize == '24x24' ? 'jenkins-table--medium' : ''} sortable" id="people">
Expand Down
Loading