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

Sharique Azim
Kilo Sage

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"]
}

Screenshot content tree config 2.pngScreenshot content tree config 1.png

Screenshot UI error.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

 

2 REPLIES 2

debendudas
Mega Sage

I have already provided a solution in the other post (Link), please try that and let me know if it works!

 

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

I have marked your json format structure correct there, thank you.