satyendrakgupta
Tera Guru

My requirement was very specific to write a reusable/generic Notification Email Script that can print/return the MRVS in Tabular/Grid format so that when a Request Submitter or Approver receives an Email Notification, he/she can see what they submitted or what to approve. Also, this needs to be easily managed by my Production Support team for any existing or future Catalog Item where there is an MRVS, with the help of a Configuration Change.

So let start with, What is the Multi-Row Variable Set?

Multi-Row Variable Set (MRVS) is a Variable Set of Type Multi-Row. It captures the variable data in a grid/tabular layout.
The article by Brad Tilton describes MRVS pretty well [Multi-row variable set]. Also, you can check ServiceNow docs for details of MRVS.
Now, To get this done. I made the following changes—

Step 1: Add a new List Control in table: Catalog Item/Maintain Item (sc_cat_item)
Application Scope: Global Table: sc_cat_item                Type: List             Column Label: Multi-row variable set in Email
Column Name: u_multi_row_variable_set_in_email         Reference: Variable Set            Reference qual condition: Type - is - Multi Row

and bring this to the Catalog Item form for later Configuration Change. (Note: you can select the MRVS letter in this control)

find_real_file.png

Step 2: A script include. This will help in formatting the MRVS and return that in HTML tabular format.
Name: mrvsFormatter             Application: Global              Accessible from: All application scope

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

    /*
    Call this function like this--
    var objFncs = new mrvsFormatter();
    gs.print(objFncs.getMRVSFormated('7b750d0b1b232814be65c955624bcb40')); //ritm --> sys_id
    */
    getMRVSFormated: function(i_ritmSysId) {
        var ritmSysId = i_ritmSysId,
            mrvsSysIDes = '',
            mrvsTable = '';

        var grRITM = new GlideRecord("sc_req_item");
        if (grRITM.get(ritmSysId)) {
            mrvsSysIDes = grRITM.cat_item.u_multi_row_variable_set_in_email;
            if (mrvsSysIDes != '') {
                var grVarSet = new GlideRecord("item_option_new_set");
                grVarSet.addQuery("sys_idIN" + mrvsSysIDes);
                grVarSet.query();
                while (grVarSet.next()) {
                    var mrvsSysId = grVarSet.sys_id;
                    var mrvsTitle = grVarSet.title;
                    var mrvsInternalName = grVarSet.internal_name;
                    mrvsTable = mrvsTable + "\n" + this.getFormatedTable(mrvsSysId, mrvsInternalName, mrvsTitle, i_ritmSysId);
                }
            }
        }
        return mrvsTable;
    },

    /*
	Call this function like this--
	var objFncs = new mrvsFormatter();
	gs.print(objFncs.getFormatedTable('11e0039bdbc87700bc1a72fc0f9619bf', 'grp_owner_domain', '7b750d0b1b232814be65c955624bcb40'));
	*/
    getFormatedTable: function(i_mrvsSysId, i_mrvsName, i_mrvsTitle, i_ritmSysId) {
        var mrvsSysId = i_mrvsSysId,
            ritmSysId = i_ritmSysId,
            mrvsName = i_mrvsName,
            mrvsTitle = i_mrvsTitle,
            col = '',
            colHeader = '';
        Table = '<table style="width: 100%; border-collapse: collapse; border: 0.0pt; float: left; font-family: arial, sans-serif; font-size: 12px; line-height: 25px; color: #55565A; padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; text-align: justify; vertical-align: top;" cellspacing="0" cellpadding="2">',
            TH = [],
            TR = '',
            rowCount = 0,
            totalColumn = 0;

        var grMultiRow = new GlideRecord('item_option_new');
        grMultiRow.addQuery('variable_set=' + mrvsSysId + '^active=true');
        grMultiRow.orderBy('order');
        grMultiRow.query();
        while (grMultiRow.next()) {
            var variableName = grMultiRow.question_text.toString();
            if (col == '')
                col = variableName;
            else
                col = col + ',' + variableName;
        }
        TH = col.split(',');
        totalColumn = TH.length;

        colHeader = colHeader + '<tr style="height: 25px; text-align: left;">';
        for (var h = 0; h < totalColumn; h++) {
            colHeader = colHeader + '<td style="font-weight:bold; border-collapse: collapse; border-style: solid; border: solid #AEAAAA 1.0pt;">' + TH[h].toString() + '</td>';
        }
        colHeader = colHeader + '</tr>';
        var now_GR = new GlideRecord('sc_req_item');
        if (now_GR.get(ritmSysId)) {
            var mrvs = now_GR.variables[mrvsName]; //.grp_owner_domain;
            //gs.print(mrvs);
            rowCount = mrvs.getRowCount();
            for (var i = 0; i < rowCount; i++) {
                TR = TR + '<tr style="text-align: left; height: 25px;">';
                var row = mrvs.getRow(i);
                var cells = row.getCells();
                for (var k = 0; k < cells.length; k++) {
                    var cell = cells[k];
                    //gs.info(cell.getLabel() + ":" + cell.getCellDisplayValue())
                    var colVal = cell.getCellDisplayValue();
                    TR = TR + '<td style="border-collapse: collapse; border-style: solid; border: solid #AEAAAA 1.0pt;">' + colVal + '</td>';
                }
                TR = TR + '</tr>';
            }
        }

        if (rowCount > 0) {
            Table = Table + '<tr><td colspan=' + totalColumn + ' style="font-weight:bold; border: 0px; text-decoration: underline;">\n\n' + mrvsTitle + '\n</td></tr>';
            Table = Table + colHeader;
            Table = Table + TR;
            Table = Table + '</table>';
        } else
            Table = ''; //If there is no data in mrvs don't print
        return Table;
    },
	
    type: 'mrvsFormatter'
});

