|
@@ -0,0 +1,124 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * @node merge
|
|
|
|
|
+ * @name Merge
|
|
|
|
|
+ * @category flow-control
|
|
|
|
|
+ * @version 1.0.0
|
|
|
|
|
+ * @description Join two branches by appending, combining on a key, or taking whichever arrived
|
|
|
|
|
+ * @icon git-merge
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+const inputs = [
|
|
|
|
|
+ { name: 'input1', displayName: 'Input 1', type: 'any', required: false },
|
|
|
|
|
+ { name: 'input2', displayName: 'Input 2', type: 'any', required: false }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const configSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ mode: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Mode',
|
|
|
|
|
+ description: 'append joins two arrays, combine-by-key matches items on a field, choose-first takes whichever branch produced anything',
|
|
|
|
|
+ enum: ['append', 'combine-by-key', 'choose-first'],
|
|
|
|
|
+ default: 'append'
|
|
|
|
|
+ },
|
|
|
|
|
+ key: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Key Field',
|
|
|
|
|
+ description: 'Field to match on, for combine-by-key'
|
|
|
|
|
+ },
|
|
|
|
|
+ path1: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Path In Input 1',
|
|
|
|
|
+ description: 'Dotted path to the array inside input 1. Leave empty to use the input as it arrives'
|
|
|
|
|
+ },
|
|
|
|
|
+ path2: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Path In Input 2',
|
|
|
|
|
+ description: 'Dotted path to the array inside input 2. Leave empty to use the input as it arrives'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const inputSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ input1: { type: 'any' },
|
|
|
|
|
+ input2: { type: 'any' }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const outputSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ merged: { type: 'any', description: 'The joined result' },
|
|
|
|
|
+ count: { type: 'number', description: 'Number of items in the result' }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+function asArray(value) {
|
|
|
|
|
+ if (value === undefined || value === null) return [];
|
|
|
|
|
+ return Array.isArray(value) ? value : [value];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function selectInput(value, path) {
|
|
|
|
|
+ if (!path) {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ return smartbotic.utils.getFieldValue(value, path);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function execute(config, input, context) {
|
|
|
|
|
+ const mode = config.mode || 'append';
|
|
|
|
|
+ const first = selectInput(input ? input.input1 : undefined, config.path1);
|
|
|
|
|
+ const second = selectInput(input ? input.input2 : undefined, config.path2);
|
|
|
|
|
+
|
|
|
|
|
+ if (mode === 'choose-first') {
|
|
|
|
|
+ const chosen = first !== undefined && first !== null ? first : second;
|
|
|
|
|
+ smartbotic.log.info('Merge: choose-first took ' + (first !== undefined && first !== null ? 'input1' : 'input2'));
|
|
|
|
|
+ return {
|
|
|
|
|
+ merged: chosen === undefined ? null : chosen,
|
|
|
|
|
+ count: Array.isArray(chosen) ? chosen.length : (chosen === undefined || chosen === null ? 0 : 1)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (mode === 'combine-by-key') {
|
|
|
|
|
+ const key = config.key;
|
|
|
|
|
+ if (!key) {
|
|
|
|
|
+ throw new Error('Merge: combine-by-key needs a key field');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const byKey = {};
|
|
|
|
|
+ const order = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (const item of asArray(first)) {
|
|
|
|
|
+ if (item === null || typeof item !== 'object') continue;
|
|
|
|
|
+ const id = String(item[key]);
|
|
|
|
|
+ byKey[id] = JSON.parse(JSON.stringify(item));
|
|
|
|
|
+ order.push(id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (const item of asArray(second)) {
|
|
|
|
|
+ if (item === null || typeof item !== 'object') continue;
|
|
|
|
|
+ const id = String(item[key]);
|
|
|
|
|
+ if (byKey[id] === undefined) {
|
|
|
|
|
+ byKey[id] = JSON.parse(JSON.stringify(item));
|
|
|
|
|
+ order.push(id);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ for (const field of Object.keys(item)) {
|
|
|
|
|
+ byKey[id][field] = item[field];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const combined = order.map(function (id) { return byKey[id]; });
|
|
|
|
|
+ smartbotic.log.info('Merge: combined ' + combined.length + ' items on "' + key + '"');
|
|
|
|
|
+ return { merged: combined, count: combined.length };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const appended = asArray(first).concat(asArray(second));
|
|
|
|
|
+ smartbotic.log.info('Merge: appended to ' + appended.length + ' items');
|
|
|
|
|
+ return { merged: appended, count: appended.length };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = { configSchema, inputSchema, outputSchema, inputs, execute };
|