feat(KoBoToolbox Node): Add support for Media file API (#4578)

This commit is contained in:
Yann Jouanique 2022-12-06 11:00:53 +01:00 committed by GitHub
parent c3114241fd
commit 37e580eb06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 321 additions and 44 deletions

View file

@ -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 <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
},
/* -------------------------------------------------------------------------- */
/* 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',
},
];

View file

@ -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 <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
},
/* -------------------------------------------------------------------------- */
/* form:getAll */

View file

@ -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<any> {
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

View file

@ -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 <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
},
{
displayName: 'Hook Log ID',
name: 'logId',

View file

@ -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);
}

View file

@ -74,7 +74,6 @@ export class KoBoToolboxTrigger implements INodeType {
],
};
// @ts-ignore
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {

View file

@ -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 <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
},
{
displayName: 'Return All',
name: 'returnAll',