- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
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
Created a Script Include (LicenseCostHelper) that returns a JS object/array representing the tree.
Configure a Transform Data Resource (TDR) “LicenseCostData” to use that Script Include as its data source.
- ACL to execute with sys id of the TDR.
Preview the TDR in UI Builder—the JSON looks correct (includes userId, userName,directCost,totalCost, children, etc.).
Adding a Tree component onto the page, inside a 2 column page.
Adding Data Resource id as licenseCostData_1.
Use Items binding (e.g., @Data.licensecostdata_1.output.children)
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"] }
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
Solved! Go to Solution.
- Labels:
-
UI Builder : Next Experience
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
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👍!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
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👍!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
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).