This article is a supplement to the ServiceNow documentation. For full documentation please refer ServiceNow official website
Checkout our NEW Video Channel you can like and subscribe too!

Introduction

ServiceNow with Jira is most commonly used integration in many companies.Let us see in this post few essential setup requirement to integrate Jira and ServiceNow.

Functional Requirement

  1. If we create a ticket in ServiceNow then corresponding ticket will get created in Jira.
  2. When we add comments in ServiceNow then the corresponding ticket in Jira will be updated with this comment in comment history.
  3. When we add a comment in Jira then corresponding ServiceNow ticket will get updated.
  4. Any status change in ServiceNow or Jira will get synced.
  5. When Jira ticket gets created then the Jira ticket url will get added in ServiceNow comment section.
  6. Also the Jira ticket number will be updated in correlation field in ServiceNow. In two ways we can create Jira ticket in ServiceNow
    • We need to create Jira ticket button - button label create Jira ticket
    • When user selects Jira Issue from the category dropdown.
  7. There will be an additional button as View Jira ticket. Once user clicks the button a popup window opens up and displays corresponding Jira ticket.

Let us now explore the various components and steps to setup Jira in Snow

Settings

jiraintegration0105202060.PNG jiraintegration0105202061.PNG jiraintegration0105202062.PNG

Business Rules

Create a Business Rule that addComment

jiraintegration0105202030.PNG jiraintegration0105202031.PNG jiraintegration0105202032.PNG

Condition:
gs.getProperty("com.snc.integration.jira.enable_integration") == "true" && gs.getSession().isInteractive() && current.value.toString().startsWith('Status:') == false

jiraintegration0105202033.PNG

  var j = new JiraIntegration();

  var gr = new GlideRecord("incident");
  gr.get(current.element_id);
  if( gr.isValid() ){
    j.debug("correlation display: " + gr.correlation_display);
    j.debug("Should be: " + j.CORRELATION_DISPLAY);
    if( gr.correlation_display == j.CORRELATION_DISPLAY ){
        j.debug("Comments were added, add them to the issue");
        var comment = new Object();
        comment.body = ""+current.value;
        j.addComment(comment, ""+gr.correlation_id);
    }
  }

Create a Business Rule that changeStatus

jira050120203.PNG jira050120204.PNG jira050120205.PNG

Condition:current.correlation_display == JiraIntegration.prototype.CORRELATION_DISPLAY
&& gs.getSession().isInteractive() 
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"

jira050120206.PNG jira050120207.PNG

var j = new JiraIntegration();
j.debug("State changed?  " + current.state.changes());
//For now, handling only Resolved & Closed.  Jira is strict on the transitions we can do by default
if( current.state.changes()){
   j.debug("incident state changed, going to see if we should update Jira");
   if(current.state  <= 5 && previous.state <= 5){
      j.debug("Going from a SN Open type state to another open state. No need to change Jira state");
   } else {
      j.debug("Incident state changed....set status transition in Jira Issue");
      
      var status = new Object();
      status.transition = new Object();
      status.transition.id = j.getJiraStatusID(current.state);
      /* Not Required for POV
      if(current.state > 5){
         status.fields = new Object();
         status.fields.resolution = new Object();
         status.fields.resolution.id = "1"; //hard coding "Fixed" resolution for PoV
      }
       */
      
      if(j.verbose == "true"){
         JSUtil.logObject(status);
      }
      
      j.debug("Jira status info set.  Ready to set status in Jira");
      j.changeStatus(status, current.correlation_id);
      
   }
}

Create a Business Rule that changeStatus (new)

jiraintegration010520208.PNG jiraintegration010520209.PNG jiraintegration0105202010.PNG

Condition:current.correlation_display == JiraIntegration.prototype.CORRELATION_DISPLAY 
&& gs.getSession().isInteractive()  
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"

jiraintegration0105202011.PNG jiraintegration0105202012.PNG

var j = new JiraIntegration();
j.debug("State changed? " + current.state.changes());
//For now, handling only Resolved & Closed.  Jira is strict on the transitions we can do by default
if( current.state.changes()){
    j.debug("incident state changed, going to see if we should update Jira");
    
    var pstate = previous.state;
    var cstate = current.state;
    var transid = 21;
    if( pstate == 1 && cstate == 2 ) transid=11;
        if( pstate == 1 && cstate == 6 ) transid=21;
        if( pstate == 2 && cstate == 1 ) transid=31;
        if( pstate == 2 && cstate == 6 ) transid=41;
        if( pstate == 6 && cstate == 1 ) transid=51;
        if( pstate == 6 && cstate == 2 ) transid=61;
         
    j.debug("Incident state changed....set status transition in Jira Issue");
    
    var status = new Object();
    status.transition = new Object();
    status.transition.id = transid;

    if(j.verbose == "true"){
        JSUtil.logObject(status);
    }
    
    j.debug("Jira status info set.  Ready to set status in Jira");
    j.changeStatus(status, current.correlation_id);

}

Create a Business Rule that CreateIssue

jiraintegration0105202038.PNG jiraintegration0105202034.PNG jiraintegration0105202035.PNG

Condition:current.category.getDisplayValue()=="Jira Issue" 
&& gs.getSession().isInteractive() 
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"

jiraintegration0105202036.PNG jiraintegration0105202037.PNG

var j = new JiraIntegration();

