|
@@ -0,0 +1,125 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * @node split-out
|
|
|
|
|
+ * @name Split Out
|
|
|
|
|
+ * @category data
|
|
|
|
|
+ * @version 1.0.0
|
|
|
|
|
+ * @description Turn an array field into a list of items, optionally carrying the parent fields along
|
|
|
|
|
+ * @icon list
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+const configSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ field: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Array Field',
|
|
|
|
|
+ description: 'Path to the array to split out, such as data.result.orders'
|
|
|
|
|
+ },
|
|
|
|
|
+ include: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Carry Parent Fields',
|
|
|
|
|
+ description: 'none keeps only the item, all-fields merges the parent object into each item, selected merges only the listed paths',
|
|
|
|
|
+ enum: ['none', 'all-fields', 'selected'],
|
|
|
|
|
+ default: 'none'
|
|
|
|
|
+ },
|
|
|
|
|
+ includeFields: {
|
|
|
|
|
+ type: 'array',
|
|
|
|
|
+ title: 'Fields to Carry',
|
|
|
|
|
+ description: 'Dotted paths from the parent, used when Carry is set to selected',
|
|
|
|
|
+ items: { type: 'string' }
|
|
|
|
|
+ },
|
|
|
|
|
+ itemField: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Wrap Scalars As',
|
|
|
|
|
+ description: 'When an item is not an object, put it under this field name',
|
|
|
|
|
+ default: 'value'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const inputSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ data: { type: 'any' }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const outputSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ items: { type: 'array', description: 'The split-out items' },
|
|
|
|
|
+ count: { type: 'number' }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+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];
|
|
|
|
|
+ const existing = cursor[key];
|
|
|
|
|
+ if (existing === undefined) {
|
|
|
|
|
+ cursor[key] = {};
|
|
|
|
|
+ } else if (existing === null || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
|
|
|
+ const held = Array.isArray(existing) ? 'an array' : existing === null ? 'null' : 'a ' + typeof existing;
|
|
|
|
|
+ throw new Error('Cannot set "' + path + '": "' + keys.slice(0, i + 1).join('.') +
|
|
|
|
|
+ '" already holds ' + held + ', which would be overwritten');
|
|
|
|
|
+ }
|
|
|
|
|
+ cursor = cursor[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ cursor[keys[keys.length - 1]] = value;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function execute(config, input, context) {
|
|
|
|
|
+ const field = config.field;
|
|
|
|
|
+ const include = config.include || 'none';
|
|
|
|
|
+ const includeFields = Array.isArray(config.includeFields) ? config.includeFields : [];
|
|
|
|
|
+ const itemField = config.itemField || 'value';
|
|
|
|
|
+
|
|
|
|
|
+ const source = input && input.data !== undefined ? input.data : input;
|
|
|
|
|
+
|
|
|
|
|
+ let list;
|
|
|
|
|
+ if (Array.isArray(field)) {
|
|
|
|
|
+ list = field;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ list = smartbotic.utils.getFieldValue(input, field);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!Array.isArray(list)) {
|
|
|
|
|
+ throw new Error('Split Out: the value at "' + field + '" is not an array');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const parent = source !== null && typeof source === 'object' && !Array.isArray(source) ? source : {};
|
|
|
|
|
+
|
|
|
|
|
+ const items = list.map(function (entry) {
|
|
|
|
|
+ let item;
|
|
|
|
|
+ if (entry !== null && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
|
|
|
+ item = JSON.parse(JSON.stringify(entry));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ item = {};
|
|
|
|
|
+ item[itemField] = entry;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (include === 'all-fields') {
|
|
|
|
|
+ for (const key of Object.keys(parent)) {
|
|
|
|
|
+ if (!(key in item)) {
|
|
|
|
|
+ item[key] = parent[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (include === 'selected') {
|
|
|
|
|
+ for (const path of includeFields) {
|
|
|
|
|
+ if (path) {
|
|
|
|
|
+ setByPath(item, path, smartbotic.utils.getFieldValue(parent, path));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return item;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ smartbotic.log.info('Split Out: ' + items.length + ' items from "' + field + '"');
|
|
|
|
|
+
|
|
|
|
|
+ return { items: items, count: items.length };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = { configSchema, inputSchema, outputSchema, execute };
|