mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(KoBoToolbox Node): Add KoBoToolbox Regular and Trigger Node (#2765)
* First version * Added hooks * Added Credentials test * Add support for downloading attachments * Slight restructure of downloaded binaries * Added Trigger node * Some linting * Reverting package-lock changes * Minor GeoJSON parsing fixes * KoboToolbox: improve GeoJSON format * Kobo: Support for get/set validation status * Remove some logs * [kobo] Fix default attachment options * Proper debug logging * Support for hook log status filter * Kobo: Review fixes * [kobo]: Add Get All Forms + lookup Form ID * [kobo] Lookup Form ID in Trigger node * [kobo] Update branded spelling * [kobo] Support pagination * ⚡ fix linting issue * ⚡ Improvements to #2510 * ⚡ Download files using n8n helper * ⚡ Improvements * ⚡ Improvements * 🐛 Fix filenames * ⚡ Fix some issues Co-authored-by: Yann Jouanique <yann.jouanique@oneacrefund.org> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
8a88f948f2
commit
1a7f0a4246
416
package-lock.json
generated
416
package-lock.json
generated
|
@ -13837,6 +13837,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
|
||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||
|
@ -13872,6 +13881,21 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
|
@ -13934,6 +13958,58 @@
|
|||
"worker-rpc": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
|
||||
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
|
@ -13968,6 +14044,12 @@
|
|||
"slash": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"optional": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
|
@ -13996,6 +14078,16 @@
|
|||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
|
@ -14031,6 +14123,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
|
@ -14041,6 +14144,15 @@
|
|||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
||||
|
@ -14134,6 +14246,12 @@
|
|||
"requires": {
|
||||
"tslib": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -23641,124 +23759,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
|
||||
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"optional": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
|
@ -29714,6 +29714,23 @@
|
|||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "5.7.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
|
||||
"integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg=="
|
||||
},
|
||||
"escodegen": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||
"requires": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"optionator": "^0.8.1",
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"jest-util": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz",
|
||||
|
@ -29733,6 +29750,44 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
|
||||
"integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"acorn": "^5.5.3",
|
||||
"acorn-globals": "^4.1.0",
|
||||
"array-equal": "^1.0.0",
|
||||
"cssom": ">= 0.3.2 < 0.4.0",
|
||||
"cssstyle": "^1.0.0",
|
||||
"data-urls": "^1.0.0",
|
||||
"domexception": "^1.0.1",
|
||||
"escodegen": "^1.9.1",
|
||||
"html-encoding-sniffer": "^1.0.2",
|
||||
"left-pad": "^1.3.0",
|
||||
"nwsapi": "^2.0.7",
|
||||
"parse5": "4.0.0",
|
||||
"pn": "^1.1.0",
|
||||
"request": "^2.87.0",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"sax": "^1.2.4",
|
||||
"symbol-tree": "^3.2.2",
|
||||
"tough-cookie": "^2.3.4",
|
||||
"w3c-hr-time": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2",
|
||||
"whatwg-encoding": "^1.0.3",
|
||||
"whatwg-mimetype": "^2.1.0",
|
||||
"whatwg-url": "^6.4.1",
|
||||
"ws": "^5.2.0",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"parse5": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
|
||||
"integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
|
||||
},
|
||||
"slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
|
@ -29742,6 +29797,37 @@
|
|||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"tr46": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
|
||||
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
|
||||
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz",
|
||||
"integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -31837,100 +31923,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
|
||||
"integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"acorn": "^5.5.3",
|
||||
"acorn-globals": "^4.1.0",
|
||||
"array-equal": "^1.0.0",
|
||||
"cssom": ">= 0.3.2 < 0.4.0",
|
||||
"cssstyle": "^1.0.0",
|
||||
"data-urls": "^1.0.0",
|
||||
"domexception": "^1.0.1",
|
||||
"escodegen": "^1.9.1",
|
||||
"html-encoding-sniffer": "^1.0.2",
|
||||
"left-pad": "^1.3.0",
|
||||
"nwsapi": "^2.0.7",
|
||||
"parse5": "4.0.0",
|
||||
"pn": "^1.1.0",
|
||||
"request": "^2.87.0",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"sax": "^1.2.4",
|
||||
"symbol-tree": "^3.2.2",
|
||||
"tough-cookie": "^2.3.4",
|
||||
"w3c-hr-time": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2",
|
||||
"whatwg-encoding": "^1.0.3",
|
||||
"whatwg-mimetype": "^2.1.0",
|
||||
"whatwg-url": "^6.4.1",
|
||||
"ws": "^5.2.0",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "5.7.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
|
||||
"integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg=="
|
||||
},
|
||||
"escodegen": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||
"requires": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"optionator": "^0.8.1",
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"parse5": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
|
||||
"integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"optional": true
|
||||
},
|
||||
"tr46": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
|
||||
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
|
||||
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^1.0.1",
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz",
|
||||
"integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class KoBoToolboxApi implements ICredentialType {
|
||||
name = 'koBoToolboxApi';
|
||||
displayName = 'KoBoToolbox API Token';
|
||||
// See https://support.kobotoolbox.org/api.html
|
||||
documentationUrl = 'koBoToolbox';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API root URL',
|
||||
name: 'URL',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: 'https://kf.kobotoolbox.org/',
|
||||
},
|
||||
{
|
||||
displayName: 'API Token',
|
||||
name: 'token',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
hint: 'You can get your API token at https://[api-root]/token/?format=json (for a logged in user)',
|
||||
},
|
||||
];
|
||||
}
|
202
packages/nodes-base/nodes/KoBoToolbox/FormDescription.ts
Normal file
202
packages/nodes-base/nodes/KoBoToolbox/FormDescription.ts
Normal file
|
@ -0,0 +1,202 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const formOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a form',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all forms',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
},
|
||||
];
|
||||
|
||||
export const formFields: INodeProperties[] = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form ID',
|
||||
name: 'formId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return all results',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
required: false,
|
||||
typeOptions: {
|
||||
maxValue: 3000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 1000,
|
||||
description: 'The number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
placeholder: 'Add Option',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'sort',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'Add Sort',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'value',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Descending',
|
||||
name: 'descending',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Sort by descending order',
|
||||
},
|
||||
{
|
||||
displayName: 'Order By',
|
||||
name: 'ordering',
|
||||
type: 'options',
|
||||
required: false,
|
||||
default: 'date_modified',
|
||||
options: [
|
||||
{
|
||||
name: 'Asset Type',
|
||||
value: 'asset_type',
|
||||
},
|
||||
{
|
||||
name: 'Date Modified',
|
||||
value: 'date_modified',
|
||||
},
|
||||
{
|
||||
name: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
name: 'Owner Username',
|
||||
value: 'owner__username',
|
||||
},
|
||||
{
|
||||
name: 'Subscribers Count',
|
||||
value: 'subscribers_count',
|
||||
},
|
||||
],
|
||||
description: 'Field to order by',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'filter',
|
||||
type: 'string',
|
||||
default: 'asset_type:survey',
|
||||
required: false,
|
||||
description: 'A text search query based on form data - e.g. "owner__username:meg AND name__icontains:quixotic" - see <a href="https://github.com/kobotoolbox/kpi#searching" target="_blank">docs</a> for more details',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
238
packages/nodes-base/nodes/KoBoToolbox/GenericFunctions.ts
Normal file
238
packages/nodes-base/nodes/KoBoToolbox/GenericFunctions.ts
Normal file
|
@ -0,0 +1,238 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
IHttpRequestOptions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export async function koBoToolboxApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = await this.getCredentials('koBoToolboxApi') as IDataObject;
|
||||
|
||||
// Set up pagination / scrolling
|
||||
const returnAll = !!option.returnAll;
|
||||
if (returnAll) {
|
||||
// Override manual pagination options
|
||||
_.set(option, 'qs.limit', 3000);
|
||||
// Don't pass this custom param to helpers.httpRequest
|
||||
delete option.returnAll;
|
||||
}
|
||||
|
||||
const options: IHttpRequestOptions = {
|
||||
url: '',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Token ${credentials.token}`,
|
||||
},
|
||||
json: true,
|
||||
};
|
||||
if (Object.keys(option)) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
if (options.url && !/^http(s)?:/.test(options.url)) {
|
||||
options.url = credentials.URL + options.url;
|
||||
}
|
||||
|
||||
let results = null;
|
||||
let keepLooking = true;
|
||||
while (keepLooking) {
|
||||
const response = await this.helpers.httpRequest(options);
|
||||
// Append or set results
|
||||
results = response.results ? _.concat(results || [], response.results) : response;
|
||||
if (returnAll && response.next) {
|
||||
options.url = response.next;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
keepLooking = false;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function parseGeoPoint(geoPoint: string): null | number[] {
|
||||
// Check if it looks like a "lat lon z precision" flat string e.g. "-1.931161 30.079811 0 0" (lat, lon, elevation, precision)
|
||||
const coordinates = _.split(geoPoint, ' ');
|
||||
if (coordinates.length >= 2 && _.every(coordinates, coord => coord && /^-?\d+(?:\.\d+)?$/.test(_.toString(coord)))) {
|
||||
// NOTE: GeoJSON uses lon, lat, while most common systems use lat, lon order!
|
||||
return _.concat([
|
||||
_.toNumber(coordinates[1]),
|
||||
_.toNumber(coordinates[0]),
|
||||
], _.toNumber(coordinates[2]) ? _.toNumber(coordinates[2]) : []);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function parseStringList(value: string): string[] {
|
||||
return _.split(_.toString(value), /[\s,]+/);
|
||||
}
|
||||
|
||||
const matchWildcard = (value: string, pattern: string): boolean => {
|
||||
const regex = new RegExp(`^${_.escapeRegExp(pattern).replace('\\*', '.*')}$`);
|
||||
return regex.test(value);
|
||||
};
|
||||
|
||||
const formatValue = (value: any, format: string): any => { //tslint:disable-line:no-any
|
||||
if (_.isString(value)) {
|
||||
// Sanitize value
|
||||
value = _.toString(value);
|
||||
|
||||
// Parse geoPoints
|
||||
const geoPoint = parseGeoPoint(value);
|
||||
if (geoPoint) {
|
||||
return {
|
||||
type: 'Point',
|
||||
coordinates: geoPoint,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if it's a closed polygon geo-shape: -1.954117 30.085159 0 0;-1.955005 30.084622 0 0;-1.956057 30.08506 0 0;-1.956393 30.086229 0 0;-1.955853 30.087143 0 0;-1.954609 30.08725 0 0;-1.953966 30.086735 0 0;-1.953805 30.085897 0 0;-1.954117 30.085159 0 0
|
||||
const points = value.split(';');
|
||||
if (points.length >= 2 && /^[-\d\.\s;]+$/.test(value)) {
|
||||
// Using the GeoJSON format as per https://geojson.org/
|
||||
const coordinates = _.compact(points.map(parseGeoPoint));
|
||||
// Only return if all values are properly parsed
|
||||
if (coordinates.length === points.length) {
|
||||
return {
|
||||
type: _.first(points) === _.last(points) ? 'Polygon' : 'LineString', // check if shape is closed or open
|
||||
coordinates,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Parse numbers
|
||||
if ('number' === format) {
|
||||
return _.toNumber(value);
|
||||
}
|
||||
|
||||
// Split multi-select
|
||||
if ('multiSelect' === format) {
|
||||
return _.split(_.toString(value), ' ');
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export function formatSubmission(submission: IDataObject, selectMasks: string[] = [], numberMasks: string[] = []): IDataObject {
|
||||
// Create a shallow copy of the submission
|
||||
const response = {} as IDataObject;
|
||||
|
||||
for (const key of Object.keys(submission)) {
|
||||
let value = _.clone(submission[key]);
|
||||
// Sanitize key names: split by group, trim _
|
||||
const sanitizedKey = key.split('/').map(k => _.trim(k, ' _')).join('.');
|
||||
const leafKey = sanitizedKey.split('.').pop() || '';
|
||||
let format = 'string';
|
||||
if (_.some(numberMasks, mask => matchWildcard(leafKey, mask))) {
|
||||
format = 'number';
|
||||
}
|
||||
if (_.some(selectMasks, mask => matchWildcard(leafKey, mask))) {
|
||||
format = 'multiSelect';
|
||||
}
|
||||
|
||||
value = formatValue(value, format);
|
||||
|
||||
_.set(response, sanitizedKey, value);
|
||||
}
|
||||
|
||||
// Reformat _geolocation
|
||||
if (_.isArray(response.geolocation) && response.geolocation.length === 2 && response.geolocation[0] && response.geolocation[1]) {
|
||||
response.geolocation = {
|
||||
type: 'Point',
|
||||
coordinates: [response.geolocation[1], response.geolocation[0]],
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function downloadAttachments(this: IExecuteFunctions | IWebhookFunctions, submission: IDataObject, options: IDataObject): Promise<INodeExecutionData> {
|
||||
// Initialize return object with the original submission JSON content
|
||||
const binaryItem: INodeExecutionData = {
|
||||
json: {
|
||||
...submission,
|
||||
},
|
||||
binary: {},
|
||||
};
|
||||
|
||||
const credentials = await this.getCredentials('koBoToolboxApi') as IDataObject;
|
||||
|
||||
// Look for attachment links - there can be more than one
|
||||
const attachmentList = (submission['_attachments'] || submission['attachments']) as any[]; // tslint:disable-line:no-any
|
||||
if (attachmentList && attachmentList.length) {
|
||||
for (const [index, attachment] of attachmentList.entries()) {
|
||||
// look for the question name linked to this attachment
|
||||
const filename = attachment.filename;
|
||||
Object.keys(submission).forEach(question => {
|
||||
if (filename.endsWith('/' + _.toString(submission[question]).replace(/\s/g, '_'))) {
|
||||
}
|
||||
});
|
||||
|
||||
// Download attachment
|
||||
// NOTE: this needs to follow redirects (possibly across domains), while keeping Authorization headers
|
||||
// The Axios client will not propagate the Authorization header on redirects (see https://github.com/axios/axios/issues/3607), so we need to follow ourselves...
|
||||
let response = null;
|
||||
const attachmentUrl = attachment[options.version as string] || attachment.download_url as string;
|
||||
let final = false, redir = 0;
|
||||
|
||||
const axiosOptions: IHttpRequestOptions = {
|
||||
url: attachmentUrl,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Token ${credentials.token}`,
|
||||
},
|
||||
ignoreHttpStatusErrors: true,
|
||||
returnFullResponse: true,
|
||||
disableFollowRedirect: true,
|
||||
encoding: 'arraybuffer',
|
||||
};
|
||||
|
||||
while (!final && redir < 5) {
|
||||
response = await this.helpers.httpRequest(axiosOptions);
|
||||
|
||||
if (response && response.headers.location) {
|
||||
// Follow redirect
|
||||
axiosOptions.url = response.headers.location;
|
||||
redir++;
|
||||
} else {
|
||||
final = true;
|
||||
}
|
||||
}
|
||||
|
||||
const dataPropertyAttachmentsPrefixName = options.dataPropertyAttachmentsPrefixName || 'attachment_';
|
||||
const fileName = filename.split('/').pop();
|
||||
|
||||
if (response && response.body) {
|
||||
binaryItem.binary![`${dataPropertyAttachmentsPrefixName}${index}`] = await this.helpers.prepareBinaryData(response.body, fileName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete binaryItem.binary;
|
||||
}
|
||||
|
||||
// Add item to final output - even if there's no attachment retrieved
|
||||
return binaryItem;
|
||||
}
|
||||
|
||||
export async function loadForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const responseData = await koBoToolboxApiRequest.call(this, {
|
||||
url: '/api/v2/assets/',
|
||||
qs: {
|
||||
q: 'asset_type:survey',
|
||||
ordering: 'name',
|
||||
},
|
||||
scroll: true,
|
||||
});
|
||||
|
||||
return responseData?.map((survey: any) => ({ name: survey.name, value: survey.uid })) || []; // tslint:disable-line:no-any
|
||||
}
|
184
packages/nodes-base/nodes/KoBoToolbox/HookDescription.ts
Normal file
184
packages/nodes-base/nodes/KoBoToolbox/HookDescription.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const hookOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a single hook definition',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'List all hooks on a form',
|
||||
},
|
||||
{
|
||||
name: 'Logs',
|
||||
value: 'getLogs',
|
||||
description: 'Get hook logs',
|
||||
},
|
||||
{
|
||||
name: 'Retry All',
|
||||
value: 'retryAll',
|
||||
description: 'Retry all failed attempts for a given hook',
|
||||
},
|
||||
{
|
||||
name: 'Retry One',
|
||||
value: 'retryOne',
|
||||
description: 'Retry a specific hook',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
];
|
||||
|
||||
export const hookFields: INodeProperties[] = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* hook:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form ID',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'loadForms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'retryOne',
|
||||
'retryAll',
|
||||
'getLogs',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)',
|
||||
},
|
||||
{
|
||||
displayName: 'Hook ID',
|
||||
name: 'hookId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'retryOne',
|
||||
'retryAll',
|
||||
'getLogs',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Hook ID (starts with h, e.g. hVehywQ2oXPYGHJHKtqth4)',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* hook:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form ID',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'loadForms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)',
|
||||
},
|
||||
{
|
||||
displayName: 'Hook Log ID',
|
||||
name: 'logId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
operation: [
|
||||
'retryOne',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Hook log ID (starts with hl, e.g. hlSbGKaUKzTVNoWEVMYbLHe)',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
'getLogs',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return all results',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
required: false,
|
||||
typeOptions: {
|
||||
maxValue: 3000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'hook',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
'getLogs',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 1000,
|
||||
description: 'The number of results to return',
|
||||
},
|
||||
];
|
371
packages/nodes-base/nodes/KoBoToolbox/KoBoToolbox.node.ts
Normal file
371
packages/nodes-base/nodes/KoBoToolbox/KoBoToolbox.node.ts
Normal file
|
@ -0,0 +1,371 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
downloadAttachments,
|
||||
formatSubmission,
|
||||
koBoToolboxApiRequest,
|
||||
loadForms,
|
||||
parseStringList,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
formFields,
|
||||
formOperations
|
||||
} from './FormDescription';
|
||||
|
||||
import {
|
||||
submissionFields,
|
||||
submissionOperations,
|
||||
} from './SubmissionDescription';
|
||||
|
||||
import {
|
||||
hookFields,
|
||||
hookOperations,
|
||||
} from './HookDescription';
|
||||
|
||||
export class KoBoToolbox implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'KoBoToolbox',
|
||||
name: 'koBoToolbox',
|
||||
icon: 'file:koBoToolbox.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Work with KoBoToolbox forms and submissions',
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
defaults: {
|
||||
name: 'KoBoToolbox',
|
||||
color: '#64C0FF',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'koBoToolboxApi',
|
||||
required: true,
|
||||
testedBy: 'koBoToolboxApiCredentialTest',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Form',
|
||||
value: 'form',
|
||||
},
|
||||
{
|
||||
name: 'Hook',
|
||||
value: 'hook',
|
||||
},
|
||||
{
|
||||
name: 'Submission',
|
||||
value: 'submission',
|
||||
},
|
||||
],
|
||||
default: 'submission',
|
||||
required: true,
|
||||
},
|
||||
...formOperations,
|
||||
...formFields,
|
||||
...hookOperations,
|
||||
...hookFields,
|
||||
...submissionOperations,
|
||||
...submissionFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async koBoToolboxApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
try {
|
||||
const response = await this.helpers.request({
|
||||
url: `${credentials!.URL}/api/v2/assets/hash`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Token ${credentials!.token}`,
|
||||
},
|
||||
json: true,
|
||||
});
|
||||
|
||||
if (response.hash) {
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: `Credentials are not valid. Response: ${response.detail}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: `Credentials validation failed: ${(err as JsonObject).message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
loadOptions: {
|
||||
loadForms,
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
// tslint:disable-next-line:no-any
|
||||
let responseData: any;
|
||||
// tslint:disable-next-line:no-any
|
||||
let returnData: any[] = [];
|
||||
const binaryItems: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
if (resource === 'form') {
|
||||
// *********************************************************************
|
||||
// Form
|
||||
// *********************************************************************
|
||||
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------
|
||||
// Form: get
|
||||
// ----------------------------------
|
||||
const formId = this.getNodeParameter('formId', i) as string;
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}`,
|
||||
})];
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
// ----------------------------------
|
||||
// Form: getAll
|
||||
// ----------------------------------
|
||||
const formQueryOptions = this.getNodeParameter('options', i) as {
|
||||
sort: {
|
||||
value: {
|
||||
descending: boolean,
|
||||
ordering: string,
|
||||
}
|
||||
}
|
||||
};
|
||||
const formFilterOptions = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
responseData = await koBoToolboxApiRequest.call(this, {
|
||||
url: '/api/v2/assets/',
|
||||
qs: {
|
||||
limit: this.getNodeParameter('limit', i, 1000) as number,
|
||||
...(formFilterOptions.filter && { q: formFilterOptions.filter }),
|
||||
...(formQueryOptions?.sort?.value?.ordering && { ordering: (formQueryOptions?.sort?.value?.descending ? '-' : '') + formQueryOptions?.sort?.value?.ordering }),
|
||||
},
|
||||
scroll: this.getNodeParameter('returnAll', i) as boolean,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (resource === 'submission') {
|
||||
// *********************************************************************
|
||||
// Submissions
|
||||
// *********************************************************************
|
||||
const formId = this.getNodeParameter('formId', i) as string;
|
||||
|
||||
if (operation === 'getAll') {
|
||||
// ----------------------------------
|
||||
// Submissions: getAll
|
||||
// ----------------------------------
|
||||
|
||||
const submissionQueryOptions = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
responseData = await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/data/`,
|
||||
qs: {
|
||||
limit: this.getNodeParameter('limit', i, 1000) as number,
|
||||
...(submissionQueryOptions.query && { query: submissionQueryOptions.query }),
|
||||
//...(submissionQueryOptions.sort && { sort: submissionQueryOptions.sort }),
|
||||
...(submissionQueryOptions.fields && { fields: JSON.stringify(parseStringList(submissionQueryOptions.fields as string)) }),
|
||||
},
|
||||
scroll: this.getNodeParameter('returnAll', i) as boolean,
|
||||
});
|
||||
|
||||
if (submissionQueryOptions.reformat) {
|
||||
responseData = responseData.map((submission: IDataObject) => {
|
||||
return formatSubmission(submission, parseStringList(submissionQueryOptions.selectMask as string), parseStringList(submissionQueryOptions.numberMask as string));
|
||||
});
|
||||
}
|
||||
|
||||
if (submissionQueryOptions.download) {
|
||||
// Download related attachments
|
||||
for (const submission of responseData) {
|
||||
binaryItems.push(await downloadAttachments.call(this, submission, submissionQueryOptions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------
|
||||
// Submissions: get
|
||||
// ----------------------------------
|
||||
const submissionId = this.getNodeParameter('submissionId', i) as string;
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/data/${submissionId}`,
|
||||
qs: {
|
||||
...(options.fields && { fields: JSON.stringify(parseStringList(options.fields as string)) }),
|
||||
},
|
||||
})];
|
||||
|
||||
if (options.reformat) {
|
||||
responseData = responseData.map((submission: IDataObject) => {
|
||||
return formatSubmission(submission, parseStringList(options.selectMask as string), parseStringList(options.numberMask as string));
|
||||
});
|
||||
}
|
||||
|
||||
if (options.download) {
|
||||
// Download related attachments
|
||||
for (const submission of responseData) {
|
||||
binaryItems.push(await downloadAttachments.call(this, submission, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'delete') {
|
||||
// ----------------------------------
|
||||
// Submissions: delete
|
||||
// ----------------------------------
|
||||
const id = this.getNodeParameter('submissionId', i) as string;
|
||||
|
||||
await koBoToolboxApiRequest.call(this, {
|
||||
method: 'DELETE',
|
||||
url: `/api/v2/assets/${formId}/data/${id}`,
|
||||
});
|
||||
|
||||
responseData = [{
|
||||
success: true,
|
||||
}];
|
||||
}
|
||||
|
||||
if (operation === 'getValidation') {
|
||||
// ----------------------------------
|
||||
// Submissions: getValidation
|
||||
// ----------------------------------
|
||||
const submissionId = this.getNodeParameter('submissionId', i) as string;
|
||||
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/data/${submissionId}/validation_status/`,
|
||||
})];
|
||||
}
|
||||
|
||||
if (operation === 'setValidation') {
|
||||
// ----------------------------------
|
||||
// Submissions: setValidation
|
||||
// ----------------------------------
|
||||
const submissionId = this.getNodeParameter('submissionId', i) as string;
|
||||
const status = this.getNodeParameter('validationStatus', i) as string;
|
||||
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
method: 'PATCH',
|
||||
url: `/api/v2/assets/${formId}/data/${submissionId}/validation_status/`,
|
||||
body: {
|
||||
'validation_status.uid': status,
|
||||
},
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'hook') {
|
||||
const formId = this.getNodeParameter('formId', i) as string;
|
||||
// *********************************************************************
|
||||
// Hook
|
||||
// *********************************************************************
|
||||
|
||||
if (operation === 'getAll') {
|
||||
// ----------------------------------
|
||||
// Hook: getAll
|
||||
// ----------------------------------
|
||||
responseData = await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/hooks/`,
|
||||
qs: {
|
||||
limit: this.getNodeParameter('limit', i, 1000) as number,
|
||||
},
|
||||
scroll: this.getNodeParameter('returnAll', i) as boolean,
|
||||
});
|
||||
}
|
||||
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------
|
||||
// Hook: get
|
||||
// ----------------------------------
|
||||
const hookId = this.getNodeParameter('hookId', i) as string;
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/hooks/${hookId}`,
|
||||
})];
|
||||
}
|
||||
|
||||
if (operation === 'retryAll') {
|
||||
// ----------------------------------
|
||||
// Hook: retryAll
|
||||
// ----------------------------------
|
||||
const hookId = this.getNodeParameter('hookId', i) as string;
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
method: 'PATCH',
|
||||
url: `/api/v2/assets/${formId}/hooks/${hookId}/retry/`,
|
||||
})];
|
||||
}
|
||||
|
||||
if (operation === 'getLogs') {
|
||||
// ----------------------------------
|
||||
// Hook: getLogs
|
||||
// ----------------------------------
|
||||
const hookId = this.getNodeParameter('hookId', i) as string;
|
||||
responseData = await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/hooks/${hookId}/logs/`,
|
||||
qs: {
|
||||
start: this.getNodeParameter('start', i, 0) as number,
|
||||
limit: this.getNodeParameter('limit', i, 1000) as number,
|
||||
},
|
||||
scroll: this.getNodeParameter('returnAll', i) as boolean,
|
||||
});
|
||||
}
|
||||
|
||||
if (operation === 'retryOne') {
|
||||
// ----------------------------------
|
||||
// Hook: retryOne
|
||||
// ----------------------------------
|
||||
const hookId = this.getNodeParameter('hookId', i) as string;
|
||||
const logId = this.getNodeParameter('logId', i) as string;
|
||||
|
||||
responseData = [await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/hooks/${hookId}/logs/${logId}/retry/`,
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
returnData = returnData.concat(responseData);
|
||||
}
|
||||
|
||||
// Map data to n8n data
|
||||
return binaryItems.length > 0
|
||||
? [binaryItems]
|
||||
: [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
168
packages/nodes-base/nodes/KoBoToolbox/KoBoToolboxTrigger.node.ts
Normal file
168
packages/nodes-base/nodes/KoBoToolbox/KoBoToolboxTrigger.node.ts
Normal file
|
@ -0,0 +1,168 @@
|
|||
import {
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookFunctions,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
downloadAttachments,
|
||||
formatSubmission,
|
||||
koBoToolboxApiRequest,
|
||||
loadForms,
|
||||
parseStringList
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
options,
|
||||
} from './Options';
|
||||
|
||||
export class KoBoToolboxTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'KoBoToolbox Trigger',
|
||||
name: 'koBoToolboxTrigger',
|
||||
icon: 'file:koBoToolbox.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Process KoBoToolbox submissions',
|
||||
defaults: {
|
||||
name: 'KoBoToolbox Trigger',
|
||||
color: '#64C0FF',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'koBoToolboxApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Form Name/ID',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'loadForms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)',
|
||||
},
|
||||
{
|
||||
displayName: 'Trigger On',
|
||||
name: 'triggerOn',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'formSubmission',
|
||||
options: [
|
||||
{
|
||||
name: 'On Form Submission',
|
||||
value: 'formSubmission',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ ...options },
|
||||
],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const formId = this.getNodeParameter('formId') as string; //tslint:disable-line:variable-name
|
||||
const webhooks = await koBoToolboxApiRequest.call(this, {
|
||||
url: `/api/v2/assets/${formId}/hooks/`,
|
||||
});
|
||||
for (const webhook of webhooks || []) {
|
||||
if (webhook.endpoint === webhookUrl && webhook.active === true) {
|
||||
webhookData.webhookId = webhook.uid;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const formId = this.getNodeParameter('formId') as string; //tslint:disable-line:variable-name
|
||||
|
||||
const response = await koBoToolboxApiRequest.call(this, {
|
||||
method: 'POST',
|
||||
url: `/api/v2/assets/${formId}/hooks/`,
|
||||
body: {
|
||||
name: `n8n-webhook:${webhookUrl}`,
|
||||
endpoint: webhookUrl,
|
||||
email_notification: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.uid) {
|
||||
webhookData.webhookId = response.uid;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const formId = this.getNodeParameter('formId') as string; //tslint:disable-line:variable-name
|
||||
try {
|
||||
await koBoToolboxApiRequest.call(this, {
|
||||
method: 'DELETE',
|
||||
url: `/api/v2/assets/${formId}/hooks/${webhookData.webhookId}`,
|
||||
});
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
delete webhookData.webhookId;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
loadForms,
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const req = this.getRequestObject();
|
||||
const formatOptions = this.getNodeParameter('formatOptions') as IDataObject;
|
||||
|
||||
const responseData = formatOptions.reformat
|
||||
? formatSubmission(req.body, parseStringList(formatOptions.selectMask as string), parseStringList(formatOptions.numberMask as string))
|
||||
: req.body;
|
||||
|
||||
if (formatOptions.download) {
|
||||
// Download related attachments
|
||||
return {
|
||||
workflowData: [
|
||||
[await downloadAttachments.call(this, responseData, formatOptions)],
|
||||
],
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray([responseData]),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
87
packages/nodes-base/nodes/KoBoToolbox/Options.ts
Normal file
87
packages/nodes-base/nodes/KoBoToolbox/Options.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const options = {
|
||||
displayName: 'Options',
|
||||
placeholder: 'Add Option',
|
||||
name: 'formatOptions',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attachments Prefix',
|
||||
name: 'dataPropertyAttachmentsPrefixName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
download: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'attachment_',
|
||||
description: 'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"',
|
||||
},
|
||||
{
|
||||
displayName: 'Download Attachments',
|
||||
name: 'download',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Download submitted attachments',
|
||||
},
|
||||
{
|
||||
displayName: 'File Size',
|
||||
name: 'version',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
download: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'download_url',
|
||||
description: 'Attachment size to retrieve, if multiple versions are available',
|
||||
options: [
|
||||
{
|
||||
name: 'Original',
|
||||
value: 'download_url',
|
||||
},
|
||||
{
|
||||
name: 'Small',
|
||||
value: 'download_small_url',
|
||||
},
|
||||
{
|
||||
name: 'Medium',
|
||||
value: 'download_medium_url',
|
||||
},
|
||||
{
|
||||
name: 'Large',
|
||||
value: 'download_large_url',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Multiselect Mask',
|
||||
name: 'selectMask',
|
||||
type: 'string',
|
||||
default: 'select_*',
|
||||
description: 'Comma-separated list of wildcard-style selectors for fields that should be treated as multiselect fields, i.e. parsed as arrays',
|
||||
},
|
||||
{
|
||||
displayName: 'Number Mask',
|
||||
name: 'numberMask',
|
||||
type: 'string',
|
||||
default: 'n_*, f_*',
|
||||
description: 'Comma-separated list of wildcard-style selectors for fields that should be treated as numbers',
|
||||
},
|
||||
{
|
||||
displayName: 'Reformat',
|
||||
name: 'reformat',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Apply some reformatting to the submission data, such as parsing GeoJSON coordinates',
|
||||
},
|
||||
],
|
||||
} as INodeProperties;
|
304
packages/nodes-base/nodes/KoBoToolbox/SubmissionDescription.ts
Normal file
304
packages/nodes-base/nodes/KoBoToolbox/SubmissionDescription.ts
Normal file
|
@ -0,0 +1,304 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const submissionOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a single submission',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a single submission',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all submissions',
|
||||
},
|
||||
{
|
||||
name: 'Get Validation Status',
|
||||
value: 'getValidation',
|
||||
description: 'Get the validation status for the submission',
|
||||
},
|
||||
{
|
||||
name: 'Update Validation Status',
|
||||
value: 'setValidation',
|
||||
description: 'Set the validation status of the submission',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
];
|
||||
|
||||
export const submissionFields: INodeProperties[] = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* submission:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form ID',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'loadForms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'delete',
|
||||
'getValidation',
|
||||
'setValidation',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)',
|
||||
},
|
||||
{
|
||||
displayName: 'Submission ID',
|
||||
name: 'submissionId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'delete',
|
||||
'getValidation',
|
||||
'setValidation',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Submission ID (number, e.g. 245128)',
|
||||
},
|
||||
{
|
||||
displayName: 'Validation Status',
|
||||
name: 'validationStatus',
|
||||
type: 'options',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'setValidation',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
options: [
|
||||
{
|
||||
name: 'Approved',
|
||||
value: 'validation_status_approved',
|
||||
},
|
||||
{
|
||||
name: 'Not Approved',
|
||||
value: 'validation_status_not_approved',
|
||||
},
|
||||
{
|
||||
name: 'On Hold',
|
||||
value: 'validation_status_on_hold',
|
||||
},
|
||||
],
|
||||
description: 'Desired Validation Status',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* submission:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form Name/ID',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'loadForms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return all results',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
required: false,
|
||||
typeOptions: {
|
||||
maxValue: 3000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 100,
|
||||
description: 'The number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'submission',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Option',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attachments Prefix',
|
||||
name: 'dataPropertyAttachmentsPrefixName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
download: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'attachment_',
|
||||
description: 'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"',
|
||||
},
|
||||
{
|
||||
displayName: 'Download Attachments',
|
||||
name: 'download',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Download submitted attachments',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields to Retrieve',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comma-separated list of fields to retrieve (e.g. _submission_time,_submitted_by). If left blank, all fields are retrieved',
|
||||
},
|
||||
{
|
||||
displayName: 'File Size',
|
||||
name: 'version',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
download: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'download_url',
|
||||
description: 'Attachment size to retrieve, if multiple versions are available',
|
||||
options: [
|
||||
{
|
||||
name: 'Original',
|
||||
value: 'download_url',
|
||||
},
|
||||
{
|
||||
name: 'Small',
|
||||
value: 'download_small_url',
|
||||
},
|
||||
{
|
||||
name: 'Medium',
|
||||
value: 'download_medium_url',
|
||||
},
|
||||
{
|
||||
name: 'Large',
|
||||
value: 'download_large_url',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Multiselect Mask',
|
||||
name: 'selectMask',
|
||||
type: 'string',
|
||||
default: 'select_*',
|
||||
description: 'Comma-separated list of wildcard-style selectors for fields that should be treated as multiselect fields, i.e. parsed as arrays',
|
||||
},
|
||||
{
|
||||
displayName: 'Number Mask',
|
||||
name: 'numberMask',
|
||||
type: 'string',
|
||||
default: 'n_*, f_*',
|
||||
description: 'Comma-separated list of wildcard-style selectors for fields that should be treated as numbers',
|
||||
},
|
||||
{
|
||||
displayName: 'Reformat',
|
||||
name: 'reformat',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Apply some reformatting to the submission data, such as parsing GeoJSON coordinates',
|
||||
},
|
||||
// {
|
||||
// displayName: 'Sort',
|
||||
// name: 'sort',
|
||||
// type: 'json',
|
||||
// default: '',
|
||||
// description: 'Sort predicates, in Mongo JSON format (e.g. {"_submission_time":1})',
|
||||
// },
|
||||
],
|
||||
},
|
||||
];
|
1
packages/nodes-base/nodes/KoBoToolbox/koBoToolbox.svg
Normal file
1
packages/nodes-base/nodes/KoBoToolbox/koBoToolbox.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="23.17 23.5 19.18 28.6"><style>.st79{fill:#64c0ff}</style><g id="Layer_4"><path class="st79" d="M38.6 42.8v2.8c0 1.6-1.3 2.8-2.8 2.8h-6.1c-1.6 0-2.8-1.3-2.8-2.8V30c0-1.6 1.3-2.8 2.8-2.8h6.1c1.6 0 2.8 1.3 2.8 2.8v.4c.5-.2 1.1-.3 1.7-.3.7 0 1.4.1 2 .4V30c0-3.6-2.9-6.5-6.5-6.5h-6.1c-3.6 0-6.5 2.9-6.5 6.5v15.6c0 3.6 2.9 6.5 6.5 6.5h6.1c3.6 0 6.5-2.9 6.5-6.5v-7.1l-3.7 4.3z"/><path class="st79" d="M35.6 41.9l6.6-7.6c.2-.2.2-.6-.1-.8-1.2-1-2.9-.9-3.9.3l-4.1 4.7c-.1.1-.3.1-.3 0l-1.5-1.9c-.3-.4-.8-.4-1.2 0-1 1-1 2.6-.1 3.7l1.2 1.5c.8 1.2 2.5 1.2 3.4.1z"/></g></svg>
|
After Width: | Height: | Size: 610 B |
|
@ -159,6 +159,7 @@
|
|||
"dist/credentials/Kafka.credentials.js",
|
||||
"dist/credentials/KeapOAuth2Api.credentials.js",
|
||||
"dist/credentials/KitemakerApi.credentials.js",
|
||||
"dist/credentials/KoBoToolboxApi.credentials.js",
|
||||
"dist/credentials/LemlistApi.credentials.js",
|
||||
"dist/credentials/LinearApi.credentials.js",
|
||||
"dist/credentials/LineNotifyOAuth2Api.credentials.js",
|
||||
|
@ -493,6 +494,8 @@
|
|||
"dist/nodes/Keap/Keap.node.js",
|
||||
"dist/nodes/Keap/KeapTrigger.node.js",
|
||||
"dist/nodes/Kitemaker/Kitemaker.node.js",
|
||||
"dist/nodes/KoBoToolbox/KoBoToolbox.node.js",
|
||||
"dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js",
|
||||
"dist/nodes/Lemlist/Lemlist.node.js",
|
||||
"dist/nodes/Lemlist/LemlistTrigger.node.js",
|
||||
"dist/nodes/Line/Line.node.js",
|
||||
|
@ -787,4 +790,4 @@
|
|||
"json"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue