From e1e9a68d91a70645dab0388e5b03e7ba5921b4ef Mon Sep 17 00:00:00 2001 From: mertmit Date: Wed, 3 Aug 2022 13:57:57 +0300 Subject: [PATCH] feat(NocoDB Node): Add support v0.90.0+ (#3146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(NocoDB Node): add support for new NocoDB API * fix(NocoDB Node): fix binary row update on old NocoDB API * fix(NocoDB Node): fix getAll endpoint * feat(NocoDB Node): allow xc-token as credential * fix(NocoDB Node): get all for new api * feat(NocoDB Node): list options & change to single data apis * Moved to new format and reduced some code reuse * Added API Version to Node Settings * Improvements to remove code reuse and use bulk endpoints * Added new credentials to close off PR#2909 * Credential testing working * :zap: Improvements * :zap: Add generic authentication type to credentials * :fire: Remove credentials verification * Fixed Get All not working with manual limit * Removed json object from project / table fields * added fix from n8n-4159 * :shirt: Fix linting issue * feat: Improvements to pairedItem * refactor: Consolidate hoisted package versions (#3724) * :package: Consolidate hoisted package versions * :package: Update `package-lock.json` * :package: Update `package-lock.json` * :package: Update `package-lock.json` * refactor: Upgrade to ESLint 8 (#3722) * :arrow_up: Upgrade to ESLint 8 * :package: Update package-lock.json * :shirt: Add lint exceptions * :shirt: Add more lint exceptions * :heavy_minus_sign: Remove `tslint` from some packages * :shirt: Except init file * :package: Update `package-lock.json` * :package: Update `package-lock.json` * :shirt: Add exceptions to new lines coming from `master ` Co-authored-by: Jan Oberhauser * refactor: Format all credentials (#3720) * Apply Prettier to all credentials * Fix quotes for lint * :shirt: Remove `quotemark` rule * :shirt: Run Prettier to take over quotes * :arrow_up: Upgrade `eslint-plugin-n8n-nodes-base` * :package: Update `package-lock.json` Co-authored-by: Omar Ajoue Co-authored-by: Jan Oberhauser * fix: Fix node_type property in all events (#3759) * :arrow_up: Update package-lock.json file * fix(Mautic Node): Fix authentication issue (#3761) * Fixes mautic credential issue * removed unused imports Co-authored-by: Jan Oberhauser * fix(AWS DynamoDB Node): Fix expression attribute names (#3763) * Fix expression attribute names in getAll * fix: EAN value should be a string, not object * Removed extra code for working out what credentials are in use * fix(editor): Fix linking buttons color (#3770) * fix color of icon * center buttons * fix(editor): Restore pindata header colors (#3758) * :art: Restore `color-secondary-tint` colors * :fire: Remove typing from JS file * fix(editor): Fix sticky duplication and position bug (#3755) * fix bug when inserting sticky * center sticky on insert * export as const * refactor(editor): Move all colors to css variables (#3723) * update white color * update white color * update more whites * update color * update curr running * update text color #555 * update white color * set search bar colors * update colors * update node executing * update text colors * update light color * update theme * update theme * update overlays carousel * update theme vars * add dark theme tokens * update text * update table colors * fix conflict * update colors * feat(Metabase Node): Add Metabase Node (#3033) * Boilerplate with new node's version for metabse * Metabases MVP features * Added new credential for metabse, added custom auth for metabase * Fixed bug with one enpoint not working * Clean up code * Uniformised the renovate token * Made two example of responses for review * Fixed lint issues * Feature add datasources * Changed output from databases * Changed questions data output * Fixed issue when testing credentials with new node format * Add the possibility to get raw data * Removed handle for the metabase meta results, changed export's name * Add binary extraction for the result data * Fixed binary download issue * :zap: Add preAuthentication method to credentials * Revert "Added new credential for metabse, added custom auth for metabase" This reverts commit 5f1b7607adb85d6ec897b184853bdfdbae77df6d. * Revert "Added new credential for metabse, added custom auth for metabase" This reverts commit 5f1b7607adb85d6ec897b184853bdfdbae77df6d. * Added preAuth and fixed autfixable linting rules * Fixed linting errors * Linting fixes * Remove / at the end of url, and add placeholder for cred url * Make export to Json retun only json and no binary * Fix lint issues * Add action and exception for lint rule * Remove unnecessary credential file * :zap: Simplify and cleanup Co-authored-by: ricardo Co-authored-by: Omar Ajoue Co-authored-by: Jan Oberhauser * fix(editor): Fix spaces bug (#3774) * refactor(editor): Change welcome sticky content (#3769) * Updated Welcome sticky content * Updated welcome sticky thumbnail image Image was swapped out, used exact same file name + sizing so did not require code changes. * Replaced welcome sticky thumbnail image * fix(Fix Rocketchat Node): Fix authentication issue (#3778) * Add suggested VSCode settings (#3783) * :zap: Add suggested settings * :fire: Remove app-level setting * :art: Update indentation * fix(core): Add windows support to import:credentials --separate (#3589) * feat(Item List Node): Add operation for creating array from input items (#3149) * :hammer: create array operation * :hammer: removed semicolumn * :hammer: updated UI * :zap: display option fix * :zap: aggregate operation description update, default aggregate item * refactor: Add Onboarding call prompts (#3682) * ✨ Implemented initial onboarding call prompt logic * ✨ Added onboarding call prompt feature environment variable * ✨ Implemented onboarding session signup modal * 📈 Added initial telemetry for the onboarding call prompt * ✔️ Fixing linter error in server.ts * 💄 Updating onboaring call prompt and modal wording and styling * ✨ Implemented initial version of fake doors feature * ✨ Added parameters to onboarding call prompt request * ✨ Finished implementing fake doors in settings * 🔨 Updating onboarding call prompt fetching logic (fetching before timeout starts) * 👌 Updating onboarding call prompt and fake door components based on the front-end review feedback * ✨ Updated fake doors so they support UI location specification. Added credentials UI fake doors. * ⚡ Added checkbox to the signup form, improved N8NCheckbox formatting to better handle overflow * 💄 Moving seignup checkbox label text to i18n file, updating checkbox component css to force text wrap * ✨ Update API calls to work with the new workflow request and response formats * 👌 Updating fake door front-end based on the review feedback * 👌 Updating onboarding call prompt and fake doors UI based in the product feedback * ✨ Updated onboarding call prompts front-end to work with new endpoints and added new telemetry events * 🐛 Fixing onboarding call prompts not appearing in first user sessions * ⚡️ add createdAt to PublicUser * 👌 Updating onboarding call prompts front-end to work with the latest back-end and addressing latest product review * ✨ Improving error handling when submitting user emails on signup * 💄 Updating info text on Logging feature page * 💄 Updating first onboarding call prompt timeout to 5 minutes * 💄 Fixing `N8nCheckbox` component font overflow Co-authored-by: Ben Hesseldieck * feat(Kafka Trigger Node): Add additional options (#3600) * :hammer: additional options to kafka trigger * :zap: option for maxInFlightRequests * :zap: Small change Co-authored-by: ricardo * fix(editor): Fix pin data in executions when pinData is null. (#3787) * :arrow_up: Update package-lock.json file * :bookmark: Release n8n-workflow@0.110.0 * :arrow_up: Set n8n-workflow@0.110.0 on n8n-core * :bookmark: Release n8n-core@0.128.0 * :arrow_up: Set n8n-core@0.128.0 and n8n-workflow@0.110.0 on n8n-node-dev * :bookmark: Release n8n-node-dev@0.67.0 * :arrow_up: Set n8n-core@0.128.0 and n8n-workflow@0.110.0 on n8n-nodes-base * :bookmark: Release n8n-nodes-base@0.186.0 * :bookmark: Release n8n-design-system@0.28.0 * :arrow_up: Set n8n-design-system@0.28.0 and n8n-workflow@0.110.0 on n8n-editor-ui * :bookmark: Release n8n-editor-ui@0.154.0 * :arrow_up: Set n8n-core@0.128.0, n8n-editor-ui@0.154.0, n8n-nodes-base@0.186.0 and n8n-workflow@0.110.0 on n8n * :bookmark: Release n8n@0.188.0 * :bookmark: Update main package.json to 0.188.0 * :books: Update CHANGELOG.md with version 0.188.0 * :shirt: Adjust line endings for Prettier lintings (#3786) * build: Use package-lock.json file with custom build * 💄 Updating onboarding prompt label * :arrow_up: Set eslint@8.0.0 on n8n-workflow (#3768) * :arrow_up: Upgrade `n8n-workflow` to ESLint 8 * :package: Update `package-lock.json` * :package: Re-update `package-lock.json` * :zap: Fix on error behaviour for Delete, Get and Update Co-authored-by: Jonathan Bennetts Co-authored-by: ricardo Co-authored-by: Ricardo Espinoza Co-authored-by: Jan Oberhauser Co-authored-by: Iván Ovejero Co-authored-by: Omar Ajoue Co-authored-by: Ahsan Virani Co-authored-by: Nicholas Penree Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Co-authored-by: agobrech <45268029+agobrech@users.noreply.github.com> Co-authored-by: maxtkacz Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com> Co-authored-by: Milorad FIlipović Co-authored-by: Ben Hesseldieck Co-authored-by: Alex Grozav Co-authored-by: Milorad Filipovic --- .../credentials/NocoDb.credentials.ts | 2 +- .../credentials/NocoDbApiToken.credentials.ts | 37 +++ .../nodes/NocoDB/GenericFunctions.ts | 19 +- .../nodes-base/nodes/NocoDB/NocoDB.node.ts | 277 ++++++++++++++++-- .../nodes/NocoDB/OperationDescription.ts | 55 +++- packages/nodes-base/package.json | 1 + 6 files changed, 353 insertions(+), 38 deletions(-) create mode 100644 packages/nodes-base/credentials/NocoDbApiToken.credentials.ts diff --git a/packages/nodes-base/credentials/NocoDb.credentials.ts b/packages/nodes-base/credentials/NocoDb.credentials.ts index 4977393a705f1..85d8141bab7b7 100644 --- a/packages/nodes-base/credentials/NocoDb.credentials.ts +++ b/packages/nodes-base/credentials/NocoDb.credentials.ts @@ -11,7 +11,7 @@ export class NocoDb implements ICredentialType { documentationUrl = 'nocoDb'; properties: INodeProperties[] = [ { - displayName: 'API Token', + displayName: 'User Token', name: 'apiToken', type: 'string', default: '', diff --git a/packages/nodes-base/credentials/NocoDbApiToken.credentials.ts b/packages/nodes-base/credentials/NocoDbApiToken.credentials.ts new file mode 100644 index 0000000000000..62455a9d5abf5 --- /dev/null +++ b/packages/nodes-base/credentials/NocoDbApiToken.credentials.ts @@ -0,0 +1,37 @@ +import { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + + +export class NocoDbApiToken implements ICredentialType { + name = 'nocoDbApiToken'; + displayName = 'NocoDB API Token'; + documentationUrl = 'nocoDb'; + properties: INodeProperties[] = [ + { + displayName: 'API Token', + name: 'apiToken', + type: 'string', + default: '', + }, + { + displayName: 'Host', + name: 'host', + type: 'string', + default: '', + placeholder: 'http(s)://localhost:8080', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + 'xc-token': '={{$credentials.apiToken}}', + }, + }, + }; +} diff --git a/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts b/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts index 053bf2dc46d75..f2e64b3910b3b 100644 --- a/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts +++ b/packages/nodes-base/nodes/NocoDB/GenericFunctions.ts @@ -35,14 +35,22 @@ interface IAttachment { * @returns {Promise} */ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, endpoint: string, body: object, query?: IDataObject, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credentials = await this.getCredentials('nocoDb'); + const authenticationMethod = this.getNodeParameter('authentication', 0) as string; + const credentials = await this.getCredentials(authenticationMethod); + + if (credentials === undefined) { + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); + } + + const baseUrl = credentials.host as string; + query = query || {}; const options: OptionsWithUri = { method, body, qs: query, - uri: uri || `${credentials.host}${endpoint}`, + uri: uri || baseUrl.endsWith('/') ? `${baseUrl.slice(0, -1)}${endpoint}` : `${baseUrl}${endpoint}`, json: true, }; @@ -56,7 +64,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa } try { - return await this.helpers.requestWithAuthentication.call(this, 'nocoDb', options); + return await this.helpers.requestWithAuthentication.call(this, authenticationMethod, options); } catch (error) { throw new NodeApiError(this.getNode(), error); } @@ -76,6 +84,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa * @returns {Promise} */ export async function apiRequestAllItems(this: IHookFunctions | IExecuteFunctions | IPollFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any + const version = this.getNode().typeVersion as number; if (query === undefined) { query = {}; @@ -88,12 +97,12 @@ export async function apiRequestAllItems(this: IHookFunctions | IExecuteFunction do { responseData = await apiRequest.call(this, method, endpoint, body, query); - returnData.push(...responseData); + version === 1 ? returnData.push(...responseData) : returnData.push(...responseData.list); query.offset += query.limit; } while ( - responseData.length !== 0 + version === 1 ? responseData.length !== 0 : responseData.pageInfo.isLastPage !== true ); return returnData; diff --git a/packages/nodes-base/nodes/NocoDB/NocoDB.node.ts b/packages/nodes-base/nodes/NocoDB/NocoDB.node.ts index df9c78784a25b..35c0f60d3875f 100644 --- a/packages/nodes-base/nodes/NocoDB/NocoDB.node.ts +++ b/packages/nodes-base/nodes/NocoDB/NocoDB.node.ts @@ -6,6 +6,7 @@ import { import { IBinaryData, IDataObject, + ILoadOptionsFunctions, INodeExecutionData, INodeType, INodeTypeDescription, @@ -29,7 +30,7 @@ export class NocoDB implements INodeType { name: 'nocoDb', icon: 'file:nocodb.svg', group: ['input'], - version: 1, + version: [1, 2], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Read, update, write and delete data from NocoDB', defaults: { @@ -41,9 +42,91 @@ export class NocoDB implements INodeType { { name: 'nocoDb', required: true, + displayOptions: { + show: { + authentication: [ + 'nocoDb', + ], + }, + }, + }, + { + name: 'nocoDbApiToken', + required: true, + displayOptions: { + show: { + authentication: [ + 'nocoDbApiToken', + ], + }, + }, }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'User Token', + value: 'nocoDb', + }, + { + name: 'API Token', + value: 'nocoDbApiToken', + }, + ], + default: 'nocoDb', + }, + { + displayName: 'API Version', + name: 'version', + type: 'options', + displayOptions: { + show: { + '@version': [ + 1, + ], + }, + }, + isNodeSetting: true, + options: [ + { + name: 'Before v0.90.0', + value: 1, + }, + { + name: 'v0.90.0 Onwards', + value: 2, + }, + ], + default: 1, + }, + { + displayName: 'API Version', + name: 'version', + type: 'options', + displayOptions: { + show: { + '@version': [ + 2, + ], + }, + }, + isNodeSetting: true, + options: [ + { + name: 'Before v0.90.0', + value: 1, + }, + { + name: 'v0.90.0 Onwards', + value: 2, + }, + ], + default: 2, + }, { displayName: 'Resource', name: 'resource', @@ -107,28 +190,66 @@ export class NocoDB implements INodeType { ], }; + methods = { + loadOptions: { + async getProjects(this: ILoadOptionsFunctions) { + try { + const requestMethod = 'GET'; + const endpoint = '/api/v1/db/meta/projects/'; + const responseData = await apiRequest.call(this, requestMethod, endpoint, {}, {}); + return responseData.list.map((i: IDataObject) => ({ name: i.title, value: i.id })); + } catch (e) { + throw new NodeOperationError(this.getNode(), `Error while fetching projects! (${e})`); + } + + }, + // This only supports using the Project ID + async getTables(this: ILoadOptionsFunctions) { + const projectId = this.getNodeParameter('projectId', 0) as string; + if (projectId) { + try { + const requestMethod = 'GET'; + const endpoint = `/api/v1/db/meta/projects/${projectId}/tables`; + const responseData = await apiRequest.call(this, requestMethod, endpoint, {}, {}); + return responseData.list.map((i: IDataObject) => ({ name: i.title, value: i.id })); + } catch (e) { + throw new NodeOperationError(this.getNode(), `Error while fetching tables! (${e})`); + } + } else { + throw new NodeOperationError(this.getNode(), `No project selected!`); + } + }, + }, + }; + async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; let responseData; + const version = this.getNodeParameter('version', 0) as number; const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; - const projectId = this.getNodeParameter('projectId', 0) as string; - const table = this.getNodeParameter('table', 0) as string; let returnAll = false; - let endpoint = ''; let requestMethod = ''; let qs: IDataObject = {}; - if (resource === 'row') { + let endPoint = ''; - if (operation === 'create') { + const projectId = this.getNodeParameter('projectId', 0) as string; + const table = this.getNodeParameter('table', 0) as string; + if (resource === 'row') { + if (operation === 'create') { requestMethod = 'POST'; - endpoint = `/nc/${projectId}/api/v1/${table}/bulk`; + + if (version === 1) { + endPoint = `/nc/${projectId}/api/v1/${table}/bulk`; + } else if (version === 2) { + endPoint = `/api/v1/db/data/bulk/noco/${projectId}/${table}`; + } const body: IDataObject[] = []; @@ -183,7 +304,14 @@ export class NocoDB implements INodeType { }; const qs = { project_id: projectId }; - responseData = await apiRequest.call(this, 'POST', '/dashboard', {}, qs, undefined, { formData }); + let postUrl = ''; + if (version === 1) { + postUrl = '/dashboard'; + } else if (version === 2) { + postUrl = '/api/v1/db/storage/upload'; + } + + responseData = await apiRequest.call(this, 'POST', postUrl, {}, qs, undefined, { formData }); newItem[field.fieldName] = JSON.stringify([responseData]); } } @@ -191,7 +319,7 @@ export class NocoDB implements INodeType { body.push(newItem); } try { - responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + responseData = await apiRequest.call(this, requestMethod, endPoint, body, qs); // Calculate ID manually and add to return data let id = responseData[0]; @@ -206,32 +334,62 @@ export class NocoDB implements INodeType { } throw new NodeApiError(this.getNode(), error); } - } else if (operation === 'delete') { + } + if (operation === 'delete') { requestMethod = 'DELETE'; - endpoint = `/nc/${projectId}/api/v1/${table}/bulk`; + if (version === 1) { + endPoint = `/nc/${projectId}/api/v1/${table}/bulk`; + } else if (version === 2) { + endPoint = `/api/v1/db/data/bulk/noco/${projectId}/${table}`; + } + const body: IDataObject[] = []; for (let i = 0; i < items.length; i++) { const id = this.getNodeParameter('id', i) as string; body.push({ id }); } + try { - responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); - returnData.push(...items.map(item => item.json)); + responseData = await apiRequest.call(this, requestMethod, endPoint, body, qs); + if (version === 1) { + returnData.push(...items.map(item => item.json)); + } else if (version === 2 ) { + + returnData.push(...responseData.map((result: number, index: number) => { + if (result === 0) { + const errorMessage = `The row with the ID "${body[index].id}" could not be deleted. It probably doesn't exist.`; + if (this.continueOnFail()) { + return { error: errorMessage }; + } + throw new NodeApiError(this.getNode(), { message: errorMessage }, { message: errorMessage, itemIndex: index }); + } + return { + success: true, + }; + })); + } } catch (error) { if (this.continueOnFail()) { returnData.push({ error: error.toString() }); } throw new NodeApiError(this.getNode(), error); } - } else if (operation === 'getAll') { + } + + if (operation === 'getAll') { const data = []; const downloadAttachments = this.getNodeParameter('downloadAttachments', 0) as boolean; try { for (let i = 0; i < items.length; i++) { requestMethod = 'GET'; - endpoint = `/nc/${projectId}/api/v1/${table}`; + + if (version === 1) { + endPoint = `/nc/${projectId}/api/v1/${table}`; + } else if (version === 2 ) { + endPoint = `/api/v1/db/data/noco/${projectId}/${table}`; + } returnAll = this.getNodeParameter('returnAll', 0) as boolean; qs = this.getNodeParameter('options', i, {}) as IDataObject; @@ -246,10 +404,13 @@ export class NocoDB implements INodeType { } if (returnAll === true) { - responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, {}, qs); + responseData = await apiRequestAllItems.call(this, requestMethod, endPoint, {}, qs); } else { qs.limit = this.getNodeParameter('limit', 0) as number; - responseData = await apiRequest.call(this, requestMethod, endpoint, {}, qs); + responseData = await apiRequest.call(this, requestMethod, endPoint, {}, qs); + if (version === 2) { + responseData = responseData.list; + } } returnData.push.apply(returnData, responseData); @@ -265,24 +426,51 @@ export class NocoDB implements INodeType { return [data]; } - } catch (error) { + } catch (error) { if (this.continueOnFail()) { returnData.push({ error: error.toString() }); } throw error; } - } else if (operation === 'get') { + } + if (operation === 'get') { requestMethod = 'GET'; const newItems: INodeExecutionData[] = []; for (let i = 0; i < items.length; i++) { try { const id = this.getNodeParameter('id', i) as string; - endpoint = `/nc/${projectId}/api/v1/${table}/${id}`; - responseData = await apiRequest.call(this, requestMethod, endpoint, {}, qs); - const newItem: INodeExecutionData = { json: responseData }; + if (version === 1) { + endPoint = `/nc/${projectId}/api/v1/${table}/${id}`; + } else if (version === 2) { + endPoint = `/api/v1/db/data/noco/${projectId}/${table}/${id}`; + } + + responseData = await apiRequest.call(this, requestMethod, endPoint, {}, qs); + + let newItem: INodeExecutionData = { json: {} }; + + if (version === 1) { + newItem = { json: responseData }; + } else if (version === 2 ) { + if (Object.keys(responseData).length === 0) { + // Get did fail + const errorMessage = `The row with the ID "${id}" could not be queried. It probably doesn't exist.`; + if (this.continueOnFail()) { + newItem = { + json: { error: errorMessage }, + }; + } + throw new NodeApiError(this.getNode(), { message: errorMessage }, { message: errorMessage, itemIndex: i }); + } else { + // Get did work + newItem = { json: responseData }; + } + } + + // const newItem: INodeExecutionData = { json: responseData }; const downloadAttachments = this.getNodeParameter('downloadAttachments', i) as boolean; if (downloadAttachments === true) { @@ -301,12 +489,18 @@ export class NocoDB implements INodeType { } } return this.prepareOutputData(newItems); + } - } else if (operation === 'update') { + if (operation === 'update') { - requestMethod = 'PUT'; - endpoint = `/nc/${projectId}/api/v1/${table}/bulk`; + let requestMethod = 'PATCH'; + if (version === 1) { + endPoint = `/nc/${projectId}/api/v1/${table}/bulk`; + requestMethod = 'PUT'; + } else if (version === 2) { + endPoint = `/api/v1/db/data/bulk/noco/${projectId}/${table}`; + } const body: IDataObject[] = []; for (let i = 0; i < items.length; i++) { @@ -326,13 +520,13 @@ export class NocoDB implements INodeType { } else { const fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as Array<{ fieldName: string; - upload: boolean; + binaryData: boolean; fieldValue?: string; binaryProperty?: string; }>; for (const field of fields) { - if (!field.upload) { + if (!field.binaryData) { newItem[field.fieldName] = field.fieldValue; } else if (field.binaryProperty) { if (!items[i].binary) { @@ -361,8 +555,13 @@ export class NocoDB implements INodeType { }), }; const qs = { project_id: projectId }; - - responseData = await apiRequest.call(this, 'POST', '/dashboard', {}, qs, undefined, { formData }); + let postUrl = ''; + if (version === 1) { + postUrl = '/dashboard'; + } else if (version === 2) { + postUrl = '/api/v1/db/storage/upload'; + } + responseData = await apiRequest.call(this, 'POST', postUrl, {}, qs, undefined, { formData }); newItem[field.fieldName] = JSON.stringify([responseData]); } } @@ -371,8 +570,24 @@ export class NocoDB implements INodeType { } try { - responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); - returnData.push(...body); + responseData = await apiRequest.call(this, requestMethod, endPoint, body, qs); + + if (version === 1) { + returnData.push(...body); + } else if (version === 2 ) { + returnData.push(...responseData.map((result: number, index: number) => { + if (result === 0) { + const errorMessage = `The row with the ID "${body[index].id}" could not be updated. It probably doesn't exist.`; + if (this.continueOnFail()) { + return { error: errorMessage }; + } + throw new NodeApiError(this.getNode(), { message: errorMessage }, { message: errorMessage, itemIndex: index }); + } + return { + success: true, + }; + })); + } } catch (error) { if (this.continueOnFail()) { returnData.push({ error: error.toString() }); diff --git a/packages/nodes-base/nodes/NocoDB/OperationDescription.ts b/packages/nodes-base/nodes/NocoDB/OperationDescription.ts index 9f7b8d23ce440..9e4d4f4c00358 100644 --- a/packages/nodes-base/nodes/NocoDB/OperationDescription.ts +++ b/packages/nodes-base/nodes/NocoDB/OperationDescription.ts @@ -11,14 +11,67 @@ export const operationFields: INodeProperties[] = [ name: 'projectId', type: 'string', default: '', + displayOptions: { + show: { + version: [ + 1, + ], + }, + }, required: true, description: 'The ID of the project', }, + { + displayName: 'Project Name or ID', + name: 'projectId', + type: 'options', + default: '', + displayOptions: { + show: { + version: [ + 2, + ], + }, + }, + required: true, + description: 'Choose from the list, or specify an ID using an expression. Choose from the list, or specify an ID using an expression. Choose from the list, or specify an ID using an expression.', + typeOptions: { + loadOptionsMethod: 'getProjects', + }, + }, + { + displayName: 'Table Name or ID', + name: 'table', + type: 'options', + default: '', + displayOptions: { + show: { + version: [ + 2, + ], + }, + }, + required: true, + description: 'The table to operate on. Choose from the list, or specify an ID using an expression. Choose from the list, or specify an ID using an expression.', + typeOptions: { + loadOptionsDependsOn: [ + 'projectId', + ], + loadOptionsMethod: 'getTables', + }, + }, { displayName: 'Table', name: 'table', type: 'string', default: '', + displayOptions: { + show: { + version: [ + 1, + ], + }, + }, required: true, description: 'The name of the table', }, @@ -75,7 +128,7 @@ export const operationFields: INodeProperties[] = [ minValue: 1, maxValue: 100, }, - default: 100, + default: 50, description: 'Max number of results to return', }, { diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 7dc371cd5aaf2..b84a21f9c32ac 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -210,6 +210,7 @@ "dist/credentials/NextCloudApi.credentials.js", "dist/credentials/NextCloudOAuth2Api.credentials.js", "dist/credentials/NocoDb.credentials.js", + "dist/credentials/NocoDbApiToken.credentials.js", "dist/credentials/NotionApi.credentials.js", "dist/credentials/NotionOAuth2Api.credentials.js", "dist/credentials/OAuth1Api.credentials.js",