Skip to main content

Template File and Loading

The template file is named risksheetTopPanel.vm and is stored as a document attachment in Polarion. The /api/panel endpoint loads the file, renders it through the Velocity engine with full access to Polarion services, and returns the resulting HTML. If an error occurs during rendering, a red error message box displays instead of the panel content. diagram
If no risksheetTopPanel.vm file is attached to the current document, Risksheet searches the document’s template hierarchy. The system uses the same configuration inheritance path as risksheet.json. See Template Path Configuration for the lookup order.

Template Structure

A typical risksheetTopPanel.vm file contains three sections:
SectionPurposeExample
<style> blockCSS rules for panel layout, image sizing, and custom classes.rs-top-panel-info { padding: 8px; }
HTML markupDocument metadata display, action buttons, filter controls<div>Product: $!productFamily</div>
<script> blockJavaScript functions for formulas, query factories, and context menu actionsfunction calcRisk(info) { ... }
## Velocity comments start with ##

## 1. CSS styling
<style>
  .panel-info { display: flex; gap: 16px; padding: 8px; }
</style>

## 2. HTML content
<div class="panel-info">
  <span>Document: $doc.moduleNameWithSpace</span>
</div>

## 3. JavaScript functions
<script>
  function myFormulaHelper(info) {
    return info.item['sev'] * info.item['occ'];
  }
</script>

Velocity Context Variables

The top panel template receives the full Velocity context with access to Polarion services and document data. See Velocity Template Context for the complete reference.
VariableTypeDescription
$docIModuleCurrent Polarion document object. Provides access to document metadata, custom fields, and the module API
$documentDocumentRisksheet document wrapper with customFields access for direct field reading
$transactionITransactionCurrent Polarion transaction for data operations
$trackerServiceITrackerServicePolarion tracker service for work item queries and data retrieval
$projectServiceIProjectServicePolarion project service for project-level metadata

Accessing Document Custom Fields

There are two approaches to reading document-level custom fields in the top panel.

Method 1: $doc.getOldApi().getValue()

Use $doc.getOldApi().getValue('customFieldID') for programmatic access. This is the preferred method when you need to pass values into JavaScript variables:
#set($productFamily = $doc.getOldApi().getValue('productFamily'))
#set($riskMatrixVersion = $doc.getOldApi().getValue('riskMatrixVersion'))
<script>
  var documentProductFamily = "$!productFamily";
  var matrixVersion = "$!riskMatrixVersion";
</script>

Method 2: $document.customFields

Use $document.customFields.fieldName for direct display in HTML markup:
<div class="rs-top-panel-info">
  <span>Product Family: $!document.customFields.productFamily</span>
  <span>Risk Matrix: $!document.customFields.riskMatrixVersion</span>
</div>
The top panel can display document custom fields but cannot modify them. To change document field values, use the standard Polarion document editor. Custom context menu actions for document workflow transitions are not natively supported.
Table-type custom fields can also be accessed via $doc.getOldApi().getValue('customFieldID'). The returned object structure depends on the Polarion table field implementation.

Bridging Server Data to Client-Side Formulas

The primary use case for the top panel template is defining JavaScript functions that risksheet.json formulas can call. This bridges server-side Polarion data (accessible via Velocity) into client-side formula execution.

Pattern

diagram Step 1. Define a JavaScript function in risksheetTopPanel.vm using Velocity to inject server-side data:
#set($matrixData = $doc.getOldApi().getValue('riskMatrix'))
<script>
  var riskMatrix = $matrixData;

  function getRiskAcceptance(severity, occurrence) {
    return riskMatrix[severity][occurrence];
  }
</script>
Step 2. Reference the top panel function from a formula in risksheet.json:
{
  "formulas": {
    "riskAcceptance": "function(info) { return getRiskAcceptance(info.item['severity'], info.item['occurrence']); }"
  }
}
Top panel functions called from formulas receive the same info object. Access work item field values via info.item['fieldId'] and return the result. Prepare all needed values within functions defined in the top panel file.

