Not able to display the content tree in UI builder, even when my transform data resource is correct

Sharique Azim
Kilo Sage

This is a duplicate post of https://www.servicenow.com/community/developer-forum/not-able-to-display-the-content-tree-in-ui-buil...

 

Description

I’m building an interactive Tree component in UI Builder on ServiceNow, backed by a Script Include and a Transform Data Resource (TDR). The JSON preview in the TDR shows the correct structure, but when I bind the output to my Tree component, nothing renders—no nodes, no errors.

What I’m Trying to Achieve

  • Fetch hierarchical data via a Script Include of user's manager available in alm_user_entitlement table, fetched through assigned_to field.

  • Surface that data through a Transform Data Resource in UI Builder

  • Bind the TDR’s JSON output to a Tree component’s Items path so the tree renders dynamically

What I have done so far

  1. Created a Script Include (LicenseCostHelper) that returns a JS object/array representing the tree.

  2. Configure a Transform Data Resource (TDR) “LicenseCostData” to use that Script Include as its data source.

  3. ACL to execute with sys id of the TDR.
  4. Preview the TDR in UI Builder—the JSON looks correct (includes userId, userName,directCost,totalCost, children, etc.).

  5. Adding a Tree component onto the page, inside a 2 column page.

  6. Adding Data Resource id  as licenseCostData_1.

  7. Use Items binding (e.g., @Data.licensecostdata_1.output.children

  8. Save and preview the page—no nodes appear.

     

     

     

    Expected Behavior

    The Tree component should render the hierarchical nodes returned by the Script Include, allowing expand/collapse of child items.

     

    Actual Behavior

    The Tree is completely empty on the page, with no data visible and no binding errors in the console.

     

    Troubleshooting So Far

    • Confirmed the Script Include returns correct JSON via background script.

    • Verified the TDR preview shows the expected children array.

    • Ensured the component is saved and the binding path matches Preview.

     

    Script Include(Client Callable)

var LicenseCostHelper = Class.create();
LicenseCostHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    type: 'LicenseCostHelper',
    initialize: function() {

  _getAllReportsIterative: function(rootManagerSysId) {
        var visitedMap = {},
            managerQueue = [rootManagerSysId],
            reportUserIds = [];

        while (managerQueue.length) {
            var currentManagerId = managerQueue.shift();
            if (visitedMap[currentManagerId])
                continue;

            visitedMap[currentManagerId] = true;

            var userGr = new GlideRecord('sys_user');
            userGr.addQuery('manager', currentManagerId);
            userGr.query();
            while (userGr.next()) {
                var reportUserId = userGr.getUniqueValue();
                if (!visitedMap[reportUserId]) {
                    reportUserIds.push(reportUserId);
                    managerQueue.push(reportUserId);
                }
            }
        }

        return reportUserIds;
    },
    // Build a nested tree of users with cost roll-ups
    buildNestedTreeWithCosts: function(rootManagerSysId) {
        // 1. collect everyone
        var userIds = this._getAllReportsIterative(rootManagerSysId);
        userIds.push(rootManagerSysId);

        // 2. count licenses per user & licenseId
        var ga = new GlideAggregate('alm_entitlement_user');
        ga.addAggregate('COUNT');
        ga.groupBy('assigned_to');
        ga.groupBy('licensed_by');
        ga.addQuery('assigned_to', 'IN', userIds.join(','));
        ga.query();

        var countsByUser = {},
            licenseIds = [];
        while (ga.next()) {
            var uid = ga.getValue('assigned_to'),
                lid = ga.getValue('licensed_by'),
                cnt = parseInt(ga.getAggregate('COUNT'), 10) || 0;

            countsByUser[uid] = countsByUser[uid] || {};
            countsByUser[uid][lid] = cnt;

            if (licenseIds.indexOf(lid) === -1) licenseIds.push(lid);
        }

        // 3. fetch cost per license
        var costByLicense = {};
        if (licenseIds.length) {
            var licGr = new GlideRecord('alm_license');
            licGr.addQuery('sys_id', 'IN', licenseIds.join(','));
            licGr.query();
            while (licGr.next()) {
                costByLicense[licGr.getUniqueValue()] =
                    parseFloat(licGr.getValue('u_cost_per_month')) || 0;
            }
        }

        // 4. compute each user's directCost + breakdown[] + init totalCost = directCost
        var nodeData = {};
        userIds.forEach(function(uid) {
            var dir = 0,
                br = [];
            var userLics = countsByUser[uid] || {};
            Object.keys(userLics).forEach(function(lid) {
                var cost = (costByLicense[lid] || 0) * userLics[lid];
                dir += cost;
                br.push({
                    cost: cost
                });
            });
            nodeData[uid] = {
                directCost: dir,
                breakdown: br,
                totalCost: dir
            };
        });

        // 5. fetch user names + manager links
        var userInfo = {};
        var ur = new GlideRecord('sys_user');
        ur.addQuery('sys_id', 'IN', userIds.join(','));
        ur.query();
        while (ur.next()) {
            userInfo[ur.getUniqueValue()] = {
                name: ur.getValue('name'),
                manager: ur.getValue('manager')
            };
        }

        // 6. initialize nodes map
        var nodes = {};
        userIds.forEach(function(uid) {
            nodes[uid] = {
                userId: uid,
                userName: userInfo[uid]?.name || 'Unknown',
                directCost: nodeData[uid].directCost,
                totalCost: nodeData[uid].totalCost,
                breakdown: nodeData[uid].breakdown,
                children: []
            };
        });

        // 7. assign children to their managers
        userIds.forEach(function(uid) {
            if (uid === rootManagerSysId) return;
            var mgr = userInfo[uid]?.manager;
            if (mgr && nodes[mgr]) {
                nodes[mgr].children.push(nodes[uid]);
            }
        });

        // 8. post-order recursion to roll up totalCost
        function aggregate(node) {
            node.children.forEach(aggregate);
            node.totalCost = node.directCost +
                node.children.reduce(function(sum, c) {
                    return sum + c.totalCost;
                }, 0);
        }
        aggregate(nodes[rootManagerSysId]);

        // 9. return the root node
        return nodes[rootManagerSysId];
    },

});

