├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── common ├── responseLogger.js └── usage.js ├── dispatcher ├── loginDispatcher.js └── taskDispatcher.js ├── images ├── arch_diagram.jpg ├── login.png ├── node_flow.png ├── nodejs-logo.jpg ├── servicenow-logo.jpg ├── task_detail.png └── task_list.png ├── mywork_update_set └── sys_remote_update_set_2f48a7d74f4652002fa02f1e0210c785.xml ├── package.json ├── public ├── img │ └── icon.png ├── index.html ├── js │ ├── app.js │ ├── loginController.js │ ├── loginSvc.js │ ├── taskDetailController.js │ └── taskListController.js └── view │ ├── css │ └── task.css │ ├── login.html │ ├── task_detail.html │ └── task_list.html ├── server.js ├── sn_api ├── basicAuth.js └── task.js └── test └── tasksTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log 2 | 3 | ### v1.00.0 (2016/03/31) 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/LICENSE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example REST Client My Work App: Node.js 2 | This project contains source code for a [Node.js](https://nodejs.org/) web application that interacts with ServiceNow's [REST APIs](https://docs.servicenow.com/bundle/helsinki-servicenow-platform/page/integrate/inbound_rest/concept/c_RESTAPI.html) including a [Scripted REST API](https://docs.servicenow.com/bundle/helsinki-servicenow-platform/page/integrate/custom_web_services/concept/c_CustomWebServices.html). The simple use case is a "MyWork" application which displays a user's current tasks and allows comments to be added. This application demonstrates how to build the MyWork app using Node.js. To see the same use case implemented in iOS, see [Example REST Client My Work App: iOS](https://github.com/ServiceNow/example-restclient-myworkapp-ios). 3 | 4 | ## Architecture 5 | Here is an overview of the MyWork application architecture. Note both this Node.js application and the iOS application are represented in the diagram. 6 | ![Architecture diagram](/images/arch_diagram.jpg "Architecture diagram") 7 | 8 | --------------------------------------------------------------------------- 9 | 10 | ## Prerequisites 11 | * [Node.js](https://nodejs.org/) installed 12 | * A ServiceNow instance ([Geneva Patch 3](https://docs.servicenow.com/bundle/geneva-release-notes/page/c2/geneva-patch-3-2.html) or later). 13 | * **Don't have a ServiceNow instance?** Get one **FREE** by signing up at https://developer.servicenow.com 14 | * Not sure what version of ServiceNow your instance is running? [Determine running version](http://wiki.servicenow.com/index.php?title=Upgrades_Best_Practices#Prepare_for_Upgrading) 15 | 16 | -------------------------------------------------------------------------- 17 | 18 | ## Install the Node.js project on your host. This could be your laptop or a server where you have installed Node.js. 19 | 1. Clone the project and install dependencies 20 | * Git clone 21 | ```bash 22 | $ git clone https://github.com/ServiceNow/example-restclient-myworkapp-nodejs.git 23 | $ cd example-restclient-myworkapp-nodejs 24 | $ npm install 25 | ``` 26 | --or-- 27 | * [Download](https://github.com/ServiceNow/example-restclient-myworkapp-nodejs/archive/master.zip) the full project as a Zip file 28 | ```bash 29 | 30 | $ cd example-restclient-myworkapp-nodejs 31 | $ npm install 32 | ``` 33 | 2. Install the **MyWork Update Set** in your ServiceNow instance. This is a ServiceNow scoped application which contains the **Task Tracker API** Scripted REST API and related files. Note that you must have the admin role on your ServiceNow instance to install update sets. 34 | 1. Obtain the "My Work" update set 35 | * Download the update set from [share.servicenow.com](https://share.servicenow.com/app.do#/detailV2/e43cf2f313de5600e77a36666144b0b4/overview) 36 |
--or-- 37 | * Get the update set from the directory where you cloned the GitHub repository: **example-restclient-myworkapp-nodejs/mywork_update_set/sys_remote_update_set_2f48a7d74f4652002fa02f1e0210c785.xml** 38 | 2. Install the Update Set XML 39 | 1. In your ServiceNow instance, navigate to **Retrieved Update Sets** 40 | 2. Click **Import Update Set from XML** 41 | 3. Click **Choose File**, browse to find the downloaded update set XML file from Step 1, and click **Upload** 42 | 4. Click to open the **My Work** update set 43 | 5. Click **Preview Update Set** 44 | 6. Click **Commit Update Set** 45 | 3. Verify the MyWork Update Set installed using the API Explorer 46 | 1. In your ServiceNow instance, navigate to **Scripted REST APIs** 47 | 2. Open the **Task Tracker** Scripted REST API, then open the **My Tasks** API resource 48 | 3. Click **Explore REST API** (this opens the API in the REST API Explorer) 49 | 4. In the API Explorer, click **Send** and verify the API request is sent and receives a **200-OK** response 50 | 51 | -------------------------------------------------------------------------- 52 | 53 | ## Running the Node.js application 54 | Start the Node.js server 55 | ```bash 56 | $ cd example-restclient-myworkapp-nodejs 57 | $ node server.js 58 | Server listening on: http://localhost:3000 59 | 60 | ``` 61 | 62 | By default the Node application will listen for requests on localhost port 3000. Navigate in your web browser to [http://localhost:3000](http://localhost:3000) and you should see the login screen for the My Work application. 63 | 64 | To run the server on a different port, use the `--port=xxxx` option 65 | ```bash 66 | $ node server.js --port=8080 67 | Server listening on: http://localhost:8080 68 | ``` 69 | 70 | For more information about available options, use --help 71 | ```bash 72 | $ node server.js --help 73 | 74 | Usage: node server.js [ -h ] [ -p ] [ -v ] 75 | -h, --help Show this usage help 76 | -p , --port= HTTP server listen port (default 3000) 77 | -v, --verbose Verbose HTTP output 78 | $ 79 | ``` 80 | 81 | -------------------------------------------------------------------------- 82 | 83 | ## About the application 84 | In this application, the web browser is the client which makes HTTP calls to the Node.js server 'server.js' to get task details. 85 | 86 | Server side, the Node application uses the **Task Tracker** Scripted REST API to get the list of tasks assigned to the logged-in user. Dispatchers handle interaction between Node and the ServiceNow instance. 87 | 88 | ### Functional flow 89 | 90 | #### 1. Login 91 | ![Login](/images/login.png) 92 | 93 | After starting the Node.js web server, navigate your browser to http://localhost:3000. You're presented with a login page where you need to input your ServiceNow instance name (for example, if your instance URL is https://myinstance.service-now.com, then enter `myinstance` into the Instance text box). 94 | 95 | Enter the user ID and password for a user on the instance. This application uses Basic Authentication to manage user authentication with the ServiceNow REST API. When a user enters credentials, an HTTP POST call is made to the `/login` endpoint of the Node.js server), which internally establishes a session to the REST API. 96 | 97 | **NOTE**: The application makes all REST API calls from the Node.js server side, as opposed to client side from the web browser. This application could also be refactored so that the API calls would be made from the client (such as using AJAX), but that was not the intention of this example application. 98 | 99 | On successful login the user is directed to the `/tasks` endpoint. On failure, the user is directed to the login page to reenter credentials. 100 | 101 | After login, the application displays the tasks assigned to the user grouped by task type. The application uses the **Task Tracker API** to retrieve the list of tasks from ServiceNow. The logged in user must have access to view the tasks (such as Incidents, Problems, Tickets) for these tasks to be returned in the REST API and subsequently displayed in the 'MyWork App'. 102 | 103 | **> REST API Call:** Get user details (Table API) 104 | ``` 105 | GET /api/now/v2/table/sys_user?user_name=john.doe 106 | ``` 107 | 108 | #### 2. View my tasks 109 | ![Task List](/images/task_list.png) 110 | 111 | Click an item in the list to open the task details. 112 | 113 | **> REST API Call:** Get my tasks (Task Tracker API) 114 | ``` 115 | GET /api/x_snc_my_work/v1/tracker/task 116 | ``` 117 | 118 | #### 3. View task detail/add comment 119 | ![Task Details](/images/task_detail.png) 120 | 121 | Comments can be added to a task and will appear on the work notes for the task both in this application as well as within ServiceNow. 122 | 123 | **> REST API Calls:** Get comments, Add comment (Task Tracker API, Table API) 124 | ``` 125 | GET /api/now/v2/table/sys_journal_field?element_id= 126 | 127 | POST /api/x_snc_my_work/v1/tracker/task/{task_id}/comment 128 | {"comment":"Hello, world!"} 129 | ``` 130 | 131 | ### Application Flow Detail 132 | ![App Flow](/images/node_flow.png) 133 | 134 | #### Client side 135 | On the client side, the application uses [AngularJS](https://angularjs.org/) for client side scripting and interaction with the Node.js server. Each page is associated with an [Angular controller](https://docs.angularjs.org/guide/controller). 136 | 137 | | Page | Controller | Details | 138 | |---------------|-----------------------|-----------------------| 139 | | login.html | [loginController.js](/public/js/loginController.js) | Collect user instance and credentials | 140 | | task_list.html | [taskListController.js](/public/js/taskListController.js) | List of tasks assigned to the user | 141 | | task_detail.html | [taskDetailController.js](/public/js/taskDetailController.js) | Details of a single task, view and add comments | 142 | 143 | #### Server side 144 | The Node.js server has 2 components: dispatchers and sn_api module. 145 | * Dispatchers (`/dispatcher`) 146 | * Dispatch calls to ServiceNow REST API endpoints using the sn_api module. The 2 dispatchers used by this app are detailed below 147 | * **sn_api** module (`/sn_api`) 148 | * Encapsulate the details of sending REST API calls to ServiceNow 149 | 150 | `loginDispatcher.js`: handles user authentication. The dispatcher handles calls reaching /login endpoint of Node.js server. 151 | 152 | |Node endpoint| HTTP Method |Details| 153 | |--------|--------|--------| 154 | |/login | POST | Uses the REST Table API to query for user details from the sys_user table (`GET /api/now/v2/table/sys_user`) in ServiceNow. | 155 | 156 | `taskDispatcher.js`: handles any task related interaction with ServiceNow instance. The dispatcher handles calls to 3 endpoints. 157 | 158 | | Node endpoint | HTTP Method | Details | 159 | |--------|-------------|------------| 160 | | /tasks | GET | Uses the **Task Tracker** Scripted REST API to get tasks assigned to a user (`GET /api/x_snc_my_work/v1/tracker/task`)| 161 | | /tasks/:task/comments| GET | Uses the REST Table API to query for a task's work notes from the sys_journal_field table (`GET /api/now/v2/table/sys_journal_field`) | 162 | | /tasks/:task/comments | POST | Uses the **Task Tracker** Scripted REST API to add a comment (work note) to a task (`POST /api/x_snc_my_work/v1/tracker/task/{task_id}/comment`) | 163 | 164 | #### Session management 165 | Two types of sessions are managed by the application, between: 166 | * Browser and Node.js web application 167 | * Node.js server and ServiceNow instance 168 | 169 | When a user enters credentials on the login page, the browser POSTs them to the `/login` endpoint of the Node.js server. The Node.js server creates a session object and makes an HTTP GET to the REST Table API to retrieve the sys_user record from ServiceNow. 170 | 171 | If the request is successful then a session is automatically created on ServiceNow and the response contains cookies that can be used to bind to the same session. These cookies are stored in the session object in Node.js. On subsequent calls to ServiceNow REST endpoints, the cookies are retrieved from the session object and applied to outgoing requests. 172 | 173 | To process a logout, an HTTP DELETE call is made to the `/logout` endpoint, in which the session object is deleted for the user. 174 | 175 | -------------------------------------------------------------------------- 176 | 177 | ## Sample REST API requests/responses 178 | 179 | ### 1. Login/retrieve user account 180 | The initial request to ServiceNow submits the user credentials and retrieves the user account. This establishes a session with ServiceNow which can be maintained by saving and resending the cookies returned from the first request. 181 | 182 | Here is an equivalent sample curl request. It saves the response cookies in a new file called cookies.txt. The same file is specified on subsequent request in order to apply all cookies. 183 | ``` 184 | $ curl --verbose --request GET \ 185 | --header "Accept: application/json" \ 186 | --user "john.doe:password" --cookie cookies.txt --cookie-jar cookies.txt \ 187 | "https://myinstance.service-now.com/api/now/v2/table/sys_user?user_name=john.doe&sysparm_fields=user_name,first_name,last_name,sys_id" 188 | 189 | > GET /api/now/v2/table/sys_user?user_name=john.doe&sysparm_fields=user_name,first_name,last_name,sys_id HTTP/1.1 190 | > Authorization: Basic am9obi5kb2U6cGFzc3dvcmQ= 191 | > Host: myinstance.service-now.com 192 | > Accept: application/json 193 | 194 | < HTTP/1.1 200 OK 195 | < Set-Cookie: JSESSIONID=3BFF4F3A8AC5F4695E0477F6F8E34BDE;Secure; Path=/; HttpOnly 196 | < Set-Cookie: glide_user="";secure; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly 197 | < Set-Cookie: glide_user_session="";secure; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly 198 | < Set-Cookie: glide_user_route=glide.787db27f9eb4d8275f143168c5481c86;secure; Expires=Mon, 27-Mar-2084 19:32:44 GMT; Path=/; HttpOnly 199 | < Set-Cookie: glide_session_store=292391354F4212008A5AB895F110C722; Expires=Wed, 09-Mar-2016 16:48:37 GMT; Path=/; HttpOnly 200 | < Set-Cookie: BIGipServerpool_myinstance=2927640842.52542.0000; path=/ 201 | < X-Total-Count: 1 202 | < Pragma: no-store,no-cache 203 | < Cache-control: no-cache,no-store,must-revalidate,max-age=-1 204 | < Expires: 0 205 | < Content-Type: application/json;charset=UTF-8 206 | < Transfer-Encoding: chunked 207 | { 208 | "result": [ 209 | { 210 | "first_name": "John", 211 | "last_name": "Doe", 212 | "sys_id": "ea2bc1b14f4212008a5ab895f110c7d1", 213 | "user_name": "john.doe" 214 | } 215 | ] 216 | } 217 | ``` 218 | 219 | ### 2. Get user's tasks 220 | Next, the user's tasks are retrieved. Note how the cookies from the first request are sent with subsequent requests, and user credentials no longer need to be sent: 221 | ``` 222 | $ curl --verbose --request GET \ 223 | --header "Accept: application/json" \ 224 | --cookie cookies.txt --cookie-jar cookies.txt \ 225 | "https://myinstance.service-now.com/api/x_snc_my_work/v1/tracker/task" 226 | 227 | > GET /api/x_snc_my_work/v1/tracker/task HTTP/1.1 228 | > Host: myinstance.service-now.com 229 | > Cookie: BIGipServerpool_myinstance=2927640842.52542.0000; JSESSIONID=3BFF4F3A8AC5F4695E0477F6F8E34BDE; glide_session_store=292391354F4212008A5AB895F110C722; glide_user_route=glide.787db27f9eb4d8275f143168c5481c86 230 | > Accept: application/json 231 | 232 | < HTTP/1.1 200 OK 233 | < Set-Cookie: glide_user="U0N2Mjo1ODczMTEzNTIxNDIxMjAwOWE3NDgyZDFlZjg3Mzk4OQ==";Secure; Version=1; Max-Age=2147483647; Expires=Mon, 27-Mar-2084 19:34:00 GMT; Path=/; HttpOnly 234 | < Set-Cookie: glide_user_session="U0N2Mjo1ODczMTEzNTIxNDIxMjAwOWE3NDgyZDFlZjg3Mzk4OQ==";Secure; Version=1; Path=/; HttpOnly 235 | < Set-Cookie: glide_session_store=292391354F4212008A5AB895F110C722; Expires=Wed, 09-Mar-2016 16:24:53 GMT; Path=/; HttpOnly 236 | < Pragma: no-store,no-cache 237 | < Cache-control: no-cache,no-store,must-revalidate,max-age=-1 238 | < Expires: 0 239 | < Content-Type: application/json;charset=UTF-8 240 | < Transfer-Encoding: chunked 241 | { 242 | "result": { 243 | "Incident": [ 244 | { 245 | "short_desc": "my computer doesn't work", 246 | "snowui": "https://myinstance.service-now.com/incident.do?sys_id=061c92d26f030200d7aecd9c5d3ee4f8", 247 | "number": "INC0010021", 248 | "sys_id": "061c92d26f030200d7aecd9c5d3ee4f8", 249 | "link": "https://myinstance.service-now.com/api/now/v2/table/incident/061c92d26f030200d7aecd9c5d3ee4f8", 250 | "created": "2015-10-14 07:45:55" 251 | } 252 | ], 253 | "Problem": [ 254 | { 255 | "short_desc": "Unknown source of outage", 256 | "snowui": "https://myinstance.service-now.com/problem.do?sys_id=d7296d02c0a801670085e737da016e70", 257 | "number": "PRB0000011", 258 | "sys_id": "d7296d02c0a801670085e737da016e70", 259 | "link": "https://myinstance.service-now.com/api/now/v2/table/problem/d7296d02c0a801670085e737da016e70", 260 | "created": "2014-02-04 04:58:15" 261 | }, 262 | { 263 | "short_desc": "Getting NPE stack trace accessing link", 264 | "snowui": "https://myinstance.service-now.com/problem.do?sys_id=fb9620914fc212008a5ab895f110c7c4", 265 | "number": "PRB0040010", 266 | "sys_id": "fb9620914fc212008a5ab895f110c7c4", 267 | "link": "https://myinstance.service-now.com/api/now/v2/table/problem/fb9620914fc212008a5ab895f110c7c4", 268 | "created": "2016-03-07 23:47:43" 269 | } 270 | ] 271 | } 272 | } 273 | ``` 274 | 275 | ### 3. Add a comment 276 | To add a comment, send a POST request with a JSON payload using the Task Tracker API. 277 | 278 | ``` 279 | $ curl --verbose --request POST \ 280 | --header "Accept: application/json" --header "Content-Type: application/json" \ 281 | --cookie cookies.txt --cookie-jar cookies.txt \ 282 | --data '{"comment":"Hello, world!"}' \ 283 | "https://myinstance.service-now.com/api/x_snc_my_work/v1/tracker/task/d7296d02c0a801670085e737da016e70/comment" 284 | 285 | > POST /api/x_snc_my_work/v1/tracker/task/d7296d02c0a801670085e737da016e70/comment HTTP/1.1 286 | > Host: myinstance.service-now.com 287 | > Cookie: BIGipServerpool_myinstance=2927640842.52542.0000; JSESSIONID=3BFF4F3A8AC5F4695E0477F6F8E34BDE; glide_session_store=292391354F4212008A5AB895F110C722; glide_user="U0N2Mjo1ODczMTEzNTIxNDIxMjAwOWE3NDgyZDFlZjg3Mzk4OQ=="; glide_user_route=glide.787db27f9eb4d8275f143168c5481c86; glide_user_session="U0N2Mjo1ODczMTEzNTIxNDIxMjAwOWE3NDgyZDFlZjg3Mzk4OQ==" 288 | > Accept: application/json 289 | > Content-Type: application/json 290 | > Content-Length: 27 291 | {"comment":"Hello, world!"} 292 | 293 | < HTTP/1.1 201 Created 294 | < Set-Cookie: glide_session_store=292391354F4212008A5AB895F110C722; Expires=Wed, 09-Mar-2016 16:29:58 GMT; Path=/; HttpOnly 295 | < Content-Type: application/json 296 | < Transfer-Encoding: chunked 297 | { 298 | "data": "Successfully inserted" 299 | } 300 | ``` 301 | -------------------------------------------------------------------------------- /common/responseLogger.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2016 ServiceNow, Inc. 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | /* 10 | * This module provides a simple response logging mechanism 11 | */ 12 | module.exports = { 13 | logResponse: function(options, response, body) { 14 | if (options.verbose) { 15 | console.log("RESPONSE " + response.statusCode); 16 | console.log("RESPONSE headers"); console.log(response.headers); 17 | console.log("RESPONSE body"); console.log(JSON.stringify(body, null, 2)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/usage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2016 ServiceNow, Inc. 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | /* 10 | * Wraps the command line arguments 11 | */ 12 | module.exports = { 13 | 14 | // gets options from the command line 15 | processArgs: function(filename) { 16 | // helper function for printing usage info 17 | function printUsageAndExit() { 18 | console.log("\nUsage: node " + filename + " [ -h ] [ -p ] [ -v ]"); 19 | console.log(" -h, --help Show this usage help"); 20 | console.log(" -p , --port= HTTP server listen port (default 3000)"); 21 | console.log(" -v, --verbose Verbose HTTP output"); 22 | process.exit(-1); 23 | } 24 | 25 | var minimist = require('minimist'); 26 | 27 | var o = { 28 | port: 3000, // default listen port 29 | verbose: false 30 | }; 31 | 32 | var argv = minimist(process.argv.slice(2)); 33 | o.verbose = 'verbose' in argv || 'v' in argv; 34 | 35 | // print the usage on --help or -h 36 | if ('help' in argv || 'h' in argv) { 37 | printUsageAndExit(); 38 | } 39 | 40 | // get the listen port 41 | if ('port' in argv) { 42 | o.port = argv['port']; 43 | } else if ('p' in argv) { 44 | o.port = argv['p']; 45 | } 46 | if (isNaN(o.port) || o.port % 1 !== 0) { 47 | console.log("Error: port must be an integer"); 48 | printUsageAndExit(); 49 | } 50 | 51 | return o; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dispatcher/loginDispatcher.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2016 ServiceNow, Inc. 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | /* 10 | This module handles the login requests. It receives the message from browser and extracts the username, password and 11 | ServiceNow instance url from the request. Then authenticate the user with snAuth module and finally saves the cookie 12 | in client session. 13 | */ 14 | module.exports = { 15 | login: function(serverRequest, serverResponse) { 16 | var bodyString = ''; 17 | var body = {}; 18 | 19 | // Server receives data as parts. Use this method to concatenate them to one message. 20 | serverRequest.on('data', function(data) { 21 | bodyString += data; 22 | }); 23 | 24 | // At the end use the full string to extract the data and invoke back end. 25 | serverRequest.on('end', function() { 26 | body = JSON.parse(bodyString); 27 | var SNAuth = serverRequest.app.get('snAuth'); 28 | var options = serverRequest.app.get('options'); 29 | var snAuth = new SNAuth(body.hosturl, body.username, body.password, options); 30 | 31 | // Initialize the sessions to store details. 32 | serverRequest.session['snConfig'] = {}; 33 | serverRequest.session.snConfig['snInstanceURL'] = body.hosturl; 34 | 35 | // Invoke the snAuth module. That returns the response and cookie. 36 | snAuth.authenticate(function(error, response, body, cookie) { 37 | serverRequest.app.get('respLogger').logResponse(options, response, body); 38 | if (response) { 39 | if (response && response.statusCode == 200) { 40 | // This means username, password is valid and response contains the valid cookie. 41 | serverRequest.session.snConfig['snCookie'] = cookie; 42 | serverResponse.status(response.statusCode).send(body); 43 | } else if (response && response.statusCode == 401) { 44 | // This means username or password is invalid. 45 | serverResponse.status(response.statusCode).send('Invalid username or password'); 46 | } else { 47 | serverResponse.status(response.statusCode).send( 48 | 'Error occured while communicating with ServiceNow instance. '); 49 | } 50 | } else { 51 | // This means either ServiceNow instance url is wrong or it is down. 52 | serverResponse.status(500).send( 53 | 'Error occured while communicating with ServiceNow instance. '); 54 | } 55 | }); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /dispatcher/taskDispatcher.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2016 ServiceNow, Inc. 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | /* 10 | * This module dispatches the task related operations to relevant task api method. It assumes an earlier 11 | * request using authenticate module should have save the cookie in session. 12 | */ 13 | module.exports = { 14 | // Returns the tasks assigned to user. 15 | getTasks: function(serverRequest, serverResponse) { 16 | if (serverRequest.session && serverRequest.session.snConfig && serverRequest.session.snConfig.snCookie) { 17 | var SNTask = serverRequest.app.get('snTask'); 18 | var options = serverRequest.app.get('options'); 19 | var snTask = new SNTask(serverRequest.session.snConfig.snInstanceURL, serverRequest.session.snConfig.snCookie, options); 20 | snTask.getTasks(function(error, response, body) { 21 | serverRequest.app.get('respLogger').logResponse(options, response, body); 22 | if (!error) { 23 | if (response.statusCode == 200) { 24 | // the successful body message should contain all the tasks as a JSON message. 25 | serverResponse.status(response.statusCode).send(body); 26 | } else if (response.statusCode == 400) { 27 | serverResponse.status(response.statusCode).send('The Task Tracker API is not found on this instance. Did you install the "My Work" Update Set?'); 28 | } else { 29 | serverResponse.status(response.statusCode).send( 30 | 'Error occured while communicating with ServiceNow instance. ' + response.statusMessage); 31 | } 32 | } else { 33 | serverResponse.status(500).send( 34 | 'Error occured while communicating with ServiceNow instance. '); 35 | } 36 | }); 37 | } else { 38 | serverResponse.status(401).send('User sesion invalidated'); 39 | } 40 | }, 41 | // Adds a comments for a given task. Task id is part of the url and comment is send at the request body. Here we 42 | // assume the user is a legitimate user and should have logged in and viewed tasks before making this request. 43 | addComment: function(serverRequest, serverResponse) { 44 | var bodyString = ''; 45 | // Aggregate the request body parts to create the whole message. 46 | serverRequest.on('data', function(data) { 47 | bodyString += data; 48 | }); 49 | 50 | //After receive all parts, we need to send the message to ServiceNow backend. 51 | serverRequest.on('end', function() { 52 | var body = JSON.parse(bodyString); 53 | var SNTask = serverRequest.app.get('snTask'); 54 | var options = serverRequest.app.get('options'); 55 | var snTask = new SNTask(serverRequest.session.snConfig.snInstanceURL, serverRequest.session.snConfig.snCookie, options); 56 | snTask.addComment(serverRequest.params.taskid, body.comment, function(error, response, body) { 57 | serverRequest.app.get('respLogger').logResponse(options, response, body); 58 | serverResponse.status(response.statusCode).send(body); 59 | }); 60 | }); 61 | }, 62 | 63 | // Returns the comments for a given task. The task id is received as the part of url. Here also we assume user is a legitimate 64 | // user and he has already logged in and has viewed the tasks. 65 | getComments: function(serverRequest, serverResponse) { 66 | var SNTask = serverRequest.app.get('snTask'); 67 | var options = serverRequest.app.get('options'); 68 | // Session cookie should be available in session at this time. 69 | var snTask = new SNTask(serverRequest.session.snConfig.snInstanceURL, serverRequest.session.snConfig.snCookie, options); 70 | snTask.getComments(serverRequest.params.taskid, function(error, response, body) { 71 | serverRequest.app.get('respLogger').logResponse(options, response, body); 72 | serverResponse.status(response.statusCode).send(body); 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /images/arch_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/arch_diagram.jpg -------------------------------------------------------------------------------- /images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/login.png -------------------------------------------------------------------------------- /images/node_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/node_flow.png -------------------------------------------------------------------------------- /images/nodejs-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/nodejs-logo.jpg -------------------------------------------------------------------------------- /images/servicenow-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/servicenow-logo.jpg -------------------------------------------------------------------------------- /images/task_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/task_detail.png -------------------------------------------------------------------------------- /images/task_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceNow/example-restclient-myworkapp-nodejs/6e4b93759c45b1d33a2515e40af049f6b784d8f0/images/task_list.png -------------------------------------------------------------------------------- /mywork_update_set/sys_remote_update_set_2f48a7d74f4652002fa02f1e0210c785.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11e9a50b4f0a12002fa02f1e0210c737 5 | My Work 6 | x_snc_my_work 7 | 2.0.0 8 | 9 | 10 | 11 | 12 | 13 | My Work 14 | 15 | 16 | f94867d74f4652002fa02f1e0210c735 17 | loaded 18 | 19 | admin 20 | 2016-03-14 19:01:11 21 | 2f48a7d74f4652002fa02f1e0210c785 22 | 0 23 | admin 24 | 2016-03-14 19:01:11 25 | 26 | 27 | 28 | 29 | 30 | INSERT_OR_UPDATE 31 | 11e9a50b4f0a12002fa02f1e0210c737 32 | customer 33 | 34 | sys_script_include_4b43f5034f4a12002fa02f1e0210c7c5 35 | <?xml version="1.0" encoding="UTF-8"?><record_update table="sys_script_include"><sys_script_include action="INSERT_OR_UPDATE"><access>package_private</access><active>true</active><api_name>x_snc_my_work.TaskRetriever</api_name><client_callable>false</client_callable><description>Reusable logic to retrieve tasks</description><name>TaskRetriever</name><script><![CDATA[var TaskRetriever = Class.create(); 36 | TaskRetriever.prototype = { 37 | initialize: function() { 38 | }, 39 | getTaskCount:function(userId, tableName){ 40 | var gr = new GlideRecordSecure(tableName); 41 | gr.addQuery('assigned_to', userId); 42 | gr.addQuery('active', true); 43 | gr.query(); 44 | return gr.getRowCount(); 45 | }, 46 | getTaskRecordsForUser:function(userId){ 47 | var gr = new GlideRecordSecure('task'); 48 | gr.addQuery('assigned_to', userId); 49 | gr.addQuery('active', true); 50 | gr.orderBy('sys_created_on'); 51 | gr.query(); 52 | return gr; 53 | }, 54 | getTaskRecordsForTeam:function(teamName){ 55 | var rec = new GlideRecord("sys_user_group"); 56 | rec.addQuery("name",teamName); 57 | rec.query(); 58 | rec.next(); 59 | var grpId = rec.getValue("sys_id"); 60 | var gr = new GlideRecordSecure('task'); 61 | gr.addQuery('assignment_group', grpId); 62 | gr.addQuery('active', true); 63 | gr.orderBy('sys_created_on'); 64 | gr.query(); 65 | return gr; 66 | }, 67 | populateResponse:function(taskRecs){ 68 | var resp={}; 69 | while (taskRecs.next()) { 70 | var clazz = taskRecs.getDisplayValue('sys_class_name'); 71 | if (!(clazz in resp)) 72 | resp[clazz] = []; 73 | 74 | var sys_id = taskRecs.getValue('sys_id'); 75 | resp[clazz].push({ 76 | number: taskRecs.getValue('number'), 77 | short_desc: taskRecs.getValue('short_description'), 78 | sys_id: sys_id, 79 | link: gs.getProperty('glide.servlet.uri') + 'api/now/v2/table/' + clazz + '/' + sys_id, 80 | snowui: gs.getProperty('glide.servlet.uri') + clazz + '.do?sys_id=' + sys_id, 81 | created: taskRecs.getValue('sys_created_on') 82 | }); 83 | } 84 | return resp; 85 | }, 86 | type: 'TaskRetriever' 87 | };]]></script><sys_class_name>sys_script_include</sys_class_name><sys_created_by>admin</sys_created_by><sys_created_on>2016-03-13 15:53:00</sys_created_on><sys_customer_update>true</sys_customer_update><sys_id>4b43f5034f4a12002fa02f1e0210c7c5</sys_id><sys_mod_count>1</sys_mod_count><sys_name>TaskRetriever</sys_name><sys_package display_value="My Work" source="x_snc_my_work">11e9a50b4f0a12002fa02f1e0210c737</sys_package><sys_policy>read</sys_policy><sys_replace_on_upgrade>false</sys_replace_on_upgrade><sys_scope display_value="My Work">11e9a50b4f0a12002fa02f1e0210c737</sys_scope><sys_update_name>sys_script_include_4b43f5034f4a12002fa02f1e0210c7c5</sys_update_name><sys_updated_by>admin</sys_updated_by><sys_updated_on>2016-03-14 19:00:42</sys_updated_on></sys_script_include></record_update> 88 | 2f48a7d74f4652002fa02f1e0210c785 89 | false 90 | admin 91 | 2016-03-14 19:01:11 92 | 2348a7d74f4652002fa02f1e0210c786 93 | 0 94 | admin 95 | 2016-03-14 19:01:11 96 | 97 | TaskRetriever 98 | Script Include 99 | global 100 | 101 | 102 | 103 | 104 | INSERT_OR_UPDATE 105 | 11e9a50b4f0a12002fa02f1e0210c737 106 | customer 107 | 108 | sys_ws_version_b7cc25cb4f0a12002fa02f1e0210c7a6 109 | truefalsefalsesys_ws_versionadmin2016-03-13 15:23:08trueb7cc25cb4f0a12002fa02f1e0210c7a62v111e9a50b4f0a12002fa02f1e0210c737false11e9a50b4f0a12002fa02f1e0210c737sys_ws_version_b7cc25cb4f0a12002fa02f1e0210c7a6admin2016-03-14 18:18:471v1fc1aa14b4f0a12002fa02f1e0210c72a]]> 110 | 2f48a7d74f4652002fa02f1e0210c785 111 | false 112 | admin 113 | 2016-03-14 19:01:11 114 | 2748a7d74f4652002fa02f1e0210c786 115 | 0 116 | admin 117 | 2016-03-14 19:01:11 118 |
119 | v1 120 | Scripted REST Version 121 | global 122 | 123 | 124 | 125 | 126 | INSERT_OR_UPDATE 127 | 11e9a50b4f0a12002fa02f1e0210c737 128 | customer 129 | 130 | sys_ws_definition_fc1aa14b4f0a12002fa02f1e0210c72a 131 | true/api/x_snc_my_work/trackerapplication/json,application/xml,text/xmlfalseNo active default versionhttps://morningstoryanddilbert.files.wordpress.com/2014/09/266-strip.giftrueTask tracker APIx_snc_my_workapplication/json,application/xml,text/xmlfalsetrackerUser task trackingsys_ws_definitionadmin2016-03-13 15:14:37truefc1aa14b4f0a12002fa02f1e0210c72a3Task tracker API11e9a50b4f0a12002fa02f1e0210c737false11e9a50b4f0a12002fa02f1e0210c737sys_ws_definition_fc1aa14b4f0a12002fa02f1e0210c72aadmin2016-03-14 18:58:05]]> 132 | 2f48a7d74f4652002fa02f1e0210c785 133 | false 134 | admin 135 | 2016-03-14 19:01:11 136 | 6348a7d74f4652002fa02f1e0210c786 137 | 0 138 | admin 139 | 2016-03-14 19:01:11 140 |
141 | Task tracker API 142 | Scripted REST API 143 | global 144 | 145 | 146 | 147 | 148 | INSERT_OR_UPDATE 149 | 11e9a50b4f0a12002fa02f1e0210c737 150 | customer 151 | 152 | sys_app_11e9a50b4f0a12002fa02f1e0210c737 153 | truetrueMy Workfalsex_snc_my_workx_snc_my_worksys_appadmin2016-03-13 15:10:2411e9a50b4f0a12002fa02f1e0210c7371admin2016-03-14 19:01:04