├── ApproveChange.PNG ├── IncidentAssigned.PNG ├── IncidentsAssignedTo.PNG ├── MessageNotification.PNG ├── RESTServiceConfiguration.PNG ├── SlackMessageNotifier.js └── README.md /ApproveChange.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybaloney/ServiceNowHackathon2016/HEAD/ApproveChange.PNG -------------------------------------------------------------------------------- /IncidentAssigned.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybaloney/ServiceNowHackathon2016/HEAD/IncidentAssigned.PNG -------------------------------------------------------------------------------- /IncidentsAssignedTo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybaloney/ServiceNowHackathon2016/HEAD/IncidentsAssignedTo.PNG -------------------------------------------------------------------------------- /MessageNotification.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybaloney/ServiceNowHackathon2016/HEAD/MessageNotification.PNG -------------------------------------------------------------------------------- /RESTServiceConfiguration.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybaloney/ServiceNowHackathon2016/HEAD/RESTServiceConfiguration.PNG -------------------------------------------------------------------------------- /SlackMessageNotifier.js: -------------------------------------------------------------------------------- 1 | // Insert this as a system script include 2 | // 3 | 4 | var SlackMessageNotifier = Class.create(); 5 | SlackMessageNotifier.prototype = Object.extendsObject(AbstractAjaxProcessor, { 6 | 7 | type: 'SlackMessageNotifier', 8 | 'sendMessage': function (message) { 9 | // Create and send the REST Message 10 | var r = new sn_ws.RESTMessageV2('Slack', 'get'); 11 | r.setStringParameter('text', message); 12 | var response = r.execute(); 13 | return response; 14 | }, 15 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServiceNow Hackathon 2 | 3 | The purpose of this hackathon entry was to integrate Slack into ServiceNow enabling support engineers to interact with ServiceNow and receive notifications. 4 | The focus was incidents but the code is reusable. 5 | 6 | [Demo](https://youtu.be/rwyhp_CgKKI) 7 | 8 | ## Configuring a Slack app 9 | 10 | Before you start you need a Slack team. 11 | 12 | In Slack, add an application under the integrations panel, this will also give you the chance to issue a developer token. You will need this to authenticate 13 | 14 | ## Add the REST configuration in ServiceNow 15 | 16 | Once you have an auth token, an application ID and a botname (and more importantly a cool icon), setup the REST service action in ServiceNow 17 | 18 | ![Configure the REST Service](RESTServiceConfiguration.PNG) 19 | 20 | Once this is done, add a Script Include so this can be used from a business rule or UI script 21 | 22 | ```javascript 23 | var SlackMessageNotifier = Class.create(); 24 | SlackMessageNotifier.prototype = Object.extendsObject(AbstractAjaxProcessor, { 25 | 26 | type: 'SlackMessageNotifier', 27 | 'sendMessage': function (message) { 28 | // Create and send the REST Message 29 | var r = new sn_ws.RESTMessageV2('Slack', 'get'); 30 | r.setStringParameter('text', message); 31 | var response = r.execute(); 32 | return response; 33 | }, 34 | }); 35 | ``` 36 | 37 | ### Example business rule 38 | 39 | Now you can setup a business rule, I tried checking when the incident table changes and the assigned_to field has been modified. I also added a slack_username field to the sys_user table. 40 | 41 | This is the script of the rule 42 | 43 | ```javascript 44 | (function executeRule(current, previous /*null when async*/) { 45 | gs.info("Hello I fired"); 46 | var request = new SlackMessageNotifier(); 47 | request.sendMessage("Incident " + current.number.toString() + " (" + current.short_description + ") was resassigned to @" + current.assigned_to.u_slack_username.getDisplayValue() + " from " + 48 | (previous.assigned_to != null ? "@"+previous.assigned_to.u_slack_username.getDisplayValue() : "nobody")); 49 | gs.info("Yey I finished"); 50 | })(current, previous); 51 | ``` 52 | 53 | Testing that out, you will see a message in your Slack channel 54 | 55 | ![Hello](MessageNotification.PNG) 56 | 57 | ### Communication back the other way 58 | 59 | I setup 3 bot commands to coordinate the rule the other way around, this was using [StackStorm](http://stackstorm.com/) as my Python engine and all round awesome-sauce as well as Github Hubot. 60 | 61 | All my code for the integration from Slack to ServiceNow is [In this branch](https://github.com/tonybaloney/st2contrib/tree/feature/servicenow/packs/servicenow) 62 | 63 | I will contribute this upstream to StackStorm... 64 | 65 | My first integration showing getting a list of incidents for a user, this is the Hubot configuration 66 | 67 | ![Incidents](IncidentsAssignedTo.PNG) 68 | 69 | ```yaml 70 | --- 71 | name: "servicenow.get_incidents_assigned_to" 72 | action_ref: "servicenow.get_incidents_assigned_to" 73 | description: "Get a list of incidents assigned to a particular username" 74 | formats: 75 | - "incidents assigned to {{name}}" 76 | ack: 77 | format: "Finding all incidents assigned " 78 | result: 79 | format: | 80 | {% if execution.result.tasks[1].result.result|length %} 81 | found some incidents for you: {~} 82 | {% for incident in execution.result.tasks[1].result.result %} 83 | {{ loop.index }}. *{{ incident.number }}*: {{incident.short_description}} 84 | {% endfor %} 85 | {% else %} 86 | couldn't find anything, sorry! 87 | {% endif %} 88 | ``` 89 | 90 | And then an action-chain workflow to first fetch the user with the name requested, then all incidents assigned. You could adapt the query to show certain incidents. 91 | 92 | ```yaml 93 | --- 94 | chain: 95 | - 96 | name: "getUser" 97 | ref: "servicenow.get_non_structured" 98 | parameters: 99 | table: "sys_user" 100 | query: "user_name={{name}}" 101 | on-success: getIncidents 102 | publish: 103 | user_id: "{{getUser.result[0].sys_id}}" 104 | - 105 | name: "getIncidents" 106 | ref: "servicenow.get_non_structured" 107 | parameters: 108 | table: "incident" 109 | query: "assigned_to={{user_id}}" 110 | publish: 111 | incidents: "{{getIncidents.result}}" 112 | ``` 113 | 114 | Then I did incident assignment, which was a little more complex, this is the hubot config 115 | 116 | ![Incident](IncidentAssigned.PNG) 117 | 118 | ```yaml 119 | --- 120 | name: "servicenow.assign_incident_to" 121 | action_ref: "servicenow.assign_incident_to" 122 | description: "Assign an incident to a username" 123 | formats: 124 | - "assign incident {{number}} to {{name}}" 125 | ack: 126 | format: "Reassiging incident" 127 | result: 128 | format: "incident reassigned" 129 | ``` 130 | 131 | then the workflow 132 | 133 | ```yaml 134 | --- 135 | chain: 136 | - 137 | name: "getUser" 138 | ref: "servicenow.get_non_structured" 139 | parameters: 140 | table: "sys_user" 141 | query: "user_name={{name}}" 142 | on-success: setIncidents 143 | publish: 144 | user_id: "{{getUser.result[0].sys_id}}" 145 | - 146 | name: "setIncidents" 147 | ref: "servicenow.set_incident_owner" 148 | parameters: 149 | number: "{{number}}" 150 | user_id: "{{user_id}}" 151 | publish: 152 | incidents: "{{setIncidents.result}}" 153 | ``` 154 | 155 | This time I had to write a little Python to automate the assignment of the incident owner 156 | 157 | ```python 158 | from lib.actions import BaseAction 159 | 160 | 161 | class AssignIncidentToAction(BaseAction): 162 | def run(self, user_id, number): 163 | s = self.client 164 | s.table='incident' 165 | res = s.get({'number': number}) 166 | sys_id = res[0]['sys_id'] 167 | response = s.update({'assigned_to': user_id}, sys_id) 168 | return response 169 | 170 | ``` 171 | 172 | And lastly, approving a Change Request 173 | 174 | ![Change](ApproveChange.PNG) 175 | 176 | ```yaml 177 | --- 178 | name: "servicenow.approve_change" 179 | action_ref: "servicenow.approve_change" 180 | description: "Set the approval status of a Change Request to approved." 181 | formats: 182 | - "approve {{number}}" 183 | ack: 184 | format: "Approving change" 185 | result: 186 | format: "Change approved" 187 | ``` 188 | 189 | And the Python 190 | ```python 191 | from lib.actions import BaseAction 192 | 193 | 194 | class ApprovalAction(BaseAction): 195 | def run(self, number): 196 | s = self.client 197 | s.table='change_request' 198 | res = s.get({'number': number}) 199 | sys_id = res[0]['sys_id'] 200 | response = s.update({'approval': 'approved'}, sys_id) 201 | return response 202 | 203 | ``` --------------------------------------------------------------------------------