Dynamic Risk Matrices from External Sources

The top panel can reference external data sources via Velocity context and Polarion APIs, enabling dynamic risk matrix definitions shared across projects without duplicating formula logic in each risksheet.json:
#set($xmlConfig = $trackerService.getDataService().getAttachment($doc, 'risk-matrix.xml'))
<script>
  var riskMatrix = parseRiskMatrix('$!xmlConfig');

  function evaluateRiskCondition(severity, occurrence) {
    return riskMatrix.lookup(severity, occurrence);
  }
</script>
This pattern replaces hardcoded riskCondition formulas in each project’s risksheet.json with a centralized, dynamic matrix definition.
The exact API for loading attachments and external XML files depends on your Polarion version and installed plugins. The pattern above demonstrates the general approach — verify the specific Polarion API calls in your environment.

Filtering Linked Items with Query Factory

Combine the top panel template with a query factory function to filter item suggestions based on document-level fields. This is useful when different documents in the same project should only link to items matching their product family or variant: Top panel template (risksheetTopPanel.vm):
#set($productFamily = $doc.getOldApi().getValue('productFamily'))
<script>
  var documentProductFamily = "$!productFamily";

  window.risksheet = window.risksheet || {};
  window.risksheet.customQueryFactory = function(column, item) {
    return 'type:requirement AND productFamily:' + documentProductFamily;
  };
</script>
Column configuration in risksheet.json:
{
  "columns": [
    {
      "binding": "linkedRequirement",
      "header": "Linked Requirement",
      "type": "itemLink",
      "queryFactory": "customQueryFactory"
    }
  ]
}
The query factory function constructs a Lucene query string that filters the item suggester results. The function receives the column configuration and the current item as parameters.

Enum Type Identifiers

When configuring enum columns, the type property in risksheet.json must match the enum definition name in Polarion’s custom fields XML. The top panel can help verify or map enum identifiers:
<script>
  function getEnumDisplayValue(info) {
    var fieldValue = info.item['riskCategory'];
    // Map enum ID to display label if needed
    return fieldValue;
  }
</script>
Check the .polarion/documents/fields/custom-fields.xml file in your project’s SVN repository for the exact enum definition name to use in the type property. The enum type identifier in risksheet.json must match exactly.

CSS Styling in the Top Panel

The top panel template can include <style> blocks to control the appearance of the panel content, rich text images, and custom cell rendering:
<style>
  .rs-rich-text-image img {
    max-height: 40px;
    object-fit: contain;
  }
  .rs-top-panel-info {
    display: flex;
    gap: 16px;
    padding: 8px;
    font-size: 13px;
    color: #333;
  }
  .rs-top-panel-info .label {
    font-weight: 600;
    color: #555;
  }
</style>
When rendering rich text fields with images via server render columns, set the bindings to task.$item rather than the specific field binding. Control image sizing through CSS in the top panel. See Render Custom Data for details.

Context Menu Integration

Custom context menu actions can be registered via window.risksheet.customContextMenuActions in the top panel <script> block. Functions must exist on the global window object.
<script>
  window.myCustomAction = function(item) {
    var projectId = "$!{project.id}";
    // Custom action implementation using server-side data
    alert('Running check for item ' + item.ID + ' in project ' + projectId);
  };

  window.risksheet = window.risksheet || {};
  window.risksheet.customContextMenuActions = [
    {
      label: "Run Custom Check",
      enabled: function(item) { return !item.isNew; },
      execute: function(item) { window.myCustomAction(item); }
    }
  ];
</script>
Each custom context menu action requires three properties:
PropertyTypeDescription
labelstringText displayed in the context menu
enabledfunction(item)Returns true if the action is available for the given item. The function receives the current row’s data object
executefunction(item)The action to perform when clicked. The function receives the current row’s data object

