Integration of ServiceNow and Jira via MuleSoft

19 May, 2021 | 7 minutes read

Introduction

Integrating different systems helps businesses get functionalities and features of both systems, thus increasing their efficiency. We are all familiar with ServiceNow, and Jira and the benefits that they offer to companies. But, what if we integrate those two systems? What will the end result be? In the following blog post, we are going to explain how to integrate ServiceNow and Jira via MuleSoft, and in conclusion, we will see the benefits of this integration. We will cover a bi-directional real-time synchronization of ticket creation, field modification, and also attachments and comments synchronization.

How to do the integration

  1. ServiceNow

1.1 Building Business Rules and REST Messages

First things first, we will start by building the Business rules that will trigger the Rest Messages. These Rest Messages will allow communication between ServiceNow and Jira. By using them, we will send the data to the Endpoints that are provided by the MuleSoft team.

On create synchronization

Business rule

This business rule will be triggered on incident creation. The only condition to not trigger the rule is when the incident is created by a specified user, so we can prevent entering in a loop.

(function executeRule(current, previous /*null when async*/ ) {
   var fName1 = "";
    var fType1 = "";
    var base64Data1 = "";
    var attachments = [];
    var attachmentObj = [];
	var commentObj = [];
        var r = new sn_ws.RESTMessageV2('Mule Integration Create', 'Default POST');
    function countProperties(obj) {
        var count = 0;
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop))
                ++count;
        }
        return count;
    }
    var priority = "";
    if (current.urgency == 1) {
        priority = "High";
    } else if (current.urgency == 2) {
        priority = "Medium";
    } else {
        priority = "Low";
    }
    var state = "";
    if (current.state == 1) {
        state = "Open";
    } else if (current.state == 2) {
        state = "In Progress";
    } else if (current.state == 3) {
        state = "On Hold";
    } else if (current.state == 6) {
        state = "Resolved";
    } else if (current.state == 7) {
        state = "Closed";
    } else {
        state = "Canceled";
    }
    var att = new GlideRecord('sys_attachment');
    att.addQuery('table_name', 'incident');
    att.addQuery('table_sys_id', current.sys_id);
    //att.addQuery('u_sent_to_hp','false');
    att.query();
    while (att.next()) {
        var numOfElements = countProperties(att);
        if (numOfElements > 0) {
            fName1 = att.getValue('file_name');
            fType1 = att.getValue('content_type');
            var sa1 = new GlideSysAttachment();
            var binData1 = sa1.getBytes(att);
            base64Data1 = GlideStringUtil.base64Encode(binData1);
            attachmentObj = "\n" + "{" + "\n" + '"' + "attachmentName" + '"' + ": " + '"' + fName1 + '",' + "\n" + '"' + "attachmentContent" + '"' + ": " + '"' + base64Data1 + '"' + "\n" + "}";
            attachments.push(attachmentObj);
            r.setStringParameterNoEscape('attachments', attachments);
        }
    }
   var comments = "";
	var cm = new GlideRecord('sys_journal_field');
   cm.addQuery('element_id', current.sys_id);
   cm.orderByDesc('sys_created_on');
   cm.setLimit(1);
   cm.query();
 while (cm.next()) {
       comments = cm.value;
commentObj = "\n" + "{" + "\n" + '"' + "comment" + '"' + ": " + '"' + comments + '"' + "}";
 }
    var issueKey = current.u_jira_id;
    try {
        r.setStringParameterNoEscape('sys_id', current.sys_id);
        r.setStringParameterNoEscape('incNumber', current.number);
      
            r.setStringParameterNoEscape('issueKey', issueKey);
             if (previous.short_description != current.short_description) {
            r.setStringParameterNoEscape('summary', current.short_description);
        }
        if (previous.description != current.description) {
            r.setStringParameterNoEscape('description', current.description);
        }
        if (previous.due_date != current.due_date) {
            r.setStringParameterNoEscape('due_date', current.due_date);
        }
        if (previous.state != current.state) {
            r.setStringParameterNoEscape('status', state);
        }
        if (previous.urgency != current.urgency) {
            r.setStringParameterNoEscape('priority', priority);
        }
        r.setStringParameterNoEscape('comments', commentObj);
        var response = r.execute();
        var responseBody = response.getBody();
        var httpStatus = response.getStatusCode();
    } catch (ex) {
        var message = ex.message;
    }
})(current, previous);

Rest Message Create

