mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
✨ Add Baserow node (#1938)
* Add Baserow node * ⚡ Add JWT method to credentials * ⚡ Refactor Baserow node * 🔨 Refactor to add continueOnFail * ⚡ Extract table ID from URL * ✏️ Reword descriptions per feedback * 🔥 Remove API token auth per feedback * 🔨 Reformat for readability * 💡 Fix issues identified by nodelinter * ⚡ Add columns param to create and update * ⚡ Refactor JWT token retrieval * ⚡ Add resource loaders * ⚡ Improvements * ⚡ Improve types * ⚡ Clean up descriptions and comments * ⚡ Make minor UX improvements * ⚡ Update input data description * 🔨 Refactor data to send for create and update * ⚡ Add text to description * ⚡ Small improvements * ⚡ Change parameter names and descriptions Co-authored-by: Jeremie Pardou-Piquemal <571533+jrmi@users.noreply.github.com> Co-authored-by: ricardo <ricardoespinoza105@gmail.com> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
cce804a534
commit
b058aee6c1
34
packages/nodes-base/credentials/BaserowApi.credentials.ts
Normal file
34
packages/nodes-base/credentials/BaserowApi.credentials.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
// https://api.baserow.io/api/redoc/#section/Authentication
|
||||
|
||||
export class BaserowApi implements ICredentialType {
|
||||
name = 'baserowApi';
|
||||
displayName = 'Baserow API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Host',
|
||||
name: 'host',
|
||||
type: 'string',
|
||||
default: 'https://api.baserow.io',
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
19
packages/nodes-base/nodes/Baserow/Baserow.node.json
Normal file
19
packages/nodes-base/nodes/Baserow/Baserow.node.json
Normal file
|
@ -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/credentials/baserow"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.baserow/"
|
||||
}
|
||||
],
|
||||
"generic": []
|
||||
}
|
||||
}
|
318
packages/nodes-base/nodes/Baserow/Baserow.node.ts
Normal file
318
packages/nodes-base/nodes/Baserow/Baserow.node.ts
Normal file
|
@ -0,0 +1,318 @@
|
|||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
baserowApiRequest,
|
||||
baserowApiRequestAllItems,
|
||||
getJwtToken,
|
||||
TableFieldMapper,
|
||||
toOptions,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
operationFields
|
||||
} from './OperationDescription';
|
||||
|
||||
import {
|
||||
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',
|
||||
color: '#00a2ce',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'baserowApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Row',
|
||||
value: 'row',
|
||||
},
|
||||
],
|
||||
default: 'row',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'row',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a row',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a row',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a row',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all rows',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a row',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
...operationFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getDatabaseIds(this: ILoadOptionsFunctions) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const endpoint = '/api/applications/';
|
||||
const databases = await baserowApiRequest.call(this, 'GET', endpoint, {}, {}, jwtToken) as LoadedResource[];
|
||||
return toOptions(databases);
|
||||
},
|
||||
|
||||
async getTableIds(this: ILoadOptionsFunctions) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
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 = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
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: IDataObject[] = [];
|
||||
const operation = this.getNodeParameter('operation', 0) as Operation;
|
||||
|
||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
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', 0) 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, {}, qs, jwtToken) as Row[];
|
||||
|
||||
rows.forEach(row => mapper.idsToNames(row));
|
||||
|
||||
returnData.push(...rows);
|
||||
|
||||
} 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);
|
||||
|
||||
returnData.push(row);
|
||||
|
||||
} 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' | 'autoMapColumns';
|
||||
|
||||
if (dataToSend === 'autoMapColumns') {
|
||||
|
||||
const incomingKeys = Object.keys(items[i].json);
|
||||
const rawInputsToIgnore = this.getNodeParameter('inputDataToIgnore', 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 fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as FieldsUiValues;
|
||||
for (const field of fields) {
|
||||
body[`field_${field.fieldId}`] = field.fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = `/api/database/rows/table/${tableId}/`;
|
||||
const createdRow = await baserowApiRequest.call(this, 'POST', endpoint, body, {}, jwtToken);
|
||||
|
||||
mapper.idsToNames(createdRow);
|
||||
|
||||
returnData.push(createdRow);
|
||||
|
||||
} 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 fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as FieldsUiValues;
|
||||
for (const field of fields) {
|
||||
body[`field_${field.fieldId}`] = field.fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = `/api/database/rows/table/${tableId}/${rowId}/`;
|
||||
const updatedRow = await baserowApiRequest.call(this, 'PATCH', endpoint, body, {}, jwtToken);
|
||||
|
||||
mapper.idsToNames(updatedRow);
|
||||
|
||||
returnData.push(updatedRow);
|
||||
|
||||
} 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);
|
||||
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
198
packages/nodes-base/nodes/Baserow/GenericFunctions.ts
Normal file
198
packages/nodes-base/nodes/Baserow/GenericFunctions.ts
Normal file
|
@ -0,0 +1,198 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Accumulator,
|
||||
BaserowCredentials,
|
||||
LoadedResource,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Make a request to Baserow API.
|
||||
*/
|
||||
export async function baserowApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
jwtToken: string,
|
||||
) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results from a paginated query to Baserow API.
|
||||
*/
|
||||
export async function baserowApiRequestAllItems(
|
||||
this: IExecuteFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject,
|
||||
qs: IDataObject = {},
|
||||
jwtToken: string,
|
||||
): Promise<IDataObject[]> {
|
||||
const returnData: IDataObject[] = [];
|
||||
let responseData;
|
||||
|
||||
qs.page = 1;
|
||||
qs.size = 100;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
|
||||
const limit = this.getNodeParameter('limit', 0, 0) as number;
|
||||
|
||||
do {
|
||||
responseData = await baserowApiRequest.call(this, method, endpoint, body, qs, jwtToken);
|
||||
returnData.push(...responseData.results);
|
||||
|
||||
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: OptionsWithUri = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
455
packages/nodes-base/nodes/Baserow/OperationDescription.ts
Normal file
455
packages/nodes-base/nodes/Baserow/OperationDescription.ts
Normal file
|
@ -0,0 +1,455 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const operationFields = [
|
||||
// ----------------------------------
|
||||
// shared
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Database',
|
||||
name: 'databaseId',
|
||||
type: 'options',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Database to operate on',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabaseIds',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Table',
|
||||
name: 'tableId',
|
||||
type: 'options',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Table to operate on',
|
||||
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: '',
|
||||
required: false,
|
||||
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 ID',
|
||||
name: 'fieldId',
|
||||
type: 'options',
|
||||
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: 'How many 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: 'field',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Field to compare',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: [
|
||||
'tableId',
|
||||
],
|
||||
loadOptionsMethod: 'getTableFields',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'operator',
|
||||
description: 'Operator to compare field and value with',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
description: 'Field is equal to value',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'not_equal',
|
||||
description: 'Field is not equal to value',
|
||||
},
|
||||
{
|
||||
name: 'Date Equal',
|
||||
value: 'date_equal',
|
||||
description: 'Field is date. Format: \'YYYY-MM-DD\'',
|
||||
},
|
||||
{
|
||||
name: 'Date Not Equal',
|
||||
value: 'date_not_equal',
|
||||
description: 'Field is not date. Format: \'YYYY-MM-DD\'',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Today',
|
||||
value: 'date_equals_today',
|
||||
description: 'Field is today. Format: string',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Month',
|
||||
value: 'date_equals_month',
|
||||
description: 'Field in this month. Format: string',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Year',
|
||||
value: 'date_equals_year',
|
||||
description: 'Field in this year. Format: string',
|
||||
},
|
||||
{
|
||||
name: 'Contains',
|
||||
value: 'contains',
|
||||
description: 'Field contains value',
|
||||
},
|
||||
{
|
||||
name: 'File Name Contains',
|
||||
value: 'filename_contains',
|
||||
description: 'Field filename contains value',
|
||||
},
|
||||
{
|
||||
name: 'Contains Not',
|
||||
value: 'contains_not',
|
||||
description: 'Field does not contain value',
|
||||
},
|
||||
{
|
||||
name: 'Higher Than',
|
||||
value: 'higher_than',
|
||||
description: 'Field is higher than value',
|
||||
},
|
||||
{
|
||||
name: 'Lower Than',
|
||||
value: 'lower_than',
|
||||
description: 'Field is lower than 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',
|
||||
},
|
||||
{
|
||||
name: 'Is True',
|
||||
value: 'boolean',
|
||||
description: 'Boolean field is true',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'empty',
|
||||
description: 'Field is empty',
|
||||
},
|
||||
{
|
||||
name: 'Not Empty',
|
||||
value: 'not_empty',
|
||||
description: 'Field is not empty',
|
||||
},
|
||||
],
|
||||
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',
|
||||
name: 'field',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Field name to sort by',
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
143
packages/nodes-base/nodes/Baserow/baserow.svg
Normal file
143
packages/nodes-base/nodes/Baserow/baserow.svg
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg:svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 60 60.000001"
|
||||
version="1.1"
|
||||
id="svg14"
|
||||
sodipodi:docname="baserow.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<svg:metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</svg:metadata>
|
||||
<svg:defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1848"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview16"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="2.5579745"
|
||||
inkscape:cx="-29.906474"
|
||||
inkscape:cy="40.001009"
|
||||
inkscape:window-x="1512"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Group_163" />
|
||||
<svg:g
|
||||
id="Group_163"
|
||||
data-name="Group 163"
|
||||
transform="translate(-451.976,-540.719)">
|
||||
<svg:g
|
||||
id="Group_162"
|
||||
data-name="Group 162"
|
||||
transform="matrix(1.8935807,0,0,1.8935807,451.976,543.89927)"
|
||||
style="stroke-width:0.52810001">
|
||||
<svg:g
|
||||
id="Group_161"
|
||||
data-name="Group 161"
|
||||
style="stroke-width:0.52810001">
|
||||
<svg:rect
|
||||
id="Rectangle_257"
|
||||
data-name="Rectangle 257"
|
||||
width="21.863001"
|
||||
height="3.9920001"
|
||||
transform="translate(6.654,24.335)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#566270;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_258"
|
||||
data-name="Rectangle 258"
|
||||
width="23.955"
|
||||
height="3.9920001"
|
||||
transform="translate(0,18.251)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#4570a9;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_259"
|
||||
data-name="Rectangle 259"
|
||||
width="6.4640002"
|
||||
height="3.9920001"
|
||||
transform="translate(25.222,18.251)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#4570a9;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_260"
|
||||
data-name="Rectangle 260"
|
||||
width="11.597"
|
||||
height="3.9920001"
|
||||
transform="translate(4.563,12.167)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#00a2ce;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_261"
|
||||
data-name="Rectangle 261"
|
||||
width="5.2280002"
|
||||
height="3.9920001"
|
||||
transform="translate(17.728,12.167)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#00a2ce;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_262"
|
||||
data-name="Rectangle 262"
|
||||
width="19.392"
|
||||
height="3.9920001"
|
||||
transform="translate(8.365,6.084)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#6acaff;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_263"
|
||||
data-name="Rectangle 263"
|
||||
width="16.35"
|
||||
height="3.9920001"
|
||||
transform="translate(7.319)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#9addff;stroke-width:0.52810001" />
|
||||
<svg:rect
|
||||
id="Rectangle_264"
|
||||
data-name="Rectangle 264"
|
||||
width="5.2280002"
|
||||
height="3.9920001"
|
||||
transform="translate(0.285)"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#9addff;stroke-width:0.52810001" />
|
||||
</svg:g>
|
||||
</svg:g>
|
||||
</svg:g>
|
||||
<script />
|
||||
</svg:svg>
|
After Width: | Height: | Size: 4.2 KiB |
41
packages/nodes-base/nodes/Baserow/types.d.ts
vendored
Normal file
41
packages/nodes-base/nodes/Baserow/types.d.ts
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
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;
|
||||
}
|
||||
|
||||
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';
|
|
@ -43,6 +43,7 @@
|
|||
"dist/credentials/AffinityApi.credentials.js",
|
||||
"dist/credentials/BannerbearApi.credentials.js",
|
||||
"dist/credentials/BeeminderApi.credentials.js",
|
||||
"dist/credentials/BaserowApi.credentials.js",
|
||||
"dist/credentials/BitbucketApi.credentials.js",
|
||||
"dist/credentials/BitlyApi.credentials.js",
|
||||
"dist/credentials/BitlyOAuth2Api.credentials.js",
|
||||
|
@ -311,6 +312,7 @@
|
|||
"dist/nodes/Aws/AwsSns.node.js",
|
||||
"dist/nodes/Aws/AwsSnsTrigger.node.js",
|
||||
"dist/nodes/Bannerbear/Bannerbear.node.js",
|
||||
"dist/nodes/Baserow/Baserow.node.js",
|
||||
"dist/nodes/Beeminder/Beeminder.node.js",
|
||||
"dist/nodes/Bitbucket/BitbucketTrigger.node.js",
|
||||
"dist/nodes/Bitly/Bitly.node.js",
|
||||
|
|
Loading…
Reference in a new issue