diff --git a/changelog.html b/changelog.html index 4b7d94e465..0cfb84d2ec 100644 --- a/changelog.html +++ b/changelog.html @@ -58,6 +58,9 @@ Upcoming changes
  • Pass full list of all possible jobs to ViewJobFilter when recurse option is set (issue 20143) +
  • + Added API allowing plugins to hide entries from the context menu even while they appear in the sidepanel. + (issue 19173)
  • diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 4dae0aebcc..33014eb652 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -153,6 +153,7 @@ import com.google.common.base.Predicates; import java.util.concurrent.atomic.AtomicLong; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Utility functions used in views. @@ -1848,4 +1849,14 @@ public class Functions { rsp.setHeader("X-Jenkins-CLI-Host", TcpSlaveAgentListener.CLI_HOST_NAME); } } + + @Restricted(NoExternalUse.class) // for actions.jelly and ContextMenu.add + public static boolean isContextMenuVisible(Action a) { + if (a instanceof ModelObjectWithContextMenu.ContextMenuVisibility) { + return ((ModelObjectWithContextMenu.ContextMenuVisibility) a).isVisible(); + } else { + return true; + } + } + } diff --git a/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java b/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java index 6e04fb4b43..0e1e315b26 100644 --- a/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java +++ b/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java @@ -6,7 +6,6 @@ import hudson.model.Action; import hudson.model.Actionable; import hudson.model.BallColor; import hudson.model.Computer; -import hudson.model.Item; import hudson.model.Job; import hudson.model.ModelObject; import hudson.model.Node; @@ -58,7 +57,7 @@ public interface ModelObjectWithContextMenu extends ModelObject { /** * Data object that represents the context menu. - * + * * Via {@link HttpResponse}, this class is capable of converting itself to JSON that <l:breadcrumb/> understands. */ @ExportedBean @@ -83,8 +82,14 @@ public interface ModelObjectWithContextMenu extends ModelObject { add(a); return this; } - + + /** + * @see ContextMenuVisibility + */ public ContextMenu add(Action a) { + if (!Functions.isContextMenuVisible(a)) { + return this; + } StaplerRequest req = Stapler.getCurrentRequest(); String text = a.getDisplayName(); String base = Functions.getIconFilePath(a); @@ -95,7 +100,7 @@ public interface ModelObjectWithContextMenu extends ModelObject { return add(url,icon,text); } - + public ContextMenu add(String url, String icon, String text) { if (text != null && icon != null && url != null) items.add(new MenuItem(url,icon,text)); @@ -314,4 +319,22 @@ public interface ModelObjectWithContextMenu extends ModelObject { return withDisplayName(o.getDisplayName()); } } + + /** + * Allows an action to decide whether it will be visible in a context menu. + * @since 1.538 + */ + interface ContextMenuVisibility extends Action { + + /** + * Determines whether to show this action right now. + * Can always return false, for an action which should never be in the context menu; + * or could examine {@link Stapler#getCurrentRequest}. + * @return true to display it, false to hide + * @see ContextMenu#add(Action) + */ + boolean isVisible(); + + } + } diff --git a/core/src/main/resources/lib/hudson/actions.jelly b/core/src/main/resources/lib/hudson/actions.jelly index d1fb6b30d1..41f36b1e56 100644 --- a/core/src/main/resources/lib/hudson/actions.jelly +++ b/core/src/main/resources/lib/hudson/actions.jelly @@ -39,7 +39,7 @@ THE SOFTWARE. + href="${h.getActionUrl(it.url,action)}" contextMenu="${h.isContextMenuVisible(action)}"/> diff --git a/test/src/test/java/jenkins/model/ContextMenuTest.java b/test/src/test/java/jenkins/model/ContextMenuTest.java new file mode 100644 index 0000000000..2efd6e91f3 --- /dev/null +++ b/test/src/test/java/jenkins/model/ContextMenuTest.java @@ -0,0 +1,102 @@ +/* + * The MIT License + * + * Copyright 2013 Jesse Glick. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package jenkins.model; + +import hudson.model.AbstractProject; +import hudson.model.Action; +import hudson.model.FreeStyleProject; +import hudson.model.TransientProjectActionFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.Callable; +import static jenkins.model.ModelObjectWithContextMenu.*; +import static org.junit.Assert.assertEquals; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Bug; +import org.jvnet.hudson.test.For; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.Stapler; + +@For(ContextMenu.class) +public class ContextMenuTest { + + @Rule public JenkinsRule j = new JenkinsRule(); + + @Bug(19173) + @Test public void contextMenuVisibility() throws Exception { + final FreeStyleProject p = j.createFreeStyleProject("p"); + Callable doContextMenu = new Callable() { + @Override public ContextMenu call() throws Exception { + return p.doContextMenu(Stapler.getCurrentRequest(), Stapler.getCurrentResponse()); + } + }; + ActionFactory f = j.jenkins.getExtensionList(TransientProjectActionFactory.class).get(ActionFactory.class); + f.visible = true; + ContextMenu menu = j.executeOnServer(doContextMenu); + Map parsed = parse(menu); + assertEquals(parsed.toString(), "Hello", parsed.get("testing")); + f.visible = false; + menu = j.executeOnServer(doContextMenu); + parsed = parse(menu); + assertEquals(parsed.toString(), null, parsed.get("testing")); + } + + @TestExtension public static class ActionFactory extends TransientProjectActionFactory { + + boolean visible; + + @SuppressWarnings("rawtypes") + @Override public Collection createFor(AbstractProject target) { + return Collections.singleton(new ContextMenuVisibility() { + @Override public boolean isVisible() { + return visible; + } + @Override public String getIconFileName() { + return "whatever"; + } + @Override public String getDisplayName() { + return "Hello"; + } + @Override public String getUrlName() { + return "testing"; + } + }); + } + + } + + private static Map parse(ContextMenu menu) { + Map r = new TreeMap(); + for (MenuItem mi : menu.items) { + r.put(mi.url.replaceFirst("^.*/(.)", "$1"), mi.displayName); + } + return r; + } + +}