Background:

For teams working in regulated environments, proving end-to-end traceability is a critical part of compliance. You often need to demonstrate that high-level Software Requirements Specifications (SRS) successfully propagate down through your Software Architecture Documents (SAD) and your detailed Software Design Documents (SDD).

A common way to manage this in Spira is to track all three levels within the Requirements module and use the Component field to categorize them (e.g., setting the component to "Requirements," "Architecture," or "Design").

Prerequisites and Setup

Given your data within the Requirements module is structured by parent-child hierarchy and the Component field to classify the artifacts.

Example structure used in this report:

  • SRS (Software Requirements Specification) — Child artifacts have Component set to Requirement

  • SAD (Software Architecture Document) — Child artifacts have Component set to Architecture

  • SDD (Software Design Document) — Child artifacts have Component set to Design

Create a report:

This article assumes you are familiar with the basics of writing custom reports in Spira.
In this example we will be using a custom report with a custom ESQL section.

To create the report you need to:

  1. Go to Administration  > Edit Reports
  2. Click on Create a new Report (at the bottom of the list of existing Reports)
  3. Specify that it should allow generation in MS-Word, Excel, HTML and PDF formats
  4. Choose to add a Custom Section:

  5. Paste into the Query section the query itself.

SQL Query:

SELECT 
  RQ.REQUIREMENT_ID, 
  RQ.NAME AS REQUIREMENT_NAME, 
  RQ.INDENT_LEVEL,
  RQ.IS_SUMMARY,
  RQ.COMPONENT_NAME AS BASE_COMPONENT, 
  LR.NAME AS LINKED_NAME, 
  LR.COMPONENT_NAME AS LINKED_COMPONENT 
FROM SpiraTestEntities.R_Requirements AS RQ 
LEFT JOIN SpiraTestEntities.R_ArtifactAssociations AS AA 
  ON AA.SOURCE_ARTIFACT_TYPE_ID = 1 
  AND AA.DEST_ARTIFACT_TYPE_ID = 1 
  AND ( (RQ.REQUIREMENT_ID = AA.SOURCE_ARTIFACT_ID) OR (RQ.REQUIREMENT_ID = AA.DEST_ARTIFACT_ID) ) 
LEFT JOIN SpiraTestEntities.R_Requirements AS LR 
  ON LR.IS_DELETED = False 
  AND ( 
    (AA.DEST_ARTIFACT_ID = LR.REQUIREMENT_ID AND RQ.REQUIREMENT_ID = AA.SOURCE_ARTIFACT_ID) OR 
    (AA.SOURCE_ARTIFACT_ID = LR.REQUIREMENT_ID AND RQ.REQUIREMENT_ID = AA.DEST_ARTIFACT_ID) 
  ) 
WHERE RQ.PROJECT_ID = ${ProjectId} 
  AND RQ.IS_DELETED = False 
ORDER BY RQ.INDENT_LEVEL

How the Query Works:

  • Bidirectional Link Matching: Standard Spira associations store a directional SOURCE_ARTIFACT_ID and DEST_ARTIFACT_ID. The dual OR statements in the AA and LR joins resolve links symmetrically - if artifact A is linked to artifact B, the association is returned whether the link originated from A or from B.

    Requirement-to-Requirement Scoping: Both SOURCE_ARTIFACT_TYPE_ID and DEST_ARTIFACT_TYPE_ID are filtered to 1. Since SRS requirements, Architecture, and Design artifacts are all stored within the Requirements module, this isolates inter-requirement links while excluding associations to test cases, tasks, or incidents.

    Hierarchy Preservation: Extracting RQ.INDENT_LEVEL and RQ.IS_SUMMARY allows the report to identify requirement hierarchy (e.g., SRS, SAD, SDD). Sorting by INDENT_LEVEL ensures that parent requirements and child items maintain their exact tree sequence.

    Comprehensive Cross-Tier Coverage: By omitting component filters in both the WHERE clause and the LR join, the query avoids dropping peer requirements or cross-tier links, leaving the categorization and column placement to the XSLT template.

