ソースを参照

feat: a Set Fields node, so shaping data stops needing a Code node

fszontagh 1 ヶ月 前
親
コミット
f04eae933d
2 ファイル変更、154 行追加、0 行削除
  1. 124 0
      nodes/core/set-fields.js
  2. 30 0
      tests/nodes/set-fields.json

+ 124 - 0
nodes/core/set-fields.js

@@ -0,0 +1,124 @@
+/**
+ * @node set-fields
+ * @name Set / Edit Fields
+ * @category data
+ * @version 1.0.0
+ * @description Build an output object from values and expressions, keeping or dropping everything else
+ * @icon pencil
+ */
+
+const configSchema = {
+    type: 'object',
+    properties: {
+        mode: {
+            type: 'string',
+            title: 'Mode',
+            description: 'Keep everything from the input and add these fields, or output only the fields set here',
+            enum: ['keep-all', 'only-set'],
+            default: 'keep-all'
+        },
+        fields: {
+            type: 'array',
+            title: 'Fields',
+            items: {
+                type: 'object',
+                properties: {
+                    name: {
+                        type: 'string',
+                        title: 'Name',
+                        description: 'Field name, or a dotted path such as user.address.city'
+                    },
+                    value: {
+                        type: 'string',
+                        title: 'Value',
+                        description: 'A literal value, or an expression such as {{data.result.total}}'
+                    }
+                }
+            }
+        },
+        dropFields: {
+            type: 'array',
+            title: 'Fields to Remove',
+            description: 'Dotted paths to delete from the output',
+            items: { type: 'string' }
+        }
+    }
+};
+
+const inputSchema = {
+    type: 'object',
+    properties: {
+        data: { type: 'any' }
+    }
+};
+
+const outputSchema = {
+    type: 'object',
+    properties: {
+        data: { type: 'any', description: 'The rebuilt object' }
+    }
+};
+
+function setByPath(target, path, value) {
+    const keys = String(path).split('.');
+    let cursor = target;
+    for (let i = 0; i < keys.length - 1; i++) {
+        const key = keys[i];
+        if (cursor[key] === null || typeof cursor[key] !== 'object') {
+            cursor[key] = {};
+        }
+        cursor = cursor[key];
+    }
+    cursor[keys[keys.length - 1]] = value;
+}
+
+function deleteByPath(target, path) {
+    const keys = String(path).split('.');
+    let cursor = target;
+    for (let i = 0; i < keys.length - 1; i++) {
+        const key = keys[i];
+        if (cursor === null || typeof cursor !== 'object' || !(key in cursor)) {
+            return;
+        }
+        cursor = cursor[key];
+    }
+    if (cursor !== null && typeof cursor === 'object') {
+        delete cursor[keys[keys.length - 1]];
+    }
+}
+
+async function execute(config, input, context) {
+    const mode = config.mode || 'keep-all';
+    const fields = Array.isArray(config.fields) ? config.fields : [];
+    const dropFields = Array.isArray(config.dropFields) ? config.dropFields : [];
+
+    const source = input && input.data !== undefined ? input.data : input;
+
+    let output;
+    if (mode === 'only-set') {
+        output = {};
+    } else {
+        output = source !== null && typeof source === 'object' && !Array.isArray(source)
+            ? JSON.parse(JSON.stringify(source))
+            : {};
+    }
+
+    for (const field of fields) {
+        if (!field || !field.name) {
+            continue;
+        }
+        setByPath(output, field.name, field.value);
+    }
+
+    for (const path of dropFields) {
+        if (path) {
+            deleteByPath(output, path);
+        }
+    }
+
+    smartbotic.log.info('Set Fields: ' + fields.length + ' set, ' + dropFields.length + ' removed, mode ' + mode);
+
+    return output;
+}
+
+module.exports = { configSchema, inputSchema, outputSchema, execute };

+ 30 - 0
tests/nodes/set-fields.json

@@ -0,0 +1,30 @@
+{
+  "name": "verify-set-fields",
+  "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 { total: 42, junk: 'drop me', keep: 'kept' };"}},
+    {"id": "n3", "name": "Set", "type": "set-fields", "position": {"x": 0, "y": 200},
+     "config": {
+       "mode": "keep-all",
+       "fields": [
+         {"name": "label", "value": "hello"},
+         {"name": "amount", "value": "{{data.result.total}}"},
+         {"name": "user.city", "value": "Budapest"}
+       ],
+       "dropFields": ["result.junk"]
+     }}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "main", "targetNodeId": "n3", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "completed", "output": {
+      "label": "hello",
+      "amount": 42,
+      "user": {"city": "Budapest"},
+      "result": {"keep": "kept"}
+    }}
+  }
+}