As we mentioned above, we will use the Endpoint provided by the MuleSoft team. Next we will build the request message.

     {
"jiraId": "${issueKey}" ,
"incidentId": "${sys_id}",
"incidentNumber": "${incNumber}",
"shortDescription": "${summary}",
"description": "${description}",
"status": "${status}",
"dueDate": "${due_date}",
"priority": "${priority}",
"comments": [${comments}],
 "attachments": [${attachments}]
      }

On update synchronization

Business rule

This business rule will be triggered on incident updates.

(function executeRule(current, previous /*null when async*/ ) {
    if (previous.short_description != current.short_description || previous.description != current.description || previous.due_date != current.due_date || previous.state != current.state || previous.urgency != current.urgency) {
        var jiraID = current.u_jira_id;
        var r = new sn_ws.RESTMessageV2('Mule Integration Update', 'Default Patch');
        var priority = "";
        if (current.urgency == 1) {
            priority = "High";
        } else if (current.urgency == 2) {
            priority = "Medium";
        } else {
            priority = "Low";
        }
        var state = "";
        if (current.state == 1) {
            state = "Open";
        } else if (current.state == 2) {
            state = "In Progress";
        } else if (current.state == 3) {
            state = "On Hold";
        } else if (current.state == 6) {
            state = "Resolved";
        } else if (current.state == 7) {
            state = "Closed";
        } else {
            state = "Canceled";
        }
        try {
            r.setStringParameterNoEscape('sys_id', '"' + current.sys_id + '"');
            r.setStringParameterNoEscape('incNumber', '"' + current.number + '"');
            r.setStringParameterNoEscape('jiraID', jiraID);
            if (previous.short_description != current.short_description) {
                r.setStringParameterNoEscape('summary', '"' + current.short_description + '"');
            } else {
                r.setStringParameterNoEscape('summary', null);
            }
            if (previous.description != current.description) {
                r.setStringParameterNoEscape('description', '"' + current.description + '"');
            } else {
                r.setStringParameterNoEscape('description', null);
            }
            if (previous.due_date != current.due_date) {
                r.setStringParameterNoEscape('due_date', '"' + current.due_date + '"');
            } else {
                r.setStringParameterNoEscape('due_date', null);
            }
            if (previous.state != current.state) {
                r.setStringParameterNoEscape('status', '"' + state + '"');
            } else {
                r.setStringParameterNoEscape('status', null);
            }
            if (previous.urgency != current.urgency) {
                r.setStringParameterNoEscape('priority', '"' + priority + '"');
            } else {
                r.setStringParameterNoEscape('priority', null);
            }
            var response = r.execute();
            var responseBody = response.getBody();
            var httpStatus = response.getStatusCode();
        } catch (ex) {
            var message = ex.message;
        }
    }
})(current, previous);

Rest Message

Again, we will use the Endpoint provided by MuleSoft and next we will build the content of the Rest Message.

{
"incidentId": ${sys_id},
"incidentNumber": ${incNumber},
"shortDescription": ${summary},
"description": ${description},
"status": ${status},
"dueDate": ${due_date},
"priority": ${priority}
      }

Synchronizing attachments

Add attachment

Business Rule

This business rule will be triggered when a record is added to the attachment table. Similar as previously, we add a condition so we can prevent entering in a loop when making Rest Calls.

(function executeRule(current, previous /*null when async*/ ) {
    var fName1 = current.file_name;
    var fType1 = current.content_type;
    var base64Data1 = "";
    var attachments = [];
    var attachmentObj = [];
    var jiraID = "";
    var r = new sn_ws.RESTMessageV2('Mule Attachment', 'Attach POST');
    var gr = new GlideRecord('incident');
    gr.addQuery('sys_id', current.table_sys_id);
    gr.query();
    while (gr.next()) {
        jiraID = gr.u_jira_id;
    }
    if (jiraID) {
        try {
            var sa1 = new GlideSysAttachment();
            var binData1 = sa1.getBytes(current);
            base64Data1 = GlideStringUtil.base64Encode(binData1);
            attachmentObj = "\n" + "{" + "\n" + '"' + "attachmentName" + '"' + ": " + '"' + fName1 + '",' + "\n" + '"' + "attachmentContent" + '"' + ": " + '"' + base64Data1 + '"' + "\n" + "}" + "\n";
            attachments.push(attachmentObj);
            r.setStringParameterNoEscape('attachments', attachments);
            r.setStringParameterNoEscape('jiraID', jiraID);
            var response = r.execute();
            var responseBody = response.getBody();
            var httpStatus = response.getStatusCode();
        } catch (ex) {
            var message = ex.message;
        }
    }
})(current, previous);

Rest Message

