first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import type { AllEntities, Entity, PropertiesOf } from 'n8n-workflow';
|
||||
|
||||
type SeaTableMap = {
|
||||
row: 'create' | 'get' | 'search' | 'update' | 'remove' | 'lock' | 'unlock' | 'list';
|
||||
base: 'snapshot' | 'metadata' | 'collaborator';
|
||||
link: 'add' | 'list' | '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,58 @@
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as asset from './asset';
|
||||
import * as base from './base';
|
||||
import * as link from './link';
|
||||
import * as row from './row';
|
||||
|
||||
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: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.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,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Asset Path',
|
||||
name: 'assetPath',
|
||||
type: 'string',
|
||||
placeholder: '/images/2023-09/logo.png',
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['asset'],
|
||||
operation: ['getPublicURL'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as getPublicURL from './getPublicURL.operation';
|
||||
import * as upload from './upload.operation';
|
||||
|
||||
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 or image',
|
||||
},
|
||||
],
|
||||
default: 'upload',
|
||||
},
|
||||
...upload.description,
|
||||
...getPublicURL.description,
|
||||
];
|
||||
@@ -0,0 +1,232 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
import type { IUploadLink, IRowObject } from '../Interfaces';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Table Name',
|
||||
name: 'tableName',
|
||||
type: 'options',
|
||||
placeholder: 'Select a table',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTableNames',
|
||||
},
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description:
|
||||
'Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column Name',
|
||||
name: 'uploadColumn',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getAssetColumns',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description:
|
||||
'Choose from the list, or specify the name using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Row ID',
|
||||
name: 'rowId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getRowIds',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Name',
|
||||
name: 'dataPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
description: 'Name of the binary property which contains the data for the file to be written',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Replace Existing File',
|
||||
name: 'replace',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to replace the existing asset with the same name (true). Otherwise, a new version with a different name (numeral in parentheses) will be uploaded (false).',
|
||||
},
|
||||
{
|
||||
displayName: 'Append to Column',
|
||||
name: 'append',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to keep existing files/images in the column and append the new asset (true). Otherwise, the existing files/images are removed from the column (false).',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['asset'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const uploadColumn = this.getNodeParameter('uploadColumn', index) as string;
|
||||
const uploadColumnType = uploadColumn.split(':::')[1];
|
||||
const uploadColumnName = uploadColumn.split(':::')[0];
|
||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', index);
|
||||
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||
const uploadLink = (await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'GET',
|
||||
'/api/v2.1/dtable/app-upload-link/',
|
||||
)) as IUploadLink;
|
||||
const relativePath =
|
||||
uploadColumnType === 'image' ? uploadLink.img_relative_path : uploadLink.file_relative_path;
|
||||
|
||||
const options = this.getNodeParameter('options', index);
|
||||
|
||||
// get server url
|
||||
const credentials: any = await this.getCredentials('seaTableApi');
|
||||
const serverURL: string = credentials.domain
|
||||
? credentials.domain.replace(/\/$/, '')
|
||||
: 'https://cloud.seatable.io';
|
||||
|
||||
// get workspaceId
|
||||
const workspaceId = (
|
||||
await this.helpers.httpRequest({
|
||||
headers: {
|
||||
Authorization: `Token ${credentials.token}`,
|
||||
},
|
||||
url: `${serverURL}/api/v2.1/dtable/app-access-token/`,
|
||||
json: true,
|
||||
})
|
||||
).workspace_id;
|
||||
|
||||
// if there are already assets attached to the column
|
||||
let existingAssetArray = [];
|
||||
const append = options.append ?? true;
|
||||
if (append) {
|
||||
const rowToUpdate = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'GET',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/rows/' + rowId,
|
||||
{},
|
||||
{
|
||||
table_name: tableName,
|
||||
convert_keys: true,
|
||||
},
|
||||
);
|
||||
existingAssetArray = rowToUpdate[uploadColumnName] ?? [];
|
||||
}
|
||||
|
||||
// Get the binary data and prepare asset for upload
|
||||
const fileBufferData = await this.helpers.getBinaryDataBuffer(index, dataPropertyName);
|
||||
const binaryData = this.helpers.assertBinaryData(index, dataPropertyName);
|
||||
const requestOptions = {
|
||||
formData: {
|
||||
file: {
|
||||
value: fileBufferData,
|
||||
options: {
|
||||
filename: binaryData.fileName,
|
||||
contentType: binaryData.mimeType,
|
||||
},
|
||||
},
|
||||
parent_dir: uploadLink.parent_path,
|
||||
replace: options.replace ? '1' : '0',
|
||||
relative_path: relativePath,
|
||||
},
|
||||
};
|
||||
|
||||
// Send the upload request
|
||||
const uploadAsset = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
`/seafhttp/upload-api/${uploadLink.upload_link.split('seafhttp/upload-api/')[1]}?ret-json=true`,
|
||||
{},
|
||||
{},
|
||||
'',
|
||||
requestOptions,
|
||||
);
|
||||
|
||||
// attach the asset to a column in a base
|
||||
for (let c = 0; c < uploadAsset.length; c++) {
|
||||
const rowInput = {} as IRowObject;
|
||||
|
||||
const filePath = `${serverURL}/workspace/${workspaceId}${uploadLink.parent_path}/${relativePath}/${uploadAsset[c].name}`;
|
||||
|
||||
if (uploadColumnType === 'image') {
|
||||
rowInput[uploadColumnName] = [filePath];
|
||||
} else if (uploadColumnType === 'file') {
|
||||
rowInput[uploadColumnName] = uploadAsset;
|
||||
uploadAsset[c].type = 'file';
|
||||
uploadAsset[c].url = filePath;
|
||||
}
|
||||
|
||||
// merge with existing assets in this column or with [] and remove duplicates
|
||||
const mergedArray = existingAssetArray.concat(rowInput[uploadColumnName]);
|
||||
|
||||
// Remove duplicates from input, keeping the last one
|
||||
const uniqueAssets = Array.from(new Set(mergedArray));
|
||||
|
||||
// Update the rowInput with the unique assets and store into body.row.
|
||||
rowInput[uploadColumnName] = uniqueAssets;
|
||||
const body = {
|
||||
table_name: tableName,
|
||||
updates: [
|
||||
{
|
||||
row_id: rowId,
|
||||
row: rowInput,
|
||||
},
|
||||
],
|
||||
} as IDataObject;
|
||||
|
||||
// attach assets to table row
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'PUT',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/rows/',
|
||||
body,
|
||||
);
|
||||
|
||||
uploadAsset[c].upload_successful = responseData.success;
|
||||
}
|
||||
|
||||
return this.helpers.returnJsonArray(uploadAsset as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
import type { ICollaborator } from '../Interfaces';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Name or email of the collaborator',
|
||||
name: 'searchString',
|
||||
type: 'string',
|
||||
placeholder: 'Enter the name or the email or the collaborator',
|
||||
required: true,
|
||||
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.',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['base'],
|
||||
operation: ['collaborator'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const searchString = this.getNodeParameter('searchString', index) as string;
|
||||
|
||||
const collaboratorsResult = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'GET',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/related-users/',
|
||||
);
|
||||
const collaborators = collaboratorsResult.user_list || [];
|
||||
|
||||
const data = collaborators.filter(
|
||||
(col: ICollaborator) =>
|
||||
col.contact_email.includes(searchString) || col.name.includes(searchString),
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(data as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as collaborator from './collaborator.operation';
|
||||
import * as metadata from './metadata.operation';
|
||||
import * as snapshot from './snapshot.operation';
|
||||
|
||||
export { snapshot, metadata, 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: 'Collaborator',
|
||||
value: 'collaborator',
|
||||
description: 'Get the username from the email or name of a collaborator',
|
||||
action: 'Get username from email or name',
|
||||
},
|
||||
],
|
||||
default: 'snapshot',
|
||||
},
|
||||
...snapshot.description,
|
||||
...metadata.description,
|
||||
...collaborator.description,
|
||||
];
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export const properties: INodeProperties[] = [];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['base'],
|
||||
operation: ['metadata'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'GET',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/metadata/',
|
||||
);
|
||||
return this.helpers.returnJsonArray(responseData.metadata as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export const properties: INodeProperties[] = [];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['base'],
|
||||
operation: ['snapshot'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/snapshot/',
|
||||
{ dtable_name: 'snapshot' },
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Table Name (Source)',
|
||||
name: 'tableName',
|
||||
type: 'options',
|
||||
placeholder: 'Name of table',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTableNameAndId',
|
||||
},
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description:
|
||||
'Choose from the list, of specify by using an expression. Provide it in the way "table_name:::table_id".',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Link Column',
|
||||
name: 'linkColumn',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getLinkColumns',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description:
|
||||
'Choose from the list of specify the Link Column by using an expression. You have to provide it in the way "column_name:::link_id:::other_table_id".',
|
||||
},
|
||||
{
|
||||
displayName: 'Row ID From the Source Table',
|
||||
name: 'linkColumnSourceId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Provide the row ID of table you selected',
|
||||
},
|
||||
{
|
||||
displayName: 'Row ID From the Target',
|
||||
name: 'linkColumnTargetId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Provide the row ID of table you want to link',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['link'],
|
||||
operation: ['add'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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 body = {
|
||||
link_id: linkColumn.split(':::')[1],
|
||||
table_id: tableName.split(':::')[1],
|
||||
other_table_id: linkColumn.split(':::')[2],
|
||||
other_rows_ids_map: {
|
||||
[linkColumnSourceId]: [linkColumnTargetId],
|
||||
},
|
||||
};
|
||||
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/links/',
|
||||
body,
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as add from './add.operation';
|
||||
import * as list from './list.operation';
|
||||
import * as remove from './remove.operation';
|
||||
|
||||
export { add, list, 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: 'List',
|
||||
value: 'list',
|
||||
description: 'List all links of a specific row',
|
||||
action: 'List row links',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description: 'Remove a link between two rows from a link column',
|
||||
action: 'Remove a row link',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
},
|
||||
...add.description,
|
||||
...list.description,
|
||||
...remove.description,
|
||||
];
|
||||
@@ -0,0 +1,92 @@
|
||||
/* eslint-disable n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options */
|
||||
/* eslint-disable n8n-nodes-base/node-param-description-wrong-for-dynamic-options */
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Table Name',
|
||||
name: 'tableName',
|
||||
type: 'options',
|
||||
placeholder: 'Select a table',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTableNameAndId',
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Choose from the list, of specify by using an expression. Provide it in the way "table_name:::table_id".',
|
||||
},
|
||||
{
|
||||
displayName: 'Link Column',
|
||||
name: 'linkColumn',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getLinkColumnsWithColumnKey',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
description:
|
||||
'Choose from the list of specify the Link Column by using an expression. You have to provide it in the way "column_name:::link_id:::other_table_id:::column_key".',
|
||||
},
|
||||
{
|
||||
displayName: 'Row ID',
|
||||
name: 'rowId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getRowIds',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['link'],
|
||||
operation: ['list'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
// get parameters
|
||||
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||
const linkColumn = this.getNodeParameter('linkColumn', index) as string;
|
||||
const rowId = this.getNodeParameter('rowId', index) as string;
|
||||
|
||||
// get rows
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/query-links/',
|
||||
{
|
||||
table_id: tableName.split(':::')[1],
|
||||
link_column_key: linkColumn.split(':::')[3],
|
||||
rows: [
|
||||
{
|
||||
row_id: rowId,
|
||||
offset: 0,
|
||||
limit: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/* eslint-disable n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options */
|
||||
/* eslint-disable n8n-nodes-base/node-param-description-wrong-for-dynamic-options */
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Table Name (Source)',
|
||||
name: 'tableName',
|
||||
type: 'options',
|
||||
placeholder: 'Name of table',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTableNameAndId',
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Choose from the list, of specify by using an expression. Provide it in the way "table_name:::table_id".',
|
||||
},
|
||||
{
|
||||
displayName: 'Link Column',
|
||||
name: 'linkColumn',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getLinkColumns',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
description:
|
||||
'Choose from the list of specify the Link Column by using an expression. You have to provide it in the way "column_name:::link_id:::other_table_id".',
|
||||
},
|
||||
{
|
||||
displayName: 'Row ID From the Source Table',
|
||||
name: 'linkColumnSourceId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Provide the row ID of table you selected',
|
||||
},
|
||||
{
|
||||
displayName: 'Row ID From the Target Table',
|
||||
name: 'linkColumnTargetId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Provide the row ID of table you want to link',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['link'],
|
||||
operation: ['remove'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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 body = {
|
||||
link_id: linkColumn.split(':::')[1],
|
||||
table_id: tableName.split(':::')[1],
|
||||
other_table_id: linkColumn.split(':::')[2],
|
||||
other_rows_ids_map: {
|
||||
[linkColumnSourceId]: [linkColumnTargetId],
|
||||
},
|
||||
};
|
||||
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'DELETE',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/links/',
|
||||
body,
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import * as asset from './asset';
|
||||
import * as base from './base';
|
||||
import type { SeaTable } from './Interfaces';
|
||||
import * as link from './link';
|
||||
import * as row from './row';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const operationResult: INodeExecutionData[] = [];
|
||||
let responseData: IDataObject | IDataObject[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const resource = this.getNodeParameter<SeaTable>('resource', i);
|
||||
const operation = this.getNodeParameter('operation', i);
|
||||
|
||||
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(
|
||||
responseData as INodeExecutionData[],
|
||||
{
|
||||
itemData: { item: i },
|
||||
},
|
||||
);
|
||||
|
||||
operationResult.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
operationResult.push({ json: this.getInputData(i)[0].json, error });
|
||||
} else {
|
||||
if (error.context) error.context.itemIndex = i;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [operationResult];
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
seaTableApiRequest,
|
||||
getTableColumns,
|
||||
split,
|
||||
rowExport,
|
||||
updateAble,
|
||||
splitStringColumnsToArrays,
|
||||
} from '../../GenericFunctions';
|
||||
import type { TColumnValue, TColumnsUiValues } from '../../types';
|
||||
import type { IRowObject } from '../Interfaces';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
default: 'defineBelow',
|
||||
description: 'Whether to insert the input data this node receives in the new row',
|
||||
},
|
||||
{
|
||||
displayName: 'Apply Column Default Values',
|
||||
name: 'apply_default',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to use the column default values to populate new rows during creation (only available for normal backend)',
|
||||
displayOptions: {
|
||||
show: {
|
||||
bigdata: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName:
|
||||
'In this mode, make sure the incoming data fields are named the same as the columns in SeaTable. (Use an "Edit Fields" node before this node to change them if required.)',
|
||||
name: 'notice',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/fieldsToSend': ['autoMapInputData'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Inputs to Ignore',
|
||||
name: 'inputsToIgnore',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
|
||||
placeholder: 'Enter properties...',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/fieldsToSend': ['autoMapInputData'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Columns to Send',
|
||||
name: 'columnsUi',
|
||||
placeholder: 'Add Column',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValueButtonText: 'Add Column to Send',
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/fieldsToSend': ['defineBelow'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Column',
|
||||
name: 'columnValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Column Name or ID',
|
||||
name: 'columnName',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getTableUpdateAbleColumns',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Column Value',
|
||||
name: 'columnValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
default: {},
|
||||
description:
|
||||
'Add destination column with its value. Provide the value in this way. Date: YYYY-MM-DD or YYYY-MM-DD hh:mm. Duration: time in seconds. Checkbox: true, on or 1. Multi-Select: comma-separated list.',
|
||||
},
|
||||
{
|
||||
displayName: 'Save to "Big Data" Backend',
|
||||
name: 'bigdata',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether write to Big Data backend (true) or not (false). True requires the activation of the Big Data backend in the base.',
|
||||
},
|
||||
{
|
||||
displayName:
|
||||
'Hint: Link, files, images or digital signatures have to be added separately. These column types cannot be set with this node.',
|
||||
name: 'notice',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
operation: ['create'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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 bigdata = this.getNodeParameter('bigdata', index) as boolean;
|
||||
const apply_default = this.getNodeParameter('apply_default', index, false) as boolean;
|
||||
|
||||
const body = {
|
||||
table_name: tableName,
|
||||
rows: {},
|
||||
} 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);
|
||||
|
||||
// save to big data backend
|
||||
if (bigdata) {
|
||||
body.rows = [rowInput];
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/add-archived-rows/',
|
||||
body,
|
||||
);
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
// save to normal backend
|
||||
else {
|
||||
body.rows = [rowInput];
|
||||
if (apply_default) {
|
||||
body.apply_default = true;
|
||||
}
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/rows/',
|
||||
body,
|
||||
);
|
||||
if (responseData.first_row) {
|
||||
return this.helpers.returnJsonArray(responseData.first_row as IDataObject[]);
|
||||
}
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
seaTableApiRequest,
|
||||
enrichColumns,
|
||||
simplify_new,
|
||||
getBaseCollaborators,
|
||||
} from '../../GenericFunctions';
|
||||
import type { IRowResponse, IDtableMetadataColumn } from '../Interfaces';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Return Column Names',
|
||||
name: 'convert',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to return the column keys (false) or the column names (true)',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
operation: ['get'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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 options = this.getNodeParameter('options', index);
|
||||
|
||||
// get collaborators
|
||||
const collaborators = await getBaseCollaborators.call(this);
|
||||
|
||||
// get rows
|
||||
const sqlResult = (await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/sql/',
|
||||
{
|
||||
sql: `SELECT * FROM \`${tableName}\` WHERE _id = '${rowId}'`,
|
||||
convert_keys: options.convert ?? true,
|
||||
},
|
||||
)) as IRowResponse;
|
||||
const metadata = sqlResult.metadata as IDtableMetadataColumn[];
|
||||
const rows = sqlResult.results;
|
||||
|
||||
// hide columns like button
|
||||
rows.map((row) => enrichColumns(row, metadata, collaborators));
|
||||
const simple = options.simple ?? true;
|
||||
// remove columns starting with _ if simple;
|
||||
if (simple) {
|
||||
rows.map((row) => simplify_new(row));
|
||||
}
|
||||
|
||||
return this.helpers.returnJsonArray(rows as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as create from './create.operation';
|
||||
import * as get from './get.operation';
|
||||
import * as list from './list.operation';
|
||||
import * as lock from './lock.operation';
|
||||
import * as remove from './remove.operation';
|
||||
import * as search from './search.operation';
|
||||
import { sharedProperties } from './sharedProperties';
|
||||
import * as unlock from './unlock.operation';
|
||||
import * as update from './update.operation';
|
||||
|
||||
export { create, get, search, update, remove, lock, unlock, list };
|
||||
|
||||
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: 'Delete',
|
||||
value: 'remove',
|
||||
description: 'Delete a row',
|
||||
action: 'Delete a row',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get the content of a row',
|
||||
action: 'Get a row',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'list',
|
||||
description: 'Get many rows from a table or a table view',
|
||||
action: 'Get many rows',
|
||||
},
|
||||
{
|
||||
name: 'Lock',
|
||||
value: 'lock',
|
||||
description: 'Lock a row to prevent further changes',
|
||||
action: 'Add a row lock',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search one or multiple rows',
|
||||
action: 'Search a row by keyword',
|
||||
},
|
||||
{
|
||||
name: 'Unlock',
|
||||
value: 'unlock',
|
||||
description: 'Remove the lock from a row',
|
||||
action: 'Remove a row lock',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update the content of a row',
|
||||
action: 'Update a row',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
...sharedProperties,
|
||||
...create.description,
|
||||
...get.description,
|
||||
...list.description,
|
||||
...search.description,
|
||||
...update.description,
|
||||
];
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
seaTableApiRequest,
|
||||
enrichColumns,
|
||||
simplify_new,
|
||||
getBaseCollaborators,
|
||||
} from '../../GenericFunctions';
|
||||
import type { IRow } from '../Interfaces';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'View Name',
|
||||
name: 'viewName',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getTableViews',
|
||||
},
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description:
|
||||
'The name of SeaTable view to access, or specify by using an expression. Provide it in the way "col.name:::col.type".',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Return Column Names',
|
||||
name: 'convert',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to return the column keys (false) or the column names (true)',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
operation: ['list'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
// get parameters
|
||||
const tableName = this.getNodeParameter('tableName', index) as string;
|
||||
const viewName = this.getNodeParameter('viewName', index) as string;
|
||||
const options = this.getNodeParameter('options', index);
|
||||
|
||||
// get collaborators
|
||||
const collaborators = await getBaseCollaborators.call(this);
|
||||
|
||||
// get rows
|
||||
const requestMeta = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'GET',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/metadata/',
|
||||
);
|
||||
|
||||
const requestRows = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'GET',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/rows/',
|
||||
{},
|
||||
{
|
||||
table_name: tableName,
|
||||
view_name: viewName,
|
||||
limit: 1000,
|
||||
convert_keys: options.convert ?? true,
|
||||
},
|
||||
);
|
||||
|
||||
const metadata =
|
||||
requestMeta.metadata.tables.find((table: { name: string }) => table.name === tableName)
|
||||
?.columns ?? [];
|
||||
const rows = requestRows.rows as IRow[];
|
||||
|
||||
// hide columns like button
|
||||
rows.map((row) => enrichColumns(row, metadata, collaborators));
|
||||
|
||||
const simple = options.simple ?? true;
|
||||
// remove columns starting with _ if simple;
|
||||
if (simple) {
|
||||
rows.map((row) => simplify_new(row));
|
||||
}
|
||||
|
||||
return this.helpers.returnJsonArray(rows as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { IDataObject, INodeExecutionData, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export async function execute(
|
||||
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',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/lock-rows/',
|
||||
{
|
||||
table_name: tableName,
|
||||
row_ids: [rowId],
|
||||
},
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { IDataObject, INodeExecutionData, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export async function execute(
|
||||
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_ids: [rowId],
|
||||
};
|
||||
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'DELETE',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/rows/',
|
||||
requestBody,
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
seaTableApiRequest,
|
||||
enrichColumns,
|
||||
simplify_new,
|
||||
getBaseCollaborators,
|
||||
} from '../../GenericFunctions';
|
||||
import type { IDtableMetadataColumn, IRowResponse } from '../Interfaces';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Column Name or ID',
|
||||
name: 'searchColumn',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getSearchableColumns',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
|
||||
description:
|
||||
'Select the column to be searched. Not all column types are supported for search. Choose from the list, or specify a name using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Search Term',
|
||||
name: 'searchTerm',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'What to look for?',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Case Insensitive Search',
|
||||
name: 'insensitive',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the search ignores case sensitivity (true). Otherwise, it distinguishes between uppercase and lowercase characters.',
|
||||
},
|
||||
{
|
||||
displayName: 'Activate Wildcard Search',
|
||||
name: 'wildcard',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether the search only results perfect matches (true). Otherwise, it finds a row even if the search value is part of a string (false).',
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Return Column Names',
|
||||
name: 'convert',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to return the column keys (false) or the column names (true)',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
operation: ['search'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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 string | number;
|
||||
let searchTermString = String(searchTerm);
|
||||
const options = this.getNodeParameter('options', index);
|
||||
|
||||
// get collaborators
|
||||
const collaborators = await getBaseCollaborators.call(this);
|
||||
|
||||
// this is the base query. The WHERE has to be finalized...
|
||||
let sqlQuery = `SELECT * FROM \`${tableName}\` WHERE \`${searchColumn}\``;
|
||||
|
||||
if (options.insensitive) {
|
||||
searchTermString = searchTermString.toLowerCase();
|
||||
sqlQuery = `SELECT * FROM \`${tableName}\` WHERE lower(\`${searchColumn}\`)`;
|
||||
}
|
||||
|
||||
const wildcard = options.wildcard ?? true;
|
||||
|
||||
if (wildcard) sqlQuery = sqlQuery + ' LIKE "%' + searchTermString + '%"';
|
||||
else if (!wildcard) sqlQuery = sqlQuery + ' = "' + searchTermString + '"';
|
||||
|
||||
const sqlResult = (await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'POST',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/sql',
|
||||
{
|
||||
sql: sqlQuery,
|
||||
convert_keys: options.convert ?? true,
|
||||
},
|
||||
)) as IRowResponse;
|
||||
const metadata = sqlResult.metadata as IDtableMetadataColumn[];
|
||||
const rows = sqlResult.results;
|
||||
|
||||
// hide columns like button
|
||||
rows.map((row) => enrichColumns(row, metadata, collaborators));
|
||||
|
||||
// remove columns starting with _;
|
||||
if (options.simple) {
|
||||
rows.map((row) => simplify_new(row));
|
||||
}
|
||||
|
||||
return this.helpers.returnJsonArray(rows as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const sharedProperties: INodeProperties[] = [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Table Name',
|
||||
name: 'tableName',
|
||||
type: 'options',
|
||||
placeholder: 'Select a table',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTableNames',
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Row ID',
|
||||
name: 'rowId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['tableName'],
|
||||
loadOptionsMethod: 'getRowIds',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
},
|
||||
hide: {
|
||||
operation: ['create', 'list', 'search'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { IDataObject, INodeExecutionData, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { seaTableApiRequest } from '../../GenericFunctions';
|
||||
|
||||
export async function execute(
|
||||
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',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/unlock-rows/',
|
||||
{
|
||||
table_name: tableName,
|
||||
row_ids: [rowId],
|
||||
},
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
updateDisplayOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
seaTableApiRequest,
|
||||
getTableColumns,
|
||||
split,
|
||||
rowExport,
|
||||
updateAble,
|
||||
splitStringColumnsToArrays,
|
||||
} from '../../GenericFunctions';
|
||||
import type { TColumnsUiValues, TColumnValue } from '../../types';
|
||||
import type { IRowObject } from '../Interfaces';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
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 or ID',
|
||||
name: 'columnName',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['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. Provide the value in this way:Date: YYYY-MM-DD or YYYY-MM-DD hh:mmDuration: time in secondsCheckbox: true, on or 1Multi-Select: comma-separated list.',
|
||||
},
|
||||
{
|
||||
displayName: 'Hint: Link, files, images or digital signatures have to be added separately.',
|
||||
name: 'notice',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
operation: ['update'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
const body = {
|
||||
table_name: tableName,
|
||||
updates: [
|
||||
{
|
||||
row_id: rowId,
|
||||
row: rowInput,
|
||||
},
|
||||
],
|
||||
} as IDataObject;
|
||||
|
||||
const responseData = await seaTableApiRequest.call(
|
||||
this,
|
||||
{},
|
||||
'PUT',
|
||||
'/api-gateway/api/v2/dtables/{{dtable_uuid}}/rows/',
|
||||
body,
|
||||
);
|
||||
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as asset from './asset';
|
||||
import * as base from './base';
|
||||
import * as link from './link';
|
||||
import * as row from './row';
|
||||
|
||||
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: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.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,
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user