I am looking to write a jenkins plugin that lets me add a new link that links to a new page of my creating. I have determined how to create the new link by extending RootAction and I can get it to link to a static resource in the webapp directory. My problem is many fold though. I think I need to use a jelly page because of the nature of action I want to do on this new page of mine and even if I could do what I wanted to with static web content the link pops me out of the jenkins interface to a page only containing my static web content.
What I need to know is, how do you go about creating a brand new page? I have been scowering the JavaDoc looking for an interface and the internet for more documentation on how to write plugins. But the internet seems to be sadly lacking on information pertaining to Jenkins plugin development.
What I want to do is be able to click my new link and have it take me to my new page, still with all the Jenkins navigation and such, and on this new page I am going to have a form for performing some actions on files.
Any help or pointers to documentation I have not found would be greatly appreciated.
As you found, the first step is to extend a RootAction or Action, depending on where you want the page to be located: globally or per-project, respectively.
A simple RootAction:
package my.package;
#Extension
public class MyNewPage implements hudson.model.RootAction
{
#Override
public String getDisplayName()
{
return "My Custom Page";
}
#Override
public String getIconFileName()
{
return (Jenkins.RESOURCE_PATH + "/images/48x48/terminal.png").replaceFirst("^/", "");
}
#Override
public String getUrlName()
{
return "my-url"; // the url path
}
}
Next, add an associated jelly file located at src/main/resources/my/package/MyNewPage/index.jelly
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout norefresh="true">
<l:header title="My New Page" />
<l:main-panel>
<h1>Put content here</h1>
</l:main-panel>
</l:layout>
</j:jelly>
See the jenkins jelly tag reference for more tags. Note that the l:layout tag is completely optional, you don't have to use jenkins tags at all and can instead dump static HTML content in the jelly
Those two files will then be used to render requests for http://localhost:8080/jenkins/my-url
Alternatively, for completely dynamic content, use the stapler library built into jenkins to render arbitrary urls. Here is a silly little example that prints back the url:
public void doDynamic(StaplerRequest req, StaplerResponse resp) throws IOException
{
resp.setStatus(418);
resp.getOutputStream().print("I'm a teapot! You requested this path: " + req.getRestOfPath());
}
Just add that method, or any of the stapler methods, to the MyNewPage (or any extension that supports it). It will respond to all urls under http://localhost:8080/jenkins/my-url/*
Some other good references I've found:
How to extend Jenkins job page with new links and icons
The build-failure-analyzer plugin source, notably CauseManagement.java
Related
How can I change favicon of my pages in Vaadin ? I would like to change favicon of my pages but I have no idea where is the place to change it ? Has somebody experience on it ?
First, create a theme directory: /WebContent/VAADIN/themes/mynewtheme
Then, put your custom favicon.ico in this directory. You also need to set theme property in your application :
public class MyNewApplication extends Application {
#Override
public void init() {
...
...
setTheme("mynewtheme");
}
}
Here is a more detailed version of the similar Answer posted by Greg Ballot. My Answer here relates to Vaadin 7, current as of 7.5.3.
Custom Theme
In Vaadin 7.5, you can drop your favicon graphics image file into your own custom theme. If using the Vaadin plugin for various IDEs (NetBeans, Eclipse) or the Maven archetypes, a custom theme named mytheme should have already been created for you. Drop your image file into that mytheme folder.
The main part of your Vaadin 7 app, your subclass of UI, must specify that it uses your custom theme. Again, if using the IDE plugins and/or Maven archetype, this should have already been configured for you. The easiest way is an Java Annotation on the UI subclass.
#Theme ( "mytheme" ) // Tell Vaadin to apply your custom theme, usually a subclass of the Valo or Reindeer theme.
#Title ( "PowerWrangler" ) // Statically specify the title to appear in web browser window/tab.
#SuppressWarnings ( "serial" ) // If not serializing such as "sticky sessions" and such, disable compiler warnings about serialization.
#Push ( PushMode.AUTOMATIC ) // If using Push technology.
public class MyVaadinUI extends UI
{
…
Favicon Usage/Behavior Not Standard
Remember that favicon behavior is not standardized. Favicons developed haphazardly, mostly out of a sense of fun. The exact behavior depends on the particular browser and particular server. Other than the particular folder location, none of this is special to Vaadin.
Image File Formats
Originally the ICO file format was used exclusively. Since then most browsers have evolved to accept any of several formats including JPEG, TIFF, and PNG.
Image Size/Resolution
Originally favicons were intended to be very small bitmap icons. Some browsers have made various uses of the favicon in situations where you may want to provide a higher-resolution image. But remember that smaller files load faster without keeping your users waiting.
Favicon File Name
Some browsers or servers may handle other file names or name extensions, but I've found it easiest to name my file exactly favicon.ico -- even if using a different format! I usually use a PNG file but name it with the .ico extension. While I cannot guarantee this practice works one every server and browser, I’ve not encountered any problem.
Existing Favicon File
Recent versions of Vaadin have included a Vaadin-related icon in a favicon.ico file in a configured project. So you must replace that file with your own. In Vaadin 7.5.3 the file contains four sizes, the largest looking like this:
Older versions did not add a file, so you drop in your own.
IDE Screen Shots
Here are a pair of screen shots. One is the project (logical) view in NetBeans 8, while the other is a files (physical) view.
In case of custom icon name (Vaadin 7):
public class MyServlet extends VaadinServlet implements SessionInitListener {
#Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(this);
}
#Override
public void sessionInit(SessionInitEvent event) throws ServiceException {
event.getSession().addBootstrapListener(new BootstrapListener() {
#Override
public void modifyBootstrapPage(BootstrapPageResponse response) {
response.getDocument().head()
.getElementsByAttributeValue("rel", "shortcut icon")
.attr("href", "./VAADIN/themes/mynewtheme/custom.ico");
response.getDocument().head()
.getElementsByAttributeValue("rel", "icon")
.attr("href", "./VAADIN/themes/mynewtheme/custom.ico");
}
#Override
public void modifyBootstrapFragment(BootstrapFragmentResponse response) {
}
});
}
}
EDIT
It is better to use the BootstrapListener as a static nested class: link
Vaadin 23.x (plain spring/war application, no springboot!):
Derive an implementation of com.vaadin.flow.component.page.AppShellConfigurator:
#Theme(value = "mytheme")
#PWA(name = "My application", shortName = "MyApp", iconPath = "icons/favicon.ico" )
public class AppShellConfiguratiorImpl implements AppShellConfigurator {
#Override
public void configurePage(AppShellSettings settings) {
settings.addFavIcon("icon", "icons/favicon.ico", "16x16");
}
}
And put your favicon.ico into src\main\webapp\icons (in order that it is encluded in <war-root>/icons/favicon.ico)
A servlet container (3.0 plus, e.g. Tomcat 8.5) will pick up this class automagically and load it.
So I decided to use AjaxDependencySelection Plugin for Grails, and it has proven to be very useful. However, I am trying to implement autoComplete boxes, and it does not seem to be saving the object id when using an Autocompleted selection. Here is my implementation in my gsp
<g:selectPrimary id="template" name="template"
domain='dms.nexusglobal.Template'
searchField='templateName'
collectField='id'
domain2='dms.nexusglobal.Tag'
bindid="template.id"
searchField2='tagName'
collectField2='id'
hidden="hiddenNew"
noSelection="['': 'Please choose Template']"
setId="tag"
value="${documentPartInstance?.template}"/>
<g:selectSecondary id="tag" name="tag"
domain2='dms.nexusglobal.Subtag'
bindid="tag.id"
searchField2='subtagName'
collectField2='id'
autocomp="1"
noSelection="['': 'Please choose Tag']"
setId="subtag"
value="${documentPartInstance?.tag}"/>
<g:autoCompleteSecondary id="subtag" name="subtagId"
domain='dms.nexusglobal.Subtag'
primarybind='tag.id'
hidden='tag'
hidden2='hidden5'
searchField='subtagName'
collectField='id'
value='${documentPartInstance?.subtag}'/>
<input type=hidden id="hidden5" name="subtagId" value="${documentPartInstance?.subtag}"/>
However, everytime I save it, I am presented with this error Column 'subtag_id' cannot be null . Here is my domain class definition for Subtag
class Subtag {
static scaffold = true
String subtagName
static belongsTo = [tag : Tag]
public Subtag()
{
}
public Subtag(String s)
{
subtagName = s
}
static constraints = {
}
String toString(){
subtagName
}
}
Tag hasMany subtags as well
It seems to be creating new Subtag instances when using the autoselect box (as an error shows up saying Could not find matching constructor for:packagename.Subtag(java.lang.String) Although this is a feature I am looking to implement in my application at later stages (being able to create new Subtags on the fly when creating a document Part), right now, all I would like to be able to do is just choose from my already existing subtags.
When I add in a string constructor, it comes back with the error that Column subtag_id cannot be null
I have developed it so will try help you through your issue.
The problem is that you are trying to push a value from selectSecondary and update the elementId of g:autocomplete which is actually a seperate entity.
I will update the plugin with a new method, need to test it out first.. Also take a look at g:selectAutoComplete. Although this method would only work if your secondary was the primary task... so no good in that case either..
hang on and look out for 0.37 release
Released 0.37 documentation on how to do such a thing here: https://github.com/vahidhedayati/ajaxdependancyselection/wiki/from-selection-to-autocomplete---how-to
I'm translating a web application and things are generally going smoothly with wicket:message and properties files. But Wicket always wants to have a component for looking up strings.
How can I translate converters and renderers (i.e. implementations of IConverter and IChoiceRenderer) which don't have access to any Wicket component in their methods?
So far I found one way - Application.get().getResourceSettings().getLocalizer().getString(key, null) - but I have to make the strings "global", i.e. associated with the application class. That's not nice for separation and reuse. How can I do it better?
I think you should invent you own way how to achieve this. Here in my current project we registered our own IStringResourceLoader like this:
IStringResourceLoader stringResourceLoader = new OurOwnResourceLoaderImpl();
Application.get().getResourceSettings().getStringResourceLoaders().add(stringResourceLoader);
Then for example in IChoiceRenderer we just call Application.get().getLocalizer().getString("key", null).
Inside our IStringResourceLoader we are looking for bundles (property files) with some string pattern according our own conventions.
Or you can just register localization bundle (ie. properties file) distributed inside your library's jar in Application#init through org.apache.wicket.resource.loader.BundleStringResourceLoader.
Afaik there is no standard way to do that so it's up to you what path you choose.
Updated:
I found another solution how your library/extension can register it's own localization by itself so you needn't to touch Application#init or create your own IStringResourceLoaders.
There is preregistered string resource loader org.apache.wicket.resource.loader.InitializerStringResourceLoader (see wickets default IResourceSetting implementation ie. ResourceSetting and it's constructor) which uses wicket's Initializer mechanism - see IInitializer javadoc - basically you add wicket.properties file in your jar class root (ie. it is in default/none package) and inside file there is:
initializer=i.am.robot.MyInitilizer
then i.am.robot.MyInitilizer:
public class MyInitializer implements IInitializer {
/**
* #param application
* The application loading the component
*/
void init(Application application) {
// do whatever want
}
/**
* #param application
* The application loading the component
*/
void destroy(Application application) {
}
}
and now you create your localization bundles in same package and same name as IInitializer implementation (in our example MyInitializer)
I think I found another way...
I noticed that IStringResourceLoader also has a method String loadStringResource(Class<?> clazz, String key, Locale locale, String style); (and one more parameter for variation in newer Wicket versions) which does not require a component. clazz is supposed to be a component class, but... it doesn't actually have to be :)
I was able to implement my own class MyLocalizer extends Localizer with a new method
getString(String key, Class<?> cl, IModel<?> model, Locale locale, String defaultValue)
which works in a similar way to
getString(String key, Component component, IModel<?> model, String defaultValue)
but uses the class directly instead of a component. It still uses the same properties cache and resource loaders.
Then I wrote an abstract class MyConverter implements IConverter which has a MyLocalizer getLocalizer() and a few getString methods like the Component class. Basically it does getLocalizer().getString(key, getClass(), model, locale, defaultValue), so the properties can now be attached to the converter class.
Seems to work :)
If I understand your question...
You can use package based properties that means if you put your keys/values into a property file 'package.properties' in a package. Each localized resource of any subpackage under that package returns the value associated to the requested key until you override it in another property file.
The file name is 'package.properties' in Wicket prior to 1.6.x and 'wicket-package.properties' in Wicket 1.6+
See
https://cwiki.apache.org/confluence/display/WICKET/Migration+to+Wicket+6.0#MigrationtoWicket6.0-package.propertiesrenamedtowicket-package.properties
However it works just for componet, outside the componet (when component argument is null), it is possible to use:
WicketApplication.properties (the WebApplication class is WicketApplication.class, this property file is in the same package).
applicationGlobalProperty=My Global Localized Property
wicket-package.properties (package based, place it in the same package as the page)
localText=Localized text: A local component text based on wicket-package.properties
LocalizedPage.html (markup template)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Localized Page</title>
</head>
<body xmlns:wicket="http://wicket.apache.org">
<div>
<div>
<h2>Texts</h2>
<div>
<wicket:message key="localText"/> <br/>
<span wicket:id="localizedLabel"></span>
</div>
</div>
</div>
</body>
</html>
LocalizePage.java (code)
public class LocalizedPage extends WebPage {
private static final long serialVersionUID = 1L;
public LocalizedPage() {
super();
}
#Override
protected void onInitialize() {
super.onInitialize();
add(new Label("localizedLabel", new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
#Override
public String getObject() {
return WicketApplication.get().getResourceSettings().getLocalizer().getString("applicationGlobalProperty", null);
}
}));
}
}
See the full example on https://repo.twinstone.org/projects/WISTF/repos/wicket-examples-6.x/browse
I'm trying to implement the Struts 2 Annotations in my project, but I don't know how.
I added the convention-plugin v 2.1.8.1 to my pom
I modified the web.xml
...
<init-param>
<param-name>actionPackages</param-name>
<param-value>org.apache.struts.helloworld.action</param-value>
</init-param>
...
My Action
package org.apache.struts.helloworld.action;
import org.apache.struts.helloworld.model.MessageStore;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
#Results({
#Result(name="success", location="HelloWorld.jsp")
})
public class HelloWorld extends ActionSupport {
public String execute() throws Exception {
messageStore = new MessageStore() ; return SUCCESS;
}
The jsp page from where I'm trying to use my action.
<body>
<h1>Welcome To Struts 2!</h1>
<p>Hello World</p>
</body>
When I press the link associated to the action helloWorld, but it's sends me to the exactly the same page. So, from index.jsp, it's sends to index.jsp.
The way it should behave: it should send me to HelloWorld.jsp.
I uploaded the project (a very simple HelloWorld app) to FileFront, maybe someone sees where is the problem. http://www.filefront.com/16364385/Hello_World.zip
Convention uses a different convention to convert CamelCaseAction names to url's and jsp's names. If you are using Convention's defaults, I believe you should have used the following names:
ActionClass: HelloWorldAction.java
JSP: hello-world.jsp
Action: hello-world
Also, notice that by default convention will look for your JSPs on WEB-INF/content . The documentation is a bit shallow, you have to understand by the examples, but you can consult all default values there: http://struts.apache.org/2.x/docs/convention-plugin.html
I havent used Struts2 with annotations (which Struts2 version? are you following some tutorial or doc?) . But should not that location attribute (in the Result annotation) be instead value ?
What do the logs say? Have you tried using /HelloWorld.jsp for "success". I think that struts framework does not find the resource and is loading the same page.
When you use /HelloWorld.jsp, hopefully you will see the result page.
I have my target language in Session["lang"], which is either "en" or "it". I have added this to the Site.master:
<script runat="server">
void Page_Load(object sender, EventArgs e) {
string lang = Session["lang"].ToString();
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(lang);
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.CreateSpecificCulture(lang);
}
</script>
Then I'd like to invoke a resource string like this:
<asp:Label ID="Label1" runat="server" Text="<%$ Resources:Global, test %>"></asp:Label>
I have two files in the App_GlobalResources, named Global.resx and Global.en.resx.
The problems is that no matter what is in the lang variable, I always get the results from the main Global.resx, and I never get the english version from Global.en.resx
I am doing this wrong entirely??
I tried putting the System.Threading... part in the Application_PreRequestHandlerExecute method in Global.asax.cs but the result was the same.
Thanks
PS: I am asking about a way to make this work in a simple way. If I was to use the complicate way, I'd go with this: http://helios.ca/2009/05/27/aspnet-mvc-and-localization/
i had the same dilema(how to implement localization) in my asp.net mvc app.
I followed the instructions posted here and it works like a charm.
So i created a folder named Localization under Content and then i create Resources resx files for each language i want to translate. Keep in mind that there is a convention for the resx file names. ie
Resources.resx is the default fall back for everything.
Resources.en-GB.resx is for english GB
Resources.en-US.resx is for english US
etc.
Just make sure you follow the instructions posted in the link to embed and make the Resources available in all places in your app (views, controllers etc)
Edit:
I want to add that i ommited this line from web.config since i wanted to manually set the local from my app.
<globalization uiCulture="auto" culture="auto"/>
Instead i have created the following class:
public class SmartController : Controller
{
public SmartController()
{
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
}
}
All controllers inherit from this class.
Since this is an administrative set of the locale i have to set it from my apps settings. You could read it from Cookies and set it, or otherwise. This is imo the simplest solution for localization that i have encountered so far.
Once implemented you can refer to any string you add by the following simple line of code, no extra code needed.
<%= Resources.Strings.TranslatedTerm %>
I bet this one is a duplicate.
Anyway - all you need is here (assuming that you are using webforms viewengine (might work with others too, haven't investigated)).
Oh well... here goes my 'summary':
Helpers are just a part. You need to do some modifications with your default view engine too . On createview/createpartialview it should return localizationwebformview which adds a path key to viewdata which is used by htmlhelper to find resourceexpressionsfields and pass them to localizationhelpers class which retrieves desired value.
Little bonus=>
This might be handy if you don't want to recreate resource folders for view subfolders
(in case you modify viewengine.view/partialviewlocationformats):
private static string ReformatVirtualPath(string virtualPath)
{
//This allows NOT to duplicate App_localResources directory
// ~/Views/Shared/Partial/Some/BulltihS/_View.ascx
// turns into =>
// ~/Views/Shared/_View.ascx
var start = #"(~(/?\w*/?){2})";
var end = #"(\w*.as(c|p)x)";
start = Regex.Match(virtualPath, start).Value;
end = Regex.Match(virtualPath, end).Value;
return start + end;
}
usage:
internal static ResourceExpressionFields GetResourceFields
(string expression, string virtualPath)
{
virtualPath = ReformatVirtualPath(virtualPath);
var context = new ExpressionBuilderContext(virtualPath);
var builder = new ResourceExpressionBuilder();
return (ResourceExpressionFields)
builder.ParseExpression(expression, typeof(string), context);
}
EDIT:
but it might be a good idea to avoid App_GlobalResources and App_LocalResources as K. Scott Allen suggests (check Konstantinos answer).