j.debug("New Incident of Category 'Jira Issue'...create a Jira Issue");

var issue = new Object();
issue.fields = new Object();
issue.fields.project = new Object();
issue.fields.project.key = gs.getProperty("com.snc.integration.jira.project");
issue.fields.summary = ""+current.short_description;
//issue.fields.description = ""+current.comments;
issue.fields.issuetype = new Object();
issue.fields.issuetype.name=""+current.subcategory;
issue.fields['customfield_'+j.sysidField] = j.getSysIdPrefix()+":"+current.sys_id;
issue.fields.priority = new Object();
issue.fields.priority.id = j.getJiraPriority(""+current.priority);

if(j.verbose == "true"){
   JSUtil.logObject(issue);
}

j.debug("Jira fields set.  Ready to create issue in Jira");
var jiraIssue = j.createIssue(issue);
j.debug("Jira issue created: " + jiraIssue.self);

current.correlation_id = jiraIssue.key;
current.correlation_display = j.CORRELATION_DISPLAY;

var wn = "A corresponding JIRA Issue has been created:\n";
wn += "ID: "+jiraIssue.id+"\n";
wn += "Issue Number: "+jiraIssue.key+"\n";
wn += "Issue URL: "+gs.getProperty('com.snc.integration.jira.base_jira_instance_url')+"/browse/"+jiraIssue.key+"\n";
current.work_notes = wn;
current.update();

j.addExistingComments(current.sys_id, current.correlation_id);

Create a Business Rule that ModifyIssue

jiraintegration0105202020.PNG jiraintegration0105202017.PNG jiraintegration0105202018.PNG

Condition:current.correlation_display == JiraIntegration.prototype.CORRELATION_DISPLAY 
&& gs.getSession().isInteractive() 
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"

jiraintegration0105202039.PNG

if( current.short_description.changes() ||
   current.subcategory.changes() ||
   current.priority.changes())
{
   var j = new JiraIntegration();
   j.debug("Incident changed....modify the Jira Issue");
   var issue = new Object();
   issue.fields = new Object();
   issue.fields.summary = ""+current.short_description;
   issue.fields.issuetype = new Object();
   issue.fields.issuetype.name=""+current.subcategory;
   issue.fields.priority = new Object();
   issue.fields.priority.id = j.getJiraPriority(""+current.priority);
   
   if(j.verbose == "true"){
      JSUtil.logObject(issue);
   }
   
   j.debug("Jira fields set.  Ready to modify issue in Jira");
   var jiraIssue = j.modifyIssue(issue, current.correlation_id);
   if(jiraIssue.getStatusCode()!="204"){
      current.work_notes = "ERROR posting changes back to Jira.";
   }
   
}

Create a Business Rule that PullSettings

jiraintegration0105202040.PNG jiraintegration0105202041.PNG jiraintegration0105202042.PNG

Condition:current.name=="com.snc.integration.jira.pull_data" || 
current.name=="com.snc.integration.jira.pull_interval" ||  
current.name=="com.snc.integration.jira.pull_padding"

jiraintegration0105202043.PNG

var j = new JiraIntegration();   
j.debug("Pull Settings Business Rule on : "+current.name + " -- with value: " + current.value);
var pullDataName = "com.snc.integration.jira.pull_data";
var pullIntervalName = "com.snc.integration.jira.pull_interval";
var pullPaddingName = "com.snc.integration.jira.pull_padding";


var scheduleScript = "/*   *** DO NOT EDIT --- ALL CHANGES WILL EVENTUALLY BE LOST *** */\n";
scheduleScript += 'if( gs.getProperty("com.snc.integration.jira.enable_integration")=="true"){\n';
scheduleScript += '  var j = new JiraIntegration();\n';
scheduleScript += '  j.checkJiraForIssueUpdates(';
scheduleScript += (parseInt(gs.getProperty(pullIntervalName,2))+parseInt(gs.getProperty(pullPaddingName,1))) + ');\n}';

if( current.name == pullDataName ){
   if( current.value == "true" ){
      //enabling data pulling
      setupSchedule(true);
   } else {
      disableSchedule();
   }
} else {
   if( current.name == pullIntervalName ){
	  j.debug("Data changed for the PULL INTERVAL");
      setupSchedule(false);
      changePullInterval();
   }
   if( current.name == pullPaddingName ){
      setupSchedule(false);
      changePullPadding();
   }
}


function setupSchedule(enableSchedule){
   var schedName = "Jira Integration - Get Updates";
   var sched = getScheduleQueryObject(schedName);
   var exists = true;
   if(!sched.next()){
	  j.debug("No schedule exists...creating one");
	  sched = new GlideRecord("sysauto_script");
      sched.initialize();
	  j.debug("Going to set the schedule name to: " + schedName);
      sched.name = schedName;
	  j.debug("Setting the Run Type");
      sched.run_type = "periodically";
	  j.debug("setting exists to false");
      exists = false;
   }
   j.debug("Enable the schedule? " + enableSchedule);
   if(enableSchedule){
	  j.debug("We will activate this schedule as well.");
      sched.active = 1;
   }
   sched.script = scheduleScript;
   sched.run_period.setDateNumericValue(getDurationObject(gs.getProperty(pullIntervalName,"2")));
   j.debug("Scheduled Job exists? "+exists);
   if(exists){
      sched.update();
   } else {
      sched.insert();
   }
}

