mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 09:04:07 -08:00
rework of seatable-n8n-node
This commit is contained in:
parent
742c8a8534
commit
e791055fcd
|
@ -1,24 +1,10 @@
|
||||||
import type { ICredentialType, INodeProperties, INodePropertyOptions } from 'n8n-workflow';
|
import type { ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
import moment from 'moment-timezone';
|
|
||||||
|
|
||||||
// Get options for timezones
|
|
||||||
const timezones: INodePropertyOptions[] = moment.tz
|
|
||||||
.countries()
|
|
||||||
.reduce((tz: INodePropertyOptions[], country: string) => {
|
|
||||||
const zonesForCountry = moment.tz
|
|
||||||
.zonesForCountry(country)
|
|
||||||
.map((zone) => ({ value: zone, name: zone }));
|
|
||||||
return tz.concat(zonesForCountry);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
export class SeaTableApi implements ICredentialType {
|
export class SeaTableApi implements ICredentialType {
|
||||||
name = 'seaTableApi';
|
name = 'seaTableApi';
|
||||||
|
|
||||||
displayName = 'SeaTable API';
|
displayName = 'SeaTable API';
|
||||||
|
documentationUrl =
|
||||||
documentationUrl = 'seaTable';
|
'https://seatable.io/docs/n8n-integration/erstellen-eines-api-tokens-fuer-n8n/?lang=auto';
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Environment',
|
displayName: 'Environment',
|
||||||
|
@ -41,7 +27,7 @@ export class SeaTableApi implements ICredentialType {
|
||||||
name: 'domain',
|
name: 'domain',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '',
|
default: '',
|
||||||
placeholder: 'https://www.mydomain.com',
|
placeholder: 'https://seatable.example.com',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
environment: ['selfHosted'],
|
environment: ['selfHosted'],
|
||||||
|
@ -52,16 +38,20 @@ export class SeaTableApi implements ICredentialType {
|
||||||
displayName: 'API Token (of a Base)',
|
displayName: 'API Token (of a Base)',
|
||||||
name: 'token',
|
name: 'token',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'The API-Token of the SeaTable base you would like to use with n8n. n8n can only connect to one base a at a time.',
|
||||||
typeOptions: { password: true },
|
typeOptions: { password: true },
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
displayName: 'Timezone',
|
|
||||||
name: 'timezone',
|
|
||||||
type: 'options',
|
|
||||||
default: '',
|
|
||||||
description: "Seatable server's timezone",
|
|
||||||
options: [...timezones],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
test: ICredentialTestRequest = {
|
||||||
|
request: {
|
||||||
|
baseURL: '={{$credentials?.domain || "https://cloud.seatable.io" }}',
|
||||||
|
url: '/api/v2.1/dtable/app-access-token/',
|
||||||
|
headers: {
|
||||||
|
Authorization: '={{"Token " + $credentials.token}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,451 +1,26 @@
|
||||||
import type {
|
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
|
||||||
IExecuteFunctions,
|
import { VersionedNodeType } from 'n8n-workflow';
|
||||||
IDataObject,
|
|
||||||
ILoadOptionsFunctions,
|
|
||||||
INodeExecutionData,
|
|
||||||
INodePropertyOptions,
|
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
import { SeaTableV1 } from './v1/SeaTableV1.node';
|
||||||
getTableColumns,
|
import { SeaTableV2 } from './v2/SeaTableV2.node';
|
||||||
getTableViews,
|
|
||||||
rowExport,
|
|
||||||
rowFormatColumns,
|
|
||||||
rowMapKeyToName,
|
|
||||||
seaTableApiRequest,
|
|
||||||
setableApiRequestAllItems,
|
|
||||||
split,
|
|
||||||
updateAble,
|
|
||||||
} from './GenericFunctions';
|
|
||||||
|
|
||||||
import { rowFields, rowOperations } from './RowDescription';
|
export class SeaTable extends VersionedNodeType {
|
||||||
|
constructor() {
|
||||||
import type { TColumnsUiValues, TColumnValue } from './types';
|
const baseDescription: INodeTypeBaseDescription = {
|
||||||
|
|
||||||
import type { ICtx, IRow, IRowObject } from './Interfaces';
|
|
||||||
|
|
||||||
export class SeaTable implements INodeType {
|
|
||||||
description: INodeTypeDescription = {
|
|
||||||
displayName: 'SeaTable',
|
displayName: 'SeaTable',
|
||||||
name: 'seaTable',
|
name: 'seatable',
|
||||||
icon: 'file:seaTable.svg',
|
icon: 'file:seatable.svg',
|
||||||
group: ['input'],
|
group: ['output'],
|
||||||
version: 1,
|
|
||||||
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
||||||
description: 'Consume the SeaTable API',
|
description: 'Read, update, write and delete data from SeaTable',
|
||||||
defaults: {
|
defaultVersion: 2,
|
||||||
name: 'SeaTable',
|
|
||||||
},
|
|
||||||
inputs: ['main'],
|
|
||||||
outputs: ['main'],
|
|
||||||
credentials: [
|
|
||||||
{
|
|
||||||
name: 'seaTableApi',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
displayName: 'Resource',
|
|
||||||
name: 'resource',
|
|
||||||
type: 'options',
|
|
||||||
noDataExpression: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: 'Row',
|
|
||||||
value: 'row',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
default: 'row',
|
|
||||||
},
|
|
||||||
...rowOperations,
|
|
||||||
...rowFields,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
methods = {
|
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||||
loadOptions: {
|
1: new SeaTableV1(),
|
||||||
async getTableNames(this: ILoadOptionsFunctions) {
|
2: new SeaTableV2(baseDescription),
|
||||||
const returnData: INodePropertyOptions[] = [];
|
|
||||||
const {
|
|
||||||
metadata: { tables },
|
|
||||||
} = await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
{},
|
|
||||||
'GET',
|
|
||||||
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
|
||||||
);
|
|
||||||
for (const table of tables) {
|
|
||||||
returnData.push({
|
|
||||||
name: table.name,
|
|
||||||
value: table.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
},
|
|
||||||
async getTableIds(this: ILoadOptionsFunctions) {
|
|
||||||
const returnData: INodePropertyOptions[] = [];
|
|
||||||
const {
|
|
||||||
metadata: { tables },
|
|
||||||
} = await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
{},
|
|
||||||
'GET',
|
|
||||||
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
|
||||||
);
|
|
||||||
for (const table of tables) {
|
|
||||||
returnData.push({
|
|
||||||
name: table.name,
|
|
||||||
value: table._id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getTableUpdateAbleColumns(this: ILoadOptionsFunctions) {
|
|
||||||
const tableName = this.getNodeParameter('tableName') as string;
|
|
||||||
const columns = await getTableColumns.call(this, tableName);
|
|
||||||
return columns
|
|
||||||
.filter((column) => column.editable)
|
|
||||||
.map((column) => ({ name: column.name, value: column.name }));
|
|
||||||
},
|
|
||||||
async getAllSortableColumns(this: ILoadOptionsFunctions) {
|
|
||||||
const tableName = this.getNodeParameter('tableName') as string;
|
|
||||||
const columns = await getTableColumns.call(this, tableName);
|
|
||||||
return columns
|
|
||||||
.filter(
|
|
||||||
(column) =>
|
|
||||||
!['file', 'image', 'url', 'collaborator', 'long-text'].includes(column.type),
|
|
||||||
)
|
|
||||||
.map((column) => ({ name: column.name, value: column.name }));
|
|
||||||
},
|
|
||||||
async getViews(this: ILoadOptionsFunctions) {
|
|
||||||
const tableName = this.getNodeParameter('tableName') as string;
|
|
||||||
const views = await getTableViews.call(this, tableName);
|
|
||||||
return views.map((view) => ({ name: view.name, value: view.name }));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
super(nodeVersions, baseDescription);
|
||||||
const items = this.getInputData();
|
|
||||||
const returnData: INodeExecutionData[] = [];
|
|
||||||
let responseData;
|
|
||||||
|
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
|
||||||
|
|
||||||
const body: IDataObject = {};
|
|
||||||
const qs: IDataObject = {};
|
|
||||||
const ctx: ICtx = {};
|
|
||||||
|
|
||||||
if (resource === 'row') {
|
|
||||||
if (operation === 'create') {
|
|
||||||
// ----------------------------------
|
|
||||||
// row:create
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
const tableName = this.getNodeParameter('tableName', 0) as string;
|
|
||||||
const tableColumns = await getTableColumns.call(this, tableName);
|
|
||||||
|
|
||||||
body.table_name = tableName;
|
|
||||||
|
|
||||||
const fieldsToSend = this.getNodeParameter('fieldsToSend', 0) as
|
|
||||||
| 'defineBelow'
|
|
||||||
| 'autoMapInputData';
|
|
||||||
let rowInput: IRowObject = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
rowInput = {} as IRowObject;
|
|
||||||
try {
|
|
||||||
if (fieldsToSend === 'autoMapInputData') {
|
|
||||||
const incomingKeys = Object.keys(items[i].json);
|
|
||||||
const inputDataToIgnore = split(
|
|
||||||
this.getNodeParameter('inputsToIgnore', i, '') as string,
|
|
||||||
);
|
|
||||||
for (const key of incomingKeys) {
|
|
||||||
if (inputDataToIgnore.includes(key)) continue;
|
|
||||||
rowInput[key] = items[i].json[key] as TColumnValue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const columns = this.getNodeParameter(
|
|
||||||
'columnsUi.columnValues',
|
|
||||||
i,
|
|
||||||
[],
|
|
||||||
) as TColumnsUiValues;
|
|
||||||
for (const column of columns) {
|
|
||||||
rowInput[column.columnName] = column.columnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body.row = rowExport(rowInput, updateAble(tableColumns));
|
|
||||||
|
|
||||||
responseData = await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
ctx,
|
|
||||||
'POST',
|
|
||||||
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { _id: insertId } = responseData;
|
|
||||||
if (insertId === undefined) {
|
|
||||||
throw new NodeOperationError(
|
|
||||||
this.getNode(),
|
|
||||||
'SeaTable: No identity after appending row.',
|
|
||||||
{ itemIndex: i },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRowInsertData = rowMapKeyToName(responseData as IRow, tableColumns);
|
|
||||||
|
|
||||||
qs.table_name = tableName;
|
|
||||||
qs.convert = true;
|
|
||||||
const newRow = await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
ctx,
|
|
||||||
'GET',
|
|
||||||
`/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/${encodeURIComponent(
|
|
||||||
insertId as string,
|
|
||||||
)}/`,
|
|
||||||
body,
|
|
||||||
qs,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newRow._id === undefined) {
|
|
||||||
throw new NodeOperationError(
|
|
||||||
this.getNode(),
|
|
||||||
'SeaTable: No identity for appended row.',
|
|
||||||
{ itemIndex: i },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const row = rowFormatColumns(
|
|
||||||
{ ...newRowInsertData, ...(newRow as IRow) },
|
|
||||||
tableColumns.map(({ name }) => name).concat(['_id', '_ctime', '_mtime']),
|
|
||||||
);
|
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(row),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} catch (error) {
|
|
||||||
if (this.continueOnFail()) {
|
|
||||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray({ error: error.message }),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionErrorData);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation === 'get') {
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
try {
|
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
|
||||||
const rowId = this.getNodeParameter('rowId', i) as string;
|
|
||||||
const response = (await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
ctx,
|
|
||||||
'GET',
|
|
||||||
`/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/${rowId}`,
|
|
||||||
{},
|
|
||||||
{ table_id: tableId, convert: true },
|
|
||||||
)) as IDataObject;
|
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(response),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} catch (error) {
|
|
||||||
if (this.continueOnFail()) {
|
|
||||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray({ error: error.message }),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionErrorData);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation === 'getAll') {
|
|
||||||
// ----------------------------------
|
|
||||||
// row:getAll
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
const tableName = this.getNodeParameter('tableName', 0) as string;
|
|
||||||
const tableColumns = await getTableColumns.call(this, tableName);
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
try {
|
|
||||||
const endpoint = '/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/';
|
|
||||||
qs.table_name = tableName;
|
|
||||||
const filters = this.getNodeParameter('filters', i);
|
|
||||||
const options = this.getNodeParameter('options', i);
|
|
||||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
|
||||||
|
|
||||||
Object.assign(qs, filters, options);
|
|
||||||
|
|
||||||
if (qs.convert_link_id === false) {
|
|
||||||
delete qs.convert_link_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (returnAll) {
|
|
||||||
responseData = await setableApiRequestAllItems.call(
|
|
||||||
this,
|
|
||||||
ctx,
|
|
||||||
'rows',
|
|
||||||
'GET',
|
|
||||||
endpoint,
|
|
||||||
body,
|
|
||||||
qs,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
qs.limit = this.getNodeParameter('limit', 0);
|
|
||||||
responseData = await seaTableApiRequest.call(this, ctx, 'GET', endpoint, body, qs);
|
|
||||||
responseData = responseData.rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = responseData.map((row: IRow) =>
|
|
||||||
rowFormatColumns(
|
|
||||||
{ ...row },
|
|
||||||
tableColumns.map(({ name }) => name).concat(['_id', '_ctime', '_mtime']),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(rows as IDataObject[]),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} catch (error) {
|
|
||||||
if (this.continueOnFail()) {
|
|
||||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray({ error: error.message }),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionErrorData);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation === 'delete') {
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
try {
|
|
||||||
const tableName = this.getNodeParameter('tableName', 0) as string;
|
|
||||||
const rowId = this.getNodeParameter('rowId', i) as string;
|
|
||||||
const requestBody: IDataObject = {
|
|
||||||
table_name: tableName,
|
|
||||||
row_id: rowId,
|
|
||||||
};
|
|
||||||
const response = (await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
ctx,
|
|
||||||
'DELETE',
|
|
||||||
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
|
||||||
requestBody,
|
|
||||||
qs,
|
|
||||||
)) as IDataObject;
|
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray(response),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} catch (error) {
|
|
||||||
if (this.continueOnFail()) {
|
|
||||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray({ error: error.message }),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionErrorData);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (operation === 'update') {
|
|
||||||
// ----------------------------------
|
|
||||||
// row:update
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
const tableName = this.getNodeParameter('tableName', 0) as string;
|
|
||||||
const tableColumns = await getTableColumns.call(this, tableName);
|
|
||||||
|
|
||||||
body.table_name = tableName;
|
|
||||||
|
|
||||||
const fieldsToSend = this.getNodeParameter('fieldsToSend', 0) as
|
|
||||||
| 'defineBelow'
|
|
||||||
| 'autoMapInputData';
|
|
||||||
let rowInput: IRowObject = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const rowId = this.getNodeParameter('rowId', i) as string;
|
|
||||||
rowInput = {} as IRowObject;
|
|
||||||
try {
|
|
||||||
if (fieldsToSend === 'autoMapInputData') {
|
|
||||||
const incomingKeys = Object.keys(items[i].json);
|
|
||||||
const inputDataToIgnore = split(
|
|
||||||
this.getNodeParameter('inputsToIgnore', i, '') as string,
|
|
||||||
);
|
|
||||||
for (const key of incomingKeys) {
|
|
||||||
if (inputDataToIgnore.includes(key)) continue;
|
|
||||||
rowInput[key] = items[i].json[key] as TColumnValue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const columns = this.getNodeParameter(
|
|
||||||
'columnsUi.columnValues',
|
|
||||||
i,
|
|
||||||
[],
|
|
||||||
) as TColumnsUiValues;
|
|
||||||
for (const column of columns) {
|
|
||||||
rowInput[column.columnName] = column.columnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body.row = rowExport(rowInput, updateAble(tableColumns));
|
|
||||||
body.table_name = tableName;
|
|
||||||
body.row_id = rowId;
|
|
||||||
responseData = await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
ctx,
|
|
||||||
'PUT',
|
|
||||||
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
|
|
||||||
const executionData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray({ _id: rowId, ...(responseData as IDataObject) }),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
|
|
||||||
returnData.push(...executionData);
|
|
||||||
} catch (error) {
|
|
||||||
if (this.continueOnFail()) {
|
|
||||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
|
||||||
this.helpers.returnJsonArray({ error: error.message }),
|
|
||||||
{ itemData: { item: i } },
|
|
||||||
);
|
|
||||||
returnData.push(...executionErrorData);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [returnData];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,33 @@
|
||||||
import type {
|
import type {
|
||||||
IPollFunctions,
|
IPollFunctions,
|
||||||
ILoadOptionsFunctions,
|
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodePropertyOptions,
|
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { getColumns, rowFormatColumns, seaTableApiRequest, simplify } from './GenericFunctions';
|
import { seaTableApiRequest, simplify_new, enrichColumns } from './v2/GenericFunctions';
|
||||||
|
|
||||||
import type { ICtx, IRow, IRowResponse } from './Interfaces';
|
import type {
|
||||||
|
ICtx,
|
||||||
|
IRow,
|
||||||
|
IRowResponse,
|
||||||
|
IGetMetadataResult,
|
||||||
|
IGetRowsResult,
|
||||||
|
IDtableMetadataColumn,
|
||||||
|
ICollaborator,
|
||||||
|
ICollaboratorsResult,
|
||||||
|
IColumnDigitalSignature,
|
||||||
|
} from './v2/actions/Interfaces';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { loadOptions } from './v2/methods';
|
||||||
|
|
||||||
export class SeaTableTrigger implements INodeType {
|
export class SeaTableTrigger implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'SeaTable Trigger',
|
displayName: 'SeaTable Trigger',
|
||||||
name: 'seaTableTrigger',
|
name: 'seaTableTrigger',
|
||||||
icon: 'file:seaTable.svg',
|
icon: 'file:seatable.svg',
|
||||||
group: ['trigger'],
|
group: ['trigger'],
|
||||||
version: 1,
|
version: 1,
|
||||||
description: 'Starts the workflow when SeaTable events occur',
|
description: 'Starts the workflow when SeaTable events occur',
|
||||||
|
@ -35,6 +45,29 @@ export class SeaTableTrigger implements INodeType {
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: ['main'],
|
outputs: ['main'],
|
||||||
properties: [
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Event',
|
||||||
|
name: 'event',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'New Row',
|
||||||
|
value: 'newRow',
|
||||||
|
description: 'Trigger on newly created rows',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New or Updated Row',
|
||||||
|
value: 'updatedRow',
|
||||||
|
description: 'Trigger has recently created or modified rows',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'New Signature',
|
||||||
|
value: 'newAsset',
|
||||||
|
description: 'Trigger on new signatures',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'newRow',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Table Name or ID',
|
displayName: 'Table Name or ID',
|
||||||
name: 'tableName',
|
name: 'tableName',
|
||||||
|
@ -48,109 +81,206 @@ export class SeaTableTrigger implements INodeType {
|
||||||
'The name of SeaTable table to access. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
'The name of SeaTable table to access. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Event',
|
displayName: 'View Name or ID (optional)',
|
||||||
name: 'event',
|
name: 'viewName',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
options: [
|
required: false,
|
||||||
{
|
displayOptions: {
|
||||||
name: 'Row Created',
|
show: {
|
||||||
value: 'rowCreated',
|
event: ['newRow', 'updatedRow'],
|
||||||
description: 'Trigger on newly created rows',
|
|
||||||
},
|
},
|
||||||
// {
|
},
|
||||||
// name: 'Row Modified',
|
typeOptions: {
|
||||||
// value: 'rowModified',
|
loadOptionsDependsOn: ['tableName'],
|
||||||
// description: 'Trigger has recently modified rows',
|
loadOptionsMethod: 'getTableViews',
|
||||||
// },
|
},
|
||||||
],
|
default: '',
|
||||||
default: 'rowCreated',
|
description: 'The name of SeaTable view to access. Choose from the list, or specify ...',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Simplify',
|
displayName: 'Signature column',
|
||||||
|
name: 'assetColumn',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
event: ['newAsset'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getSignatureColumns',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Select the digital-signature column that should be tracked.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Simplify output',
|
||||||
name: 'simple',
|
name: 'simple',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
description:
|
description:
|
||||||
'Whether to return a simplified version of the response instead of the raw data',
|
'Simplified returns only the columns of your base. Non-simplified will return additional columns like _ctime (=creation time), _mtime (=modification time) etc.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: '"Fetch Test Event" returns max. three items of the last hour.',
|
||||||
|
name: 'notice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
methods = {
|
methods = { loadOptions };
|
||||||
loadOptions: {
|
|
||||||
async getTableNames(this: ILoadOptionsFunctions) {
|
|
||||||
const returnData: INodePropertyOptions[] = [];
|
|
||||||
const {
|
|
||||||
metadata: { tables },
|
|
||||||
} = await seaTableApiRequest.call(
|
|
||||||
this,
|
|
||||||
{},
|
|
||||||
'GET',
|
|
||||||
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
|
||||||
);
|
|
||||||
for (const table of tables) {
|
|
||||||
returnData.push({
|
|
||||||
name: table.name,
|
|
||||||
value: table.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
|
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
|
||||||
const webhookData = this.getWorkflowStaticData('node');
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
const tableName = this.getNodeParameter('tableName') as string;
|
|
||||||
const simple = this.getNodeParameter('simple') as boolean;
|
|
||||||
const event = this.getNodeParameter('event') as string;
|
const event = this.getNodeParameter('event') as string;
|
||||||
|
const tableName = this.getNodeParameter('tableName') as string;
|
||||||
|
const viewName = (event != 'newAsset' ? this.getNodeParameter('viewName') : '') as string;
|
||||||
|
const assetColumn = (event == 'newAsset' ? this.getNodeParameter('assetColumn') : '') as string;
|
||||||
|
const simple = this.getNodeParameter('simple') as boolean;
|
||||||
|
|
||||||
const ctx: ICtx = {};
|
const ctx: ICtx = {};
|
||||||
const credentials = await this.getCredentials('seaTableApi');
|
|
||||||
|
|
||||||
const timezone = (credentials.timezone as string) || 'Europe/Berlin';
|
const startDate =
|
||||||
const now = moment().utc().format();
|
this.getMode() === 'manual'
|
||||||
const startDate = (webhookData.lastTimeChecked as string) || now;
|
? moment().utc().subtract(1, 'h').format()
|
||||||
const endDate = now;
|
: (webhookData.lastTimeChecked as string);
|
||||||
webhookData.lastTimeChecked = endDate;
|
const endDate = (webhookData.lastTimeChecked = moment().utc().format());
|
||||||
|
|
||||||
let rows;
|
// this is working, even if the columns _mtime and _ctime have other names. Only relevant for newRow / updatedRow.
|
||||||
|
const filterField = event === 'newRow' ? '_ctime' : '_mtime';
|
||||||
|
|
||||||
const filterField = event === 'rowCreated' ? '_ctime' : '_mtime';
|
// Difference between getRows and SqlQuery:
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
// getRows (if view is selected)
|
||||||
|
// getRows always gets up to 1.000 rows of the selected view.
|
||||||
|
// getRows delivers only the rows, not the metadata
|
||||||
|
// no possibility to filter for _ctime or _mtime with the API call.
|
||||||
|
// Problems, not yet solved:
|
||||||
|
// if a column is empty, the column is not returned!
|
||||||
|
// view with more than 1.000 rows will not work!
|
||||||
|
|
||||||
|
// SqlQuery (if no view is selected)
|
||||||
|
// SqlQuery returns up to 1.000. WHERE by time and ORDER BY _ctime or _mtime is possible.
|
||||||
|
// SqlQuery returns rows and metadata
|
||||||
|
|
||||||
|
let requestMeta: IGetMetadataResult;
|
||||||
|
let requestRows: IGetRowsResult;
|
||||||
|
let metadata: IDtableMetadataColumn[] = [];
|
||||||
|
let rows: IRow[];
|
||||||
|
let sqlResult: IRowResponse;
|
||||||
|
|
||||||
|
const limit = this.getMode() === 'manual' ? 3 : 1000;
|
||||||
|
|
||||||
|
// New Signature
|
||||||
|
if (event == 'newAsset') {
|
||||||
const endpoint = '/dtable-db/api/v1/query/{{dtable_uuid}}/';
|
const endpoint = '/dtable-db/api/v1/query/{{dtable_uuid}}/';
|
||||||
|
sqlResult = await seaTableApiRequest.call(this, ctx, 'POST', endpoint, {
|
||||||
|
sql: `SELECT _id, _ctime, _mtime, \`${assetColumn}\` FROM ${tableName} WHERE \`${assetColumn}\` IS NOT NULL ORDER BY _mtime DESC LIMIT ${limit}`,
|
||||||
|
convert_keys: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
metadata = sqlResult.metadata as IDtableMetadataColumn[];
|
||||||
|
const columnType = metadata.find((obj) => obj.name == assetColumn);
|
||||||
|
const assetColumnType = columnType?.type || null;
|
||||||
|
|
||||||
|
// remove unwanted entries
|
||||||
|
rows = sqlResult.results.filter(
|
||||||
|
(obj) => new Date(obj['_mtime']) > new Date(startDate),
|
||||||
|
) as IRow[];
|
||||||
|
|
||||||
|
// split the objects into new lines (not necessary for digital-sign)
|
||||||
|
const newRows: any = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
if (assetColumnType === 'digital-sign') {
|
||||||
|
let signature = (row[assetColumn] as IColumnDigitalSignature) || [];
|
||||||
|
if (signature.sign_time) {
|
||||||
|
if (new Date(signature.sign_time) > new Date(startDate)) {
|
||||||
|
newRows.push(signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// View => use getRows.
|
||||||
|
else if (viewName) {
|
||||||
|
requestMeta = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata/',
|
||||||
|
);
|
||||||
|
requestRows = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
table_name: tableName,
|
||||||
|
view_name: viewName,
|
||||||
|
limit: limit,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// I need only metadata of the selected table.
|
||||||
|
metadata =
|
||||||
|
requestMeta.metadata.tables.find((table) => table.name === tableName)?.columns ?? [];
|
||||||
|
|
||||||
|
// remove unwanted rows that are too old (compare startDate with _ctime or _mtime)
|
||||||
if (this.getMode() === 'manual') {
|
if (this.getMode() === 'manual') {
|
||||||
rows = (await seaTableApiRequest.call(this, ctx, 'POST', endpoint, {
|
rows = requestRows.rows as IRow[];
|
||||||
sql: `SELECT * FROM ${tableName} LIMIT 1`,
|
|
||||||
})) as IRowResponse;
|
|
||||||
} else {
|
} else {
|
||||||
rows = (await seaTableApiRequest.call(this, ctx, 'POST', endpoint, {
|
rows = requestRows.rows.filter(
|
||||||
sql: `SELECT * FROM ${tableName}
|
(obj) => new Date(obj[filterField]) > new Date(startDate),
|
||||||
WHERE ${filterField} BETWEEN "${moment(startDate).tz(timezone).format('YYYY-MM-D HH:mm:ss')}"
|
) as IRow[];
|
||||||
AND "${moment(endDate).tz(timezone).format('YYYY-MM-D HH:mm:ss')}"`,
|
}
|
||||||
})) as IRowResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let response;
|
// No view => use SQL-Query
|
||||||
|
else {
|
||||||
|
const endpoint = '/dtable-db/api/v1/query/{{dtable_uuid}}/';
|
||||||
|
const sqlQuery = `SELECT * FROM \`${tableName}\` WHERE ${filterField} BETWEEN "${moment(
|
||||||
|
startDate,
|
||||||
|
).format('YYYY-MM-D HH:mm:ss')}" AND "${moment(endDate).format(
|
||||||
|
'YYYY-MM-D HH:mm:ss',
|
||||||
|
)}" ORDER BY ${filterField} DESC LIMIT ${limit}`;
|
||||||
|
sqlResult = await seaTableApiRequest.call(this, ctx, 'POST', endpoint, {
|
||||||
|
sql: sqlQuery,
|
||||||
|
convert_keys: true,
|
||||||
|
});
|
||||||
|
metadata = sqlResult.metadata as IDtableMetadataColumn[];
|
||||||
|
rows = sqlResult.results as IRow[];
|
||||||
|
}
|
||||||
|
|
||||||
if (rows.metadata && rows.results) {
|
// =========================================
|
||||||
const columns = getColumns(rows);
|
// => now I have rows and metadata.
|
||||||
|
|
||||||
|
// lets get the collaborators
|
||||||
|
let collaboratorsResult: ICollaboratorsResult = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/related-users/',
|
||||||
|
);
|
||||||
|
let collaborators: ICollaborator[] = collaboratorsResult.user_list || [];
|
||||||
|
|
||||||
|
if (Array.isArray(rows) && rows.length > 0) {
|
||||||
|
// remove columns starting with _ if simple;
|
||||||
if (simple) {
|
if (simple) {
|
||||||
response = simplify(rows, columns);
|
rows = rows.map((row) => simplify_new(row));
|
||||||
} else {
|
|
||||||
response = rows.results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allColumns = rows.metadata.map((meta) => meta.name);
|
// enrich column types like {collaborator, creator, last_modifier}, {image, file}
|
||||||
|
// remove button column from rows
|
||||||
|
rows = rows.map((row) => enrichColumns(row, metadata, collaborators));
|
||||||
|
|
||||||
response = response
|
// prepare for final output
|
||||||
//@ts-ignore
|
return [this.helpers.returnJsonArray(rows)];
|
||||||
.map((row: IRow) => rowFormatColumns(row, allColumns))
|
|
||||||
.map((row: IRow) => ({ json: row }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(response) && response.length) {
|
|
||||||
return [response];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
Before Width: | Height: | Size: 629 B After Width: | Height: | Size: 629 B |
451
packages/nodes-base/nodes/SeaTable/v1/SeaTableV1.node.ts
Normal file
451
packages/nodes-base/nodes/SeaTable/v1/SeaTableV1.node.ts
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getTableColumns,
|
||||||
|
getTableViews,
|
||||||
|
rowExport,
|
||||||
|
rowFormatColumns,
|
||||||
|
rowMapKeyToName,
|
||||||
|
seaTableApiRequest,
|
||||||
|
setableApiRequestAllItems,
|
||||||
|
split,
|
||||||
|
updateAble,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import { rowFields, rowOperations } from './RowDescription';
|
||||||
|
|
||||||
|
import type { TColumnsUiValues, TColumnValue } from './types';
|
||||||
|
|
||||||
|
import type { ICtx, IRow, IRowObject } from './Interfaces';
|
||||||
|
|
||||||
|
export class SeaTableV1 implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'SeaTable',
|
||||||
|
name: 'seaTable',
|
||||||
|
icon: 'file:seaTable.svg',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
||||||
|
description: 'Consume the SeaTable API',
|
||||||
|
defaults: {
|
||||||
|
name: 'SeaTable',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'seaTableApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Row',
|
||||||
|
value: 'row',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'row',
|
||||||
|
},
|
||||||
|
...rowOperations,
|
||||||
|
...rowFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
loadOptions: {
|
||||||
|
async getTableNames(this: ILoadOptionsFunctions) {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const {
|
||||||
|
metadata: { tables },
|
||||||
|
} = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
||||||
|
);
|
||||||
|
for (const table of tables) {
|
||||||
|
returnData.push({
|
||||||
|
name: table.name,
|
||||||
|
value: table.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
async getTableIds(this: ILoadOptionsFunctions) {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const {
|
||||||
|
metadata: { tables },
|
||||||
|
} = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
||||||
|
);
|
||||||
|
for (const table of tables) {
|
||||||
|
returnData.push({
|
||||||
|
name: table.name,
|
||||||
|
value: table._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTableUpdateAbleColumns(this: ILoadOptionsFunctions) {
|
||||||
|
const tableName = this.getNodeParameter('tableName') as string;
|
||||||
|
const columns = await getTableColumns.call(this, tableName);
|
||||||
|
return columns
|
||||||
|
.filter((column) => column.editable)
|
||||||
|
.map((column) => ({ name: column.name, value: column.name }));
|
||||||
|
},
|
||||||
|
async getAllSortableColumns(this: ILoadOptionsFunctions) {
|
||||||
|
const tableName = this.getNodeParameter('tableName') as string;
|
||||||
|
const columns = await getTableColumns.call(this, tableName);
|
||||||
|
return columns
|
||||||
|
.filter(
|
||||||
|
(column) =>
|
||||||
|
!['file', 'image', 'url', 'collaborator', 'long-text'].includes(column.type),
|
||||||
|
)
|
||||||
|
.map((column) => ({ name: column.name, value: column.name }));
|
||||||
|
},
|
||||||
|
async getViews(this: ILoadOptionsFunctions) {
|
||||||
|
const tableName = this.getNodeParameter('tableName') as string;
|
||||||
|
const views = await getTableViews.call(this, tableName);
|
||||||
|
return views.map((view) => ({ name: view.name, value: view.name }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
const ctx: ICtx = {};
|
||||||
|
|
||||||
|
if (resource === 'row') {
|
||||||
|
if (operation === 'create') {
|
||||||
|
// ----------------------------------
|
||||||
|
// row:create
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
const tableName = this.getNodeParameter('tableName', 0) as string;
|
||||||
|
const tableColumns = await getTableColumns.call(this, tableName);
|
||||||
|
|
||||||
|
body.table_name = tableName;
|
||||||
|
|
||||||
|
const fieldsToSend = this.getNodeParameter('fieldsToSend', 0) as
|
||||||
|
| 'defineBelow'
|
||||||
|
| 'autoMapInputData';
|
||||||
|
let rowInput: IRowObject = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
rowInput = {} as IRowObject;
|
||||||
|
try {
|
||||||
|
if (fieldsToSend === 'autoMapInputData') {
|
||||||
|
const incomingKeys = Object.keys(items[i].json);
|
||||||
|
const inputDataToIgnore = split(
|
||||||
|
this.getNodeParameter('inputsToIgnore', i, '') as string,
|
||||||
|
);
|
||||||
|
for (const key of incomingKeys) {
|
||||||
|
if (inputDataToIgnore.includes(key)) continue;
|
||||||
|
rowInput[key] = items[i].json[key] as TColumnValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const columns = this.getNodeParameter(
|
||||||
|
'columnsUi.columnValues',
|
||||||
|
i,
|
||||||
|
[],
|
||||||
|
) as TColumnsUiValues;
|
||||||
|
for (const column of columns) {
|
||||||
|
rowInput[column.columnName] = column.columnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.row = rowExport(rowInput, updateAble(tableColumns));
|
||||||
|
|
||||||
|
responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'POST',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { _id: insertId } = responseData;
|
||||||
|
if (insertId === undefined) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'SeaTable: No identity after appending row.',
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRowInsertData = rowMapKeyToName(responseData as IRow, tableColumns);
|
||||||
|
|
||||||
|
qs.table_name = tableName;
|
||||||
|
qs.convert = true;
|
||||||
|
const newRow = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'GET',
|
||||||
|
`/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/${encodeURIComponent(
|
||||||
|
insertId as string,
|
||||||
|
)}/`,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newRow._id === undefined) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'SeaTable: No identity for appended row.',
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = rowFormatColumns(
|
||||||
|
{ ...newRowInsertData, ...(newRow as IRow) },
|
||||||
|
tableColumns.map(({ name }) => name).concat(['_id', '_ctime', '_mtime']),
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(row),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (operation === 'get') {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
try {
|
||||||
|
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', i) as string;
|
||||||
|
const response = (await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'GET',
|
||||||
|
`/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/${rowId}`,
|
||||||
|
{},
|
||||||
|
{ table_id: tableId, convert: true },
|
||||||
|
)) as IDataObject;
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(response),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (operation === 'getAll') {
|
||||||
|
// ----------------------------------
|
||||||
|
// row:getAll
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
const tableName = this.getNodeParameter('tableName', 0) as string;
|
||||||
|
const tableColumns = await getTableColumns.call(this, tableName);
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
try {
|
||||||
|
const endpoint = '/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/';
|
||||||
|
qs.table_name = tableName;
|
||||||
|
const filters = this.getNodeParameter('filters', i);
|
||||||
|
const options = this.getNodeParameter('options', i);
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||||
|
|
||||||
|
Object.assign(qs, filters, options);
|
||||||
|
|
||||||
|
if (qs.convert_link_id === false) {
|
||||||
|
delete qs.convert_link_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await setableApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'rows',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs.limit = this.getNodeParameter('limit', 0);
|
||||||
|
responseData = await seaTableApiRequest.call(this, ctx, 'GET', endpoint, body, qs);
|
||||||
|
responseData = responseData.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = responseData.map((row: IRow) =>
|
||||||
|
rowFormatColumns(
|
||||||
|
{ ...row },
|
||||||
|
tableColumns.map(({ name }) => name).concat(['_id', '_ctime', '_mtime']),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(rows as IDataObject[]),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (operation === 'delete') {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
try {
|
||||||
|
const tableName = this.getNodeParameter('tableName', 0) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', i) as string;
|
||||||
|
const requestBody: IDataObject = {
|
||||||
|
table_name: tableName,
|
||||||
|
row_id: rowId,
|
||||||
|
};
|
||||||
|
const response = (await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'DELETE',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
requestBody,
|
||||||
|
qs,
|
||||||
|
)) as IDataObject;
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(response),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (operation === 'update') {
|
||||||
|
// ----------------------------------
|
||||||
|
// row:update
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
const tableName = this.getNodeParameter('tableName', 0) as string;
|
||||||
|
const tableColumns = await getTableColumns.call(this, tableName);
|
||||||
|
|
||||||
|
body.table_name = tableName;
|
||||||
|
|
||||||
|
const fieldsToSend = this.getNodeParameter('fieldsToSend', 0) as
|
||||||
|
| 'defineBelow'
|
||||||
|
| 'autoMapInputData';
|
||||||
|
let rowInput: IRowObject = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const rowId = this.getNodeParameter('rowId', i) as string;
|
||||||
|
rowInput = {} as IRowObject;
|
||||||
|
try {
|
||||||
|
if (fieldsToSend === 'autoMapInputData') {
|
||||||
|
const incomingKeys = Object.keys(items[i].json);
|
||||||
|
const inputDataToIgnore = split(
|
||||||
|
this.getNodeParameter('inputsToIgnore', i, '') as string,
|
||||||
|
);
|
||||||
|
for (const key of incomingKeys) {
|
||||||
|
if (inputDataToIgnore.includes(key)) continue;
|
||||||
|
rowInput[key] = items[i].json[key] as TColumnValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const columns = this.getNodeParameter(
|
||||||
|
'columnsUi.columnValues',
|
||||||
|
i,
|
||||||
|
[],
|
||||||
|
) as TColumnsUiValues;
|
||||||
|
for (const column of columns) {
|
||||||
|
rowInput[column.columnName] = column.columnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.row = rowExport(rowInput, updateAble(tableColumns));
|
||||||
|
body.table_name = tableName;
|
||||||
|
body.row_id = rowId;
|
||||||
|
responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'PUT',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ _id: rowId, ...(responseData as IDataObject) }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [returnData];
|
||||||
|
}
|
||||||
|
}
|
338
packages/nodes-base/nodes/SeaTable/v2/GenericFunctions.ts
Normal file
338
packages/nodes-base/nodes/SeaTable/v2/GenericFunctions.ts
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
import type { OptionsWithUri } from 'request';
|
||||||
|
import FormData from 'form-data';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IPollFunctions,
|
||||||
|
JsonObject,
|
||||||
|
IHttpRequestMethods,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import type { TDtableMetadataColumns, TEndpointVariableName } from './types';
|
||||||
|
|
||||||
|
import { schema } from './Schema';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ICollaborator,
|
||||||
|
ICollaboratorsResult,
|
||||||
|
ICredential,
|
||||||
|
ICtx,
|
||||||
|
IDtableMetadataColumn,
|
||||||
|
IEndpointVariables,
|
||||||
|
IName,
|
||||||
|
IRow,
|
||||||
|
IRowObject,
|
||||||
|
IColumnDigitalSignature,
|
||||||
|
IFile,
|
||||||
|
} from './actions/Interfaces';
|
||||||
|
|
||||||
|
// remove last backslash
|
||||||
|
const userBaseUri = (uri?: string) => {
|
||||||
|
if (uri === undefined) return uri;
|
||||||
|
if (uri.endsWith('/')) return uri.slice(0, -1);
|
||||||
|
return uri;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveBaseUri(ctx: ICtx) {
|
||||||
|
return ctx?.credentials?.environment === 'cloudHosted'
|
||||||
|
? 'https://cloud.seatable.io'
|
||||||
|
: userBaseUri(ctx?.credentials?.domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBaseAccessToken(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||||
|
ctx: ICtx,
|
||||||
|
) {
|
||||||
|
if (ctx?.base?.access_token !== undefined) return;
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Token ${ctx?.credentials?.token}`,
|
||||||
|
},
|
||||||
|
uri: `${resolveBaseUri(ctx)}/api/v2.1/dtable/app-access-token/`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
ctx.base = await this.helpers.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function endpointCtxExpr(ctx: ICtx, endpoint: string): string {
|
||||||
|
const endpointVariables: IEndpointVariables = {};
|
||||||
|
endpointVariables.access_token = ctx?.base?.access_token;
|
||||||
|
endpointVariables.dtable_uuid = ctx?.base?.dtable_uuid;
|
||||||
|
|
||||||
|
return endpoint.replace(
|
||||||
|
/({{ *(access_token|dtable_uuid|server) *}})/g,
|
||||||
|
(match: string, expr: string, name: TEndpointVariableName) => {
|
||||||
|
// I need expr. Why?
|
||||||
|
return (endpointVariables[name] as string) || match;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function seaTableApiRequest(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||||
|
ctx: ICtx,
|
||||||
|
method: IHttpRequestMethods,
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject | FormData | string | Buffer = {},
|
||||||
|
qs: IDataObject = {},
|
||||||
|
url: string | undefined = undefined,
|
||||||
|
option: IDataObject = {},
|
||||||
|
): Promise<any> {
|
||||||
|
const credentials = await this.getCredentials('seaTableApi');
|
||||||
|
|
||||||
|
ctx.credentials = credentials as unknown as ICredential;
|
||||||
|
|
||||||
|
await getBaseAccessToken.call(this, ctx);
|
||||||
|
|
||||||
|
// some API endpoints require the api_token instead of base_access_token.
|
||||||
|
const token =
|
||||||
|
endpoint.indexOf('/api/v2.1/dtable/app-download-link/') === 0 ||
|
||||||
|
endpoint == '/api/v2.1/dtable/app-upload-link/' ||
|
||||||
|
endpoint.indexOf('/seafhttp/upload-api') === 0
|
||||||
|
? `${ctx?.credentials?.token}`
|
||||||
|
: `${ctx?.base?.access_token}`;
|
||||||
|
|
||||||
|
let options: OptionsWithUri = {
|
||||||
|
uri: url || `${resolveBaseUri(ctx)}${endpointCtxExpr(ctx, endpoint)}`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Token ${token}`,
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
qs,
|
||||||
|
body,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(option).length !== 0) {
|
||||||
|
options = Object.assign({}, options, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove header from download request.
|
||||||
|
if (endpoint.indexOf('/seafhttp/files/') === 0) {
|
||||||
|
delete options.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enhance header for upload request
|
||||||
|
if (endpoint.indexOf('/seafhttp/upload-api') === 0) {
|
||||||
|
options.json = true;
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG-MODE OR API-REQUESTS
|
||||||
|
// console.log(options);
|
||||||
|
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.helpers.requestWithAuthentication.call(this, 'seaTableApi', options);
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBaseCollaborators(
|
||||||
|
this: ILoadOptionsFunctions | IExecuteFunctions | IPollFunctions,
|
||||||
|
): Promise<any> {
|
||||||
|
let collaboratorsResult: ICollaboratorsResult = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/related-users/',
|
||||||
|
);
|
||||||
|
let collaborators: ICollaborator[] = collaboratorsResult.user_list || [];
|
||||||
|
return collaborators;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTableColumns(
|
||||||
|
this: ILoadOptionsFunctions | IExecuteFunctions | IPollFunctions,
|
||||||
|
tableName: string,
|
||||||
|
ctx: ICtx = {},
|
||||||
|
): Promise<TDtableMetadataColumns> {
|
||||||
|
const {
|
||||||
|
metadata: { tables },
|
||||||
|
} = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
||||||
|
);
|
||||||
|
for (const table of tables) {
|
||||||
|
if (table.name === tableName) {
|
||||||
|
return table.columns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function simplify_new(row: IRow) {
|
||||||
|
for (const key of Object.keys(row)) {
|
||||||
|
if (key.startsWith('_')) delete row[key];
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*const uniquePredicate = (current: string, index: number, all: string[]) =>
|
||||||
|
all.indexOf(current) === index;
|
||||||
|
const nonInternalPredicate = (name: string) => !Object.keys(schema.internalNames).includes(name);*/
|
||||||
|
const namePredicate = (name: string) => (named: IName) => named.name === name;
|
||||||
|
export const nameOfPredicate = (names: readonly IName[]) => (name: string) =>
|
||||||
|
names.find(namePredicate(name));
|
||||||
|
|
||||||
|
const normalize = (subject: string): string => (subject ? subject.normalize() : '');
|
||||||
|
|
||||||
|
/* will ich diesen call ? */
|
||||||
|
export const split = (subject: string): string[] =>
|
||||||
|
normalize(subject)
|
||||||
|
.split(/\s*((?:[^\\,]*?(?:\\[\s\S])*)*?)\s*(?:,|$)/)
|
||||||
|
.filter((s) => s.length)
|
||||||
|
.map((s) => s.replace(/\\([\s\S])/gm, ($0, $1) => $1));
|
||||||
|
|
||||||
|
// INTERNAL: get collaborator info from @auth.local address
|
||||||
|
function getCollaboratorInfo(
|
||||||
|
authLocal: string | null | undefined,
|
||||||
|
collaboratorList: ICollaborator[],
|
||||||
|
) {
|
||||||
|
let collaboratorDetails: ICollaborator;
|
||||||
|
collaboratorDetails = collaboratorList.find(
|
||||||
|
(singleCollaborator) => singleCollaborator['email'] === authLocal,
|
||||||
|
) || { contact_email: 'unknown', name: 'unkown', email: 'unknown' };
|
||||||
|
return collaboratorDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INTERNAL: split asset path.
|
||||||
|
function getAssetPath(type: string, url: string) {
|
||||||
|
const parts = url.split(`/${type}/`);
|
||||||
|
if (parts[1]) {
|
||||||
|
return '/' + type + '/' + parts[1];
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CDB: neu von mir
|
||||||
|
export function enrichColumns(
|
||||||
|
row: IRow,
|
||||||
|
metadata: IDtableMetadataColumn[],
|
||||||
|
collaboratorList: ICollaborator[],
|
||||||
|
): IRow {
|
||||||
|
Object.keys(row).forEach((key) => {
|
||||||
|
let columnDef = metadata.find((obj) => obj.name === key || obj.key === key);
|
||||||
|
//console.log(key + " is from type " + columnDef?.type);
|
||||||
|
|
||||||
|
if (columnDef?.type === 'collaborator') {
|
||||||
|
// collaborator is an array of strings.
|
||||||
|
let collaborators = (row[key] as string[]) || [];
|
||||||
|
if (collaborators.length > 0) {
|
||||||
|
let newArray = collaborators.map((email) => {
|
||||||
|
let collaboratorDetails = getCollaboratorInfo(email, collaboratorList);
|
||||||
|
let newColl = {
|
||||||
|
email: email,
|
||||||
|
contact_email: collaboratorDetails['contact_email'],
|
||||||
|
name: collaboratorDetails['name'],
|
||||||
|
};
|
||||||
|
return newColl;
|
||||||
|
});
|
||||||
|
row[key] = newArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
columnDef?.type === 'last-modifier' ||
|
||||||
|
columnDef?.type === 'creator' ||
|
||||||
|
columnDef?.key === '_creator' ||
|
||||||
|
columnDef?.key === '_last_modifier'
|
||||||
|
) {
|
||||||
|
// creator or last-modifier are always a single string.
|
||||||
|
let collaboratorDetails = getCollaboratorInfo(row[key] as string, collaboratorList);
|
||||||
|
row[key] = {
|
||||||
|
email: row[key],
|
||||||
|
contact_email: collaboratorDetails['contact_email'],
|
||||||
|
name: collaboratorDetails['name'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnDef?.type === 'image') {
|
||||||
|
let pictures = (row[key] as string[]) || [];
|
||||||
|
if (pictures.length > 0) {
|
||||||
|
let newArray = pictures.map((url) => ({
|
||||||
|
name: url.split('/').pop(),
|
||||||
|
size: 0,
|
||||||
|
type: 'image',
|
||||||
|
url: url,
|
||||||
|
path: getAssetPath('images', url),
|
||||||
|
}));
|
||||||
|
row[key] = newArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnDef?.type === 'file') {
|
||||||
|
let files = (row[key] as IFile[]) || [];
|
||||||
|
files.forEach((file) => {
|
||||||
|
file.path = getAssetPath('files', file.url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnDef?.type === 'digital-sign') {
|
||||||
|
let digitalSignature: IColumnDigitalSignature | any = row[key];
|
||||||
|
let collaboratorDetails = getCollaboratorInfo(digitalSignature?.username, collaboratorList);
|
||||||
|
if (digitalSignature?.username) {
|
||||||
|
digitalSignature.contact_email = collaboratorDetails['contact_email'];
|
||||||
|
digitalSignature.name = collaboratorDetails['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnDef?.type === 'button') {
|
||||||
|
delete row[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// using create, I input a string like a5adebe279e04415a28b2c7e256e9e8d@auth.local and it should be transformed to an array.
|
||||||
|
// same with multi-select.
|
||||||
|
export function splitStringColumnsToArrays(
|
||||||
|
row: IRowObject,
|
||||||
|
columns: TDtableMetadataColumns,
|
||||||
|
): IRowObject {
|
||||||
|
columns.map((column) => {
|
||||||
|
if (column.type == 'collaborator' || column.type == 'multiple-select') {
|
||||||
|
if (typeof row[column.name] === 'string') {
|
||||||
|
const input = row[column.name] as string;
|
||||||
|
row[column.name] = input.split(',').map((item) => item.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sollte eher heißen: remove nonUpdateColumnTypes and only use allowed columns!
|
||||||
|
export function rowExport(row: IRowObject, columns: TDtableMetadataColumns): IRowObject {
|
||||||
|
let rowAllowed = {} as IRowObject;
|
||||||
|
columns.map((column) => {
|
||||||
|
if (row[column.name]) {
|
||||||
|
rowAllowed[column.name] = row[column.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rowAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dtableSchemaIsColumn = (column: IDtableMetadataColumn): boolean =>
|
||||||
|
!!schema.columnTypes[column.type];
|
||||||
|
|
||||||
|
const dtableSchemaIsUpdateAbleColumn = (column: IDtableMetadataColumn): boolean =>
|
||||||
|
!!schema.columnTypes[column.type] && !schema.nonUpdateAbleColumnTypes[column.type];
|
||||||
|
|
||||||
|
export const dtableSchemaColumns = (columns: TDtableMetadataColumns): TDtableMetadataColumns =>
|
||||||
|
columns.filter(dtableSchemaIsColumn);
|
||||||
|
|
||||||
|
export const updateAble = (columns: TDtableMetadataColumns): TDtableMetadataColumns =>
|
||||||
|
columns.filter(dtableSchemaIsUpdateAbleColumn);
|
61
packages/nodes-base/nodes/SeaTable/v2/Schema.ts
Normal file
61
packages/nodes-base/nodes/SeaTable/v2/Schema.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import type { TColumnType, TDateTimeFormat, TInheritColumnKey } from './types';
|
||||||
|
|
||||||
|
export type ColumnType = keyof typeof schema.columnTypes;
|
||||||
|
|
||||||
|
export const schema = {
|
||||||
|
rowFetchSegmentLimit: 1000,
|
||||||
|
dateTimeFormat: 'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
||||||
|
internalNames: {
|
||||||
|
_id: 'text',
|
||||||
|
_creator: 'creator',
|
||||||
|
_ctime: 'ctime',
|
||||||
|
_last_modifier: 'last-modifier',
|
||||||
|
_mtime: 'mtime',
|
||||||
|
_seq: 'auto-number',
|
||||||
|
},
|
||||||
|
columnTypes: {
|
||||||
|
text: 'Text',
|
||||||
|
'long-text': 'Long Text',
|
||||||
|
number: 'Number',
|
||||||
|
collaborator: 'Collaborator',
|
||||||
|
date: 'Date',
|
||||||
|
duration: 'Duration',
|
||||||
|
'single-select': 'Single Select',
|
||||||
|
'multiple-select': 'Multiple Select',
|
||||||
|
image: 'Image',
|
||||||
|
file: 'File',
|
||||||
|
email: 'Email',
|
||||||
|
url: 'URL',
|
||||||
|
checkbox: 'Checkbox',
|
||||||
|
rate: 'Rating',
|
||||||
|
formula: 'Formula',
|
||||||
|
'link-formula': 'Link-Formula',
|
||||||
|
geolocation: 'Geolocation',
|
||||||
|
link: 'Link',
|
||||||
|
creator: 'Creator',
|
||||||
|
ctime: 'Created time',
|
||||||
|
'last-modifier': 'Last Modifier',
|
||||||
|
mtime: 'Last modified time',
|
||||||
|
'auto-number': 'Auto number',
|
||||||
|
button: 'Button',
|
||||||
|
'digital-sign': 'Digital Signature',
|
||||||
|
},
|
||||||
|
nonUpdateAbleColumnTypes: {
|
||||||
|
creator: 'creator',
|
||||||
|
ctime: 'ctime',
|
||||||
|
'last-modifier': 'last-modifier',
|
||||||
|
mtime: 'mtime',
|
||||||
|
'auto-number': 'auto-number',
|
||||||
|
button: 'button',
|
||||||
|
formula: 'formula',
|
||||||
|
'link-formula': 'link-formula',
|
||||||
|
link: 'link',
|
||||||
|
'digital-sign': 'digital-sign',
|
||||||
|
},
|
||||||
|
} as {
|
||||||
|
rowFetchSegmentLimit: number;
|
||||||
|
dateTimeFormat: TDateTimeFormat;
|
||||||
|
internalNames: { [key in TInheritColumnKey]: ColumnType };
|
||||||
|
columnTypes: { [key in TColumnType]: string };
|
||||||
|
nonUpdateAbleColumnTypes: { [key in ColumnType]: ColumnType };
|
||||||
|
};
|
27
packages/nodes-base/nodes/SeaTable/v2/SeaTableV2.node.ts
Normal file
27
packages/nodes-base/nodes/SeaTable/v2/SeaTableV2.node.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
INodeTypeBaseDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { versionDescription } from './actions/versionDescription';
|
||||||
|
import { loadOptions } from './methods';
|
||||||
|
import { router } from './actions/router';
|
||||||
|
|
||||||
|
export class SeaTableV2 implements INodeType {
|
||||||
|
description: INodeTypeDescription;
|
||||||
|
|
||||||
|
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||||
|
this.description = {
|
||||||
|
...baseDescription,
|
||||||
|
...versionDescription,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
methods = { loadOptions };
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions) {
|
||||||
|
return router.call(this);
|
||||||
|
}
|
||||||
|
}
|
195
packages/nodes-base/nodes/SeaTable/v2/actions/Interfaces.ts
Normal file
195
packages/nodes-base/nodes/SeaTable/v2/actions/Interfaces.ts
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import type { AllEntities, Entity, PropertiesOf } from 'n8n-workflow';
|
||||||
|
|
||||||
|
type SeaTableMap = {
|
||||||
|
row: 'create' | 'get' | 'search' | 'update' | 'remove' | 'lock' | 'unlock';
|
||||||
|
base: 'snapshot' | 'metadata' | 'apiCall' | 'collaborator';
|
||||||
|
link: 'add' | 'remove';
|
||||||
|
asset: 'upload' | 'getPublicURL';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SeaTable = AllEntities<SeaTableMap>;
|
||||||
|
|
||||||
|
export type SeaTableRow = Entity<SeaTableMap, 'row'>;
|
||||||
|
export type SeaTableBase = Entity<SeaTableMap, 'base'>;
|
||||||
|
export type SeaTableLink = Entity<SeaTableMap, 'link'>;
|
||||||
|
export type SeaTableAsset = Entity<SeaTableMap, 'asset'>;
|
||||||
|
|
||||||
|
export type RowProperties = PropertiesOf<SeaTableRow>;
|
||||||
|
export type BaseProperties = PropertiesOf<SeaTableBase>;
|
||||||
|
export type LinkProperties = PropertiesOf<SeaTableLink>;
|
||||||
|
export type AssetProperties = PropertiesOf<SeaTableAsset>;
|
||||||
|
|
||||||
|
import type {
|
||||||
|
TColumnType,
|
||||||
|
TColumnValue,
|
||||||
|
TDtableMetadataColumns,
|
||||||
|
TDtableMetadataTables,
|
||||||
|
TSeaTableServerEdition,
|
||||||
|
TSeaTableServerVersion,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
export interface IApi {
|
||||||
|
server: string;
|
||||||
|
token: string;
|
||||||
|
appAccessToken?: IAppAccessToken;
|
||||||
|
info?: IServerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServerInfo {
|
||||||
|
version: TSeaTableServerVersion;
|
||||||
|
edition: TSeaTableServerEdition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppAccessToken {
|
||||||
|
app_name: string;
|
||||||
|
access_token: string;
|
||||||
|
dtable_uuid: string;
|
||||||
|
dtable_server: string;
|
||||||
|
dtable_socket: string;
|
||||||
|
workspace_id: number;
|
||||||
|
dtable_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDtableMetadataColumn {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
type: TColumnType;
|
||||||
|
editable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TDtableViewColumn {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDtableMetadataTable {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
columns: TDtableMetadataColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDtableMetadata {
|
||||||
|
tables: TDtableMetadataTables;
|
||||||
|
version: string;
|
||||||
|
format_version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEndpointVariables {
|
||||||
|
[name: string]: string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRowObject {
|
||||||
|
[name: string]: TColumnValue | object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRow extends IRowObject {
|
||||||
|
_id: string;
|
||||||
|
_ctime: string;
|
||||||
|
_mtime: string;
|
||||||
|
_seq?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IName {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TOperation = 'cloudHosted' | 'selfHosted';
|
||||||
|
|
||||||
|
export interface ICredential {
|
||||||
|
token: string;
|
||||||
|
domain: string;
|
||||||
|
environment: TOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBase {
|
||||||
|
dtable_uuid: string;
|
||||||
|
access_token: string;
|
||||||
|
workspace_id: number;
|
||||||
|
dtable_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICtx {
|
||||||
|
base?: IBase;
|
||||||
|
credentials?: ICredential;
|
||||||
|
}
|
||||||
|
|
||||||
|
// response object of SQL-Query!
|
||||||
|
export interface IRowResponse {
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
results: IRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// das ist bad
|
||||||
|
export interface IRowResponse2 {
|
||||||
|
rows: IRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** neu von mir **/
|
||||||
|
|
||||||
|
// response object of SQL-Query!
|
||||||
|
export interface ISqlQueryResult {
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
results: IRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// response object of GetMetadata
|
||||||
|
export interface IGetMetadataResult {
|
||||||
|
metadata: IDtableMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
// response object of GetRows
|
||||||
|
export interface IGetRowsResult {
|
||||||
|
rows: IRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICollaboratorsResult {
|
||||||
|
user_list: ICollaborator[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICollaborator {
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
contact_email: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
id_in_org?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IColumnDigitalSignature {
|
||||||
|
username: string;
|
||||||
|
sign_image_url: string;
|
||||||
|
sign_time: string;
|
||||||
|
contact_email?: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFile {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
type: 'file';
|
||||||
|
url: string;
|
||||||
|
path?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILinkData {
|
||||||
|
table_id: string;
|
||||||
|
other_table_id: string;
|
||||||
|
link_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUploadLink {
|
||||||
|
upload_link: string;
|
||||||
|
parent_path: string;
|
||||||
|
img_relative_path: string;
|
||||||
|
file_relative_path: string;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import type { AssetProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const assetGetPublicURLDescription: AssetProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Asset path',
|
||||||
|
name: 'assetPath',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: '/images/2023-09/logo.png',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['getPublicURL'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,21 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function getPublicURL(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const assetPath = this.getNodeParameter('assetPath', index) as string;
|
||||||
|
|
||||||
|
let responseData = [] as IDataObject[];
|
||||||
|
if (assetPath) {
|
||||||
|
responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
`/api/v2.1/dtable/app-download-link/?path=${assetPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { getPublicURL as execute } from './execute';
|
||||||
|
import { assetGetPublicURLDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
36
packages/nodes-base/nodes/SeaTable/v2/actions/asset/index.ts
Normal file
36
packages/nodes-base/nodes/SeaTable/v2/actions/asset/index.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as upload from './upload';
|
||||||
|
import * as getPublicURL from './getPublicURL';
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export { upload, getPublicURL };
|
||||||
|
|
||||||
|
export const descriptions: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Public URL',
|
||||||
|
value: 'getPublicURL',
|
||||||
|
description: 'Get the public URL from asset path',
|
||||||
|
action: 'Get the public URL from asset path',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Upload',
|
||||||
|
value: 'upload',
|
||||||
|
description: 'Add a file/image to an existing row',
|
||||||
|
action: 'Upload a file/image',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'upload',
|
||||||
|
},
|
||||||
|
...upload.description,
|
||||||
|
...getPublicURL.description,
|
||||||
|
];
|
|
@ -0,0 +1,104 @@
|
||||||
|
import type { AssetProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const assetUploadDescription: AssetProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Column',
|
||||||
|
name: 'uploadColumn',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getAssetColumns',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Select the column for the upload.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID',
|
||||||
|
name: 'rowId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getRowIds',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workspace ID',
|
||||||
|
name: 'workspaceId',
|
||||||
|
type: 'number',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
numberStepSize: 1,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'How to get the workspace ID: https://seatable.io/docs/arbeiten-mit-gruppen/workspace-id-einer-gruppe-ermitteln/?lang=auto',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Property Name',
|
||||||
|
name: 'dataPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: 'data',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the binary property which contains the data for the file to be written',
|
||||||
|
},
|
||||||
|
/*{
|
||||||
|
displayName: 'Replace',
|
||||||
|
name: 'replace',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['asset'],
|
||||||
|
operation: ['upload'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Replace existing file if the file/image already exists with same name.',
|
||||||
|
},*/
|
||||||
|
];
|
|
@ -0,0 +1,89 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
import type { IUploadLink, IRowObject } from '../../Interfaces';
|
||||||
|
|
||||||
|
export async function upload(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
// step 1: upload file to base
|
||||||
|
const uploadColumn = this.getNodeParameter('uploadColumn', index) as any;
|
||||||
|
const uploadColumnType = uploadColumn.split(':::')[1];
|
||||||
|
const dataPropertyName = this.getNodeParameter('dataPropertyName', index) as string;
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||||
|
const workspaceId = this.getNodeParameter('workspaceId', index) as string;
|
||||||
|
const uploadLink = (await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/api/v2.1/dtable/app-upload-link/',
|
||||||
|
)) as IUploadLink;
|
||||||
|
|
||||||
|
// Get the binary data
|
||||||
|
const fileBufferData = await this.helpers.getBinaryDataBuffer(index, dataPropertyName);
|
||||||
|
const binaryData = this.helpers.assertBinaryData(index, dataPropertyName);
|
||||||
|
// Create our request option
|
||||||
|
const options = {
|
||||||
|
formData: {
|
||||||
|
file: {
|
||||||
|
value: fileBufferData,
|
||||||
|
options: {
|
||||||
|
filename: binaryData.fileName,
|
||||||
|
contentType: binaryData.mimeType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parent_dir: uploadLink.parent_path,
|
||||||
|
replace: '0',
|
||||||
|
relative_path:
|
||||||
|
uploadColumnType === 'image' ? uploadLink.img_relative_path : uploadLink.file_relative_path,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
let uploadAsset = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
`/seafhttp/upload-api/${uploadLink.upload_link.split('seafhttp/upload-api/')[1]}?ret-json=true`,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
'',
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
// now step 2 (attaching the file to a column in a base)
|
||||||
|
for (let c = 0; c < uploadAsset.length; c++) {
|
||||||
|
const body = {
|
||||||
|
table_name: tableName,
|
||||||
|
row_id: rowId,
|
||||||
|
row: {},
|
||||||
|
} as IDataObject;
|
||||||
|
let rowInput = {} as IRowObject;
|
||||||
|
|
||||||
|
const filePath = [
|
||||||
|
`/workspace/${workspaceId}${uploadLink.parent_path}/${uploadLink.img_relative_path}/${uploadAsset[c].name}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (uploadColumnType === 'image') {
|
||||||
|
rowInput[uploadColumn.split(':::')[0]] = filePath;
|
||||||
|
} else if (uploadColumnType === 'file') {
|
||||||
|
rowInput[uploadColumn.split(':::')[0]] = uploadAsset;
|
||||||
|
uploadAsset[c].type = 'file';
|
||||||
|
uploadAsset[c].url = filePath;
|
||||||
|
}
|
||||||
|
body.row = rowInput;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'PUT',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
uploadAsset[c]['upload_successful'] = responseData.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(uploadAsset as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { upload as execute } from './execute';
|
||||||
|
import { assetUploadDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,136 @@
|
||||||
|
import type { BaseProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const baseApiCallDescription: BaseProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
operation: ['apiCall'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'HTTP Method',
|
||||||
|
name: 'apiMethod',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'POST',
|
||||||
|
value: 'POST',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GET',
|
||||||
|
value: 'GET',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'POST',
|
||||||
|
value: 'POST',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DELETE',
|
||||||
|
value: 'DELETE',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
operation: ['apiCall'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Hint: The Authentication header is included automatically.',
|
||||||
|
name: 'notice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
operation: ['apiCall'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'URL',
|
||||||
|
name: 'apiEndpoint',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: ['apiCall'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
placeholder: '/dtable-server/...',
|
||||||
|
description:
|
||||||
|
'The URL has to start with /dtable-server/ or /dtable-db/. All possible requests can be found at the SeaTable API Reference at https://api.seatable.io \
|
||||||
|
Please be aware that only request from the section Base Operations that use an Base-Token for the authentication are allowed to use.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Query String Parameters',
|
||||||
|
name: 'apiParams',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
default: '',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'These params will be URL-encoded and appended to the URL when making the request.',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'apiParamsValues',
|
||||||
|
displayName: 'Parameters',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
operation: ['apiCall'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body',
|
||||||
|
name: 'apiBody',
|
||||||
|
type: 'json',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
operation: ['apiCall'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Only valid JSON is accepted. n8n will pass anything you enter as raw input. For example, {"foo", "bar"} is perfectly valid. Of cause you can use variables from n8n inside your JSON.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,15 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function apiCall(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata/',
|
||||||
|
);
|
||||||
|
return this.helpers.returnJsonArray(responseData.metadata as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { apiCall as execute } from './execute';
|
||||||
|
import { baseApiCallDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { BaseProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const baseCollaboratorDescription: BaseProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Name or email of the collaborator',
|
||||||
|
name: 'searchString',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Enter the name or the email or the collaborator',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
operation: ['collaborator'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'SeaTable identifies users with a unique username like 244b43hr6fy54bb4afa2c2cb7369d244@auth.local. Get this username from an email or the name of a collaborator.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
import { ICollaborator } from '../../Interfaces';
|
||||||
|
|
||||||
|
export async function collaborator(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const searchString = this.getNodeParameter('searchString', index) as string;
|
||||||
|
|
||||||
|
const collaboratorsResult = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/related-users/',
|
||||||
|
);
|
||||||
|
const collaborators = collaboratorsResult.user_list || [];
|
||||||
|
|
||||||
|
const collaborator = collaborators.filter(
|
||||||
|
(col: ICollaborator) =>
|
||||||
|
col.contact_email.includes(searchString) || col.name.includes(searchString),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(collaborator as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { collaborator as execute } from './execute';
|
||||||
|
import { baseCollaboratorDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
53
packages/nodes-base/nodes/SeaTable/v2/actions/base/index.ts
Normal file
53
packages/nodes-base/nodes/SeaTable/v2/actions/base/index.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import * as snapshot from './snapshot';
|
||||||
|
import * as metadata from './metadata';
|
||||||
|
import * as apiCall from './apiCall';
|
||||||
|
import * as collaborator from './collaborator';
|
||||||
|
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export { snapshot, metadata, apiCall, collaborator };
|
||||||
|
|
||||||
|
export const descriptions: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['base'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Snapshot',
|
||||||
|
value: 'snapshot',
|
||||||
|
description: 'Create a snapshot of the base',
|
||||||
|
action: 'Create a Snapshot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Metadata',
|
||||||
|
value: 'metadata',
|
||||||
|
description: 'Get the complete metadata of the base',
|
||||||
|
action: 'Get metadata of a base',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'API Call',
|
||||||
|
value: 'apiCall',
|
||||||
|
description: 'Perform an authorized API call (Base Operation)',
|
||||||
|
action: 'Make an API Call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Collaborator',
|
||||||
|
value: 'collaborator',
|
||||||
|
description: 'Get this username from the email or name of a collaborator.',
|
||||||
|
action: 'Get username from email or name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
...snapshot.description,
|
||||||
|
...metadata.description,
|
||||||
|
...apiCall.description,
|
||||||
|
...collaborator.description,
|
||||||
|
];
|
|
@ -0,0 +1,3 @@
|
||||||
|
import type { BaseProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const baseMetadataDescription: BaseProperties = [];
|
|
@ -0,0 +1,15 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function metadata(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata/',
|
||||||
|
);
|
||||||
|
return this.helpers.returnJsonArray(responseData.metadata as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { metadata as execute } from './execute';
|
||||||
|
import { baseMetadataDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,3 @@
|
||||||
|
import type { BaseProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const baseSnapshotDescription: BaseProperties = [];
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function snapshot(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/snapshot/',
|
||||||
|
{ dtable_name: 'snapshot' },
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { snapshot as execute } from './execute';
|
||||||
|
import { baseSnapshotDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,69 @@
|
||||||
|
import type { LinkProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const linkAddDescription: LinkProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name (Source)',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Name of table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNameAndId',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['add'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link column',
|
||||||
|
name: 'linkColumn',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['add'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getLinkColumns',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Select the column to create a link.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID from the source table',
|
||||||
|
name: 'linkColumnSourceId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['add'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Provide the row ID of table you selected.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID from the target',
|
||||||
|
name: 'linkColumnTargetId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['add'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Provide the row ID of table you want to link.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function add(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const linkColumn = this.getNodeParameter('linkColumn', index) as any;
|
||||||
|
const linkColumnSourceId = this.getNodeParameter('linkColumnSourceId', index) as string;
|
||||||
|
const linkColumnTargetId = this.getNodeParameter('linkColumnTargetId', index) as string;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/links/',
|
||||||
|
{
|
||||||
|
link_id: linkColumn.split(':::')[1],
|
||||||
|
table_id: tableName.split(':::')[1],
|
||||||
|
table_row_id: linkColumnSourceId,
|
||||||
|
other_table_id: linkColumn.split(':::')[2],
|
||||||
|
other_table_row_id: linkColumnTargetId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { add as execute } from './execute';
|
||||||
|
import { linkAddDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
36
packages/nodes-base/nodes/SeaTable/v2/actions/link/index.ts
Normal file
36
packages/nodes-base/nodes/SeaTable/v2/actions/link/index.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as add from './add';
|
||||||
|
import * as remove from './remove';
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export { add, remove };
|
||||||
|
|
||||||
|
export const descriptions: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add',
|
||||||
|
value: 'add',
|
||||||
|
description: 'Create a link between two rows in a link column',
|
||||||
|
action: 'Add a row link',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove',
|
||||||
|
value: 'remove',
|
||||||
|
description: 'Remove a link between two rows from a link column',
|
||||||
|
action: 'Remove a row link',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'add',
|
||||||
|
},
|
||||||
|
...add.description,
|
||||||
|
...remove.description,
|
||||||
|
];
|
|
@ -0,0 +1,69 @@
|
||||||
|
import type { LinkProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const linkRemoveDescription: LinkProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name (Source)',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Name of table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNameAndId',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link column',
|
||||||
|
name: 'linkColumn',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getLinkColumns',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Select the column to create a link.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID from the source table',
|
||||||
|
name: 'linkColumnSourceId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Provide the row ID of table you selected.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID from the target',
|
||||||
|
name: 'linkColumnTargetId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['link'],
|
||||||
|
operation: ['remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Provide the row ID of table you want to link.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,28 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function remove(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const linkColumn = this.getNodeParameter('linkColumn', index) as any;
|
||||||
|
const linkColumnSourceId = this.getNodeParameter('linkColumnSourceId', index) as string;
|
||||||
|
const linkColumnTargetId = this.getNodeParameter('linkColumnTargetId', index) as string;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'DELETE',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/links/',
|
||||||
|
{
|
||||||
|
link_id: linkColumn.split(':::')[1],
|
||||||
|
table_id: tableName.split(':::')[1],
|
||||||
|
table_row_id: linkColumnSourceId,
|
||||||
|
other_table_id: linkColumn.split(':::')[2],
|
||||||
|
other_table_row_id: linkColumnTargetId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { remove as execute } from './execute';
|
||||||
|
import { linkRemoveDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
56
packages/nodes-base/nodes/SeaTable/v2/actions/router.ts
Normal file
56
packages/nodes-base/nodes/SeaTable/v2/actions/router.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as row from './row';
|
||||||
|
import * as base from './base';
|
||||||
|
import * as link from './link';
|
||||||
|
import * as asset from './asset';
|
||||||
|
|
||||||
|
import type { SeaTable } from './Interfaces';
|
||||||
|
|
||||||
|
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const operationResult: INodeExecutionData[] = [];
|
||||||
|
let responseData: IDataObject | IDataObject[] = [];
|
||||||
|
|
||||||
|
//console.log('ITEM LENGTH ' + items.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const resource = this.getNodeParameter<SeaTable>('resource', i);
|
||||||
|
const operation = this.getNodeParameter('operation', i);
|
||||||
|
|
||||||
|
//console.log(operation);
|
||||||
|
//console.log(resource);
|
||||||
|
|
||||||
|
const seatable = {
|
||||||
|
resource,
|
||||||
|
operation,
|
||||||
|
} as SeaTable;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (seatable.resource === 'row') {
|
||||||
|
responseData = await row[seatable.operation].execute.call(this, i);
|
||||||
|
} else if (seatable.resource === 'base') {
|
||||||
|
responseData = await base[seatable.operation].execute.call(this, i);
|
||||||
|
} else if (seatable.resource === 'link') {
|
||||||
|
responseData = await link[seatable.operation].execute.call(this, i);
|
||||||
|
} else if (seatable.resource === 'asset') {
|
||||||
|
responseData = await asset[seatable.operation].execute.call(this, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
operationResult.push(...executionData);
|
||||||
|
} catch (err) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
operationResult.push({ json: this.getInputData(i)[0].json, error: err });
|
||||||
|
} else {
|
||||||
|
if (err.context) err.context.itemIndex = i;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [operationResult];
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowCreateDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Data to Send',
|
||||||
|
name: 'fieldsToSend',
|
||||||
|
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: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['create'],
|
||||||
|
fieldsToSend: ['autoMapInputData'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
|
||||||
|
placeholder: 'Enter properties...',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Columns to Send',
|
||||||
|
name: 'columnsUi',
|
||||||
|
placeholder: 'Add Column',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValueButtonText: 'Add Column to Send',
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Column',
|
||||||
|
name: 'columnValues',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Column Name',
|
||||||
|
name: 'columnName',
|
||||||
|
type: 'options',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getTableUpdateAbleColumns',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Column Value',
|
||||||
|
name: 'columnValue',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['create'],
|
||||||
|
fieldsToSend: ['defineBelow'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
description: 'Add destination column with its value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Hint: Link, files, images or digital signatures have to be added separately.',
|
||||||
|
name: 'notice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,62 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
seaTableApiRequest,
|
||||||
|
getTableColumns,
|
||||||
|
split,
|
||||||
|
rowExport,
|
||||||
|
updateAble,
|
||||||
|
splitStringColumnsToArrays,
|
||||||
|
} from '../../../GenericFunctions';
|
||||||
|
import type { IRowObject } from '../../Interfaces';
|
||||||
|
import type { TColumnValue, TColumnsUiValues } from '../../../types';
|
||||||
|
|
||||||
|
export async function create(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const tableColumns = await getTableColumns.call(this, tableName);
|
||||||
|
const fieldsToSend = this.getNodeParameter('fieldsToSend', index) as
|
||||||
|
| 'defineBelow'
|
||||||
|
| 'autoMapInputData';
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
table_name: tableName,
|
||||||
|
row: {},
|
||||||
|
} as IDataObject;
|
||||||
|
let rowInput = {} as IRowObject;
|
||||||
|
|
||||||
|
// get rowInput, an object of key:value pairs like { Name: 'Promo Action 1', Status: "Draft" }.
|
||||||
|
if (fieldsToSend === 'autoMapInputData') {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const incomingKeys = Object.keys(items[index].json);
|
||||||
|
const inputDataToIgnore = split(this.getNodeParameter('inputsToIgnore', index, '') as string);
|
||||||
|
for (const key of incomingKeys) {
|
||||||
|
if (inputDataToIgnore.includes(key)) continue;
|
||||||
|
rowInput[key] = items[index].json[key] as TColumnValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const columns = this.getNodeParameter('columnsUi.columnValues', index, []) as TColumnsUiValues;
|
||||||
|
for (const column of columns) {
|
||||||
|
rowInput[column.columnName] = column.columnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only keep key:value pairs for columns that are allowed to update.
|
||||||
|
rowInput = rowExport(rowInput, updateAble(tableColumns));
|
||||||
|
|
||||||
|
// string to array: multi-select and collaborators
|
||||||
|
rowInput = splitStringColumnsToArrays(rowInput, tableColumns);
|
||||||
|
|
||||||
|
body.row = rowInput;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { create as execute } from './execute';
|
||||||
|
import { rowCreateDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,54 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowGetDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID',
|
||||||
|
name: 'rowId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getRowIds',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Simplify output',
|
||||||
|
name: 'simple',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Simplified returns only the columns of your base. Non-simplified will return additional columns like _ctime (=creation time), _mtime (=modification time) etc.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,42 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import type { IRow, IRowResponse, IDtableMetadataColumn } from './../../Interfaces';
|
||||||
|
import {
|
||||||
|
seaTableApiRequest,
|
||||||
|
enrichColumns,
|
||||||
|
simplify_new,
|
||||||
|
getBaseCollaborators,
|
||||||
|
} from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function get(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
// get parameters
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||||
|
const simple = this.getNodeParameter('simple', index) as boolean;
|
||||||
|
|
||||||
|
// get collaborators
|
||||||
|
const collaborators = await getBaseCollaborators.call(this);
|
||||||
|
|
||||||
|
// get rows
|
||||||
|
let sqlResult = (await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
'/dtable-db/api/v1/query/{{dtable_uuid}}/',
|
||||||
|
{
|
||||||
|
sql: `SELECT * FROM \`${tableName}\` WHERE _id = '${rowId}'`,
|
||||||
|
convert_keys: true,
|
||||||
|
},
|
||||||
|
)) as IRowResponse;
|
||||||
|
let metadata = sqlResult.metadata as IDtableMetadataColumn[];
|
||||||
|
let rows = sqlResult.results as IRow[];
|
||||||
|
|
||||||
|
// hide columns like button
|
||||||
|
rows.map((row) => enrichColumns(row, metadata, collaborators));
|
||||||
|
|
||||||
|
// remove columns starting with _ if simple;
|
||||||
|
if (simple) {
|
||||||
|
rows.map((row) => simplify_new(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(rows as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { get as execute } from './execute';
|
||||||
|
import { rowGetDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
76
packages/nodes-base/nodes/SeaTable/v2/actions/row/index.ts
Normal file
76
packages/nodes-base/nodes/SeaTable/v2/actions/row/index.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import * as create from './create';
|
||||||
|
import * as get from './get';
|
||||||
|
import * as search from './search';
|
||||||
|
import * as update from './update';
|
||||||
|
import * as remove from './remove';
|
||||||
|
import * as lock from './lock';
|
||||||
|
import * as unlock from './unlock';
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export { create, get, search, update, remove, lock, unlock };
|
||||||
|
|
||||||
|
export const descriptions: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new row',
|
||||||
|
action: 'Create a row',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get the content of a row',
|
||||||
|
action: 'Get a row',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search',
|
||||||
|
value: 'search',
|
||||||
|
description: 'Search one or multiple rows',
|
||||||
|
action: 'Search a row by keyword',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update the content of a row',
|
||||||
|
action: 'Update a row',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'remove',
|
||||||
|
description: 'Delete a row',
|
||||||
|
action: 'Delete a row',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lock',
|
||||||
|
value: 'lock',
|
||||||
|
description: 'Lock a row to prevent further changes.',
|
||||||
|
action: 'Add a row lock',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Unlock',
|
||||||
|
value: 'unlock',
|
||||||
|
description: 'Remove the lock from a row',
|
||||||
|
action: 'Remove a row lock',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
...create.description,
|
||||||
|
...get.description,
|
||||||
|
...search.description,
|
||||||
|
...update.description,
|
||||||
|
...remove.description,
|
||||||
|
...lock.description,
|
||||||
|
...unlock.description,
|
||||||
|
];
|
|
@ -0,0 +1,40 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowLockDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['lock'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID',
|
||||||
|
name: 'rowId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getRowIds',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['lock'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function lock(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'PUT',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/lock-rows/',
|
||||||
|
{
|
||||||
|
table_name: tableName,
|
||||||
|
row_ids: [rowId],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { lock as execute } from './execute';
|
||||||
|
import { rowLockDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,40 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowRemoveDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID',
|
||||||
|
name: 'rowId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getRowIds',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function remove(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||||
|
|
||||||
|
const requestBody: IDataObject = {
|
||||||
|
table_name: tableName,
|
||||||
|
row_id: rowId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'DELETE',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
requestBody,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { remove as execute } from './execute';
|
||||||
|
import { rowRemoveDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,83 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowSearchDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Column',
|
||||||
|
name: 'searchColumn',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getSearchableColumns',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'Select the column to be searched. Not all column types are supported for search.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Search term',
|
||||||
|
name: 'searchTerm',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
description: 'What to look for?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Activate wildcard search',
|
||||||
|
name: 'wildcard',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
'FALSE: The search only results perfect matches. TRUE: Finds a row even if the search value is part of a string.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Simplify output',
|
||||||
|
name: 'simple',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'Simplified returns only the columns of your base. Non-simplified will return additional columns like _ctime (=creation time), _mtime (=modification time) etc.',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,67 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
seaTableApiRequest,
|
||||||
|
enrichColumns,
|
||||||
|
simplify_new,
|
||||||
|
getBaseCollaborators,
|
||||||
|
} from '../../../GenericFunctions';
|
||||||
|
import { IDtableMetadataColumn, IRow, IRowResponse } from '../../Interfaces';
|
||||||
|
|
||||||
|
export async function search(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const searchColumn = this.getNodeParameter('searchColumn', index) as string;
|
||||||
|
const searchTerm = this.getNodeParameter('searchTerm', index) as any; // string or integer
|
||||||
|
const wildcard = this.getNodeParameter('wildcard', index) as boolean;
|
||||||
|
const simple = this.getNodeParameter('simple', index) as boolean;
|
||||||
|
|
||||||
|
// get collaborators
|
||||||
|
const collaborators = await getBaseCollaborators.call(this);
|
||||||
|
|
||||||
|
//let metadata: IDtableMetadataColumn[] = [];
|
||||||
|
//let rows: IRow[];
|
||||||
|
//let sqlResult: IRowResponse;
|
||||||
|
|
||||||
|
// get the collaborators (avoid executing this multiple times !!!!)
|
||||||
|
/*let collaboratorsResult: ICollaboratorsResult = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/related-users/',
|
||||||
|
);
|
||||||
|
let collaborators: ICollaborator[] = collaboratorsResult.user_list || [];
|
||||||
|
*/
|
||||||
|
|
||||||
|
// this is the base query. The WHERE has to be finalized...
|
||||||
|
let sqlQuery = `SELECT * FROM \`${tableName}\` WHERE \`${searchColumn}\``;
|
||||||
|
|
||||||
|
if (wildcard && isNaN(searchTerm)) sqlQuery = sqlQuery + ' LIKE "%' + searchTerm + '%"';
|
||||||
|
else if (!wildcard && isNaN(searchTerm)) sqlQuery = sqlQuery + ' = "' + searchTerm + '"';
|
||||||
|
else if (wildcard && !isNaN(searchTerm)) sqlQuery = sqlQuery + ' LIKE %' + searchTerm + '%';
|
||||||
|
else if (!wildcard && !isNaN(searchTerm)) sqlQuery = sqlQuery + ' = ' + searchTerm;
|
||||||
|
|
||||||
|
const sqlResult = (await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
'/dtable-db/api/v1/query/{{dtable_uuid}}/',
|
||||||
|
{
|
||||||
|
sql: sqlQuery,
|
||||||
|
convert_keys: true,
|
||||||
|
},
|
||||||
|
)) as IRowResponse;
|
||||||
|
const metadata = sqlResult.metadata as IDtableMetadataColumn[];
|
||||||
|
let rows = sqlResult.results as IRow[];
|
||||||
|
|
||||||
|
// hide columns like button
|
||||||
|
rows.map((row) => enrichColumns(row, metadata, collaborators));
|
||||||
|
|
||||||
|
// remove columns starting with _;
|
||||||
|
if (simple) {
|
||||||
|
rows.map((row) => simplify_new(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(rows as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { search as execute } from './execute';
|
||||||
|
import { rowSearchDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,40 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowUnlockDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['unlock'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID',
|
||||||
|
name: 'rowId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getRowIds',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['unlock'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { seaTableApiRequest } from '../../../GenericFunctions';
|
||||||
|
|
||||||
|
export async function unlock(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'PUT',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/unlock-rows/',
|
||||||
|
{
|
||||||
|
table_name: tableName,
|
||||||
|
row_ids: [rowId],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { unlock as execute } from './execute';
|
||||||
|
import { rowUnlockDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,137 @@
|
||||||
|
import type { RowProperties } from '../../Interfaces';
|
||||||
|
|
||||||
|
export const rowUpdateDescription: RowProperties = [
|
||||||
|
{
|
||||||
|
displayName: 'Table Name',
|
||||||
|
name: 'tableName',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Select a table',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTableNames',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The name of SeaTable table to access. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Row ID',
|
||||||
|
name: 'rowId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getRowIds',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Data to Send',
|
||||||
|
name: 'fieldsToSend',
|
||||||
|
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: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['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: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['update'],
|
||||||
|
fieldsToSend: ['autoMapInputData'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
|
||||||
|
placeholder: 'Enter properties...',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Columns to Send',
|
||||||
|
name: 'columnsUi',
|
||||||
|
placeholder: 'Add Column',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValueButtonText: 'Add Column to Send',
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Column',
|
||||||
|
name: 'columnValues',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Column Name',
|
||||||
|
name: 'columnName',
|
||||||
|
type: 'options',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['tableName'],
|
||||||
|
loadOptionsMethod: 'getTableUpdateAbleColumns',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Column Value',
|
||||||
|
name: 'columnValue',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['update'],
|
||||||
|
fieldsToSend: ['defineBelow'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
description: 'Add destination column with its value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Hint: Link, files, images or digital signatures have to be added separately.',
|
||||||
|
name: 'notice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['row'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,63 @@
|
||||||
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
seaTableApiRequest,
|
||||||
|
getTableColumns,
|
||||||
|
split,
|
||||||
|
rowExport,
|
||||||
|
updateAble,
|
||||||
|
splitStringColumnsToArrays,
|
||||||
|
} from '../../../GenericFunctions';
|
||||||
|
import type { IRowObject } from '../../Interfaces';
|
||||||
|
import type { TColumnsUiValues, TColumnValue } from '../../../types';
|
||||||
|
|
||||||
|
export async function update(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
index: number,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||||
|
const tableColumns = await getTableColumns.call(this, tableName);
|
||||||
|
const fieldsToSend = this.getNodeParameter('fieldsToSend', index) as
|
||||||
|
| 'defineBelow'
|
||||||
|
| 'autoMapInputData';
|
||||||
|
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
table_name: tableName,
|
||||||
|
row_id: rowId,
|
||||||
|
} as IDataObject;
|
||||||
|
let rowInput = {} as IRowObject;
|
||||||
|
|
||||||
|
// get rowInput, an object of key:value pairs like { Name: 'Promo Action 1', Status: "Draft" }.
|
||||||
|
if (fieldsToSend === 'autoMapInputData') {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const incomingKeys = Object.keys(items[index].json);
|
||||||
|
const inputDataToIgnore = split(this.getNodeParameter('inputsToIgnore', index, '') as string);
|
||||||
|
for (const key of incomingKeys) {
|
||||||
|
if (inputDataToIgnore.includes(key)) continue;
|
||||||
|
rowInput[key] = items[index].json[key] as TColumnValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const columns = this.getNodeParameter('columnsUi.columnValues', index, []) as TColumnsUiValues;
|
||||||
|
for (const column of columns) {
|
||||||
|
rowInput[column.columnName] = column.columnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only keep key:value pairs for columns that are allowed to update.
|
||||||
|
rowInput = rowExport(rowInput, updateAble(tableColumns));
|
||||||
|
|
||||||
|
// string to array: multi-select and collaborators
|
||||||
|
rowInput = splitStringColumnsToArrays(rowInput, tableColumns);
|
||||||
|
|
||||||
|
body.row = rowInput;
|
||||||
|
|
||||||
|
const responseData = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'PUT',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/',
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { update as execute } from './execute';
|
||||||
|
import { rowUpdateDescription as description } from './description';
|
||||||
|
|
||||||
|
export { description, execute };
|
|
@ -0,0 +1,57 @@
|
||||||
|
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
import * as row from './row';
|
||||||
|
import * as base from './base';
|
||||||
|
import * as link from './link';
|
||||||
|
import * as asset from './asset';
|
||||||
|
|
||||||
|
export const versionDescription: INodeTypeDescription = {
|
||||||
|
displayName: 'SeaTable',
|
||||||
|
name: 'seaTable',
|
||||||
|
icon: 'file:seatable.svg',
|
||||||
|
group: ['output'],
|
||||||
|
version: 2,
|
||||||
|
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
||||||
|
description: 'Consume the SeaTable API',
|
||||||
|
defaults: {
|
||||||
|
name: 'SeaTable',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'seaTableApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Row',
|
||||||
|
value: 'row',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Base',
|
||||||
|
value: 'base',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Link',
|
||||||
|
value: 'link',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Asset',
|
||||||
|
value: 'asset',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'row',
|
||||||
|
},
|
||||||
|
...row.descriptions,
|
||||||
|
...base.descriptions,
|
||||||
|
...link.descriptions,
|
||||||
|
...asset.descriptions,
|
||||||
|
],
|
||||||
|
};
|
1
packages/nodes-base/nodes/SeaTable/v2/methods/index.ts
Normal file
1
packages/nodes-base/nodes/SeaTable/v2/methods/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as loadOptions from './loadOptions';
|
227
packages/nodes-base/nodes/SeaTable/v2/methods/loadOptions.ts
Normal file
227
packages/nodes-base/nodes/SeaTable/v2/methods/loadOptions.ts
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||||
|
import { getTableColumns, seaTableApiRequest, updateAble } from '../GenericFunctions';
|
||||||
|
import type { IRow } from '../actions/Interfaces';
|
||||||
|
|
||||||
|
export async function getTableNames(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
// this.getCurrentNodeParameter('viewName'); // das kommt vom trigger. Brauche ich das???
|
||||||
|
const {
|
||||||
|
metadata: { tables },
|
||||||
|
} = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
||||||
|
);
|
||||||
|
for (const table of tables) {
|
||||||
|
returnData.push({
|
||||||
|
name: table.name,
|
||||||
|
value: table.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTableNameAndId(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const {
|
||||||
|
metadata: { tables },
|
||||||
|
} = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
||||||
|
);
|
||||||
|
for (const table of tables) {
|
||||||
|
returnData.push({
|
||||||
|
name: table.name,
|
||||||
|
value: table.name + ':::' + table._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSearchableColumns(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const tableName = this.getCurrentNodeParameter('tableName') as string;
|
||||||
|
if (tableName) {
|
||||||
|
const columns = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/columns',
|
||||||
|
{},
|
||||||
|
{ table_name: tableName },
|
||||||
|
);
|
||||||
|
for (const col of columns.columns) {
|
||||||
|
if (
|
||||||
|
col.type === 'text' ||
|
||||||
|
col.type === 'long-text' ||
|
||||||
|
col.type === 'number' ||
|
||||||
|
col.type === 'single-select' ||
|
||||||
|
col.type === 'email' ||
|
||||||
|
col.type === 'url' ||
|
||||||
|
col.type === 'rate' ||
|
||||||
|
col.type === 'formula'
|
||||||
|
) {
|
||||||
|
returnData.push({
|
||||||
|
name: col.name,
|
||||||
|
value: col.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLinkColumns(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
let tableName = this.getCurrentNodeParameter('tableName') as string;
|
||||||
|
tableName = tableName.split(':::')[0];
|
||||||
|
//const tableId = (tableName.split(':::')[1] ? tableName.split(':::')[1] : "");
|
||||||
|
if (tableName) {
|
||||||
|
const columns = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/columns',
|
||||||
|
{},
|
||||||
|
{ table_name: tableName },
|
||||||
|
);
|
||||||
|
for (const col of columns.columns) {
|
||||||
|
if (col.type === 'link') {
|
||||||
|
returnData.push({
|
||||||
|
name: col.name,
|
||||||
|
value: col.name + ':::' + col.data.link_id + ':::' + col.data.other_table_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAssetColumns(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const tableName = this.getCurrentNodeParameter('tableName') as string;
|
||||||
|
if (tableName) {
|
||||||
|
const columns = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/columns',
|
||||||
|
{},
|
||||||
|
{ table_name: tableName },
|
||||||
|
);
|
||||||
|
for (const col of columns.columns) {
|
||||||
|
if (col.type === 'image' || col.type === 'file') {
|
||||||
|
returnData.push({
|
||||||
|
name: col.name,
|
||||||
|
value: col.name + ':::' + col.type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSignatureColumns(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const tableName = this.getCurrentNodeParameter('tableName') as string;
|
||||||
|
if (tableName) {
|
||||||
|
// only execute if table is selected
|
||||||
|
const columns = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/columns',
|
||||||
|
{},
|
||||||
|
{ table_name: tableName },
|
||||||
|
);
|
||||||
|
for (const col of columns.columns) {
|
||||||
|
if (col.type === 'digital-sign') {
|
||||||
|
// file+image are difficult: every time the row changes, all files trigger.
|
||||||
|
returnData.push({
|
||||||
|
name: col.name,
|
||||||
|
value: col.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTableUpdateAbleColumns(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName') as string;
|
||||||
|
let columns = await getTableColumns.call(this, tableName);
|
||||||
|
|
||||||
|
// remove columns that could not be filled
|
||||||
|
columns = updateAble(columns);
|
||||||
|
|
||||||
|
return columns
|
||||||
|
.filter((column) => column.editable)
|
||||||
|
.map((column) => ({ name: column.name, value: column.name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRowIds(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const tableName = this.getNodeParameter('tableName') as string;
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
|
||||||
|
if (tableName) {
|
||||||
|
const sqlResult = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'POST',
|
||||||
|
'/dtable-db/api/v1/query/{{dtable_uuid}}/',
|
||||||
|
{
|
||||||
|
sql: `SELECT * FROM \`${tableName}\` LIMIT 1000`,
|
||||||
|
convert_keys: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let rows = sqlResult.results as IRow[];
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
returnData.push({
|
||||||
|
name: row['0000'] + ' (' + row._id + ')',
|
||||||
|
value: row._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTableViews(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const tableName = this.getCurrentNodeParameter('tableName') as string;
|
||||||
|
if (tableName) {
|
||||||
|
// only execute if table is selected, to avoid unnecessary API requests
|
||||||
|
const { views } = await seaTableApiRequest.call(
|
||||||
|
this,
|
||||||
|
{},
|
||||||
|
'GET',
|
||||||
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/views',
|
||||||
|
{},
|
||||||
|
{ table_name: tableName },
|
||||||
|
);
|
||||||
|
returnData.push({
|
||||||
|
name: '<Do not limit to a view>',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
for (const view of views) {
|
||||||
|
returnData.push({
|
||||||
|
name: view.name,
|
||||||
|
value: view.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
97
packages/nodes-base/nodes/SeaTable/v2/types.ts
Normal file
97
packages/nodes-base/nodes/SeaTable/v2/types.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// ----------------------------------
|
||||||
|
// SeaTable
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
export type TSeaTableServerVersion = '2.0.6';
|
||||||
|
export type TSeaTableServerEdition = 'enterprise edition';
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// dtable
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
import type {
|
||||||
|
IDtableMetadataColumn,
|
||||||
|
IDtableMetadataTable,
|
||||||
|
TDtableViewColumn,
|
||||||
|
} from './actions/Interfaces';
|
||||||
|
import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export type TColumnType =
|
||||||
|
| 'text'
|
||||||
|
| 'long-text'
|
||||||
|
| 'number'
|
||||||
|
| 'collaborator'
|
||||||
|
| 'date'
|
||||||
|
| 'duration'
|
||||||
|
| 'single-select'
|
||||||
|
| 'multiple-select'
|
||||||
|
| 'image'
|
||||||
|
| 'file'
|
||||||
|
| 'email'
|
||||||
|
| 'url'
|
||||||
|
| 'checkbox'
|
||||||
|
| 'rate'
|
||||||
|
| 'formula'
|
||||||
|
| 'link-formula'
|
||||||
|
| 'geolocation'
|
||||||
|
| 'link'
|
||||||
|
| 'creator'
|
||||||
|
| 'ctime'
|
||||||
|
| 'last-modifier'
|
||||||
|
| 'mtime'
|
||||||
|
| 'auto-number'
|
||||||
|
| 'button'
|
||||||
|
| 'digital-sign';
|
||||||
|
|
||||||
|
export type TInheritColumnKey =
|
||||||
|
| '_id'
|
||||||
|
| '_creator'
|
||||||
|
| '_ctime'
|
||||||
|
| '_last_modifier'
|
||||||
|
| '_mtime'
|
||||||
|
| '_seq'
|
||||||
|
| '_archived'
|
||||||
|
| '_locked'
|
||||||
|
| '_locked_by';
|
||||||
|
|
||||||
|
export type TColumnValue = undefined | boolean | number | string | string[] | null;
|
||||||
|
export type TColumnKey = TInheritColumnKey | string;
|
||||||
|
|
||||||
|
export type TDtableMetadataTables = readonly IDtableMetadataTable[];
|
||||||
|
export type TDtableMetadataColumns = IDtableMetadataColumn[];
|
||||||
|
export type TDtableViewColumns = readonly TDtableViewColumn[];
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// api
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
export type TEndpointVariableName = 'access_token' | 'dtable_uuid' | 'server';
|
||||||
|
|
||||||
|
// Template Literal Types requires-ts-4.1.5 -- deferred
|
||||||
|
export type TMethod = 'GET' | 'POST';
|
||||||
|
type TEndpoint =
|
||||||
|
| '/api/v2.1/dtable/app-access-token/'
|
||||||
|
| '/dtable-server/api/v1/dtables/{{dtable_uuid}}/rows/';
|
||||||
|
export type TEndpointExpr = TEndpoint;
|
||||||
|
export type TEndpointResolvedExpr =
|
||||||
|
TEndpoint; /* deferred: but already in use for header values, e.g. authentication */
|
||||||
|
|
||||||
|
export type TDateTimeFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ' /* moment.js */;
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// node
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
export type TCredentials = ICredentialDataDecryptedObject | undefined;
|
||||||
|
|
||||||
|
export type TTriggerOperation = 'create' | 'update';
|
||||||
|
|
||||||
|
export type TOperation = 'append' | 'list' | 'metadata';
|
||||||
|
|
||||||
|
export type TLoadedResource = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
export type TColumnsUiValues = Array<{
|
||||||
|
columnName: string;
|
||||||
|
columnValue: string;
|
||||||
|
}>;
|
Loading…
Reference in a new issue