Maximize/Restore Toggle

The top panel visibility can be toggled by the user to maximize the grid viewing area. When toggled:
  • The top panel hides entirely
  • The grid expands to use the full available space
  • The toggle button is always available in the toolbar
This is useful when the top panel displays informational content that is not needed during active editing.

Error Handling

If the risksheetTopPanel.vm template contains errors (Velocity syntax errors, missing variable references, or JavaScript errors), the panel displays a red error message box instead of the expected content. The grid remains functional even when the top panel fails to render.
If the top panel displays an error, check the Polarion server logs for Velocity rendering errors. Common issues include missing $doc references, undefined custom field IDs, and malformed Velocity syntax. Use $!variable (with exclamation mark) to suppress null reference errors for optional fields.

Complete Example

A full risksheetTopPanel.vm file for an FMEA document with metadata display, risk evaluation function, query factory, and custom context menu action:
## risksheetTopPanel.vm - FMEA document top panel
## Displays product family, matrix version, and document name
## Provides risk evaluation function and filtered item linking

#set($productFamily = $doc.getOldApi().getValue('productFamily'))
#set($riskMatrixVersion = $doc.getOldApi().getValue('riskMatrixVersion'))

<style>
  .fmea-panel {
    display: flex;
    gap: 16px;
    padding: 8px 12px;
    font-size: 13px;
    background: #f5f5f5;
    border-bottom: 1px solid #ddd;
  }
  .fmea-panel .label {
    font-weight: 600;
    color: #555;
  }
  .fmea-panel .value {
    color: #333;
  }
</style>

<div class="fmea-panel">
  <span><span class="label">Product Family:</span>
    <span class="value">$!productFamily</span></span>
  <span><span class="label">Matrix Version:</span>
    <span class="value">$!riskMatrixVersion</span></span>
  <span><span class="label">Document:</span>
    <span class="value">$doc.moduleNameWithSpace</span></span>
</div>

<script>
  // Server-side data injected via Velocity
  var documentProductFamily = "$!productFamily";

  // Risk evaluation function called from risksheet.json formulas
  function getRiskLevel(severity, occurrence) {
    var matrix = {
      "high":   { "high": "unacceptable", "medium": "undesirable", "low": "acceptable" },
      "medium": { "high": "undesirable",  "medium": "acceptable",  "low": "acceptable" },
      "low":    { "high": "acceptable",   "medium": "acceptable",  "low": "negligible" }
    };
    return matrix[severity]
      ? (matrix[severity][occurrence] || "unknown")
      : "unknown";
  }

  // Query factory for filtering linked item suggestions by product family
  window.risksheet = window.risksheet || {};
  window.risksheet.customQueryFactory = function(column, item) {
    return 'type:requirement AND productFamily:' + documentProductFamily;
  };

  // Custom context menu action
  window.risksheet.customContextMenuActions = [
    {
      label: "Evaluate Risk Level",
      enabled: function(item) { return item['severity'] && item['occurrence']; },
      execute: function(item) {
        var level = getRiskLevel(item['severity'], item['occurrence']);
        alert('Risk Level: ' + level);
      }
    }
  ];
</script>
The corresponding risksheet.json formulas reference the top panel functions:
{
  "formulas": {
    "riskLevel": "function(info){ return getRiskLevel(info.item['severity'], info.item['occurrence']); }"
  },
  "columns": [
    {
      "id": "riskLevel",
      "header": "Risk Level",
      "binding": "riskLevel",
      "formula": "riskLevel"
    },
    {
      "binding": "linkedRequirement",
      "header": "Linked Requirement",
      "type": "itemLink",
      "queryFactory": "customQueryFactory"
    }
  ]
}
Support TicketsSource Code
  • RisksheetViewServlet.java
  • AppConfig.ts
  • MaximizeViewCommand.ts
  • DocumentConfigProvider.java
  • RiskSheetContextMenu.ts