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 get created in Jira
.
When we add comments
in ServiceNow then the corresponding ticket in Jira 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
.
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
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 ();
}
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 ] + " \n A 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 += " \n WITH INCOMING HEADERS \n " ;
returnMessage += headerMsg ;
returnMessage += " \n WITH 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
/**
* 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 ();
}
/**
* 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
Http Methods post
,put
,get
,delete
Jira Issue Search
Jira Issue Transition
Inbound Jira WebService
We have created two inbound webservices
( 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 );
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
and Issue 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 is SI
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
Comments