Step 3: A Notification Email Scripts. this can be inserted in the Notification and ensures that the MRVS is printed on the notification email.
Name: mrvsEmailScript

(function runMailScript( /* GlideRecord */ current, /* TemplatePrinter */ template,
    /* Optional EmailOutbound */
    email, /* Optional GlideRecord */ email_action,
    /* Optional GlideRecord */
    event) {
    var scCatItemSysId = '', mrvsSysIDs = '', ritmSysId = '', mrvsTable = '';
	scCatItemSysId = current.cat_item.sys_id;
	mrvsSysIDs = current.cat_item.u_multi_row_variable_set_in_email;
	ritmSysId = current.sys_id;

    if (mrvsSysIDs != '') {
        var objFncs = new global.mrvsFormatter();
        mrvsTable = objFncs.getMRVSFormated(ritmSysId); //'7b750d0b1b232814be65c955624bcb40'));
        if (mrvsTable != '') {
            template.print(mrvsTable);
        }
    }
})(current, template, email, email_action, event);

Now we are all set to use this in the Notification.

Step 4: If you don't have a Catalog Item with MRVS. Create it and then select your MRVS (in my case Internal Name: please_link_laptop_or_computer_to_user) in the "Include in". My MRVS looks as below-

find_real_file.png

Step 5: A Notifications (System Notifications -> Email -> Notifications). (I create a custom but you can use any existing Notification).
Name: Request Item Status Change                 Table: sc_req_item                 Send When: Record inserted or updated Updated: Checked            Condition: State – Changes       Who will receive -> Users/Groups in fields -> Request.Opened by          What it will contain -> Subject: Number: ${number}         Message HTML: ${mail_script:mrvsEmailScript}

find_real_file.png

Step 6: Now let's go to the Catalog Item form and select the MRVS (please_link_laptop_or_computer_to_user) in the LIST (ref: Step 1).

Step 7: Submit a Request and copy the RITM number. Go to the Emails table and search your email and click on "Preview Email"

find_real_file.png

Here you go!!!

find_real_file.png

Note: If you don't want a reusable code, only the function getFormatedTable of script include should be suffecient.

 

Comments
MelissaD
Giga Contributor

@satyendrakgupta 

Can you clarify the script include..

(objFncs.getMRVSFormated('7b750d0b1b232814be65c955624bcb40')); //ritm --> sys_id */

&

