mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge 426177bf87
into d2dd1796a8
This commit is contained in:
commit
386c324903
|
@ -10,18 +10,22 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
baserowApiRequest,
|
baserowApiRequest,
|
||||||
|
baserowFileUploadRequest,
|
||||||
baserowApiRequestAllItems,
|
baserowApiRequestAllItems,
|
||||||
getJwtToken,
|
getJwtToken,
|
||||||
TableFieldMapper,
|
TableFieldMapper,
|
||||||
toOptions,
|
toOptions,
|
||||||
|
getTableFields,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
import { operationFields } from './OperationDescription';
|
import { operationFields } from './OperationDescription';
|
||||||
import type {
|
import type {
|
||||||
BaserowCredentials,
|
BaserowCredentials,
|
||||||
FieldsUiValues,
|
FieldsUiValues,
|
||||||
|
FileOperation,
|
||||||
GetAllAdditionalOptions,
|
GetAllAdditionalOptions,
|
||||||
LoadedResource,
|
LoadedResource,
|
||||||
Operation,
|
Resource,
|
||||||
|
RowOperation,
|
||||||
Row,
|
Row,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
@ -53,6 +57,10 @@ export class Baserow implements INodeType {
|
||||||
type: 'options',
|
type: 'options',
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
name: 'File',
|
||||||
|
value: 'file',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Row',
|
name: 'Row',
|
||||||
value: 'row',
|
value: 'row',
|
||||||
|
@ -104,6 +112,48 @@ export class Baserow implements INodeType {
|
||||||
],
|
],
|
||||||
default: 'getAll',
|
default: 'getAll',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['file'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Upload',
|
||||||
|
value: 'upload',
|
||||||
|
description: 'Upload a file',
|
||||||
|
action: 'Upload a file',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Upload via URL',
|
||||||
|
value: 'upload-via-url',
|
||||||
|
description: 'Upload a file via URL',
|
||||||
|
action: 'Upload a file via URL',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'upload',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Input Data Field Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
|
||||||
|
type: 'string',
|
||||||
|
default: 'data',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: ['upload'],
|
||||||
|
resource: ['file'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
description:
|
||||||
|
'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG.',
|
||||||
|
},
|
||||||
...operationFields,
|
...operationFields,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -155,182 +205,250 @@ export class Baserow implements INodeType {
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const mapper = new TableFieldMapper();
|
|
||||||
const returnData: INodeExecutionData[] = [];
|
|
||||||
const operation = this.getNodeParameter('operation', 0) as Operation;
|
|
||||||
|
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as Resource;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as RowOperation | FileOperation;
|
||||||
|
|
||||||
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||||
const jwtToken = await getJwtToken.call(this, credentials);
|
const jwtToken = await getJwtToken.call(this, credentials);
|
||||||
const fields = await mapper.getTableFields.call(this, tableId, jwtToken);
|
|
||||||
mapper.createMappings(fields);
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
try {
|
try {
|
||||||
if (operation === 'getAll') {
|
if (resource === 'row') {
|
||||||
// ----------------------------------
|
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||||
// getAll
|
const fields = await getTableFields.call(this, tableId, jwtToken);
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
// https://api.baserow.io/api/redoc/#operation/list_database_table_rows
|
const mapper = new TableFieldMapper();
|
||||||
|
mapper.createMappings(fields);
|
||||||
|
|
||||||
const { order, filters, filterType, search } = this.getNodeParameter(
|
if (operation === 'getAll') {
|
||||||
'additionalOptions',
|
// ----------------------------------
|
||||||
i,
|
// getAll
|
||||||
) as GetAllAdditionalOptions;
|
// ----------------------------------
|
||||||
|
|
||||||
const qs: IDataObject = {};
|
// https://api.baserow.io/api/redoc/#operation/list_database_table_rows
|
||||||
|
|
||||||
if (order?.fields) {
|
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
|
||||||
qs.order_by = order.fields
|
const limit = this.getNodeParameter('limit', 0, 0);
|
||||||
.map(({ field, direction }) => `${direction}${mapper.setField(field)}`)
|
|
||||||
.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.fields) {
|
const { order, filters, filterType, search } = this.getNodeParameter(
|
||||||
filters.fields.forEach(({ field, operator, value }) => {
|
'additionalOptions',
|
||||||
qs[`filter__field_${mapper.setField(field)}__${operator}`] = value;
|
i,
|
||||||
});
|
) as GetAllAdditionalOptions;
|
||||||
}
|
|
||||||
|
|
||||||
if (filterType) {
|
const qs: IDataObject = {};
|
||||||
qs.filter_type = filterType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search) {
|
if (order?.fields) {
|
||||||
qs.search = search;
|
qs.order_by = order.fields
|
||||||
}
|
.map(({ field, direction }) => `${direction}${mapper.setField(field)}`)
|
||||||
|
.join(',');
|
||||||
const endpoint = `/api/database/rows/table/${tableId}/`;
|
|
||||||
const rows = (await baserowApiRequestAllItems.call(
|
|
||||||
this,
|
|
||||||
'GET',
|
|
||||||
endpoint,
|
|
||||||
jwtToken,
|
|
||||||
{},
|
|
||||||
qs,
|
|
||||||
)) as Row[];
|
|
||||||
|
|
||||||
rows.forEach((row) => mapper.idsToNames(row));
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(rows),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} else if (operation === 'get') {
|
|
||||||
// ----------------------------------
|
|
||||||
// get
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
// https://api.baserow.io/api/redoc/#operation/get_database_table_row
|
|
||||||
|
|
||||||
const rowId = this.getNodeParameter('rowId', i) as string;
|
|
||||||
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
|
||||||
const row = await baserowApiRequest.call(this, 'GET', endpoint, jwtToken);
|
|
||||||
|
|
||||||
mapper.idsToNames(row as Row);
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(row as Row),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} else if (operation === 'create') {
|
|
||||||
// ----------------------------------
|
|
||||||
// create
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
// https://api.baserow.io/api/redoc/#operation/create_database_table_row
|
|
||||||
|
|
||||||
const body: IDataObject = {};
|
|
||||||
|
|
||||||
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
|
||||||
| 'defineBelow'
|
|
||||||
| 'autoMapInputData';
|
|
||||||
|
|
||||||
if (dataToSend === 'autoMapInputData') {
|
|
||||||
const incomingKeys = Object.keys(items[i].json);
|
|
||||||
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
|
|
||||||
const inputDataToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
|
|
||||||
|
|
||||||
for (const key of incomingKeys) {
|
|
||||||
if (inputDataToIgnore.includes(key)) continue;
|
|
||||||
body[key] = items[i].json[key];
|
|
||||||
mapper.namesToIds(body);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const fieldsUi = this.getNodeParameter('fieldsUi.fieldValues', i, []) as FieldsUiValues;
|
if (filters?.fields) {
|
||||||
for (const field of fieldsUi) {
|
filters.fields.forEach(({ field, operator, value }) => {
|
||||||
body[`field_${field.fieldId}`] = field.fieldValue;
|
qs[`filter__field_${mapper.setField(field)}__${operator}`] = value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterType) {
|
||||||
|
qs.filter_type = filterType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
qs.search = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/api/database/rows/table/${tableId}/`;
|
||||||
|
const rows = (await baserowApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
jwtToken,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
returnAll,
|
||||||
|
limit,
|
||||||
|
)) as Row[];
|
||||||
|
|
||||||
|
rows.forEach((row) => mapper.idsToNames(row));
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(rows),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} else if (operation === 'get') {
|
||||||
|
// ----------------------------------
|
||||||
|
// get
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
// https://api.baserow.io/api/redoc/#operation/get_database_table_row
|
||||||
|
|
||||||
|
const rowId = this.getNodeParameter('rowId', i) as string;
|
||||||
|
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
||||||
|
const row = await baserowApiRequest.call(this, 'GET', endpoint, jwtToken);
|
||||||
|
|
||||||
|
mapper.idsToNames(row as Row);
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(row as Row),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} else if (operation === 'create') {
|
||||||
|
// ----------------------------------
|
||||||
|
// create
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
// https://api.baserow.io/api/redoc/#operation/create_database_table_row
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
||||||
|
| 'defineBelow'
|
||||||
|
| 'autoMapInputData';
|
||||||
|
|
||||||
|
if (dataToSend === 'autoMapInputData') {
|
||||||
|
const incomingKeys = Object.keys(items[i].json);
|
||||||
|
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
|
||||||
|
const inputDataToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
|
||||||
|
|
||||||
|
for (const key of incomingKeys) {
|
||||||
|
if (inputDataToIgnore.includes(key)) continue;
|
||||||
|
body[key] = items[i].json[key];
|
||||||
|
mapper.namesToIds(body);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const fieldsUi = this.getNodeParameter(
|
||||||
|
'fieldsUi.fieldValues',
|
||||||
|
i,
|
||||||
|
[],
|
||||||
|
) as FieldsUiValues;
|
||||||
|
for (const field of fieldsUi) {
|
||||||
|
body[`field_${field.fieldId}`] = field.fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/api/database/rows/table/${tableId}/`;
|
||||||
|
const createdRow = await baserowApiRequest.call(this, 'POST', endpoint, jwtToken, body);
|
||||||
|
|
||||||
|
mapper.idsToNames(createdRow as Row);
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(createdRow as Row),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} else if (operation === 'update') {
|
||||||
|
// ----------------------------------
|
||||||
|
// update
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
// https://api.baserow.io/api/redoc/#operation/update_database_table_row
|
||||||
|
|
||||||
|
const rowId = this.getNodeParameter('rowId', i) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
||||||
|
| 'defineBelow'
|
||||||
|
| 'autoMapInputData';
|
||||||
|
|
||||||
|
if (dataToSend === 'autoMapInputData') {
|
||||||
|
const incomingKeys = Object.keys(items[i].json);
|
||||||
|
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
|
||||||
|
const inputsToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
|
||||||
|
|
||||||
|
for (const key of incomingKeys) {
|
||||||
|
if (inputsToIgnore.includes(key)) continue;
|
||||||
|
body[key] = items[i].json[key];
|
||||||
|
mapper.namesToIds(body);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const fieldsUi = this.getNodeParameter(
|
||||||
|
'fieldsUi.fieldValues',
|
||||||
|
i,
|
||||||
|
[],
|
||||||
|
) as FieldsUiValues;
|
||||||
|
for (const field of fieldsUi) {
|
||||||
|
body[`field_${field.fieldId}`] = field.fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
||||||
|
const updatedRow = await baserowApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
endpoint,
|
||||||
|
jwtToken,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
mapper.idsToNames(updatedRow as Row);
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(updatedRow as Row),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} else if (resource === 'row' && operation === 'delete') {
|
||||||
|
// ----------------------------------
|
||||||
|
// delete
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
// https://api.baserow.io/api/redoc/#operation/delete_database_table_row
|
||||||
|
|
||||||
|
const rowId = this.getNodeParameter('rowId', i) as string;
|
||||||
|
|
||||||
|
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
||||||
|
await baserowApiRequest.call(this, 'DELETE', endpoint, jwtToken);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
[{ json: { success: true } }],
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
}
|
}
|
||||||
|
} else if (resource === 'file') {
|
||||||
|
if (operation === 'upload-via-url') {
|
||||||
|
// ----------------------------------
|
||||||
|
// upload-via-url
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
const endpoint = `/api/database/rows/table/${tableId}/`;
|
// https://api.baserow.io/api/redoc/#tag/User-files/operation/upload_via_url
|
||||||
const createdRow = await baserowApiRequest.call(this, 'POST', endpoint, jwtToken, body);
|
|
||||||
|
|
||||||
mapper.idsToNames(createdRow as Row);
|
const url = this.getNodeParameter('url', i) as string;
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
const endpoint = '/api/user-files/upload-via-url/';
|
||||||
this.helpers.returnJsonArray(createdRow as Row),
|
const body = { url };
|
||||||
{ itemData: { item: i } },
|
const file = await baserowApiRequest.call(this, 'POST', endpoint, jwtToken, body);
|
||||||
);
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} else if (operation === 'update') {
|
|
||||||
// ----------------------------------
|
|
||||||
// update
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
// https://api.baserow.io/api/redoc/#operation/update_database_table_row
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(file),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} else if (operation === 'upload') {
|
||||||
|
// ----------------------------------
|
||||||
|
// upload
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
const rowId = this.getNodeParameter('rowId', i) as string;
|
// https://api.baserow.io/api/redoc/#tag/User-files/operation/upload_file
|
||||||
|
|
||||||
const body: IDataObject = {};
|
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
|
||||||
|
const { fileName, mimeType } = this.helpers.assertBinaryData(i, binaryPropertyName);
|
||||||
|
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
||||||
|
|
||||||
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
const file = await baserowFileUploadRequest.call(
|
||||||
| 'defineBelow'
|
this,
|
||||||
| 'autoMapInputData';
|
jwtToken,
|
||||||
|
binaryDataBuffer,
|
||||||
|
fileName as string,
|
||||||
|
mimeType,
|
||||||
|
);
|
||||||
|
|
||||||
if (dataToSend === 'autoMapInputData') {
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
const incomingKeys = Object.keys(items[i].json);
|
this.helpers.returnJsonArray(file),
|
||||||
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
|
{ itemData: { item: i } },
|
||||||
const inputsToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
for (const key of incomingKeys) {
|
|
||||||
if (inputsToIgnore.includes(key)) continue;
|
|
||||||
body[key] = items[i].json[key];
|
|
||||||
mapper.namesToIds(body);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const fieldsUi = this.getNodeParameter('fieldsUi.fieldValues', i, []) as FieldsUiValues;
|
|
||||||
for (const field of fieldsUi) {
|
|
||||||
body[`field_${field.fieldId}`] = field.fieldValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
|
||||||
const updatedRow = await baserowApiRequest.call(this, 'PATCH', endpoint, jwtToken, body);
|
|
||||||
|
|
||||||
mapper.idsToNames(updatedRow as Row);
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(updatedRow as Row),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} else if (operation === 'delete') {
|
|
||||||
// ----------------------------------
|
|
||||||
// delete
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
// https://api.baserow.io/api/redoc/#operation/delete_database_table_row
|
|
||||||
|
|
||||||
const rowId = this.getNodeParameter('rowId', i) as string;
|
|
||||||
|
|
||||||
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
|
||||||
await baserowApiRequest.call(this, 'DELETE', endpoint, jwtToken);
|
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
[{ json: { success: true } }],
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionData);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
|
|
|
@ -1,20 +1,57 @@
|
||||||
import type {
|
import type {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
IHttpRequestMethods,
|
IHttpRequestMethods,
|
||||||
|
IHttpRequestOptions,
|
||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
IRequestOptions,
|
IPollFunctions,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeApiError } from 'n8n-workflow';
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { Accumulator, BaserowCredentials, LoadedResource } from './types';
|
import type { Accumulator, BaserowCredentials, LoadedResource } from './types';
|
||||||
|
|
||||||
|
export async function baserowFileUploadRequest(
|
||||||
|
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
jwtToken: string,
|
||||||
|
file: Buffer,
|
||||||
|
fileName: string,
|
||||||
|
mimeType: string,
|
||||||
|
) {
|
||||||
|
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
headers: {
|
||||||
|
Authorization: `JWT ${jwtToken}`,
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
url: `${credentials.host}/api/user-files/upload-file/`,
|
||||||
|
json: false,
|
||||||
|
body: {
|
||||||
|
file: {
|
||||||
|
value: file,
|
||||||
|
options: {
|
||||||
|
filename: fileName,
|
||||||
|
contentType: mimeType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.helpers.httpRequest(options);
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a request to Baserow API.
|
* Make a request to Baserow API.
|
||||||
*/
|
*/
|
||||||
export async function baserowApiRequest(
|
export async function baserowApiRequest(
|
||||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||||
method: IHttpRequestMethods,
|
method: IHttpRequestMethods,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
jwtToken: string,
|
jwtToken: string,
|
||||||
|
@ -23,17 +60,26 @@ export async function baserowApiRequest(
|
||||||
) {
|
) {
|
||||||
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||||
|
|
||||||
const options: IRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `JWT ${jwtToken}`,
|
Authorization: `JWT ${jwtToken}`,
|
||||||
},
|
},
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
qs,
|
qs,
|
||||||
uri: `${credentials.host}${endpoint}`,
|
url: `${credentials.host}${endpoint}`,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (body.formData) {
|
||||||
|
options.json = false;
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
};
|
||||||
|
options.returnFullResponse = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(qs).length === 0) {
|
if (Object.keys(qs).length === 0) {
|
||||||
delete options.qs;
|
delete options.qs;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +89,7 @@ export async function baserowApiRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.helpers.request(options);
|
return await this.helpers.httpRequest(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
}
|
}
|
||||||
|
@ -53,27 +99,28 @@ export async function baserowApiRequest(
|
||||||
* Get all results from a paginated query to Baserow API.
|
* Get all results from a paginated query to Baserow API.
|
||||||
*/
|
*/
|
||||||
export async function baserowApiRequestAllItems(
|
export async function baserowApiRequestAllItems(
|
||||||
this: IExecuteFunctions,
|
this: IExecuteFunctions | IPollFunctions,
|
||||||
method: IHttpRequestMethods,
|
method: IHttpRequestMethods,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
jwtToken: string,
|
jwtToken: string,
|
||||||
body: IDataObject,
|
body: IDataObject,
|
||||||
qs: IDataObject = {},
|
qs: IDataObject = {},
|
||||||
|
returnAll: boolean = false,
|
||||||
|
limit: number = 0,
|
||||||
): Promise<IDataObject[]> {
|
): Promise<IDataObject[]> {
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
let responseData;
|
let responseData;
|
||||||
|
|
||||||
qs.page = 1;
|
qs.page = 1;
|
||||||
qs.size = 100;
|
if (!qs.size) {
|
||||||
|
qs.size = 100;
|
||||||
const returnAll = this.getNodeParameter('returnAll', 0, false);
|
}
|
||||||
const limit = this.getNodeParameter('limit', 0, 0);
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
responseData = await baserowApiRequest.call(this, method, endpoint, jwtToken, body, qs);
|
responseData = await baserowApiRequest.call(this, method, endpoint, jwtToken, body, qs);
|
||||||
returnData.push(...(responseData.results as IDataObject[]));
|
returnData.push(...(responseData.results as IDataObject[]));
|
||||||
|
|
||||||
if (!returnAll && returnData.length > limit) {
|
if (!returnAll && limit > 0 && returnData.length > limit) {
|
||||||
return returnData.slice(0, limit);
|
return returnData.slice(0, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,21 +134,21 @@ export async function baserowApiRequestAllItems(
|
||||||
* Get a JWT token based on Baserow account username and password.
|
* Get a JWT token based on Baserow account username and password.
|
||||||
*/
|
*/
|
||||||
export async function getJwtToken(
|
export async function getJwtToken(
|
||||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||||
{ username, password, host }: BaserowCredentials,
|
{ username, password, host }: BaserowCredentials,
|
||||||
) {
|
) {
|
||||||
const options: IRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
},
|
},
|
||||||
uri: `${host}/api/user/token-auth/`,
|
url: `${host}/api/user/token-auth/`,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { token } = (await this.helpers.request(options)) as { token: string };
|
const { token } = (await this.helpers.httpRequest(options)) as { token: string };
|
||||||
return token;
|
return token;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
|
@ -127,6 +174,15 @@ export async function getFieldNamesAndIds(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getTableFields(
|
||||||
|
this: IExecuteFunctions | IPollFunctions,
|
||||||
|
table: string,
|
||||||
|
jwtToken: string,
|
||||||
|
): Promise<LoadedResource[]> {
|
||||||
|
const endpoint = `/api/database/fields/table/${table}/`;
|
||||||
|
return await baserowApiRequest.call(this, 'GET', endpoint, jwtToken);
|
||||||
|
}
|
||||||
|
|
||||||
export const toOptions = (items: LoadedResource[]) =>
|
export const toOptions = (items: LoadedResource[]) =>
|
||||||
items.map(({ name, id }) => ({ name, value: id }));
|
items.map(({ name, id }) => ({ name, value: id }));
|
||||||
|
|
||||||
|
@ -140,15 +196,6 @@ export class TableFieldMapper {
|
||||||
|
|
||||||
mapIds = true;
|
mapIds = true;
|
||||||
|
|
||||||
async getTableFields(
|
|
||||||
this: IExecuteFunctions,
|
|
||||||
table: string,
|
|
||||||
jwtToken: string,
|
|
||||||
): Promise<LoadedResource[]> {
|
|
||||||
const endpoint = `/api/database/fields/table/${table}/`;
|
|
||||||
return await baserowApiRequest.call(this, 'GET', endpoint, jwtToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
createMappings(tableFields: LoadedResource[]) {
|
createMappings(tableFields: LoadedResource[]) {
|
||||||
this.nameToIdMapping = this.createNameToIdMapping(tableFields);
|
this.nameToIdMapping = this.createNameToIdMapping(tableFields);
|
||||||
this.idToNameMapping = this.createIdToNameMapping(tableFields);
|
this.idToNameMapping = this.createIdToNameMapping(tableFields);
|
||||||
|
@ -181,6 +228,10 @@ export class TableFieldMapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idToName(id: string) {
|
||||||
|
return this.idToNameMapping[id] ?? id;
|
||||||
|
}
|
||||||
|
|
||||||
namesToIds(obj: Record<string, unknown>) {
|
namesToIds(obj: Record<string, unknown>) {
|
||||||
Object.entries(obj).forEach(([key, value]) => {
|
Object.entries(obj).forEach(([key, value]) => {
|
||||||
if (this.nameToIdMapping[key] !== undefined) {
|
if (this.nameToIdMapping[key] !== undefined) {
|
||||||
|
@ -189,4 +240,8 @@ export class TableFieldMapper {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameToId(name: string) {
|
||||||
|
return this.nameToIdMapping[name] ?? name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,11 @@ export const operationFields: INodeProperties[] = [
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Database Name or ID',
|
displayName: 'Database Name or ID',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
},
|
||||||
|
},
|
||||||
name: 'databaseId',
|
name: 'databaseId',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -18,6 +23,11 @@ export const operationFields: INodeProperties[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Table Name or ID',
|
displayName: 'Table Name or ID',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
},
|
||||||
|
},
|
||||||
name: 'tableId',
|
name: 'tableId',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -442,4 +452,32 @@ export const operationFields: INodeProperties[] = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'File',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['file'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'file',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The file to upload',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'File URL',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['file'],
|
||||||
|
operation: ['upload-via-url'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The URL of the file to upload',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
173
packages/nodes-base/nodes/Baserow/test/GenericFunctions.test.ts
Normal file
173
packages/nodes-base/nodes/Baserow/test/GenericFunctions.test.ts
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IN8nHttpFullResponse,
|
||||||
|
IN8nHttpResponse,
|
||||||
|
INode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { baserowApiRequest, baserowFileUploadRequest, getJwtToken } from '../GenericFunctions';
|
||||||
|
import { username } from 'minifaker';
|
||||||
|
import { httpRequest } from 'n8n-core';
|
||||||
|
|
||||||
|
export const node: INode = {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow Upload',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'upload',
|
||||||
|
domain: 'file',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Baserow GenericFunctions', () => {
|
||||||
|
describe('getJwtToken', () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
token: 'jwt',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as unknown as IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
it('should return the JWT token', async () => {
|
||||||
|
const jwtToken = await getJwtToken.call(mockThis, {
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jwtToken).toBe('jwt');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('baserowApiRequest', () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
requestWithAuthentication: jest.fn().mockResolvedValue({ statusCode: 200 }),
|
||||||
|
httpRequest: jest.fn(),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn(),
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
it('should upload a file via url', async () => {
|
||||||
|
mockThis.getCredentials.mockResolvedValue({
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
mockThis.helpers.httpRequest.mockResolvedValue({
|
||||||
|
statusCode: 200,
|
||||||
|
body: {
|
||||||
|
size: 2147483647,
|
||||||
|
mime_type: 'string',
|
||||||
|
is_image: true,
|
||||||
|
image_width: 32767,
|
||||||
|
image_height: 32767,
|
||||||
|
uploaded_at: '2019-08-24T14:15:22Z',
|
||||||
|
url: 'http://example.com',
|
||||||
|
thumbnails: {
|
||||||
|
property1: null,
|
||||||
|
property2: null,
|
||||||
|
},
|
||||||
|
name: 'string',
|
||||||
|
original_name: 'string',
|
||||||
|
},
|
||||||
|
} as IN8nHttpFullResponse | IN8nHttpResponse);
|
||||||
|
|
||||||
|
const jwtToken = 'jwt';
|
||||||
|
const fileUrl = 'http://example.com/image.png';
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
file_url: fileUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
await baserowApiRequest.call(
|
||||||
|
mockThis,
|
||||||
|
'POST',
|
||||||
|
'/api/user-files/upload-via-url/',
|
||||||
|
jwtToken,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockThis.getCredentials).toHaveBeenCalledWith('baserowApi');
|
||||||
|
expect(mockThis.helpers.httpRequest).toHaveBeenCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: `JWT ${jwtToken}`,
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://my-host.com/api/user-files/upload-via-url/',
|
||||||
|
body,
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('baserowFileUploadRequest', () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
requestWithAuthentication: jest.fn().mockResolvedValue({ statusCode: 200 }),
|
||||||
|
httpRequest: jest.fn(),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn(),
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
it('should make an authenticated API file-upload request to Baserow', async () => {
|
||||||
|
mockThis.getCredentials.mockResolvedValue({
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
// see https://api.baserow.io/api/redoc/#tag/User-files/operation/upload_file
|
||||||
|
mockThis.helpers.httpRequest.mockResolvedValue({
|
||||||
|
statusCode: 200,
|
||||||
|
body: {
|
||||||
|
size: 2147483647,
|
||||||
|
mime_type: 'string',
|
||||||
|
is_image: true,
|
||||||
|
image_width: 32767,
|
||||||
|
image_height: 32767,
|
||||||
|
uploaded_at: '2019-08-24T14:15:22Z',
|
||||||
|
url: 'http://example.com',
|
||||||
|
thumbnails: {
|
||||||
|
property1: null,
|
||||||
|
property2: null,
|
||||||
|
},
|
||||||
|
name: 'string',
|
||||||
|
original_name: 'string',
|
||||||
|
},
|
||||||
|
} as IN8nHttpFullResponse | IN8nHttpResponse);
|
||||||
|
|
||||||
|
const jwtToken = 'jwt';
|
||||||
|
const file = Buffer.from('file');
|
||||||
|
const filename = 'filename.txt';
|
||||||
|
const mimeType = 'text/plain';
|
||||||
|
|
||||||
|
await baserowFileUploadRequest.call(mockThis, jwtToken, file, filename, mimeType);
|
||||||
|
|
||||||
|
expect(mockThis.getCredentials).toHaveBeenCalledWith('baserowApi');
|
||||||
|
|
||||||
|
expect(mockThis.helpers.httpRequest).toHaveBeenCalledWith({
|
||||||
|
body: {
|
||||||
|
file: {
|
||||||
|
options: { contentType: 'text/plain', filename: 'filename.txt' },
|
||||||
|
value: file,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers: { Authorization: 'JWT jwt', 'Content-Type': 'multipart/form-data' },
|
||||||
|
json: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://my-host.com/api/user-files/upload-file/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { baserowApiRequest, getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowApiRequest: jest.fn().mockResolvedValue({
|
||||||
|
size: 2147483647,
|
||||||
|
mime_type: 'string',
|
||||||
|
is_image: true,
|
||||||
|
image_width: 32767,
|
||||||
|
image_height: 32767,
|
||||||
|
uploaded_at: '2019-08-24T14:15:22Z',
|
||||||
|
url: 'http://example.com',
|
||||||
|
thumbnails: {
|
||||||
|
property1: null,
|
||||||
|
property2: null,
|
||||||
|
},
|
||||||
|
name: 'string',
|
||||||
|
original_name: 'string',
|
||||||
|
}),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: file', () => {
|
||||||
|
it('upload-via-url should upload a file via url', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow upload-via-url',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'upload-via-url',
|
||||||
|
resource: 'file',
|
||||||
|
tableId: 1,
|
||||||
|
rowId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'file';
|
||||||
|
case 'operation':
|
||||||
|
return 'upload-via-url';
|
||||||
|
case 'url':
|
||||||
|
return 'https://example.com/image.jpg';
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
expect(baserowApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'POST',
|
||||||
|
'/api/user-files/upload-via-url/',
|
||||||
|
'jwt',
|
||||||
|
{ url: 'https://example.com/image.jpg' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
image_height: 32767,
|
||||||
|
image_width: 32767,
|
||||||
|
is_image: true,
|
||||||
|
mime_type: 'string',
|
||||||
|
name: 'string',
|
||||||
|
original_name: 'string',
|
||||||
|
size: 2147483647,
|
||||||
|
thumbnails: { property1: null, property2: null },
|
||||||
|
uploaded_at: '2019-08-24T14:15:22Z',
|
||||||
|
url: 'http://example.com',
|
||||||
|
},
|
||||||
|
pairedItem: { item: 0 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
156
packages/nodes-base/nodes/Baserow/test/node/file/upload.test.ts
Normal file
156
packages/nodes-base/nodes/Baserow/test/node/file/upload.test.ts
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { baserowFileUploadRequest } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowFileUploadRequest: jest.fn().mockResolvedValue({
|
||||||
|
size: 2147483647,
|
||||||
|
mime_type: 'string',
|
||||||
|
is_image: true,
|
||||||
|
image_width: 32767,
|
||||||
|
image_height: 32767,
|
||||||
|
uploaded_at: '2019-08-24T14:15:22Z',
|
||||||
|
url: 'http://example.com',
|
||||||
|
thumbnails: {
|
||||||
|
property1: null,
|
||||||
|
property2: null,
|
||||||
|
},
|
||||||
|
name: 'string',
|
||||||
|
original_name: 'string',
|
||||||
|
}),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: file', () => {
|
||||||
|
it('upload should upload a file', async () => {
|
||||||
|
const buffer = Buffer.from('test');
|
||||||
|
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
assertBinaryData: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ fileName: 'test.png', mimeType: 'image/png' }),
|
||||||
|
getBinaryDataBuffer: jest.fn().mockResolvedValue(buffer),
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow upload',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'upload',
|
||||||
|
resource: 'file',
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'file';
|
||||||
|
case 'operation':
|
||||||
|
return 'upload';
|
||||||
|
case 'url':
|
||||||
|
return 'https://example.com/image.jpg';
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
case 'binaryPropertyName':
|
||||||
|
return 'data';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
|
||||||
|
expect(mockThis.helpers.assertBinaryData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockThis.helpers.getBinaryDataBuffer).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(baserowFileUploadRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowFileUploadRequest).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'jwt',
|
||||||
|
buffer,
|
||||||
|
'test.png',
|
||||||
|
'image/png',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
size: 2147483647,
|
||||||
|
mime_type: 'string',
|
||||||
|
is_image: true,
|
||||||
|
image_width: 32767,
|
||||||
|
image_height: 32767,
|
||||||
|
uploaded_at: '2019-08-24T14:15:22Z',
|
||||||
|
url: 'http://example.com',
|
||||||
|
thumbnails: {
|
||||||
|
property1: null,
|
||||||
|
property2: null,
|
||||||
|
},
|
||||||
|
name: 'string',
|
||||||
|
original_name: 'string',
|
||||||
|
},
|
||||||
|
pairedItem: { item: 0 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
140
packages/nodes-base/nodes/Baserow/test/node/rows/create.test.ts
Normal file
140
packages/nodes-base/nodes/Baserow/test/node/rows/create.test.ts
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { baserowApiRequest, getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowApiRequest: jest.fn().mockResolvedValue({
|
||||||
|
id: 1,
|
||||||
|
field_1: 'baz',
|
||||||
|
}),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
getTableFields: jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: row', () => {
|
||||||
|
it('create should create a record', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow get',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'create',
|
||||||
|
resource: 'row',
|
||||||
|
tableId: 1,
|
||||||
|
rowId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
my_field_name: 'new value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'row';
|
||||||
|
case 'operation':
|
||||||
|
return 'create';
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'dataToSend':
|
||||||
|
return 'autoMapInputData';
|
||||||
|
case 'inputsToIgnore':
|
||||||
|
return '';
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
|
||||||
|
expect(getTableFields).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'POST',
|
||||||
|
'/api/database/rows/table/1/',
|
||||||
|
'jwt',
|
||||||
|
{ field_1: 'new value' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: 1,
|
||||||
|
my_field_name: 'baz',
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
125
packages/nodes-base/nodes/Baserow/test/node/rows/delete.test.ts
Normal file
125
packages/nodes-base/nodes/Baserow/test/node/rows/delete.test.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { baserowApiRequest, getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowApiRequest: jest.fn().mockResolvedValue({
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
}),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
getTableFields: jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: row', () => {
|
||||||
|
it('delete should delete a record', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow delete',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'delete',
|
||||||
|
resource: 'row',
|
||||||
|
tableId: 1,
|
||||||
|
rowId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'row';
|
||||||
|
case 'operation':
|
||||||
|
return 'delete';
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'rowId':
|
||||||
|
return 1;
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
|
||||||
|
expect(getTableFields).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'DELETE',
|
||||||
|
'/api/database/rows/table/1/1/',
|
||||||
|
'jwt',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
137
packages/nodes-base/nodes/Baserow/test/node/rows/get.test.ts
Normal file
137
packages/nodes-base/nodes/Baserow/test/node/rows/get.test.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { baserowApiRequest, getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowApiRequest: jest.fn().mockResolvedValue({
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
}),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
getTableFields: jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: row', () => {
|
||||||
|
it('get should fetch a record', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow get',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'get',
|
||||||
|
resource: 'row',
|
||||||
|
tableId: 1,
|
||||||
|
rowId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'row';
|
||||||
|
case 'operation':
|
||||||
|
return 'get';
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'rowId':
|
||||||
|
return 1;
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
|
||||||
|
expect(getTableFields).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'GET',
|
||||||
|
'/api/database/rows/table/1/1/',
|
||||||
|
'jwt',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
my_field_name: 'baz',
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
123
packages/nodes-base/nodes/Baserow/test/node/rows/getAll.test.ts
Normal file
123
packages/nodes-base/nodes/Baserow/test/node/rows/getAll.test.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
getTableFields: jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: row', () => {
|
||||||
|
it('getAll should fetch all records', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow getAll',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'getAll',
|
||||||
|
resource: 'row',
|
||||||
|
tableId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'row';
|
||||||
|
case 'operation':
|
||||||
|
return 'getAll';
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
|
||||||
|
expect(getTableFields).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
my_field_name: 'baz',
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
144
packages/nodes-base/nodes/Baserow/test/node/rows/update.test.ts
Normal file
144
packages/nodes-base/nodes/Baserow/test/node/rows/update.test.ts
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { Baserow } from '../../../Baserow.node';
|
||||||
|
import { baserowApiRequest, getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowApiRequest: jest.fn().mockResolvedValue({
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'changed',
|
||||||
|
}),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
getTableFields: jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Node', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resource: row', () => {
|
||||||
|
it('update should update a record', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow get',
|
||||||
|
type: 'n8n-nodes-base.Baserow',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
operation: 'update',
|
||||||
|
resource: 'row',
|
||||||
|
tableId: 1,
|
||||||
|
rowId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
my_field_name: 'changed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'resource':
|
||||||
|
return 'row';
|
||||||
|
case 'operation':
|
||||||
|
return 'update';
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'rowId':
|
||||||
|
return 1;
|
||||||
|
case 'dataToSend':
|
||||||
|
return 'autoMapInputData';
|
||||||
|
case 'inputsToIgnore':
|
||||||
|
return '';
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
const node = new Baserow();
|
||||||
|
const response: INodeExecutionData[][] = await node.execute.call(mockThis);
|
||||||
|
|
||||||
|
expect(getTableFields).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequest).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'PATCH',
|
||||||
|
'/api/database/rows/table/1/1/',
|
||||||
|
'jwt',
|
||||||
|
{ field_1: 'changed' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
my_field_name: 'changed',
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,232 @@
|
||||||
|
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BaserowTrigger } from '../../../BaserowTrigger.node';
|
||||||
|
import { baserowApiRequestAllItems, getTableFields } from '../../../GenericFunctions';
|
||||||
|
import type { GetAllAdditionalOptions } from '../../../types';
|
||||||
|
|
||||||
|
jest.mock('../../../GenericFunctions', () => {
|
||||||
|
const originalModule: { [key: string]: any } = jest.requireActual('../../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
baserowApiRequestAllItems: jest.fn(),
|
||||||
|
getJwtToken: jest.fn().mockResolvedValue('jwt'),
|
||||||
|
getTableFields: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baserow Trigger Node', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(new Date('2024-12-10T12:57:41Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../GenericFunctions');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('trigger', () => {
|
||||||
|
it('should not trigger when no new record', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 0,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: [],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getWorkflowStaticData() {
|
||||||
|
return {
|
||||||
|
node: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getMode() {
|
||||||
|
return 'auto';
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow Trigger',
|
||||||
|
type: 'n8n-nodes-base.baserowTrigger',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
databaseId: 1,
|
||||||
|
tableId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'databaseId':
|
||||||
|
return 1;
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'additionalFields':
|
||||||
|
return { viewId: 1 };
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
getTableFields.mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
baserowApiRequestAllItems.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const node = new BaserowTrigger();
|
||||||
|
const response: INodeExecutionData[][] | null = await node.poll.call(mockThis);
|
||||||
|
|
||||||
|
expect(baserowApiRequestAllItems).toHaveBeenCalledTimes(1);
|
||||||
|
expect(getTableFields).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(response).toBeNull();
|
||||||
|
});
|
||||||
|
it('should trigger when new record', async () => {
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray,
|
||||||
|
constructExecutionMetaData,
|
||||||
|
httpRequest: jest.fn().mockResolvedValue({
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
getMode() {
|
||||||
|
return 'auto';
|
||||||
|
},
|
||||||
|
getWorkflowStaticData() {
|
||||||
|
return {
|
||||||
|
node: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return {
|
||||||
|
id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da',
|
||||||
|
name: 'Baserow Trigger',
|
||||||
|
type: 'n8n-nodes-base.baserowTrigger',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
databaseId: 1,
|
||||||
|
tableId: 1,
|
||||||
|
},
|
||||||
|
} as INode;
|
||||||
|
},
|
||||||
|
getCredentials: jest.fn().mockResolvedValue({
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
host: 'https://my-host.com',
|
||||||
|
}),
|
||||||
|
getInputData: () => [
|
||||||
|
{
|
||||||
|
json: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getNodeParameter: (parameter: string) => {
|
||||||
|
switch (parameter) {
|
||||||
|
case 'databaseId':
|
||||||
|
return 1;
|
||||||
|
case 'tableId':
|
||||||
|
return 1;
|
||||||
|
case 'triggerField':
|
||||||
|
return 'changed_at';
|
||||||
|
case 'additionalOptions':
|
||||||
|
return {} as GetAllAdditionalOptions;
|
||||||
|
case 'additionalFields':
|
||||||
|
return { viewId: 1 };
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueOnFail: () => false,
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
baserowApiRequestAllItems.mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
field_1: 'baz',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
getTableFields.mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'my_field_name',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const node = new BaserowTrigger();
|
||||||
|
const response: INodeExecutionData[][] | null = await node.poll.call(mockThis);
|
||||||
|
|
||||||
|
expect(getTableFields).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequestAllItems).toHaveBeenCalledTimes(1);
|
||||||
|
expect(baserowApiRequestAllItems).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'GET',
|
||||||
|
'/api/database/rows/table/1/',
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ filter__changed_at__date_after: 'Africa/Abidjan?2024-12-10T12:57:41Z', view_id: 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
id: 1,
|
||||||
|
order: '^-?\\(?:\\.\\)?$',
|
||||||
|
my_field_name: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -35,7 +35,9 @@ export type Row = Record<string, string>;
|
||||||
|
|
||||||
export type FieldsUiValues = Array<{
|
export type FieldsUiValues = Array<{
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
fieldValue: string;
|
fieldValue: string | string[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type Operation = 'create' | 'delete' | 'update' | 'get' | 'getAll';
|
export type Resource = 'file' | 'row' | 'table' | 'database';
|
||||||
|
export type RowOperation = 'create' | 'delete' | 'update' | 'get' | 'getAll';
|
||||||
|
export type FileOperation = 'upload' | 'upload-via-url';
|
||||||
|
|
Loading…
Reference in a new issue