- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 10-13-2021 06:43 AM
Limiting the users in displayed in 'Add Members' on a Visual Task Board
Situation: We recently had a request to only display a specific set of users in the 'Add Members' drop down on a Visual Task Board.
Evaluation:
When selecting a Visual Task Board all active users are displayed in the drop down. Normally this would be a quick change to a reference qualifier on a relevant table (see Notes for tables used by VTB) so we need to build something a little different.
The trick here is to use your browser's developer tools (most modern browsers this is activated by pressing the F12 key) to determine how the page is querying the database for the list of users.
- Navigate to a Visual Task Board
- Select the User's tab
- In the Developer tools window, click the Network tab then select the trashcan to clear the data in the network window
- Select 'Add Members' in the Users pane on your visual task board and you should see a new entry appear in the network window.
Scrolling through this data, you will see a referrer label which is a link to your current visual task board. Note that the URL contains 'vtb.do' and 'angular.do?sysparm_type=ref_list_data'. This what we'll reference in the next few steps to tell ServiceNow that the browser is asking for the user list from a visual task board.
Solution:
The following steps will show you how to limit the 'Add Members' drop down to only display a chosen subset of users on all Visual task boards.
1. In Filter navigator, type sys_user.FILTER to open the Users table. From here, build a query with the conditions to limit the users you would like to appear in the Add Members drop down. Once your filter is accurate, right click on the filter and select copy query.
Your clipboard should contain something similar to:
roles=itil^active=true
Keep this around as you'll be copying more things to your clipboard in the next few steps.
2. In your filter navigator, type Business Rules and select Business rules under System Definition.
-
- Click New to create a new business rule
- Give this business rule a name that will make sense when you come back to it months from now such as 'Restrict VTB Add Members to ITIL users'
- On the When to run tab:
-
- Set the When dropdown to Before and Check the Query box
- Check the Advanced checkbox in the top right and navigate to the Advanced tab that appears
- In the Advanced tab, set the condition box to:
gs.getSession().isInteractive() && gs.action != ''
This tells the Business rule to only run when there is an interactive session and action. In our case this is a user clicking the 'Add Members' dropdown.
- Add the following in the Script box:
This script looks at that Referrer URL that we saw earlier in our Developer tools to contain vtb.do and is also looking that the URL contains 'angular.do?sys_parm=ref_list_data' which is the user selecting the 'Add Members' dropdown.
(function executeRule(current, previous /*null when async*/) {
//If the vtb page calls for a list of user records, restrict the records sent back to <add query contions here>
if ((gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1 || (gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1) {
current.addEncodedQuery('<add encoded query here>');
}
})(current, previous);
Before saving, you'll need to update the <> portions of this script, first with comments on what function this script is performing and also to replace the <add encoded query here> with the sys_user encoded query from above.
Return to a visual task board to confirm that only the users in the select query are visible in the drop down.
Notes:
VTB associated tables:
- vtb_board_label
- vtb_board_member
- vtb_card
- vtb_card_history
- vtb_lane
- vtb_task
If your users cannot see any members to Visual task boards see this knowledge article.
- 2,064 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thank you much for this! I recently discovered that VTBs can be shared to any active user, including ones that are marked as locked out. In turn, in our instance we have over 2 million sys_user records (sounds crazy, but true and needed), and so filtering that down to only the users we want helps significantly.
I am curious why specifically we need both pieces of the "if" statement, as it seems to only be the right side of it that controls the query. I tried testing each separately, and only the right one made a difference, so I was able to simplify my script down.
I added "(gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1" to the condition field, and just left the "current.addEncodedQuery()" in the script section itself, and it still works, however I want to make sure it wouldn't cause issues elsewhere, being a before query rule.
On top of this, I'd also be curious if something similar could be done for Dashboards, as they also seem to be able to be shared to users who are locked out, although it does restrict inactive users. Except when sharing a Dashboard, you search across multiple tables, not just sys_user, since they can be shared to groups and roles as well.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Grant,
The left side of the IF statement only limits incoming requests associated with 'vtb.do'. If other parts of the system use 'angular.do?sysparm_type=ref_list_data' in the url, those queries would be limited by this business rule as well without the vtb.do in the IF statement.
Thanks,
Brian
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
I guess the part that confuses me though is that they're separated by an OR operator ( || ). Technically, that means that it would still run in either of those conditions. I had tried using an AND operator (&&) in its place, however that seemed to make it not work anymore.

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
This script works, but it is causing tons of errors in our logs.
JavaScript evaluation error on:
(function executeRule(current, previous /*null when async*/) {
//If the vtb page calls for a list of user records, restrict the records sent back to exclude Inactive Users and Service Accounts
if ((gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1 || (gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1) {
current.addEncodedQuery('active=true^u_do_not_delete=false');
}
})(current, previous);
Root cause of JavaScriptException: java.lang.NullPointerException
: java.lang.NullPointerException:
The URI it is coming from is
vtbapi/now/connect/conversations?api=api&initialLoad=true&limit=25&offset=0

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
I would recommend adding this to the if condition as the first parameter
current.getEncodedQuery().indexOf('sys_id=')
so
if (current.getEncodedQuery().indexOf('sys_id=') && (gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1 || (gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1) {
current.addEncodedQuery('active=true^u_do_not_delete=false');
}
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hello, thank you for this. I'm attempting to do the same thing as in your example. Limit to Active users with the itil role. It is working for the active piece but not for the itil role piece.
Any suggestions?
Thanks!

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hey Grant,
Yes valid point I was thinking the same thing, from my testing the reason it doesn't work as an && is because at the point of querying the user table "gs.action.getGlideURI().toString()" is only equal to "angular.do?sysparm_type=ref_list_data". I'm guessing this is something to do with when the query business rule executes - gs.action.getGlideURI() doesn't have access to the the URI in the users browser. So that means that "gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1" is redundant and will likely never evaluate to true.
Edit: See my other comment, I did manage to get this working for reports and dashboards.

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thanks very much for this post.
I've used the same principle for locking down Report sharing, dashboard sharing and @ mentions as well.
I did mention in response to another user, it doesn't appear that "gs.action.getGlideURI().toString()).indexOf('vtb.do')" works because of when the query business rule runs . Happy to be corrected though!
"at the point of querying the user table "gs.action.getGlideURI().toString()" is only equal to "angular.do?sysparm_type=ref_list_data". I'm guessing this is something to do with when the query business rule executes - gs.action.getGlideURI() doesn't have access to the the URI in the users browser. So that means that "gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1" is redundant and will likely never evaluate to true."
All business rules below run on the sys_user table and are query BRs with the same script condition as you mentioned.
VTB Sharing:
(function executeRule(current, previous /*null when async*/ ) {
//If the vtb page calls for a list of user records, restrict the records sent back to it using the below query
//Active users, non integration users with the role snc_internal.
var queryString = "roles=snc_internal^active=true^web_service_access_only=false^internal_integration_user=false";
if (current.getEncodedQuery().indexOf('sys_id=') && (gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1 ) {
current.addEncodedQuery(queryString);
}
})(current, previous);
Dashboards sharing: (needs below property set to true)
"glide.cms.dashboards.sharing_with_secure_search", also found under "Dashboard properties" in app filter. When this is true ACLS and query BRs are respected.
(function executeRule(current, previous /*null when async*/ ) {
//If the dashboard page calls for a list of user records, restrict the records sent back to it using the below query
//Active users, non integration users with the role snc_internal.
var queryString = "roles=snc_internal^active=true^web_service_access_only=false^internal_integration_user=false";
if (current.getEncodedQuery().indexOf('sys_id=') && gs.action.getGlideURI().toString().indexOf('sysparm_type=dashboardPermissionsProcessor') != -1) {
current.addEncodedQuery(queryString);
}
})(current, previous);
Report sharing:
(function executeRule(current, previous /*null when async*/ ) {
//If the report page calls for a list of user records, restrict the records sent back to it using the below query
//Active users, non integration users with the role snc_internal.
var queryString = "roles=snc_internal^active=true^web_service_access_only=false^internal_integration_user=false";
//sysparm_target=sys_report_users_groups.user_id <-This condition is when a user clicks the lookup button to select a user
//sysparm_name=sys_report_users_groups.user_id <- This condition is when a user enters a persons name to search for them
if (current.getEncodedQuery().indexOf('sys_id=') && gs.action.getGlideURI().toString().indexOf('sysparm_target=sys_report_users_groups.user_id') != -1 || gs.action.getGlideURI().toString().indexOf('sysparm_name=sys_report_users_groups.user_id') != -1) {
current.addEncodedQuery(queryString);
}
})(current, previous);
At @ mention feature:
(function executeRule(current, previous /*null when async*/ ) {
//If a user utilises the @ mention feature, return a list of users using the query below.
//Active users, non integration users with the role snc_internal.
var queryString = "roles=snc_internal^active=true^web_service_access_only=false^internal_integration_user=false";
if (current.getEncodedQuery().indexOf('sys_id=') && gs.action.getGlideURI().toString().indexOf('api/now/form/mention/record') != -1) {
current.addEncodedQuery(queryString);
}
})(current, previous);

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hey Max, did you build the role into your copied query?
When you make a filter on the user table there's a field available called roles, you can enter any role in here that a you need. Once you right click and copy on the query you should have something like this.
"roles=itil^active=true"
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi Rhodri,
Yes, here is my BR.
(function executeRule(current, previous /*null when async*/ ) {
//If the vtb page calls for a list of user records, restrict the records sent back to active=true^roles=itil
if ((gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1) {
if ((gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1) {
current.addEncodedQuery('active=true^roles=itil');
}
}
})(current, previous);

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hey Max,
From some testing when I was implementing this I found that the below line was always returning false (or -1) as it was never found in getGlideURI() at the time of query.
gs.action.getGlideURI().toString()).indexOf('vtb.do')
Because you have this in it's own IF statement and have nested the other it is never evaluating to true and therefore never running the inner IF and your query. Because of that it is just running the default VTB filter which Brian mentioned in his original post is just any active user.
I've run a quick test and the solution should be changing your BR to the below (removing the outer IF statement).
(function executeRule(current, previous /*null when async*/ ) {
//If the vtb page calls for a list of user records, restrict the records sent back to active=true^roles=itil
if ((gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1) {
current.addEncodedQuery('active=true^roles=itil');
}
})(current, previous);
OR you can have it the way Brian structures it in his original post with a || OR operator between the two conditions (but I am 99% sure the indexOf('vtb.do') is redundant and it won't hit it):
(function executeRule(current, previous /*null when async*/) {
//If the vtb page calls for a list of user records, restrict the records sent back to <add query contions here>
if ((gs.action.getGlideURI().toString()).indexOf('vtb.do') != -1 || (gs.action.getGlideURI().toString()).indexOf('angular.do?sysparm_type=ref_list_data') != -1) {
current.addEncodedQuery('active=true^roles=itil');
}
})(current, previous);
Hope that helps!

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@Rhodri Have you found anything as a solution to Reports being unable to filter fields that reference the sys_user table? We discovered this issue as well today when someone tried running a report on Incident for a "Caller" that was a user that gets filtered by the business rule we have in place.
We also noticed that this only happens in the new reporting UI, and as a workaround you can swap to the Classic UI. I've confirmed that in the new reporting UI, when you attempt to filter a field that references sys_user, "gs.action.getGlideURI().toString()" only contains "angular.do?sysparm_type=ref_list_data", which is why it fails.
Have you found a solution that works yet?