Call this function like this-- var objFncs = new mrvsFormatter(); gs.print(objFncs.getFormatedTable('11e0039bdbc87700bc1a72fc0f9619bf', 'grp_owner_domain', '7b750d0b1b232814be65c955624bcb40')); */

This seems confusing to me - but I am fairly new to scripting in ServiceNow- just wondering which sys_id would be inserted into each line? 

Thank you

Melissa

 

Chris150
Tera Guru

Looks like this is working for me.  Thanks for putting this together.  Well written!!

satyendrakgupta
Tera Guru

Thank you, Chris! I am glad to hear this.

 

JC S_
Mega Guru

Thanks for this! This is such a life saver.

Would you have an idea how can we combine this with the other normal variables in the RITM so that we can get exactly what the user submitted? We currently get the normal variable value using GlideappVariablePoolQuestionSet but that will omit any MRVS. Now that you have this script for the MRVS, how can we incorporate them both so that whatever order of the normal variables/MRSV will be on the form, that's exactly how it will be printed out by the script.

satyendrakgupta
Tera Guru

Thank you, JC! I am glad, for your word - "Life Saver".

 

Certainly, we can print the variables in the same order. Let me write that for you and post it here.

 

With regards,

Satyendra Kumar

Ed Hefford
Tera Guru

This was fantastic and helped me greatly. Any ideas on how you could CC the attendees name into the email notification?

satyendrakgupta
Tera Guru

I am glad to know that this helped you!

You can specify copied and blind copied recipients by using the email object within a mail script.

email.addAddress("cc", 'er.satyendragupta@gmail.com', 'Satyendra Kumar');

email.addAddress("bcc", 'satyendrakgupta@outlook.com', 'Satyendra Gupta');

 

Reference: Example scripting for email notifications

 

Thanks and regards,

Satyendra Kumar

Mike Lyttle
Giga Contributor

Hi Sat,

Is this able to handle multiple MVRS tables in the one catalog item?

satyendrakgupta
Tera Guru
Hi Mike, Yes, just select those MRVS in the list and it will work. With regards, Satyendra
Mike Lyttle
Giga Contributor

Thanks Sat,

Is this setup currently just set up to run for notifications off the sc_req_item table? or should it also work from the catalog task and/or approval table?

Mike Lyttle
Giga Contributor

Guessing would just need to change the mailscript to refer to the related RITM fields, will give that a go tomorrow

satyendrakgupta
Tera Guru

Hi Mike,

Request, Request Item, Task & Approvals have a very good relationship with each other and hence this solution can work for any one of these. In case you have any challenge implementing it, please share your exact requirement & if I get time, I'll write a solution for you.

 

With regards,

Satyendra Kumar 

jkessler1
Tera Contributor

Awesome work, thank you

Chris150
Tera Guru

I have the same need.  We have notifications that are sent from the sc_task table and the approval table.

Mike Lyttle
Giga Contributor

Hi Chris, this email script should cater for emails that need to send from the request/request item/catalog task and sysapproval table.

 

(function runMailScript(current,template,email, email_action,event) {
    var scCatItemSysId = '', mrvsSysIDs = '', ritmSysId = '', mrvsTable = '';

	var curTaskType = current.getTableName();
	if (curTaskType == 'sysapproval_approver'){
		var relTaskType = current.sysapproval.sys_class_name;
		if (relTaskType == 'sc_req_item'){
			scCatItemSysId = current.sysapproval.ref_sc_req_item.cat_item.sys_id;
			mrvsSysIDs = current.sysapproval.ref_sc_req_item.cat_item.u_multi_row_variable_set_in_email;
			ritmSysId = current.sysapproval.ref_sc_req_item.sys_id;
			}
		else if (relTaskType == 'sc_request'){
			var gr = new GlideRecord('sc_req_item');
			gr.addQuery('request.sys_id', current.sysapproval.ref_sc_request.sys_id);
			gr.query();
			if (gr.next()){
					scCatItemSysId = gr.cat_item.sys_id;
					mrvsSysIDs = gr.cat_item.u_multi_row_variable_set_in_email;
					ritmSysId = gr.sys_id;
			}
		}
	}
	else if (curTaskType == 'sc_task'){
		scCatItemSysId = current.request_item.cat_item.sys_id;
		mrvsSysIDs = current.request_item.cat_item.u_multi_row_variable_set_in_email;
		ritmSysId = current.request_item.sys_id;
	}
	else if (curTaskType == 'sc_req_item'){
		scCatItemSysId = current.cat_item.sys_id;
		mrvsSysIDs = current.cat_item.u_multi_row_variable_set_in_email;
		ritmSysId = current.sys_id;		
	}
	else return;
	
    if (mrvsSysIDs != '') {
        var objFncs = new global.mrvsFormatter();
        mrvsTable = objFncs.getMRVSFormated(ritmSysId); 
        if (mrvsTable != '') {
            template.print(mrvsTable);
        }
    }
})(current, template, email, email_action, event);
Surabhi5
Kilo Contributor

