Last active
March 6, 2017 15:38
-
-
Save NielsGregers/d725e83f602adf33b14d5b4301d2eca6 to your computer and use it in GitHub Desktop.
ServiceNow (SN) and Visual Studio Team Services (VSTS) integration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Incidents from ServiceNow | |
* | |
*/ | |
/** | |
* compares source and target array - match source.key with target.foreignKey | |
* | |
* @param {any} source | |
* @param {any} target | |
* @param {any} cb | |
* @returns | |
*/ | |
function compareArray(source, target, cb) { | |
target = target.result; | |
var results = { | |
new: [], | |
removed: [] | |
}; | |
for (var x = 0; x < source.length; x++) { | |
var sourceItem = source[x]; | |
var match = false; | |
for (var y = 0; y < target.length; y++) { | |
var targetItem = target[y]; | |
if (targetItem.foreignKey === sourceItem.key) { | |
match = true; | |
} | |
} | |
if (!match) { | |
results.new.push(sourceItem); | |
} | |
} | |
for (var y = 0; y < target.length; y++) { | |
var targetItem = target[y]; | |
var match = false; | |
for (var x = 0; x < source.length; x++) { | |
var sourceItem = source[x]; | |
if (targetItem.foreignKey === sourceItem.key) { | |
match = true; | |
} | |
} | |
if (!match) { | |
results.removed.push(targetItem); | |
} | |
} | |
return cb(null, results); | |
} | |
/** | |
* reads Visual Studio WorkItems | |
* | |
* @param {any} auth | |
* @param {any} filter | |
* @param {any} cb | |
* @returns | |
*/ | |
function readVisualStudioWorkItems(auth, tenant, project, filter, cb) { | |
var unirest = require("unirest"); | |
var result = { | |
items: [] | |
}; | |
function check(items) { | |
try { | |
if (items.length <= 0) { | |
console.log("no more to process"); | |
return cb(null, { | |
result: result.items | |
}); | |
} | |
console.log("Remaining", items.length); | |
var comma = ""; | |
var ids = ""; | |
var count = 0; | |
while ((items.length > 0) && (count < 200)) { | |
var item = items.splice(0, 1); | |
ids += comma; | |
ids += item[0].id; | |
comma = ","; | |
} | |
var req = unirest("GET", "https://" + tenant + ".visualstudio.com/DefaultCollection/_apis/wit/workitems?ids=" + ids); | |
req.headers({ | |
"cache-control": "no-cache", | |
"authorization": auth, | |
"content-type": "application/json", | |
"accept": "application/json" | |
}); | |
req.end(function(res) { | |
if (res.error) { | |
return cb(res.error, null); | |
} | |
for (var x = 0; x < res.body.count; x++) { | |
var w = res.body.value[x]; | |
var item = {}; | |
item.title = w.fields["System.Title"]; | |
var sntagIndex = item.title.indexOf("[SN:"); | |
var suffix = item.title.substr(sntagIndex + 4); | |
var endtagIndex = suffix.indexOf("]"); | |
if (endtagIndex > -1) { | |
item.serviceNowId = suffix.substr(0, endtagIndex); | |
item.foreignKey = "SN:" + item.serviceNowId; | |
} | |
result.items.push(item); | |
} | |
check(items); // take the next batch .. | |
}); | |
} catch (error) { | |
return cb(error); | |
} | |
} | |
var req = unirest("POST", "https://" + tenant + ".visualstudio.com/DefaultCollection/_apis/wit/wiql"); | |
req.query({ | |
"api-version": "1.0" | |
}); | |
req.headers({ | |
"cache-control": "no-cache", | |
"authorization": auth, | |
"content-type": "application/json", | |
"accept": "application/json" | |
}); | |
req.send("{query:' Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.Title] CONTAINS \"[SN:\" AND [System.TeamProject] = \"" + project + "\" order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc' }"); | |
req.end(function(res) { | |
if (res.error) { | |
return cb(res.error, res.body); | |
} | |
check(res.body.workItems); | |
}); | |
} | |
/** | |
* Reads ServiceNow incidents matching filter | |
* | |
* @param {any} auth | |
* @param {any} filter | |
* @param {any} cb | |
*/ | |
function readServiceNowIncidents(auth, tenant, filter, cb) { | |
var unirest = require("unirest"); | |
var req = unirest.get("https://" + tenant + ".service-now.com/api/now/v1/table/incident"); | |
req.headers({ | |
"cache-control": "no-cache", | |
"accept": "application/json", | |
"authorization": auth | |
}); | |
req.query({ | |
"sysparm_limit": "10000", | |
"sysparm_fields": "number,sys_id,u_incident_type,active,short_description,u_business_consequences,caller_id", | |
"sysparm_query": "active=true^" + filter, | |
"sysparm_view": "ess", | |
"sysparm_cancelable": "true" | |
}); | |
req.end(function(res) { | |
if (res.error) { | |
return cb(res.error); | |
} else { | |
var results = []; | |
var callbacks = 0; | |
res.body.result.forEach(function(x) { | |
var item = {}; | |
item.key = "SN:" + x.number; | |
item.number = x.number; //'INC0109501' | |
item.sys_id = x.sys_id; // 'e66acbb76fa7aa00e8cf2f7bcf3ee4b2' | |
item.title = x.short_description; //'Chrome is unable to communicate with google.com' | |
item.type = x.u_incident_type; // 'Incident' | |
item.status = x.active; // 'true' | |
item.body = x.u_business_consequences; | |
item.url = "https://" + tenant + ".service-now.com/nav_to.do?uri=vtb_task.do?sys_id=" + x.sys_id + "%26sysparm_view=ess"; | |
callbacks += 1; | |
unirest | |
.get(x.caller_id.link) | |
.headers({ | |
"cache-control": "no-cache", | |
"accept": "application/json", | |
"authorization": auth | |
}) | |
.end(function(caller) { | |
callbacks -= 1; | |
if (caller.error) { | |
return cb(caller.error); | |
} | |
item.ownerDisplayName = caller.body.result.u_preferred_name; | |
item.ownerEmail = caller.body.result.email; | |
results.push(item); | |
if (callbacks === 0) { | |
return cb(null, results); | |
} | |
}); | |
}, this); | |
} | |
}); | |
} | |
/** | |
* emit new Items by creating Stamplay objects | |
* | |
* @param {any} stamplayObject | |
* @param {any} newItems | |
* @param {any} cb | |
* @returns | |
*/ | |
function signalNew(auth, tenant, stamplayObject, newItems, cb) { | |
var Stamplay = require('stamplay'); | |
var stamplay = new Stamplay(tenant, auth); | |
var jobs = 0; | |
var createdItems = []; | |
if (newItems.length === 0) { | |
return cb(null, createdItems); | |
} | |
newItems.forEach(function(newItem) { | |
jobs += 1; | |
var newObject = { | |
"key": newItem.key, | |
"sysid": newItem.sys_id, | |
"title": newItem.title, | |
"url": newItem.url, | |
"ownername": newItem.ownerDisplayName, | |
"owneremail": newItem.ownerEmail | |
}; | |
stamplay.Object(stamplayObject) | |
.save(newObject, | |
function(err, res) { | |
if (err) { | |
return cb(err); | |
} | |
createdItems.push(res); | |
jobs -= 1; | |
if (jobs <= 0) { | |
return cb(null, createdItems); | |
} | |
}); | |
}, this); | |
} | |
/** | |
* entry Point | |
*/ | |
module.exports = function(context, cb) { | |
if (!context) { return cb("Need a context"); } | |
if (!context.secrets) { return cb("Need a context.secrets object"); } | |
if (!context.secrets.serviceNowAuth) { return cb("Need a context.secrets.serviceNowAuth property"); } | |
if (!context.secrets.visualStudioAuth) { return cb("Need a context.secrets.visualStudioAuth property"); } | |
if (!context.secrets.stamplayAuth) { return cb("Need a context.secrets.stamplayAuth property"); } | |
if (!context.body) { return cb("Need a context body object"); } | |
if (!context.body.serviceNowFilter) { return cb("Need a context.body.serviceNowFilter property"); } | |
if (!context.body.visualStudioFilter) { return cb("Need a context.body.serviceNowFilter property"); } | |
if (!context.body.stamPlayObject) { return cb("Need a context.body.stamPlayObject property"); } | |
if (!context.body.vstsProject) { return cb("Need a context.body.vstsProject"); } | |
if (!context.body.vstsTenant) { return cb("Need a context.body.vstsTenant"); } | |
if (!context.body.snTenant) { return cb("Need a context.body.snTenant"); } | |
if (!context.body.stamplayTenant) { return cb("Need a context.body.stamplayTenant"); } | |
readServiceNowIncidents( | |
context.secrets.serviceNowAuth, | |
context.body.snTenant, | |
context.body.serviceNowFilter, | |
function(err, incidents) { | |
if (err) { | |
return cb(err); | |
} | |
readVisualStudioWorkItems( | |
context.secrets.visualStudioAuth, | |
context.body.vstsTenant, | |
context.body.vstsProject, | |
context.body.visualStudioFilter, | |
function(err, items) { | |
if (err) { | |
return cb(err); | |
} | |
compareArray( | |
incidents, | |
items, | |
function(err, differences) { | |
if (err) { | |
return cb(err); | |
} | |
signalNew( | |
context.secrets.stamplayAuth, | |
context.body.stamplayTenant, | |
context.body.stamPlayObject, | |
differences.new, | |
function(err, newObjects) { | |
if (err) { | |
return cb(err); | |
} | |
return cb(null, newObjects); | |
}); | |
}); | |
}); | |
}); | |
}; | |
module.exports.test = function(exec, secrets) { | |
exec({ | |
body: { | |
serviceNowFilter: "cmdb_ci=e13650532b1e6a00cb7b393db5da15e5", | |
visualStudioFilter: "3N_CB", | |
stamPlayObject: "incident_for_3n_cb", | |
vstsProject: "3N_CB", | |
vstsTenant: secrets.tenant.vsto, | |
snTenant: secrets.tenant.snc, | |
stamplayTenant: secrets.tenant.stamplay, | |
}, | |
secrets: { | |
serviceNowAuth: secrets.snc, | |
visualStudioAuth: secrets.vsto, | |
stamplayAuth: secrets.stamplay | |
} | |
}, module.exports); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment