From 0510e17fc71b8d74ca2ee3a5544975f1bd42a9be Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 11 Aug 2022 16:59:44 +0200 Subject: [PATCH 001/167] =?UTF-8?q?=E2=9C=A8=20Implemented=20initial=20ver?= =?UTF-8?q?sion=20of=20list=20mode=20dropdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResourceLocator/ListModeDropdown.vue | 47 +++++++++++++++++++ .../ResourceLocator/ResourceLocator.vue | 41 ++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue diff --git a/packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue b/packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue new file mode 100644 index 0000000000000..b8d63b8c4c740 --- /dev/null +++ b/packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index 153b3247b1bfe..a15403abfedc6 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -55,11 +55,28 @@ @keydown.stop @focus="onFocus" @blur="onBlur" + @click.native="listModeDropDownToggle" > +
+ +
+ + +
@@ -332,4 +356,21 @@ export default mixins().extend({ .info-text { margin-top: var(--spacing-2xs); } + +.select-icon { + cursor: pointer; + font-size: 14px; + transition: transform 0.3s, -webkit-transform 0.3s; + -webkit-transform: rotateZ(0); + transform: rotateZ(0); + + &.is-reverse { + -webkit-transform: rotateZ(180deg); + transform: rotateZ(180deg); + } +} + +.list-mode-input-container * { + cursor: pointer; +} From d08e874aeb3d3a81822065873d76b7d8982e88c4 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Mon, 15 Aug 2022 12:17:37 +0200 Subject: [PATCH 002/167] =?UTF-8?q?=E2=9C=A8=20Handling=20mode=20switching?= =?UTF-8?q?=20and=20expression=20support=20in=20list=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/N8nInput/Input.vue | 3 ++ .../src/components/ParameterInputFull.vue | 6 +++- .../ResourceLocator/ResourceLocator.vue | 34 +++++++++++++++---- .../src/components/ResourceLocator/helpers.ts | 11 ++++-- .../src/plugins/i18n/locales/en.json | 7 ++-- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/packages/design-system/src/components/N8nInput/Input.vue b/packages/design-system/src/components/N8nInput/Input.vue index 88dd1c08acda4..865b29984ce46 100644 --- a/packages/design-system/src/components/N8nInput/Input.vue +++ b/packages/design-system/src/components/N8nInput/Input.vue @@ -52,6 +52,9 @@ export default Vue.extend({ disabled: { type: Boolean, }, + readonly: { + type: Boolean, + }, clearable: { type: Boolean, }, diff --git a/packages/editor-ui/src/components/ParameterInputFull.vue b/packages/editor-ui/src/components/ParameterInputFull.vue index 20219a81d9305..ec52fbe0fc2d2 100644 --- a/packages/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/editor-ui/src/components/ParameterInputFull.vue @@ -7,7 +7,7 @@ :bold="false" size="small" > - @@ -200,6 +212,7 @@ export default mixins().extend({ inputClasses (): {[c: string]: boolean} { const classes = { ...this.parameterInputClasses, + [this.$style['list-mode-input-container']]: this.selectedMode === 'list', }; if (this.resourceIssues.length) { classes['has-issues'] = true; @@ -223,6 +236,12 @@ export default mixins().extend({ this.switchFromListMode(); } }, + mode (newMode: string) { + if (this.selectedMode !== newMode) { + this.selectedMode = newMode; + this.validate(); + } + }, }, mounted () { this.selectedMode = this.mode; @@ -257,6 +276,10 @@ export default mixins().extend({ }, onModeSelected (value: string): void { this.validate(); + if (value === 'list') { + this.tempValue = ''; + this.$emit('valueChanged', { value: '', mode: 'list' }); + } this.$emit('modeChanged', { mode: value, value: this.value }); }, onDrop(data: string) { diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 76755af87ae78..31cc6a48221e7 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -676,6 +676,8 @@ "resourceLocator.mode.url": "By URL", "resourceLocator.mode.list": "From list", "resourceLocator.modeSelector.listMode.disabled.title": "Change to Fixed mode to choose From List", + "resourceLocator.listModeDropdown.error.title": "Could not load list", + "resourceLocator.listModeDropdown.error.description": "Check that your credential is set up correctly", "runData.switchToBinary.info": "This item only has", "runData.switchToBinary.binary": "binary data", "runData.linking.hint": "Link displayed input and output runs", From 65cf95edaee44de643e9ad4152058ecf3c3deef8 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 19 Aug 2022 11:07:36 +0200 Subject: [PATCH 005/167] =?UTF-8?q?=F0=9F=92=84=20Updating=20padding-right?= =?UTF-8?q?=20for=20input=20fields=20with=20suffix=20slots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/design-system/theme/src/input.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/design-system/theme/src/input.scss b/packages/design-system/theme/src/input.scss index a2ec1cb955332..335643081c885 100644 --- a/packages/design-system/theme/src/input.scss +++ b/packages/design-system/theme/src/input.scss @@ -216,7 +216,13 @@ @include mixins.m(suffix) { .el-input__inner { - padding-right: 30px; + padding-right: 0; + } + + &:hover { + .el-input__inner { + padding-right: 30px; + } } } From 52542489515ef06f0d21e9175584872328e8e9ac Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 19 Aug 2022 11:23:22 +0200 Subject: [PATCH 006/167] =?UTF-8?q?=E2=9C=A8=20Minor=20fixes=20to=20valida?= =?UTF-8?q?tion,=20mode=20switching=20logic=20and=20styling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResourceLocator/ResourceLocator.vue | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index 04fd7adab49d3..2843fc0aff718 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -254,8 +254,8 @@ export default mixins().extend({ // List mode is selected by default if it's available const listMode = this.parameter.modes.find((mode : INodePropertyMode) => mode.name === 'list'); this.selectedMode = listMode ? listMode.name : this.parameter.modes[0].name; + this.validate(); } - this.validate(); }, validate (): void { const valueToValidate = this.displayValue ? this.displayValue.toString() : this.value ? this.value.toString() : ''; @@ -279,8 +279,11 @@ export default mixins().extend({ if (value === 'list') { this.tempValue = ''; this.$emit('valueChanged', { value: '', mode: 'list' }); + this.$emit('modeChanged', { value: '', mode: value }); + } else { + this.$emit('modeChanged', { mode: value, value: this.value }); } - this.$emit('modeChanged', { mode: value, value: this.value }); + }, onDrop(data: string) { this.switchFromListMode(); @@ -301,6 +304,7 @@ export default mixins().extend({ const mode = this.parameter.modes.find(m => m.name !== 'list'); if (mode) { this.selectedMode = mode.name; + this.$emit('modeChanged', { value: this.value, mode: mode.name }); } } }, @@ -347,9 +351,6 @@ export default mixins().extend({ flex-grow: 1; } - input { - border-radius: 0 var(--border-radius-base) var(--border-radius-base) 0; - } &:hover .edit-window-button { display: inline; } @@ -361,6 +362,10 @@ export default mixins().extend({ align-items: center; flex-basis: calc(100% - var(--mode-selector-width)); flex-grow: 1; + + input { + border-radius: 0 var(--border-radius-base) var(--border-radius-base) 0; + } } } } From 31c3be1b78044cad9cee53f42c4d9e90416a46bf Mon Sep 17 00:00:00 2001 From: Mutasem Date: Wed, 24 Aug 2022 14:22:32 +0200 Subject: [PATCH 007/167] update error --- packages/nodes-base/nodes/Trello/v2/BoardDescription.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts index 91feabcae32cb..4606291220567 100644 --- a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts +++ b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts @@ -374,7 +374,7 @@ export const boardFields: INodeProperties[] = [ type: 'regex', properties: { regex: '[a-zA-Z0-9]+', - errorMessage: 'ID value cannot be empty', + errorMessage: 'Id can only be numbers and letters.', }, }, ], @@ -435,7 +435,7 @@ export const boardFields: INodeProperties[] = [ type: 'regex', properties: { regex: '[a-zA-Z0-9]+', - errorMessage: 'ID value cannot be empty', + errorMessage: 'Id can only be numbers and letters.', }, }, ], From f10202e6ed94e8281af06d7c990c545914687ad0 Mon Sep 17 00:00:00 2001 From: Mutasem Date: Wed, 24 Aug 2022 14:25:40 +0200 Subject: [PATCH 008/167] 2 or more regex --- packages/nodes-base/nodes/Trello/v2/BoardDescription.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts index 4606291220567..52e0b8c3587d0 100644 --- a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts +++ b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts @@ -373,7 +373,7 @@ export const boardFields: INodeProperties[] = [ { type: 'regex', properties: { - regex: '[a-zA-Z0-9]+', + regex: '[a-zA-Z0-9]{2,}', errorMessage: 'Id can only be numbers and letters.', }, }, @@ -434,7 +434,7 @@ export const boardFields: INodeProperties[] = [ { type: 'regex', properties: { - regex: '[a-zA-Z0-9]+', + regex: '[a-zA-Z0-9]{2,}', errorMessage: 'Id can only be numbers and letters.', }, }, From 91e0758e3f88bb4ab111e5c08cfef61b420bb49b Mon Sep 17 00:00:00 2001 From: Mutasem Date: Wed, 24 Aug 2022 14:35:55 +0200 Subject: [PATCH 009/167] update regex to be more strict --- packages/editor-ui/src/components/ResourceLocator/helpers.ts | 2 +- packages/nodes-base/nodes/Trello/v2/BoardDescription.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceLocator/helpers.ts b/packages/editor-ui/src/components/ResourceLocator/helpers.ts index da19143f77164..cdad4f58a2562 100644 --- a/packages/editor-ui/src/components/ResourceLocator/helpers.ts +++ b/packages/editor-ui/src/components/ResourceLocator/helpers.ts @@ -24,7 +24,7 @@ export const validateResourceLocatorParameter = (displayValue: string, parameter // Currently only regex validation is supported on the front-end if (validation && (validation as INodePropertyModeValidation).type === 'regex') { const regexValidation = validation as INodePropertyRegexValidation; - const regex = new RegExp(regexValidation.properties.regex); + const regex = new RegExp(`^${regexValidation.properties.regex}$`); if (!regex.test(displayValue)) { validationErrors.push(regexValidation.properties.errorMessage); diff --git a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts index 52e0b8c3587d0..99588384206cd 100644 --- a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts +++ b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts @@ -374,7 +374,7 @@ export const boardFields: INodeProperties[] = [ type: 'regex', properties: { regex: '[a-zA-Z0-9]{2,}', - errorMessage: 'Id can only be numbers and letters.', + errorMessage: 'Id must be at least 2 chars of numbers and letters', }, }, ], @@ -435,7 +435,7 @@ export const boardFields: INodeProperties[] = [ type: 'regex', properties: { regex: '[a-zA-Z0-9]{2,}', - errorMessage: 'Id can only be numbers and letters.', + errorMessage: 'Id must be at least 2 chars of numbers and letters', }, }, ], From f9a7c8ce84253e6c1b713f7a651a421d1c33f8ef Mon Sep 17 00:00:00 2001 From: Mutasem Date: Wed, 24 Aug 2022 14:43:13 +0200 Subject: [PATCH 010/167] remove expr colors --- .../src/components/ResourceLocator/ResourceLocator.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index 2843fc0aff718..ce8d064573796 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -320,6 +320,8 @@ export default mixins().extend({ .mode-selector { --input-background-color: initial; + --input-font-color: initial; + --input-border-color: initial; flex-basis: var(--mode-selector-width); input { From 6dd96f7e730e4dd20e53434eebf693b5cefd4d27 Mon Sep 17 00:00:00 2001 From: Mutasem Date: Wed, 24 Aug 2022 15:11:28 +0200 Subject: [PATCH 011/167] update hint --- .../ResourceLocator/ResourceLocator.vue | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index ce8d064573796..ee79c9c01974e 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -92,14 +92,7 @@ v-if="selectedMode === 'list' && listModeDropdownOpen" /> -
- - {{ infoText }} - -
+
@@ -113,6 +106,7 @@ import DraggableTarget from '@/components/DraggableTarget.vue'; import ExpressionEdit from '@/components/ExpressionEdit.vue'; import ParameterIssues from '@/components/ParameterIssues.vue'; import ListModeDropdown from '@/components/ResourceLocator/ListModeDropdown.vue'; +import ParameterInputHint from '@/components/ParameterInputHint.vue'; import { PropType } from 'vue'; @@ -123,6 +117,7 @@ export default mixins().extend({ ExpressionEdit, ListModeDropdown, ParameterIssues, + ParameterInputHint, }, props: { parameter: { @@ -402,10 +397,6 @@ export default mixins().extend({ } } -.info-text { - margin-top: var(--spacing-2xs); -} - .select-icon { cursor: pointer; font-size: 14px; From 936132328a748842df8e8bd190a4e0f227c5919e Mon Sep 17 00:00:00 2001 From: Valya Bullions Date: Wed, 24 Aug 2022 16:47:48 +0100 Subject: [PATCH 012/167] :construction: 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. --- packages/cli/src/Server.ts | 63 ++++- packages/cli/src/requests.d.ts | 19 ++ packages/core/src/LoadNodeListSearch.ts | 224 ++++++++++++++++++ packages/core/src/index.ts | 1 + .../nodes/Google/Drive/GoogleDrive.node.ts | 44 +++- packages/workflow/src/Interfaces.ts | 24 ++ 6 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/LoadNodeListSearch.ts diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 864aebe9111c1..afc601d710896 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -57,13 +57,20 @@ import { createHmac, randomBytes } from 'crypto'; // tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ... import { compare } from 'bcryptjs'; -import { BinaryDataManager, Credentials, LoadNodeParameterOptions, UserSettings } from 'n8n-core'; +import { + BinaryDataManager, + Credentials, + LoadNodeParameterOptions, + LoadNodeListSearch, + UserSettings, +} from 'n8n-core'; import { ICredentialType, IDataObject, INodeCredentials, INodeCredentialsDetails, + INodeListSearchResult, INodeParameters, INodePropertyOptions, INodeType, @@ -1338,6 +1345,60 @@ class App { ), ); + // Returns parameter values which normally get loaded from an external API or + // get generated dynamically + this.app.get( + `/${this.restEndpoint}/node-list-search`, + ResponseHelper.send(async (req: NodeListSearchRequest): Promise => { + const nodeTypeAndVersion = JSON.parse(req.query.nodeTypeAndVersion) as INodeTypeNameVersion; + + const { path, methodName } = req.query; + + 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, + ); + + // TODO: check filter is sent + if (methodName) { + return listSearchInstance.getOptionsViaMethodName( + methodName, + additionalData, + req.query.filter, + req.query.paginationToken, + ); + } + // // @ts-ignore + // if (req.query.listSearch) { + // return listSearchInstance.getOptionsViaRequestProperty( + // // @ts-ignore + // JSON.parse(req.query.listSearch as string), + // additionalData, + // ); + // } + + return { results: [] }; + }), + ); + // Returns all the node-types this.app.get( `/${this.restEndpoint}/node-types`, diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index 2f486a66b2bd8..2eda253e13225 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -285,6 +285,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?: unknown; + } +>; + // ---------------------------------- // /tags // ---------------------------------- diff --git a/packages/core/src/LoadNodeListSearch.ts b/packages/core/src/LoadNodeListSearch.ts new file mode 100644 index 0000000000000..c5f9b1486597e --- /dev/null +++ b/packages/core/src/LoadNodeListSearch.ts @@ -0,0 +1,224 @@ +/* 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} + * @memberof LoadNodeParameterOptions + */ + async getOptionsViaMethodName( + methodName: string, + additionalData: IWorkflowExecuteAdditionalData, + filter: string, + paginationToken?: unknown, + ): Promise { + 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); + } + + // Disable for now + // /** + // * Returns the available options via a load request informatoin + // * + // * @param {ILoadOptions} loadOptions The load options which also contain the request information + // * @param {IWorkflowExecuteAdditionalData} additionalData + // * @returns {Promise} + // * @memberof LoadNodeParameterOptions + // */ + // async getOptionsViaRequestProperty( + // loadOptions: ILoadOptions, + // additionalData: IWorkflowExecuteAdditionalData, + // ): Promise { + // const node = this.workflow.getNode(TEMP_NODE_NAME); + + // const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node!.type, node?.typeVersion); + + // if ( + // nodeType === undefined || + // !nodeType.description.requestDefaults || + // !nodeType.description.requestDefaults.baseURL + // ) { + // // This in in here for now for security reasons. + // // Background: As the full data for the request to make does get send, and the auth data + // // will then be applied, would it be possible to retrieve that data like that. By at least + // // requiring a baseURL to be defined can at least not a random server be called. + // // In the future this code has to get improved that it does not use the request information from + // // the request rather resolves it via the parameter-path and nodeType data. + // throw new Error( + // `The node-type "${ + // node!.type + // }" does not exist or does not have "requestDefaults.baseURL" defined!`, + // ); + // } + + // const mode = 'internal'; + // const runIndex = 0; + // const connectionInputData: INodeExecutionData[] = []; + // const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; + + // const routingNode = new RoutingNode( + // this.workflow, + // node!, + // connectionInputData, + // runExecutionData ?? null, + // additionalData, + // mode, + // ); + + // // Create copy of node-type with the single property we want to get the data off + // const tempNode: INodeType = { + // ...nodeType, + // ...{ + // description: { + // ...nodeType.description, + // properties: [ + // { + // displayName: '', + // type: 'string', + // name: '', + // default: '', + // routing: loadOptions.routing, + // } as INodeProperties, + // ], + // }, + // }, + // }; + + // const inputData: ITaskDataConnections = { + // main: [[{ json: {} }]], + // }; + + // const optionsData = await routingNode.runNode( + // inputData, + // runIndex, + // tempNode, + // { node: node!, source: null, data: {} }, + // NodeExecuteFunctions, + // ); + + // if (optionsData?.length === 0) { + // return []; + // } + + // if (!Array.isArray(optionsData)) { + // throw new Error('The returned data is not an array!'); + // } + + // return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[]; + // } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 11d130d02155d..6a327177ccc5c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,6 +15,7 @@ export * from './Constants'; export * from './Credentials'; export * from './Interfaces'; export * from './LoadNodeParameterOptions'; +export * from './LoadNodeListSearch'; export * from './NodeExecuteFunctions'; export * from './WorkflowExecute'; export { NodeExecuteFunctions, UserSettings }; diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index 1aeef25646471..d9b4dbdd47f8c 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -2,7 +2,10 @@ import { IExecuteFunctions } from 'n8n-core'; import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodeListSearchResult, + INodePropertyOptions, INodeType, INodeTypeDescription, NodeOperationError, @@ -13,6 +16,26 @@ import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; import { v4 as uuid } from 'uuid'; export class GoogleDrive implements INodeType { + methods = { + listSearch: { + async testListMethod( + this: ILoadOptionsFunctions, + filter: string, + paginationToken: unknown, + ): Promise { + const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, { + // TODO: escape filter + q: `name contains '${filter}'`, + pageToken: paginationToken as string, + }); + return { + // tslint:disable-next-line: no-any + results: res.files.map((i: any) => ({ name: i.name, value: i.value })), + paginationToken: res.nextPageToken, + }; + }, + }, + }; description: INodeTypeDescription = { displayName: 'Google Drive', name: 'googleDrive', @@ -248,11 +271,24 @@ export class GoogleDrive implements INodeType { // file:download // ---------------------------------- { - displayName: 'File ID', - name: 'fileId', - type: 'string', - default: '', + displayName: 'File', + name: 'fileDownloadId', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'List', + name: 'list', + type: 'list', + hint: 'List', + placeholder: 'List', + typeOptions: { + searchListMethod: 'testListMethod', + }, + }, + ], displayOptions: { show: { operation: ['download'], diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 2876ff71286d3..76af3b995a79c 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -987,6 +987,10 @@ export interface INodeProperties { modes?: INodePropertyMode[]; } +export interface INodePropertyModeTypeOptions { + searchListMethod?: string; // Supported by: options +} + export interface INodePropertyMode { displayName: string; name: string; @@ -1011,7 +1015,9 @@ export interface INodePropertyMode { }; }; search?: INodePropertyRouting; + typeOptions?: INodePropertyModeTypeOptions; } + export interface INodePropertyModeValidation { type: string; properties: {}; @@ -1033,6 +1039,18 @@ export interface INodePropertyOptions { routing?: INodePropertyRouting; } +export interface INodeListSearchItems extends INodePropertyOptions { + breadcrumb?: string[]; + icon?: string; + url?: string; + disabled?: boolean; +} + +export interface INodeListSearchResult { + results: INodeListSearchItems[]; + paginationToken?: unknown; +} + export interface INodePropertyCollection { displayName: string; name: string; @@ -1087,6 +1105,12 @@ export interface INodeType { loadOptions?: { [key: string]: (this: ILoadOptionsFunctions) => Promise; }; + listSearch?: { + [key: string]: ( + this: ILoadOptionsFunctions, + paginationToken?: unknown, + ) => Promise; + }; credentialTest?: { // Contains a group of functins that test credentials. [functionName: string]: ICredentialTestFunction; From 402e036d577ae9fa05c7cb8be3da6ca39e9ce0f6 Mon Sep 17 00:00:00 2001 From: Valya Bullions Date: Thu, 25 Aug 2022 16:22:22 +0100 Subject: [PATCH 013/167] :construction: fixed up type errors and return urls --- packages/cli/src/Server.ts | 10 +------- packages/cli/src/requests.d.ts | 4 ++-- packages/core/src/LoadNodeListSearch.ts | 2 +- .../nodes/Google/Drive/GoogleDrive.node.ts | 24 +++++++++++++------ packages/workflow/src/Interfaces.ts | 1 + 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index afc601d710896..3b668cdb0577a 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -155,6 +155,7 @@ import { resolveJwt } from './UserManagement/auth/jwt'; import { User } from './databases/entities/User'; import type { ExecutionRequest, + NodeListSearchRequest, NodeParameterOptionsRequest, OAuthRequest, TagsRequest, @@ -1377,7 +1378,6 @@ class App { currentNodeParameters, ); - // TODO: check filter is sent if (methodName) { return listSearchInstance.getOptionsViaMethodName( methodName, @@ -1386,14 +1386,6 @@ class App { req.query.paginationToken, ); } - // // @ts-ignore - // if (req.query.listSearch) { - // return listSearchInstance.getOptionsViaRequestProperty( - // // @ts-ignore - // JSON.parse(req.query.listSearch as string), - // additionalData, - // ); - // } return { results: [] }; }), diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index 2eda253e13225..a5810b6619e10 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -299,8 +299,8 @@ export type NodeListSearchRequest = AuthenticatedRequest< path: string; currentNodeParameters: string; credentials: string; - filter: string; - paginationToken?: unknown; + filter?: string; + paginationToken?: string; } >; diff --git a/packages/core/src/LoadNodeListSearch.ts b/packages/core/src/LoadNodeListSearch.ts index c5f9b1486597e..9c49c7575e4cf 100644 --- a/packages/core/src/LoadNodeListSearch.ts +++ b/packages/core/src/LoadNodeListSearch.ts @@ -103,7 +103,7 @@ export class LoadNodeListSearch { async getOptionsViaMethodName( methodName: string, additionalData: IWorkflowExecuteAdditionalData, - filter: string, + filter?: string, paginationToken?: unknown, ): Promise { const node = this.workflow.getNode(TEMP_NODE_NAME); diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index d9b4dbdd47f8c..b0fd03a184717 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -15,22 +15,32 @@ import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; import { v4 as uuid } from 'uuid'; +interface GoogleDriveFilesItem { + id: string; + name: string; + mimeType: string; + webViewLink: string; +} + export class GoogleDrive implements INodeType { methods = { listSearch: { async testListMethod( this: ILoadOptionsFunctions, - filter: string, - paginationToken: unknown, + filter?: string, + paginationToken?: unknown, ): Promise { const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, { - // TODO: escape filter - q: `name contains '${filter}'`, - pageToken: paginationToken as string, + q: filter ? `name contains '${filter.replace("'", "\\'")}'` : undefined, + pageToken: paginationToken as string | undefined, + fields: 'nextPageToken,files(id,name,mimeType,webViewLink)', }); return { - // tslint:disable-next-line: no-any - results: res.files.map((i: any) => ({ name: i.name, value: i.value })), + results: res.files.map((i: GoogleDriveFilesItem) => ({ + name: i.name, + value: i.id, + url: i.webViewLink, + })), paginationToken: res.nextPageToken, }; }, diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 76af3b995a79c..a3d00eb9bfccc 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1108,6 +1108,7 @@ export interface INodeType { listSearch?: { [key: string]: ( this: ILoadOptionsFunctions, + filter?: string, paginationToken?: unknown, ) => Promise; }; From a45d1ebfd5c82b04e1d7e9405565715c375d1b5a Mon Sep 17 00:00:00 2001 From: Mutasem Date: Thu, 25 Aug 2022 17:50:34 +0200 Subject: [PATCH 014/167] begin list impl --- packages/editor-ui/src/Interface.ts | 11 ++++ packages/editor-ui/src/api/nodeTypes.ts | 48 ++++++++++++++ .../src/components/ParameterInput.vue | 4 +- .../ResourceLocator/ListModeDropdown.vue | 47 -------------- .../ResourceLocator/ResourceLocator.vue | 63 ++++++++++++++++--- packages/editor-ui/src/modules/nodeTypes.ts | 18 +++++- .../src/plugins/i18n/locales/en.json | 2 + 7 files changed, 135 insertions(+), 58 deletions(-) delete mode 100644 packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 1d88fe5cf4dd5..355d1e0e7efea 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1072,3 +1072,14 @@ export interface ITab { align?: 'right'; tooltip?: string; } + +export interface IResourceLocatorResult { + name: string; + value: string; + url?: string; +} + +export interface IResourceLocatorResponse { + results: Array; + paginationToken?: string | number; +} diff --git a/packages/editor-ui/src/api/nodeTypes.ts b/packages/editor-ui/src/api/nodeTypes.ts index 2328db1a70f7b..6d95f820bf7cb 100644 --- a/packages/editor-ui/src/api/nodeTypes.ts +++ b/packages/editor-ui/src/api/nodeTypes.ts @@ -1,6 +1,7 @@ import { makeRestApiRequest } from './helpers'; import type { INodeTranslationHeaders, + IResourceLocatorResponse, IRestApiContext, } from '@/Interface'; import type { @@ -45,3 +46,50 @@ export async function getNodeParameterOptions( ): Promise { return makeRestApiRequest(context, 'GET', '/node-parameter-options', sendData); } + +export async function getResourceLocatorResults( + context: IRestApiContext, + sendData: { + nodeTypeAndVersion: INodeTypeNameVersion, + path: string, + methodName?: string, + loadOptions?: ILoadOptions, + currentNodeParameters: INodeParameters, + credentials?: INodeCredentials, + filter?: string, + paginationToken?: string | number, + }, +): Promise { + // return makeRestApiRequest(context, 'GET', '/node-list-search', sendData); + + const response: IResourceLocatorResponse = { + "results": [ + { + name: "File 1", + value: "file1", + url: "http://example.com/preview/file1.txt", + }, + { + name: "Folder 1", + value: "folder1", + }, + { + name: "File 2", + value: "file2", + url: "http://example.com/preview/file1.txt", + }, + { + name: "File 3", + value: "file3", + url: "http://example.com/preview/file1.txt", + }, + { + name: "File 4", + value: "file4", + url: "http://example.com/preview/file1.txt", + }, + ], + }; + return await Promise.resolve(response); +} + diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 8dbfc9231af25..5d2477b623875 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -28,6 +28,8 @@ :isReadOnly="isReadOnly" :parameterIssues="getIssues" :droppable="droppable" + :node="node" + :path="path" @valueChanged="valueChanged" @modeChanged="valueChanged" @focus="setFocus" @@ -82,7 +84,7 @@ v-else v-model="tempValue" ref="inputField" - :size="inputSize" + size="large" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" diff --git a/packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue b/packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue deleted file mode 100644 index bdf69efa0efbc..0000000000000 --- a/packages/editor-ui/src/components/ResourceLocator/ListModeDropdown.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index ee79c9c01974e..85f26a6a4a4d2 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -1,10 +1,22 @@ + + From 2271f15f83264219a27f29853cc566fee83e6817 Mon Sep 17 00:00:00 2001 From: Mutasem Date: Mon, 29 Aug 2022 13:43:42 +0200 Subject: [PATCH 018/167] add more behavior --- .../ResourceLocator/ResourceLocator.vue | 15 ++++-- .../ResourceLocatorDropdown.vue | 53 +++++++++++++++++-- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index 55945658f37e0..d6def8a272ed1 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -1,5 +1,5 @@