Skip to main content

Understanding Multi-Level Hierarchies

A multi-level hierarchy occurs when you need to display:
  • Level 1: Properties from items directly linked to your risk item (e.g., System Requirements)
  • Level 2: Properties from items linked to Level 1 items (e.g., Test Cases linked to those Requirements)
  • Level 3: Properties from items linked to Level 2 items (and so on)
diagram

Configure Nested Linked Item Columns

Start with a standard upstream traceability column:
{
  "header": "System Requirement",
  "type": "itemLink",
  "width": 200,
  "level": 1,
  "id": "sysReq",
  "typeProperties": {
    "linkRole": "implements",
    "linkTypes": "systemrequirement"
  }
}

Step 2: Add Level 2 Nested Item Column

Use serverRender with bindings to access items linked to the Level 1 item:
{
  "header": "Test Cases",
  "type": "text",
  "width": 250,
  "level": 1,
  "id": "testCases",
  "bindings": "sysReq.$item",
  "serverRender": "<ol style='margin-block-start: -16px;margin-block-end: -16px;padding-inline-start: 16px;'>#foreach($linkedWI in $sysReq.$item.fields().linkedWorkItems()) #set($testCase = $linkedWI.fields().workItem().get()) #set($typeId = $testCase.fields().type().get().id()) #if($typeId.equals(\"testcase\")) <li>$testCase.fields().title().get()</li> #end #end</ol>"
}
Key elements:
  • bindings: "sysReq.$item" — Access the linked System Requirement work item
  • $sysReq.$item.fields().linkedWorkItems() — Get all items linked to that requirement
  • #if($typeId.equals("testcase")) — Filter to show only Test Case work items

Step 3: Add Level 2 Property Columns

Display specific fields from Level 2 items in separate columns:
{
  "header": "Test Status",
  "type": "text",
  "width": 120,
  "level": 1,
  "id": "testStatus",
  "bindings": "sysReq.$item",
  "serverRender": "<ol style='margin-block-start: -16px;margin-block-end: -16px;padding-inline-start: 16px;'>#foreach($linkedWI in $sysReq.$item.fields().linkedWorkItems()) #set($testCase = $linkedWI.fields().workItem().get()) #set($typeId = $testCase.fields().type().get().id()) #if($typeId.equals(\"testcase\")) <li>$testCase.fields().status().render().withIcon(true).htmlFor().forFrame()</li> #end #end</ol>"
}

Step 4: Add Level 3 Columns (Items Linked to Level 2)

For even deeper nesting, chain the bindings pattern:
{
  "header": "Test Results",
  "type": "text",
  "width": 200,
  "level": 1,
  "id": "testResults",
  "bindings": "sysReq.$item",
  "serverRender": "<ol style='margin-block-start: -16px;margin-block-end: -16px;padding-inline-start: 16px;'>#foreach($linkedWI in $sysReq.$item.fields().linkedWorkItems()) #set($testCase = $linkedWI.fields().workItem().get()) #if($testCase.fields().type().get().id().equals(\"testcase\")) #foreach($tcLinked in $testCase.fields().linkedWorkItems()) #set($result = $tcLinked.fields().workItem().get()) #if($result.fields().type().get().id().equals(\"testrun\")) <li>$result.fields().title().get() - $result.fields().status().render().htmlFor().forFrame()</li> #end #end #end #end</ol>"
}

Multi-Level Hierarchy Configuration Pattern

LevelBindings PatternAccess PathExample
1Direct link column$itemSystem Requirement linked to Risk
2firstLevel.$item$firstLevel.$item.fields().linkedWorkItems()Test Cases linked to Requirement
3firstLevel.$item (nested loop)Iterate Level 2, then .linkedWorkItems()Test Results linked to Test Case
  • Missing bindings property: You MUST include "bindings": "columnId.$item" to access nested linked items. Without this, $columnId.$item will be undefined.
  • JSON escaping: When writing serverRender in JSON, escape double quotes with \" for type checks like $typeId.equals(\"testcase\").
  • Performance impact: Each serverRender column makes additional queries. Limit multi-level columns to essential fields only.
  • Read-only limitation: All serverRender columns are read-only. Users cannot edit nested item properties directly from these columns.
  • Filter by type: Always filter linked items by type using $item.fields().type().get().id().equals("typename") to avoid displaying irrelevant links.
  • Use render() methods: For enum fields, use .render().withIcon(true).htmlFor().forFrame() to display proper icons and formatted values.
  • Consistent styling: Use the same <ol> wrapper style across all nested columns for visual consistency:
    &lt;ol style='margin-block-start: -16px;margin-block-end: -16px;padding-inline-start: 16px;'&gt;
    
  • Sort results: Add .sort("id") to queries for consistent ordering: $item.transaction().workItems().search().query("...").sort("id")

Downstream Multi-Level Hierarchies

For downstream/backlink scenarios (showing items that link TO your linked items):
{
  "header": "Issues Referencing Requirement",
  "type": "text",
  "width": 250,
  "level": 1,
  "id": "reqIssues",
  "bindings": "sysReq.$item",
  "serverRender": "<ol style='margin-block-start: -16px;margin-block-end: -16px;padding-inline-start: 16px;'>#set($reqRef = $sysReq.$item.getReference()) #foreach($issue in $item.transaction().workItems().search().query(\"type:issue AND linkedWorkItems:${reqRef.projectId()}/${reqRef.id()}\").sort(\"id\")) <li>$issue.fields().id().get() - $issue.fields().severity().render().withIcon(true).htmlFor().forFrame()</li> #end</ol>"
}
Query pattern: linkedWorkItems:${reqRef.projectId()}/${reqRef.id()} finds all items linking to the Level 1 item.

Verification

After configuring multi-level hierarchy columns:
  1. Reload the RISKSHEET
  2. Check Level 1 column — Should show directly linked items (e.g., System Requirements)
  3. Check Level 2 columns — Should display items linked to Level 1 items (e.g., Test Cases linked to each Requirement)
  4. Check Level 3 columns — Should show items linked to Level 2 items (e.g., Test Results)
  5. Verify ordering — Items should appear in consistent order (by ID if sorted)
  6. Test empty cases — Rows where Level 1 has no linked items should show empty Level 2/3 columns
You should now see a complete traceability chain showing nested linked items and their properties across multiple columns, with each level displaying relevant work item fields in a read-only format.

See Also

KB ArticlesSupport TicketsSource Code
  • risksheet.json
  • PolarionAppConfigManager.java
  • AppConfig.ts
  • CustomMergeManager.ts
  • TextEditor.ts