function disableSchedule(){
   var sched = getScheduleQueryObject();
   if(sched.next()){
	  sched.active = 0;
	  sched.update();
   }
}

function changePullInterval(){
   var sched = getScheduleQueryObject();
   if(sched.next()){
	  sched.run_period = getDurationObject(gs.getProperty(pullIntervalName,"2"));
	  sched.update();
   }
}

function changePullPadding(){
   var sched = getScheduleQueryObject();
   if(sched.next()){
	  sched.script = scheduleScript;
	  sched.update();
   }   
}

function getScheduleQueryObject(schedName){
//   var schedName = "Jira Integration - Get Updates";
   var sched = new GlideRecord("sysauto_script");
   sched.addQuery("name", schedName);
   sched.query();
   return sched;
}
function getDurationObject(minutes){
   var d = new GlideDateTime();
   d.setNumericValue(0);
   d.addSeconds(minutes*60);
   return d.getNumericValue();
}

Create a Business Rule that to update RestCredentials

jiraintegration0105202044.PNG jiraintegration0105202045.PNG jiraintegration0105202046.PNG

Condition:current.name=="com.snc.integration.jira.jira_api_user" || 
current.name=="com.snc.integration.jira.jira_api_password"

jiraintegration0105202047.PNG

var r = new GlideRecord("sys_rest_message_fn");
r.addEncodedQuery("rest_messageSTARTSWITHJira Issue");
r.query();
while(r.next()){
   if(current.name == "com.snc.integration.jira.jira_api_user"){
	  r.basic_auth_user = current.value;
   } else {
	  r.basic_auth_password = current.value;
   }
   r.update();
}

Create a Business Rule that sync Comments

jiraintegration0105202048.PNG jiraintegration0105202049.PNG jiraintegration0105202050.PNG

Condition:!current.u_synced

jiraintegration0105202051.PNG

var gr = new GlideRecord('incident');
gr.addQuery("correlation_id", current.u_issue_id);
gr.query();
if(gr.next()){
   gr.comments = ""+current.u_comment;
   gr.update();
   current.u_synced = true;
   current.update();
}

Or:

(function executeRule(current, previous /*null when async*/ ) {
    var jiraUpdate = '';

    var _prob = new GlideRecord('problem');
    _prob.addQuery("correlation_id", current.u_issue_id.toString());
    _prob.query();
    if (_prob.next()) {
        var _href = gs.getProperty('com.snc.integration.jira.base_jira_instance_url') + "/browse/" + current.u_issue_id;
        jiraUpdate = "Update from JIRA : \n";
        jiraUpdate += "ID: " + current.u_comment_id + "\n";
        jiraUpdate += "Issue Number: " + current.u_issue_id + "\n";
        jiraUpdate += "Issue URL: [code]<a href='" + _href + "' target='_blank'>" + _href + "</a>[/code]\n";


        _prob.comments = jiraUpdate + "Comment : " + current.u_comment.toString();
        _prob.update();
    } else {

        var gr = new GlideRecord('incident');
        gr.addQuery("correlation_id", current.u_issue_id);
        gr.query();
        if (gr.next()) {
            jiraUpdate = "Update from JIRA : \n";
            jiraUpdate += "ID: " + current.u_comment_id + "\n";
            jiraUpdate += "Issue Number: " + current.u_issue_id + "\n";
            jiraUpdate += "Issue URL: " + gs.getProperty('com.snc.integration.jira.base_jira_instance_url') + "/browse/" + current.u_issue_id + "\n";

            gr.comments = jiraUpdate + "Comment : " + current.u_comment;
            gr.update();

        }
    }
    current.u_synced = true;
    current.update();

})(current, previous); 

Script Include

Create a Script Include - JiraIntegration

jira050120201.PNG