6. Copy and paste the XSLT template into Template section:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="ReqGroup" match="*[REQUIREMENT_ID]" use="REQUIREMENT_ID" />

  <xsl:template match="/">
    <style>
      .TraceabilityGrid { 
        width: 100%; 
        border-collapse: collapse; 
        font-family: Arial, sans-serif; 
        font-size: 13px; 
      }
      .TraceabilityGrid th { 
        border: 1px solid #cbd5e1; 
        padding: 8px 10px; 
        text-align: left; 
        font-weight: 600; 
        color: #1e293b; 
        background-color: #f8fafc;
      }
      .TraceabilityGrid td { 
        border: 1px solid #e2e8f0; 
        padding: 8px 10px; 
        vertical-align: top; 
      }
      .parent-req { 
        font-weight: bold; 
        color: #0f172a; 
      }
      .child-req { 
        padding-left: 20px !important; 
        font-weight: normal; 
        color: #334155; 
      }
    </style>

    <table class="TraceabilityGrid">
      <thead>
        <tr>
          <th style="width: 31%;">Artifact</th>
          <th style="width: 23%;">Linked Requirements</th>
          <th style="width: 23%;">Architecture</th>
          <th style="width: 23%;">Design</th>
        </tr>
      </thead>
      <tbody>
        <xsl:for-each select="//*[REQUIREMENT_ID][generate-id() = generate-id(key('ReqGroup', REQUIREMENT_ID)[1])]">
          <xsl:variable name="CurrentReqID" select="REQUIREMENT_ID" />
          <xsl:variable name="IsSummary" select="IS_SUMMARY" />

          <xsl:choose>
            <!-- Parent Headers (SRS, SAD, SDD - Bold text) -->
            <xsl:when test="$IsSummary = 'true' or $IsSummary = 'True'">
              <tr>
                <td class="parent-req">
                  <xsl:value-of select="REQUIREMENT_NAME"/>
                </td>
                <td></td>
                <td></td>
                <td></td>
              </tr>
            </xsl:when>

            <!-- Child Artifacts -->
            <xsl:otherwise>
              <tr>
                <td class="child-req">
                  <xsl:value-of select="REQUIREMENT_NAME"/>
                </td>

                <!-- Linked Requirements Column -->
                <td>
                  <xsl:for-each select="key('ReqGroup', $CurrentReqID)[LINKED_COMPONENT = 'Requirement']">
                    <xsl:if test="position() &gt; 1"><br /></xsl:if>
                    <xsl:value-of select="LINKED_NAME"/>
                  </xsl:for-each>
                </td>

                <!-- Linked Architecture Column -->
                <td>
                  <xsl:for-each select="key('ReqGroup', $CurrentReqID)[LINKED_COMPONENT = 'Architecture']">
                    <xsl:if test="position() &gt; 1"><br /></xsl:if>
                    <xsl:value-of select="LINKED_NAME"/>
                  </xsl:for-each>
                </td>

                <!-- Linked Design Column -->
                <td>
                  <xsl:for-each select="key('ReqGroup', $CurrentReqID)[LINKED_COMPONENT = 'Design']">
                    <xsl:if test="position() &gt; 1"><br /></xsl:if>
                    <xsl:value-of select="LINKED_NAME"/>
                  </xsl:for-each>
                </td>
              </tr>
            </xsl:otherwise>
          </xsl:choose>

        </xsl:for-each>
      </tbody>
    </table>
  </xsl:template>
</xsl:stylesheet>

7. Click [Save] twice and you can run your report.

This XSLT acts as a true pivot table—if a user adds a brand new component to their project tomorrow, the report will instantly generate a new column for it without any code changes!

Here is how the template processes the data:

  • Muenchian Grouping (xsl:key): Because a single requirement might be linked to five different design artifacts, the database returns five separate rows for that requirement. The <xsl:key> groups these rows by REQUIREMENT_ID so the base requirement only prints once on the left, while its linked items are aggregated on the right.

  • Parent Header vs. Child Row Rendering (xsl:choose): The template looks at the IS_SUMMARY flag generated by the ESQL query. If an item is a folder (e.g., SRS), it is rendered in a custom .parent-req CSS class (bold text, no indent) and leaves the downstream columns empty. If it is a standard artifact, it gets a 20px indent to visually nest it under its parent.

  • Targeted Column Placement: Inside the columns, the xsl:for-each loop filters the aggregated links using [LINKED_COMPONENT = 'Architecture'] and [LINKED_COMPONENT = 'Design']. This guarantees that an associated design artifact never accidentally displays in the Architecture column, and multiple items in the same cell are stacked cleanly using <br />.

8. Running the report and expected output

As a result of the report execution you should get something like: