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
- If we create a
ticket in ServiceNow
then corresponding ticket will getcreated in Jira
. - When we
add comments
in ServiceNow then the corresponding ticket inJira will be updated
with this comment in comment history. - When we
add a comment in Jira
then corresponding ServiceNow ticket will get updated. - Any status change in ServiceNow or Jira will get synced.
- When Jira ticket gets created then the Jira ticket url will get added in ServiceNow comment section.
- 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
.
- We need to create Jira ticket button - button label
- 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
Business Rules
Create a Business Rule that addComment
Condition:
gs.getProperty("com.snc.integration.jira.enable_integration") == "true" && gs.getSession().isInteractive() && current.value.toString().startsWith('Status:') == false
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
Condition:current.correlation_display == JiraIntegration.prototype.CORRELATION_DISPLAY
&& gs.getSession().isInteractive()
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"
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)
Condition:current.correlation_display == JiraIntegration.prototype.CORRELATION_DISPLAY
&& gs.getSession().isInteractive()
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"
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
Condition:current.category.getDisplayValue()=="Jira Issue"
&& gs.getSession().isInteractive()
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"
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
Condition:current.correlation_display == JiraIntegration.prototype.CORRELATION_DISPLAY
&& gs.getSession().isInteractive()
&& gs.getProperty("com.snc.integration.jira.enable_integration")=="true"
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
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"
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
Condition:current.name=="com.snc.integration.jira.jira_api_user" ||
current.name=="com.snc.integration.jira.jira_api_password"
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
Condition:!current.u_synced
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
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
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
/*
* 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
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
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
var jiraCreate = new CreateJiraTicket();
var jiraGetResponse = jiraCreate.fnCreateJiraTicket(current);
action.setRedirectURL(current);
Rest Message
There are 4 rest messages
Jira Issue
Http Methods put
,delete
,get
,ModifySN
,post
Jira Issue Comment
Http Methods post
,put
,get
,delete
Jira Issue Search
Jira Issue Transition
Inbound Jira WebService
We have created two inbound webservices
To configure Jira
Transform Map
(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
Transform Map
Schedule Jobs
Create Issue in Jira using Rest API
- Create a free jira cloud account using your email
- Add a new user by sending invite
learnnowlab.atlassian.net/trusted-admin/users/invite
- Login as new user
- Got to
https://id.atlassian.com/manage-profile/security/api-tokens
- Create a new API token
- Now to create a new issue through API we need to key things
Project key
andIssue type
id. - Project key can be checked in the url itself like
https://learnnowlab.atlassian.net/jira/software/projects/SI/boards/1
So project key here isSI
- 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
- Understanding Request, RITM, Task in ServiceNow
- Steps to create a case in ServiceNow (CSM)
- Performance Analytics in 10 mins
- Event Management in 10 minutes - part1
- Event Management in 10 minutes - part2
- Custom Lookup List
- Script includes in 5 minutes
- Interactive Filter in 5 minutes
- UI Policy in 6 Minutes
- Client Side Script Versus Server Side Script in 3 minutes
-
Snow
- Performance Analytics
- ServiceNow Scripts
- Script include
- Useful scripts
- Basic Glide Scripts
- Client Script
- Advance Glide Script
- Glide System Script
- Admin
- Import Set
- Work Flow
- ACL
- SLA
- Notification
- Core Application
- UI Policy
- UI Action
- Client Script
- CAB Workbech
- Data Policy
- Connect Support
- Catalog
- Discovery
- CSM
- Event Management
- HR
- Integrations
- SSO Integration
- LDAP Integration
- SCCM Integration
- AWS Intergration
- Slack Integration
- CTI Integration
- Jira Integration
- Ebonding ServiceNow
- SOAP Integration
- IBM Netcool Integration
- VIP Mobile App Integration
- Rest Integration
- Service Portal
- Questions
- ACL
- Performance analytics(PA) Interactive Filter
- Various Configurations in Performance analytics(PA)
- Service Portal
- Performance Analytics(PA) Widgets
- Performance Analytics(PA) Indicator
- Performance Analytics(PA) Buckets
- Performance Analytics(PA) Automated Breakdown
- Client Script
- Rest Integration
- Understanding the Request, RITM, Task
- Service Catalogs
- Events in ServiceNow
- Advance glide script in ServiceNow
- CAB Workbench
Comments