var JiraIntegration = Class.create();
JiraIntegration.prototype = {
    //REST_ISSUE_PATH:  "/rest/api/2/issue",
    LOGGER_SOURCE: "Jira Integration",
    CORRELATION_DISPLAY: "Jira Integration",
    initialize: function() {
        this.verbose = gs.getProperty("com.snc.integration.jira.debug", "false");
        this.midServer = gs.getProperty("com.snc.integration.jira.midserver", "");
        this.sysidField = gs.getProperty("com.snc.integration.jira.sysid_field", "10020");
    },

    debug: function(msg) {
        if (this.verbose == "true") {
            gs.log(msg, this.LOGGER_SOURCE);
        }
    },

    createIssue: function(issue) {
        var json = new JSON();
        var body = json.encode(issue);

        var r = new RESTMessage('Jira Issue', 'post');
        r.setBasicAuth(gs.getProperty('com.snc.integration.jira.jira_api_user'), gs.getProperty('com.snc.integration.jira.jira_api_password'));

        this.debug("BODY to be Submitted: \n" + body);
        r.setXMLParameter('issuebody', body);
        r.setStringParameter('base_endpoint', gs.getProperty('com.snc.integration.jira.base_jira_instance_url'));

        var res = this._submitBodyTypeRequest(r, true);
        return res;
    },

    modifyIssue: function(issue, key) {
        var json = new JSON();
        var body = json.encode(issue);
        var r = new RESTMessage('Jira Issue', 'put');
        r.setBasicAuth(gs.getProperty('com.snc.integration.jira.jira_api_user'), gs.getProperty('com.snc.integration.jira.jira_api_password'));
        this.debug("BODY to be Submitted: \n" + body);
        r.setXMLParameter('issuebody', body);
        r.setStringParameter('base_endpoint', gs.getProperty('com.snc.integration.jira.base_jira_instance_url'));
        r.setStringParameter("issueKey", key);
        var res = this._submitBodyTypeRequest(r, false);
        return res;

    },

    addComment: function(comment, key) {
        var json = new JSON();
        var body = json.encode(comment);
        var r = new RESTMessage('Jira Issue Comment', 'post');
        r.setBasicAuth(gs.getProperty('com.snc.integration.jira.jira_api_user'), gs.getProperty('com.snc.integration.jira.jira_api_password'));
        this.debug("Comment BODY to be Submitted: \n" + body);
        r.setXMLParameter('commentBody', body);
        r.setStringParameter('base_endpoint', gs.getProperty('com.snc.integration.jira.base_jira_instance_url'));
        r.setStringParameter("issueKey", key);
        resBody = this._submitBodyTypeRequest(r, true);

        this.debug("Handling the response data from Jira");
        if (resBody) {
            if (this.verbose == "true") {
                JSUtil.logObject(resBody);
            }
            this.createCommentSyncRecord(key, resBody.id, resBody.body, 1);
        }
    },

    getComments: function(key) {
        var r = new RESTMessage('Jira Issue Comment', 'get');
        r.setBasicAuth(gs.getProperty('com.snc.integration.jira.jira_api_user'), gs.getProperty('com.snc.integration.jira.jira_api_password'));
        this.debug("Retreiving comments for jira ticket: \n" + key);
        if (this.midServer) {
            r.setMIDServer(this.midServer);
        }
        r.setStringParameter('base_url', gs.getProperty('com.snc.integration.jira.base_jira_instance_url'));
        r.setStringParameter("issueKey", key);
        response = this._executeRest(r);
        this.debug("GetComments response: " + response);
        return response;
    },

    changeStatus: function(status, key) {
        var json = new JSON();
        var body = json.encode(status);

        var r = new RESTMessage('Jira Issue Transition', 'post');
        r.setBasicAuth(gs.getProperty('com.snc.integration.jira.jira_api_user'), gs.getProperty('com.snc.integration.jira.jira_api_password'));
        r.setStringParameter('base_url', gs.getProperty('com.snc.integration.jira.base_jira_instance_url'));
        r.setStringParameter('issue_key', key);
        r.setXMLParameter('body', body);
        this._submitBodyTypeRequest(r, false);
    },

    createCommentSyncRecord: function(issueID, commentID, body, synced) {
        var gr = new GlideRecord("u_jira_comment");
        gr.initialize();
        gr.u_issue_id = issueID;
        gr.u_comment_id = commentID;
        gr.u_comment = body;
        gr.u_synced = synced;
        gr.insert();
    },

    _submitBodyTypeRequest: function(r, waitForResponse) {
        if (waitForResponse == null) {
            waitForResponse = true;
        }
        if (this.midServer) {
            r.setMIDServer(this.midServer);
        }
        if (waitForResponse) {
            var response = this._executeRest(r);
            if (!response) {
                return null;
            }
            this.debug("RESPONSE: \n" + response.getBody());

            var parser = new JSONParser();
            var jiraIssue = parser.parse(response.getBody());

            return jiraIssue;
        } else {
            r.execute();
        }
    },

    getJiraPriority: function(p) {
        //For Now the ID's match up well with OOB values, so keeping them the same ID's
        var jp = p;
        this.debug("Jira priority equivalent to SNC Priority " + p + " is: " + jp);
        return jp;
    },

    getJiraStatusID: function(snID) {
        if (snID == "6") return "5";
        if (snID == "7") return "701";
        return "3"; //else guess that it is reopen
    },

    getSysIdPrefix: function() {
        //if( gs.getProperty("com.snc.integration.jira.pull_data") == "true" ){
        return gs.getProperty("com.snc.integration.jira.pull_instance_identifier", "SNC");
        //}
        return "";
    },

    _executeRest: function(r) {
        if (this.midServer) {
            this.debug("Executing request synchronously via MID Server: " + this.midServer);
            return this.executeMidSync(r);
        } else {
            this.debug("Executing request synchronously and directly");
            return this.executeSync(r);
        }
    },

    executeSync: function(r) {
        return r.execute();
    },

    executeMidSync: function(r) {
        r.execute();

        var k = 1;
        var response = r.getResponse();
        this.debug("Initial response: " + response);
        this.debug("Going to loop for a response from MID Server");
        while (response == null) {
            this.debug("waiting ... " + k + " seconds");
            response = r.getResponse(1000); //wait 1 second before looking for the response
            k++;

            if (k > 30) {
                gs.log("ERROR: Web Service did not respond after 30 seconds", this.LOGGER_SOURCE);
                break; // service did not respond after 30 tries
            }
        }
        if (this.verbose) {
            JSUtil.logObject(response);
        }
        return response;
    },

    checkJiraForIssueUpdates: function(minutesAgo) {
        var r = new RESTMessage('Jira Issue Search', 'get');
        r.setBasicAuth(gs.getProperty('com.snc.integration.jira.jira_api_user'), gs.getProperty('com.snc.integration.jira.jira_api_password'));
        r.setStringParameter('base_url', gs.getProperty('com.snc.integration.jira.base_jira_instance_url'));
        r.setXMLParameter('jqlstring', 'updated>-' + minutesAgo + 'm and cf[' + this.sysidField + ']~"' + this.getSysIdPrefix() + '*"'); //For newer versions of an instance
        //r.setXMLParameter('jqlstring', 'updated%3E-'+minutesAgo+'m%20and%20cf%5B'+this.sysidField+'%5D%7E%22'+this.getSysIdPrefix()+'*%22');  //For older versions of an instance
        r.setStringParameter('fields', 'id,key,summary,issuetype,priority,status');
        if (this.midServer) {
            r.setMIDServer(this.midServer);
        }
        var response = this._executeRest(r);
        if (response) {
            var responseBodyString = response.getBody();
            this.debug("Search Response: " + responseBodyString);
            this.debug("Apply the search result the issues import set table");
            this.applyJiraSearchResultStringToImportSet(responseBodyString);
        }
    },

    applyJiraSearchResultStringToImportSet: function(json) {
        var parser = new JSONParser();
        var obj = parser.parse(json);
        if (this.verbose == "true") {
            JSUtil.logObject(obj);
        }

        for (var i = 0; i < obj.total; i++) {
            var is = new GlideRecord("u_jira");
            is.initialize();
            is.u_issue_number = obj.issues[i].key;
            is.u_issue_type = obj.issues[i].fields.issueType.name;
            is.u_status = obj.issues[i].fields.status.id;
            is.u_summary = obj.issues[i].fields.summary;
            var sysid = is.insert();
            this.debug("New IS Record created: " + sysid);
        }
    },

    requestCommentsForIssues: function(json) {
        var parser = new JSONParser();
        var obj = parser.parse(json);
        if (this.verbose == "true") {
            JSUtil.logObject(obj);
        }
        for (var i = 0; i < obj.total; i++) {
            this.requestCommentsForSingleIssue(obj.issues[i].key);
        }
    },

    requestCommentsForSingleIssue: function(issueKey) {
        var parser = new JSONParser();
        var response = this.getComments(issueKey);
        var jsonComments = response.getBody();

        var comments = parser.parse(jsonComments);
        this.debug("Comments: " + comments);
        if (this.verbose == "true") {
            this.debug("Comments Object for key: " + issueKey);
            JSUtil.logDebug(comments);
        }
        for (var c = 0; c < comments.total; c++) {
            var is = new GlideRecord("u_jira_comment_sync");
            is.initialize();
            is.u_issue_id = issueKey;
            is.u_comment_id = comments.comments[c].id;
            is.u_comment = comments.comments[c].body;
            is.u_synced = false;
            var sysid = is.insert();
            this.debug("New IS Record created for comments: " + sysid);
        }
    },

    addExistingComments: function(incID, corrID) {
        var journal = new GlideRecord("sys_journal_field");
        journal.addEncodedQuery("element=comments^element_id=" + incID);
        journal.query();
        while (journal.next()) {
            var comment = new Object();
            comment.body = "" + journal.value;
            this.addComment(comment, corrID);
        }
    },

    type: 'JiraIntegration'
}

