Unable to set collaborators in Demand from a business rule

gjz
Mega Sage

I'm am trying to populate the Collaborators field in a Demand using a script include and business rule, but it doesn't get populated and I don't know why.

 

For those who may not use Demands, the Collaborators field is a list of users who are collaborating with the demand.  The field is defined as a glide_list; it's the same as a watch list or additional assignee field on incident or any table extended from Task.

 

I created a script include that uses specific criteria to get a list of users who should be collaborators.  At the end of the script include, I remove the duplicate entries and return the unique array to the business rule that calls the script include and is supposed to populate the users to the collaborators field.  I'm getting good data back from the script include, but the collaborators field never gets populated and I don't know why and could use some help.

 

Script include snippet (that returns the correct data):

        // Turn the list into an array and get the unique values and turn it back into a string
        var listArray = liaison_list ? liaison_list.split(',') : [];
        var uniqueArray = new ArrayUtil().unique(listArray);
        var cleanString = uniqueArray.join(',');
        gs.log('@@@ array: ' + uniqueArray);

        //return uniqueArray.split(',');
        return uniqueArray;
 
Business rule:
    var dept = current.u_department;
    var liaisonList = [];
   
    var ppm = new XXX_PPM_Utils();
    liaisonList = ppm.getCollaborators(dept);
    gs.log('@@@ lisiaonList: ' + liaisonList);
    current.setValue('collaborators', liaisonList.join(','));
 
When I check the logs, the liaisonList has the correct data, but when I check the demand, the collaborators field is empty.
 
I've tried returning a string and an array to the business rule to populate the collaborators field, but neither works.
 
I followed this example I found on Google, it seems straight forward.
 
gjz_0-1786646442425.png

 

 This is what the data looks like in the log:
gjz_1-1786646955354.png

Can anyone spot what is missing?

1 ACCEPTED SOLUTION

Hi @gjz ,

Thanks for sharing the complete code. With the additional information, I think we can narrow this down.

The behavior you saw in your PDI with the quotes is expected JavaScript behavior.

This is valid:

var userList = ['be82abf03710200044e0bfc8bcbe5d1c', 'af2a757b1b74c4500750fc87cc4bcbc1'];

The sys_ids have to be quoted because they are JavaScript strings.

This is not valid:

var userList = [be82abf03710200044e0bfc8bcbe5d1c, af2a757b1b74c4500750fc87cc4bcbc1];

Without quotes, JavaScript treats those values as variable names.

However, you do NOT need to add quotes around every sys_id before setting the Collaborators field. A Glide List field can be populated with a comma-separated string of sys_ids.

I would remove ArrayUtil and the GlideElement concatenation from this implementation and use the actual stored values from the Glide List fields.

Replace your getCollaborators method with this:

getCollaborators: function(dept) {

var collaborators = [];
var uniqueUsers = {};

if (gs.nil(dept)) {
    return '';
}

var divisionGR = new GlideRecord('u_cmn_divisions');
divisionGR.addQuery('u_department', dept);
divisionGR.query();

while (divisionGR.next()) {

    var liaisons = divisionGR.getValue('u_liaisons') || '';
    var fundingLiaisons = divisionGR.getValue('u_funding_approvers') || '';

    var users = [];

    if (liaisons) {
        users = users.concat(liaisons.split(','));
    }

    if (fundingLiaisons) {
        users = users.concat(fundingLiaisons.split(','));
    }

    for (var i = 0; i < users.length; i++) {

        var userSysId = users[i] + '';

        if (!userSysId) {
            continue;
        }

        if (!uniqueUsers[userSysId]) {
            uniqueUsers[userSysId] = true;
            collaborators.push(userSysId);
        }
    }
}

var collaboratorString = collaborators.join(',');

gs.info('@@@ getCollaborators result: ' + collaboratorString);

return collaboratorString;

},

Then configure the Demand Business Rule as follows:

When: Before
Insert: Checked, if this should also populate during insert
Update: Checked
Advanced: Checked

For testing, leave the condition empty so that it runs on every update.

Most importantly, make sure this is a BEFORE Business Rule, not After or Async.

Use this exact Business Rule script:

(function executeRule(current, previous) {

var dept = current.getValue('u_department');

gs.info('@@@ Department: ' + dept);

if (gs.nil(dept)) {
    current.setValue('collaborators', '');
    return;
}

var ppm = new XXX_PPM_Utils();
var liaisonList = ppm.getCollaborators(dept);

gs.info('@@@ Collaborators returned: ' + liaisonList);

current.setValue('collaborators', liaisonList || '');

gs.info('@@@ Collaborators on current before save: ' + current.getValue('collaborators'));

})(current, previous);

