Where Velocity Runs in Polarion
Apache Velocity is the template language Polarion evaluates on the server when it renders reporting pages. A Velocity script reads data through Polarion's Java API, mixes it with HTML, and returns markup that the browser displays. Nothing in the script runs in the reader's browser, so everything it touches is server-side data that the current session is allowed to read.
You will meet Velocity in several places:
- LiveReport pages (Rich Pages). The page type has carried both names across releases. The Script - Block widget accepts Velocity directly: you add the widget to the page, paste the script into its parameter, and apply. This is the quickest way to try a query or build a one-off report.
- Classic Wiki pages and LiveDoc wiki content blocks. Both evaluate Velocity as well, which is why some older projects still carry report logic inside wiki markup.
- Custom widgets. A packaged widget separates its parameter definition from its rendering template, so page authors configure it through a form and never edit script. Reusable reports usually end up here.
One limitation is worth knowing early: Script Block widgets do not run when the page is displayed as a My Polarion page. Build and test the report on a regular project or repository page.
Objects Available to a Velocity Script
Velocity can only call what the host application puts in its context. Polarion places a set of service objects there; the exact set depends on where the script runs and on the Polarion version. The ones you will use most in a page script are:
$trackerService— the main entry point for work items. ItsqueryWorkItems(query, sort)method runs a Lucene query and returns anIPObjectListofIWorkItemobjects.$projectService— project and user lookups.$securityService— information about the current user and their permissions.$page— the page being rendered, useful for finding the page's own project and path.
Some objects appear only in particular locations; for example, a document object is available when the script runs inside a LiveDoc, and test-run or plan objects appear in their respective contexts. Widgets written against the newer rendering API also receive their own objects for reading data and building rendered fields. When in doubt, check the SDK documentation that ships with your server under /polarion/sdk; it matches your installed version.
On an IWorkItem the everyday read methods are getId(), getTitle(), getStatus(), getType() and getAuthor(). getStatus() and getType() return enumeration options, so you call getName() on the result to display the label. Links are read with getLinkedWorkItemsStructsDirect(), which returns one structure per outgoing link; each structure exposes getLinkRole() and getLinkedItem().
Query Work Items and Render an HTML Table
The script below lists the requirements in one project with their ID, title, status and outgoing links. Paste it into a Script - Block widget on a LiveReport page and replace MYPROJECT with your project ID.
## Requirements in one project, with status and outgoing links.
## Replace MYPROJECT with your project ID. Try it on a test server first.
#set($Q = '"')
#set($query = "${Q}project.id${Q}:MYPROJECT AND type:requirement")
#set($items = $trackerService.queryWorkItems($query, "id"))
#set($max = 200)
## Escape user-entered text before writing it into HTML.
#macro(escHtml $raw)
#set($escTmp = "$!raw")
#set($escTmp = $escTmp.replace("&", "&"))
#set($escTmp = $escTmp.replace("<", "<"))
#set($escTmp = $escTmp.replace(">", ">"))
#set($escTmp = $escTmp.replace('"', """))
$escTmp#end
<p>$items.size() work items match.#if($items.size() > $max) Showing the first $max.#end</p>
<table border="1" cellpadding="4" style="border-collapse:collapse">
<tr><th>ID</th><th>Title</th><th>Status</th><th>Outgoing links</th></tr>
#foreach($wi in $items)
#if($velocityCount <= $max)
## Reset per row: #set with a null value leaves the old value in place.
#set($statusName = "")
#if($wi.getStatus())
#set($statusName = $wi.getStatus().getName())
#end
#set($linkText = "")
#foreach($link in $wi.getLinkedWorkItemsStructsDirect())
#if($link.getLinkRole() && $link.getLinkedItem())
#set($roleName = $link.getLinkRole().getName())
#set($targetId = $link.getLinkedItem().getId())
#set($linkText = "$linkText$roleName: $targetId<br/>")
#end
#end
<tr>
<td>$wi.getId()</td>
<td>#escHtml($wi.getTitle())</td>
<td>#escHtml($statusName)</td>
<td>$linkText</td>
</tr>
#end
#end
</table>
Version note. Polarion's APIs and Velocity context vary between releases. Check every method against the SDK documentation for your version and run new scripts on a non-production server before you place them on a page that others rely on.
How the Example Works
The query
The first argument to queryWorkItems is a Lucene query in the same syntax as the Polarion query builder. type:requirement filters by work item type ID, so use the type IDs configured in your project, which can differ from the labels shown in the UI. The project clause uses a small Velocity trick: $Q holds a double quote, which lets the script write the quoted field name "project.id" without fighting Velocity's own string quoting. Without a project clause the query is not limited to the page's project and returns matches from every project the reader can see.
The second argument, "id", is the sort field. Sorting happens in the query, so the list arrives in order and the template never has to compare objects.
The loop
#foreach walks the result list and $velocityCount holds the one-based position of the current item, which the script uses to stop rendering rows after $max. The paragraph above the table reports the full match count, so a reader can see when the list has been cut.
For each row the script resets $statusName and $linkText before filling them. That reset matters, and the pitfalls section explains why.
The links
getLinkedWorkItemsStructsDirect() returns the outgoing links of the work item. The inner loop reads each link's role and target, checks both exist, and appends a line such as verifies: PRJ-123. Back links, the items that point at this one, need a separate call or query; keep them out of the first version of a report until you know the readers need them.
Keeping a Velocity Report Fast
A Script Block runs every time someone opens the page. A slow script slows the page for every reader and adds load to the server for everyone else, so treat performance as part of correctness.
- Let the query do the filtering. Put every condition you can into the Lucene query: project, type, status, document, dates. Filtering inside a Velocity loop means Polarion has to load each object before the template can discard it.
- Limit what you render. Cap the number of rows, show the total, and give readers a narrower query or page parameter when they need more. A table of several thousand rows is rarely read and always expensive.
- Sort server-side. Pass the sort field to
queryWorkItems. Sorting collections in template code is slow and awkward in Velocity. - Keep history out of loops. Reading revision history, baselines or older versions of each item inside a per-row loop multiplies the cost by the row count. Fetch history only for the rows that need it, or move the comparison into a dedicated widget designed for it.
- Avoid a query per row. A nested query inside the main loop turns one query into hundreds. Where you need related items, collect them in one query and match them up afterwards.
- Measure on realistic data. A script that is instant on a small test project can be slow on a production project with years of history. Test against a copy with production-sized data.
Common Pitfalls in Polarion Velocity Scripts
Null values and #set
In Polarion's Velocity configuration, #set($x = $something) does nothing when $something is null; $x keeps its previous value. Inside a loop, that means a work item with no status can silently display the status of the previous row. Initialise row variables at the top of each iteration, guard optional values with #if, and use the quiet reference form $!value when you want an empty string in place of a literal $value in the output.
HTML escaping
Velocity writes values into the page exactly as they are. A title containing < or & can break the table layout or inject markup into the page. Escape plain-text values that users type, such as titles and custom string fields, before writing them into HTML, as the #escHtml macro does. Rich-text fields such as the description already contain HTML; render them deliberately and never paste them into an attribute value.
Permissions
The script runs in the session of the person viewing the page, so Polarion's read permissions apply. Two readers can see different row counts on the same page. Linked items can live in projects the reader cannot open, so check objects before calling methods on them and avoid assuming every link resolves to a readable item. If a report is used as evidence, define which role generates it and record that alongside the output.
Quoting and parser errors
Velocity parses the script before Polarion evaluates anything. An apostrophe inside a single-quoted string ends the string early and breaks the whole block, so use double quotes for text. Dotted Lucene field names need quoting inside the query, as shown in the example. When a block renders nothing at all, reduce it to the first few lines and add back one section at a time; the error is usually a parse problem near the last section you added.
Field IDs versus labels
Queries and custom field reads use IDs, and the UI shows labels. A field labelled Verification Method may have the ID verificationMethod or something else entirely. Look up the IDs in the project's administration pages before writing the query.
From a Script Block to a Maintained Widget
A Script Block is the right tool for exploring data and for reports that one team owns. Once a report is copied onto many pages, used in reviews, or cited as evidence in a design history file, the script becomes software that needs an owner: parameters in place of hard-coded project IDs, version control, testing against each Polarion upgrade, and a defined way to change it.
The Redline Report Widget is an example of that path. It is built on Velocity, runs inside a LiveReport page, takes a Lucene query to narrow its scope, and compares two collection baselines to show added, removed and modified items in a redline view. Packaging it as a widget gives page authors a configuration form and keeps the comparison logic in one tested place.
If your team has Script Blocks that have grown into critical reports, or needs a report that a Script Block cannot deliver efficiently, our Polarion scripting service covers Velocity widgets, GraalJS and Groovy workflow scripts, and Java extensions, written and documented for regulated environments.