Hi Satyendra,

 

We have a similar requirement however on the approval table.

How can we implement this on approval notifications?

Any help would be really appreciated.

 

Regards,

Surabhi

Surabhi5
Kilo Contributor

Hi Mike,

 

I tried using the above script for approval notification but doesnt seem to be working.

Can you suggest what else do we need to do for the table to be printed on  the approval notification.

Regards,

Surabhi

satyendrakgupta
Tera Guru

Hi Surbhi,

The Request Item's approval also goes to Approval table and as far as Multi-row variable set is concern, it is only available as a Catalog Item's variable. So, this solution should work. May be I need a bit details about your requirement or may be the way you are trying to implement.

 

Thanks and regards,

Satyendra Kumar

Chris150
Tera Guru

Hi Mike,

Thanks for sharing this.  We were able to get this to work for us.

EdN1
Tera Contributor

Hi there @satyendrakgupta ,

We've been able to implement this for one of our business needs. Great work!
However, I'm using this for multiple MVRS tables, and even though they all show up, they don't end up showing up in the same order in the notification email than in the form. 

Form example:
MVRS 1: A1 (order 100)
MVRS2: A2 (order 200)
MVRS3: A3 (order 300)

Notification received:
A3
A1
A2

Any ideas on this?
Thanks for your help!

EdN1
Tera Contributor

In reply to my previous question, I found the answer if anyone had the same question regarding the order of multiple MVRS tables being all over the place. 

Solution: 
In the script include (here called mvrsFormatter) insert the following line : grVarSet.orderBy("order");

 

