diff --git a/packages/nodes-base/nodes/KoBoToolbox/FileDescription.ts b/packages/nodes-base/nodes/KoBoToolbox/FileDescription.ts new file mode 100644 index 0000000000..2840deebeb --- /dev/null +++ b/packages/nodes-base/nodes/KoBoToolbox/FileDescription.ts @@ -0,0 +1,164 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const fileOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['file'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a file', + action: 'Create a file', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete file', + action: 'Delete a file', + }, + { + name: 'Get', + value: 'get', + description: 'Get a file content', + action: 'Get a file content', + }, + { + name: 'Get Many', + value: 'getAll', + description: 'Get many files', + action: 'Get many files', + }, + ], + default: 'get', + }, +]; + +export const fileFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* file:* */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Form Name or ID', + name: 'formId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'loadForms', + }, + required: true, + default: '', + displayOptions: { + show: { + resource: ['file'], + }, + }, + description: + 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg). Choose from the list, or specify an ID using an expression.', + }, + /* -------------------------------------------------------------------------- */ + /* file:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['file'], + operation: ['delete', 'get'], + }, + }, + description: 'Uid of the file (should start with "af" e.g. "afQoJxA4kmKEXVpkH6SYbhb"', + }, + { + displayName: 'Property Name', + name: 'binaryPropertyName', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + resource: ['file'], + operation: ['get'], + }, + }, + description: 'Name of the binary property to write the file into', + }, + { + displayName: 'Download File Content', + name: 'download', + type: 'boolean', + required: true, + default: false, + displayOptions: { + show: { + resource: ['file'], + operation: ['get'], + }, + }, + description: 'Whether to download the file content into a binary property', + }, + { + displayName: 'File Upload Mode', + name: 'fileMode', + type: 'options', + required: true, + default: 'binary', + displayOptions: { + show: { + resource: ['file'], + operation: ['create'], + }, + }, + options: [ + { + name: 'Binary Data', + value: 'binary', + }, + { + name: 'URL', + value: 'url', + }, + ], + }, + { + displayName: 'Property Name', + name: 'binaryPropertyName', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + resource: ['file'], + operation: ['create'], + fileMode: ['binary'], + }, + }, + description: + 'Name of the binary property containing the file to upload. Supported types: image, audio, video, csv, xml, zip.', + }, + { + displayName: 'File URL', + name: 'fileUrl', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['file'], + operation: ['create'], + fileMode: ['url'], + }, + }, + description: 'HTTP(s) link to the file to upload', + }, +]; diff --git a/packages/nodes-base/nodes/KoBoToolbox/FormDescription.ts b/packages/nodes-base/nodes/KoBoToolbox/FormDescription.ts index 0ce615f7fa..301fea92f1 100644 --- a/packages/nodes-base/nodes/KoBoToolbox/FormDescription.ts +++ b/packages/nodes-base/nodes/KoBoToolbox/FormDescription.ts @@ -24,6 +24,12 @@ export const formOperations: INodeProperties[] = [ description: 'Get many forms', action: 'Get many forms', }, + { + name: 'Redeploy', + value: 'redeploy', + description: 'Redeploy Current Form Version', + action: 'Redeploy Current Form Version', + }, ], default: 'get', }, @@ -34,18 +40,22 @@ export const formFields: INodeProperties[] = [ /* form:get */ /* -------------------------------------------------------------------------- */ { - displayName: 'Form ID', + displayName: 'Form Name or ID', name: 'formId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'loadForms', + }, required: true, default: '', displayOptions: { show: { resource: ['form'], - operation: ['get'], + operation: ['get', 'redeploy'], }, }, - description: 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg)', + description: + 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg). Choose from the list, or specify an ID using an expression.', }, /* -------------------------------------------------------------------------- */ /* form:getAll */ diff --git a/packages/nodes-base/nodes/KoBoToolbox/GenericFunctions.ts b/packages/nodes-base/nodes/KoBoToolbox/GenericFunctions.ts index de39154965..7b8f8c8f7c 100644 --- a/packages/nodes-base/nodes/KoBoToolbox/GenericFunctions.ts +++ b/packages/nodes-base/nodes/KoBoToolbox/GenericFunctions.ts @@ -60,6 +60,20 @@ export async function koBoToolboxApiRequest( return results; } +export async function koBoToolboxRawRequest( + this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, + option: IHttpRequestOptions, + // tslint:disable-next-line:no-any +): Promise { + const credentials = await this.getCredentials('koBoToolboxApi'); + + if (option.url && !/^http(s)?:/.test(option.url)) { + option.url = credentials.URL + option.url; + } + + return await this.helpers.httpRequestWithAuthentication.call(this, 'koBoToolboxApi', option); +} + function parseGeoPoint(geoPoint: string): null | number[] { // Check if it looks like a "lat lon z precision" flat string e.g. "-1.931161 30.079811 0 0" (lat, lon, elevation, precision) // NOTE: we are discarding the elevation and precision values since they're not (well) supported in GeoJSON diff --git a/packages/nodes-base/nodes/KoBoToolbox/HookDescription.ts b/packages/nodes-base/nodes/KoBoToolbox/HookDescription.ts index 9e35470bd5..855a07b9f4 100644 --- a/packages/nodes-base/nodes/KoBoToolbox/HookDescription.ts +++ b/packages/nodes-base/nodes/KoBoToolbox/HookDescription.ts @@ -63,7 +63,7 @@ export const hookFields: INodeProperties[] = [ displayOptions: { show: { resource: ['hook'], - operation: ['get', 'retryOne', 'retryAll', 'getLogs'], + operation: ['get', 'retryOne', 'retryAll', 'getLogs', 'getAll'], }, }, description: @@ -86,24 +86,6 @@ export const hookFields: INodeProperties[] = [ /* -------------------------------------------------------------------------- */ /* hook:getAll */ /* -------------------------------------------------------------------------- */ - { - displayName: 'Form Name or ID', - name: 'formId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'loadForms', - }, - required: true, - default: '', - displayOptions: { - show: { - resource: ['hook'], - operation: ['getAll'], - }, - }, - description: - 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg). Choose from the list, or specify an ID using an expression.', - }, { displayName: 'Hook Log ID', name: 'logId', diff --git a/packages/nodes-base/nodes/KoBoToolbox/KoBoToolbox.node.ts b/packages/nodes-base/nodes/KoBoToolbox/KoBoToolbox.node.ts index 1d47179c1a..b9807c3931 100644 --- a/packages/nodes-base/nodes/KoBoToolbox/KoBoToolbox.node.ts +++ b/packages/nodes-base/nodes/KoBoToolbox/KoBoToolbox.node.ts @@ -1,11 +1,19 @@ import { IExecuteFunctions } from 'n8n-core'; -import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { + IBinaryData, + IBinaryKeyData, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; import { downloadAttachments, formatSubmission, koBoToolboxApiRequest, + koBoToolboxRawRequest, loadForms, parseStringList, } from './GenericFunctions'; @@ -16,6 +24,8 @@ import { submissionFields, submissionOperations } from './SubmissionDescription' import { hookFields, hookOperations } from './HookDescription'; +import { fileFields, fileOperations } from './FileDescription'; + export class KoBoToolbox implements INodeType { description: INodeTypeDescription = { displayName: 'KoBoToolbox', @@ -43,6 +53,10 @@ export class KoBoToolbox implements INodeType { type: 'options', noDataExpression: true, options: [ + { + name: 'File', + value: 'file', + }, { name: 'Form', value: 'form', @@ -65,6 +79,8 @@ export class KoBoToolbox implements INodeType { ...hookFields, ...submissionOperations, ...submissionFields, + ...fileOperations, + ...fileFields, ], }; @@ -129,7 +145,21 @@ export class KoBoToolbox implements INodeType { scroll: this.getNodeParameter('returnAll', i), }); } + + if (operation === 'redeploy') { + // ---------------------------------- + // Form: redeploy + // ---------------------------------- + const formId = this.getNodeParameter('formId', i) as string; + responseData = [ + await koBoToolboxApiRequest.call(this, { + method: 'PATCH', + url: `/api/v2/assets/${formId}/deployment/`, + }), + ]; + } } + if (resource === 'submission') { // ********************************************************************* // Submissions @@ -342,6 +372,102 @@ export class KoBoToolbox implements INodeType { } } + if (resource === 'file') { + // ********************************************************************* + // File + // ********************************************************************* + const formId = this.getNodeParameter('formId', i) as string; + + if (operation === 'getAll') { + responseData = [ + await koBoToolboxApiRequest.call(this, { + url: `/api/v2/assets/${formId}/files`, + qs: { + file_type: 'form_media', + }, + scroll: true, + }), + ]; + } + + if (operation === 'get') { + const fileId = this.getNodeParameter('fileId', i) as string; + const download = this.getNodeParameter('download', i) as boolean; + + responseData = [ + await koBoToolboxApiRequest.call(this, { + url: `/api/v2/assets/${formId}/files/${fileId}`, + }), + ]; + + if (responseData && responseData[0] && download) { + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; + + const binaryItem: INodeExecutionData = { + json: responseData[0], + binary: {}, + }; + + const response = await koBoToolboxRawRequest.call(this, { + url: `/api/v2/assets/${formId}/files/${fileId}/content`, + encoding: 'arraybuffer', + }); + + console.dir(response); + + binaryItem.binary![binaryPropertyName] = await this.helpers.prepareBinaryData( + response, + responseData[0].metadata.filename, + ); + + binaryItems.push(binaryItem); + } + } + + if (operation === 'delete') { + const fileId = this.getNodeParameter('fileId', i) as string; + responseData = [ + await koBoToolboxApiRequest.call(this, { + method: 'DELETE', + url: `/api/v2/assets/${formId}/files/${fileId}`, + }), + ]; + } + + if (operation === 'create') { + const fileMode = this.getNodeParameter('fileMode', i) as string; + const body: IDataObject = { + description: 'Uploaded file', + file_type: 'form_media', + }; + + if ('binary' === fileMode) { + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; + const item = items[i].binary as IBinaryKeyData; + const binaryData = item[binaryPropertyName] as IBinaryData; + + body.base64Encoded = 'data:' + binaryData.mimeType + ';base64,' + binaryData.data; + body.metadata = { + filename: binaryData.fileName, + }; + } else { + const fileUrl = this.getNodeParameter('fileUrl', i) as string; + + body.metadata = { + redirect_url: fileUrl, + }; + } + + responseData = [ + await koBoToolboxApiRequest.call(this, { + method: 'POST', + url: `/api/v2/assets/${formId}/files/`, + body, + }), + ]; + } + } + returnData = returnData.concat(responseData); } diff --git a/packages/nodes-base/nodes/KoBoToolbox/KoBoToolboxTrigger.node.ts b/packages/nodes-base/nodes/KoBoToolbox/KoBoToolboxTrigger.node.ts index 876498f718..0ad35b3180 100644 --- a/packages/nodes-base/nodes/KoBoToolbox/KoBoToolboxTrigger.node.ts +++ b/packages/nodes-base/nodes/KoBoToolbox/KoBoToolboxTrigger.node.ts @@ -74,7 +74,6 @@ export class KoBoToolboxTrigger implements INodeType { ], }; - // @ts-ignore webhookMethods = { default: { async checkExists(this: IHookFunctions): Promise { diff --git a/packages/nodes-base/nodes/KoBoToolbox/SubmissionDescription.ts b/packages/nodes-base/nodes/KoBoToolbox/SubmissionDescription.ts index c6809c67f0..5b5a19c578 100644 --- a/packages/nodes-base/nodes/KoBoToolbox/SubmissionDescription.ts +++ b/packages/nodes-base/nodes/KoBoToolbox/SubmissionDescription.ts @@ -63,7 +63,7 @@ export const submissionFields: INodeProperties[] = [ displayOptions: { show: { resource: ['submission'], - operation: ['get', 'delete', 'getValidation', 'setValidation'], + operation: ['get', 'delete', 'getValidation', 'setValidation', 'getAll'], }, }, description: @@ -114,24 +114,6 @@ export const submissionFields: INodeProperties[] = [ /* -------------------------------------------------------------------------- */ /* submission:getAll */ /* -------------------------------------------------------------------------- */ - { - displayName: 'Form Name or ID', - name: 'formId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'loadForms', - }, - required: true, - default: '', - displayOptions: { - show: { - resource: ['submission'], - operation: ['getAll'], - }, - }, - description: - 'Form ID (e.g. aSAvYreNzVEkrWg5Gdcvg). Choose from the list, or specify an ID using an expression.', - }, { displayName: 'Return All', name: 'returnAll',