TDR script

function transform(input) {

    var sys_id_ceo = "{valid sys id string}";

    var result = new LicenseCostHelper().buildNestedTreeWithCosts(sys_id_ceo); //root manager- CEO
    return result;
}

TDR properties:

[
    {
        "name": "selectedManager",
        "label": "Selected Manager",
        "description": "Sys_ID of the selected manager",
        "readOnly": false,
        "fieldType": "string",
        "mandatory": false
    },
    {
        "name": "selectedLicense",
        "label": "Software Name",
        "description": "Name of the software to filter by",
        "readOnly": false,
        "fieldType": "string",
        "mandatory": false
    },
    {
        "name": "startDate",
        "label": "Start Date",
        "description": "Start date for license cost calculation",
        "readOnly": false,
        "fieldType": "string", 
        "mandatory": false
    },
    {
        "name": "endDate",
        "label": "End Date",
        "description": "End date for license cost calculation",
        "readOnly": false,
        "fieldType": "string",
        "mandatory": false
    }
]

TDR Output schema

{
  "type": "object",
  "properties": {
    "userId": {
      "type": "string",
      "description": "sys_id of the user"
    },
    "userName": {
      "type": "string",
      "description": "Display name of the user"
    },
    "directCost": {
      "type": "number",
      "description": "Cost of licenses directly assigned to this user"
    },
    "totalCost": {
      "type": "number",
      "description": "Sum of directCost plus all descendants' totalCost"
    },
    "breakdown": {
      "type": "array",
      "description": "List of this user's license costs",
      "items": {
        "type": "object",
        "properties": {
          "cost": {
            "type": "number",
            "description": "Cost for one license type"
          }
        },
        "required": ["cost"]
      }
    },
    "children": {
      "type": "array",
      "description": "Direct reports in the org tree",
      "items": { "$ref": "#" }
    }
  },
  "required": ["userId", "userName", "directCost", "totalCost", "breakdown", "children"]
}

ShariqueAzim_0-1755327506681.png

 

ShariqueAzim_1-1755327506556.png

 



ShariqueAzim_2-1755327506664.png

 




Any insights on why the Tree component won’t render even though the TDR preview shows a valid JSON array? What binding or configuration might I be missing? Help appreciated!

Regards,
Shariq

1 ACCEPTED SOLUTION

debendudas
Mega Sage

Hi @Sharique Azim ,

The structure of the JSON required in the Content tree component is as below:

[
  {
    "id": "p1",
    "label": "Tree parent item",
    "children": [
      {
        "id": "c1",
        "label": "Tree child item 1"
      },
      {
        "id": "c2",
        "label": "Tree child item 2"
      },
      {
        "id": "c3",
        "label": "Tree child item 3"
      }
    ]
  }
]

 

id -> should be unique

label -> the text you want to show in the Content tree

children -> Array of object containing id and label -> [{"id": "uniqueId1", "label", "label 1"}]

 

Please restructure the data resource output in this format so that Content tree component can render.

 

If this solution helps you then, mark it as accepted solution ‌‌✔️ and give thumbs up👍

 

View solution in original post

2 REPLIES 2

debendudas
Mega Sage

Hi @Sharique Azim ,

The structure of the JSON required in the Content tree component is as below:

[
  {
    "id": "p1",
    "label": "Tree parent item",
    "children": [
      {
        "id": "c1",
        "label": "Tree child item 1"
      },
      {
        "id": "c2",
        "label": "Tree child item 2"
      },
      {
        "id": "c3",
        "label": "Tree child item 3"
      }
    ]
  }
]

 

id -> should be unique

label -> the text you want to show in the Content tree

children -> Array of object containing id and label -> [{"id": "uniqueId1", "label", "label 1"}]

 

Please restructure the data resource output in this format so that Content tree component can render.

 

If this solution helps you then, mark it as accepted solution ‌‌✔️ and give thumbs up👍

 

yes, this schema was relevant , i changed my script to return similar json structure thanks. i used and created json objects of label, name and value(instead of "id" as suggested).