Create a Script Include - CreateJiraTicket

jira050120202.PNG

var CreateJiraTicket = Class.create();
CreateJiraTicket.prototype = {
    fnCreateJiraTicket: function(current) {

        var j = new global.JiraIntegration();

        j.debug("New Incident of Category 'Jira Issue'...create a Jira Issue");
        var _priority = current.priority.toString();
        _priority = parseInt(_priority);
        if (_priority == 5)
            _priority = 4;
        _priority = _priority.toString();

        var issue = {};
        issue.fields = {};
        issue.fields.project = {};
        issue.fields.project.key = gs.getProperty("com.snc.integration.jira.project");
        issue.fields.summary = "" + current.short_description.toString();
        issue.fields.description = "" + current.description;

        issue.fields.assignee = {};
        issue.fields.assignee.name = "martin.dusek";

        issue.fields.issuetype = {};
        issue.fields.issuetype.name = "" + "Bug";
        issue.fields['customfield_' + j.sysidField] = j.getSysIdPrefix() + ":" + current.number;

        if (!current.assigned_to.nil())
            issue.fields.customfield_12800 = current.assigned_to.getDisplayValue(); //Reporter name

        if (current.caller_id && current.getTableName() == 'incident')
            issue.fields.customfield_12720 = current.caller_id.getDisplayValue(); //Caller for Incident name
        if (current.u_reporter && current.getTableName() == 'problem')
            issue.fields.customfield_12720 = current.u_reporter.getDisplayValue(); // reporter for problem
        else
            issue.fields.customfield_12720 = current.opened_by.getDisplayValue(); //Requestor name

        if (current.u_versions.toString() != '') {
            issue.fields.versions = [];
            issue.fields.versions.push({
                'name': current.u_versions.toString()
            });
        }
        if (current.u_orignal_ticket_id.toString() != '') // Orignal ticket id
            issue.fields.customfield_12903 = "" + current.u_orignal_ticket_id.toString();


        if (current.u_components.toString() != '') {
            issue.fields.components = [];
            issue.fields.components.push({
                'name': current.u_components.toString()
            });
        }

        issue.fields.priority = {}; //Priority: Low, Medium, High, Highest
        issue.fields.priority.id = _priority;

        if (current.u_customfield_12018.toString() != '') {
            issue.fields.customfield_12018 = {}; //Severity: Low, Medium, High
            issue.fields.customfield_12018.value = current.u_customfield_12018.toString();
        }

        if (current.u_customfield_12017.toString() != '') {
            issue.fields.customfield_12017 = []; //Product Specification: WPH, E-R, OSS, External App
            issue.fields.customfield_12017.push({
                'value': current.u_customfield_12017.toString()
            });
        }

        if (current.u_customfield_12019.toString() != '') {
            issue.fields.customfield_12019 = {}; //Frequency: Rare, Occasional, Recurrent / Regular
            issue.fields.customfield_12019.value = current.u_customfield_12019.toString();
        }
        if (current.u_customfield_11514.toString() != '') {
            issue.fields.customfield_11514 = {}; //Development Phase:Development, Staging, Production
            issue.fields.customfield_11514.value = current.u_customfield_11514.toString();
        }

        if (current.u_customfield_11741.toString() != '') {
            issue.fields.customfield_11741 = {}; //Reported by: Internal, Customer
            issue.fields.customfield_11741.value = current.u_customfield_11741.toString();
        }
        issue.fields.customfield_11733 = {}; //Bug Type:Incident
        if (current.getTableName() == 'incident')
            issue.fields.customfield_11733.value = 'Incident';
        else if (current.getTableName() == 'problem')
            issue.fields.customfield_11733.value = 'Problem';


        if (j.verbose == "true") {
            JSUtil.logObject(issue);
        }

        j.debug("Jira fields set.  Ready to create issue in Jira");
        var jiraIssue = j.createIssue(issue);

        var parser = new JSONParser();
        parser = parser.parse(jiraIssue[1]);

        if (jiraIssue[0] == '201') {

            current.correlation_id = parser.key;
            current.correlation_display = j.CORRELATION_DISPLAY;

            if (parser.key) {
                var _href = gs.getProperty('com.snc.integration.jira.base_jira_instance_url') + "/browse/" + parser.key;
                var wn = "Status:- " + jiraIssue[0] + "\nA corresponding JIRA Issue has been created:\n";
                wn += "ID: " + parser.id + "\n";
                wn += "Issue Number: " + parser.key + "\n";
                wn += "Issue URL: [code]<a href='" + _href + "' target='_blank'>" + _href + "</a>[/code]";
                current.work_notes = wn;
                //addWorknotes(wn);
                //j.addExistingComments(current.sys_id, jiraIssue.key);
            }
        } else {
            current.work_notes = "Status:- " + jiraIssue[0] + ". JIRA ticket is not created. " + parser.errorMessages[0];
            //var _wn = "Status:- "+jiraIssue[0]+". JIRA ticket is not created. "+parser.errorMessages[0];
            //addWorknotes(_wn);
        }
        //current.setWorkflow(false);
        current.update();


        j.addExistingComments(current.sys_id, current.correlation_id); //Add comment in JIRA

        if (current.operation() == 'insert') //Add attacment in JIRA
            j.addExistingAttachments(current);

    },

    type: 'CreateJiraTicket'
}; 

