mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-08 01:26:03 +00:00
[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:
8 files changed
+87
-15
No files matched your search
@@ -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("<discarded/>\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")));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in new issue
Block a user