Do not use current.update() inside this Business Rule.

The important part is that the Business Rule runs Before the Demand is written to the database. The value assigned to current.collaborators will then be included in the same database update.

For example, assume your divisions contain:

Division 1

u_liaisons:
userA,userB

u_funding_approvers:
userC

Division 2

u_liaisons:
userA,userD

u_funding_approvers:
userC

The method will return:

userA,userB,userC,userD

Duplicates are removed before the value is returned.

There is also one thing I noticed in your description that I would verify.

You referred to the second field as "Funding Liaisons", but your script is querying:

u_funding_approvers

Please confirm that u_funding_approvers is really the backend field name of that Glide List field.

If the actual field is something like:

u_funding_liaisons

then change this line:

var fundingLiaisons = divisionGR.getValue('u_funding_approvers') || '';

to use the actual backend field name.

I would test this exact version first.

After updating the Demand, check the logs for:

@@@ getCollaborators result:

@@@ Collaborators returned:

@@@ Collaborators on current before save:

All three should contain the same comma-separated sys_ids.

If "@@@ Collaborators on current before save" contains the correct sys_ids but the Collaborators field is empty after the record is saved, then the Script Include is no longer the issue. That would indicate another Business Rule, Flow, or other server-side process is clearing or replacing Collaborators later in the transaction.

But with the Business Rule running Before and the correct backend field names, the above code should populate the Glide List directly without needing quotes around individual sys_ids.

View solution in original post

5 REPLIES 5

Abhishek Pal
Giga Guru

Hi @gjz ,

I can spot one issue directly in the code you shared.

Your Script Include appears to be returning the correct sys_ids, but in the Business Rule you are doing:

current.setValue('collaborators', liaisonList.join(''));

The problem is:

join('')

This concatenates all sys_ids together without any separator.

For example, instead of storing:

sys_id_1,sys_id_2,sys_id_3

you are effectively creating:

sys_id_1sys_id_2sys_id_3

A List / Glide List field expects a comma-separated list of sys_ids.

So change:

current.setValue('collaborators', liaisonList.join(''));

to:

current.setValue('collaborators', liaisonList.join(','));

Corrected Business Rule:

var dept = current.u_department;

var ppm = new XXX_PPM_Utils();

var liaisonList = ppm.getCollaborators(dept);

gs.info(
'Collaborators returned: ' +
liaisonList
);

if (liaisonList && liaisonList.length > 0) {

current.setValue(
'collaborators',
liaisonList.join(',')
);

} else {

current.setValue(
'collaborators',
''
);
}

There is also an important reason why your log may have looked correct.

When you log an Array like:

gs.info(liaisonList);

JavaScript displays the array values separated by commas.

Therefore the log may look like:

sys_id_1,sys_id_2,sys_id_3

even though the code:

liaisonList.join('')

actually removes those commas before setting the Collaborators field.

This can make the debugging output misleading.

I would also simplify the Script Include.

Currently you have logic similar to:

var listArray = liaison_list
? liaison_list.split(',')
: [];

var uniqueArray =
new ArrayUtil().unique(listArray);

var cleanString =
uniqueArray.join(',');

return uniqueArray;

Notice that you create:

cleanString

but never return it.

You can choose either of these approaches.

OPTION 1 - Return the Array

Script Include:

var listArray = liaison_list
? liaison_list.split(',')
: [];

var uniqueArray =
new ArrayUtil().unique(listArray);

return uniqueArray;

Business Rule:

var liaisonList =
ppm.getCollaborators(dept);

current.setValue(
'collaborators',
liaisonList.join(',')
);

This is perfectly fine.

OPTION 2 - Recommended for this use case

Since the Script Include is ultimately preparing a value for a Glide List field, return the final comma-separated string directly.

Script Include:

var listArray = liaison_list
? liaison_list.split(',')
: [];

var uniqueArray =
new ArrayUtil().unique(listArray);

return uniqueArray.join(',');

Then your Business Rule becomes simpler:

var dept = current.u_department;

var ppm = new XXX_PPM_Utils();

var collaborators =
ppm.getCollaborators(dept);

gs.info(
'Collaborators: ' +
collaborators
);

current.setValue(
'collaborators',
collaborators
);

I would personally use Option 2 because the Script Include returns exactly the format required by the Collaborators field.

There is one more important thing to verify:

Check WHEN your Business Rule executes.