Processor

Create a Processor to parse inbound Jira webhook

jiraintegration0105202052.PNG jiraintegration0105202053.PNG

/*
 * We could create logic around what to do if this request is
 * a GET, PUT, POST, or DELETE request.  To get that HTTP Method,
 * we use the getMethod() function on the request.
 * In this example, we just log it to a string variable
 */
var methodMsg = "Method string: "+g_request.getMethod()+"\n";

var jira = new JiraIntegration();

/*
 * We could iterate through all of the headers to make
 * intelligent decisions about how to handle this request,
 * but in this example, we will just write each header
 * and its value to a logging string
 */
var headerList = g_request.getHeaderNames();
var headerMsg = ""; //we're going to log header info here
while(headerList.hasMoreElements()){
    var header = headerList.nextElement();
    var value = g_request.getHeader(header);
    headerMsg += ("Header: ["+header+"] has a value of: "+value+"\n");
}

/*
 * We could iterate through all of the URL parameters to make
 * intelligent decisions about how to handle this request,
 * but in this example, we will just write each URL parameter
 * and its value to a logging string
 */
var urlParamList = g_request.getParameterNames();
var paramMsg = ""; //we're going to log parameter info here
while(urlParamList.hasMoreElements()){
    var param = urlParamList.nextElement();
    var value = g_request.getParameter(param);
    paramMsg += ("Parameter: ["+param+"] has a value of: "+value+"\n");
}

/*
 * Now we would normally process the request given the data posted to
 * us.  However, for this example, we are going to simply build out
 * a text response that is tailored to the information given to us
 */
var returnMessage = "";
returnMessage += "We received a Request from you with the following information:\n";
returnMessage += methodMsg;
returnMessage += "\nWITH INCOMING HEADERS\n";
returnMessage += headerMsg;
returnMessage += "\nWITH URL PARAMETERS OF\n";
returnMessage += paramMsg;
returnMessage += "\n\n";

jira.debug("OTHER DATA: " + returnMessage);




var is = g_request.getInputStream();
var sb = GlideStringUtil.getStringFromStream(is);
//gs.log("SB: " +sb, "TEST1");

var parser = new JSONParser();
var parsed = parser.parse(sb);
//JSUtil.logObject(parsed);

