Have any questions?
+44 1234 567 890
Chapter 9. Extending the Client
9. Extending the Client
In this chapter we consider various ways to extend the client side of ReportServer. Some of these extensions will be configured via configuration files and which allow, for example, to add further information tabs to the TeamView or add further links to the module bar. Other extensions need scripting. These include adding additional report exporters, displaying messages on login, or adding custom information to the status bar.
9.1. UrlView - Incorporating Websites and More
Via the configuration file /etc/ui/urlview.cf you can add links and tabs to various locations within ReportServer. For example, you can add a new link to the module bar and specify to which URL it should point. Additionally you can restrict what you want to add to certain users or groups. A typical configuration file could like
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<adminviews>
<view>
<!-- View Configuration -->
</view>
</adminviews>
<objectinfo>
<view>
<!-- View Configuration -->
</view>
<view>
<!-- View Configuration -->
</view>
</objectinfo>
<module>
<!-- View Configuration -->
</module>
</configuration>
The three scopes (adminviews, objectinfo and module) allow you to add additional information tabs to objects in the administration module (for example, add an additional tab to a user object displaying information on that particular user), additional tabs to objects in the teamspace and within module you can add additional links to the module bar. The documentation report within teamspaces is by default, for example, integrated via the an objectinfo view configuration.
View configurations all share the following configuration
<view>
<restrictTo><!-- can be used to restrict this to specific users/groups--></restrictTo>
<name>The display name</name>
<url>http://someUrl/</url>
</view>
Via restrictTo you can specify users by username or groups or OUs (organizational units, i.e., folders in the user tree) via IDs to include this view only for users that match one of the restrictions. The name is the name to be displayed on the tab or in the module bar. The URL denotes the URL to be loaded.
Restrictions are specified in the following form
<restrictTo>
<users>username1,username2</users>
<groups>123,234</groups>
<ous>876</ous>
</restrictTo>
For adminviews and objectinfo views you can further restrict the view to specific object types. This is done by adding a "types" tag which takes a comma separated list of fully qualified names of objects for which this view applies. For example the following restriction would restrict the (objectinfo) view to report objects within the teamspace.
<types>net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto</types>
The following types are available for objectinfo views
- {net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.AbstractTsDiskNodeDto} All Objects in a Teamspace.
- {net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskFolderDto} All folders in a Teamspace.
- {net.datenwerke.rs.tsreportarea.client.tsreportarea.dto.TsDiskReportReferenceDto} All reports and variants in a Teamspace.
- {net.datenwerke.rs.scheduleasfile.client.scheduleasfile.dto.ExecutedReportFileReferenceDto} All "exported reports" which were, for example, created by the scheduler.
The following types are available for adminviews
Prefix: net.datenwerke.rs.core.client.reportmanager.dto.reports.
Type | Description |
AbstractReportManagerNodeDto | All objects in the report management tree. |
ReportDto | reports |
ReportFolderDto | folders |
Prefix net.datenwerke.security.client.usermanager.dto
Type | Description |
AbstractUserManagerNodeDto | All objects in the user management tree. |
UserDto | users |
GroupDto | groups |
OrganisationalUnitDto | organisational units (folders) |
Prefix net.datenwerke.rs.fileserver.client.fileserver.dto.
Type | Description |
AbstractFileServerNodeDto | All objects in the file server. |
FileServerFolderDto | folders |
FileServerFileDto | files |
Prefix net.datenwerke.rs.core.client.datasourcemanager.dto.
Type | Description |
AbstractDatasourceManagerNodeDto | All objects in datasource management. |
DatasourceFolderDto | folders |
DatasourceDefinitionDto | datasources |
Prefix net.datenwerke.rs.dashboard.client.dashboard.dto.
Type | Description |
AbstractDashboardManagerNodeDto | All objects in the dashboard tree |
DashboardNodeDto | dashboards |
DashboardFolderDto | folders |
You can use replacements within the URL to generate dynamic URLs. The following replacements are available
- ${username} Is replaced by the current user's username.
- ${type} Is replaced by the class name of the corresponding object. This replacement is only available for objectinfo and adminviews.
- ${id} Is replaced by the ID of the corresponding object. This replacement is only available for objectinfo and adminviews.
- ${reportId} Report objects within a team space are always references. Thus, the replacement ${id} maps to the id of the reference rather than to the id of the actual report. In order to use the actual report's id you can use the replacement ${reportId}
9.1.1. ReportServer Specific URLs
Besides specifying a view via a URL ReportServer you can tell ReportServer to display the preview view of a report. For this, you need to define the URL as
where ID should be replaced by the id of the corresponding report or, for example, by the replacement ${reportId} when defining this view for report objects in the TeamSpace.
Note that, as with any configuration file you need to run config reload on the terminal for changes to take effect. Furthermore you need to reload your browser to see effects as the configuration is loaded onto the client on login.
9.2. CommandResult - A Script's Result
We will now get back to ReportServer scripts and have a closer look at the object returned by a script. In theory, you can return any object from a script. For example the script
"Hello World"
returns an object of type String. In order to transport the result onto the client the terminal process wraps such a result in an object of type CommandResult (located in package net.datenwerke.rs.terminal.service.terminal.obj) which the client knows how to handle. If executed in the terminal the terminal will extract the string and display
reportserver$ exec hello.groovy
Hello World
This is done dynamically behind the scenes. But, you can also make this explicit and directly return a CommandResult. For this, you need to change the script to
import net.datenwerke.rs.terminal.service.terminal.obj.CommandResult
new CommandResult("Hello World")
When executing this script the result is identical to before. However, making the CommandResult explicit will give us more control over how the client treats the result. In Chapter 3. we have already seen how to add hyperlinks and anchors to CommandResults. You can furthermore add simple lists, tables and strings to structure the output on the terminal. Consider the following script
import net.datenwerke.rs.terminal.service.terminal.obj.CommandResult
def result = new CommandResult()
result.addResultList(['Hello','World'])
result.addResultLine('----------')
result.addResultList(['Hello','World'])
return result
Executing this script produces the following output
reportserver$ exec hello.groovy
Hello
World
----------
Hello
World
Besides lists and tables, you can also directly return HTML.
import net.datenwerke.rs.terminal.service.terminal.obj.CommandResult
def result = new CommandResult()
result.addResultHtml('<b>Hello</b> World')
return result
These techniques, however, are limited to format the results of a script when displayed on a terminal. The CommandResult object, on the other hand, is not limited to terminal formatting. A CommandResult object can contain one or more objects of type CommandResultExtensions which trigger certain responses on the client. For example, this allows you to display popup messages or execute arbitrary JavaScript code.
9.2.1. Displaying Messages
In order to display popup messages your script needs to add a CommandResultExtension of type CreMessage to the CommandResult object. Following is a simple example.
import net.datenwerke.rs.terminal.service.terminal.obj.*
def result = new CommandResult()
def msg = new CreMessage("Hello World")
result.addExtension(msg)
return result
You can further control the size of the window and also directly add HTML.
import net.datenwerke.rs.terminal.service.terminal.obj.*
def result = new CommandResult()
def msg = new CreMessage()
msg.setTitle("Hello")
msg.setWindowTitle("Hello")
msg.setWidth(400)
msg.setHeight(300)
msg.setHtml("<b>Hello World</b>")
result.addExtension(msg)
return result
9.2.2. Executing Custom JavaScript
The CreJavaScript CommandResultObject allows to specify custom JavaScript which is executed on the client.
import net.datenwerke.rs.terminal.service.terminal.obj.*
def result = new CommandResult()
def script = """
alert("Hello World");
"""
result.addExtension(new CreJavaScript(script))
return result
9.3. ClientExtensionService
So far we have seen how to customize the output within the terminal, how to display messages in popups or execute custom JavaScript. In this section we will look at the so-called ClientExtensionService that can be accessed via the GLOBALS object. The ClientExtensionService offers methods to insert entries to various toolbars and context menus. Each new entry comes with a callback, that is, a ReportServer script that is executed when the specified entry is activated. Depending on where you added the entry your script is provided with additional information. For example, when adding an entry to the context menu in the report management view in the admin module, your script will get the id of the corresponding report object as an argument.
One thing to keep in mind when using the ClientExtensionService is that all effects are only valid as long as the current page is not refreshed. In order to install the changes permanently you should thus add the script into the onlogin.d folder.
The ClientExtensionService currently offers the following methods.
addStatusBarLabel() | Allows to display info texts in the status bar. |
addMenuEntry() | Allows to add entries to certain context menus. |
addToolbarEntry() | Allows to add buttons to certain toolbars. |
addReportExportOutputFormat() | Allows to create custom exporters. We discuss this in detail in Chapter 12. |
9.3.1. Adding Information to the Status Bar
The first method (addStatusBarLabel) allows you to add simple info texts to the status bar. The concept is best explained by a small example.
def service = GLOBALS.services['clientExtensionService']
service.addStatusBarLabel("All is fine")
As you might have guessed, the first line obtains the ClientExtensionService from the GLOBALs object. All that is left to do is to set a status update. Besides just adding text you can also call
addStatusBarLabel("All is fine", "path to some icon", true)
where the second parameter should point to an icon and the third parameter controls whether the object is added to the left or to the right (default).
You can of course access all server objects and methods within this script. E.g., the following executes a given dynamic list and prints the report name and its number of records into the status bar.
import net.datenwerke.rs.base.service.reportengines.table.entities.*
import net.datenwerke.rs.base.service.reportengines.table.*
import net.datenwerke.rs.core.service.reportmanager.*
import net.datenwerke.rs.base.service.reportengines.table.entities.TableReportVariant
import net.datenwerke.rs.core.service.reportmanager.ReportService
import net.datenwerke.rs.core.service.reportmanager.ReportExecutorService
import net.datenwerke.rs.base.service.reportengines.table.output.object.RSTableModel
import net.datenwerke.rs.core.service.reportmanager.engine.config.ReportExecutionConfig
ReportService reportService = GLOBALS.getRsService(ReportService.class)
ReportExecutorService reportExec = GLOBALS.getRsService(ReportExecutorService.class)
TableReportVariant report = reportService.getReportById(2078898)
String reportName = report.getName()
RSTableModel reportCompiled = (RSTableModel) reportExec.execute(report, "RS_TABLE", ReportExecutionConfig.EMPTY_CONFIG)
int rowCount = reportCompiled.getRowCount()
/* prepare output */
def ces = GLOBALS.services.clientExtensionService
ces.addStatusBarLabel("Info: ${reportName} - ${rowCount} rows")
9.3.2. Adding Context Menu Entries
The ClientExtensionService allows you to add menu entries to the following context menus
Module | Description | Menu Name |
Datasource Manager | The datasource manager within the admin module | datasource:admin:tree:menu |
Report Manager | The report manager within the admin module | reportmanager:admin:tree:menu |
File Server | The file server within the admin module | fileserver:admin:tree:menu |
Dashboard Library | The dashboard library within the admin module | dashboard:admin:tree:menu |
User Manager | The user manager within the admin module | usermanager:admin:tree:menu |
To add an entry to a specific menu you need to access the menu by name (we give the menu names in the above table, for example, usermanager:admin:tree:menu corresponds to the context menu within the user manager tree).
The simplest way to add a menu entry is to simply call the method addMenuEntry() from the ClientExtensionService:
public void addMenuEntry(String menuName, String entryName, String scriptLocation, String configArgument)
The method takes the menu name, a name for the entry, a location of the script that is to be called when the entry is activated, and an argument given to the script. In the following we want to add a menu to the user tree. We call our script that generates the entry adddisplayinfoentry.groovy.
def service = GLOBALS.services['clientExtensionService']
service.addMenuEntry("usermanager:admin:tree:menu", "display info", "fileserver/bin/extensions/menu/displayuserinfo.groovy", "an argument")
display info
If you activate the entry you will see an error message, since we haven't yet created a script at location
fileserver/bin/extensions/menu/displayuserinfo.groovy
We are now going to create this file and use the above and display a simple message outputting the object's id.
import net.datenwerke.rs.terminal.service.terminal.obj.*
def result = new CommandResult()
def msg = new CreMessage("ID: " + context['id'] + ", args" + args)
result.addExtension(msg)
return result
id | The object's id. |
classname | The object's classname. |
path | The object's path. |
Sometimes it might be inconvenient to add the menu entry to every object in the tree. That is, maybe we want to add an entry only to user objects, but not to groups or organizational units. To have full control over how the menu item is created you can directly create an object of type AddMenuEntryExtension located in package net.datenwerke.rs.scripting.service.scripting.extensions. To better control when the entry is displayed, use DisplayConditions (located in the same package).
import net.datenwerke.rs.scripting.service.scripting.extensions.*
def service = GLOBALS.services['clientExtensionService']
def entry = new AddMenuEntryExtension()
entry.setMenuName("usermanager:admin:tree:menu")
entry.setLabel("display info")
entry.setScriptLocation("fileserver/bin/extensions/menu/displayuserinfo.groovy")
def cond = new DisplayCondition("classname", "net.datenwerke.security.client.usermanager.dto.UserDto")
entry.addDisplayCondition(cond)
service.addMenuEntry(entry)
Note that the for menus you can currently only compare classnames. Also note that always the base class of the DTO is used, that is, instead of UserDtoDec you need to use UserDto.
9.3.3. Adding Toolbar Entries
Besides adding entries to context menus, you can add buttons to various toolbars, including all toolbars within the various admin modules. The mechanism is similar to the mechanism we described above. The following toolbars support the addition of custom buttons:
Module | Description | Menu Name |
Datasource Manager | The datasource manager within the admin module | datasource:admin:view:toolbar |
Report Manager | The report manager within the admin module | reportmanager:admin:view:toolbar |
File Server | The file server within the admin module | fileserver:admin:view:toolbar |
Dashboard Library | The dashboard library within the admin module | dashboard:admin:view:toolbar |
User Manager | The user manager within the admin module | usermanager:admin:view:toolbar |
Report Executor | The report executor module. This includes the report execution by URL. | reportexecutor:main:toolbar |
Scheduler Job List | The scheduler job list module. | schedulerlist:main:toolbar |
Similar as above you can either use helper methods within the ClientExtensionService such as
public void addToolbarEntry(String toolbarName, String entryName, String entryIcon, String scriptLocation, String arguments){
or alternatively create an object of type AddToolbarEntryExtension, also located in the package net.datenwerke.rs.scripting.service.scripting.extensions.
import net.datenwerke.rs.scripting.service.scripting.extensions.*
def service = GLOBALS.services['clientExtensionService']
def entry = new AddToolbarEntryExtension()
entry.setToolbarName("usermanager:admin:view:toolbar")
entry.setLabel("display info")
entry.setIcon("path/to/icon")
entry.setScriptLocation("fileserver/bin/extensions/menu/displayuserinfo.groovy")
def cond = new DisplayCondition("classname", "net.datenwerke.security.client.usermanager.dto.decorator.UserDtoDec")
entry.addDisplayCondition(cond)
service.addToolbarEntry(entry)
Note that with toolbar buttons you have more control over when buttons are to be displayed, as you can specify the exact type, that is, TableReportVariantDto instead of ReportVariantDto. This, for example, allows you to add a custom button only to dynamic lists, but not to jasper reports.
9.3.4. Future Additions
The possibilities offered by the ClientExtensionService are still rudimentary. We plan to extend these in future versions and are keen to hear your thoughts on what you would like to be able to do and what you are currently missing.