If you are changing a field on the same Demand record using:

current.setValue()

the Business Rule should normally be:

When:
Before

Insert:
As required

Update:
As required

For example:

Before Update

Condition:
Department changes

or whatever condition matches your requirement.

ServiceNow recommends Before Business Rules when setting values on the current record because those changes are included automatically in the database update.

If your Business Rule is:

After

then:

current.setValue('collaborators', ...);

changes the in-memory current record after the original database update has already occurred, so the new Collaborators value will not normally be persisted.

Do NOT solve that by adding:

current.update();

inside an After Business Rule.

That can retrigger Business Rules and introduce recursion/performance problems.

Instead, change the rule to Before when the requirement is simply:

Department changes
-> Calculate Collaborators
-> Save Collaborators on the same Demand

Recommended final design:

Demand Department changes
-> Before Business Rule
-> Call Script Include
-> Build unique collaborator sys_ids
-> Return comma-separated sys_ids
-> Set current.collaborators
-> Normal Demand update saves the value

So I would check these two items in this order:

1. Fix:

liaisonList.join('')

to:

liaisonList.join(',')

2. Verify the Business Rule is Before, not After/Async.

Based on the code shown in your screenshot, the missing comma in join() is definitely an issue and should be corrected first.

Official ServiceNow reference:

Business Rules and Script Includes:
https://www.servicenow.com/docs/r/application-development/business-rules-and-script-includes.html

Related ServiceNow Community solution for populating Glide List fields:

https://www.servicenow.com/community/developer-forum/populating-glide-lists-script-question/td-p/151...

Hope this helps!

If this response helped, please mark it as Helpful.
If it resolves your issue, please Accept it as Solution.

Kind Regards,
Abhishek Pal

@Abhishek Pal - Well, shoot, it still doesn't work.  But, you appear to know a lot more about arrays than I do, so do you mind helping me some more?

 

We have a custom division table and in this table are two fields that are glide_list, Liaisons and Funding Liaisons.  Each field must have at least one user, most have multiple users.  What I need to do is get the users in these fields for the specific dept they belong to (department to division = 1:many).  Because the same user can be in both fields and there may be multiple divisions for the department, there will be duplicates in the list.

 

I switched to your option 2 since I agree, it does seem simpler, but it still doesn't work.  For now, I just have the business rule firing always on an update until I can get it to work.  I'll change it to the correct criteria once it does work.

 

Here is the complete code for the script include: 

    // Get a unique list of liaisons and funding liaisons as collaborators for a demand
    getCollaborators: function(dept) {
        var liaison_list;
        var ctr = 0;

        // Get a list of all liaisons for all divisions
        var l = new GlideRecord('u_cmn_divisions');
        l.addQuery('u_department', dept);
        l.query();
        while(l.next()) {
            if (ctr == 0) {
                liaison_list = l.u_liaisons.toString();
                liaison_list = liaison_list + ',' + l.u_funding_approvers;
                ctr = 1;
            } else {
                liaison_list = liaison_list + ',' + l.u_liaisons;
                liaison_list = liaison_list + ',' + l.u_funding_approvers;
            }
        }

        // // Turn the list into an array and get the unique values and turn it back into a string
        var listArray = liaison_list ? liaison_list.split(',') : [];
        var uniqueArray = new ArrayUtil().unique(listArray);
        gs.log('@@@ uniqueArray: ' + uniqueArray);

        return uniqueArray.join(',');

    },
 
and the changed business rule:
    // set the collaborators if they weren't set on insert.
    var dept = current.u_department;
   
    var ppm = new XXX_PPM_Utils();
    var liaisonList = ppm.getCollaborators(dept);
    gs.log('@@@ liaisonList: ' + liaisonList);
    current.setValue('collaborators', liaisonList);
 
I did try the Google code snippet in a business rule in my PDI and it didn't work there either.  However, when I added single quotes around each sys_id, it worked.
 
Here's the business rule I created in my PDI on the incident table:
 
(function executeRule(current, previous /*null when async*/) {

    // this list works
    var userList = ['be82abf03710200044e0bfc8bcbe5d1c','af2a757b1b74c4500750fc87cc4bcbc1','b682abf03710200044e0bfc8bcbe5d0f'];
    // this list doesn't work
        var userList = [be82abf03710200044e0bfc8bcbe5d1c,af2a757b1b74c4500750fc87cc4bcbc1,b682abf03710200044e0bfc8bcbe5d0f];

    current.setValue('watch_list', userList.join(','));

})(current, previous);
 
With this information, do you see anything else I may be missing?

