Ver Fonte

feat: Template and JSON nodes

fszontagh há 1 mês atrás
pai
commit
034b71f02e
3 ficheiros alterados com 197 adições e 0 exclusões
  1. 107 0
      nodes/core/json.js
  2. 61 0
      nodes/core/template.js
  3. 29 0
      tests/nodes/template-json.json

+ 107 - 0
nodes/core/json.js

@@ -0,0 +1,107 @@
+/**
+ * @node json
+ * @name JSON
+ * @category data
+ * @version 1.0.0
+ * @description Parse a JSON string, stringify an object, or pull one value out by path
+ * @icon braces
+ */
+
+const configSchema = {
+    type: 'object',
+    properties: {
+        operation: {
+            type: 'string',
+            title: 'Operation',
+            enum: ['parse', 'stringify', 'extract'],
+            default: 'parse'
+        },
+        inputField: {
+            type: 'string',
+            title: 'Input Field',
+            description: 'Path to the value to work on',
+            default: 'data'
+        },
+        path: {
+            type: 'string',
+            title: 'Path',
+            description: 'Dotted path to pull out, for the extract operation'
+        },
+        outputField: {
+            type: 'string',
+            title: 'Output Field',
+            default: 'value'
+        },
+        pretty: {
+            type: 'boolean',
+            title: 'Pretty Print',
+            description: 'Indent the output of stringify',
+            default: false
+        },
+        onError: {
+            type: 'string',
+            title: 'On Error',
+            description: 'throw fails the workflow, null returns an empty value instead',
+            enum: ['throw', 'null'],
+            default: 'throw'
+        }
+    }
+};
+
+const inputSchema = {
+    type: 'object',
+    properties: {
+        data: { type: 'any' }
+    }
+};
+
+const outputSchema = {
+    type: 'object',
+    properties: {
+        value: { type: 'any', description: 'The parsed, stringified or extracted value' }
+    }
+};
+
+async function execute(config, input, context) {
+    const operation = config.operation || 'parse';
+    const outputField = config.outputField || 'value';
+    const onError = config.onError || 'throw';
+
+    const source = smartbotic.utils.getFieldValue(input, config.inputField || 'data');
+    const result = {};
+
+    if (operation === 'stringify') {
+        result[outputField] = config.pretty
+            ? JSON.stringify(source, null, 2)
+            : JSON.stringify(source);
+        return result;
+    }
+
+    if (operation === 'extract') {
+        result[outputField] = smartbotic.utils.getFieldValue(source, config.path);
+        return result;
+    }
+
+    if (typeof source !== 'string') {
+        if (onError === 'null') {
+            result[outputField] = null;
+            return result;
+        }
+        throw new Error('JSON parse: the value at "' + (config.inputField || 'data') + '" is not a string');
+    }
+
+    try {
+        result[outputField] = JSON.parse(source);
+    } catch (e) {
+        if (onError === 'null') {
+            smartbotic.log.warn('JSON parse failed, returning null: ' + e.message);
+            result[outputField] = null;
+            return result;
+        }
+        throw new Error('JSON parse failed: ' + e.message);
+    }
+
+    return result;
+}
+
+module.exports = { configSchema, inputSchema, outputSchema, execute };

+ 61 - 0
nodes/core/template.js

@@ -0,0 +1,61 @@
+/**
+ * @node template
+ * @name Template
+ * @category data
+ * @version 1.0.0
+ * @description Render text from a template, for email bodies and chat messages
+ * @icon file-text
+ */
+
+const configSchema = {
+    type: 'object',
+    properties: {
+        template: {
+            type: 'string',
+            title: 'Template',
+            description: 'Text with {{path.to.field}} placeholders, resolved against the input',
+            format: 'textarea',
+            default: ''
+        },
+        outputField: {
+            type: 'string',
+            title: 'Output Field',
+            default: 'text'
+        }
+    },
+    required: ['template']
+};
+
+const inputSchema = {
+    type: 'object',
+    properties: {
+        data: { type: 'any' }
+    }
+};
+
+const outputSchema = {
+    type: 'object',
+    properties: {
+        text: { type: 'string', description: 'The rendered text' }
+    }
+};
+
+async function execute(config, input, context) {
+    const template = config.template;
+    const outputField = config.outputField || 'text';
+
+    if (typeof template !== 'string') {
+        throw new Error('Template: no template text was given');
+    }
+
+    const source = input && input.data !== undefined ? input.data : input;
+    const rendered = smartbotic.utils.interpolate(template, source);
+
+    smartbotic.log.info('Template: rendered ' + rendered.length + ' characters');
+
+    const result = {};
+    result[outputField] = rendered;
+    return result;
+}
+
+module.exports = { configSchema, inputSchema, outputSchema, execute };

+ 29 - 0
tests/nodes/template-json.json

@@ -0,0 +1,29 @@
+{
+  "name": "verify-template-json",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "Fixture", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "return { name: 'Ada', payload: '{\"nested\":{\"count\":7}}' };"}},
+    {"id": "n3", "name": "Parse", "type": "json", "position": {"x": -100, "y": 200},
+     "config": {"operation": "parse", "inputField": "data.result.payload", "outputField": "value"}},
+    {"id": "n4", "name": "Extract", "type": "json", "position": {"x": -100, "y": 300},
+     "config": {"operation": "extract", "inputField": "data.value", "path": "nested.count", "outputField": "count"}},
+    {"id": "n5", "name": "Bad", "type": "json", "position": {"x": 100, "y": 200},
+     "config": {"operation": "parse", "inputField": "data.result.name", "outputField": "value", "onError": "null"}},
+    {"id": "n6", "name": "Render", "type": "template", "position": {"x": 0, "y": 400},
+     "config": {"template": "Hello {{data.result.name}}", "outputField": "text"}}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n3", "targetInput": "data"},
+    {"sourceNodeId": "n3", "sourceOutput": "main", "targetNodeId": "n4", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n5", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n6", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "completed", "output": {"value": {"nested": {"count": 7}}}},
+    "n4": {"status": "completed", "output": {"count": 7}},
+    "n5": {"status": "completed", "output": {"value": null}},
+    "n6": {"status": "completed", "output": {"text": "Hello Ada"}}
+  }
+}