node-red-contrib-spark
Advanced tools
Comparing version 1.1.1 to 2.0.0
@@ -0,1 +1,8 @@ | ||
#### 2.0.0: Major Release | ||
- Updated all node inline documentation and label formats | ||
- api node now sends array responses as individual sequential messages | ||
- parse node rebuilt to allow more than 1 property to be parsed from payload | ||
- updated README.md to include updated documentation around parse and api node | ||
#### 1.1.1: Maintenance Release | ||
@@ -29,5 +36,5 @@ | ||
- Updated node labels to "api" and "webhook" | ||
- Updated node category to "cisco_spark" | ||
- updated node labels to "api" and "webhook" | ||
- updated node category to "cisco_spark" | ||
#### 1.0.0: Initial Release |
{ | ||
"name": "node-red-contrib-spark", | ||
"version": "1.1.1", | ||
"version": "2.0.0", | ||
"description": "Node-RED Nodes to integrate with the Cisco Spark API", | ||
@@ -5,0 +5,0 @@ "dependencies": { |
@@ -5,2 +5,4 @@ # node-red-contrib-spark | ||
Version 2.0.0 [(changelog)](https://github.com/nmarus/node-red-contrib-spark/blob/master/CHANGELOG.md) | ||
![](https://github.com/nmarus/node-red-contrib-spark/raw/master/images/flow01.jpg) | ||
@@ -12,3 +14,3 @@ | ||
* **webhook** - This Node creates, removes, and manages the Webhook features of the Spark API. Once deployed, and subsequently triggered, the contents of the notification will be sent to the output of this Node. | ||
* **parser** - This is a utility Node that accepts input from the output of either the Api or Webhook Node. This acts similarly to the `map` functionality provided in many programming languages and additionally provides options when dealing with input that is in the form of a collection (array of objects). | ||
* **parser** - This is a utility Node that accepts input from the output of either the API or Webhook Node. | ||
* **auth** - This is a Config Node that holds the credential Bearer Token for the Spark API. Once initially defined, either under the Api or Webhook Node, it will be available to select on all other Api and Webhook Nodes that are created. You can define multiple Auth profiles so as to work with different Spark Accounts or Bots within the same flow. | ||
@@ -55,3 +57,3 @@ | ||
The Spark API Node sends REST queries via messages received by the input connector in the `msg.payload` object. Results of the API call are provided at the output in the `msg.payload` object. | ||
The Spark API Node sends REST queries via messages received by the input connector in the `msg.payload` object. Results of the API call are provided at the output in the `msg.payload` object. If multiple records are returned from the Spark API Call, these are passed to the output as individual sequential messages. The `msg.parts` property is set appropriately for use with the `join` node if a single array payload is preferred. | ||
@@ -66,3 +68,3 @@ ![](https://github.com/nmarus/node-red-contrib-spark/raw/master/images/api-node.jpg) | ||
By convention the output from the Spark API call will have a `msg.payload` property containing the results of the API call in JSON format. The format of this JSON object will be the same as documented at [developer.ciscospark.com](https://developer.ciscospark.com) for the responses from the API call. | ||
By convention, the output from the Spark API call will have a `msg.payload` property. This contains the results of the API call in JSON format. The format of this JSON object will be the same as documented at [developer.ciscospark.com](https://developer.ciscospark.com) for the responses from the API call. | ||
@@ -72,9 +74,7 @@ Additionally the following are defined as part of the msg object: | ||
* `msg.status` : http return code | ||
* `msg.error` : error object (will evaluate to `null` when no error is present) | ||
* `msg.error.message` : error message | ||
* `msg.error.description` : error description (only available for certain errors) | ||
* `msg.error.trackingId` : tracking id (only available for certain errors) | ||
* `msg.headers` - response headers object | ||
* `msg._msgid` - unique identifier | ||
#### Multiple Results | ||
If multiple records are returned from the Spark API Call, these are passed to the output as individual sequential messages. The `msg.parts` property is set appropriately for use with the `join` node if a single array payload is preferred. | ||
**Example: Get Person by Email** | ||
@@ -184,16 +184,4 @@ | ||
The Spark Parse Node allows parsing of messages received from either the Webhook or API Node. The parsed value is placed in the `msg.payload` of the first output. The value of the "parse" field is delivered in `msg.topic` for use with supporting functions like `join`. The original `msg.payload` is passed through to the second output. | ||
The Spark Parse Node allows parsing of specific properties from the JSON `msg.payload` received from either the "Spark Webhook Node" or the "Spark API Node". | ||
The output specifies how the `msg.payload` is formatted. Options are: | ||
<ul> | ||
<li>original - The original value of the property without modifying data | ||
type.</li> | ||
<li>object - The original value of the property placed into an object with | ||
the object key being the parser value, or optionally the topic if | ||
specified.</li> | ||
</ul> | ||
</p> | ||
If the parser input receives an array, each element of the array is parsed individually. The results are returned as multiple sequential messages to the output with each msg having a `msg.payload` and `msg.topic` property. | ||
![](https://github.com/nmarus/node-red-contrib-spark/raw/master/images/parser-node.jpg) | ||
@@ -203,8 +191,6 @@ | ||
* **Parse** - The object property to parse from the input. | ||
* **Output** - The selector on how to handle parsed output: | ||
* **the individual property value** - Outputs the original value of the property without modifying data type. | ||
* **a key/value object** - Outputs the original value of the property placed into an object with the object key being the parser value, or optionally the topic if specified. | ||
* **Topic** - By default, the value of "parse" is used as msg.topic. This can be overridden here if needed. | ||
Define each *"property"* to parse from the inbound `msg.payload`. Optionally, define a *"name"* to remap the property name used in the oubound JSON object. This allows remapping of properties such as `msg.payload.id` to `msg.payload.roomId`. If *"name"* is left undefined, it will use the same value as the original property. Add additional rows to define multiple properties and names to construct the output JSON object. | ||
![](https://github.com/nmarus/node-red-contrib-spark/raw/master/images/parser-node-config.jpg) | ||
## License | ||
@@ -211,0 +197,0 @@ |
@@ -6,2 +6,3 @@ module.exports = function(RED) { | ||
var fs = require('fs'); | ||
var _ = require('lodash'); | ||
@@ -44,7 +45,11 @@ var swaggerClient = null; | ||
processResponse(msg, response); | ||
}, function(error) { | ||
processError(msg, error); | ||
}, function(errMessage) { | ||
if(errMessage) { | ||
processError(new Error(errMessage)); | ||
} else { | ||
processError(new Error('swagger client returned undefined error')); | ||
} | ||
}); | ||
} else { | ||
processError(msg, 'error with spark api token or swagger client'); | ||
processError(new Error('spark api token or swagger client invalid or not defined')); | ||
} | ||
@@ -62,2 +67,3 @@ } | ||
node.method = n.method; | ||
node.id = n.id; | ||
@@ -67,9 +73,2 @@ node.reqCount = 0; | ||
// set default status | ||
node.status({ | ||
fill: 'blue', | ||
shape: 'ring', | ||
text: 'Spark API: ready' | ||
}); | ||
// set node status visual indicators | ||
@@ -143,80 +142,53 @@ function setNodeStatus(state) { | ||
}, 200); | ||
}; | ||
} | ||
function processResponse(msg, response) { | ||
// extend msg object | ||
msg.payload = {}; | ||
msg.error = {}; | ||
msg.status = 500; | ||
reqPing(); | ||
// handle undefined response | ||
if(typeof response === 'undefined') { | ||
// set error message | ||
msg.error.message = 'response not received'; | ||
var newMsg = {}; | ||
newMsg.topic = node.resource + '.' + node.method; | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// handle object response | ||
if(response && typeof response === 'object') { | ||
// set node status | ||
setNodeStatus('warning'); | ||
} | ||
// handle string response | ||
else if(typeof response === 'string') { | ||
msg.payload = response; | ||
// set error message | ||
msg.error.message = 'response is not object'; | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('warning'); | ||
} | ||
// handle object response | ||
else if(response && typeof response === 'object') { | ||
// capture headers | ||
if(response && typeof response.headers === 'object') { | ||
msg.headers = response.headers; | ||
if(response.hasOwnProperty('headers') && typeof response.headers === 'object') { | ||
newMsg.headers = response.headers; | ||
} | ||
// capture status | ||
if(response && response.status) { | ||
msg.status = response.status; | ||
if(response.hasOwnProperty('status')) { | ||
newMsg.status = response.status; | ||
} else { | ||
newMsg.status = 500; | ||
} | ||
// handle non 2xx status | ||
if(msg.status > 299) { | ||
processError(response); | ||
return; | ||
} | ||
// if response.data | ||
if(response.hasOwnProperty('data')) { | ||
var data = response.data; | ||
// if empty response | ||
if(data === '' || data === '{}') { | ||
// if response is from a delete... | ||
if(newMsg.status === 204) { | ||
newMsg.payload = {}; | ||
// determine if empty respose is from delete | ||
if(response.status && response.status === 204) { | ||
// set msg.error to null | ||
msg.error = null; | ||
} else { | ||
// set error message | ||
msg.error.message = 'response is empty'; | ||
// send message | ||
node.send(newMsg); | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
return null; | ||
} | ||
// set node status | ||
setNodeStatus('warning'); | ||
} | ||
// if empty response... | ||
if(typeof data === 'string' && (data === '' || data === '{}')) { | ||
// show warning | ||
node.warn('error processsing response'); | ||
// set node status | ||
setNodeStatus('warning'); | ||
return null; | ||
} | ||
// if response.data is string | ||
else if(typeof data === 'string') { | ||
// if response.data is valid... | ||
if(typeof data === 'string') { | ||
// attempt parsing of json | ||
@@ -227,233 +199,104 @@ try { | ||
catch(err) { | ||
// set error message | ||
msg.error.message = 'json data not found in response'; | ||
// show warning | ||
node.warn('error: ' + err.message || 'undefined'); | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('warning'); | ||
// set msg.topic | ||
msg.topic = node.resource + '.' + node.method; | ||
// send message | ||
node.send(msg); | ||
return; | ||
return null; | ||
} | ||
// abstract away items container for array response | ||
if(data && data.hasOwnProperty('items') && data.items instanceof Array) { | ||
msg.payload = data.items; | ||
} else { | ||
msg.payload = data; | ||
} | ||
// if response is array of items | ||
if(typeof data === 'object' && data.hasOwnProperty('items') && data.items instanceof Array) { | ||
// set msg.error to null | ||
msg.error = null; | ||
} | ||
} | ||
// if as multiple messages | ||
if(true) { | ||
// define _msgid | ||
newMsg._msgid = RED.util.generateId(); | ||
// else, response.data does not exist... | ||
else { | ||
// set error message | ||
msg.error.message = 'response has no content'; | ||
// define messages | ||
var newMsgArray = _.map(data.items, function(item, index) { | ||
var msg = _.cloneDeep(newMsg); | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
msg.payload = item; | ||
msg.parts = { | ||
"id": msg._msgid, | ||
"type": "array", | ||
"index": index, | ||
"count": data.items.length | ||
}; | ||
return msg; | ||
}); | ||
// set node status | ||
setNodeStatus('warning'); | ||
} | ||
} | ||
// send message | ||
node.send([newMsgArray]); | ||
} | ||
// set msg.topic | ||
msg.topic = node.resource + '.' + node.method; | ||
// else, as single message | ||
else { | ||
// define _msgid | ||
newMsg._msgid = msg._msgid; | ||
// send msg | ||
node.send(msg); | ||
} | ||
// define paypload | ||
newMsg.payload = data.items; | ||
// api validation error | ||
function processError(msg, error) { | ||
// extend msg object | ||
msg.payload = {}; | ||
msg.error = {}; | ||
msg.status = 500; | ||
// send message | ||
node.send(newMsg); | ||
} | ||
// handle undefined error | ||
if(typeof error === 'undefined') { | ||
// set error message | ||
msg.error.message = 'unknown error'; | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('error'); | ||
} | ||
// handle string error | ||
else if(error && typeof error === 'string') { | ||
// set error message | ||
msg.error.message = error; | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('error'); | ||
} | ||
// handle object response | ||
else if(error && typeof error === 'object') { | ||
// capture headers | ||
if(error && typeof error.headers === 'object') { | ||
msg.headers = error.headers; | ||
} | ||
// capture status | ||
if(error && error.status) { | ||
msg.status = error.status; | ||
} | ||
// check for error object from Spark API | ||
if(error && error.hasOwnProperty('data')) { | ||
var data = error.data; | ||
// if empty response | ||
if(data === '' || data === '{}') { | ||
// set error message | ||
msg.error.message = 'unknown error'; | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('error'); | ||
} | ||
// if error.data is string | ||
else if(typeof data === 'string') { | ||
// atempt parsing of json | ||
try { | ||
var data = JSON.parse(data); | ||
} | ||
catch(err) { | ||
// set error message | ||
msg.error.message = 'unknown error'; | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// else, response is single item | ||
else { | ||
newMsg.payload = data; | ||
// set node status | ||
setNodeStatus('error'); | ||
// set msg.topic | ||
msg.topic = node.resource + '.' + node.method; | ||
// send message | ||
node.send(msg); | ||
return; | ||
node.send(newMsg); | ||
} | ||
// msg.error.message | ||
if(data.hasOwnProperty('message')) { | ||
msg.error.message = data.message; | ||
} else { | ||
// define errors that do not generate message | ||
switch(msg.status) { | ||
case 400: | ||
// set error message | ||
msg.error.message = '400 (request invalid) response received'; | ||
break; | ||
case 401: | ||
// set error message | ||
msg.error.message = '401 (request unauthorized) response received'; | ||
break; | ||
case 403: | ||
// set error message | ||
msg.error.message = '403 (request not allowed) response received'; | ||
break; | ||
case 404: | ||
// set error message | ||
msg.error.message = '404 (request unauthorized) response received'; | ||
break; | ||
case 429: | ||
// set error message | ||
msg.error.message = '429 (rate limiter hit) response received'; | ||
break; | ||
case 500: | ||
// set error message | ||
msg.error.message = '500 (something went wrong on server) response received'; | ||
break; | ||
case 501: | ||
// set error message | ||
msg.error.message = '501 response received'; | ||
break; | ||
case 502: | ||
// set error message | ||
msg.error.message = '502 response received'; | ||
break; | ||
case 503: | ||
// set error message | ||
msg.error.message = '503 (server is overloaded) response received'; | ||
break; | ||
default: | ||
// set error message | ||
msg.error.message = msg.status + ' response received'; | ||
break; | ||
} | ||
} | ||
// msg.error.description | ||
if(data.hasOwnProperty('errors') && data.errors instanceof Array) { | ||
if(data.errors.length > 0 && data.errors[0].hasOwnProperty('description')) { | ||
msg.error.description = data.errors[0].description; | ||
} | ||
} | ||
// msg.error.trackingId | ||
if(data.hasOwnProperty('trackingId')) { | ||
msg.error.trackingId = data.trackingId; | ||
} | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('error'); | ||
} | ||
} | ||
// else error.data does not exist | ||
// else, response.data does not exist... | ||
else { | ||
// set error message | ||
msg.error.message = 'unknown error'; | ||
// show warning | ||
node.warn('response has no content'); | ||
// show warning in debug window | ||
node.warn(msg.error.message); | ||
// set node status | ||
setNodeStatus('error'); | ||
setNodeStatus('warning'); | ||
} | ||
} | ||
// set msg.topic | ||
msg.topic = node.resource + '.' + node.method; | ||
else { | ||
node.warn('response invalid or not received'); | ||
// send message | ||
node.send(msg); | ||
// set node status | ||
setNodeStatus('warning'); | ||
} | ||
} | ||
function processError(err) { | ||
if(err && typeof err === 'object' && err.hasOwnProperty('message')) { | ||
// show error | ||
node.error(err.message); | ||
} else { | ||
node.error('unknown error'); | ||
} | ||
// set node status | ||
setNodeStatus('error'); | ||
} | ||
// start node if profileConfig is defined | ||
if(node.profileConfig) { | ||
// set default status | ||
setNodeStatus(); | ||
// create client | ||
createClient(node.apiUrl, function(err) { | ||
// if error creating client | ||
if(err) { | ||
// show warning in debug window | ||
// show warning | ||
node.warn('invalid input'); | ||
@@ -465,5 +308,8 @@ | ||
return; | ||
} else { | ||
} | ||
// input event | ||
// else, client created | ||
else { | ||
// create input event | ||
node.on('input', function(msg) { | ||
@@ -484,3 +330,3 @@ | ||
if(params instanceof Array) { | ||
// show warning in debug window | ||
// show warning | ||
node.warn('invalid input'); | ||
@@ -495,3 +341,3 @@ | ||
catch(err) { | ||
// show warning in debug window | ||
// show warning | ||
node.warn(err.message); | ||
@@ -513,3 +359,3 @@ | ||
else { | ||
// show warning in debug window | ||
// show warning | ||
node.warn('invalid input'); | ||
@@ -524,3 +370,2 @@ | ||
// Send Spark API Request | ||
reqPing(); | ||
sendRequest(msg, node.resource, node.method, params, opts, node.profileConfig.credentials.token, processResponse, processError); | ||
@@ -527,0 +372,0 @@ }); |
@@ -10,76 +10,46 @@ module.exports = function(RED) { | ||
node.parser = n.parser; | ||
node.output = n.output.toLowerCase(); | ||
node.topic = n.topic; | ||
node.parsers = n.parsers; | ||
function processPayload(pl, k) { | ||
// determine topic | ||
var topic = typeof node.topic === 'string' && node.topic.length > 0 ? node.topic : k; | ||
// return value | ||
function getPayloadKeyValue(payload, key) { | ||
// validate payload is object | ||
if(typeof payload === 'object') { | ||
// value | ||
var value = ''; | ||
var value = ''; | ||
// validate payload is object | ||
if(typeof pl === 'object') { | ||
// if webhook | ||
if(pl.hasOwnProperty('data')) { | ||
if(pl.data.hasOwnProperty(k)) { | ||
value = pl.data[k]; | ||
} else { | ||
return null; | ||
} | ||
// search msg.payload.data | ||
if(payload.hasOwnProperty('data') && payload.data.hasOwnProperty(key)) { | ||
value = payload.data[key]; | ||
} | ||
// else api | ||
else { | ||
if(pl.hasOwnProperty(k)) { | ||
value = pl[k]; | ||
} else { | ||
return null; | ||
} | ||
// search msg.payload | ||
else if(payload.hasOwnProperty(key)) { | ||
value = payload[key]; | ||
} | ||
// format output | ||
// if output is original | ||
if(node.output === 'original') { | ||
if(value != '') { | ||
return value; | ||
} | ||
// else, if output is object | ||
else if(node.output === 'object') { | ||
return { [topic]: value }; | ||
} | ||
// else, unrecognized | ||
else { | ||
return null; | ||
} | ||
} | ||
// else, not an object | ||
else { | ||
return null; | ||
} | ||
} | ||
function processPayloadArray(plArr, k) { | ||
// determine topic | ||
var topic = typeof node.topic === 'string' && node.topic.length > 0 ? node.topic : k; | ||
function processMsg(msg, parsers) { | ||
var newMsg = _.cloneDeep(msg); | ||
var newPayload = {}; | ||
var collection = _.map(plArr, function(pl) { | ||
// define payload | ||
var payload = processPayload(pl, k); | ||
if(payload) { | ||
return { "payload": payload, "topic": topic }; | ||
} | ||
}); | ||
if(parsers && parsers instanceof Array && parsers.length > 0) { | ||
_.forEach(parsers, function(parser) { | ||
if(parser.hasOwnProperty('key') && parser.key !== '') { | ||
if(!parser.hasOwnProperty('as') || (parser.hasOwnProperty('as') && parser.as === '')) { | ||
parser.as = parser.key; | ||
} | ||
newPayload[parser.as] = getPayloadKeyValue(newMsg.payload, parser.key); | ||
} | ||
}); | ||
} | ||
collection = _.compact(collection); | ||
if(newPayload !== {}) { | ||
newMsg.payload = newPayload ; | ||
if(collection && collection.length > 0) { | ||
return collection; | ||
} else { | ||
return null; | ||
return newMsg; | ||
} | ||
@@ -90,38 +60,18 @@ } | ||
node.on('input', function(msg) { | ||
// determine topic | ||
var topic = typeof node.topic === 'string' && node.topic.length > 0 ? node.topic : node.parser; | ||
var msgParser = {}; | ||
// if input is valid | ||
if(typeof node.parser === 'string' && msg && typeof msg === 'object' && msg.hasOwnProperty('payload')) { | ||
var payload = _.clone(msg.payload); | ||
if(msg && typeof msg === 'object' && msg.hasOwnProperty('payload')) { | ||
// if payload is array | ||
if(payload instanceof Array) { | ||
if(payload.length > 0) { | ||
var newArray = processPayloadArray(payload, node.parser); | ||
if(newArray && newArray.length > 0) { | ||
msgParser = newArray; | ||
} else { | ||
msgParser = null; | ||
node.warn('unmatched parser'); | ||
} | ||
} | ||
} | ||
// create new message and preserve properties that this node is not manipulating | ||
var newMsg = _.cloneDeep(msg); | ||
// else, payload is not instance of array | ||
else { | ||
var newPayload = processPayload(payload, node.parser); | ||
if(newPayload) { | ||
msgParser.payload = newPayload; | ||
msgParser.topic = topic; | ||
} else { | ||
msgParser = null; | ||
node.warn('unmatched parser'); | ||
// if payload is object | ||
if(typeof newMsg.payload === 'object') { | ||
newMsg = processMsg(newMsg, node.parsers); | ||
// if parser returned value | ||
if(newMsg) { | ||
node.send(newMsg); | ||
} | ||
} | ||
// send msgs | ||
node.send([msgParser, msg]); | ||
} | ||
@@ -132,5 +82,2 @@ | ||
node.warn('invalid input'); | ||
// send msgs | ||
node.send([null, msg]); | ||
} | ||
@@ -137,0 +84,0 @@ }); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1085279
25
18464
198