Hi @gjz ,

Thanks for sharing the complete code. With the additional information, I think we can narrow this down.

The behavior you saw in your PDI with the quotes is expected JavaScript behavior.

This is valid:

var userList = ['be82abf03710200044e0bfc8bcbe5d1c', 'af2a757b1b74c4500750fc87cc4bcbc1'];

The sys_ids have to be quoted because they are JavaScript strings.

This is not valid:

var userList = [be82abf03710200044e0bfc8bcbe5d1c, af2a757b1b74c4500750fc87cc4bcbc1];

Without quotes, JavaScript treats those values as variable names.

However, you do NOT need to add quotes around every sys_id before setting the Collaborators field. A Glide List field can be populated with a comma-separated string of sys_ids.

I would remove ArrayUtil and the GlideElement concatenation from this implementation and use the actual stored values from the Glide List fields.

Replace your getCollaborators method with this:

getCollaborators: function(dept) {

var collaborators = [];
var uniqueUsers = {};

if (gs.nil(dept)) {
    return '';
}

var divisionGR = new GlideRecord('u_cmn_divisions');
divisionGR.addQuery('u_department', dept);
divisionGR.query();

while (divisionGR.next()) {

    var liaisons = divisionGR.getValue('u_liaisons') || '';
    var fundingLiaisons = divisionGR.getValue('u_funding_approvers') || '';

    var users = [];

    if (liaisons) {
        users = users.concat(liaisons.split(','));
    }

    if (fundingLiaisons) {
        users = users.concat(fundingLiaisons.split(','));
    }

    for (var i = 0; i < users.length; i++) {

        var userSysId = users[i] + '';

        if (!userSysId) {
            continue;
        }

        if (!uniqueUsers[userSysId]) {
            uniqueUsers[userSysId] = true;
            collaborators.push(userSysId);
        }
    }
}

var collaboratorString = collaborators.join(',');

gs.info('@@@ getCollaborators result: ' + collaboratorString);

return collaboratorString;

},

Then configure the Demand Business Rule as follows:

When: Before
Insert: Checked, if this should also populate during insert
Update: Checked
Advanced: Checked

For testing, leave the condition empty so that it runs on every update.

Most importantly, make sure this is a BEFORE Business Rule, not After or Async.

Use this exact Business Rule script:

(function executeRule(current, previous) {

var dept = current.getValue('u_department');

gs.info('@@@ Department: ' + dept);

if (gs.nil(dept)) {
    current.setValue('collaborators', '');
    return;
}

var ppm = new XXX_PPM_Utils();
var liaisonList = ppm.getCollaborators(dept);

gs.info('@@@ Collaborators returned: ' + liaisonList);

current.setValue('collaborators', liaisonList || '');

gs.info('@@@ Collaborators on current before save: ' + current.getValue('collaborators'));

})(current, previous);

Do not use current.update() inside this Business Rule.

The important part is that the Business Rule runs Before the Demand is written to the database. The value assigned to current.collaborators will then be included in the same database update.

For example, assume your divisions contain:

Division 1

u_liaisons:
userA,userB

u_funding_approvers:
userC

Division 2

u_liaisons:
userA,userD

u_funding_approvers:
userC

The method will return:

userA,userB,userC,userD

Duplicates are removed before the value is returned.

There is also one thing I noticed in your description that I would verify.

You referred to the second field as "Funding Liaisons", but your script is querying:

u_funding_approvers

Please confirm that u_funding_approvers is really the backend field name of that Glide List field.

If the actual field is something like:

u_funding_liaisons

then change this line:

var fundingLiaisons = divisionGR.getValue('u_funding_approvers') || '';

to use the actual backend field name.

I would test this exact version first.

After updating the Demand, check the logs for:

@@@ getCollaborators result:

@@@ Collaborators returned:

@@@ Collaborators on current before save:

All three should contain the same comma-separated sys_ids.

If "@@@ Collaborators on current before save" contains the correct sys_ids but the Collaborators field is empty after the record is saved, then the Script Include is no longer the issue. That would indicate another Business Rule, Flow, or other server-side process is clearing or replacing Collaborators later in the transaction.

But with the Business Rule running Before and the correct backend field names, the above code should populate the Glide List directly without needing quotes around individual sys_ids.

@Abhishek Pal  - Thank you so much, this worked!

 

Interestingly, it's close to code that was already written in a script include for finding the correct liaisons for request approvals.  I started with that code but I couldn't get it to work, which is why I went down the path I chose originally.

 

Again - thanks!