first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.baserow",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/baserow/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.baserow/"
|
||||
}
|
||||
],
|
||||
"generic": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
import {
|
||||
type IExecuteFunctions,
|
||||
type IDataObject,
|
||||
type ILoadOptionsFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
NodeConnectionTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
baserowApiRequest,
|
||||
baserowApiRequestAllItems,
|
||||
getJwtToken,
|
||||
TableFieldMapper,
|
||||
toOptions,
|
||||
} from './GenericFunctions';
|
||||
import { operationFields } from './OperationDescription';
|
||||
import type {
|
||||
BaserowCredentials,
|
||||
FieldsUiValues,
|
||||
GetAllAdditionalOptions,
|
||||
LoadedResource,
|
||||
Operation,
|
||||
Row,
|
||||
} from './types';
|
||||
|
||||
export class Baserow implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Baserow',
|
||||
name: 'baserow',
|
||||
icon: 'file:baserow.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
description: 'Consume the Baserow API',
|
||||
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
||||
defaults: {
|
||||
name: 'Baserow',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
usableAsTool: true,
|
||||
credentials: [
|
||||
{
|
||||
name: 'baserowApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Row',
|
||||
value: 'row',
|
||||
},
|
||||
],
|
||||
default: 'row',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a row',
|
||||
action: 'Create a row',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a row',
|
||||
action: 'Delete a row',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a row',
|
||||
action: 'Get a row',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve many rows',
|
||||
action: 'Get many rows',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a row',
|
||||
action: 'Update a row',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
...operationFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getDatabaseIds(this: ILoadOptionsFunctions) {
|
||||
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const endpoint = '/api/applications/';
|
||||
const databases = (await baserowApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
endpoint,
|
||||
jwtToken,
|
||||
)) as LoadedResource[];
|
||||
// Baserow has different types of applications, we only want the databases
|
||||
// https://api.baserow.io/api/redoc/#tag/Applications/operation/list_all_applications
|
||||
return toOptions(databases.filter((database) => database.type === 'database'));
|
||||
},
|
||||
|
||||
async getTableIds(this: ILoadOptionsFunctions) {
|
||||
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const databaseId = this.getNodeParameter('databaseId', 0) as string;
|
||||
const endpoint = `/api/database/tables/database/${databaseId}/`;
|
||||
const tables = (await baserowApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
endpoint,
|
||||
jwtToken,
|
||||
)) as LoadedResource[];
|
||||
return toOptions(tables);
|
||||
},
|
||||
|
||||
async getTableFields(this: ILoadOptionsFunctions) {
|
||||
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||
const endpoint = `/api/database/fields/table/${tableId}/`;
|
||||
const fields = (await baserowApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
endpoint,
|
||||
jwtToken,
|
||||
)) as LoadedResource[];
|
||||
return toOptions(fields);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
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 credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||
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++) {
|
||||
try {
|
||||
if (operation === 'getAll') {
|
||||
// ----------------------------------
|
||||
// getAll
|
||||
// ----------------------------------
|
||||
|
||||
// https://api.baserow.io/api/redoc/#operation/list_database_table_rows
|
||||
|
||||
const { order, filters, filterType, search } = this.getNodeParameter(
|
||||
'additionalOptions',
|
||||
i,
|
||||
) as GetAllAdditionalOptions;
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
if (order?.fields) {
|
||||
qs.order_by = order.fields
|
||||
.map(({ field, direction }) => `${direction}${mapper.setField(field)}`)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
if (filters?.fields) {
|
||||
filters.fields.forEach(({ field, operator, value }) => {
|
||||
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,
|
||||
)) 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 (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) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message, json: {}, itemIndex: i });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHttpRequestMethods,
|
||||
ILoadOptionsFunctions,
|
||||
IRequestOptions,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import type { Accumulator, BaserowCredentials, LoadedResource } from './types';
|
||||
|
||||
/**
|
||||
* Make a request to Baserow API.
|
||||
*/
|
||||
export async function baserowApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
jwtToken: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
) {
|
||||
const credentials = await this.getCredentials<BaserowCredentials>('baserowApi');
|
||||
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
Authorization: `JWT ${jwtToken}`,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: `${credentials.host}${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (Object.keys(qs).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
if (Object.keys(body).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request(options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results from a paginated query to Baserow API.
|
||||
*/
|
||||
export async function baserowApiRequestAllItems(
|
||||
this: IExecuteFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
jwtToken: string,
|
||||
body: IDataObject,
|
||||
qs: IDataObject = {},
|
||||
): Promise<IDataObject[]> {
|
||||
const returnData: IDataObject[] = [];
|
||||
let responseData;
|
||||
|
||||
qs.page = 1;
|
||||
qs.size = 100;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0, false);
|
||||
const limit = this.getNodeParameter('limit', 0, 0);
|
||||
|
||||
do {
|
||||
responseData = await baserowApiRequest.call(this, method, endpoint, jwtToken, body, qs);
|
||||
returnData.push(...(responseData.results as IDataObject[]));
|
||||
|
||||
if (!returnAll && returnData.length > limit) {
|
||||
return returnData.slice(0, limit);
|
||||
}
|
||||
|
||||
qs.page += 1;
|
||||
} while (responseData.next !== null);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JWT token based on Baserow account username and password.
|
||||
*/
|
||||
export async function getJwtToken(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
{ username, password, host }: BaserowCredentials,
|
||||
) {
|
||||
const options: IRequestOptions = {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
uri: `${host}/api/user/token-auth/`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
try {
|
||||
const { token } = (await this.helpers.request(options)) as { token: string };
|
||||
return token;
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFieldNamesAndIds(
|
||||
this: IExecuteFunctions,
|
||||
tableId: string,
|
||||
jwtToken: string,
|
||||
) {
|
||||
const endpoint = `/api/database/fields/table/${tableId}/`;
|
||||
const response = (await baserowApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
endpoint,
|
||||
jwtToken,
|
||||
)) as LoadedResource[];
|
||||
|
||||
return {
|
||||
names: response.map((field) => field.name),
|
||||
ids: response.map((field) => `field_${field.id}`),
|
||||
};
|
||||
}
|
||||
|
||||
export const toOptions = (items: LoadedResource[]) =>
|
||||
items.map(({ name, id }) => ({ name, value: id }));
|
||||
|
||||
/**
|
||||
* Responsible for mapping field IDs `field_n` to names and vice versa.
|
||||
*/
|
||||
export class TableFieldMapper {
|
||||
nameToIdMapping: Record<string, string> = {};
|
||||
|
||||
idToNameMapping: Record<string, string> = {};
|
||||
|
||||
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[]) {
|
||||
this.nameToIdMapping = this.createNameToIdMapping(tableFields);
|
||||
this.idToNameMapping = this.createIdToNameMapping(tableFields);
|
||||
}
|
||||
|
||||
private createIdToNameMapping(responseData: LoadedResource[]) {
|
||||
return responseData.reduce<Accumulator>((acc, cur) => {
|
||||
acc[`field_${cur.id}`] = cur.name;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
private createNameToIdMapping(responseData: LoadedResource[]) {
|
||||
return responseData.reduce<Accumulator>((acc, cur) => {
|
||||
acc[cur.name] = `field_${cur.id}`;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
setField(field: string) {
|
||||
return this.mapIds ? field : (this.nameToIdMapping[field] ?? field);
|
||||
}
|
||||
|
||||
idsToNames(obj: Record<string, unknown>) {
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (this.idToNameMapping[key] !== undefined) {
|
||||
delete obj[key];
|
||||
obj[this.idToNameMapping[key]] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
namesToIds(obj: Record<string, unknown>) {
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (this.nameToIdMapping[key] !== undefined) {
|
||||
delete obj[key];
|
||||
obj[this.nameToIdMapping[key]] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const operationFields: INodeProperties[] = [
|
||||
// ----------------------------------
|
||||
// shared
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Database Name or ID',
|
||||
name: 'databaseId',
|
||||
type: 'options',
|
||||
default: '',
|
||||
required: true,
|
||||
description:
|
||||
'Database to operate on. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabaseIds',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Table Name or ID',
|
||||
name: 'tableId',
|
||||
type: 'options',
|
||||
default: '',
|
||||
required: true,
|
||||
description:
|
||||
'Table to operate on. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['databaseId'],
|
||||
loadOptionsMethod: 'getTableIds',
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// get
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Row ID',
|
||||
name: 'rowId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'ID of the row to return',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// update
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Row ID',
|
||||
name: 'rowId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'ID of the row to update',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// create/update
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Data to Send',
|
||||
name: 'dataToSend',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMapInputData',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Define Below for Each Column',
|
||||
value: 'defineBelow',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create', 'update'],
|
||||
},
|
||||
},
|
||||
default: 'defineBelow',
|
||||
description: 'Whether to insert the input data this node receives in the new row',
|
||||
},
|
||||
{
|
||||
displayName: 'Inputs to Ignore',
|
||||
name: 'inputsToIgnore',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create', 'update'],
|
||||
dataToSend: ['autoMapInputData'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
|
||||
placeholder: 'Enter properties...',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValueButtonText: 'Add Field to Send',
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create', 'update'],
|
||||
dataToSend: ['defineBelow'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'fieldValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Name or ID',
|
||||
name: 'fieldId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableId'],
|
||||
loadOptionsMethod: 'getTableFields',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Field Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Row ID',
|
||||
name: 'rowId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'ID of the row to delete',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// getAll
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'additionalOptions',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
placeholder: 'Add Filter',
|
||||
description: 'Filter rows based on comparison operators',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'fields',
|
||||
displayName: 'Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Name or ID',
|
||||
name: 'field',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description:
|
||||
'Field to compare. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableId'],
|
||||
loadOptionsMethod: 'getTableFields',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'operator',
|
||||
description: 'Operator to compare field and value with',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Contains',
|
||||
value: 'contains',
|
||||
description: 'Field contains value',
|
||||
},
|
||||
{
|
||||
name: 'Contains Not',
|
||||
value: 'contains_not',
|
||||
description: 'Field does not contain value',
|
||||
},
|
||||
{
|
||||
name: 'Date After Date',
|
||||
value: 'date_after',
|
||||
description: "Field after this date. Format: 'YYYY-MM-DD'.",
|
||||
},
|
||||
{
|
||||
name: 'Date Before Date',
|
||||
value: 'date_before',
|
||||
description: "Field before this date. Format: 'YYYY-MM-DD'.",
|
||||
},
|
||||
{
|
||||
name: 'Date Equal',
|
||||
value: 'date_equal',
|
||||
description: "Field is date. Format: 'YYYY-MM-DD'.",
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Month',
|
||||
value: 'date_equals_month',
|
||||
description: 'Field in this month. Format: string.',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Today',
|
||||
value: 'date_equals_today',
|
||||
description: 'Field is today. Format: string.',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Year',
|
||||
value: 'date_equals_year',
|
||||
description: 'Field in this year. Format: string.',
|
||||
},
|
||||
{
|
||||
name: 'Date Not Equal',
|
||||
value: 'date_not_equal',
|
||||
description: "Field is not date. Format: 'YYYY-MM-DD'.",
|
||||
},
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
description: 'Field is equal to value',
|
||||
},
|
||||
{
|
||||
name: 'Filename Contains',
|
||||
value: 'filename_contains',
|
||||
description: 'Field filename contains value',
|
||||
},
|
||||
{
|
||||
name: 'Higher Than',
|
||||
value: 'higher_than',
|
||||
description: 'Field is higher than value',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'empty',
|
||||
description: 'Field is empty',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Empty',
|
||||
value: 'not_empty',
|
||||
description: 'Field is not empty',
|
||||
},
|
||||
{
|
||||
name: 'Is True',
|
||||
value: 'boolean',
|
||||
description: 'Boolean field is true',
|
||||
},
|
||||
{
|
||||
name: 'Link Row Does Not Have',
|
||||
value: 'link_row_has_not',
|
||||
description: 'Field does not have link ID',
|
||||
},
|
||||
{
|
||||
name: 'Link Row Has',
|
||||
value: 'link_row_has',
|
||||
description: 'Field has link ID',
|
||||
},
|
||||
{
|
||||
name: 'Lower Than',
|
||||
value: 'lower_than',
|
||||
description: 'Field is lower than value',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'not_equal',
|
||||
description: 'Field is not equal to value',
|
||||
},
|
||||
{
|
||||
name: 'Single Select Equal',
|
||||
value: 'single_select_equal',
|
||||
description: 'Field selected option is value',
|
||||
},
|
||||
{
|
||||
name: 'Single Select Not Equal',
|
||||
value: 'single_select_not_equal',
|
||||
description: 'Field selected option is not value',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value to compare to',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Filter Type',
|
||||
name: 'filterType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AND',
|
||||
value: 'AND',
|
||||
description: 'Indicates that the rows must match all the provided filters',
|
||||
},
|
||||
{
|
||||
name: 'OR',
|
||||
value: 'OR',
|
||||
description: 'Indicates that the rows only have to match one of the filters',
|
||||
},
|
||||
],
|
||||
default: 'AND',
|
||||
description:
|
||||
'This works only if two or more filters are provided. Defaults to <code>AND</code>',
|
||||
},
|
||||
{
|
||||
displayName: 'Search Term',
|
||||
name: 'search',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Text to match (can be in any column)',
|
||||
},
|
||||
{
|
||||
displayName: 'Sorting',
|
||||
name: 'order',
|
||||
placeholder: 'Add Sort Order',
|
||||
description: 'Set the sort order of the result rows',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'fields',
|
||||
displayName: 'Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Name or ID',
|
||||
name: 'field',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description:
|
||||
'Field name to sort by. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableId'],
|
||||
loadOptionsMethod: 'getTableFields',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Direction',
|
||||
name: 'direction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ASC',
|
||||
value: '',
|
||||
description: 'Sort in ascending order',
|
||||
},
|
||||
{
|
||||
name: 'DESC',
|
||||
value: '-',
|
||||
description: 'Sort in descending order',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Sort direction, either ascending or descending',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,141 @@
|
||||
/* eslint-disable n8n-nodes-base/node-param-display-name-miscased */
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
baserowApiRequest,
|
||||
baserowApiRequestAllItems,
|
||||
getJwtToken,
|
||||
getFieldNamesAndIds,
|
||||
toOptions,
|
||||
TableFieldMapper,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
describe('Baserow > GenericFunctions', () => {
|
||||
const mockExecuteFunctions: any = {
|
||||
helpers: {
|
||||
request: jest.fn(),
|
||||
},
|
||||
getCredentials: jest.fn().mockResolvedValue({
|
||||
username: 'nathan@n8n.io',
|
||||
password: 'this-is-a-fake-password',
|
||||
host: 'https://api.baserow.io',
|
||||
}),
|
||||
getNodeParameter: jest.fn(),
|
||||
getNode: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('baserowApiRequest', () => {
|
||||
it('should return data on success', async () => {
|
||||
mockExecuteFunctions.helpers.request.mockResolvedValue({ success: true });
|
||||
const result = await baserowApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/endpoint',
|
||||
'testJwt',
|
||||
);
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockExecuteFunctions.helpers.request).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw NodeApiError on failure', async () => {
|
||||
mockExecuteFunctions.helpers.request.mockRejectedValue({ error: 'fail' });
|
||||
await expect(
|
||||
baserowApiRequest.call(mockExecuteFunctions, 'GET', '/endpoint', 'testJwt'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('baserowApiRequestAllItems', () => {
|
||||
it('should accumulate all pages', async () => {
|
||||
mockExecuteFunctions.getNodeParameter
|
||||
.mockReturnValueOnce(true) // returnAll
|
||||
.mockReturnValue(1000); // limit
|
||||
mockExecuteFunctions.helpers.request
|
||||
.mockResolvedValueOnce({ results: [{ data: 1 }], next: 'page2' })
|
||||
.mockResolvedValueOnce({ results: [{ data: 2 }], next: null });
|
||||
|
||||
const result = await baserowApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/endpoint',
|
||||
'testJwt',
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result).toEqual([{ data: 1 }, { data: 2 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getJwtToken', () => {
|
||||
it('should return a token', async () => {
|
||||
mockExecuteFunctions.helpers.request.mockResolvedValue({ token: 'mockToken' });
|
||||
const result = await getJwtToken.call(mockExecuteFunctions, {
|
||||
username: 'nathan@n8n.io',
|
||||
password: 'this-is-a-fake-password',
|
||||
host: 'https://api.baserow.io',
|
||||
});
|
||||
expect(result).toBe('mockToken');
|
||||
});
|
||||
|
||||
it('should throw NodeApiError if request fails', async () => {
|
||||
mockExecuteFunctions.helpers.request.mockRejectedValue({ error: 'fail' });
|
||||
await expect(
|
||||
getJwtToken.call(mockExecuteFunctions, {
|
||||
username: 'nathan@n8n.io',
|
||||
password: 'this-is-a-fake-password',
|
||||
host: 'https://api.baserow.io',
|
||||
}),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFieldNamesAndIds', () => {
|
||||
it('should return field names and ids', async () => {
|
||||
mockExecuteFunctions.helpers.request.mockResolvedValue([
|
||||
{ id: 1, name: 'field1' },
|
||||
{ id: 2, name: 'field2' },
|
||||
]);
|
||||
const result = await getFieldNamesAndIds.call(mockExecuteFunctions, '1', 'testJwt');
|
||||
expect(result).toEqual({
|
||||
names: ['field1', 'field2'],
|
||||
ids: ['field_1', 'field_2'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toOptions', () => {
|
||||
it('should map items to options', () => {
|
||||
const result = toOptions([
|
||||
{ id: 1, name: 'field1' },
|
||||
{ id: 2, name: 'field2' },
|
||||
]);
|
||||
expect(result).toEqual([
|
||||
{ name: 'field1', value: 1 },
|
||||
{ name: 'field2', value: 2 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TableFieldMapper', () => {
|
||||
it('should create name-to-id and id-to-name mappings', () => {
|
||||
const mapper = new TableFieldMapper();
|
||||
mapper.createMappings([
|
||||
{ id: 1, name: 'field1' },
|
||||
{ id: 2, name: 'field2' },
|
||||
]);
|
||||
expect(mapper.nameToIdMapping).toEqual({
|
||||
field1: 'field_1',
|
||||
field2: 'field_2',
|
||||
});
|
||||
expect(mapper.idToNameMapping).toEqual({
|
||||
field_1: 'field1',
|
||||
field_2: 'field2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
export const fieldsResponse = [
|
||||
{
|
||||
id: 3799030,
|
||||
table_id: 482710,
|
||||
name: 'Name',
|
||||
order: 0,
|
||||
type: 'text',
|
||||
primary: true,
|
||||
read_only: false,
|
||||
immutable_type: false,
|
||||
immutable_properties: false,
|
||||
description: null,
|
||||
text_default: '',
|
||||
},
|
||||
{
|
||||
id: 3799031,
|
||||
table_id: 482710,
|
||||
name: 'Notes',
|
||||
order: 1,
|
||||
type: 'long_text',
|
||||
primary: false,
|
||||
read_only: false,
|
||||
immutable_type: false,
|
||||
immutable_properties: false,
|
||||
description: null,
|
||||
long_text_enable_rich_text: false,
|
||||
},
|
||||
{
|
||||
id: 3799032,
|
||||
table_id: 482710,
|
||||
name: 'Active',
|
||||
order: 2,
|
||||
type: 'boolean',
|
||||
primary: false,
|
||||
read_only: false,
|
||||
immutable_type: false,
|
||||
immutable_properties: false,
|
||||
description: null,
|
||||
},
|
||||
];
|
||||
export const getResponse = {
|
||||
id: 1,
|
||||
order: '1.00000000000000000000',
|
||||
field_3799030: 'Foo',
|
||||
field_3799031: 'bar',
|
||||
field_3799032: false,
|
||||
};
|
||||
|
||||
export const getAllResponse = {
|
||||
count: 2,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
order: '1.00000000000000000000',
|
||||
field_3799030: 'Foo',
|
||||
field_3799031: 'bar',
|
||||
field_3799032: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
order: '2.00000000000000000000',
|
||||
field_3799030: 'Bar',
|
||||
field_3799031: 'foo',
|
||||
field_3799032: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const createResponse = {
|
||||
id: 3,
|
||||
order: '3.00000000000000000000',
|
||||
field_3799030: 'Nathan',
|
||||
field_3799031: 'testing',
|
||||
field_3799032: false,
|
||||
};
|
||||
|
||||
export const updateResponse = {
|
||||
id: 3,
|
||||
order: '3.00000000000000000000',
|
||||
field_3799030: 'Nathan',
|
||||
field_3799031: 'testing',
|
||||
field_3799032: true,
|
||||
};
|
||||
@@ -0,0 +1,328 @@
|
||||
{
|
||||
"name": "Baserow Test Workflow",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [-20, 400],
|
||||
"id": "fccdbab1-aa37-4606-8744-520e19a90a01",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "get",
|
||||
"databaseId": 199364,
|
||||
"tableId": 482710,
|
||||
"rowId": "1"
|
||||
},
|
||||
"type": "n8n-nodes-base.baserow",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "56b90399-9400-4fff-84b5-75430102119d",
|
||||
"name": "Baserow > Get",
|
||||
"credentials": {
|
||||
"baserowApi": {
|
||||
"id": "SWSFqWDWdnC74WMJ",
|
||||
"name": "NodeQA"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"databaseId": 199364,
|
||||
"tableId": 482710,
|
||||
"limit": 2,
|
||||
"additionalOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.baserow",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 200],
|
||||
"id": "8a183257-3297-4ec9-bcae-aefb1f563355",
|
||||
"name": "Baserow > Get Many",
|
||||
"credentials": {
|
||||
"baserowApi": {
|
||||
"id": "SWSFqWDWdnC74WMJ",
|
||||
"name": "NodeQA"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 0],
|
||||
"id": "5afa3206-a796-41dc-a2c5-54b5b9f2fbf4",
|
||||
"name": "GetResponse"
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 200],
|
||||
"id": "d61f9e61-856a-4590-aad1-d7ef88e7185b",
|
||||
"name": "GetMany Response"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "create",
|
||||
"databaseId": 199364,
|
||||
"tableId": 482710,
|
||||
"fieldsUi": {
|
||||
"fieldValues": [
|
||||
{
|
||||
"fieldId": 3799030,
|
||||
"fieldValue": "Nathan"
|
||||
},
|
||||
{
|
||||
"fieldId": 3799031,
|
||||
"fieldValue": "testing"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.baserow",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 400],
|
||||
"id": "e3c5b6c4-b4b3-40ac-8d12-2a240c174e81",
|
||||
"name": "Baserow > Create",
|
||||
"credentials": {
|
||||
"baserowApi": {
|
||||
"id": "SWSFqWDWdnC74WMJ",
|
||||
"name": "NodeQA"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 400],
|
||||
"id": "bb7648a4-1d81-421f-927d-0942325db4c9",
|
||||
"name": "Create Response"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "update",
|
||||
"databaseId": 199364,
|
||||
"tableId": 482710,
|
||||
"rowId": "3",
|
||||
"fieldsUi": {
|
||||
"fieldValues": [
|
||||
{
|
||||
"fieldId": 3799032,
|
||||
"fieldValue": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.baserow",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 600],
|
||||
"id": "557a163a-0d60-4dbf-b4e0-342533b56ad5",
|
||||
"name": "Baserow > Update",
|
||||
"credentials": {
|
||||
"baserowApi": {
|
||||
"id": "SWSFqWDWdnC74WMJ",
|
||||
"name": "NodeQA"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 600],
|
||||
"id": "34992c77-25d1-4af4-a667-ca452049908d",
|
||||
"name": "Update Response"
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 800],
|
||||
"id": "76e75c2a-2c6e-4307-ad67-fbc3d442ea53",
|
||||
"name": "Delete Response"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "delete",
|
||||
"databaseId": 199364,
|
||||
"tableId": 482710,
|
||||
"rowId": "3"
|
||||
},
|
||||
"type": "n8n-nodes-base.baserow",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 800],
|
||||
"id": "f1c84a01-f514-49c2-a54e-548b15dc91fd",
|
||||
"name": "Baserow > Delete",
|
||||
"credentials": {
|
||||
"baserowApi": {
|
||||
"id": "SWSFqWDWdnC74WMJ",
|
||||
"name": "NodeQA"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"GetResponse": [
|
||||
{
|
||||
"json": {
|
||||
"id": 1,
|
||||
"order": "1.00000000000000000000",
|
||||
"Name": "Foo",
|
||||
"Notes": "bar",
|
||||
"Active": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"GetMany Response": [
|
||||
{
|
||||
"json": {
|
||||
"id": 1,
|
||||
"order": "1.00000000000000000000",
|
||||
"Name": "Foo",
|
||||
"Notes": "bar",
|
||||
"Active": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 2,
|
||||
"order": "2.00000000000000000000",
|
||||
"Name": "Bar",
|
||||
"Notes": "foo",
|
||||
"Active": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"Create Response": [
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"order": "3.00000000000000000000",
|
||||
"Name": "Nathan",
|
||||
"Notes": "testing",
|
||||
"Active": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"Update Response": [
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"order": "3.00000000000000000000",
|
||||
"Name": "Nathan",
|
||||
"Notes": "testing",
|
||||
"Active": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"Delete Response": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Baserow > Get",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Baserow > Get Many",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Baserow > Create",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Baserow > Update",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Baserow > Delete",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Baserow > Get": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "GetResponse",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Baserow > Get Many": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "GetMany Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Baserow > Create": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Baserow > Update": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Baserow > Delete": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Delete Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "72dbd4c1-80b9-4a22-a298-7bcd577e2f0c",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "0fa937d34dcabeff4bd6480d3b42cc95edf3bc20e6810819086ef1ce2623639d"
|
||||
},
|
||||
"id": "2IrLMcqSSFfSyj76",
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
fieldsResponse,
|
||||
getAllResponse,
|
||||
getResponse,
|
||||
updateResponse,
|
||||
} from './apiResponses';
|
||||
|
||||
describe('Baserow > Workflows', () => {
|
||||
const credentials = {
|
||||
baserowApi: {
|
||||
host: 'https://api.baserow.io',
|
||||
username: 'nathan@n8n.io',
|
||||
password: 'fake-password',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Run workflow', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://api.baserow.io');
|
||||
// Baserow > Get Token
|
||||
mock
|
||||
.persist()
|
||||
.post('/api/user/token-auth/', { username: 'nathan@n8n.io', password: 'fake-password' })
|
||||
.reply(200, {
|
||||
token: 'fake-jwt-token',
|
||||
});
|
||||
// Baserow > Get Fields
|
||||
mock.get('/api/database/fields/table/482710/').reply(200, fieldsResponse);
|
||||
// Baserow > Get Row
|
||||
mock.get('/api/database/rows/table/482710/1/').reply(200, getResponse);
|
||||
// Baserow > Get all rows
|
||||
mock
|
||||
.get('/api/database/rows/table/482710/')
|
||||
.query({ page: 1, size: 100 })
|
||||
.reply(200, getAllResponse);
|
||||
// Baserow > Create Row
|
||||
mock
|
||||
.post('/api/database/rows/table/482710/', {
|
||||
field_3799030: 'Nathan',
|
||||
field_3799031: 'testing',
|
||||
})
|
||||
.reply(200, createResponse);
|
||||
// Baserow > Update Row
|
||||
mock
|
||||
.patch('/api/database/rows/table/482710/3/', {
|
||||
field_3799032: 'true',
|
||||
})
|
||||
.reply(200, updateResponse);
|
||||
// Baserow > Delete Row
|
||||
mock.delete('/api/database/rows/table/482710/3/').reply(200, {});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({ credentials });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Warstwa_1" data-name="Warstwa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><defs><style>.cls-1{fill:#4d68c4;}.cls-2{fill:#5190ef;}.cls-3{fill:#2bc3f1;}</style></defs><title>baserow_logo_glyph</title><rect class="cls-1" y="451.65233" width="374.17383" height="148.34767" rx="32.25562" transform="translate(374.17383 1051.65233) rotate(180)"/><path class="cls-2" d="M32.256,225.82617H567.744A32.256,32.256,0,0,1,600,258.08217v83.83567a32.256,32.256,0,0,1-32.256,32.256H32.25562A32.25562,32.25562,0,0,1,0,341.91821v-83.836A32.256,32.256,0,0,1,32.256,225.82617Z" transform="translate(600 600) rotate(180)"/><rect class="cls-3" x="225.82617" width="374.17383" height="148.34767" rx="32.256" transform="translate(825.82617 148.34767) rotate(180)"/><rect class="cls-1" x="451.65233" y="451.65233" width="148.34767" height="148.34767" rx="32.256" transform="translate(1051.65233 1051.65234) rotate(-180)"/><rect class="cls-3" width="148.34767" height="148.34766" rx="32.256"/></svg>
|
||||
|
After Width: | Height: | Size: 999 B |
@@ -0,0 +1,42 @@
|
||||
export type BaserowCredentials = {
|
||||
username: string;
|
||||
password: string;
|
||||
host: string;
|
||||
};
|
||||
|
||||
export type GetAllAdditionalOptions = {
|
||||
order?: {
|
||||
fields: Array<{
|
||||
field: string;
|
||||
direction: string;
|
||||
}>;
|
||||
};
|
||||
filters?: {
|
||||
fields: Array<{
|
||||
field: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
}>;
|
||||
};
|
||||
filterType: string;
|
||||
search: string;
|
||||
};
|
||||
|
||||
export type LoadedResource = {
|
||||
id: number;
|
||||
name: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export type Accumulator = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type Row = Record<string, string>;
|
||||
|
||||
export type FieldsUiValues = Array<{
|
||||
fieldId: string;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
|
||||
export type Operation = 'create' | 'delete' | 'update' | 'get' | 'getAll';
|
||||
Reference in New Issue
Block a user