How to create a list menu ui action that can do multiple attestation using a defined script?

ChuanYanF
Tera Guru

Dear experts,

 

My client would like to have a bulk attest button just like the bulk approve button in the approval table where they can select multiple records in the list then in the list menu they have an approve button to approve all the things required in servicenow. So their requirements are without going into the attestation form and fill in the attestation form which only have two fields
1. Is the control implemented (Yes or No) - Mandatory
2. Explanation
They would like to have the ui action in the list menu which is approve where when click approve it will help them fill in the first question is yes and the explanation field is just leave it blank then changed the state of attestation form to complete. I have tried my own approach to create a new approve ui action list menu and a script include below: 
UI Action Script:

function onBulkApprove() {
  var selectedRecords = g_list.getChecked();
  if (selectedRecords.length === 0) {
    alert("Please select at least one record.");
    return;
  }

  var ga = new GlideAjax('global.BulkAttestationProcessor');
  ga.addParam('sysparm_name', 'processBulkAttestation');
  ga.addParam('sysparm_sys_ids', selectedRecords.join(','));
  ga.getXMLAnswer(function(response) {
    alert(response); // Show confirmation
    g_list.refresh(); // Refresh list
  });
}


Script Include:

var BulkAttestationProcessor = Class.create();
BulkAttestationProcessor.prototype = Object.extendsObject(AbstractAjaxProcessor, {
  processBulkAttestation: function() {
    var sysIds = this.getParameter('sysparm_sys_ids').split(',');
    var count = 0;

    // Get the metric sys_id for the "Is the control implemented?" question
    var metricGr = new GlideRecord('asmt_metric');
    metricGr.addQuery('name', 'Is the control implemented?');
    metricGr.query();
    if (!metricGr.next()) {
      return 'Metric "Is the control implemented?" not found.';
    }
    var metricSysId = metricGr.getUniqueValue();

    for (var i = 0; i < sysIds.length; i++) {
      var instanceId = sysIds[i];

      var instanceGr = new GlideRecord('asmt_assessment_instance');
      if (!instanceGr.get(instanceId)) continue;

      // Check if metric result already exists
      var resultGr = new GlideRecord('asmt_metric_result');
      resultGr.addQuery('metric', metricSysId);
      resultGr.addQuery('instance', instanceId);
      resultGr.query();

      var resultExists = false;
      while (resultGr.next()) {
        resultExists = true;
        resultGr.setValue('actual_value', 1);
        resultGr.update();
        count++;
      }

      // If no result exists, create one manually
      if (!resultExists) {
        var newResult = new GlideRecord('asmt_metric_result');
        newResult.initialize();
        newResult.setValue('metric', metricSysId);
        newResult.setValue('instance', instanceId);
        newResult.setValue('actual_value', 1);
        newResult.insert();
        count++;
      }

      // Mark the instance as complete
      instanceGr.setValue('state', 'complete');
      instanceGr.update();
    }

    return count + " attestation(s) approved.";
  }
});


 

1 ACCEPTED SOLUTION

J Siva
Kilo Patron
Kilo Patron

Hi @ChuanYanF 

You need to set the survey reponse in "asmt_assessment_instance_question" table.
I've modified your script and tested it in my PDI. It's working as expected. Please review and modify as required.

UI action:

function onBulkApprove() {
    var selectedRecords = g_list.getChecked();
    if (selectedRecords.length === 0) {
        alert("Please select at least one record.");
        return;
    } else {
        var ga = new GlideAjax('BulkAttestationProcessor');
        ga.addParam('sysparm_name', 'processBulkAttestation');
        ga.addParam('sysparm_sys_ids', selectedRecords);
        ga.getXMLAnswer(function(response) {
            alert(response); // Show confirmation
            g_list.refresh(); // Refresh list
        });
    }

}

Script include:

var BulkAttestationProcessor = Class.create();
BulkAttestationProcessor.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    processBulkAttestation: function() {
        var sysIds = this.getParameter('sysparm_sys_ids').split(',');
		
        var count = 0;

        // Get the metric sys_id for the "Is the control implemented?" question
        var metricGr = new GlideRecord('asmt_metric');
        metricGr.addQuery('name', 'Is the control implemented?');
        metricGr.query();
        if (!metricGr.next()) {
            return 'Metric "Is the control implemented?" not found.';
        } else {
            var metricSysId = metricGr.getUniqueValue();
        }

        for (var i = 0; i < sysIds.length; i++) {
            var instanceId = sysIds[i];

            var instanceGr = new GlideRecord('asmt_assessment_instance');
            if (!instanceGr.get(instanceId)) continue;

            // Check if metric result already exists
            var resultGr = new GlideRecord('asmt_assessment_instance_question'); //UPDATED
            resultGr.addQuery('metric', metricSysId);
            resultGr.addQuery('instance', instanceId);
            resultGr.query();

            var resultExists = false;
            while (resultGr.next()) {
                resultExists = true;
                resultGr.setValue('value', 1);
                resultGr.update();
                count++;
            }

            // Mark the instance as complete
            instanceGr.setValue('state', 'complete');
            instanceGr.update();
        }

        return count + " attestation(s) approved.";
    },

    type: 'BulkAttestationProcessor'
});

Regards,
Siva

View solution in original post

11 REPLIES 11

Hi Siva, really appreciate your help on this, but I still face a minor problem. So I have created the script include on global application and the asmt_instance is actually under GRC : Profiles, but I have allowed to all application scopes and I renamed the UI Action script to below: which stated the global.BulkAttestationProcessor because initially when I used your script it says access blocks for sn_grc: something like that, but after I renamed it it says null as below

ChuanYanF_0-1746603422543.png

 

function onBulkApprove() {
    var selectedRecords = g_list.getChecked();
    if (selectedRecords.length === 0) {
        alert("Please select at least one record.");
        return;
    } else {
        var ga = new GlideAjax('global.BulkAttestationProcessor');
        ga.addParam('sysparm_name', 'processBulkAttestation');
        ga.addParam('sysparm_sys_ids', selectedRecords);
        ga.getXMLAnswer(function(response) {
            alert(response); // Show confirmation
            g_list.refresh(); // Refresh list
        });
    }

}

  

@ChuanYanF Everything looks good in the above script.
Just verify the script inlcude.

Hi Siva, yes but I dont know why is just the access to this script include is blocked, this is my script include set up

 

ChuanYanF_1-1746604306010.png

 

@ChuanYanF I've created both the UI action and script include in the Global scope. Have you tried logging some messages inside the script include to verify if it's being called by the client script?

If I just follow the script u gave me it shows this error

ChuanYanF_1-1746603523147.pngChuanYanF_2-1746603559347.png