mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat: add resource locator parameter (#3932)
* ✨ Added resource locator interfaces to `n8n-workflow` package * ✅ Updating Trello node to use resource locator property type * ✨ Added resource locator prop to Delete Board` Trello operation * ✔️ Fiixing linting errors in Trello node * ✨ Added list mode to Trello test node * ⚡ Updating resource locator modes interface * ⚡ Updating Trello test node validation messages and placeholders * N8N-4175 resource locator component (#3812) * ✨ Implemented initial version of resource locator component * ✨ Implemented front-end validation for resource locator component. Improved responsiveness. Minor refactoring. * ⚡ Setting resource locator default state to list. Updating hover states and expand icon. * 🔨 Moving resource locator component to `ParameterInput` from `ParameterInputFull * 🔨 Moving `ResourceLocator` to a separate Vue component * 🔨 Implementing expression and drag'n'drop support in ResourceLocator` component * 🔨 Cleaning up `ResourceLocator` component code * ✨ Implemented resource locator selected mode persistance * 💄 Minor refactoring and fixes in `ResourceLocator` * 🔨 Updating `ResourceLocator` front-end validation logic * ⚡ Saving resource locator mode in node parameters * 💄 Updating the `ResourceLocator` component based on the design review * 🐛 Fixing resource locator mode parameters handling when loading node parameter values on front-end * 💄 Removing leftover unused CSS * ⚡ Updating interfaces to support resource locator value types * ⚡ Updating `ResourceLocator` component to work with object parameter values * 🔨 Cleaning up `ResourceLocator` and related components code * ⚡ Preventing `DraggableTarget` to be sticky if disabled * 🐛 Fixing a bug with resource locator value parameter * 👌 Adding new type alias for all possible node parameter value types * 👌 Updating `ResourceLocator` and related components based on PR review feedback * ⚡ Adding disabled mode to `ResourceLocator` component, fixing expression handling, minor refactoring. * 💄 Updating disabled state styling in `ResourceLocator` component * ⚡ Setting correct default value for test node and removing unnecessary logic * 💄 Added regex URL validation to Trello test node * ✨ Updating Trello test node with another (list mode only) test case * ✔️ Fixing linting error in Trello node * 🔨 Removing hardcoded custom modes and modes order * Add value extractor to routing node (#3777) * ✨ add value extractor to routing node * ✨ add value extractor to property modes * 🔊 improve error logging for value extractor * 🔥 remove old extractValue methods from RoutingNode * ⚡ extractValue inside getNodeParameter * 🔥 remove extract value test from RoutingNode * ✨ make value extraction optional * 🥅 move extract value so proper error messages are sent * 🚨 readd accidentally removed eslint-disable * ✨ add resource locator support extractValue * 🚨 remove unused import * 🐛 fix getting value of resource locator * 💄 Updating resource locator component styling and handling reset value action * ✨ create v2 of Trello node for resource locator * 💄 Updating ResourceLocator droppable & activeDrop classes and removing input padding-right * ⚡ Updating Trello test node with single-mode test case * ⚡ Updating field names in Trello node to avoid name clash * 💄 Updating test Trello node mode order and board:update parameter name * 💄 Updating test node parameter names and display options * List mode search endpoint (#3936) * 🚧 super basic version of the search endpoint This version is built using a hacked up version of the Google Drive node. I need to properly create a v2 for it but it's does work. * 🚧 fixed up type errors and return urls * ✨ add v3 of Google Drive node with RLC * ✨ add RLC to Google Drive Shared Drive operations * ♻️ address some small changes requested in review * 🐛 move list search out of /nodes/ and add check for required param * ✨ google drive folder search * ✨ google drive search sort by name * ✨ add searchable flag for RLC * ✏️ fix google drive wording for v3 * Trello and Airtable search backend (#3974) * ✨ add search to Trello boards * ✨ add RLC to Trello cards * ♻️ use new versioning system for Trello v2 * 🐛 move list search out of /nodes/ and add check for required param * ✨ re-add trello search methods * 🥅 throw error if RLC search isn't sent a method name This will likely be removed when the declarative style of search has been added. * ✨ add requires filter field to RLC search * ✨ add searchable flag to Trello searches * ✨ add RLC for cardId and boardId on all operations * ✨ add ID and URL RLC to Airtable * N8 n 4179 resource locator list mode (#3933) * ✨ Implemented initial version of list mode dropdown * ✨ Handling mode switching and expression support in list mode * 🔨 Removing `sortedModes` references * ⚡ Fixing list mode UI after latest mege * 💄 Updating padding-right for input fields with suffix slots * ✨ Minor fixes to validation, mode switching logic and styling * update error * 2 or more regex * update regex to be more strict * remove expr colors * update hint * 🚧 super basic version of the search endpoint This version is built using a hacked up version of the Google Drive node. I need to properly create a v2 for it but it's does work. * 🚧 fixed up type errors and return urls * begin list impl * ✨ add v3 of Google Drive node with RLC * fix ts issue * introduce dropdown * add more behavior * update design * show search * add filtering * push up selected * add keyboard nav * add loading * add caching * remove console * fix build issues * add debounce * fix click * keep event on focus * fix input size bug * add resource locator type * update type * update interface * update resource locator types * ✨ add search to Trello boards * ✨ add RLC to Google Drive Shared Drive operations * update * update name * add package * use stringify pckg * handle long vals * fix bug in url id modes * remove console log * add lazy loading * add lazy loading on filtering * clean up * make search clearable * add error state * ✨ add RLC to Trello cards * ♻️ address some small changes requested in review * ♻️ use new versioning system for Trello v2 * refactor a bit * fix how loading happens * clear after blur * update api * comment out test code * update api * relaod in case of error * update endpoint * 🐛 move list search out of /nodes/ and add check for required param * 🐛 move list search out of /nodes/ and add check for required param * update req handling * update endpoint * ✨ re-add trello search methods * 🥅 throw error if RLC search isn't sent a method name This will likely be removed when the declarative style of search has been added. * get api to work * update scroll handling * ✨ google drive folder search * ✨ add requires filter field to RLC search * ✨ google drive search sort by name * remove console * ✨ add searchable flag for RLC * ✨ add searchable flag to Trello searches * update searchable * ✨ add RLC for cardId and boardId on all operations * ✨ add ID and URL RLC to Airtable * fix up search * remove extra padding * add link button * update popper pos * format * fix formating * update mode change * add name urls * update regex and errors * upate error * update errors * update airtable regex * update trello regex rules * udpate param name * update * update param * update param * update drive node * update params * add keyboard nav * fix bug * update airtable default mode * fix default value issue * hide long selected value * update duplicate reqs * update node * clean up impl * dedupe resources * fix up nv * resort params * update icon * set placeholders * default to id mode * add telemetry * add refresh opt * clean up tmp val * revert test change * make placeholder optional * update validation * remove description as param hint * support more general values * fix links on long names * update resource item styles * update pos * update icon color * update link alt * check if required * move validation to workflow * update naming * only show warning at param level * show right border on focus * fix hover on all item * fix long names bug * fix expr bug * add expr * update legacy mode * fix up impl * clean up node types * clean up types * remove unnessary type * clean up types * clean up types * clean up types * clea n up localizaiton * remove unused key * clean up helpers * clean up paraminput * clean up paraminputfull * refactor into one loop * update component * update class names * update prop types * update name cases * update casing * clean up classes * clean up resource locator * update drop handling * update mode * add url for link mode * clear value by default * add placeholder * remove legacy hint * handle expr in legacy * fix typos * revert padding change * fix up spacing * update to link component * support urls for id * fix replacement * build Co-authored-by: Milorad Filipovic <milorad@n8n.io> Co-authored-by: Valya Bullions <valya@n8n.io> * refactor: Resource locator review changes (#4109) * ✨ Implemented initial version of list mode dropdown * ✨ Handling mode switching and expression support in list mode * 🔨 Removing `sortedModes` references * ⚡ Fixing list mode UI after latest mege * 💄 Updating padding-right for input fields with suffix slots * ✨ Minor fixes to validation, mode switching logic and styling * update error * 2 or more regex * update regex to be more strict * remove expr colors * update hint * 🚧 super basic version of the search endpoint This version is built using a hacked up version of the Google Drive node. I need to properly create a v2 for it but it's does work. * 🚧 fixed up type errors and return urls * begin list impl * ✨ add v3 of Google Drive node with RLC * fix ts issue * introduce dropdown * add more behavior * update design * show search * add filtering * push up selected * add keyboard nav * add loading * add caching * remove console * fix build issues * add debounce * fix click * keep event on focus * fix input size bug * add resource locator type * update type * update interface * update resource locator types * ✨ add search to Trello boards * ✨ add RLC to Google Drive Shared Drive operations * update * update name * add package * use stringify pckg * handle long vals * fix bug in url id modes * remove console log * add lazy loading * add lazy loading on filtering * clean up * make search clearable * add error state * ✨ add RLC to Trello cards * ♻️ address some small changes requested in review * ♻️ use new versioning system for Trello v2 * refactor a bit * fix how loading happens * clear after blur * update api * comment out test code * update api * relaod in case of error * update endpoint * 🐛 move list search out of /nodes/ and add check for required param * 🐛 move list search out of /nodes/ and add check for required param * update req handling * update endpoint * ✨ re-add trello search methods * 🥅 throw error if RLC search isn't sent a method name This will likely be removed when the declarative style of search has been added. * get api to work * update scroll handling * ✨ google drive folder search * ✨ add requires filter field to RLC search * ✨ google drive search sort by name * remove console * ✨ add searchable flag for RLC * ✨ add searchable flag to Trello searches * update searchable * ✨ add RLC for cardId and boardId on all operations * ✨ add ID and URL RLC to Airtable * fix up search * remove extra padding * add link button * update popper pos * format * fix formating * update mode change * add name urls * update regex and errors * upate error * update errors * update airtable regex * update trello regex rules * udpate param name * update * update param * update param * update drive node * update params * add keyboard nav * fix bug * update airtable default mode * fix default value issue * hide long selected value * update duplicate reqs * update node * clean up impl * dedupe resources * fix up nv * resort params * update icon * set placeholders * default to id mode * add telemetry * add refresh opt * clean up tmp val * revert test change * make placeholder optional * update validation * remove description as param hint * support more general values * fix links on long names * update resource item styles * update pos * update icon color * update link alt * check if required * move validation to workflow * update naming * only show warning at param level * show right border on focus * fix hover on all item * fix long names bug * ♻️ refactor extractValue to allow multiple props with same name * ♻️ use correct import for displayParameterPath * fix expr bug * add expr * update legacy mode * fix up impl * clean up node types * clean up types * ♻️ remove new version of google drive node * ♻️ removed versioned Trello node for RLC * remove unnessary type * ♻️ remove versioned Airtable not for RLC * clean up types * clean up types * clean up types * clea n up localizaiton * remove unused key * clean up helpers * clean up paraminput * clean up paraminputfull * refactor into one loop * update component * update class names * update prop types * update name cases * update casing * clean up classes * 💬 updated RLC URL regex error wording * clean up resource locator * update drop handling * update mode * 💬 reword value extractor errors * 🚨 remove unneeded eslint ignores for RLC modes * 💬 update Trello 400 error message * 🚨 re-add removed types in editor-ui Also ts-ignore something that was clean up in another commit. I've added a comment to fix after someone else can look at it. * 💬 remove hints from Google Drive RLCs * 🥅 rethrow correct errors in Trello node * ✨ add url for id mode on Google Drive * 🔥 remove unused Google Drive file * 🔊 change console.error to use logger instead * 🔀 fix bad merges * ♻️ small changes from review * ♻️ remove ts-ignore Co-authored-by: Milorad Filipovic <milorad@n8n.io> Co-authored-by: Mutasem <mutdmour@gmail.com> * fix build * update tests * fix bug with credential card * update popover component * fix expressions url * fix type issue * format * update alt * fix lint issues * fix eslint issues Co-authored-by: Milorad Filipovic <milorad@n8n.io> Co-authored-by: Milorad FIlipović <miloradfilipovic19@gmail.com> Co-authored-by: Valya <68596159+valya@users.noreply.github.com> Co-authored-by: Valya Bullions <valya@n8n.io>
This commit is contained in:
72
package-lock.json
generated
72
package-lock.json
generated
@@ -21856,22 +21856,6 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-n8n-nodes-base": {
|
|
||||||
"version": "1.9.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.9.1.tgz",
|
|
||||||
"integrity": "sha512-7OQNP5DU3Lw1VgS+m6Ez+MqspRqtifEB2cM4n4q3Pxuw1P7HtAds+hBLIs6KFcTO1qINtaLr4l7XK5/h2XKThw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@typescript-eslint/utils": "^5.17.0",
|
|
||||||
"camel-case": "^4.1.2",
|
|
||||||
"indefinite": "^2.4.1",
|
|
||||||
"pascal-case": "^3.1.2",
|
|
||||||
"pluralize": "^8.0.0",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"sentence-case": "^3.0.4",
|
|
||||||
"title-case": "^3.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint-plugin-prettier": {
|
"node_modules/eslint-plugin-prettier": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
|
||||||
@@ -52492,6 +52476,7 @@
|
|||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"dateformat": "^3.0.3",
|
"dateformat": "^3.0.3",
|
||||||
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"flatted": "^3.2.4",
|
"flatted": "^3.2.4",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
@@ -52648,7 +52633,7 @@
|
|||||||
"@types/tmp": "^0.2.0",
|
"@types/tmp": "^0.2.0",
|
||||||
"@types/uuid": "^8.3.2",
|
"@types/uuid": "^8.3.2",
|
||||||
"@types/xml2js": "^0.4.3",
|
"@types/xml2js": "^0.4.3",
|
||||||
"eslint-plugin-n8n-nodes-base": "^1.9.1",
|
"eslint-plugin-n8n-nodes-base": "^1.9.3",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"jest": "^27.4.7",
|
"jest": "^27.4.7",
|
||||||
"n8n-workflow": "~0.116.0",
|
"n8n-workflow": "~0.116.0",
|
||||||
@@ -52665,6 +52650,23 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/nodes-base/node_modules/eslint-plugin-n8n-nodes-base": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-HcHTgcTuIudZE9vuTqjZkoG5GG7xn6wydL+ckqKpH7QkIK6p8wwDv/vL2SaDaRjT0vgO4/vzeUnCTQ/DQ2eZQg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/utils": "^5.17.0",
|
||||||
|
"camel-case": "^4.1.2",
|
||||||
|
"eslint-plugin-n8n-nodes-base": "^1.9.1",
|
||||||
|
"indefinite": "^2.4.1",
|
||||||
|
"pascal-case": "^3.1.2",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"sentence-case": "^3.0.4",
|
||||||
|
"title-case": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/nodes-base/node_modules/mongodb": {
|
"packages/nodes-base/node_modules/mongodb": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.9.1.tgz",
|
||||||
@@ -70081,22 +70083,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-plugin-n8n-nodes-base": {
|
|
||||||
"version": "1.9.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.9.1.tgz",
|
|
||||||
"integrity": "sha512-7OQNP5DU3Lw1VgS+m6Ez+MqspRqtifEB2cM4n4q3Pxuw1P7HtAds+hBLIs6KFcTO1qINtaLr4l7XK5/h2XKThw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@typescript-eslint/utils": "^5.17.0",
|
|
||||||
"camel-case": "^4.1.2",
|
|
||||||
"indefinite": "^2.4.1",
|
|
||||||
"pascal-case": "^3.1.2",
|
|
||||||
"pluralize": "^8.0.0",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"sentence-case": "^3.0.4",
|
|
||||||
"title-case": "^3.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"eslint-plugin-prettier": {
|
"eslint-plugin-prettier": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
|
||||||
@@ -81700,6 +81686,7 @@
|
|||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"dateformat": "^3.0.3",
|
"dateformat": "^3.0.3",
|
||||||
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"flatted": "^3.2.4",
|
"flatted": "^3.2.4",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
@@ -81810,7 +81797,7 @@
|
|||||||
"cheerio": "1.0.0-rc.6",
|
"cheerio": "1.0.0-rc.6",
|
||||||
"chokidar": "3.5.2",
|
"chokidar": "3.5.2",
|
||||||
"cron": "~1.7.2",
|
"cron": "~1.7.2",
|
||||||
"eslint-plugin-n8n-nodes-base": "^1.9.1",
|
"eslint-plugin-n8n-nodes-base": "^1.9.3",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"fast-glob": "^3.2.5",
|
"fast-glob": "^3.2.5",
|
||||||
"fflate": "^0.7.0",
|
"fflate": "^0.7.0",
|
||||||
@@ -81869,6 +81856,23 @@
|
|||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-n8n-nodes-base": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-HcHTgcTuIudZE9vuTqjZkoG5GG7xn6wydL+ckqKpH7QkIK6p8wwDv/vL2SaDaRjT0vgO4/vzeUnCTQ/DQ2eZQg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/utils": "^5.17.0",
|
||||||
|
"camel-case": "^4.1.2",
|
||||||
|
"eslint-plugin-n8n-nodes-base": "^1.9.1",
|
||||||
|
"indefinite": "^2.4.1",
|
||||||
|
"pascal-case": "^3.1.2",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"sentence-case": "^3.0.4",
|
||||||
|
"title-case": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mongodb": {
|
"mongodb": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.9.1.tgz",
|
||||||
|
|||||||
@@ -46,17 +46,27 @@ import clientOAuth1, { RequestOptions } from 'oauth-1.0a';
|
|||||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||||
import { compare } from 'bcryptjs';
|
import { compare } from 'bcryptjs';
|
||||||
|
|
||||||
import { BinaryDataManager, Credentials, LoadNodeParameterOptions, UserSettings } from 'n8n-core';
|
import {
|
||||||
|
BinaryDataManager,
|
||||||
|
Credentials,
|
||||||
|
LoadNodeParameterOptions,
|
||||||
|
LoadNodeListSearch,
|
||||||
|
UserSettings,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
INodeCredentialsDetails,
|
INodeCredentialsDetails,
|
||||||
|
INodeListSearchResult,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
ITelemetrySettings,
|
ITelemetrySettings,
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
|
NodeHelpers,
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
@@ -86,6 +96,7 @@ import { credentialsController } from './credentials/credentials.controller';
|
|||||||
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
||||||
import type {
|
import type {
|
||||||
ExecutionRequest,
|
ExecutionRequest,
|
||||||
|
NodeListSearchRequest,
|
||||||
NodeParameterOptionsRequest,
|
NodeParameterOptionsRequest,
|
||||||
OAuthRequest,
|
OAuthRequest,
|
||||||
WorkflowRequest,
|
WorkflowRequest,
|
||||||
@@ -136,6 +147,7 @@ import {
|
|||||||
WebhookServer,
|
WebhookServer,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { ResponseError } from './ResponseHelper';
|
||||||
|
|
||||||
require('body-parser-xml')(bodyParser);
|
require('body-parser-xml')(bodyParser);
|
||||||
|
|
||||||
@@ -825,6 +837,103 @@ class App {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Returns parameter values which normally get loaded from an external API or
|
||||||
|
// get generated dynamically
|
||||||
|
this.app.get(
|
||||||
|
`/${this.restEndpoint}/nodes-list-search`,
|
||||||
|
ResponseHelper.send(
|
||||||
|
async (
|
||||||
|
req: NodeListSearchRequest,
|
||||||
|
res: express.Response,
|
||||||
|
): Promise<INodeListSearchResult | undefined> => {
|
||||||
|
const nodeTypeAndVersion = JSON.parse(
|
||||||
|
req.query.nodeTypeAndVersion,
|
||||||
|
) as INodeTypeNameVersion;
|
||||||
|
|
||||||
|
const { path, methodName } = req.query;
|
||||||
|
|
||||||
|
if (!req.query.currentNodeParameters) {
|
||||||
|
throw new ResponseError('Parameter currentNodeParameters is required.', undefined, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentNodeParameters = JSON.parse(
|
||||||
|
req.query.currentNodeParameters,
|
||||||
|
) as INodeParameters;
|
||||||
|
|
||||||
|
let credentials: INodeCredentials | undefined;
|
||||||
|
|
||||||
|
if (req.query.credentials) {
|
||||||
|
credentials = JSON.parse(req.query.credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
const listSearchInstance = new LoadNodeListSearch(
|
||||||
|
nodeTypeAndVersion,
|
||||||
|
NodeTypes(),
|
||||||
|
path,
|
||||||
|
currentNodeParameters,
|
||||||
|
credentials,
|
||||||
|
);
|
||||||
|
|
||||||
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(
|
||||||
|
req.user.id,
|
||||||
|
currentNodeParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (methodName) {
|
||||||
|
return listSearchInstance.getOptionsViaMethodName(
|
||||||
|
methodName,
|
||||||
|
additionalData,
|
||||||
|
req.query.filter,
|
||||||
|
req.query.paginationToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ResponseError('Parameter methodName is required.', undefined, 400);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns all the node-types
|
||||||
|
this.app.get(
|
||||||
|
`/${this.restEndpoint}/node-types`,
|
||||||
|
ResponseHelper.send(
|
||||||
|
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
|
||||||
|
const returnData: INodeTypeDescription[] = [];
|
||||||
|
const onlyLatest = req.query.onlyLatest === 'true';
|
||||||
|
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
const allNodes = nodeTypes.getAll();
|
||||||
|
|
||||||
|
const getNodeDescription = (nodeType: INodeType): INodeTypeDescription => {
|
||||||
|
const nodeInfo: INodeTypeDescription = { ...nodeType.description };
|
||||||
|
if (req.query.includeProperties !== 'true') {
|
||||||
|
// @ts-ignore
|
||||||
|
delete nodeInfo.properties;
|
||||||
|
}
|
||||||
|
return nodeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onlyLatest) {
|
||||||
|
allNodes.forEach((nodeData) => {
|
||||||
|
const nodeType = NodeHelpers.getVersionedNodeType(nodeData);
|
||||||
|
const nodeInfo: INodeTypeDescription = getNodeDescription(nodeType);
|
||||||
|
returnData.push(nodeInfo);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
allNodes.forEach((nodeData) => {
|
||||||
|
const allNodeTypes = NodeHelpers.getVersionedNodeTypeAll(nodeData);
|
||||||
|
allNodeTypes.forEach((element) => {
|
||||||
|
const nodeInfo: INodeTypeDescription = getNodeDescription(element);
|
||||||
|
returnData.push(nodeInfo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
this.app.get(
|
this.app.get(
|
||||||
`/${this.restEndpoint}/credential-translation`,
|
`/${this.restEndpoint}/credential-translation`,
|
||||||
ResponseHelper.send(
|
ResponseHelper.send(
|
||||||
|
|||||||
19
packages/cli/src/requests.d.ts
vendored
19
packages/cli/src/requests.d.ts
vendored
@@ -287,6 +287,25 @@ export type NodeParameterOptionsRequest = AuthenticatedRequest<
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// /node-list-search
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
export type NodeListSearchRequest = AuthenticatedRequest<
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
nodeTypeAndVersion: string;
|
||||||
|
methodName: string;
|
||||||
|
path: string;
|
||||||
|
currentNodeParameters: string;
|
||||||
|
credentials: string;
|
||||||
|
filter?: string;
|
||||||
|
paginationToken?: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// /tags
|
// /tags
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|||||||
183
packages/core/src/ExtractValue.ts
Normal file
183
packages/core/src/ExtractValue.ts
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import {
|
||||||
|
INode,
|
||||||
|
INodeParameters,
|
||||||
|
INodeProperties,
|
||||||
|
INodePropertyCollection,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
NodeOperationError,
|
||||||
|
NodeParameterValueType,
|
||||||
|
NodeHelpers,
|
||||||
|
LoggerProxy,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
function findPropertyFromParameterName(
|
||||||
|
parameterName: string,
|
||||||
|
nodeType: INodeType,
|
||||||
|
node: INode,
|
||||||
|
nodeParameters: INodeParameters,
|
||||||
|
): INodePropertyOptions | INodeProperties | INodePropertyCollection {
|
||||||
|
let property: INodePropertyOptions | INodeProperties | INodePropertyCollection | undefined;
|
||||||
|
const paramParts = parameterName.split('.');
|
||||||
|
let currentParamPath = '';
|
||||||
|
|
||||||
|
const findProp = (
|
||||||
|
name: string,
|
||||||
|
options: Array<INodePropertyOptions | INodeProperties | INodePropertyCollection>,
|
||||||
|
): INodePropertyOptions | INodeProperties | INodePropertyCollection | undefined => {
|
||||||
|
return options.find(
|
||||||
|
(i) =>
|
||||||
|
i.name === name &&
|
||||||
|
NodeHelpers.displayParameterPath(nodeParameters, i, currentParamPath, node),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const p of paramParts) {
|
||||||
|
const param = p.split('[')[0];
|
||||||
|
if (!property) {
|
||||||
|
property = findProp(param, nodeType.description.properties);
|
||||||
|
} else if ('options' in property && property.options) {
|
||||||
|
property = findProp(param, property.options);
|
||||||
|
currentParamPath += `.${param}`;
|
||||||
|
} else if ('values' in property) {
|
||||||
|
property = findProp(param, property.values);
|
||||||
|
currentParamPath += `.${param}`;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Couldn't not find property "${parameterName}"`);
|
||||||
|
}
|
||||||
|
if (!property) {
|
||||||
|
throw new Error(`Couldn't not find property "${parameterName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!property) {
|
||||||
|
throw new Error(`Couldn't not find property "${parameterName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeRegexExtractValue(
|
||||||
|
value: string,
|
||||||
|
regex: RegExp,
|
||||||
|
parameterName: string,
|
||||||
|
parameterDisplayName: string,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
const extracted = regex.exec(value);
|
||||||
|
if (!extracted) {
|
||||||
|
throw new Error(
|
||||||
|
`ERROR: ${parameterDisplayName} parameter's value is invalid. This is likely because the URL entered is incorrect`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (extracted.length < 2 || extracted.length > 2) {
|
||||||
|
throw new Error(
|
||||||
|
`Property "${parameterName}" has an invalid extractValue regex "${regex.source}". extractValue expects exactly one group to be returned.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return extracted[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractValueRLC(
|
||||||
|
value: NodeParameterValueType | object,
|
||||||
|
property: INodeProperties,
|
||||||
|
parameterName: string,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
// Not an RLC value
|
||||||
|
if (typeof value !== 'object' || !value || !('mode' in value) || !('value' in value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const modeProp = (property.modes ?? []).find((i) => i.name === value.mode);
|
||||||
|
if (!modeProp) {
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
if (!('extractValue' in modeProp) || !modeProp.extractValue) {
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value.value !== 'string') {
|
||||||
|
let typeName: string | undefined = value.value?.constructor.name;
|
||||||
|
if (value.value === null) {
|
||||||
|
typeName = 'null';
|
||||||
|
} else if (typeName === undefined) {
|
||||||
|
typeName = 'undefined';
|
||||||
|
}
|
||||||
|
LoggerProxy.error(
|
||||||
|
`Only strings can be passed to extractValue. Parameter "${parameterName}" passed "${typeName}"`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`ERROR: ${property.displayName} parameter's value is invalid. Please enter a valid ${modeProp.displayName}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modeProp.extractValue.type !== 'regex') {
|
||||||
|
throw new Error(
|
||||||
|
`Property "${parameterName}" has an unknown extractValue type "${
|
||||||
|
modeProp.extractValue.type as string
|
||||||
|
}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(modeProp.extractValue.regex);
|
||||||
|
return executeRegexExtractValue(value.value, regex, parameterName, property.displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractValueOther(
|
||||||
|
value: NodeParameterValueType | object,
|
||||||
|
property: INodeProperties | INodePropertyCollection,
|
||||||
|
parameterName: string,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
if (!('extractValue' in property) || !property.extractValue) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
let typeName: string | undefined = value?.constructor.name;
|
||||||
|
if (value === null) {
|
||||||
|
typeName = 'null';
|
||||||
|
} else if (typeName === undefined) {
|
||||||
|
typeName = 'undefined';
|
||||||
|
}
|
||||||
|
LoggerProxy.error(
|
||||||
|
`Only strings can be passed to extractValue. Parameter "${parameterName}" passed "${typeName}"`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`ERROR: ${property.displayName} parameter's value is invalid. Please enter a valid value.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.extractValue.type !== 'regex') {
|
||||||
|
throw new Error(
|
||||||
|
`Property "${parameterName}" has an unknown extractValue type "${
|
||||||
|
property.extractValue.type as string
|
||||||
|
}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(property.extractValue.regex);
|
||||||
|
return executeRegexExtractValue(value, regex, parameterName, property.displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractValue(
|
||||||
|
value: NodeParameterValueType | object,
|
||||||
|
parameterName: string,
|
||||||
|
node: INode,
|
||||||
|
nodeType: INodeType,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
let property: INodePropertyOptions | INodeProperties | INodePropertyCollection;
|
||||||
|
try {
|
||||||
|
property = findPropertyFromParameterName(parameterName, nodeType, node, node.parameters);
|
||||||
|
|
||||||
|
// Definitely doesn't have value extractor
|
||||||
|
if (!('type' in property)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.type === 'resourceLocator') {
|
||||||
|
return extractValueRLC(value, property, parameterName);
|
||||||
|
}
|
||||||
|
return extractValueOther(value, property, parameterName);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
throw new NodeOperationError(node, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
133
packages/core/src/LoadNodeListSearch.ts
Normal file
133
packages/core/src/LoadNodeListSearch.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
|
import {
|
||||||
|
INode,
|
||||||
|
INodeCredentials,
|
||||||
|
INodeListSearchResult,
|
||||||
|
INodeParameters,
|
||||||
|
INodeTypeNameVersion,
|
||||||
|
INodeTypes,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
Workflow,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { NodeExecuteFunctions } from '.';
|
||||||
|
|
||||||
|
const TEMP_NODE_NAME = 'Temp-Node';
|
||||||
|
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
||||||
|
|
||||||
|
export class LoadNodeListSearch {
|
||||||
|
currentNodeParameters: INodeParameters;
|
||||||
|
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
workflow: Workflow;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
nodeTypeNameAndVersion: INodeTypeNameVersion,
|
||||||
|
nodeTypes: INodeTypes,
|
||||||
|
path: string,
|
||||||
|
currentNodeParameters: INodeParameters,
|
||||||
|
credentials?: INodeCredentials,
|
||||||
|
) {
|
||||||
|
const nodeType = nodeTypes.getByNameAndVersion(
|
||||||
|
nodeTypeNameAndVersion.name,
|
||||||
|
nodeTypeNameAndVersion.version,
|
||||||
|
);
|
||||||
|
this.currentNodeParameters = currentNodeParameters;
|
||||||
|
this.path = path;
|
||||||
|
if (nodeType === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The node-type "${nodeTypeNameAndVersion.name} v${nodeTypeNameAndVersion.version}" is not known!`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeData: INode = {
|
||||||
|
parameters: currentNodeParameters,
|
||||||
|
id: 'uuid-1234',
|
||||||
|
name: TEMP_NODE_NAME,
|
||||||
|
type: nodeTypeNameAndVersion.name,
|
||||||
|
typeVersion: nodeTypeNameAndVersion.version,
|
||||||
|
position: [0, 0],
|
||||||
|
};
|
||||||
|
if (credentials) {
|
||||||
|
nodeData.credentials = credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowData = {
|
||||||
|
nodes: [nodeData],
|
||||||
|
connections: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.workflow = new Workflow({
|
||||||
|
nodes: workflowData.nodes,
|
||||||
|
connections: workflowData.connections,
|
||||||
|
active: false,
|
||||||
|
nodeTypes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns data of a fake workflow
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
* @memberof LoadNodeParameterOptions
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
getWorkflowData() {
|
||||||
|
return {
|
||||||
|
name: TEMP_WORKFLOW_NAME,
|
||||||
|
active: false,
|
||||||
|
connections: {},
|
||||||
|
nodes: Object.values(this.workflow.nodes),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the available options via a predefined method
|
||||||
|
*
|
||||||
|
* @param {string} methodName The name of the method of which to get the data from
|
||||||
|
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||||
|
* @returns {Promise<INodePropertyOptions[]>}
|
||||||
|
* @memberof LoadNodeParameterOptions
|
||||||
|
*/
|
||||||
|
async getOptionsViaMethodName(
|
||||||
|
methodName: string,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const node = this.workflow.getNode(TEMP_NODE_NAME);
|
||||||
|
|
||||||
|
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node!.type, node?.typeVersion);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!nodeType ||
|
||||||
|
nodeType.methods === undefined ||
|
||||||
|
nodeType.methods.listSearch === undefined ||
|
||||||
|
nodeType.methods.listSearch[methodName] === undefined
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`The node-type "${node!.type}" does not have the method "${methodName}" defined!`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(
|
||||||
|
this.workflow,
|
||||||
|
node!,
|
||||||
|
this.path,
|
||||||
|
additionalData,
|
||||||
|
);
|
||||||
|
|
||||||
|
return nodeType.methods.listSearch[methodName].call(thisArgs, filter, paginationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,7 +52,6 @@ import {
|
|||||||
NodeApiError,
|
NodeApiError,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeOperationError,
|
NodeOperationError,
|
||||||
NodeParameterValue,
|
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
WorkflowDataProxy,
|
WorkflowDataProxy,
|
||||||
@@ -60,6 +59,8 @@ import {
|
|||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
IExecuteData,
|
IExecuteData,
|
||||||
OAuth2GrantType,
|
OAuth2GrantType,
|
||||||
|
IGetNodeParameterOptions,
|
||||||
|
NodeParameterValueType,
|
||||||
NodeExecutionWithMetadata,
|
NodeExecutionWithMetadata,
|
||||||
IPairedItemData,
|
IPairedItemData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
@@ -99,6 +100,7 @@ import {
|
|||||||
IWorkflowSettings,
|
IWorkflowSettings,
|
||||||
PLACEHOLDER_EMPTY_EXECUTION_ID,
|
PLACEHOLDER_EMPTY_EXECUTION_ID,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { extractValue } from './ExtractValue';
|
||||||
|
|
||||||
axios.defaults.timeout = 300000;
|
axios.defaults.timeout = 300000;
|
||||||
// Prevent axios from adding x-form-www-urlencoded headers by default
|
// Prevent axios from adding x-form-www-urlencoded headers by default
|
||||||
@@ -1672,9 +1674,7 @@ export function getNode(node: INode): INode {
|
|||||||
* Clean up parameter data to make sure that only valid data gets returned
|
* Clean up parameter data to make sure that only valid data gets returned
|
||||||
* INFO: Currently only converts Luxon Dates as we know for sure it will not be breaking
|
* INFO: Currently only converts Luxon Dates as we know for sure it will not be breaking
|
||||||
*/
|
*/
|
||||||
function cleanupParameterData(
|
function cleanupParameterData(inputData: NodeParameterValueType): NodeParameterValueType {
|
||||||
inputData: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
|
||||||
if (inputData === null || inputData === undefined) {
|
if (inputData === null || inputData === undefined) {
|
||||||
return inputData;
|
return inputData;
|
||||||
}
|
}
|
||||||
@@ -1691,7 +1691,9 @@ function cleanupParameterData(
|
|||||||
|
|
||||||
if (typeof inputData === 'object') {
|
if (typeof inputData === 'object') {
|
||||||
Object.keys(inputData).forEach((key) => {
|
Object.keys(inputData).forEach((key) => {
|
||||||
inputData[key] = cleanupParameterData(inputData[key]);
|
inputData[key as keyof typeof inputData] = cleanupParameterData(
|
||||||
|
inputData[key as keyof typeof inputData],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1725,7 +1727,8 @@ export function getNodeParameter(
|
|||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
executeData?: IExecuteData,
|
executeData?: IExecuteData,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`Node type "${node.type}" is not known so can not return parameter value!`);
|
throw new Error(`Node type "${node.type}" is not known so can not return parameter value!`);
|
||||||
@@ -1759,6 +1762,11 @@ export function getNodeParameter(
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is outside the try/catch because it throws errors with proper messages
|
||||||
|
if (options?.extractValue) {
|
||||||
|
returnData = extractValue(returnData, parameterName, node, nodeType);
|
||||||
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1931,12 +1939,8 @@ export function getExecutePollFunctions(
|
|||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
@@ -1955,6 +1959,7 @@ export function getExecutePollFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
undefined,
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getRestApiUrl: (): string => {
|
getRestApiUrl: (): string => {
|
||||||
@@ -2089,12 +2094,8 @@ export function getExecuteTriggerFunctions(
|
|||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
@@ -2113,6 +2114,7 @@ export function getExecuteTriggerFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
undefined,
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getRestApiUrl: (): string => {
|
getRestApiUrl: (): string => {
|
||||||
@@ -2312,12 +2314,8 @@ export function getExecuteFunctions(
|
|||||||
parameterName: string,
|
parameterName: string,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
return getNodeParameter(
|
return getNodeParameter(
|
||||||
workflow,
|
workflow,
|
||||||
runExecutionData,
|
runExecutionData,
|
||||||
@@ -2331,6 +2329,7 @@ export function getExecuteFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
executeData,
|
executeData,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getMode: (): WorkflowExecuteMode => {
|
getMode: (): WorkflowExecuteMode => {
|
||||||
@@ -2590,12 +2589,8 @@ export function getExecuteSingleFunctions(
|
|||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
return getNodeParameter(
|
return getNodeParameter(
|
||||||
workflow,
|
workflow,
|
||||||
runExecutionData,
|
runExecutionData,
|
||||||
@@ -2609,6 +2604,7 @@ export function getExecuteSingleFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
executeData,
|
executeData,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getWorkflow: () => {
|
getWorkflow: () => {
|
||||||
@@ -2744,13 +2740,7 @@ export function getLoadOptionsFunctions(
|
|||||||
},
|
},
|
||||||
getCurrentNodeParameter: (
|
getCurrentNodeParameter: (
|
||||||
parameterPath: string,
|
parameterPath: string,
|
||||||
):
|
): NodeParameterValueType | object | undefined => {
|
||||||
| NodeParameterValue
|
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object
|
|
||||||
| undefined => {
|
|
||||||
const nodeParameters = additionalData.currentNodeParameters;
|
const nodeParameters = additionalData.currentNodeParameters;
|
||||||
|
|
||||||
if (parameterPath.charAt(0) === '&') {
|
if (parameterPath.charAt(0) === '&') {
|
||||||
@@ -2768,12 +2758,8 @@ export function getLoadOptionsFunctions(
|
|||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
@@ -2792,6 +2778,7 @@ export function getLoadOptionsFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
undefined,
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getTimezone: (): string => {
|
getTimezone: (): string => {
|
||||||
@@ -2899,12 +2886,8 @@ export function getExecuteHookFunctions(
|
|||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
@@ -2923,6 +2906,7 @@ export function getExecuteHookFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
undefined,
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getNodeWebhookUrl: (name: string): string | undefined => {
|
getNodeWebhookUrl: (name: string): string | undefined => {
|
||||||
@@ -3062,12 +3046,8 @@ export function getExecuteWebhookFunctions(
|
|||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
):
|
options?: IGetNodeParameterOptions,
|
||||||
| NodeParameterValue
|
): NodeParameterValueType | object => {
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
@@ -3086,6 +3066,7 @@ export function getExecuteWebhookFunctions(
|
|||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
undefined,
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getParamsData(): object {
|
getParamsData(): object {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export * from './Constants';
|
|||||||
export * from './Credentials';
|
export * from './Credentials';
|
||||||
export * from './Interfaces';
|
export * from './Interfaces';
|
||||||
export * from './LoadNodeParameterOptions';
|
export * from './LoadNodeParameterOptions';
|
||||||
|
export * from './LoadNodeListSearch';
|
||||||
export * from './NodeExecuteFunctions';
|
export * from './NodeExecuteFunctions';
|
||||||
export * from './WorkflowExecute';
|
export * from './WorkflowExecute';
|
||||||
export { NodeExecuteFunctions, UserSettings };
|
export { NodeExecuteFunctions, UserSettings };
|
||||||
|
|||||||
@@ -382,6 +382,10 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
|||||||
--button-hover-color: var(--color-success);
|
--button-hover-color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.tertiary {
|
||||||
|
--button-hover-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
&.warning {
|
&.warning {
|
||||||
--button-color: var(--color-warning);
|
--button-color: var(--color-warning);
|
||||||
--button-active-color: var(--color-warning);
|
--button-active-color: var(--color-warning);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
// Vitest Snapshot v1
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
exports[`components > N8nButton > overrides > should render correctly 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_t2kdy_115 _secondary_t2kdy_170 _medium_t2kdy_254 _icon_t2kdy_381\\" icon=\\"plus-circle\\" type=\\"secondary\\"><span class=\\"_icon_t2kdy_381\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
exports[`components > N8nButton > overrides > should render correctly 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _secondary_1qq65_170 _medium_1qq65_254 _icon_1qq65_384\\" icon=\\"plus-circle\\" type=\\"secondary\\"><span class=\\"_icon_1qq65_384\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
||||||
|
|
||||||
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_t2kdy_115 _primary_t2kdy_288 _medium_t2kdy_254 _icon_t2kdy_381\\"><span class=\\"_icon_t2kdy_381\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254 _icon_1qq65_384\\"><span class=\\"_icon_1qq65_384\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
||||||
|
|
||||||
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button disabled=\\"disabled\\" aria-disabled=\\"false\\" aria-busy=\\"true\\" aria-live=\\"polite\\" class=\\"button _button_t2kdy_115 _primary_t2kdy_288 _medium_t2kdy_254 _loading_t2kdy_352 _icon_t2kdy_381\\"><span class=\\"_icon_t2kdy_381\\"><n8n-spinner-stub size=\\"medium\\" type=\\"dots\\"></n8n-spinner-stub></span><span>Button</span></button>"`;
|
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button disabled=\\"disabled\\" aria-disabled=\\"false\\" aria-busy=\\"true\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254 _loading_1qq65_355 _icon_1qq65_384\\"><span class=\\"_icon_1qq65_384\\"><n8n-spinner-stub size=\\"medium\\" type=\\"dots\\"></n8n-spinner-stub></span><span>Button</span></button>"`;
|
||||||
|
|
||||||
exports[`components > N8nButton > props > square > should render square button 1`] = `
|
exports[`components > N8nButton > props > square > should render square button 1`] = `
|
||||||
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_t2kdy_115 _primary_t2kdy_288 _medium_t2kdy_254 _square_t2kdy_239\\">
|
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254 _square_1qq65_239\\">
|
||||||
<!----><span>48</span></button>"
|
<!----><span>48</span></button>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`components > N8nButton > should render correctly 1`] = `
|
exports[`components > N8nButton > should render correctly 1`] = `
|
||||||
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_t2kdy_115 _primary_t2kdy_288 _medium_t2kdy_254\\">
|
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254\\">
|
||||||
<!----><span>Button</span></button>"
|
<!----><span>Button</span></button>"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ export default Vue.extend({
|
|||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
clearable: {
|
clearable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ export default Vue.extend({
|
|||||||
.text {
|
.text {
|
||||||
color: var(--color-text-base);
|
color: var(--color-text-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: saturation(--color-primary-h, --color-primary-s, --color-primary-l, -(30%));
|
color: saturation(--color-primary-h, --color-primary-s, --color-primary-l, -(30%));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
height: var.$input-height;
|
height: var.$input-height;
|
||||||
line-height: var.$input-height;
|
line-height: var.$input-height;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0 var(--spacing-xs);
|
padding: 0 0 0 var(--spacing-2xs);
|
||||||
transition: var.$border-transition-base;
|
transition: var.$border-transition-base;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
@include mixins.e(suffix) {
|
@include mixins.e(suffix) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
right: 10px;
|
right: var(--spacing-2xs);
|
||||||
top: 0;
|
top: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
"vue-template-compiler": "~2.6.11",
|
"vue-template-compiler": "~2.6.11",
|
||||||
"vue-typed-mixins": "^0.2.0",
|
"vue-typed-mixins": "^0.2.0",
|
||||||
"vue2-touch-events": "^3.2.1",
|
"vue2-touch-events": "^3.2.1",
|
||||||
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"vuex": "^3.1.1"
|
"vuex": "^3.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
GenericValue,
|
GenericValue,
|
||||||
IConnections,
|
IConnections,
|
||||||
@@ -19,6 +18,11 @@ import {
|
|||||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
PublicInstalledPackage,
|
PublicInstalledPackage,
|
||||||
|
IResourceLocatorResult,
|
||||||
|
INodeTypeNameVersion,
|
||||||
|
ILoadOptions,
|
||||||
|
INodeCredentials,
|
||||||
|
INodeListSearchItems,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { FAKE_DOOR_FEATURES } from './constants';
|
import { FAKE_DOOR_FEATURES } from './constants';
|
||||||
|
|
||||||
@@ -1079,3 +1083,18 @@ export interface ITab {
|
|||||||
align?: 'right';
|
align?: 'right';
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IResourceLocatorReqParams {
|
||||||
|
nodeTypeAndVersion: INodeTypeNameVersion;
|
||||||
|
path: string;
|
||||||
|
methodName?: string;
|
||||||
|
searchList?: ILoadOptions;
|
||||||
|
currentNodeParameters: INodeParameters;
|
||||||
|
credentials?: INodeCredentials;
|
||||||
|
filter?: string;
|
||||||
|
paginationToken?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResourceLocatorResultExpanded extends INodeListSearchItems {
|
||||||
|
linkAlt?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { makeRestApiRequest } from './helpers';
|
import { makeRestApiRequest } from './helpers';
|
||||||
import type {
|
import type {
|
||||||
INodeTranslationHeaders,
|
INodeTranslationHeaders,
|
||||||
|
IResourceLocatorReqParams,
|
||||||
IRestApiContext,
|
IRestApiContext,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import type {
|
import type {
|
||||||
|
IDataObject,
|
||||||
ILoadOptions,
|
ILoadOptions,
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
|
INodeListSearchResult,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
@@ -45,3 +48,11 @@ export async function getNodeParameterOptions(
|
|||||||
): Promise<INodePropertyOptions[]> {
|
): Promise<INodePropertyOptions[]> {
|
||||||
return makeRestApiRequest(context, 'GET', '/node-parameter-options', sendData);
|
return makeRestApiRequest(context, 'GET', '/node-parameter-options', sendData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getResourceLocatorResults(
|
||||||
|
context: IRestApiContext,
|
||||||
|
sendData: IResourceLocatorReqParams,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
return makeRestApiRequest(context, 'GET', '/nodes-list-search', sendData as unknown as IDataObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ import {
|
|||||||
|
|
||||||
import mixins from "vue-typed-mixins";
|
import mixins from "vue-typed-mixins";
|
||||||
import { genericHelpers } from "@/components/mixins/genericHelpers";
|
import { genericHelpers } from "@/components/mixins/genericHelpers";
|
||||||
|
import { debounceHelper } from "./mixins/debounce";
|
||||||
|
|
||||||
export default mixins(genericHelpers).extend({
|
export default mixins(genericHelpers, debounceHelper).extend({
|
||||||
name: "BreakpointsObserver",
|
name: "BreakpointsObserver",
|
||||||
props: [
|
props: [
|
||||||
"valueXS",
|
"valueXS",
|
||||||
@@ -98,4 +99,4 @@ export default mixins(genericHelpers).extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export default mixins(
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
this.$store.dispatch('ui/openExisitngCredential', { id: this.data.id});
|
this.$store.dispatch('ui/openExistingCredential', { id: this.data.id});
|
||||||
},
|
},
|
||||||
async onAction(action: string) {
|
async onAction(action: string) {
|
||||||
if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) {
|
if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export default mixins(
|
|||||||
},
|
},
|
||||||
|
|
||||||
editCredential (credential: ICredentialsResponse) {
|
editCredential (credential: ICredentialsResponse) {
|
||||||
this.$store.dispatch('ui/openExisitngCredential', { id: credential.id});
|
this.$store.dispatch('ui/openExistingCredential', { id: credential.id});
|
||||||
this.$telemetry.track('User opened Credential modal', { credential_type: credential.type, source: 'primary_menu', new_credential: false, workflow_id: this.$store.getters.workflowId });
|
this.$telemetry.track('User opened Credential modal', { credential_type: credential.type, source: 'primary_menu', new_credential: false, workflow_id: this.$store.getters.workflowId });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
this.hovering = e.clientX >= dim.left && e.clientX <= dim.right && e.clientY >= dim.top && e.clientY <= dim.bottom;
|
this.hovering = e.clientX >= dim.left && e.clientX <= dim.right && e.clientY >= dim.top && e.clientY <= dim.bottom;
|
||||||
|
|
||||||
if (this.sticky && this.hovering) {
|
if (!this.disabled && this.sticky && this.hovering) {
|
||||||
this.$store.commit('ui/setDraggableStickyPos', [dim.left + this.stickyOffset, dim.top + this.stickyOffset]);
|
this.$store.commit('ui/setDraggableStickyPos', [dim.left + this.stickyOffset, dim.top + this.stickyOffset]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
|
|||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { hasExpressionMapping } from './helpers';
|
import { hasExpressionMapping } from './helpers';
|
||||||
|
import { debounceHelper } from './mixins/debounce';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
externalHooks,
|
externalHooks,
|
||||||
genericHelpers,
|
genericHelpers,
|
||||||
|
debounceHelper,
|
||||||
).extend({
|
).extend({
|
||||||
name: 'ExpressionEdit',
|
name: 'ExpressionEdit',
|
||||||
props: [
|
props: [
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ export default mixins(
|
|||||||
height: 35px;
|
height: 35px;
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
color: $--custom-dialog-text-color;
|
color: $--custom-dialog-text-color;
|
||||||
--menu-item-hover-fill: var(--color-primary-tint-3);
|
--menu-item-hover-fill: var(--color-background-base);
|
||||||
|
|
||||||
.item-title {
|
.item-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -656,7 +656,7 @@ export default mixins(
|
|||||||
.el-menu {
|
.el-menu {
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
--menu-item-hover-fill: var(--color-primary-tint-3);
|
--menu-item-hover-fill: var(--color-background-base);
|
||||||
|
|
||||||
.el-menu--collapse {
|
.el-menu--collapse {
|
||||||
width: 75px;
|
width: 75px;
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ export default mixins(
|
|||||||
|
|
||||||
editCredential(credentialType: string): void {
|
editCredential(credentialType: string): void {
|
||||||
const { id } = this.node.credentials[credentialType];
|
const { id } = this.node.credentials[credentialType];
|
||||||
this.$store.dispatch('ui/openExisitngCredential', { id });
|
this.$store.dispatch('ui/openExistingCredential', { id });
|
||||||
|
|
||||||
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.$store.getters.workflowId });
|
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.$store.getters.workflowId });
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div @keydown.stop :class="parameterInputClasses">
|
<div @keydown.stop :class="parameterInputClasses">
|
||||||
<expression-edit
|
<expression-edit
|
||||||
:dialogVisible="expressionEditDialogVisible"
|
:dialogVisible="expressionEditDialogVisible"
|
||||||
:value="value"
|
:value="isResourceLocatorParameter ? (value ? value.value : '') : value"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:path="path"
|
:path="path"
|
||||||
:eventSource="eventSource || 'ndv'"
|
:eventSource="eventSource || 'ndv'"
|
||||||
@@ -14,8 +14,26 @@
|
|||||||
:style="parameterInputWrapperStyle"
|
:style="parameterInputWrapperStyle"
|
||||||
@click="openExpressionEdit"
|
@click="openExpressionEdit"
|
||||||
>
|
>
|
||||||
|
<resource-locator
|
||||||
|
v-if="isResourceLocatorParameter"
|
||||||
|
ref="resourceLocator"
|
||||||
|
:parameter="parameter"
|
||||||
|
:value="value"
|
||||||
|
:displayTitle="displayTitle"
|
||||||
|
:expressionDisplayValue="expressionDisplayValue"
|
||||||
|
:isValueExpression="isValueExpression"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
|
:parameterIssues="getIssues"
|
||||||
|
:droppable="droppable"
|
||||||
|
:node="node"
|
||||||
|
:path="path"
|
||||||
|
@input="valueChanged"
|
||||||
|
@focus="setFocus"
|
||||||
|
@blur="onBlur"
|
||||||
|
@drop="onResourceLocatorDrop"
|
||||||
|
/>
|
||||||
<n8n-input
|
<n8n-input
|
||||||
v-if="isValueExpression || droppable || forceShowExpression"
|
v-else-if="isValueExpression || droppable || forceShowExpression"
|
||||||
:size="inputSize"
|
:size="inputSize"
|
||||||
:type="getStringInputType"
|
:type="getStringInputType"
|
||||||
:rows="getArgument('rows')"
|
:rows="getArgument('rows')"
|
||||||
@@ -23,7 +41,6 @@
|
|||||||
:title="displayTitle"
|
:title="displayTitle"
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
v-else-if="
|
||||||
['json', 'string'].includes(parameter.type) ||
|
['json', 'string'].includes(parameter.type) ||
|
||||||
@@ -80,7 +97,7 @@
|
|||||||
<div slot="suffix" class="expand-input-icon-container">
|
<div slot="suffix" class="expand-input-icon-container">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="!isReadOnly"
|
v-if="!isReadOnly"
|
||||||
icon="external-link-alt"
|
icon="expand-alt"
|
||||||
class="edit-window-button clickable"
|
class="edit-window-button clickable"
|
||||||
:title="$locale.baseText('parameterInput.openEditWindow')"
|
:title="$locale.baseText('parameterInput.openEditWindow')"
|
||||||
@click="displayEditDialog()"
|
@click="displayEditDialog()"
|
||||||
@@ -260,7 +277,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<parameter-issues v-if="parameter.type !== 'credentialsSelect'" :issues="getIssues" />
|
<parameter-issues v-if="parameter.type !== 'credentialsSelect' && !isResourceLocatorParameter" :issues="getIssues" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -274,7 +291,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
IHttpRequestOptions,
|
|
||||||
ILoadOptions,
|
ILoadOptions,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
@@ -288,6 +304,7 @@ import NodeCredentials from '@/components/NodeCredentials.vue';
|
|||||||
import ScopesNotice from '@/components/ScopesNotice.vue';
|
import ScopesNotice from '@/components/ScopesNotice.vue';
|
||||||
import ParameterOptions from '@/components/ParameterOptions.vue';
|
import ParameterOptions from '@/components/ParameterOptions.vue';
|
||||||
import ParameterIssues from '@/components/ParameterIssues.vue';
|
import ParameterIssues from '@/components/ParameterIssues.vue';
|
||||||
|
import ResourceLocator from '@/components/ResourceLocator/ResourceLocator.vue';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import PrismEditor from 'vue-prism-editor';
|
import PrismEditor from 'vue-prism-editor';
|
||||||
import TextEdit from '@/components/TextEdit.vue';
|
import TextEdit from '@/components/TextEdit.vue';
|
||||||
@@ -299,7 +316,8 @@ import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
|||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { CUSTOM_API_CALL_KEY } from '@/constants';
|
import { CUSTOM_API_CALL_KEY } from '@/constants';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { hasExpressionMapping } from './helpers';
|
import { hasExpressionMapping, isValueExpression } from './helpers';
|
||||||
|
import { isResourceLocatorValue } from '@/typeGuards';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
externalHooks,
|
externalHooks,
|
||||||
@@ -308,7 +326,7 @@ export default mixins(
|
|||||||
workflowHelpers,
|
workflowHelpers,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
name: 'ParameterInput',
|
name: 'parameter-input',
|
||||||
components: {
|
components: {
|
||||||
CodeEdit,
|
CodeEdit,
|
||||||
ExpressionEdit,
|
ExpressionEdit,
|
||||||
@@ -318,6 +336,7 @@ export default mixins(
|
|||||||
ScopesNotice,
|
ScopesNotice,
|
||||||
ParameterOptions,
|
ParameterOptions,
|
||||||
ParameterIssues,
|
ParameterIssues,
|
||||||
|
ResourceLocator,
|
||||||
TextEdit,
|
TextEdit,
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
@@ -395,6 +414,9 @@ export default mixins(
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('credentials', ['allCredentialTypes']),
|
...mapGetters('credentials', ['allCredentialTypes']),
|
||||||
|
isValueExpression(): boolean {
|
||||||
|
return isValueExpression(this.parameter, this.value);
|
||||||
|
},
|
||||||
areExpressionsDisabled(): boolean {
|
areExpressionsDisabled(): boolean {
|
||||||
return this.$store.getters['ui/areExpressionsDisabled'];
|
return this.$store.getters['ui/areExpressionsDisabled'];
|
||||||
},
|
},
|
||||||
@@ -459,7 +481,7 @@ export default mixins(
|
|||||||
|
|
||||||
let returnValue;
|
let returnValue;
|
||||||
if (this.isValueExpression === false) {
|
if (this.isValueExpression === false) {
|
||||||
returnValue = this.value;
|
returnValue = this.isResourceLocatorParameter ? (this.value ? this.value.value: '') : this.value;
|
||||||
} else {
|
} else {
|
||||||
returnValue = this.expressionValueComputed;
|
returnValue = this.expressionValueComputed;
|
||||||
}
|
}
|
||||||
@@ -518,7 +540,7 @@ export default mixins(
|
|||||||
let computedValue: NodeParameterValue;
|
let computedValue: NodeParameterValue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
computedValue = this.resolveExpression(this.value) as NodeParameterValue;
|
computedValue = this.resolveExpression(this.value.value || this.value) as NodeParameterValue;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
computedValue = `[${this.$locale.baseText('parameterInput.error')}}: ${error.message}]`;
|
computedValue = `[${this.$locale.baseText('parameterInput.error')}}: ${error.message}]`;
|
||||||
}
|
}
|
||||||
@@ -610,15 +632,6 @@ export default mixins(
|
|||||||
isEditor (): boolean {
|
isEditor (): boolean {
|
||||||
return ['code', 'json'].includes(this.editorType);
|
return ['code', 'json'].includes(this.editorType);
|
||||||
},
|
},
|
||||||
isValueExpression () {
|
|
||||||
if (this.parameter.noDataExpression === true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof this.value === 'string' && this.value.charAt(0) === '=') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
editorType (): string {
|
editorType (): string {
|
||||||
return this.getArgument('editor') as string;
|
return this.getArgument('editor') as string;
|
||||||
},
|
},
|
||||||
@@ -659,7 +672,7 @@ export default mixins(
|
|||||||
const styles = {
|
const styles = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
};
|
};
|
||||||
if (this.parameter.type === 'credentialsSelect') {
|
if (this.parameter.type === 'credentialsSelect' || this.isResourceLocatorParameter) {
|
||||||
return styles;
|
return styles;
|
||||||
}
|
}
|
||||||
if (this.getIssues.length) {
|
if (this.getIssues.length) {
|
||||||
@@ -683,6 +696,9 @@ export default mixins(
|
|||||||
workflow (): Workflow {
|
workflow (): Workflow {
|
||||||
return this.getCurrentWorkflow();
|
return this.getCurrentWorkflow();
|
||||||
},
|
},
|
||||||
|
isResourceLocatorParameter (): boolean {
|
||||||
|
return this.parameter.type === 'resourceLocator';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isRemoteParameterOption(option: INodePropertyOptions) {
|
isRemoteParameterOption(option: INodePropertyOptions) {
|
||||||
@@ -730,9 +746,9 @@ export default mixins(
|
|||||||
this.remoteParameterOptions.length = 0;
|
this.remoteParameterOptions.length = 0;
|
||||||
|
|
||||||
// Get the resolved parameter values of the current node
|
// Get the resolved parameter values of the current node
|
||||||
const currentNodeParameters = this.$store.getters.activeNode.parameters;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const currentNodeParameters = (this.$store.getters.activeNode as INodeUi).parameters;
|
||||||
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
||||||
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
|
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
|
||||||
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
|
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
|
||||||
@@ -803,7 +819,8 @@ export default mixins(
|
|||||||
return this.parameter.typeOptions[argumentName];
|
return this.parameter.typeOptions[argumentName];
|
||||||
},
|
},
|
||||||
expressionUpdated (value: string) {
|
expressionUpdated (value: string) {
|
||||||
this.valueChanged(value);
|
const val = this.isResourceLocatorParameter ? { value, mode: this.value.mode } : value;
|
||||||
|
this.valueChanged(val);
|
||||||
},
|
},
|
||||||
openExpressionEdit() {
|
openExpressionEdit() {
|
||||||
if (this.areExpressionsDisabled) {
|
if (this.areExpressionsDisabled) {
|
||||||
@@ -819,6 +836,9 @@ export default mixins(
|
|||||||
onBlur () {
|
onBlur () {
|
||||||
this.$emit('blur');
|
this.$emit('blur');
|
||||||
},
|
},
|
||||||
|
onResourceLocatorDrop(data: string) {
|
||||||
|
this.$emit('drop', data);
|
||||||
|
},
|
||||||
setFocus () {
|
setFocus () {
|
||||||
if (this.isValueExpression) {
|
if (this.isValueExpression) {
|
||||||
this.expressionEditDialogVisible = true;
|
this.expressionEditDialogVisible = true;
|
||||||
@@ -844,7 +864,7 @@ export default mixins(
|
|||||||
// Set focus on field
|
// Set focus on field
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (this.$refs.inputField) {
|
if (this.$refs.inputField && this.$refs.inputField.$el) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.$refs.inputField.focus();
|
this.$refs.inputField.focus();
|
||||||
}
|
}
|
||||||
@@ -871,7 +891,7 @@ export default mixins(
|
|||||||
|
|
||||||
this.$emit('textInput', parameterData);
|
this.$emit('textInput', parameterData);
|
||||||
},
|
},
|
||||||
valueChanged (value: string[] | string | number | boolean | Date | null) {
|
valueChanged (value: string[] | string | number | boolean | Date | {} | null) {
|
||||||
if (this.parameter.name === 'nodeCredentialType') {
|
if (this.parameter.name === 'nodeCredentialType') {
|
||||||
this.activeCredentialType = value as string;
|
this.activeCredentialType = value as string;
|
||||||
}
|
}
|
||||||
@@ -916,9 +936,14 @@ export default mixins(
|
|||||||
this.expressionEditDialogVisible = true;
|
this.expressionEditDialogVisible = true;
|
||||||
} else if (command === 'addExpression') {
|
} else if (command === 'addExpression') {
|
||||||
if (this.parameter.type === 'number' || this.parameter.type === 'boolean') {
|
if (this.parameter.type === 'number' || this.parameter.type === 'boolean') {
|
||||||
this.valueChanged(`={{${this.value}}}`);
|
this.valueChanged({ value: `={{${this.value}}}`, mode: this.value.mode });
|
||||||
}
|
} else if (this.isResourceLocatorParameter) {
|
||||||
else {
|
if (isResourceLocatorValue(this.value)) {
|
||||||
|
this.valueChanged({ value: `=${this.value.value}`, mode: this.value.mode });
|
||||||
|
} else {
|
||||||
|
this.valueChanged({ value: `=${this.value}`, mode: '' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.valueChanged(`=${this.value}`);
|
this.valueChanged(`=${this.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,8 +959,18 @@ export default mixins(
|
|||||||
.filter((value) => (this.parameterOptions || []).find((option) => option.value === value));
|
.filter((value) => (this.parameterOptions || []).find((option) => option.value === value));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.valueChanged(typeof value !== 'undefined' ? value : null);
|
if (this.isResourceLocatorParameter) {
|
||||||
|
this.valueChanged({ value, mode: this.value.mode });
|
||||||
|
} else {
|
||||||
|
this.valueChanged(typeof value !== 'undefined' ? value : null);
|
||||||
|
}
|
||||||
} else if (command === 'refreshOptions') {
|
} else if (command === 'refreshOptions') {
|
||||||
|
if (this.isResourceLocatorParameter) {
|
||||||
|
const resourceLocator = this.$refs.resourceLocator;
|
||||||
|
if (resourceLocator) {
|
||||||
|
(resourceLocator as Vue).$emit('refreshList');
|
||||||
|
}
|
||||||
|
}
|
||||||
this.loadRemoteParameterOptions();
|
this.loadRemoteParameterOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
:value="value"
|
:value="value"
|
||||||
:isReadOnly="false"
|
:isReadOnly="false"
|
||||||
:showOptions="true"
|
:showOptions="true"
|
||||||
|
:isValueExpression="isValueExpression"
|
||||||
@optionSelected="optionSelected"
|
@optionSelected="optionSelected"
|
||||||
@menu-expanded="onMenuExpanded"
|
@menu-expanded="onMenuExpanded"
|
||||||
/>
|
/>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
:errorHighlight="showRequiredErrors"
|
:errorHighlight="showRequiredErrors"
|
||||||
:isForCredential="true"
|
:isForCredential="true"
|
||||||
:eventSource="eventSource"
|
:eventSource="eventSource"
|
||||||
|
:isValueExpression="isValueExpression"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@textInput="valueChanged"
|
@textInput="valueChanged"
|
||||||
@@ -53,6 +55,8 @@ import ParameterInput from './ParameterInput.vue';
|
|||||||
import ParameterOptions from './ParameterOptions.vue';
|
import ParameterOptions from './ParameterOptions.vue';
|
||||||
import InputHint from './ParameterInputHint.vue';
|
import InputHint from './ParameterInputHint.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { isValueExpression } from './helpers';
|
||||||
|
import { INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'ParameterInputExpanded',
|
name: 'ParameterInputExpanded',
|
||||||
@@ -63,6 +67,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
parameter: {
|
parameter: {
|
||||||
|
type: Object as () => INodeProperties,
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
},
|
},
|
||||||
@@ -101,6 +106,9 @@ export default Vue.extend({
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
isValueExpression (): boolean {
|
||||||
|
return isValueExpression(this.parameter, this.value as string | INodeParameterResourceLocator);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFocus() {
|
onFocus() {
|
||||||
|
|||||||
@@ -13,12 +13,19 @@
|
|||||||
:value="value"
|
:value="value"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
:showOptions="displayOptions"
|
:showOptions="displayOptions"
|
||||||
|
:showExpressionSelector="showExpressionSelector"
|
||||||
@optionSelected="optionSelected"
|
@optionSelected="optionSelected"
|
||||||
@menu-expanded="onMenuExpanded"
|
@menu-expanded="onMenuExpanded"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template>
|
<template>
|
||||||
<DraggableTarget type="mapping" :disabled="parameter.noDataExpression || isReadOnly" :sticky="true" :stickyOffset="4" @drop="onDrop">
|
<draggable-target
|
||||||
|
type="mapping"
|
||||||
|
:disabled="isDropDisabled"
|
||||||
|
:sticky="true"
|
||||||
|
:stickyOffset="4"
|
||||||
|
@drop="onDrop"
|
||||||
|
>
|
||||||
<template v-slot="{ droppable, activeDrop }">
|
<template v-slot="{ droppable, activeDrop }">
|
||||||
<parameter-input
|
<parameter-input
|
||||||
ref="param"
|
ref="param"
|
||||||
@@ -33,9 +40,10 @@
|
|||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
|
@drop="onDrop"
|
||||||
inputSize="small" />
|
inputSize="small" />
|
||||||
</template>
|
</template>
|
||||||
</DraggableTarget>
|
</draggable-target>
|
||||||
<input-hint :class="$style.hint" :hint="$locale.nodeText().hint(parameter, path)" />
|
<input-hint :class="$style.hint" :hint="$locale.nodeText().hint(parameter, path)" />
|
||||||
</template>
|
</template>
|
||||||
</n8n-input-label>
|
</n8n-input-label>
|
||||||
@@ -57,12 +65,15 @@ import mixins from 'vue-typed-mixins';
|
|||||||
import { showMessage } from './mixins/showMessage';
|
import { showMessage } from './mixins/showMessage';
|
||||||
import { LOCAL_STORAGE_MAPPING_FLAG } from '@/constants';
|
import { LOCAL_STORAGE_MAPPING_FLAG } from '@/constants';
|
||||||
import { hasExpressionMapping } from './helpers';
|
import { hasExpressionMapping } from './helpers';
|
||||||
|
import { hasOnlyListMode } from './ResourceLocator/helpers';
|
||||||
|
import { INodePropertyMode } from 'n8n-workflow';
|
||||||
|
import { isResourceLocatorValue } from '@/typeGuards';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
showMessage,
|
showMessage,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
name: 'ParameterInputFull',
|
name: 'parameter-input-full',
|
||||||
components: {
|
components: {
|
||||||
ParameterInput,
|
ParameterInput,
|
||||||
InputHint,
|
InputHint,
|
||||||
@@ -88,6 +99,15 @@ export default mixins(
|
|||||||
node (): INodeUi | null {
|
node (): INodeUi | null {
|
||||||
return this.$store.getters.activeNode;
|
return this.$store.getters.activeNode;
|
||||||
},
|
},
|
||||||
|
isResourceLocator (): boolean {
|
||||||
|
return this.parameter.type === 'resourceLocator';
|
||||||
|
},
|
||||||
|
isDropDisabled (): boolean {
|
||||||
|
return this.parameter.noDataExpression || this.isReadOnly || this.isResourceLocator;
|
||||||
|
},
|
||||||
|
showExpressionSelector (): boolean {
|
||||||
|
return this.isResourceLocator ? !hasOnlyListMode(this.parameter): true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFocus() {
|
onFocus() {
|
||||||
@@ -117,7 +137,7 @@ export default mixins(
|
|||||||
this.forceShowExpression = true;
|
this.forceShowExpression = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
const prevValue = this.value;
|
const prevValue = this.isResourceLocator ? this.value.value : this.value;
|
||||||
let updatedValue: string;
|
let updatedValue: string;
|
||||||
if (typeof prevValue === 'string' && prevValue.startsWith('=') && prevValue.length > 1) {
|
if (typeof prevValue === 'string' && prevValue.startsWith('=') && prevValue.length > 1) {
|
||||||
updatedValue = `${prevValue} ${data}`;
|
updatedValue = `${prevValue} ${data}`;
|
||||||
@@ -126,11 +146,43 @@ export default mixins(
|
|||||||
updatedValue = `=${data}`;
|
updatedValue = `=${data}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameterData = {
|
|
||||||
node: this.node.name,
|
let parameterData;
|
||||||
name: this.path,
|
if (this.isResourceLocator) {
|
||||||
value: updatedValue,
|
if (!isResourceLocatorValue(this.value)) {
|
||||||
};
|
parameterData = {
|
||||||
|
node: this.node.name,
|
||||||
|
name: this.path,
|
||||||
|
value: { value: updatedValue, mode: '' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (this.value.mode === 'list' && this.parameter.modes && this.parameter.modes.length > 1) {
|
||||||
|
let mode = this.parameter.modes.find((mode: INodePropertyMode) => mode.name === 'id') || null;
|
||||||
|
if (!mode) {
|
||||||
|
mode = this.parameter.modes.filter((mode: INodePropertyMode) => mode.name !== 'list')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterData = {
|
||||||
|
node: this.node.name,
|
||||||
|
name: this.path,
|
||||||
|
value: { value: updatedValue, mode: mode ? mode.name : '' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parameterData = {
|
||||||
|
node: this.node.name,
|
||||||
|
name: this.path,
|
||||||
|
value: { value: updatedValue, mode: this.value.mode },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
parameterData = {
|
||||||
|
node: this.node.name,
|
||||||
|
name: this.path,
|
||||||
|
value: updatedValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
@visible-change="onMenuToggle"
|
@visible-change="onMenuToggle"
|
||||||
/>
|
/>
|
||||||
<n8n-radio-buttons
|
<n8n-radio-buttons
|
||||||
v-if="parameter.noDataExpression !== true"
|
v-if="parameter.noDataExpression !== true && showExpressionSelector"
|
||||||
size="small"
|
size="small"
|
||||||
:value="selectedView"
|
:value="selectedView"
|
||||||
:disabled="isReadOnly"
|
:disabled="isReadOnly"
|
||||||
@@ -25,20 +25,39 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import { isResourceLocatorValue } from '@/typeGuards';
|
||||||
|
import { NodeParameterValueType } from 'n8n-workflow';
|
||||||
|
import Vue, { PropType } from 'vue';
|
||||||
|
import { isValueExpression } from './helpers';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'ParameterOptions',
|
name: 'parameter-options',
|
||||||
props: [
|
props: {
|
||||||
'parameter',
|
parameter: {
|
||||||
'isReadOnly',
|
type: Object,
|
||||||
'value',
|
},
|
||||||
'showOptions',
|
isReadOnly: {
|
||||||
],
|
type: Boolean,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Object, String, Number, Boolean] as PropType<NodeParameterValueType>,
|
||||||
|
},
|
||||||
|
showOptions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showExpressionSelector: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isDefault (): boolean {
|
isDefault (): boolean {
|
||||||
return this.parameter.default === this.value;
|
return this.parameter.default === this.value;
|
||||||
},
|
},
|
||||||
|
isValueExpression(): boolean {
|
||||||
|
return isValueExpression(this.parameter, this.value);
|
||||||
|
},
|
||||||
shouldShowOptions (): boolean {
|
shouldShowOptions (): boolean {
|
||||||
if (this.isReadOnly === true) {
|
if (this.isReadOnly === true) {
|
||||||
return false;
|
return false;
|
||||||
@@ -61,15 +80,6 @@ export default Vue.extend({
|
|||||||
|
|
||||||
return 'fixed';
|
return 'fixed';
|
||||||
},
|
},
|
||||||
isValueExpression () {
|
|
||||||
if (this.parameter.noDataExpression === true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof this.value === 'string' && this.value.charAt(0) === '=') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
hasRemoteMethod (): boolean {
|
hasRemoteMethod (): boolean {
|
||||||
return !!this.getArgument('loadOptionsMethod') || !!this.getArgument('loadOptions');
|
return !!this.getArgument('loadOptionsMethod') || !!this.getArgument('loadOptions');
|
||||||
},
|
},
|
||||||
@@ -82,7 +92,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.hasRemoteMethod) {
|
if (this.hasRemoteMethod || (this.parameter.type === 'resourceLocator' && isResourceLocatorValue(this.value) && this.value.mode === 'list')) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: this.$locale.baseText('parameterInput.refreshList'),
|
label: this.$locale.baseText('parameterInput.refreshList'),
|
||||||
|
|||||||
@@ -0,0 +1,766 @@
|
|||||||
|
<template>
|
||||||
|
<div class="resource-locator">
|
||||||
|
<resource-locator-dropdown
|
||||||
|
:value="value ? value.value: ''"
|
||||||
|
:show="showResourceDropdown"
|
||||||
|
:filterable="isSearchable"
|
||||||
|
:filterRequired="requiresSearchFilter"
|
||||||
|
:resources="currentQueryResults"
|
||||||
|
:loading="currentQueryLoading"
|
||||||
|
:filter="searchFilter"
|
||||||
|
:hasMore="currentQueryHasMore"
|
||||||
|
:errorView="currentQueryError"
|
||||||
|
@input="onListItemSelected"
|
||||||
|
@hide="onDropdownHide"
|
||||||
|
@filter="onSearchFilter"
|
||||||
|
@loadMore="loadResourcesDebounced"
|
||||||
|
ref="dropdown"
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<div :class="$style.error">
|
||||||
|
<n8n-text color="text-dark" align="center" tag="div">
|
||||||
|
{{ $locale.baseText('resourceLocator.mode.list.error.title') }}
|
||||||
|
</n8n-text>
|
||||||
|
<n8n-text size="small" color="text-base" v-if="hasCredential">
|
||||||
|
{{ $locale.baseText('resourceLocator.mode.list.error.description.part1') }}
|
||||||
|
<a @click="openCredential">{{
|
||||||
|
$locale.baseText('resourceLocator.mode.list.error.description.part2')
|
||||||
|
}}</a>
|
||||||
|
{{ $locale.baseText('resourceLocator.mode.list.error.description.part3') }}
|
||||||
|
</n8n-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
[$style.resourceLocator]: true,
|
||||||
|
[$style.multipleModes]: hasMultipleModes,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div v-if="hasMultipleModes" :class="$style.modeSelector">
|
||||||
|
<n8n-select
|
||||||
|
:value="selectedMode"
|
||||||
|
filterable
|
||||||
|
:size="inputSize"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
@change="onModeSelected"
|
||||||
|
:placeholder="$locale.baseText('resourceLocator.modeSelector.placeholder')"
|
||||||
|
>
|
||||||
|
<n8n-option
|
||||||
|
v-for="mode in parameter.modes"
|
||||||
|
:key="mode.name"
|
||||||
|
:label="$locale.baseText(getModeLabel(mode.name)) || mode.displayName"
|
||||||
|
:value="mode.name"
|
||||||
|
:disabled="isValueExpression && mode.name === 'list'"
|
||||||
|
:title="
|
||||||
|
isValueExpression && mode.name === 'list'
|
||||||
|
? $locale.baseText('resourceLocator.mode.list.disabled.title')
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</n8n-option>
|
||||||
|
</n8n-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.inputContainer">
|
||||||
|
<draggable-target
|
||||||
|
type="mapping"
|
||||||
|
:disabled="hasOnlyListMode"
|
||||||
|
:sticky="true"
|
||||||
|
:stickyOffset="4"
|
||||||
|
@drop="onDrop"
|
||||||
|
>
|
||||||
|
<template v-slot="{ droppable, activeDrop }">
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
[$style.listModeInputContainer]: isListMode,
|
||||||
|
[$style.droppable]: droppable,
|
||||||
|
[$style.activeDrop]: activeDrop,
|
||||||
|
}"
|
||||||
|
@keydown.stop="onKeyDown"
|
||||||
|
>
|
||||||
|
<n8n-input
|
||||||
|
v-if="isValueExpression || droppable || forceShowExpression"
|
||||||
|
type="text"
|
||||||
|
:size="inputSize"
|
||||||
|
:value="activeDrop || forceShowExpression ? '' : expressionDisplayValue"
|
||||||
|
:title="displayTitle"
|
||||||
|
@keydown.stop
|
||||||
|
ref="input"
|
||||||
|
/>
|
||||||
|
<n8n-input
|
||||||
|
v-else
|
||||||
|
:class="{[$style.selectInput]: isListMode}"
|
||||||
|
:size="inputSize"
|
||||||
|
:value="valueToDisplay"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:readonly="isListMode"
|
||||||
|
:title="displayTitle"
|
||||||
|
:placeholder="inputPlaceholder"
|
||||||
|
type="text"
|
||||||
|
ref="input"
|
||||||
|
@input="onInputChange"
|
||||||
|
@focus="onInputFocus"
|
||||||
|
@blur="onInputBlur"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="isListMode"
|
||||||
|
slot="suffix"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
:class="{
|
||||||
|
['el-input__icon']: true,
|
||||||
|
['el-icon-arrow-down']: true,
|
||||||
|
[$style.selectIcon]: true,
|
||||||
|
[$style.isReverse]: showResourceDropdown,
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</n8n-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable-target>
|
||||||
|
<parameter-issues
|
||||||
|
v-if="parameterIssues && parameterIssues.length"
|
||||||
|
:issues="parameterIssues"
|
||||||
|
/>
|
||||||
|
<div v-else-if="urlValue" :class="$style.openResourceLink">
|
||||||
|
<n8n-link
|
||||||
|
theme="text"
|
||||||
|
@click.stop="openResource(urlValue)"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="external-link-alt"
|
||||||
|
:title="getLinkAlt(valueToDisplay)"
|
||||||
|
/>
|
||||||
|
</n8n-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</resource-locator-dropdown>
|
||||||
|
<parameter-input-hint v-if="infoText" class="mt-4xs" :hint="infoText" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ILoadOptions,
|
||||||
|
INode,
|
||||||
|
INodeCredentials,
|
||||||
|
INodeListSearchItems,
|
||||||
|
INodeListSearchResult,
|
||||||
|
INodeParameterResourceLocator,
|
||||||
|
INodeParameters,
|
||||||
|
INodeProperties,
|
||||||
|
INodePropertyMode,
|
||||||
|
NodeParameterValue,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
hasOnlyListMode,
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
|
import DraggableTarget from '@/components/DraggableTarget.vue';
|
||||||
|
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
||||||
|
import ParameterIssues from '@/components/ParameterIssues.vue';
|
||||||
|
import ParameterInputHint from '@/components/ParameterInputHint.vue';
|
||||||
|
import ResourceLocatorDropdown from './ResourceLocatorDropdown.vue';
|
||||||
|
import Vue, { PropType } from 'vue';
|
||||||
|
import { INodeUi, IResourceLocatorReqParams, IResourceLocatorResultExpanded } from '@/Interface';
|
||||||
|
import { debounceHelper } from '../mixins/debounce';
|
||||||
|
import stringify from 'fast-json-stable-stringify';
|
||||||
|
import { workflowHelpers } from '../mixins/workflowHelpers';
|
||||||
|
import { nodeHelpers } from '../mixins/nodeHelpers';
|
||||||
|
import { getAppNameFromNodeName } from '../helpers';
|
||||||
|
import { type } from 'os';
|
||||||
|
|
||||||
|
interface IResourceLocatorQuery {
|
||||||
|
results: INodeListSearchItems[];
|
||||||
|
nextPageToken: unknown;
|
||||||
|
error: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
||||||
|
name: 'resource-locator',
|
||||||
|
components: {
|
||||||
|
DraggableTarget,
|
||||||
|
ExpressionEdit,
|
||||||
|
ParameterIssues,
|
||||||
|
ParameterInputHint,
|
||||||
|
ResourceLocatorDropdown,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
parameter: {
|
||||||
|
type: Object as PropType<INodeProperties>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Object, String] as PropType<INodeParameterResourceLocator | NodeParameterValue | undefined>,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
inputSize: {
|
||||||
|
type: String,
|
||||||
|
default: 'small',
|
||||||
|
validator: (size) => {
|
||||||
|
return ['mini', 'small', 'medium', 'large', 'xlarge'].includes(size);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parameterIssues: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
displayTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
expressionDisplayValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
forceShowExpression: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isValueExpression: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
expressionEditDialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: Object as PropType<INode>,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
loadOptionsMethod: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showResourceDropdown: false,
|
||||||
|
searchFilter: '',
|
||||||
|
cachedResponses: {} as { [key: string]: IResourceLocatorQuery },
|
||||||
|
hasCompletedASearch: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
appName(): string {
|
||||||
|
if (!this.node) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeType = this.$store.getters['nodeTypes/getNodeType'](this.node.type);
|
||||||
|
return getAppNameFromNodeName(nodeType.displayName);
|
||||||
|
},
|
||||||
|
selectedMode(): string {
|
||||||
|
if (typeof this.value !== 'object') { // legacy mode
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.value) {
|
||||||
|
return this.parameter.modes? this.parameter.modes[0].name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.value.mode;
|
||||||
|
},
|
||||||
|
isListMode(): boolean {
|
||||||
|
return this.selectedMode === 'list';
|
||||||
|
},
|
||||||
|
hasCredential(): boolean {
|
||||||
|
const node = this.$store.getters.activeNode as INodeUi | null;
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !!(node && node.credentials && Object.keys(node.credentials).length === 1);
|
||||||
|
},
|
||||||
|
inputPlaceholder(): string {
|
||||||
|
if (this.currentMode.placeholder) {
|
||||||
|
return this.currentMode.placeholder;
|
||||||
|
}
|
||||||
|
const defaults: { [key: string]: string } = {
|
||||||
|
list: this.$locale.baseText('resourceLocator.mode.list.placeholder'),
|
||||||
|
id: this.$locale.baseText('resourceLocator.id.placeholder'),
|
||||||
|
url: this.$locale.baseText('resourceLocator.url.placeholder'),
|
||||||
|
};
|
||||||
|
|
||||||
|
return defaults[this.selectedMode] || '';
|
||||||
|
},
|
||||||
|
infoText(): string {
|
||||||
|
return this.currentMode.hint ? this.currentMode.hint : '';
|
||||||
|
},
|
||||||
|
currentMode(): INodePropertyMode {
|
||||||
|
return this.findModeByName(this.selectedMode) || ({} as INodePropertyMode);
|
||||||
|
},
|
||||||
|
hasMultipleModes(): boolean {
|
||||||
|
return this.parameter.modes && this.parameter.modes.length > 1 ? true : false;
|
||||||
|
},
|
||||||
|
hasOnlyListMode(): boolean {
|
||||||
|
return hasOnlyListMode(this.parameter);
|
||||||
|
},
|
||||||
|
valueToDisplay(): NodeParameterValue {
|
||||||
|
if (typeof this.value !== 'object') {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isListMode) {
|
||||||
|
return this.value? (this.value.cachedResultName || this.value.value) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.value ? this.value.value : '';
|
||||||
|
},
|
||||||
|
urlValue(): string | null {
|
||||||
|
if (this.isListMode && typeof this.value === 'object') {
|
||||||
|
return (this.value && this.value.cachedResultUrl) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedMode === 'url') {
|
||||||
|
if (this.isValueExpression && typeof this.expressionDisplayValue === 'string' && this.expressionDisplayValue.startsWith('http')) {
|
||||||
|
return this.expressionDisplayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.valueToDisplay === 'string' && this.valueToDisplay.startsWith('http')) {
|
||||||
|
return this.valueToDisplay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentMode.url) {
|
||||||
|
const value = this.isValueExpression? this.expressionDisplayValue : this.valueToDisplay;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const expression = this.currentMode.url.replace(/\{\{\$value\}\}/g, value);
|
||||||
|
const resolved = this.resolveExpression(expression);
|
||||||
|
|
||||||
|
return typeof resolved === 'string' ? resolved : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
currentRequestParams(): {
|
||||||
|
parameters: INodeParameters;
|
||||||
|
credentials: INodeCredentials | undefined;
|
||||||
|
filter: string;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
parameters: this.node.parameters,
|
||||||
|
credentials: this.node.credentials,
|
||||||
|
filter: this.searchFilter,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
currentRequestKey(): string {
|
||||||
|
const cacheKeys = {...this.currentRequestParams};
|
||||||
|
cacheKeys.parameters = Object.keys(this.node ? this.node.parameters : {}).reduce((accu: INodeParameters, param) => {
|
||||||
|
if (param !== this.parameter.name && this.node && this.node.parameters) {
|
||||||
|
accu[param] = this.node.parameters[param];
|
||||||
|
}
|
||||||
|
|
||||||
|
return accu;
|
||||||
|
}, {});
|
||||||
|
return stringify(cacheKeys);
|
||||||
|
},
|
||||||
|
currentResponse(): IResourceLocatorQuery | null {
|
||||||
|
return this.cachedResponses[this.currentRequestKey] || null;
|
||||||
|
},
|
||||||
|
currentQueryResults(): IResourceLocatorResultExpanded[] {
|
||||||
|
const results = this.currentResponse ? this.currentResponse.results : [];
|
||||||
|
|
||||||
|
return results.map((result: INodeListSearchItems): IResourceLocatorResultExpanded => ({
|
||||||
|
...result,
|
||||||
|
...(
|
||||||
|
(result.name && result.url)? { linkAlt: this.getLinkAlt(result.name) } : {}
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
currentQueryHasMore(): boolean {
|
||||||
|
return !!(this.currentResponse && this.currentResponse.nextPageToken);
|
||||||
|
},
|
||||||
|
currentQueryLoading(): boolean {
|
||||||
|
if (this.requiresSearchFilter && this.searchFilter === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.currentResponse) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !!(this.currentResponse && this.currentResponse.loading);
|
||||||
|
},
|
||||||
|
currentQueryError(): boolean {
|
||||||
|
return !!(this.currentResponse && this.currentResponse.error);
|
||||||
|
},
|
||||||
|
isSearchable(): boolean {
|
||||||
|
return !!this.getPropertyArgument(this.currentMode, 'searchable');
|
||||||
|
},
|
||||||
|
requiresSearchFilter(): boolean {
|
||||||
|
return !!this.getPropertyArgument(this.currentMode, 'searchFilterRequired');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentQueryError(curr: boolean, prev: boolean) {
|
||||||
|
if (this.showResourceDropdown && curr && !prev) {
|
||||||
|
const input = this.$refs.input;
|
||||||
|
if (input) {
|
||||||
|
(input as HTMLElement).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isValueExpression(newValue: boolean) {
|
||||||
|
if (newValue === true) {
|
||||||
|
this.switchFromListMode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$on('refreshList', this.refreshList);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getLinkAlt(entity: string) {
|
||||||
|
if (this.selectedMode === 'list' && entity) {
|
||||||
|
return this.$locale.baseText('resourceLocator.openSpecificResource', { interpolate: { entity, appName: this.appName } });
|
||||||
|
}
|
||||||
|
return this.$locale.baseText('resourceLocator.openResource', { interpolate: { appName: this.appName } });
|
||||||
|
},
|
||||||
|
refreshList() {
|
||||||
|
this.cachedResponses = {};
|
||||||
|
this.trackEvent('User refreshed resource locator list');
|
||||||
|
},
|
||||||
|
onKeyDown(e: MouseEvent) {
|
||||||
|
const dropdown = this.$refs.dropdown;
|
||||||
|
if (dropdown && this.showResourceDropdown && !this.isSearchable) {
|
||||||
|
(dropdown as Vue).$emit('keyDown', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openResource(url: string) {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
this.trackEvent('User clicked resource locator link');
|
||||||
|
},
|
||||||
|
getPropertyArgument(
|
||||||
|
parameter: INodePropertyMode,
|
||||||
|
argumentName: string,
|
||||||
|
): string | number | boolean | undefined {
|
||||||
|
if (parameter.typeOptions === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (parameter.typeOptions[argumentName] === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return parameter.typeOptions[argumentName];
|
||||||
|
},
|
||||||
|
openCredential(): void {
|
||||||
|
const node = this.$store.getters.activeNode as INodeUi | null;
|
||||||
|
if (!node || !node.credentials) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const credentialKey = Object.keys(node.credentials)[0];
|
||||||
|
if (!credentialKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const id = node.credentials[credentialKey].id;
|
||||||
|
this.$store.dispatch('ui/openExistingCredential', { id });
|
||||||
|
},
|
||||||
|
findModeByName(name: string): INodePropertyMode | null {
|
||||||
|
if (this.parameter.modes) {
|
||||||
|
return this.parameter.modes.find((mode: INodePropertyMode) => mode.name === name) || null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
getModeLabel(name: string): string | null {
|
||||||
|
if (name === 'id' || name === 'url' || name === 'list') {
|
||||||
|
return this.$locale.baseText(`resourceLocator.mode.${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onInputChange(value: string): void {
|
||||||
|
const params: INodeParameterResourceLocator = { value, mode: this.selectedMode };
|
||||||
|
if (this.isListMode) {
|
||||||
|
const resource = this.currentQueryResults.find((resource) => resource.value === value);
|
||||||
|
if (resource && resource.name) {
|
||||||
|
params.cachedResultName = resource.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource && resource.url) {
|
||||||
|
params.cachedResultUrl = resource.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('input', params);
|
||||||
|
},
|
||||||
|
onModeSelected(value: string): void {
|
||||||
|
if (typeof this.value !== 'object') {
|
||||||
|
this.$emit('input', { value: this.value, mode: value });
|
||||||
|
} else if (value === 'url' && this.value && this.value.cachedResultUrl) {
|
||||||
|
this.$emit('input', { mode: value, value: this.value.cachedResultUrl });
|
||||||
|
} else if (value === 'id' && this.selectedMode === 'list' && this.value && this.value.value) {
|
||||||
|
this.$emit('input', { mode: value, value: this.value.value });
|
||||||
|
} else {
|
||||||
|
this.$emit('input', { mode: value, value: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trackEvent('User changed resource locator mode', { mode: value });
|
||||||
|
},
|
||||||
|
trackEvent(event: string, params?: {[key: string]: string}): void {
|
||||||
|
this.$telemetry.track(event, {
|
||||||
|
instance_id: this.$store.getters.instanceId,
|
||||||
|
workflow_id: this.$store.getters.workflowId,
|
||||||
|
node_type: this.node && this.node.type,
|
||||||
|
resource: this.node && this.node.parameters && this.node.parameters.resource,
|
||||||
|
operation: this.node && this.node.parameters && this.node.parameters.operation,
|
||||||
|
field_name: this.parameter.name,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDrop(data: string) {
|
||||||
|
this.switchFromListMode();
|
||||||
|
this.$emit('drop', data);
|
||||||
|
},
|
||||||
|
onSearchFilter(filter: string) {
|
||||||
|
this.searchFilter = filter;
|
||||||
|
this.loadResourcesDebounced();
|
||||||
|
},
|
||||||
|
async loadInitialResources(): Promise<void> {
|
||||||
|
if (!this.currentResponse || (this.currentResponse && this.currentResponse.error)) {
|
||||||
|
this.searchFilter = '';
|
||||||
|
this.loadResources();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadResourcesDebounced() {
|
||||||
|
this.callDebounced('loadResources', { debounceTime: 1000, trailing: true });
|
||||||
|
},
|
||||||
|
setResponse(paramsKey: string, props: Partial<IResourceLocatorQuery>) {
|
||||||
|
this.cachedResponses = {
|
||||||
|
...this.cachedResponses,
|
||||||
|
[paramsKey]: { ...this.cachedResponses[paramsKey], ...props },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async loadResources() {
|
||||||
|
const params = this.currentRequestParams;
|
||||||
|
const paramsKey = this.currentRequestKey;
|
||||||
|
const cachedResponse = this.cachedResponses[paramsKey];
|
||||||
|
|
||||||
|
if (this.requiresSearchFilter && !params.filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paginationToken: unknown = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (cachedResponse) {
|
||||||
|
const nextPageToken = cachedResponse.nextPageToken;
|
||||||
|
if (nextPageToken) {
|
||||||
|
paginationToken = nextPageToken;
|
||||||
|
this.setResponse(paramsKey, { loading: true });
|
||||||
|
} else if (cachedResponse.error) {
|
||||||
|
this.setResponse(paramsKey, { error: false, loading: true });
|
||||||
|
} else {
|
||||||
|
return; // end of results
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setResponse(paramsKey, {
|
||||||
|
loading: true,
|
||||||
|
error: false,
|
||||||
|
results: [],
|
||||||
|
nextPageToken: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedNodeParameters = this.resolveParameter(params.parameters) as INodeParameters;
|
||||||
|
const loadOptionsMethod = this.getPropertyArgument(this.currentMode, 'searchListMethod') as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
const searchList = this.getPropertyArgument(this.currentMode, 'searchList') as
|
||||||
|
| ILoadOptions
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const requestParams: IResourceLocatorReqParams = {
|
||||||
|
nodeTypeAndVersion: {
|
||||||
|
name: this.node.type,
|
||||||
|
version: this.node.typeVersion,
|
||||||
|
},
|
||||||
|
path: this.path,
|
||||||
|
methodName: loadOptionsMethod,
|
||||||
|
searchList,
|
||||||
|
currentNodeParameters: resolvedNodeParameters,
|
||||||
|
credentials: this.node.credentials,
|
||||||
|
...(params.filter ? { filter: params.filter } : {}),
|
||||||
|
...(paginationToken ? { paginationToken } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response: INodeListSearchResult = await this.$store.dispatch(
|
||||||
|
'nodeTypes/getResourceLocatorResults',
|
||||||
|
requestParams,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setResponse(paramsKey, {
|
||||||
|
results: (cachedResponse ? cachedResponse.results : []).concat(response.results),
|
||||||
|
nextPageToken: response.paginationToken || null,
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.filter && !this.hasCompletedASearch) {
|
||||||
|
this.hasCompletedASearch = true;
|
||||||
|
this.trackEvent('User searched resource locator list');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.setResponse(paramsKey, {
|
||||||
|
loading: false,
|
||||||
|
error: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onInputFocus(): void {
|
||||||
|
if (!this.isListMode || this.showResourceDropdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadInitialResources();
|
||||||
|
this.showResourceDropdown = true;
|
||||||
|
},
|
||||||
|
switchFromListMode(): void {
|
||||||
|
if (this.isListMode && this.parameter.modes && this.parameter.modes.length > 1) {
|
||||||
|
let mode = this.findModeByName('id');
|
||||||
|
if (!mode) {
|
||||||
|
mode = this.parameter.modes.filter((mode) => mode.name !== 'list')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode) {
|
||||||
|
this.$emit('input', { value: ((this.value && typeof this.value === 'object')? this.value.value: ''), mode: mode.name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDropdownHide() {
|
||||||
|
if (!this.currentQueryError) {
|
||||||
|
this.showResourceDropdown = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onListItemSelected(value: string) {
|
||||||
|
this.onInputChange(value);
|
||||||
|
this.showResourceDropdown = false;
|
||||||
|
},
|
||||||
|
onInputBlur() {
|
||||||
|
if (!this.isSearchable || this.currentQueryError) {
|
||||||
|
this.showResourceDropdown = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
$--mode-selector-width: 92px;
|
||||||
|
|
||||||
|
.modeSelector {
|
||||||
|
--input-background-color: initial;
|
||||||
|
--input-font-color: initial;
|
||||||
|
--input-border-color: initial;
|
||||||
|
flex-basis: $--mode-selector-width;
|
||||||
|
|
||||||
|
input {
|
||||||
|
border-radius: var(--border-radius-base) 0 0 var(--border-radius-base);
|
||||||
|
border-right: none;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-right: var(--border-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resourceLocator {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.inputContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
div:first-child {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.multipleModes {
|
||||||
|
.inputContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-basis: calc(100% - $--mode-selector-width);
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
input {
|
||||||
|
border-radius: 0 var(--border-radius-base) var(--border-radius-base) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.droppable {
|
||||||
|
--input-border-color: var(--color-secondary-tint-1);
|
||||||
|
--input-background-color: var(--color-secondary-tint-2);
|
||||||
|
--input-border-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeDrop {
|
||||||
|
--input-border-color: var(--color-success);
|
||||||
|
--input-background-color: var(--color-success-tint-2);
|
||||||
|
--input-border-style: solid;
|
||||||
|
|
||||||
|
textarea,
|
||||||
|
input {
|
||||||
|
cursor: grabbing !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectInput input {
|
||||||
|
padding-right: 30px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectIcon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: transform 0.3s, -webkit-transform 0.3s;
|
||||||
|
-webkit-transform: rotateZ(0);
|
||||||
|
transform: rotateZ(0);
|
||||||
|
|
||||||
|
&.isReverse {
|
||||||
|
-webkit-transform: rotateZ(180deg);
|
||||||
|
transform: rotateZ(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.listModeInputContainer * {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
max-width: 170px;
|
||||||
|
word-break: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openResourceLink {
|
||||||
|
margin-left: var(--spacing-2xs);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
<template>
|
||||||
|
<n8n-popover
|
||||||
|
placement="bottom"
|
||||||
|
width="318"
|
||||||
|
:popper-class="$style.popover"
|
||||||
|
:value="show"
|
||||||
|
trigger="manual"
|
||||||
|
>
|
||||||
|
<div :class="$style.messageContainer" v-if="errorView">
|
||||||
|
<slot name="error"></slot>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.searchInput" v-if="filterable && !errorView" @keydown="onKeyDown">
|
||||||
|
<n8n-input size="medium" :value="filter" :clearable="true" @input="onFilterInput" @blur="onSearchBlur" ref="search" :placeholder="$locale.baseText('resourceLocator.search.placeholder')">
|
||||||
|
<font-awesome-icon :class="$style.searchIcon" icon="search" slot="prefix" />
|
||||||
|
</n8n-input>
|
||||||
|
</div>
|
||||||
|
<div v-if="filterRequired && !filter && !errorView && !loading" :class="$style.searchRequired">
|
||||||
|
{{ $locale.baseText('resourceLocator.mode.list.searchRequired') }}
|
||||||
|
</div>
|
||||||
|
<div :class="$style.messageContainer" v-else-if="!errorView && sortedResources.length === 0 && !loading">
|
||||||
|
{{ $locale.baseText('resourceLocator.mode.list.noResults') }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!errorView" ref="resultsContainer" :class="{[$style.container]: true, [$style.pushDownResults]: filterable}" @scroll="onResultsEnd">
|
||||||
|
<div
|
||||||
|
v-for="(result, i) in sortedResources"
|
||||||
|
:key="result.value"
|
||||||
|
:class="{ [$style.resourceItem]: true, [$style.selected]: result.value === value, [$style.hovering]: hoverIndex === i }"
|
||||||
|
@click="() => onItemClick(result.value)"
|
||||||
|
@mouseenter="() => onItemHover(i)"
|
||||||
|
@mouseleave="() => onItemHoverLeave()"
|
||||||
|
:ref="`item-${i}`"
|
||||||
|
>
|
||||||
|
<div :class="$style.resourceNameContainer">
|
||||||
|
<span>{{ result.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.urlLink">
|
||||||
|
<font-awesome-icon
|
||||||
|
v-if="showHoverUrl && result.url && hoverIndex === i"
|
||||||
|
icon="external-link-alt"
|
||||||
|
:title="result.linkAlt || $locale.baseText('resourceLocator.mode.list.openUrl')"
|
||||||
|
@click="openUrl($event, result.url)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="loading && !errorView">
|
||||||
|
<div v-for="(_, i) in 3" :key="i" :class="$style.loadingItem">
|
||||||
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot slot="reference" />
|
||||||
|
</n8n-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { IResourceLocatorResultExpanded } from '@/Interface';
|
||||||
|
import Vue, { PropType } from 'vue';
|
||||||
|
|
||||||
|
const SEARCH_BAR_HEIGHT_PX = 40;
|
||||||
|
const SCROLL_MARGIN_PX = 10;
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'resource-locator-dropdown',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
type: Array as PropType<IResourceLocatorResultExpanded[]>,
|
||||||
|
},
|
||||||
|
filterable: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
hasMore: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
errorView: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
filterRequired: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hoverIndex: 0,
|
||||||
|
showHoverUrl: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$on('keyDown', this.onKeyDown);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedResources(): IResourceLocatorResultExpanded[] {
|
||||||
|
const seen = new Set();
|
||||||
|
const { selected, notSelected } = this.resources.reduce((acc, item: IResourceLocatorResultExpanded) => {
|
||||||
|
if (seen.has(item.value)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
seen.add(item.value);
|
||||||
|
|
||||||
|
if (this.value && item.value === this.value) {
|
||||||
|
acc.selected = item;
|
||||||
|
} else {
|
||||||
|
acc.notSelected.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, { selected: null as IResourceLocatorResultExpanded | null, notSelected: [] as IResourceLocatorResultExpanded[] });
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
return [
|
||||||
|
selected,
|
||||||
|
...notSelected,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return notSelected;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openUrl(event: MouseEvent ,url: string) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
onKeyDown(e: KeyboardEvent) {
|
||||||
|
const container = this.$refs.resultsContainer as HTMLElement;
|
||||||
|
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
if (this.hoverIndex < this.sortedResources.length - 1) {
|
||||||
|
this.hoverIndex++;
|
||||||
|
|
||||||
|
const items = this.$refs[`item-${this.hoverIndex}`] as HTMLElement[];
|
||||||
|
if (container && Array.isArray(items) && items.length === 1) {
|
||||||
|
const item = items[0];
|
||||||
|
if ((item.offsetTop + item.clientHeight) > (container.scrollTop + container.offsetHeight)) {
|
||||||
|
const top = item.offsetTop - container.offsetHeight + item.clientHeight;
|
||||||
|
container.scrollTo({ top });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.key === 'ArrowUp') {
|
||||||
|
if (this.hoverIndex > 0) {
|
||||||
|
this.hoverIndex--;
|
||||||
|
|
||||||
|
const searchOffset = this.filterable ? SEARCH_BAR_HEIGHT_PX : 0;
|
||||||
|
const items = this.$refs[`item-${this.hoverIndex}`] as HTMLElement[];
|
||||||
|
if (container && Array.isArray(items) && items.length === 1) {
|
||||||
|
const item = items[0];
|
||||||
|
if (item.offsetTop <= container.scrollTop + searchOffset) {
|
||||||
|
container.scrollTo({ top: item.offsetTop - searchOffset });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.key === 'Enter') {
|
||||||
|
this.$emit('input', this.sortedResources[this.hoverIndex].value);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
onFilterInput(value: string) {
|
||||||
|
this.$emit('filter', value);
|
||||||
|
},
|
||||||
|
onSearchBlur() {
|
||||||
|
this.$emit('hide');
|
||||||
|
},
|
||||||
|
onItemClick(selected: string) {
|
||||||
|
this.$emit('input', selected);
|
||||||
|
},
|
||||||
|
onItemHover(index: number) {
|
||||||
|
this.hoverIndex = index;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.hoverIndex === index) {
|
||||||
|
this.showHoverUrl = true;
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
},
|
||||||
|
onItemHoverLeave() {
|
||||||
|
this.showHoverUrl = false;
|
||||||
|
},
|
||||||
|
onResultsEnd() {
|
||||||
|
if (this.loading || !this.hasMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = this.$refs.resultsContainer as HTMLElement;
|
||||||
|
if (container) {
|
||||||
|
const diff = container.offsetHeight - (container.scrollHeight - container.scrollTop);
|
||||||
|
if (diff > -(SCROLL_MARGIN_PX) && diff < SCROLL_MARGIN_PX) {
|
||||||
|
this.$emit('loadMore');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show(toShow) {
|
||||||
|
if (toShow) {
|
||||||
|
this.hoverIndex = 0;
|
||||||
|
this.showHoverUrl = false;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toShow && this.filterable && this.$refs.search) {
|
||||||
|
(this.$refs.search as HTMLElement).focus();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
loading() {
|
||||||
|
setTimeout(this.onResultsEnd, 0); // in case of filtering
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.popover {
|
||||||
|
padding: 0;
|
||||||
|
border: var(--border-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pushDownResults {
|
||||||
|
padding-top: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
max-height: 236px;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageContainer {
|
||||||
|
height: 236px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchInput {
|
||||||
|
border-bottom: var(--border-base);
|
||||||
|
--input-border-color: none;
|
||||||
|
--input-font-size: var(--font-size-2xs);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 316px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resourceItem {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 var(--spacing-xs);
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingItem {
|
||||||
|
padding: 10px var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
max-width: 120px;
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
max-height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hovering {
|
||||||
|
background-color: var(--color-background-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchRequired {
|
||||||
|
height: 50px;
|
||||||
|
margin-top: 40px;
|
||||||
|
padding-left: var(--spacing-xs);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--color-text-base);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.urlLink {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: var(--font-size-3xs);
|
||||||
|
color: var(--color-text-base);
|
||||||
|
margin-left: var(--spacing-2xs);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resourceNameContainer {
|
||||||
|
font-size: var(--font-size-2xs);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: inline-block;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchIcon {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const hasOnlyListMode = (parameter: INodeProperties) : boolean => {
|
||||||
|
return parameter.modes !== undefined && parameter.modes.length === 1 && parameter.modes[0].name === 'list';
|
||||||
|
};
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { CORE_NODES_CATEGORY, ERROR_TRIGGER_NODE_TYPE, MAPPING_PARAMS, TEMPLATES_NODES_FILTER } from '@/constants';
|
import { CORE_NODES_CATEGORY, ERROR_TRIGGER_NODE_TYPE, MAPPING_PARAMS, TEMPLATES_NODES_FILTER } from '@/constants';
|
||||||
import { INodeUi, ITemplatesNode } from '@/Interface';
|
import { INodeUi, ITemplatesNode } from '@/Interface';
|
||||||
|
import { isResourceLocatorValue } from '@/typeGuards';
|
||||||
import dateformat from 'dateformat';
|
import dateformat from 'dateformat';
|
||||||
import {IDataObject, INodeTypeDescription} from 'n8n-workflow';
|
import {IDataObject, INodeProperties, INodeTypeDescription, NodeParameterValueType} from 'n8n-workflow';
|
||||||
|
|
||||||
const KEYWORDS_TO_FILTER = ['API', 'OAuth1', 'OAuth2'];
|
const CRED_KEYWORDS_TO_FILTER = ['API', 'OAuth1', 'OAuth2'];
|
||||||
|
const NODE_KEYWORDS_TO_FILTER = ['Trigger'];
|
||||||
const SI_SYMBOL = ['', 'k', 'M', 'G', 'T', 'P', 'E'];
|
const SI_SYMBOL = ['', 'k', 'M', 'G', 'T', 'P', 'E'];
|
||||||
|
|
||||||
const COMMUNITY_PACKAGE_NAME_REGEX = /(@\w+\/)?n8n-nodes-(?!base\b)\b\w+/g;
|
const COMMUNITY_PACKAGE_NAME_REGEX = /(@\w+\/)?n8n-nodes-(?!base\b)\b\w+/g;
|
||||||
@@ -29,7 +31,11 @@ export function convertToHumanReadableDate (epochTime: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAppNameFromCredType(name: string) {
|
export function getAppNameFromCredType(name: string) {
|
||||||
return name.split(' ').filter((word) => !KEYWORDS_TO_FILTER.includes(word)).join(' ');
|
return name.split(' ').filter((word) => !CRED_KEYWORDS_TO_FILTER.includes(word)).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAppNameFromNodeName(name: string) {
|
||||||
|
return name.split(' ').filter((word) => !NODE_KEYWORDS_TO_FILTER.includes(word)).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStyleTokenValue(name: string): string {
|
export function getStyleTokenValue(name: string): string {
|
||||||
@@ -99,3 +105,16 @@ export function shorten(s: string, limit: number, keep: number) {
|
|||||||
export function hasExpressionMapping(value: unknown) {
|
export function hasExpressionMapping(value: unknown) {
|
||||||
return typeof value === 'string' && !!MAPPING_PARAMS.find((param) => value.includes(param));
|
return typeof value === 'string' && !!MAPPING_PARAMS.find((param) => value.includes(param));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValueExpression (parameter: INodeProperties, paramValue: NodeParameterValueType): boolean {
|
||||||
|
if (parameter.noDataExpression === true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof paramValue === 'string' && paramValue.charAt(0) === '=') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isResourceLocatorValue(paramValue) && paramValue.value && paramValue.value.toString().charAt(0) === '=') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
24
packages/editor-ui/src/components/mixins/debounce.ts
Normal file
24
packages/editor-ui/src/components/mixins/debounce.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { debounce } from 'lodash';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export const debounceHelper = Vue.extend({
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
debouncedFunctions: [] as any[], // tslint:disable-line:no-any
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async callDebounced (...inputParameters: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||||
|
const functionName = inputParameters.shift() as string;
|
||||||
|
const { trailing, debounceTime } = inputParameters.shift();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (this.debouncedFunctions[functionName] === undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.debouncedFunctions[functionName] = debounce(this[functionName], debounceTime, trailing ? { trailing } : { leading: true } );
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
await this.debouncedFunctions[functionName].apply(this, inputParameters);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { showMessage } from '@/components/mixins/showMessage';
|
import { showMessage } from '@/components/mixins/showMessage';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { debounce } from 'lodash';
|
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
@@ -8,7 +7,6 @@ export const genericHelpers = mixins(showMessage).extend({
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
loadingService: null as any | null, // tslint:disable-line:no-any
|
loadingService: null as any | null, // tslint:disable-line:no-any
|
||||||
debouncedFunctions: [] as any[], // tslint:disable-line:no-any
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -71,18 +69,5 @@ export const genericHelpers = mixins(showMessage).extend({
|
|||||||
this.loadingService = null;
|
this.loadingService = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async callDebounced (...inputParameters: any[]): Promise<void> { // tslint:disable-line:no-any
|
|
||||||
const functionName = inputParameters.shift() as string;
|
|
||||||
const { trailing, debounceTime } = inputParameters.shift();
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
if (this.debouncedFunctions[functionName] === undefined) {
|
|
||||||
// @ts-ignore
|
|
||||||
this.debouncedFunctions[functionName] = debounce(this[functionName], debounceTime, trailing ? { trailing } : { leading: true } );
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
await this.debouncedFunctions[functionName].apply(this, inputParameters);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ActionContext, Module } from 'vuex';
|
|||||||
import type {
|
import type {
|
||||||
ILoadOptions,
|
ILoadOptions,
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
|
INodeListSearchResult,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
@@ -15,9 +16,10 @@ import {
|
|||||||
getNodesInformation,
|
getNodesInformation,
|
||||||
getNodeTranslationHeaders,
|
getNodeTranslationHeaders,
|
||||||
getNodeTypes,
|
getNodeTypes,
|
||||||
|
getResourceLocatorResults,
|
||||||
} from '@/api/nodeTypes';
|
} from '@/api/nodeTypes';
|
||||||
import { omit } from '@/utils';
|
import { omit } from '@/utils';
|
||||||
import type { IRootState, INodeTypesState } from '../Interface';
|
import type { IRootState, INodeTypesState, IResourceLocatorReqParams } from '../Interface';
|
||||||
|
|
||||||
const module: Module<INodeTypesState, IRootState> = {
|
const module: Module<INodeTypesState, IRootState> = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -142,6 +144,12 @@ const module: Module<INodeTypesState, IRootState> = {
|
|||||||
) {
|
) {
|
||||||
return getNodeParameterOptions(context.rootGetters.getRestApiContext, sendData);
|
return getNodeParameterOptions(context.rootGetters.getRestApiContext, sendData);
|
||||||
},
|
},
|
||||||
|
async getResourceLocatorResults(
|
||||||
|
context: ActionContext<INodeTypesState, IRootState>,
|
||||||
|
sendData: IResourceLocatorReqParams,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
return getResourceLocatorResults(context.rootGetters.getRestApiContext, sendData);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ const module: Module<IUiState, IRootState> = {
|
|||||||
context.commit('setActiveId', { name: DELETE_USER_MODAL_KEY, id });
|
context.commit('setActiveId', { name: DELETE_USER_MODAL_KEY, id });
|
||||||
context.commit('openModal', DELETE_USER_MODAL_KEY);
|
context.commit('openModal', DELETE_USER_MODAL_KEY);
|
||||||
},
|
},
|
||||||
openExisitngCredential: async (context: ActionContext<IUiState, IRootState>, { id }: {id: string}) => {
|
openExistingCredential: async (context: ActionContext<IUiState, IRootState>, { id }: {id: string}) => {
|
||||||
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id });
|
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id });
|
||||||
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
||||||
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
|
|||||||
@@ -729,6 +729,24 @@
|
|||||||
"pushConnectionTracker.connectionLost": "Connection lost",
|
"pushConnectionTracker.connectionLost": "Connection lost",
|
||||||
"pushConnection.pollingNode.dataNotFound": "No {service} data found",
|
"pushConnection.pollingNode.dataNotFound": "No {service} data found",
|
||||||
"pushConnection.pollingNode.dataNotFound.message": "We didn’t find any data in {service} to simulate an event. Please create one in {service} and try again.",
|
"pushConnection.pollingNode.dataNotFound.message": "We didn’t find any data in {service} to simulate an event. Please create one in {service} and try again.",
|
||||||
|
"resourceLocator.id.placeholder": "Enter ID...",
|
||||||
|
"resourceLocator.mode.id": "By ID",
|
||||||
|
"resourceLocator.mode.url": "By URL",
|
||||||
|
"resourceLocator.mode.list": "From list",
|
||||||
|
"resourceLocator.mode.list.disabled.title": "Change to Fixed mode to choose From List",
|
||||||
|
"resourceLocator.mode.list.error.title": "Could not load list",
|
||||||
|
"resourceLocator.mode.list.error.description.part1": "Check that your",
|
||||||
|
"resourceLocator.mode.list.error.description.part2": "credential",
|
||||||
|
"resourceLocator.mode.list.error.description.part3": "is set up correctly",
|
||||||
|
"resourceLocator.mode.list.noResults": "No results",
|
||||||
|
"resourceLocator.mode.list.openUrl": "Open URL",
|
||||||
|
"resourceLocator.mode.list.placeholder": "Choose...",
|
||||||
|
"resourceLocator.mode.list.searchRequired": "Enter a search term to show results",
|
||||||
|
"resourceLocator.modeSelector.placeholder": "Mode...",
|
||||||
|
"resourceLocator.openSpecificResource": "Open {entity} in {appName}",
|
||||||
|
"resourceLocator.openResource": "Open in {appName}",
|
||||||
|
"resourceLocator.search.placeholder": "Search...",
|
||||||
|
"resourceLocator.url.placeholder": "Enter URL...",
|
||||||
"runData.emptyItemHint": "This is an item, but it's empty.",
|
"runData.emptyItemHint": "This is an item, but it's empty.",
|
||||||
"runData.emptyArray": "[empty array]",
|
"runData.emptyArray": "[empty array]",
|
||||||
"runData.emptyString": "[empty]",
|
"runData.emptyString": "[empty]",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
faEye,
|
faEye,
|
||||||
faExclamationTriangle,
|
faExclamationTriangle,
|
||||||
faExpand,
|
faExpand,
|
||||||
|
faExpandAlt,
|
||||||
faExternalLinkAlt,
|
faExternalLinkAlt,
|
||||||
faExchangeAlt,
|
faExchangeAlt,
|
||||||
faFile,
|
faFile,
|
||||||
@@ -154,6 +155,7 @@ addIcon(faEnvelope);
|
|||||||
addIcon(faEye);
|
addIcon(faEye);
|
||||||
addIcon(faExclamationTriangle);
|
addIcon(faExclamationTriangle);
|
||||||
addIcon(faExpand);
|
addIcon(faExpand);
|
||||||
|
addIcon(faExpandAlt);
|
||||||
addIcon(faExternalLinkAlt);
|
addIcon(faExternalLinkAlt);
|
||||||
addIcon(faExchangeAlt);
|
addIcon(faExchangeAlt);
|
||||||
addIcon(faFile);
|
addIcon(faFile);
|
||||||
|
|||||||
5
packages/editor-ui/src/typeGuards.ts
Normal file
5
packages/editor-ui/src/typeGuards.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { INodeParameterResourceLocator } from "n8n-workflow";
|
||||||
|
|
||||||
|
export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator {
|
||||||
|
return Boolean(typeof value === 'object' && value && 'mode' in value && 'mode' in value);
|
||||||
|
}
|
||||||
@@ -255,12 +255,11 @@ import {ICredentialType} from "n8n-workflow";
|
|||||||
import {EnterpriseEditionFeature} from "@/constants";
|
import {EnterpriseEditionFeature} from "@/constants";
|
||||||
import TemplateCard from "@/components/TemplateCard.vue";
|
import TemplateCard from "@/components/TemplateCard.vue";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import { debounce } from 'lodash';
|
import { debounceHelper } from '@/components/mixins/debounce';
|
||||||
import {genericHelpers} from "@/components/mixins/genericHelpers";
|
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
showMessage,
|
showMessage,
|
||||||
genericHelpers,
|
debounceHelper,
|
||||||
).extend({
|
).extend({
|
||||||
name: 'SettingsPersonalView',
|
name: 'SettingsPersonalView',
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ import '../plugins/PlusEndpointType';
|
|||||||
import { getAccountAge } from '@/modules/userHelpers';
|
import { getAccountAge } from '@/modules/userHelpers';
|
||||||
import { IUser } from 'n8n-design-system';
|
import { IUser } from 'n8n-design-system';
|
||||||
import {dataPinningEventBus} from "@/event-bus/data-pinning-event-bus";
|
import {dataPinningEventBus} from "@/event-bus/data-pinning-event-bus";
|
||||||
|
import { debounceHelper } from '@/components/mixins/debounce';
|
||||||
|
|
||||||
interface AddNodeOptions {
|
interface AddNodeOptions {
|
||||||
position?: XYPosition;
|
position?: XYPosition;
|
||||||
@@ -258,6 +259,7 @@ export default mixins(
|
|||||||
workflowHelpers,
|
workflowHelpers,
|
||||||
workflowRun,
|
workflowRun,
|
||||||
newVersions,
|
newVersions,
|
||||||
|
debounceHelper,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
name: 'NodeView',
|
name: 'NodeView',
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ import { mapGetters } from 'vuex';
|
|||||||
import { IDataObject } from 'n8n-workflow';
|
import { IDataObject } from 'n8n-workflow';
|
||||||
import { setPageTitle } from '@/components/helpers';
|
import { setPageTitle } from '@/components/helpers';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
|
import { debounceHelper } from '@/components/mixins/debounce';
|
||||||
|
|
||||||
interface ISearchEvent {
|
interface ISearchEvent {
|
||||||
search_string: string;
|
search_string: string;
|
||||||
@@ -94,7 +95,7 @@ interface ISearchEvent {
|
|||||||
wf_template_repo_session_id: number;
|
wf_template_repo_session_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default mixins(genericHelpers).extend({
|
export default mixins(genericHelpers, debounceHelper).extend({
|
||||||
name: 'TemplatesSearchView',
|
name: 'TemplatesSearchView',
|
||||||
components: {
|
components: {
|
||||||
CollectionsCarousel,
|
CollectionsCarousel,
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { IExecuteFunctions } from 'n8n-core';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
INodeListSearchResult,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeOperationError,
|
NodeOperationError,
|
||||||
@@ -10,6 +12,17 @@ import {
|
|||||||
|
|
||||||
import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from './GenericFunctions';
|
import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from './GenericFunctions';
|
||||||
|
|
||||||
|
interface AirtableBase {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AirtableTable {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class Airtable implements INodeType {
|
export class Airtable implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Airtable',
|
displayName: 'Airtable',
|
||||||
@@ -73,22 +86,94 @@ export class Airtable implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// All
|
// All
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
{
|
{
|
||||||
displayName: 'Base ID',
|
displayName: 'Base',
|
||||||
name: 'application',
|
name: 'application',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'url', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The ID of the base to access',
|
description: 'The Airtable Base in which to operate on',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://airtable.com/app12DiScdfes/tblAAAAAAAAAAAAA/viwHdfasdfeieg5p',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'https://airtable.com/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Airtable Base URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://airtable.com/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Airtable Base ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'appD3dfaeidke',
|
||||||
|
url: '=https://airtable.com/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Table ID',
|
displayName: 'Table',
|
||||||
name: 'table',
|
name: 'table',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'url', value: '' },
|
||||||
placeholder: 'Stories',
|
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The ID of the table to access',
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://airtable.com/app12DiScdfes/tblAAAAAAAAAAAAA/viwHdfasdfeieg5p',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Airtable Table URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Airtable Table ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'tbl3dirwqeidke',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -423,8 +508,15 @@ export class Airtable implements INodeType {
|
|||||||
|
|
||||||
const operation = this.getNodeParameter('operation', 0) as string;
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
|
||||||
const application = this.getNodeParameter('application', 0) as string;
|
const application = this.getNodeParameter('application', 0, undefined, {
|
||||||
const table = encodeURI(this.getNodeParameter('table', 0) as string);
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const table = encodeURI(
|
||||||
|
this.getNodeParameter('table', 0, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
let returnAll = false;
|
let returnAll = false;
|
||||||
let endpoint = '';
|
let endpoint = '';
|
||||||
@@ -493,7 +585,7 @@ export class Airtable implements INodeType {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json: { error: error.message }});
|
returnData.push({ json: { error: error.message } });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -538,7 +630,7 @@ export class Airtable implements INodeType {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({ json: { error: error.message } });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -589,14 +681,13 @@ export class Airtable implements INodeType {
|
|||||||
|
|
||||||
// We can return from here
|
// We can return from here
|
||||||
return [
|
return [
|
||||||
this.helpers.constructExecutionMetaData(
|
this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(returnData), {
|
||||||
this.helpers.returnJsonArray(returnData),
|
itemData: { item: 0 },
|
||||||
{ itemData: { item: 0 } },
|
}),
|
||||||
),
|
|
||||||
];
|
];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({ json: { error: error.message } });
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -631,7 +722,7 @@ export class Airtable implements INodeType {
|
|||||||
returnData.push(...executionData);
|
returnData.push(...executionData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({ json: { error: error.message } });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -718,7 +809,7 @@ export class Airtable implements INodeType {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({ json: { error: error.message } });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { IExecuteFunctions } from 'n8n-core';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
INodeListSearchResult,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeOperationError,
|
NodeOperationError,
|
||||||
@@ -12,6 +14,18 @@ import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
|
|||||||
|
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
interface GoogleDriveFilesItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
mimeType: string;
|
||||||
|
webViewLink: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GoogleDriveDriveItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class GoogleDrive implements INodeType {
|
export class GoogleDrive implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Google Drive',
|
displayName: 'Google Drive',
|
||||||
@@ -209,59 +223,146 @@ export class GoogleDrive implements INodeType {
|
|||||||
// file
|
// file
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// file:copy
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'ID',
|
displayName: 'File',
|
||||||
name: 'fileId',
|
name: 'fileId',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'File',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a file...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'fileSearch',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder:
|
||||||
|
'https://docs.google.com/spreadsheets/d/1-i6Vx0NN-3333eeeeeeeeee333333333/edit',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/(?:drive|docs)\\.google\\.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/(?:drive|docs)\\.google.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
|
||||||
|
errorMessage: 'Not a valid Google Drive File URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: '1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9\\-_]{2,}',
|
||||||
|
errorMessage: 'Not a valid Google Drive File ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: '=https://drive.google.com/file/d/{{$value}}/view',
|
||||||
|
},
|
||||||
|
],
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['copy'],
|
operation: ['download', 'copy', 'update', 'delete', 'share'],
|
||||||
resource: ['file'],
|
resource: ['file'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the file to copy',
|
description: 'The ID of the file',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
displayName: 'Folder',
|
||||||
|
name: 'fileId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'Folder',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a folder...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'folderSearch',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://drive.google.com/drive/folders/1Tx9WHbA3wBpPB4C_HcoZDH9WZFWYxAMU',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
|
||||||
|
errorMessage: 'Not a valid Google Drive Folder URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: '1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9\\-_]{2,}',
|
||||||
|
errorMessage: 'Not a valid Google Drive Folder ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: '=https://drive.google.com/drive/folders/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: ['delete', 'share'],
|
||||||
|
resource: ['folder'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the folder',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// file:copy
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// file/folder:delete
|
// file/folder:delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'ID',
|
|
||||||
name: 'fileId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['delete'],
|
|
||||||
resource: ['file', 'folder'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the file/folder to delete',
|
|
||||||
},
|
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// file:download
|
// file:download
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'File ID',
|
|
||||||
name: 'fileId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['download'],
|
|
||||||
resource: ['file'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the file to download',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Binary Property',
|
displayName: 'Binary Property',
|
||||||
name: 'binaryPropertyName',
|
name: 'binaryPropertyName',
|
||||||
@@ -621,20 +722,6 @@ export class GoogleDrive implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// file:share
|
// file:share
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'File ID',
|
|
||||||
name: 'fileId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['share'],
|
|
||||||
resource: ['file', 'folder'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the file or shared drive',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Permissions',
|
displayName: 'Permissions',
|
||||||
name: 'permissionsUi',
|
name: 'permissionsUi',
|
||||||
@@ -805,20 +892,6 @@ export class GoogleDrive implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// file:update
|
// file:update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'ID',
|
|
||||||
name: 'fileId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['update'],
|
|
||||||
resource: ['file'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the file to update',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Update Fields',
|
displayName: 'Update Fields',
|
||||||
name: 'updateFields',
|
name: 'updateFields',
|
||||||
@@ -1398,6 +1471,72 @@ export class GoogleDrive implements INodeType {
|
|||||||
],
|
],
|
||||||
default: 'create',
|
default: 'create',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
displayName: 'Drive',
|
||||||
|
name: 'driveId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
hint: 'The Google Drive drive to operator on',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'Drive',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Drive',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'driveSearch',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://drive.google.com/drive/folders/0AaaaaAAAAAAAaa',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
|
||||||
|
errorMessage: 'Not a valid Google Drive Drive URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
hint: 'The ID of the shared drive',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9\\-_]{2,}',
|
||||||
|
errorMessage: 'Not a valid Google Drive Drive ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: '=https://drive.google.com/drive/folders/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: ['delete', 'get', 'update'],
|
||||||
|
resource: ['drive'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the drive',
|
||||||
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// drive:create
|
// drive:create
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -1641,35 +1780,10 @@ export class GoogleDrive implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// drive:delete
|
// drive:delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Drive ID',
|
|
||||||
name: 'driveId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['delete'],
|
|
||||||
resource: ['drive'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the shared drive',
|
|
||||||
},
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// drive:get
|
// drive:get
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Drive ID',
|
|
||||||
name: 'driveId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['get'],
|
|
||||||
resource: ['drive'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the shared drive',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Options',
|
displayName: 'Options',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
@@ -1761,19 +1875,6 @@ export class GoogleDrive implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// drive:update
|
// drive:update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Drive ID',
|
|
||||||
name: 'driveId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['update'],
|
|
||||||
resource: ['drive'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the shared drive',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Update Fields',
|
displayName: 'Update Fields',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
@@ -1929,6 +2030,78 @@ export class GoogleDrive implements INodeType {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
listSearch: {
|
||||||
|
async fileSearch(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const query: string[] = [];
|
||||||
|
if (filter) {
|
||||||
|
query.push(`name contains '${filter.replace("'", "\\'")}'`);
|
||||||
|
}
|
||||||
|
query.push(`mimeType != 'application/vnd.google-apps.folder'`);
|
||||||
|
const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, {
|
||||||
|
q: query.join(' and '),
|
||||||
|
pageToken: paginationToken as string | undefined,
|
||||||
|
fields: 'nextPageToken,files(id,name,mimeType,webViewLink)',
|
||||||
|
orderBy: 'name_natural',
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
results: res.files.map((i: GoogleDriveFilesItem) => ({
|
||||||
|
name: i.name,
|
||||||
|
value: i.id,
|
||||||
|
url: i.webViewLink,
|
||||||
|
})),
|
||||||
|
paginationToken: res.nextPageToken,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async folderSearch(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const query: string[] = [];
|
||||||
|
if (filter) {
|
||||||
|
query.push(`name contains '${filter.replace("'", "\\'")}'`);
|
||||||
|
}
|
||||||
|
query.push(`mimeType = 'application/vnd.google-apps.folder'`);
|
||||||
|
const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, {
|
||||||
|
q: query.join(' and '),
|
||||||
|
pageToken: paginationToken as string | undefined,
|
||||||
|
fields: 'nextPageToken,files(id,name,mimeType,webViewLink)',
|
||||||
|
orderBy: 'name_natural',
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
results: res.files.map((i: GoogleDriveFilesItem) => ({
|
||||||
|
name: i.name,
|
||||||
|
value: i.id,
|
||||||
|
url: i.webViewLink,
|
||||||
|
})),
|
||||||
|
paginationToken: res.nextPageToken,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async driveSearch(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const res = await googleApiRequest.call(this, 'GET', '/drive/v3/drives', undefined, {
|
||||||
|
q: filter ? `name contains '${filter.replace("'", "\\'")}'` : undefined,
|
||||||
|
pageToken: paginationToken as string | undefined,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
results: res.drives.map((i: GoogleDriveDriveItem) => ({
|
||||||
|
name: i.name,
|
||||||
|
value: i.id,
|
||||||
|
})),
|
||||||
|
paginationToken: res.nextPageToken,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
@@ -1964,7 +2137,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
|
|
||||||
Object.assign(body, options);
|
Object.assign(body, options);
|
||||||
|
|
||||||
const response = await googleApiRequest.call(this, 'POST', `/drive/v3/drives`, body, { requestId: uuid() });
|
const response = await googleApiRequest.call(this, 'POST', `/drive/v3/drives`, body, {
|
||||||
|
requestId: uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
this.helpers.returnJsonArray(response),
|
this.helpers.returnJsonArray(response),
|
||||||
@@ -1978,7 +2153,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
// delete
|
// delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const driveId = this.getNodeParameter('driveId', i) as string;
|
const driveId = this.getNodeParameter('driveId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`);
|
await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`);
|
||||||
|
|
||||||
@@ -1994,13 +2171,21 @@ export class GoogleDrive implements INodeType {
|
|||||||
// get
|
// get
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const driveId = this.getNodeParameter('driveId', i) as string;
|
const driveId = this.getNodeParameter('driveId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const qs: IDataObject = {};
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
Object.assign(qs, options);
|
Object.assign(qs, options);
|
||||||
|
|
||||||
const response = await googleApiRequest.call(this, 'GET', `/drive/v3/drives/${driveId}`, {}, qs);
|
const response = await googleApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/drive/v3/drives/${driveId}`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
this.helpers.returnJsonArray(response),
|
this.helpers.returnJsonArray(response),
|
||||||
@@ -2048,13 +2233,20 @@ export class GoogleDrive implements INodeType {
|
|||||||
// update
|
// update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const driveId = this.getNodeParameter('driveId', i) as string;
|
const driveId = this.getNodeParameter('driveId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const body: IDataObject = {};
|
const body: IDataObject = {};
|
||||||
|
|
||||||
Object.assign(body, options);
|
Object.assign(body, options);
|
||||||
|
|
||||||
const response = await googleApiRequest.call(this, 'PATCH', `/drive/v3/drives/${driveId}`, body);
|
const response = await googleApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
`/drive/v3/drives/${driveId}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
this.helpers.returnJsonArray(response),
|
this.helpers.returnJsonArray(response),
|
||||||
@@ -2070,7 +2262,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
// copy
|
// copy
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
const fileId = this.getNodeParameter('fileId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const body: IDataObject = {
|
const body: IDataObject = {
|
||||||
fields: queryFields,
|
fields: queryFields,
|
||||||
@@ -2087,7 +2281,13 @@ export class GoogleDrive implements INodeType {
|
|||||||
supportsAllDrives: true,
|
supportsAllDrives: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await googleApiRequest.call(this, 'POST', `/drive/v3/files/${fileId}/copy`, body, qs);
|
const response = await googleApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/drive/v3/files/${fileId}/copy`,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
this.helpers.returnJsonArray(response),
|
this.helpers.returnJsonArray(response),
|
||||||
@@ -2100,7 +2300,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
// download
|
// download
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
const fileId = this.getNodeParameter('fileId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
@@ -2443,7 +2645,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
// file:update
|
// file:update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const id = this.getNodeParameter('fileId', i) as string;
|
const id = this.getNodeParameter('fileId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
const updateFields = this.getNodeParameter('updateFields', i, {}) as IDataObject;
|
const updateFields = this.getNodeParameter('updateFields', i, {}) as IDataObject;
|
||||||
|
|
||||||
const qs: IDataObject = {
|
const qs: IDataObject = {
|
||||||
@@ -2517,7 +2721,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
// delete
|
// delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
const fileId = this.getNodeParameter('fileId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
await googleApiRequest.call(
|
await googleApiRequest.call(
|
||||||
this,
|
this,
|
||||||
@@ -2539,7 +2745,9 @@ export class GoogleDrive implements INodeType {
|
|||||||
returnData.push(...executionData);
|
returnData.push(...executionData);
|
||||||
}
|
}
|
||||||
if (operation === 'share') {
|
if (operation === 'share') {
|
||||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
const fileId = this.getNodeParameter('fileId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject;
|
const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject;
|
||||||
|
|
||||||
@@ -2577,7 +2785,7 @@ export class GoogleDrive implements INodeType {
|
|||||||
if (resource === 'file' && operation === 'download') {
|
if (resource === 'file' && operation === 'download') {
|
||||||
items[i].json = { error: error.message };
|
items[i].json = { error: error.message };
|
||||||
} else {
|
} else {
|
||||||
returnData.push({ json: {error: error.message} });
|
returnData.push({ json: { error: error.message } });
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,23 +45,71 @@ export const attachmentOperations: INodeProperties[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const attachmentFields: INodeProperties[] = [
|
export const attachmentFields: INodeProperties[] = [
|
||||||
// ----------------------------------
|
|
||||||
// attachment:create
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Card ID',
|
displayName: 'Card ID',
|
||||||
name: 'cardId',
|
name: 'cardId',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a Card...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchCards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/c/e123456/card-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Card URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/c/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Card ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'wiIaGwqE',
|
||||||
|
url: '=https://trello.com/c/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['create'],
|
operation: ['delete', 'create', 'get', 'getAll'],
|
||||||
resource: ['attachment'],
|
resource: ['attachment'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the card to add attachment to',
|
description: 'The ID of the card',
|
||||||
},
|
},
|
||||||
|
// ----------------------------------
|
||||||
|
// attachment:create
|
||||||
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Source URL',
|
displayName: 'Source URL',
|
||||||
name: 'url',
|
name: 'url',
|
||||||
@@ -110,20 +158,6 @@ export const attachmentFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// attachment:delete
|
// attachment:delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['delete'],
|
|
||||||
resource: ['attachment'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card that attachment belongs to',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Attachment ID',
|
displayName: 'Attachment ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
@@ -142,20 +176,6 @@ export const attachmentFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// attachment:getAll
|
// attachment:getAll
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['getAll'],
|
|
||||||
resource: ['attachment'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to get attachments',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
@@ -182,20 +202,6 @@ export const attachmentFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// attachment:get
|
// attachment:get
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['get'],
|
|
||||||
resource: ['attachment'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to get attachment',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Attachment ID',
|
displayName: 'Attachment ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
|||||||
@@ -294,41 +294,73 @@ export const boardFields: INodeProperties[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// board:delete
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Board ID',
|
displayName: 'Board',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['delete'],
|
operation: ['get', 'delete', 'update'],
|
||||||
resource: ['board'],
|
resource: ['board'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the board to delete',
|
description: 'The ID of the board',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a Board...',
|
||||||
|
initType: 'board',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchBoards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/b/e123456/board-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/b/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Board URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/b/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Board ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'KdEAAdde',
|
||||||
|
url: '=https://trello.com/b/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// board:get
|
// board:get
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Board ID',
|
|
||||||
name: 'id',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['get'],
|
|
||||||
resource: ['board'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the board to get',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
@@ -363,20 +395,6 @@ export const boardFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// board:update
|
// board:update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Board ID',
|
|
||||||
name: 'id',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['update'],
|
|
||||||
resource: ['board'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the board to update',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Update Fields',
|
displayName: 'Update Fields',
|
||||||
name: 'updateFields',
|
name: 'updateFields',
|
||||||
|
|||||||
@@ -36,23 +36,72 @@ export const cardCommentOperations: INodeProperties[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const cardCommentFields: INodeProperties[] = [
|
export const cardCommentFields: INodeProperties[] = [
|
||||||
// ----------------------------------
|
|
||||||
// cardComment:create
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Card ID',
|
displayName: 'Card',
|
||||||
name: 'cardId',
|
name: 'cardId',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a Card...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchCards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/c/e123456/card-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Card URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/c/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Card ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'wiIaGwqE',
|
||||||
|
url: '=https://trello.com/c/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['create'],
|
operation: ['update', 'delete', 'create'],
|
||||||
resource: ['cardComment'],
|
resource: ['cardComment'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the card',
|
description: 'The ID of the card',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// cardComment:create
|
||||||
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Text',
|
displayName: 'Text',
|
||||||
name: 'text',
|
name: 'text',
|
||||||
@@ -71,20 +120,6 @@ export const cardCommentFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// cardComment:remove
|
// cardComment:remove
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['delete'],
|
|
||||||
resource: ['cardComment'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Comment ID',
|
displayName: 'Comment ID',
|
||||||
name: 'commentId',
|
name: 'commentId',
|
||||||
@@ -103,20 +138,6 @@ export const cardCommentFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// cardComment:update
|
// cardComment:update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['update'],
|
|
||||||
resource: ['cardComment'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to update',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Comment ID',
|
displayName: 'Comment ID',
|
||||||
name: 'commentId',
|
name: 'commentId',
|
||||||
|
|||||||
@@ -163,41 +163,72 @@ export const cardFields: INodeProperties[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// card:delete
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Card ID',
|
displayName: 'Card',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a Card...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchCards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/c/e123456/card-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Card URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/c/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Card ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'wiIaGwqE',
|
||||||
|
url: '=https://trello.com/c/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['delete'],
|
operation: ['get', 'delete', 'update'],
|
||||||
resource: ['card'],
|
resource: ['card'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the card to delete',
|
description: 'The ID of the card',
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// card:get
|
// card:get
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'id',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['get'],
|
|
||||||
resource: ['card'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to get',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
@@ -285,20 +316,6 @@ export const cardFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// card:update
|
// card:update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'id',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['update'],
|
|
||||||
resource: ['card'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to update',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Update Fields',
|
displayName: 'Update Fields',
|
||||||
name: 'updateFields',
|
name: 'updateFields',
|
||||||
|
|||||||
@@ -75,23 +75,80 @@ export const checklistOperations: INodeProperties[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const checklistFields: INodeProperties[] = [
|
export const checklistFields: INodeProperties[] = [
|
||||||
// ----------------------------------
|
|
||||||
// checklist:create
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Card ID',
|
displayName: 'Card',
|
||||||
name: 'cardId',
|
name: 'cardId',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a Card...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchCards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/c/e123456/card-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Card URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/c/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Card ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'wiIaGwqE',
|
||||||
|
url: '=https://trello.com/c/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['create'],
|
operation: [
|
||||||
|
'delete',
|
||||||
|
'create',
|
||||||
|
'getAll',
|
||||||
|
'deleteCheckItem',
|
||||||
|
'getCheckItem',
|
||||||
|
'updateCheckItem',
|
||||||
|
'completeCheckItems',
|
||||||
|
],
|
||||||
resource: ['checklist'],
|
resource: ['checklist'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the card to add checklist to',
|
description: 'The ID of the card',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// checklist:create
|
||||||
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Name',
|
displayName: 'Name',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -140,20 +197,6 @@ export const checklistFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// checklist:delete
|
// checklist:delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['delete'],
|
|
||||||
resource: ['checklist'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card that checklist belongs to',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Checklist ID',
|
displayName: 'Checklist ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
@@ -172,20 +215,6 @@ export const checklistFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// checklist:getAll
|
// checklist:getAll
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['getAll'],
|
|
||||||
resource: ['checklist'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to get checklists',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
@@ -314,20 +343,6 @@ export const checklistFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// checklist:deleteCheckItem
|
// checklist:deleteCheckItem
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['deleteCheckItem'],
|
|
||||||
resource: ['checklist'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card that checklist belongs to',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'CheckItem ID',
|
displayName: 'CheckItem ID',
|
||||||
name: 'checkItemId',
|
name: 'checkItemId',
|
||||||
@@ -346,20 +361,6 @@ export const checklistFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// checklist:getCheckItem
|
// checklist:getCheckItem
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['getCheckItem'],
|
|
||||||
resource: ['checklist'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card that checklist belongs to',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'CheckItem ID',
|
displayName: 'CheckItem ID',
|
||||||
name: 'checkItemId',
|
name: 'checkItemId',
|
||||||
@@ -400,20 +401,6 @@ export const checklistFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// checklist:updateCheckItem
|
// checklist:updateCheckItem
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['updateCheckItem'],
|
|
||||||
resource: ['checklist'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card that checklist belongs to',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'CheckItem ID',
|
displayName: 'CheckItem ID',
|
||||||
name: 'checkItemId',
|
name: 'checkItemId',
|
||||||
@@ -485,20 +472,6 @@ export const checklistFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// checklist:completedCheckItems
|
// checklist:completedCheckItems
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['completedCheckItems'],
|
|
||||||
resource: ['checklist'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card for checkItems',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export async function apiRequest(
|
|||||||
try {
|
try {
|
||||||
return await this.helpers.requestWithAuthentication.call(this, 'trelloApi', options);
|
return await this.helpers.requestWithAuthentication.call(this, 'trelloApi', options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof NodeApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,23 +63,73 @@ export const labelOperations: INodeProperties[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const labelFields: INodeProperties[] = [
|
export const labelFields: INodeProperties[] = [
|
||||||
// ----------------------------------
|
|
||||||
// label:create
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Board ID',
|
displayName: 'Board',
|
||||||
name: 'boardId',
|
name: 'boardId',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['create'],
|
operation: ['create', 'getAll'],
|
||||||
resource: ['label'],
|
resource: ['label'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the board to create the label on',
|
description: 'The ID of the board',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a Board...',
|
||||||
|
initType: 'board',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchBoards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/b/e123456/board-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/b/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Board URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/b/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Board ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'KdEAAdde',
|
||||||
|
url: '=https://trello.com/b/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// label:create
|
||||||
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Name',
|
displayName: 'Name',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -176,20 +226,6 @@ export const labelFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// label:getAll
|
// label:getAll
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Board ID',
|
|
||||||
name: 'boardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['getAll'],
|
|
||||||
resource: ['label'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the board to get label',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
@@ -253,23 +289,72 @@ export const labelFields: INodeProperties[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// label:addLabel
|
|
||||||
// ----------------------------------
|
|
||||||
{
|
{
|
||||||
displayName: 'Card ID',
|
displayName: 'Card ID',
|
||||||
name: 'cardId',
|
name: 'cardId',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Choose...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchCards',
|
||||||
|
searchFilterRequired: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://trello.com/c/e123456/card-name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]{2,})/.*',
|
||||||
|
errorMessage: 'Not a valid Trello Card URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https://trello.com/c/([a-zA-Z0-9]{2,})',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[a-zA-Z0-9]{2,}',
|
||||||
|
errorMessage: 'Not a valid Trello Card ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'wiIaGwqE',
|
||||||
|
url: '=https://trello.com/c/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['addLabel'],
|
operation: ['addLabel', 'removeLabel'],
|
||||||
resource: ['label'],
|
resource: ['label'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the card to get label',
|
description: 'The ID of the card',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// label:addLabel
|
||||||
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Label ID',
|
displayName: 'Label ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
@@ -288,20 +373,6 @@ export const labelFields: INodeProperties[] = [
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// label:removeLabel
|
// label:removeLabel
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
|
||||||
displayName: 'Card ID',
|
|
||||||
name: 'cardId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
required: true,
|
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: ['removeLabel'],
|
|
||||||
resource: ['label'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: 'The ID of the card to remove label from',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Label ID',
|
displayName: 'Label ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { IExecuteFunctions } from 'n8n-core';
|
import { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
INodeListSearchResult,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeOperationError,
|
NodeOperationError,
|
||||||
@@ -26,6 +27,17 @@ import { labelFields, labelOperations } from './LabelDescription';
|
|||||||
|
|
||||||
import { listFields, listOperations } from './ListDescription';
|
import { listFields, listOperations } from './ListDescription';
|
||||||
|
|
||||||
|
interface TrelloBoardType {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
desc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We retrieve the same fields. This is just to make it clear it's not actually
|
||||||
|
// getting boards back.
|
||||||
|
type TrelloCardType = TrelloBoardType;
|
||||||
|
|
||||||
export class Trello implements INodeType {
|
export class Trello implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Trello',
|
displayName: 'Trello',
|
||||||
@@ -115,6 +127,73 @@ export class Trello implements INodeType {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
listSearch: {
|
||||||
|
async searchBoards(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
query?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
if (!query) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Query required for Trello search');
|
||||||
|
}
|
||||||
|
const searchResults = await apiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'search',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
modelTypes: 'boards',
|
||||||
|
board_fields: 'name,url,desc',
|
||||||
|
// Enables partial word searching, only for the start of words though
|
||||||
|
partial: true,
|
||||||
|
// Seems like a good number since it isn't paginated. Default is 10.
|
||||||
|
boards_limit: 50,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
results: searchResults.boards.map((b: TrelloBoardType) => ({
|
||||||
|
name: b.name,
|
||||||
|
value: b.id,
|
||||||
|
url: b.url,
|
||||||
|
description: b.desc,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async searchCards(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
query?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
if (!query) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Query required for Trello search');
|
||||||
|
}
|
||||||
|
const searchResults = await apiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'search',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
modelTypes: 'cards',
|
||||||
|
board_fields: 'name,url,desc',
|
||||||
|
// Enables partial word searching, only for the start of words though
|
||||||
|
partial: true,
|
||||||
|
// Seems like a good number since it isn't paginated. Default is 10.
|
||||||
|
cards_limit: 50,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
results: searchResults.cards.map((b: TrelloBoardType) => ({
|
||||||
|
name: b.name,
|
||||||
|
value: b.id,
|
||||||
|
url: b.url,
|
||||||
|
description: b.desc,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
@@ -160,7 +239,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
endpoint = `boards/${id}`;
|
endpoint = `boards/${id}`;
|
||||||
} else if (operation === 'get') {
|
} else if (operation === 'get') {
|
||||||
@@ -170,7 +251,7 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i, undefined, { extractValue: true });
|
||||||
|
|
||||||
endpoint = `boards/${id}`;
|
endpoint = `boards/${id}`;
|
||||||
|
|
||||||
@@ -183,7 +264,7 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'PUT';
|
requestMethod = 'PUT';
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i, undefined, { extractValue: true });
|
||||||
|
|
||||||
endpoint = `boards/${id}`;
|
endpoint = `boards/${id}`;
|
||||||
|
|
||||||
@@ -286,7 +367,7 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i, undefined, { extractValue: true });
|
||||||
|
|
||||||
endpoint = `cards/${id}`;
|
endpoint = `cards/${id}`;
|
||||||
} else if (operation === 'get') {
|
} else if (operation === 'get') {
|
||||||
@@ -296,7 +377,7 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i, undefined, { extractValue: true });
|
||||||
|
|
||||||
endpoint = `cards/${id}`;
|
endpoint = `cards/${id}`;
|
||||||
|
|
||||||
@@ -309,7 +390,7 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'PUT';
|
requestMethod = 'PUT';
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i, undefined, { extractValue: true });
|
||||||
|
|
||||||
endpoint = `cards/${id}`;
|
endpoint = `cards/${id}`;
|
||||||
|
|
||||||
@@ -328,7 +409,9 @@ export class Trello implements INodeType {
|
|||||||
// create
|
// create
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
qs.text = this.getNodeParameter('text', i) as string;
|
qs.text = this.getNodeParameter('text', i) as string;
|
||||||
|
|
||||||
@@ -342,7 +425,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const commentId = this.getNodeParameter('commentId', i) as string;
|
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||||
|
|
||||||
@@ -354,7 +439,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'PUT';
|
requestMethod = 'PUT';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const commentId = this.getNodeParameter('commentId', i) as string;
|
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||||
|
|
||||||
@@ -473,7 +560,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'POST';
|
requestMethod = 'POST';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const url = this.getNodeParameter('url', i) as string;
|
const url = this.getNodeParameter('url', i) as string;
|
||||||
|
|
||||||
Object.assign(qs, {
|
Object.assign(qs, {
|
||||||
@@ -491,7 +581,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/attachments/${id}`;
|
endpoint = `cards/${cardId}/attachments/${id}`;
|
||||||
@@ -502,7 +595,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/attachments/${id}`;
|
endpoint = `cards/${cardId}/attachments/${id}`;
|
||||||
@@ -516,7 +612,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/attachments`;
|
endpoint = `cards/${cardId}/attachments`;
|
||||||
|
|
||||||
@@ -537,7 +635,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'POST';
|
requestMethod = 'POST';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const name = this.getNodeParameter('name', i) as string;
|
const name = this.getNodeParameter('name', i) as string;
|
||||||
|
|
||||||
Object.assign(qs, { name });
|
Object.assign(qs, { name });
|
||||||
@@ -553,7 +654,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/checklists/${id}`;
|
endpoint = `cards/${cardId}/checklists/${id}`;
|
||||||
@@ -577,7 +681,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/checklists`;
|
endpoint = `cards/${cardId}/checklists`;
|
||||||
|
|
||||||
@@ -590,7 +696,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const checkItemId = this.getNodeParameter('checkItemId', i) as string;
|
const checkItemId = this.getNodeParameter('checkItemId', i) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/checkItem/${checkItemId}`;
|
endpoint = `cards/${cardId}/checkItem/${checkItemId}`;
|
||||||
@@ -618,7 +727,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const checkItemId = this.getNodeParameter('checkItemId', i) as string;
|
const checkItemId = this.getNodeParameter('checkItemId', i) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/checkItem/${checkItemId}`;
|
endpoint = `cards/${cardId}/checkItem/${checkItemId}`;
|
||||||
@@ -629,7 +741,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'PUT';
|
requestMethod = 'PUT';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const checkItemId = this.getNodeParameter('checkItemId', i) as string;
|
const checkItemId = this.getNodeParameter('checkItemId', i) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/checkItem/${checkItemId}`;
|
endpoint = `cards/${cardId}/checkItem/${checkItemId}`;
|
||||||
@@ -643,7 +758,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
endpoint = `cards/${cardId}/checkItemStates`;
|
endpoint = `cards/${cardId}/checkItemStates`;
|
||||||
|
|
||||||
@@ -664,7 +781,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'POST';
|
requestMethod = 'POST';
|
||||||
|
|
||||||
const idBoard = this.getNodeParameter('boardId', i) as string;
|
const idBoard = this.getNodeParameter('boardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const name = this.getNodeParameter('name', i) as string;
|
const name = this.getNodeParameter('name', i) as string;
|
||||||
const color = this.getNodeParameter('color', i) as string;
|
const color = this.getNodeParameter('color', i) as string;
|
||||||
|
|
||||||
@@ -705,7 +825,9 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const idBoard = this.getNodeParameter('boardId', i) as string;
|
const idBoard = this.getNodeParameter('boardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
endpoint = `board/${idBoard}/labels`;
|
endpoint = `board/${idBoard}/labels`;
|
||||||
|
|
||||||
@@ -732,7 +854,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'POST';
|
requestMethod = 'POST';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
qs.value = id;
|
qs.value = id;
|
||||||
@@ -745,7 +870,10 @@ export class Trello implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'DELETE';
|
requestMethod = 'DELETE';
|
||||||
|
|
||||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
const cardId = this.getNodeParameter('cardId', i, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
const id = this.getNodeParameter('id', i) as string;
|
const id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
endpoint = `/cards/${cardId}/idLabels/${id}`;
|
endpoint = `/cards/${cardId}/idLabels/${id}`;
|
||||||
|
|||||||
@@ -734,7 +734,7 @@
|
|||||||
"@types/tmp": "^0.2.0",
|
"@types/tmp": "^0.2.0",
|
||||||
"@types/uuid": "^8.3.2",
|
"@types/uuid": "^8.3.2",
|
||||||
"@types/xml2js": "^0.4.3",
|
"@types/xml2js": "^0.4.3",
|
||||||
"eslint-plugin-n8n-nodes-base": "^1.9.1",
|
"eslint-plugin-n8n-nodes-base": "^1.9.3",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"jest": "^27.4.7",
|
"jest": "^27.4.7",
|
||||||
"n8n-workflow": "~0.116.0",
|
"n8n-workflow": "~0.116.0",
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import {
|
|||||||
IExecuteData,
|
IExecuteData,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
INodeParameterResourceLocator,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
IWorkflowDataProxyData,
|
IWorkflowDataProxyData,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
|
NodeParameterValueType,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowDataProxy,
|
WorkflowDataProxy,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
@@ -351,14 +353,9 @@ export class Expression {
|
|||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
executeData?: IExecuteData,
|
executeData?: IExecuteData,
|
||||||
defaultValue:
|
defaultValue: NodeParameterValueType | undefined = undefined,
|
||||||
| NodeParameterValue
|
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| undefined = undefined,
|
|
||||||
selfData = {},
|
selfData = {},
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | undefined {
|
): NodeParameterValueType | undefined {
|
||||||
if (parameterValue === undefined) {
|
if (parameterValue === undefined) {
|
||||||
// Value is not set so return the default
|
// Value is not set so return the default
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -423,7 +420,7 @@ export class Expression {
|
|||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
getParameterValue(
|
getParameterValue(
|
||||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
parameterValue: NodeParameterValueType | INodeParameterResourceLocator,
|
||||||
runExecutionData: IRunExecutionData | null,
|
runExecutionData: IRunExecutionData | null,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
@@ -435,17 +432,15 @@ export class Expression {
|
|||||||
executeData?: IExecuteData,
|
executeData?: IExecuteData,
|
||||||
returnObjectAsString = false,
|
returnObjectAsString = false,
|
||||||
selfData = {},
|
selfData = {},
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
): NodeParameterValueType {
|
||||||
// Helper function which returns true when the parameter is a complex one or array
|
// Helper function which returns true when the parameter is a complex one or array
|
||||||
const isComplexParameter = (
|
const isComplexParameter = (value: NodeParameterValueType) => {
|
||||||
value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
|
||||||
) => {
|
|
||||||
return typeof value === 'object';
|
return typeof value === 'object';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function which resolves a parameter value depending on if it is simply or not
|
// Helper function which resolves a parameter value depending on if it is simply or not
|
||||||
const resolveParameterValue = (
|
const resolveParameterValue = (
|
||||||
value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
value: NodeParameterValueType,
|
||||||
siblingParameters: INodeParameters,
|
siblingParameters: INodeParameters,
|
||||||
) => {
|
) => {
|
||||||
if (isComplexParameter(value)) {
|
if (isComplexParameter(value)) {
|
||||||
@@ -519,7 +514,10 @@ export class Expression {
|
|||||||
const returnData: INodeParameters = {};
|
const returnData: INodeParameters = {};
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const [key, value] of Object.entries(parameterValue)) {
|
for (const [key, value] of Object.entries(parameterValue)) {
|
||||||
returnData[key] = resolveParameterValue(value, parameterValue);
|
returnData[key] = resolveParameterValue(
|
||||||
|
value as NodeParameterValueType,
|
||||||
|
parameterValue as INodeParameters,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnObjectAsString && typeof returnData === 'object') {
|
if (returnObjectAsString && typeof returnData === 'object') {
|
||||||
|
|||||||
@@ -543,12 +543,13 @@ export interface IN8nRequestOperationPaginationOffset extends IN8nRequestOperati
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGetNodeParameterOptions {
|
||||||
|
extractValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IExecuteFunctions {
|
export interface IExecuteFunctions {
|
||||||
continueOnFail(): boolean;
|
continueOnFail(): boolean;
|
||||||
evaluateExpression(
|
evaluateExpression(expression: string, itemIndex: number): NodeParameterValueType;
|
||||||
expression: string,
|
|
||||||
itemIndex: number,
|
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
|
||||||
executeWorkflow(
|
executeWorkflow(
|
||||||
workflowInfo: IExecuteWorkflowInfo,
|
workflowInfo: IExecuteWorkflowInfo,
|
||||||
inputData?: INodeExecutionData[],
|
inputData?: INodeExecutionData[],
|
||||||
@@ -567,7 +568,8 @@ export interface IExecuteFunctions {
|
|||||||
parameterName: string,
|
parameterName: string,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object;
|
||||||
getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData;
|
getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
@@ -597,10 +599,7 @@ export interface IExecuteFunctions {
|
|||||||
|
|
||||||
export interface IExecuteSingleFunctions {
|
export interface IExecuteSingleFunctions {
|
||||||
continueOnFail(): boolean;
|
continueOnFail(): boolean;
|
||||||
evaluateExpression(
|
evaluateExpression(expression: string, itemIndex: number | undefined): NodeParameterValueType;
|
||||||
expression: string,
|
|
||||||
itemIndex: number | undefined,
|
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
|
||||||
getContext(type: string): IContextObject;
|
getContext(type: string): IContextObject;
|
||||||
getCredentials(type: string): Promise<ICredentialDataDecryptedObject>;
|
getCredentials(type: string): Promise<ICredentialDataDecryptedObject>;
|
||||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData;
|
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData;
|
||||||
@@ -610,7 +609,8 @@ export interface IExecuteSingleFunctions {
|
|||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getExecuteData(): IExecuteData;
|
getExecuteData(): IExecuteData;
|
||||||
@@ -659,16 +659,9 @@ export interface ILoadOptionsFunctions {
|
|||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
getCurrentNodeParameter(
|
): NodeParameterValueType | object;
|
||||||
parameterName: string,
|
getCurrentNodeParameter(parameterName: string): NodeParameterValueType | object | undefined;
|
||||||
):
|
|
||||||
| NodeParameterValue
|
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[]
|
|
||||||
| object
|
|
||||||
| undefined;
|
|
||||||
getCurrentNodeParameters(): INodeParameters | undefined;
|
getCurrentNodeParameters(): INodeParameters | undefined;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
@@ -703,7 +696,8 @@ export interface IHookFunctions {
|
|||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
||||||
getWebhookName(): string;
|
getWebhookName(): string;
|
||||||
@@ -732,7 +726,8 @@ export interface IPollFunctions {
|
|||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
@@ -765,7 +760,8 @@ export interface ITriggerFunctions {
|
|||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
@@ -793,7 +789,8 @@ export interface IWebhookFunctions {
|
|||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object;
|
||||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
getNodeWebhookUrl: (name: string) => string | undefined;
|
||||||
getParamsData(): object;
|
getParamsData(): object;
|
||||||
getQueryData(): object;
|
getQueryData(): object;
|
||||||
@@ -898,12 +895,33 @@ export interface INodeExecuteFunctions {
|
|||||||
getExecuteWebhookFunctions: IGetExecuteWebhookFunctions;
|
getExecuteWebhookFunctions: IGetExecuteWebhookFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The values a node property can have
|
|
||||||
export type NodeParameterValue = string | number | boolean | undefined | null;
|
export type NodeParameterValue = string | number | boolean | undefined | null;
|
||||||
|
|
||||||
export interface INodeParameters {
|
export type ResourceLocatorModes = 'id' | 'url' | 'list' | string;
|
||||||
|
export interface IResourceLocatorResult {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeParameterResourceLocator {
|
||||||
|
mode: ResourceLocatorModes;
|
||||||
|
value: NodeParameterValue;
|
||||||
|
cachedResultName?: string;
|
||||||
|
cachedResultUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeParameterValueType =
|
||||||
// TODO: Later also has to be possible to add multiple ones with the name name. So array has to be possible
|
// TODO: Later also has to be possible to add multiple ones with the name name. So array has to be possible
|
||||||
[key: string]: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
| NodeParameterValue
|
||||||
|
| INodeParameters
|
||||||
|
| INodeParameterResourceLocator
|
||||||
|
| NodeParameterValue[]
|
||||||
|
| INodeParameters[]
|
||||||
|
| INodeParameterResourceLocator[];
|
||||||
|
|
||||||
|
export interface INodeParameters {
|
||||||
|
[key: string]: NodeParameterValueType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodePropertyTypes =
|
export type NodePropertyTypes =
|
||||||
@@ -919,7 +937,8 @@ export type NodePropertyTypes =
|
|||||||
| 'number'
|
| 'number'
|
||||||
| 'options'
|
| 'options'
|
||||||
| 'string'
|
| 'string'
|
||||||
| 'credentialsSelect';
|
| 'credentialsSelect'
|
||||||
|
| 'resourceLocator';
|
||||||
|
|
||||||
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||||
|
|
||||||
@@ -967,7 +986,7 @@ export interface INodeProperties {
|
|||||||
name: string;
|
name: string;
|
||||||
type: NodePropertyTypes;
|
type: NodePropertyTypes;
|
||||||
typeOptions?: INodePropertyTypeOptions;
|
typeOptions?: INodePropertyTypeOptions;
|
||||||
default: NodeParameterValue | INodeParameters | INodeParameters[] | NodeParameterValue[];
|
default: NodeParameterValueType;
|
||||||
description?: string;
|
description?: string;
|
||||||
hint?: string;
|
hint?: string;
|
||||||
displayOptions?: IDisplayOptions;
|
displayOptions?: IDisplayOptions;
|
||||||
@@ -980,7 +999,56 @@ export interface INodeProperties {
|
|||||||
credentialTypes?: Array<
|
credentialTypes?: Array<
|
||||||
'extends:oAuth2Api' | 'extends:oAuth1Api' | 'has:authenticate' | 'has:genericAuth'
|
'extends:oAuth2Api' | 'extends:oAuth1Api' | 'has:authenticate' | 'has:genericAuth'
|
||||||
>;
|
>;
|
||||||
|
extractValue?: INodePropertyValueExtractor;
|
||||||
|
modes?: INodePropertyMode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyModeTypeOptions {
|
||||||
|
searchListMethod?: string; // Supported by: options
|
||||||
|
searchFilterRequired?: boolean;
|
||||||
|
searchable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyMode {
|
||||||
|
displayName: string;
|
||||||
|
name: string;
|
||||||
|
type: 'string' | 'list';
|
||||||
|
hint?: string;
|
||||||
|
validation?: Array<
|
||||||
|
INodePropertyModeValidation | { (this: IExecuteSingleFunctions, value: string): void }
|
||||||
|
>;
|
||||||
|
placeholder?: string;
|
||||||
|
url?: string;
|
||||||
|
extractValue?: INodePropertyValueExtractor;
|
||||||
|
initType?: string;
|
||||||
|
entryTypes?: {
|
||||||
|
[name: string]: {
|
||||||
|
selectable?: boolean;
|
||||||
|
hidden?: boolean;
|
||||||
|
queryable?: boolean;
|
||||||
|
data?: {
|
||||||
|
request?: IHttpRequestOptions;
|
||||||
|
output?: INodeRequestOutput;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
search?: INodePropertyRouting;
|
||||||
|
typeOptions?: INodePropertyModeTypeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyModeValidation {
|
||||||
|
type: string;
|
||||||
|
properties: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyRegexValidation extends INodePropertyModeValidation {
|
||||||
|
type: 'regex';
|
||||||
|
properties: {
|
||||||
|
regex: string;
|
||||||
|
errorMessage: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface INodePropertyOptions {
|
export interface INodePropertyOptions {
|
||||||
name: string;
|
name: string;
|
||||||
value: string | number | boolean;
|
value: string | number | boolean;
|
||||||
@@ -989,12 +1057,39 @@ export interface INodePropertyOptions {
|
|||||||
routing?: INodePropertyRouting;
|
routing?: INodePropertyRouting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INodeListSearchItems extends INodePropertyOptions {
|
||||||
|
icon?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeListSearchResult {
|
||||||
|
results: INodeListSearchItems[];
|
||||||
|
paginationToken?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface INodePropertyCollection {
|
export interface INodePropertyCollection {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
name: string;
|
name: string;
|
||||||
values: INodeProperties[];
|
values: INodeProperties[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyValueExtractorBase {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyValueExtractorRegex extends INodePropertyValueExtractorBase {
|
||||||
|
type: 'regex';
|
||||||
|
regex: string | RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodePropertyValueExtractorFunction {
|
||||||
|
(this: IExecuteSingleFunctions, value: string | NodeParameterValue):
|
||||||
|
| Promise<string | NodeParameterValue>
|
||||||
|
| (string | NodeParameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type INodePropertyValueExtractor = INodePropertyValueExtractorRegex;
|
||||||
|
|
||||||
export interface IParameterDependencies {
|
export interface IParameterDependencies {
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
}
|
}
|
||||||
@@ -1028,6 +1123,13 @@ export interface INodeType {
|
|||||||
loadOptions?: {
|
loadOptions?: {
|
||||||
[key: string]: (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
[key: string]: (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
||||||
};
|
};
|
||||||
|
listSearch?: {
|
||||||
|
[key: string]: (
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
) => Promise<INodeListSearchResult>;
|
||||||
|
};
|
||||||
credentialTest?: {
|
credentialTest?: {
|
||||||
// Contains a group of functions that test credentials.
|
// Contains a group of functions that test credentials.
|
||||||
[functionName: string]: ICredentialTestFunction;
|
[functionName: string]: ICredentialTestFunction;
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ import {
|
|||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeIssueObjectProperty,
|
INodeIssueObjectProperty,
|
||||||
INodeIssues,
|
INodeIssues,
|
||||||
|
INodeParameterResourceLocator,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
|
INodePropertyMode,
|
||||||
|
INodePropertyModeValidation,
|
||||||
|
INodePropertyRegexValidation,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeVersionedType,
|
INodeVersionedType,
|
||||||
IParameterDependencies,
|
IParameterDependencies,
|
||||||
@@ -849,7 +853,6 @@ export function getNodeParameters(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeParameters;
|
return nodeParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,6 +1128,37 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
|||||||
return nodeIssues;
|
return nodeIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Validates resource locator node parameters based on validation ruled defined in each parameter mode
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const validateResourceLocatorParameter = (
|
||||||
|
value: INodeParameterResourceLocator,
|
||||||
|
parameterMode: INodePropertyMode,
|
||||||
|
): string[] => {
|
||||||
|
const valueToValidate = value?.value?.toString() || '';
|
||||||
|
if (valueToValidate.startsWith('=')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationErrors: string[] = [];
|
||||||
|
// Each mode can have multiple validations specified
|
||||||
|
if (parameterMode.validation) {
|
||||||
|
for (const validation of parameterMode.validation) {
|
||||||
|
if (validation && (validation as INodePropertyModeValidation).type === 'regex') {
|
||||||
|
const regexValidation = validation as INodePropertyRegexValidation;
|
||||||
|
const regex = new RegExp(`^${regexValidation.properties.regex}$`);
|
||||||
|
|
||||||
|
if (!regex.test(valueToValidate)) {
|
||||||
|
validationErrors.push(regexValidation.properties.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationErrors;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an issue if the parameter is not defined
|
* Adds an issue if the parameter is not defined
|
||||||
*
|
*
|
||||||
@@ -1136,14 +1170,16 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
|||||||
export function addToIssuesIfMissing(
|
export function addToIssuesIfMissing(
|
||||||
foundIssues: INodeIssues,
|
foundIssues: INodeIssues,
|
||||||
nodeProperties: INodeProperties,
|
nodeProperties: INodeProperties,
|
||||||
value: NodeParameterValue,
|
value: NodeParameterValue | INodeParameterResourceLocator,
|
||||||
) {
|
) {
|
||||||
// TODO: Check what it really has when undefined
|
// TODO: Check what it really has when undefined
|
||||||
if (
|
if (
|
||||||
(nodeProperties.type === 'string' && (value === '' || value === undefined)) ||
|
(nodeProperties.type === 'string' && (value === '' || value === undefined)) ||
|
||||||
(nodeProperties.type === 'multiOptions' && Array.isArray(value) && value.length === 0) ||
|
(nodeProperties.type === 'multiOptions' && Array.isArray(value) && value.length === 0) ||
|
||||||
(nodeProperties.type === 'dateTime' && value === undefined) ||
|
(nodeProperties.type === 'dateTime' && value === undefined) ||
|
||||||
(nodeProperties.type === 'options' && (value === '' || value === undefined))
|
(nodeProperties.type === 'options' && (value === '' || value === undefined)) ||
|
||||||
|
(nodeProperties.type === 'resourceLocator' &&
|
||||||
|
(!value || (typeof value === 'object' && !value.value)))
|
||||||
) {
|
) {
|
||||||
// Parameter is required but empty
|
// Parameter is required but empty
|
||||||
if (foundIssues.parameters === undefined) {
|
if (foundIssues.parameters === undefined) {
|
||||||
@@ -1176,6 +1212,10 @@ export function getParameterValueByPath(
|
|||||||
return get(nodeValues, path ? `${path}.${parameterName}` : parameterName);
|
return get(nodeValues, path ? `${path}.${parameterName}` : parameterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isINodeParameterResourceLocator(value: unknown): value is INodeParameterResourceLocator {
|
||||||
|
return typeof value === 'object' && value !== null && 'value' in value && 'mode' in value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the issues with the given node-values
|
* Returns all the issues with the given node-values
|
||||||
*
|
*
|
||||||
@@ -1192,11 +1232,9 @@ export function getParameterIssues(
|
|||||||
node: INode,
|
node: INode,
|
||||||
): INodeIssues {
|
): INodeIssues {
|
||||||
const foundIssues: INodeIssues = {};
|
const foundIssues: INodeIssues = {};
|
||||||
let value;
|
|
||||||
|
|
||||||
if (nodeProperties.required === true) {
|
if (nodeProperties.required === true) {
|
||||||
if (displayParameterPath(nodeValues, nodeProperties, path, node)) {
|
if (displayParameterPath(nodeValues, nodeProperties, path, node)) {
|
||||||
value = getParameterValueByPath(nodeValues, nodeProperties.name, path);
|
const value = getParameterValueByPath(nodeValues, nodeProperties.name, path);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||||
@@ -1216,6 +1254,28 @@ export function getParameterIssues(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nodeProperties.type === 'resourceLocator') {
|
||||||
|
if (displayParameterPath(nodeValues, nodeProperties, path, node)) {
|
||||||
|
const value = getParameterValueByPath(nodeValues, nodeProperties.name, path);
|
||||||
|
if (isINodeParameterResourceLocator(value)) {
|
||||||
|
const mode = nodeProperties.modes?.find((option) => option.name === value.mode);
|
||||||
|
if (mode) {
|
||||||
|
const errors = validateResourceLocatorParameter(value, mode);
|
||||||
|
errors.forEach((error) => {
|
||||||
|
if (foundIssues.parameters === undefined) {
|
||||||
|
foundIssues.parameters = {};
|
||||||
|
}
|
||||||
|
if (foundIssues.parameters[nodeProperties.name] === undefined) {
|
||||||
|
foundIssues.parameters[nodeProperties.name] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foundIssues.parameters[nodeProperties.name].push(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there are any child parameters
|
// Check if there are any child parameters
|
||||||
if (nodeProperties.options === undefined) {
|
if (nodeProperties.options === undefined) {
|
||||||
// There are none so nothing else to check
|
// There are none so nothing else to check
|
||||||
@@ -1251,7 +1311,11 @@ export function getParameterIssues(
|
|||||||
let propertyOptions: INodePropertyCollection;
|
let propertyOptions: INodePropertyCollection;
|
||||||
for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||||
// Check if the option got set and if not skip it
|
// Check if the option got set and if not skip it
|
||||||
value = getParameterValueByPath(nodeValues, propertyOptions.name, basePath.slice(0, -1));
|
const value = getParameterValueByPath(
|
||||||
|
nodeValues,
|
||||||
|
propertyOptions.name,
|
||||||
|
basePath.slice(0, -1),
|
||||||
|
);
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
IN8nRequestOperations,
|
IN8nRequestOperations,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
|
NodeParameterValueType,
|
||||||
PostReceiveAction,
|
PostReceiveAction,
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
|
|
||||||
@@ -580,13 +581,13 @@ export class RoutingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getParameterValue(
|
getParameterValue(
|
||||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
parameterValue: NodeParameterValueType,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
executeData: IExecuteData,
|
executeData: IExecuteData,
|
||||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||||
returnObjectAsString = false,
|
returnObjectAsString = false,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | string {
|
): NodeParameterValueType {
|
||||||
if (
|
if (
|
||||||
typeof parameterValue === 'object' ||
|
typeof parameterValue === 'object' ||
|
||||||
(typeof parameterValue === 'string' && parameterValue.charAt(0) === '=')
|
(typeof parameterValue === 'string' && parameterValue.charAt(0) === '=')
|
||||||
@@ -642,8 +643,15 @@ export class RoutingNode {
|
|||||||
if (nodeProperties.routing) {
|
if (nodeProperties.routing) {
|
||||||
let parameterValue: string | undefined;
|
let parameterValue: string | undefined;
|
||||||
if (basePath + nodeProperties.name && 'type' in nodeProperties) {
|
if (basePath + nodeProperties.name && 'type' in nodeProperties) {
|
||||||
|
// Extract value if it has extractValue defined or if it's a
|
||||||
|
// resourceLocator component. Resource locators are likely to have extractors
|
||||||
|
// and we can't know if the mode has one unless we dig all the way in.
|
||||||
|
const shouldExtractValue =
|
||||||
|
nodeProperties.extractValue !== undefined || nodeProperties.type === 'resourceLocator';
|
||||||
parameterValue = executeSingleFunctions.getNodeParameter(
|
parameterValue = executeSingleFunctions.getNodeParameter(
|
||||||
basePath + nodeProperties.name,
|
basePath + nodeProperties.name,
|
||||||
|
undefined,
|
||||||
|
{ extractValue: shouldExtractValue },
|
||||||
) as string;
|
) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,6 +671,7 @@ export class RoutingNode {
|
|||||||
{ ...additionalKeys, $value: parameterValue },
|
{ ...additionalKeys, $value: parameterValue },
|
||||||
false,
|
false,
|
||||||
) as string;
|
) as string;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(returnData.options as Record<string, any>)[key] = propertyValue;
|
(returnData.options as Record<string, any>)[key] = propertyValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import {
|
|||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
IWorkflowSettings,
|
IWorkflowSettings,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
|
||||||
ObservableObject,
|
ObservableObject,
|
||||||
RoutingNode,
|
RoutingNode,
|
||||||
WebhookSetupMethodNames,
|
WebhookSetupMethodNames,
|
||||||
@@ -57,6 +56,7 @@ import {
|
|||||||
IObservableObject,
|
IObservableObject,
|
||||||
IRun,
|
IRun,
|
||||||
IRunNodeResponse,
|
IRunNodeResponse,
|
||||||
|
NodeParameterValueType,
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
|
|
||||||
function dedupe<T>(arr: T[]): T[] {
|
function dedupe<T>(arr: T[]): T[] {
|
||||||
@@ -437,10 +437,10 @@ export class Workflow {
|
|||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
renameNodeInExpressions(
|
renameNodeInExpressions(
|
||||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
parameterValue: NodeParameterValueType,
|
||||||
currentName: string,
|
currentName: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
): NodeParameterValueType {
|
||||||
if (typeof parameterValue !== 'object') {
|
if (typeof parameterValue !== 'object') {
|
||||||
// Reached the actual value
|
// Reached the actual value
|
||||||
if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
|
if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
|
||||||
@@ -503,7 +503,7 @@ export class Workflow {
|
|||||||
for (const parameterName of Object.keys(parameterValue || {})) {
|
for (const parameterName of Object.keys(parameterValue || {})) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
returnData[parameterName] = this.renameNodeInExpressions(
|
returnData[parameterName] = this.renameNodeInExpressions(
|
||||||
parameterValue![parameterName],
|
parameterValue![parameterName as keyof typeof parameterValue],
|
||||||
currentName,
|
currentName,
|
||||||
newName,
|
newName,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
IWorkflowDataProxyData,
|
IWorkflowDataProxyData,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
NodeParameterValueType,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from '.';
|
} from '.';
|
||||||
@@ -178,11 +178,7 @@ export class WorkflowDataProxy {
|
|||||||
get(target, name, receiver) {
|
get(target, name, receiver) {
|
||||||
name = name.toString();
|
name = name.toString();
|
||||||
|
|
||||||
let returnValue:
|
let returnValue: NodeParameterValueType;
|
||||||
| INodeParameters
|
|
||||||
| NodeParameterValue
|
|
||||||
| NodeParameterValue[]
|
|
||||||
| INodeParameters[];
|
|
||||||
if (name[0] === '&') {
|
if (name[0] === '&') {
|
||||||
const key = name.slice(1);
|
const key = name.slice(1);
|
||||||
if (!that.siblingParameters.hasOwnProperty(key)) {
|
if (!that.siblingParameters.hasOwnProperty(key)) {
|
||||||
|
|||||||
@@ -632,7 +632,7 @@ describe('RoutingNode', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const testData of tests) {
|
for (const testData of tests) {
|
||||||
test(testData.description, () => {
|
test(testData.description, async () => {
|
||||||
node.parameters = testData.input.nodeParameters;
|
node.parameters = testData.input.nodeParameters;
|
||||||
nodeType.description.properties = [testData.input.nodeTypeProperties];
|
nodeType.description.properties = [testData.input.nodeTypeProperties];
|
||||||
|
|
||||||
@@ -669,7 +669,7 @@ describe('RoutingNode', () => {
|
|||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = routingNode.getRequestOptionsFromParameters(
|
const result = await routingNode.getRequestOptionsFromParameters(
|
||||||
executeSingleFunctions,
|
executeSingleFunctions,
|
||||||
testData.input.nodeTypeProperties,
|
testData.input.nodeTypeProperties,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
|
|||||||
Reference in New Issue
Block a user