getMRVSFormated: function(i_ritmSysId,mrvsSysIDs) {
        var ritmSysId = i_ritmSysId,
            mrvsSysIDes = '',
            mrvsTable = '';

        var grRITM = new GlideRecord("sc_req_item");
        if (grRITM.get(ritmSysId)) {
            mrvsSysIDes = mrvsSysIDs;
            if (mrvsSysIDes != '') {
                var grVarSet = new GlideRecord("item_option_new_set");
                grVarSet.addQuery("sys_idIN" + mrvsSysIDes);
	grVarSet.orderBy("order");
                grVarSet.query();

 

 

it's important to include this line in the script include and not the notification email script itself. 

msc
Tera Contributor

@satyendrakgupta 

Hi Satyenragupta,

the script is helpful for me , i got one issue that it is populating the values of those variables set but it is not printing the variable labels , please find the screenshot.

msc_0-1684816206653.png

 

EricG
Kilo Sage

@Mike Lyttle 

 

Thanks for your clarification on this post.   It solved my issue with NO Data in MRV's.

Thank YOu

damzelev
Tera Contributor

My MRVS has several columns, can this be formatted for columns headers to be in the rows and individual set of data added in the columns for sake of readability? 

kumariDiksha
Tera Contributor

HI @satyendrakgupta , 

 

I have tried the notification part of getting the multirow variable set in the notification. In my case, it's showing undefined. 

 

var scCatItemSysId = '', mrvsSysIDs = '', ritmSysId = '', mrvsTable = '';
    scCatItemSysId = current.request_item.cat_item.sys_id;
    mrvsSysIDs = current.request_item.cat_item.vendor_basic_details; // vendor basic details mutiRow variable Set
    ritmSysId = current.request_item.sys_id;

    if (mrvsSysIDs != '') {
        var objFncs = new global.mrvsFormatter();
        mrvsTable = objFncs.getMRVSFormated(ritmSysId); //'7b750d0b1b232814be65c955624bcb40'));
        if (mrvsTable != '') {
            template.print(mrvsTable);
        }
    }
 
My question here, mrvsSysIDs this variable is showing undefined in my case. 
 
To let you know, in my situation the mutirow VS is hidden in the catalog item and the data in MRVS will be filled by the sc_task assignee only. variable is visible in one task only specifically. 
once the data is filled in the task then it should trigger a notification with all the data of MRVS. 
 
your
DK44
Tera Expert

Hello,

I have made a simplified version of the script.
In this script you dont need to add a field and to list the variable sets you just need to call the getMRVSFormatted function passing the sys_id of the RITM and the script will look for MVRS and for regular variables and will put them into tables.

Example of calling the the script include from mailscript:

    mrvsTable = new global.miscUtilities().getMRVSFormatted(ritmSysID);
    template.print(mrvsTable);




Script Include:

    getMRVSFormatted: function(ritmSysId) {
        var outputTable = ''; // Combined table for MRVS and standard variables
        var catalogItemSysId = this._getCatalogItemSysId(ritmSysId);
        // gs.info("Catalog Item Sys ID: " + catalogItemSysId);
        if (catalogItemSysId) {
            var mrvsSysIds = this._getMRVSFromCatalogItem(catalogItemSysId);
            //  gs.info("MRVS Sys IDs: " + mrvsSysIds.join(", "));
            if (mrvsSysIds.length > 0) {
                mrvsSysIds.forEach(function(mrvsSysId) {
                    var mrvsDetails = this._getMRVSDetails(mrvsSysId);
                    if (mrvsDetails) {
                        outputTable += this.getFormattedTable(
                            mrvsDetails.sys_id,
                            mrvsDetails.internal_name,
                            mrvsDetails.title,
                            ritmSysId
                        );
                    }

                }, this);
            }
        }

        // Step 3: Process standard variables regardless of MRVS existence
        var standardVarTable = this._getStandardVariablesTable(ritmSysId);
        if (standardVarTable) {
            outputTable += '<br>' + standardVarTable; // Append standard variable table
        }

        // Step 4: Return the combined output or an empty string if no data exists
        return outputTable || 'No data available.';
    },

    /*
    Helper function to retrieve the catalog item (cat_item) sys_id for the given RITM sys_id.
    */
    _getCatalogItemSysId: function(ritmSysId) {
        var grRITM = new GlideRecord('sc_req_item');
        if (grRITM.get(ritmSysId)) {
            return grRITM.cat_item.sys_id.toString();
        }
        return '';
    },

    /*
    Helper function to retrieve all MRVS sys_ids associated with a given catalog item sys_id.
    */
    _getMRVSFromCatalogItem: function(catalogItemSysId) {
        var mrvsSysIds = [];
        var grIoSetItem = new GlideRecord('io_set_item');
        grIoSetItem.addQuery('sc_cat_item', catalogItemSysId);
        grIoSetItem.query();
        while (grIoSetItem.next()) {
            mrvsSysIds.push(grIoSetItem.variable_set.sys_id.toString());
        }
        return mrvsSysIds;
    },

    /*
    Helper function to retrieve details (title, internal name) of an MRVS by its sys_id.
    */
    _getMRVSDetails: function(mrvsSysId) {
        var grVarSet = new GlideRecord('item_option_new_set');
        if (grVarSet.get(mrvsSysId)) {
            return {
                sys_id: grVarSet.sys_id.toString(),
                title: grVarSet.title.toString(),
                internal_name: grVarSet.internal_name.toString()
            };
        }
        return null;
    },

    getFormattedTable: function(mrvsSysId, mrvsName, mrvsTitle, ritmSysId) {
        var colHeaders = '',
            rowData = '',
            table = '',
            standardVarTable = '',
            finalOutput = '';

        // Step 1: Process the MRVS variables (unchanged logic from previous function)
        var columns = this._getMRVSColumns(mrvsSysId);
        // gs.info("Columns for MRVS Sys ID [" + mrvsSysId + "]: " + columns.join(", "));

        if (columns.length > 0) {
            // Construct column headers
            colHeaders = '<tr>';
            columns.forEach(function(column) {
                colHeaders += '<th style="border: 1px solid #AEAAAA; text-align: left; padding: 4px;">' + column + '</th>';
            });
            colHeaders += '</tr>';

            // Retrieve the RITM record
            var grRITM = new GlideRecord('sc_req_item');
            if (grRITM.get(ritmSysId)) {
                var mrvs = grRITM.variables[mrvsName];
                var rowCount = mrvs.getRowCount();
                if (rowCount > 0) {
                    // Loop through MRVS rows and construct data rows
                    for (var i = 0; i < rowCount; i++) {
                        var row = mrvs.getRow(i);
                        var cells = row.getCells();
                        rowData += '<tr>';
                        cells.forEach(function(cell) {
                            var cellValue = cell.getCellDisplayValue() || ''; // Handle empty values
                            // gs.info("Cell Value: " + cellValue);
                            rowData += '<td style="border: 1px solid #AEAAAA; padding: 4px;">' + cellValue + '</td>';
                        });
                        rowData += '</tr>';
                    }
                }
            }

            // Construct the MRVS table
            if (rowData) {
                table =
                    '<table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px; color: #55565A;">' +
                    '<thead>' +
                    '<tr><th colspan="' + columns.length + '" style="text-align: left; font-weight: bold; text-decoration: underline; padding: 4px;">' +
                    mrvsTitle +
                    '</th></tr>' +
                    colHeaders +
                    '</thead>' +
                    '<tbody>' +
                    rowData +
                    '</tbody>' +
                    '</table>';
            }
        }

        // Return the MRVS table or an empty string if no data
        return table || '';
    },

    /*
    Helper function to retrieve column names for an MRVS.
    */
    _getMRVSColumns: function(mrvsSysId) {
        var columns = [];
        var grVariables = new GlideRecord('item_option_new');
        grVariables.addQuery('variable_set', mrvsSysId);
        grVariables.addQuery('active', true);
        grVariables.orderBy('order');
        grVariables.query();
        while (grVariables.next()) {
            columns.push(grVariables.question_text.toString());
        }
        return columns;
    },

    _getStandardVariablesTable: function(ritmSysId) {
        var standardRowData = '';

        // Use GlideappVariablePoolQuestionSet to fetch standard variables
        var set = new GlideappVariablePoolQuestionSet();
        set.setRequestID(ritmSysId); // Set the RITM ID to fetch variables
        set.load();

        var questions = set.getFlatQuestions();
        for (var i = 0; i < questions.size(); i++) {
            var label = questions.get(i).getLabel(); // Question label
            var value = questions.get(i).getDisplayValue(); // Question answer
            if (value) {
                standardRowData +=
                    '<tr>' +
                    '<td style="border: 1px solid #AEAAAA; padding: 4px; font-weight: bold;">' +
                    label +
                    '</td>' +
                    '<td style="border: 1px solid #AEAAAA; padding: 4px;">' +
                    value +
                    '</td>' +
                    '</tr>';
            }
        }

        // Construct the HTML table for standard variables
        if (standardRowData) {
            return (
                '<table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px; color: #55565A; margin-top: 20px;">' +
                '<thead>' +
                '<tr>' +
                '<th colspan="2" style="text-align: left; font-weight: bold; text-decoration: underline; padding: 4px;">Questions:</th>' +
                '</tr>' +
                '<tr>' +
                '<th style="border: 1px solid #AEAAAA; text-align: left; padding: 4px;">Question</th>' +
                '<th style="border: 1px solid #AEAAAA; text-align: left; padding: 4px;">Answer</th>' +
                '</tr>' +
                '</thead>' +
                '<tbody>' +
                standardRowData +
                '</tbody>' +
                '</table>'
            );
        }

        return ''; // Return empty if no standard variables exist
    },
 
Version history
Last update:
‎04-27-2021 04:30 AM
Updated by: