UI Builder Tips Series
Welcome to the UI Builder Tips series! I'll use this series to document what I've learned while working with ServiceNow's UI Builder. Since this is a learning process, these blog posts should be seen as a set of notes.
The content of these posts will likely change often to include new information as I continue to learn. Feel free to leave feedback or corrections in the comments, and I'll update the posts as needed. Let's learn together!
An Introduction to Data Resources
In this blog post, we will explore everything I have learned so far about data resources, including how to create your own.
Pages built in UI Builder can be divided into two main parts:
The Components that form the page
The Data that populates those components
The Lifecycle of a Data Resource
A data resource specifies a piece of dynamic data and determines when it should be retrieved. There can be many data resources on a UI Builder page, each with its own triggers, logic, and events. You can think of each one like a dog playing fetch: when you tell it to get something, it runs out to retrieve it and then returns with the item after some time. All data resources are triggered, execute, and then cause resulting events to be fired off.
All data resources have 3 events that a fired off throughout their lifecycles:
Data Fetch Initiated: Fired when the data resource starts running
Data Fetch Succeeded: Fired when the data resource completes successfully
Data Fetch Failed: Fired when a data resource fails or throws an error
Non-Mutating vs Mutating
Some data resources only retrieve/read data (non-mutating) while others modify/update data (mutating). This is an important distinction and will have an impact on the behavior of the data resource.
Non-mutating data resources can be triggered in 3 different ways:
Immediately (eager): Triggered as soon as the page loads
Only when invoked (explicit): Triggered only when a specific refresh resource event is fired
Property updated by client state: Triggered any time that a client state variable bound to a property on the data resource updates
Non-mutating data resources can return a result.
Mutating data resources can only be triggered in 2 ways:
Only when invoked (explicit): Triggered only when a specific execute resource event is fired
Property updated by client state: Triggered any time that a client state variable bound to a property on the data resource updates
Mutating data resources cannot return a result.
The Different Types of Data Resources
Data resources, also referred to as data brokers, come in a couple of different flavors, each with their different use-cases. Let’s take a brief look at each one:
GraphQL (sys_ux_data_broker_graphql)
Scriptlet (sys_ux_data_broker_scriptlet)
REST (sys_ux_data_broker_rest)
Composite (sys_ux_data_broker_composite)
Transform (sys_ux_data_broker_transform)
How to Create a Custom Transform Data Resource
You can create a custom data resource by going to the appropriate table and clicking the new button in the classic UI:
Or through the data resource menu in UI Builder:
Either way will take you to the classic UI form view of a new data resource record:
Once you have a new data resource record open you can fill in each of the fields:
Name
Mutates server data - distinguishes a mutating data resource from a non-mutating data resource
Private - hides the data resource from selection menus
Description
Properties - accepts a list of property JSON objects
Script - defines the logic that the data resource will execute
Output schema - accepts a JSON schema that defines the shape of the output
ACL Failure Result - accepts a JSON object that will be returned when an ACL failure occurs
Properties
The properties field defines the input properties that your data resource accepts. This field will ultimately contain a list of property JSON objects. Each property object should have the following structure:
{
"name": "",
"label": "",
"description": "",
"readOnly": true | false,
"fieldType": "",
"mandatory": true | false,
"defaultValue": "",
}
Each property defines an input field that can have values passed in through UI Builder.
Here is an example of a complete property list from a custom transform data resource that I created recently for my DBML Generator application:
[
{
"name": "generationType",
"label": "Generation Type",
"description": "Select generation type",
"readOnly": false,
"fieldType": "choice",
"mandatory": true,
"defaultValue": "table",
"typeMetadata": {
"variant": "generation-type-choices",
"choices": [
{"label": "Table", "value": "table"},
{"label": "App","value": "app"}
]
}
},
{
"name": "table",
"label": "Table",
"description": "",
"readOnly": false,
"mandatory": false,
"fieldType": "string",
"defaultValue": ""
},
{
"name": "app",
"label": "App",
"description": "",
"readOnly": false,
"mandatory": false,
"fieldType": "string",
"defaultValue": ""
},
{
"name": "includeSysColumns",
"label": "Include System Columns",
"description": "Include system columns like sys_update_on, sys_created_on, etc. from the DBML",
"readOnly": false,
"mandatory": false,
"fieldType": "Boolean",
"defaultValue": false
},
{
"name": "includeInheritedColumns",
"label": "Include Inherited Columns",
"description": "Include columns in child tables that are inherited from a parent table",
"readOnly": false,
"mandatory": false,
"fieldType": "Boolean",
"defaultValue": false
}
]
Here’s what the data resource configuration looks like in UI Builder after it has been added:
Script
The script field defines the logic that the data resource will execute when it is triggered. The field accepts a function that receives an inputs
object as a parameter.
This script runs server-side which means that it has access to APIs like GlideRecord and script includes. It can take any shape that you want but here is an example template that I submitted as part of the ServiceNow 2024 Hacktoberfest Code Snippets challenge.
/**
* This is a template for a transform data resource
* @param {{param1: string, param2: number, param3?: boolean}} inputs
* Inputs from the properties field above; param1 and param2 are mandatory.
* @returns {string} The value returned after transformation.
*/
function transform({ param1, param2, param3 }) {
const lib = "Data Broker";
const func = "<insert data broker name here>";
let res;
try {
if (!param1) throw new Error("Missing required param 'param1'");
if (!param2) throw new Error("Missing required param 'param2'");
// Add transformation logic here
return res;
} catch (e) {
gs.error(`${lib} ${func} - ${e}`);
throw new Error(e);
}
}
Here is another example from DBML Generator:
function transform({generationType, table, app, includeSysColumns, includeInheritedColumns}) {
const lib = "Data Resource";
const func = "Generate DBML";
try {
const dbmlGen = new x_1128327_dbml_gen.DBMLGenerator(includeSysColumns, includeInheritedColumns);
let dbml;
if (generationType == "table") {
dbml = dbmlGen.generateTableDBML(table);
} else if (generationType == "app") {
dbml = dbmlGen.generateAppDBML(app);
} else {
throw new Error(`Unknown generation type ${generationType}`);
}
return dbml;
} catch (e) {
gs.error(`${lib} ${func} - ${e}`);
throw new Error(e.message);
}
}
Create Data Resource ACL
After creating your data resource it is essential to create an ACL for the data resource. Your data resource will not be allowed to execute without an accompanying ACL!
Elevate to security admin
Go to the ACL table (
sys_security_acl
)Create a new ACL with the following details
Type =
ux_data_broker
Operation =
execute
Name = the
sys_id
of your data resource record. You may need to click the blue arrow next to the name field to enter a sys_idRole = A role appropriate to execute your data resource
All of the other fields can stay as their defaults
Conclusion
Now you should have a basic understanding of UI Builder data resources and the tools necessary to create your own custom transform data broker to use in UI Builder!