[JENKINS-68137] Enable custom icon symbols for plugins (#6395)

Co-authored-by: Ullrich Hafner <ullrich.hafner@gmail.com>
Co-authored-by: Tim Jacomb <timjacomb1+github@gmail.com>
This commit is contained in:
authored and GitHub committed 2022-03-28 21:52:43 -07:00
1 parent 2716b07124
commit 04887f08ce
8 files changed
+87 -15

No files matched your search

+20
View File
@@ -2320,6 +2320,26 @@ public class Functions {
return iconMetadata;
}
@Restricted(NoExternalUse.class)
public static String extractPluginNameFromIconSrc(String iconSrc) {
if (iconSrc == null) {
return "";
}
if (!iconSrc.contains("plugin-")) {
return "";
}
String[] arr = iconSrc.split(" ");
for (String element : arr) {
if (element.startsWith("plugin-")) {
return element.replace("plugin-", "");
}
}
return "";
}
@Restricted(NoExternalUse.class)
public static String tryGetIconPath(String iconGuess, JellyContext context) {
if (iconGuess == null) {
@@ -24,6 +24,8 @@
package org.jenkins.ui.icon;
import hudson.PluginWrapper;
import hudson.Util;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@@ -32,6 +34,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.lang.StringUtils;
@@ -47,7 +50,8 @@ public class IconSet {
public static final IconSet icons = new IconSet();
private static final Map<String, String> SYMBOLS = new ConcurrentHashMap<>();
// keyed by plugin name / core, and then symbol name returning the SVG as a string
private static final Map<String, Map<String, String>> SYMBOLS = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByCSSSelector = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByUrl = new ConcurrentHashMap<>();
@@ -77,11 +81,14 @@ public class IconSet {
// for Jelly
@Restricted(NoExternalUse.class)
public static String getSymbol(String name, String title, String tooltip, String classes) {
public static String getSymbol(String name, String title, String tooltip, String classes, String pluginName) {
String translatedName = cleanName(name);
if (SYMBOLS.containsKey(translatedName)) {
String symbol = SYMBOLS.get(translatedName);
String identifier = Util.fixEmpty(pluginName) == null ? "core" : pluginName;
Map<String, String> symbolsForLookup = SYMBOLS.computeIfAbsent(identifier, (key) -> new ConcurrentHashMap<>());
if (symbolsForLookup.containsKey(translatedName)) {
String symbol = symbolsForLookup.get(translatedName);
symbol = symbol.replaceAll("(class=\")[^&]*?(\")", "$1$2");
symbol = symbol.replaceAll("(tooltip=\")[^&]*?(\")", "");
if (!tooltip.isEmpty()) {
@@ -92,7 +99,7 @@ public class IconSet {
}
// Load symbol if it exists
InputStream inputStream = IconSet.class.getResourceAsStream("/images/symbols/" + translatedName + ".svg");
InputStream inputStream = getClassLoader(identifier).getResourceAsStream("images/symbols/" + translatedName + ".svg");
String symbol = null;
try {
@@ -116,11 +123,25 @@ public class IconSet {
symbol = symbol.replaceAll("<svg", "<svg class=\"" + classes + "\"");
symbol = symbol.replace("stroke:#000", "stroke:currentColor");
SYMBOLS.put(translatedName, symbol);
symbolsForLookup.put(translatedName, symbol);
SYMBOLS.put(identifier, symbolsForLookup);
return prependTitleIfRequired(symbol, title);
}
private static ClassLoader getClassLoader(String pluginName) {
if (pluginName.equals("core")) {
return IconSet.class.getClassLoader();
}
PluginWrapper plugin = Jenkins.get().getPluginManager().getPlugin(pluginName);
if (plugin != null) {
return plugin.classLoader;
}
return IconSet.class.getClassLoader();
}
public IconSet addIcon(Icon icon) {
iconsByCSSSelector.put(icon.getNormalizedSelector(), icon);
if (icon.getUrl() != null) {
@@ -37,7 +37,7 @@ THE SOFTWARE.
the action object itself is available in the 'action' variable
-->
<st:include page="action.jelly" from="${action}" optional="true">
<j:set var="icon" value="${action.iconClassName != null ? action.iconClassName + ' icon-md' : action.iconFileName}"/>
<j:set var="icon" value="${action.iconClassName != null ? action.iconClassName : action.iconFileName}"/>
<j:if test="${icon!=null}">
<j:choose>
<j:when test="${h.getActionUrl(it.url,action).contains('pollingLog')}">
@@ -58,7 +58,7 @@ THE SOFTWARE.
<j:choose>
<j:when test="${iconClassName != null}">
<l:icon class="${iconClassName} icon-lg" alt="${it.description}" tooltip="${it.description}" />
<l:icon src="${iconClassName}" class="${iconClassName}" alt="${it.description}" tooltip="${it.description}" />
</j:when>
<j:otherwise>
<!-- "it" is not a hudson.model.BallColor. Let's try figure out the icon from its URL. -->
@@ -69,6 +69,7 @@ THE SOFTWARE.
<j:arg value="${alt ?: ''}" />
<j:arg value="${attrs.tooltip ?: ''}" />
<j:arg value="${iconMetadata.classSpec ?: ''}" />
<j:arg value="${h.extractPluginNameFromIconSrc(iconSrc)}" />
</j:invokeStatic>
<j:out value="${icon}" />
</j:when>
@@ -25,6 +25,8 @@
package hudson;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -369,6 +371,34 @@ public class FunctionsTest {
assertEquals("&lt;discarded/&gt;\n", Functions.printLogRecordHtml(lr, null)[3]);
}
@Test
public void extractPluginNameFromIconSrcHandlesNull() {
String result = Functions.extractPluginNameFromIconSrc(null);
assertThat(result, is(emptyString()));
}
@Test
public void extractPluginNameFromIconSrcHandlesEmptyString() {
String result = Functions.extractPluginNameFromIconSrc("");
assertThat(result, is(emptyString()));
}
@Test
public void extractPluginNameFromIconSrcOnlyReturnsPluginFromStart() {
String result = Functions.extractPluginNameFromIconSrc("symbol-plugin-mailer plugin-design-library");
assertThat(result, is(equalTo("design-library")));
}
@Test
public void extractPluginNameFromIconSrcExtractsPlugin() {
String result = Functions.extractPluginNameFromIconSrc("symbol-padlock plugin-design-library");
assertThat(result, is(equalTo("design-library")));
}
@Issue("JDK-6507809")
@Test public void printThrowable() {
// Basics: a single exception. No change.
@@ -21,7 +21,7 @@ public class IconSetTest {
@Test
void getSymbol() {
String symbol = IconSet.getSymbol("download", "Title", "Tooltip", "class1 class2");
String symbol = IconSet.getSymbol("download", "Title", "Tooltip", "class1 class2", "");
assertThat(symbol, containsString("<span class=\"jenkins-visually-hidden\">Title</span>"));
assertThat(symbol, containsString("tooltip=\"Tooltip\""));
@@ -30,8 +30,8 @@ public class IconSetTest {
@Test
void getSymbol_cachedSymbolDoesntReturnAttributes() {
IconSet.getSymbol("download", "Title", "Tooltip", "class1 class2");
String symbol = IconSet.getSymbol("download", "", "", "");
IconSet.getSymbol("download", "Title", "Tooltip", "class1 class2", "");
String symbol = IconSet.getSymbol("download", "", "", "", "");
assertThat(symbol, not(containsString("<span class=\"jenkins-visually-hidden\">Title</span>")));
assertThat(symbol, not(containsString("tooltip=\"Tooltip\"")));
@@ -40,8 +40,8 @@ public class IconSetTest {
@Test
void getSymbol_cachedSymbolAllowsSettingAllAttributes() {
IconSet.getSymbol("download", "Title", "Tooltip", "class1 class2");
String symbol = IconSet.getSymbol("download", "Title2", "Tooltip2", "class3 class4");
IconSet.getSymbol("download", "Title", "Tooltip", "class1 class2", "");
String symbol = IconSet.getSymbol("download", "Title2", "Tooltip2", "class3 class4", "");
assertThat(symbol, not(containsString("<span class=\"jenkins-visually-hidden\">Title</span>")));
assertThat(symbol, not(containsString("tooltip=\"Tooltip\"")));
@@ -57,7 +57,7 @@ public class IconSetTest {
*/
@Test
void getSymbol_notSettingTooltipDoesntAddTooltipAttribute() {
String symbol = IconSet.getSymbol("download", "Title", "", "class1 class2");
String symbol = IconSet.getSymbol("download", "Title", "", "class1 class2", "");
assertThat(symbol, not(containsString("tooltip")));
}
+1 -1
View File
@@ -94,7 +94,7 @@ public class IconTest {
DomElement ballColorAborted = p.getElementById("ballColorAborted");
List<DomElement> ballIcons = StreamSupport.stream(ballColorAborted.getChildElements().spliterator(), false).collect(Collectors.toList());
assertIconToSvgIconOkay(ballIcons.get(0).getFirstElementChild(), "icon-aborted icon-lg");
assertIconToSvgIconOkay(ballIcons.get(0).getFirstElementChild(), "icon-aborted");
DomElement statusIcons = p.getElementById("statusIcons");
List<DomElement> statusIconsList = StreamSupport.stream(statusIcons.getChildElements().spliterator(), false).collect(Collectors.toList());