jira.debug("WebHook Event: " + parsed.webhookEvent);
jira.debug("User causing the event: " + parsed.user.name);
jira.debug("Issue ID: " + parsed.issue.key);
for(var key in parsed.changelog.items){
    var logitem = parsed.changelog.items[key];
    jira.debug("Change Log: The " + logitem.field + " changed from value("+logitem.from+") string("+logitem.fromString + ") to value("+logitem.to+") string("+logitem.toString + ")");
}

jira.debug("Comment: " + parsed.comment.body);

var correlator = parsed.issue.fields.customfield_10000;
jira.debug("Correlator: " + correlator);

if( parsed.user.name != gs.getProperty("com.snc.integration.jira.jira_api_user") 
   && correlator.split(":")[0] == gs.getProperty("com.snc.integration.jira.pull_instance_identifier") ){
	//Change was made outside ServiceNow.  Apply to the instance
	var jira = new GlideRecord("u_jira");
	jira.initialize();
	jira.u_issue_number = parsed.issue.key;
	var summary = getCLItem("summary", parsed.changelog.items);
	if( summary != null ){
		jira.u_summary = summary.toString;
	}
	var issueType = getCLItem("issuetype", parsed.changelog.items);
	if( issueType != null ){
		jira.u_issue_type = issueType.toString;
	}
	if( parsed.comment ){
		jira.u_comment = parsed.comment.body;
	}
	var state = getCLItem("status", parsed.changelog.items);
	if( state != null ){
		jira.u_status = state.to;
	}
	jira.insert();
}
	   
	 
function getCLItem(fieldName, items){
	for( var key in items ){
		if(items[key].field == fieldName){
			return items[key];
		}
	}
	return null;
}

Create Path

jiraintegration0105202054.PNG

Transform Maps

Create transform map that pull Jira comments

/**
 * For variables go to: http://wiki.service-now.com/index.php?title=Import_Sets
 **/
if (gs.getProperty("com.snc.integration.jira.pull_data") == "true") {
    var so = new ScheduleOnce();
    so.script = 'var j = new JiraIntegration(); 
    j.debug("Fetching comments...");
    j.requestCommentsForSingleIssue("'+source.u_issue_number+'");
    ';
    so.schedule();
}

Create transform map that Sync the comments

/**
 * For variables go to: http://wiki.service-now.com/index.php?title=Import_Sets
 **/
if (action == 'update') {
    ignore = true;
}

UI Action

Create UI Action to view Jira Record

jiraintegration0105202055.PNG jiraintegration0105202056.PNG

function launchJiraVersion(){
   var gr = new GlideRecord("sys_properties");
   gr.addQuery("name", "com.snc.integration.jira.base_jira_instance_url");
   gr.query();
   gr.next();
   var sysId = gel('sys_uniqueValue');
   var current = new GlideRecord("incident");
   current.get(sysId.value);
   var location=gr.value + "/browse/" + current.correlation_id;
   window.open(location, "Jira", "width=600,height=500,toolbar=yes,scrollbars=yes");
}

Create UI Action to create Jira Record

jiraintegration0105202057.PNG jiraintegration0105202058.PNG

    var jiraCreate = new CreateJiraTicket();
    var jiraGetResponse = jiraCreate.fnCreateJiraTicket(current);
    action.setRedirectURL(current);

Rest Message

There are 4 rest messages

jiraintegration0105202059.PNG

Jira Issue

Http Methods put,delete,get,ModifySN,post

jiraintegration0105202070 (1).png jiraintegration0105202070 (2).png jiraintegration0105202070 (3).png jiraintegration0105202070 (4).png jiraintegration0105202070 (5).png jiraintegration0105202070 (6).png jiraintegration0105202070 (7).png jiraintegration0105202070 (8).png jiraintegration0105202070 (9).png jiraintegration0105202070 (10).png jiraintegration0105202070 (11).png jiraintegration0105202070 (12).png jiraintegration0105202070 (13).png

Jira Issue Comment

Http Methods post,put,get,delete

jiraintegration0105202070 (14).png jiraintegration0105202070 (15).png jiraintegration0105202070 (16).png jiraintegration0105202070 (17).png jiraintegration0105202070 (18).png

jiraintegration0105202070 (19).png jiraintegration0105202070 (20).png jiraintegration0105202070 (21).png jiraintegration0105202070 (22).png jiraintegration0105202070 (23).png jiraintegration0105202070 (24).png jiraintegration0105202070 (25).png

Jira Issue Transition

jiraintegration0105202070 (26).png jiraintegration0105202070 (27).png jiraintegration0105202070 (28).png jiraintegration0105202070 (29).png jiraintegration0105202070 (30).png jiraintegration0105202070 (31).png jiraintegration0105202070 (32).png jiraintegration0105202070 (33).png

Inbound Jira WebService

We have created two inbound webservices jiraint1205202018.PNG

To configure Jira

jiraint120520201.PNG jiraint120520202.PNG

Transform Map

jiraint120520203.PNG jiraint120520204.PNG jiraint120520205.PNG jiraint120520206.PNG jiraint120520207.PNG jiraint120520208.PNG

