TeamCity
 
You are viewing the documentation for an earlier version of TeamCity.

Web UI Extensions

Last modified: 20 April 2023

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, the fillModel(Map, HttpServletRequest) method will always be called and JSP will be rendered in UI. You cannot abort the process after isAvailable(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