Web UI Extensions
Hint: you can use source code of the existing plugins as a reference, for example:
Getting Started
The simplest way of adding your own custom tab is to derive from one of the classes:
This will add your tab to the project, build type (build configuration), build or administration page respectively. Here's an example of the Diagnostics admin page:
There are a couple of things to note here:
it is important to call "register" method; ProjectTab, BuildTypeTab and ViewLogTab will do that for you automatically, AdminPage won't, that's why the call is there;
TeamCity might have difficulties with finding your resources (JSP, CSS, JS) if you don't refer to your resources through the PluginDescriptor.
the page above doesn't provide any model to the JSP. If you need one, just override the "fillModel" method.
Here's another example of the project tab:
That's it! Just specify your tab as a Spring bean, and you'll be able to see your tab in TeamCity.
Under the Hood
If you download and take a look at the TeamCity open API sources, you'll notice that all tabs above derive from the . And the only major difference between them all is a they specify in constructor. Here's what they use:
PlaceId.PROJECT_TAB
PlaceId.BUILD_CONF_TAB
PlaceId.BUILD_RESULTS_TAB
PlaceId.ADMIN_SERVER_CONFIGURATION_TAB
Don't get confused by the variety of names, it's a long story. The main thing is there are more than 30 other place ids that you can hook into!
PlaceId.ALL_PAGES_HEADER
PlaceId.AGENT_DETAILS_TAB
PlaceId.LOGIN_PAGE
...
There is a convention that a place id named as a TAB can be used with the SimpleCustomTab. Others cannot, and to use them you will have to deal with low level . But that's pretty much the only change, take a look at the example:
This extension provides a custom HTML (usually a link) near the each file in the modification's list. We use it to add "Open in IDE", "Open in External Change Viewer", etc links. In this particular case the file itself is passed via "changedFile" attribute of the request, but this is different for different extensions.
A couple of useful notes:
isAvailable(HttpServletRequest)
method is called to determine whether page extension content should be shown or not.in case
isAvailable(HttpServletRequest)
is true, thefillModel(Map, HttpServletRequest)
method will always be called and JSP will be rendered in UI. You cannot abort the process afterisAvailable(HttpServletRequest)
is done, that's why it's usually inconvenient to handle POST requests in extensions. Use a custom controller for that (see below).One more case when you might need a custom controller is when you need to process HTTP response manually, e.g. stream a file content.
fillModel(Map, HttpServletRequest)
won't allow you to do that.
Developing a Custom Controller
Sometimes page extensions provide interaction with user and require communication with server. For example, your page extension can show a form with a "Submit" button. In this case in addition to writing your own page extension, you should provide a controller which will process requests from such forms, and use path to this controller in the form action attribute (the path is a part of URL without context path and query string).
Example:
To simplify things your controller can extend our class and implement BaseController.doHandle(HttpServletRequest, HttpServletResponse)
method.
With the custom controller you can provide completely new pages.
Obtaining paths to JSP files
Plugin resources are unpacked to <TeamCity web application>/plugins
directory when server starts. However to construct paths to your JSP or images in Java it is recommended to use . This descriptor can be obtained as any other Spring service.
In JSP files to construct paths to your resources you can use ${teamcityPluginResourcesPath}. This attribute is provided by TeamCity automatically, you can use it like this:
Note: <c:url/> is required to construct correct URL in case if TeamCity is deployed under the non root context.
Classes and interfaces from TeamCity web open API
Class / Interface | Description |
---|---|
A list of page place identifiers / extension points | |
A single page place associated with PlaceId, allows to add / remove extensions | |
Page extension interface | |
Base class for page extensions | |
Custom tab extension interface | |
Maintains a collection of page places and allows to locate PagePlace by PlaceId | |
Maintains a collection of custom controllers, allows to register custom controllers | |
Base class for controllers |