(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {

    var s = source.u_status.toString();
    if (s == "10001" || s == "11623" || s == "6") { //11623 --> Cancelled, 11642 --> Available for code review, 11311--> In Code Review, 11643--> Available for retest, 10001 --> Closed
        var _comment = "JIRA ticket has been closed.";
        if (s == '11623')
            _comment = "JIRA ticket has been cancelled.";
        var gr = new GlideRecord('u_jira_comment');
        gr.initialize();
        gr.u_issue_id = source.u_issue_number.toString();
        gr.u_comment_id = s;
        gr.u_synced = false;
        gr.u_comment = _comment;
        gr.insert();

    }

    if (action == 'insert') {
        var _prob = new GlideRecord('problem');
        _prob.addEncodedQuery('state!=4^correlation_id=' + source.u_issue_number.toString());
        _prob.query();
        if (_prob.next()) {

            _prob.correlation_display = 'Jira Integration';
            _prob.short_description = source.u_summary.toString();
            var _gr = new GlideRecord('dl_u_priority');
            _gr.addQuery('priority=' + source.u_priority.toString());
            _gr.query();
            if (_gr.next()) {
                _prob.urgency = _gr.urgency.toString();
                _prob.impact = _gr.impact.toString();
            }


            if (source.u_original_ticket_id.toString())
                _prob.u_orignal_ticket_id = source.u_original_ticket_id.toString();

            _prob.comments = source.u_comment.toString();
            _prob.update();

            if (gs.getProperty("com.snc.integration.jira.pull_data") == "true") {
                var so = new ScheduleOnce();
                so.script = 'var j = new JiraIntegration(); j.debug("Fetching comments..."); j.requestCommentsForSingleIssue("' + source.u_issue_number + '");';
                so.schedule();
            }
        }
        ignore = true;
    } else {
        if (target.state.toString() != '6' && target.state.toString() != '7' && target.state.toString() != '8') {
            var _inc = new GlideRecord('dl_u_priority');
            _inc.addQuery('priority=' + source.u_priority.toString());
            _inc.query();
            if (_inc.next()) {
                target.urgency = _inc.urgency.toString();
                target.impact = _inc.impact.toString();
            }
        } else
            ignore = true;
    }

})(source, map, log, target);

To configure Jira Comment Sync

jiraint1205202012.PNG jiraint1205202013.PNG

Transform Map

jiraint1205202014.PNG jiraint1205202015.PNG jiraint1205202017.PNG jiraint1205202016.PNG

jiraintegration0105202090.png

Schedule Jobs

jiraintegration0105202080 (1).png jiraintegration0105202080 (2).png

Create Issue in Jira using Rest API

  1. Create a free jira cloud account using your email
  2. Add a new user by sending invite learnnowlab.atlassian.net/trusted-admin/users/invite
  3. Login as new user
  4. Got to https://id.atlassian.com/manage-profile/security/api-tokens
  5. Create a new API token
  6. Now to create a new issue through API we need to key things Project key and Issue type id.
  7. Project key can be checked in the url itself like https://learnnowlab.atlassian.net/jira/software/projects/SI/boards/1 So project key here is SI
  8. To get issue type call this below endpoint from postman or curl
curl --location --request GET 'http://learnnowlab.atlassian.net/rest/api/3/issuetype' \
--header 'Authorization: Basic bGVhcm5ub3dsYWJAZ21haWwuY29tOjNibnpnZXN1MGcwUDdhdlh1TGtkOENEQg=='

response

....
  {
        "self": "https://learnnowlab.atlassian.net/rest/api/3/issuetype/10001",
        "id": "10001",
        "description": "A small, distinct piece of work.",
        "iconUrl": "https://learnnowlab.atlassian.net/secure/viewavatar?size=medium&avatarId=10318&avatarType=issuetype",
        "name": "Task",
        "subtask": false,
        "avatarId": 10318,
        "scope": {
            "type": "PROJECT",
            "project": {
                "id": "10000"
            }
        }
    }
....

So IssueTypeId is 10001 for Task

Now fire the below call to create a new Jira record

Add basic auth header like

<your-email-address>:<api-token>

which will get base64 encoded and look like below

header 'Authorization: Basic bGVhcm5ub3dsYWJAZ21haWwuY29tOjNibnpnZXN1MGcwUDdhdlh1TGtkOENEQg=='

Example

curl --location --request POST 'https://learnnowlab.atlassian.net/rest/api/3/issue/' \
--header 'Authorization: Basic bGVhcm5ub3dsYWJAZ21haWwuY29tOjNibnpnZXN1MGcwUDdhdlh1TGtkOENEQg==' \
--header 'Content-Type: application/json' \
--header 'Cookie: atlassian.xsrf.token=aec0becc-24ac-442f-8655-e7972f74b121_f2615ab686a1fa97bc767c3bbc86f3a6c1339fc6_lin' \
--data-raw '{
	"fields": {
        "summary": "Summit 2019 is awesome!",
        "issuetype": {
            "id": "10001"
        },
        "project": {
            "key": "SI"
        },
        "description": {
            "type": "doc",
            "version": 1,
            "content": [
                {
                "type": "paragraph",
                "content": [
                    {
                    "text": "This is the description.",
                    "type": "text"
                    }
                ]
                }
            ]
		}
	}
}'

you will get success response

{
    "id": "10000",
    "key": "SI-1",
    "self": "https://learnnowlab.atlassian.net/rest/api/3/issue/10000"
}

You can read further here Creating a jira cloud issue

Read more

Jira POC

    Content