Same as before, we will use the Endpoint provided by the MuleSoft team and next we will build the content of the Rest Message.

{
"attachments": [${attachments}]
}

Delete Attachment

Business Rule

This business rule will be triggered when a record is deleted from the attachment table. Similar as previously, we are adding a condition so we can prevent creating a loop of sending Rest Calls.

(function executeRule(current, previous /*null when async*/ ) {
    var r = new sn_ws.RESTMessageV2('Mule Delete Attachment', 'Delete Attachment');
    var attachmentName = current.file_name;
    var jiraID = "";
    var gr = new GlideRecord('incident');
    gr.addQuery('sys_id', current.table_sys_id);
    gr.query();
    while (gr.next()) {
        jiraID = gr.u_jira_id;
        try {
            r.setStringParameterNoEscape('attachmentid', attachmentName);
            r.setStringParameterNoEscape('jiraID', jiraID);
            var response = r.execute();
            var responseBody = response.getBody();
            var httpStatus = response.getStatusCode();
        } catch (ex) {
            var message = ex.message;
        }
    }
})(current, previous);

Rest Message

We will use the Endpoint provided by the MuleSoft team specified for deleting an attachment.

Synchronizing Comments

Business Rule

This business rule will be triggered when a record is added in the Journal Entry table. Similar as previously, we are adding a condition so we can prevent creating a loop of making rest calls.

(function executeRule(current, previous /*null when async*/ ) {
    var jiraID = "";
    var commentObj = [];
    var r = new sn_ws.RESTMessageV2('Mule Integration Add Comment', 'Default PATCH');
    var att = new GlideRecord('incident');
    att.addQuery('sys_id', current.element_id);
    att.query();
    while (att.next()) {
        jiraID = att.u_jira_id;
    }
    var comments = current.value;
    commentObj = "\n" + "{" + "\n" + '"' + "comment" + '"' + ": " + '"' + comments + '"' + "}";
    try {
        r.setStringParameterNoEscape('jiraID', jiraID);
        r.setStringParameterNoEscape('comments', commentObj);
        var response = r.execute();
        var responseBody = response.getBody();
        var httpStatus = response.getStatusCode();
    } catch (ex) {
        var message = ex.message;
    }
})(current, previous);

Rest Message

Same as before, we will use the Endpoint provided by the MuleSoft team and next we will build the content of the Rest Message.

{
"comments": [${comments}]
}

Jira

In the Jira platform we will use Automation for Jira plug-in for creating flows. The flows will contain triggers, conditions and actions for sending rest messages to MuleSoft.

Issue Created

Here we have a condition, the flow will run if a ServiceNow has not made the changes, the purpose is to not enter in a loop of creating tickets. Next, we have if condition to define if the Issue contains attachment or not. Because the rest message should be different in those scenarios.

Here is the configuration for the web requests:

Issue Updated

Again we have a condition, the flow will run if ServiceNow has not made the changes, the purpose is to not enter in a loop of creating tickets.

Next, we will send a rest message to MuleSoft.

The custom data:

{
	"shortDescription": "{{fields.summary}}",
	"description": "{{fields.description}}",
	"priority": "{{fields.priority.name}}",
	"dueDate": "{{fields.duedate}}"
}

Issue Transitioned

When the status of the issue is changed another flow is triggered.

The web request is sent to an URL provided by MuleSoft:

Custom Data

{
	"status": "{{status.name}}"
}

Comment or Attachment Added

When a comment is added, a new flow is triggered. First, we have a condition, the flow will run if ServiceNow has not made changes, the purpose is to not enter in a loop of creating tickets. Next, we have a condition to check if the comment is just a comment or the comment is attachment, so we are covering both scenarios. Based on this condition we are sending different rest messages.

Attachment

First, we are getting the attachment content:

{{attachment.content.split(",").last.asJsonString}}

Next, sending the web request:

Custom data:

{"attachments" :  [{
"attachmentName": {{attachment.fileName.split(",").last.asJsonString}},
"attachmentId": {{attachContent.substringBeforeLast("/").substringAfterLast("/").asJsonString}}
}]}

Comment

Custom data:

{
	"comments": [
		{
			"comment": "{{comment}}"
		}
	]
}

Comment or attachment deleted

Because there is not a trigger in the Automation for Jira plug-in for this purpose we will use Webhooks.

Conclusion

As we can see from the above, the solution can significantly ease our lives in many aspects. We reduce drastically the time for creating issues and raising incidents. Also, we dramatically reduce the chance of making errors when creating tickets because of the real-time synchronization. Finally, this solution is very customizable, we can synchronize every field based on the client’s needs.