How to show a menu item on all projects
This guide will guide you through showing a menu item with associated URL and view on all projects' side panels, without requiring configuration in the job.
This could be used for example to show information about the job, such as statistics, without needing additional (persistently stored) data.
In this example, we’ll add a link called Statistics that will link to a page that shows some information about the project.
Create the action
The action will be fairly basic: It will expect a reference to an jenkinsdoc:Project[]
as constructor argument, and has getters returning the number of build steps and post-build steps this project has.
package org.jenkinsci.plugins.sample;
import hudson.model.Action;
import hudson.model.Project;
public class SampleAction implements Action {
private Project project;
public SampleAction(Project project) {
this.project = project;
}
public int getBuildStepsCount() {
return project.getBuilders().size();
}
public int getPostBuildStepsCount() {
return project.getPublishersList().size();
}
@Override
public String getIconFileName() {
return "clipboard.png";
}
@Override
public String getDisplayName() {
return "Project Statistics";
}
@Override
public String getUrlName() {
return "stats";
}
}
Similarly, the index.jelly
view we create for it in the resource directory corresponding to this class — src/main/resources/org/jenkinsci/plugins/sample/SampleAction/
— is very basic, just showing the information from the getters:
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout">
<l:layout title="Project Statistics">
<l:main-panel>
<h1>
Project Statistics
</h1>
<ul>
<li>
Build Steps: ${it.buildStepsCount}
</li>
<li>
Post-Build Steps: ${it.postBuildStepsCount}
</li>
</ul>
</l:main-panel>
</l:layout>
</j:jelly>
Add the action to all projects
jenkinsdoc:TransientActionFactory[]
can be used to add any number of actions to a given instance of an jenkinsdoc:Actionable[]
subtype. TransientActionFactory
defines:
-
Which subtype of
Actionable
it applies to -
Which kinds of
Action
it creates
The class will look like this:
@Extension
public class SampleActionFactory extends TransientActionFactory<Project> {
@Override
public Class<Project> type() {
return Project.class; (1)
}
@NonNull
@Override
public Collection<? extends Action> createFor(@NonNull Project project) {
return Collections.singleton(new SampleAction(project)); (2)
}
}
1 | This will only apply to Project instances. |
2 | The factory could create different action depending on the Project , in this case, it is not needed. |
Restrict access
To only show the project information to people who otherwise would be able to obtain it by viewing the job configuration, we can set up the action so the link is only shown to those with the Item.CONFIGURE
permission.[1]
(...)
@Override
public String getIconFileName() {
return this.project.hasPermission(Item.CONFIGURE) ? "clipboard.png" : null; (1)
}
(...)
1 | Returning null is a documented way for getIconFileName to make an action not appear in the side panel. |
This will not prevent direct access via the URL however, so we need make sure to restrict who can access the action.
A reliable way to do this is to implement staplerdoc:org.kohsuke.stapler.StaplerProxy[StaplerProxy]
, an interface intended to allow objects to forward HTTP request processing to another object. By implementing the getTarget()
method and returning this
, the request will continue to be processed by the same object, but we’re able to check user permissions before that happens.
(...)
import org.kohsuke.stapler.StaplerProxy;
public class SampleAction implements Action, StaplerProxy {
(...)
@Override
public Object getTarget() {
this.project.checkPermission(Item.CONFIGURE); (1)
return this;
}
}
1 | This throws an AccessDeniedException if the check fails, resulting in the user seeing an error message (or, if not already logged in, a login screen). |