pull:初次提交
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as getMany from './getMany.operation';
|
||||
import * as getSchema from './getSchema.operation';
|
||||
|
||||
export { getMany, getSchema };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getMany',
|
||||
description: 'List all the bases',
|
||||
action: 'Get many bases',
|
||||
},
|
||||
{
|
||||
name: 'Get Schema',
|
||||
value: 'getSchema',
|
||||
description: 'Get the schema of the tables in a base',
|
||||
action: 'Get base schema',
|
||||
},
|
||||
],
|
||||
default: 'getMany',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['base'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...getMany.description,
|
||||
...getSchema.description,
|
||||
];
|
||||
@@ -0,0 +1,122 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
generatePairedItemData,
|
||||
updateDisplayOptions,
|
||||
wrapData,
|
||||
} from '../../../../../utils/utilities';
|
||||
import { apiRequest } from '../../transport';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Permission Level',
|
||||
name: 'permissionLevel',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'Comment',
|
||||
value: 'comment',
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Edit',
|
||||
value: 'edit',
|
||||
},
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'Read',
|
||||
value: 'read',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
description: 'Filter the returned bases by one or more permission levels',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['base'],
|
||||
operation: ['getMany'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
|
||||
const endpoint = 'meta/bases';
|
||||
let bases: IDataObject[] = [];
|
||||
|
||||
if (returnAll) {
|
||||
let offset: string | undefined = undefined;
|
||||
do {
|
||||
const responseData = await apiRequest.call(this, 'GET', endpoint);
|
||||
bases.push(...(responseData.bases as IDataObject[]));
|
||||
offset = responseData.offset;
|
||||
} while (offset);
|
||||
} else {
|
||||
const responseData = await apiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
if (limit && responseData.bases?.length) {
|
||||
bases = responseData.bases.slice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
const permissionLevel = this.getNodeParameter('options.permissionLevel', 0, []) as string[];
|
||||
if (permissionLevel.length) {
|
||||
bases = bases.filter((base) => permissionLevel.includes(base.permissionLevel as string));
|
||||
}
|
||||
|
||||
const itemData = generatePairedItemData(this.getInputData().length);
|
||||
|
||||
const returnData = this.helpers.constructExecutionMetaData(wrapData(bases), {
|
||||
itemData,
|
||||
});
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
|
||||
import { processAirtableError } from '../../helpers/utils';
|
||||
import { apiRequest } from '../../transport';
|
||||
import { baseRLC } from '../common.descriptions';
|
||||
import type { TablesResponse } from '../types';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
...baseRLC,
|
||||
description: 'The Airtable Base to retrieve the schema from',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['base'],
|
||||
operation: ['getSchema'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const baseId = this.getNodeParameter('base', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const responseData: TablesResponse = await apiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`meta/bases/${baseId}/tables`,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(wrapData(responseData.tables), {
|
||||
itemData: { item: i },
|
||||
});
|
||||
|
||||
returnData = returnData.concat(executionData);
|
||||
} catch (error) {
|
||||
error = processAirtableError(error as NodeApiError, undefined, i);
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const baseRLC: INodeProperties = {
|
||||
displayName: 'Base',
|
||||
name: 'base',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
// description: 'The Airtable Base in which to operate on',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'baseSearch',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://airtable.com/app12DiScdfes/tbl9WvGeEPa6lZyVq/viwHdfasdfeieg5p',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https://airtable.com/([a-zA-Z0-9]{2,})/.*',
|
||||
errorMessage: 'Not a valid Airtable Base URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https://airtable.com/([a-zA-Z0-9]{2,})',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[a-zA-Z0-9]{2,}',
|
||||
errorMessage: 'Not a valid Airtable Base ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
placeholder: 'e.g. appD3dfaeidke',
|
||||
url: '=https://airtable.com/{{$value}}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const tableRLC: INodeProperties = {
|
||||
displayName: 'Table',
|
||||
name: 'table',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['base.value'],
|
||||
},
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'tableSearch',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'https://airtable.com/app12DiScdfes/tblAAAAAAAAAAAAA/viwHdfasdfeieg5p',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})/.*',
|
||||
errorMessage: 'Not a valid Airtable Table URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[a-zA-Z0-9]{2,}',
|
||||
errorMessage: 'Not a valid Airtable Table ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
placeholder: 'tbl3dirwqeidke',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const viewRLC: INodeProperties = {
|
||||
displayName: 'View',
|
||||
name: 'view',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'viewSearch',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'https://airtable.com/app12DiScdfes/tblAAAAAAAAAAAAA/viwHdfasdfeieg5p',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https://airtable.com/[a-zA-Z0-9]{2,}/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})/.*',
|
||||
errorMessage: 'Not a valid Airtable View URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https://airtable.com/[a-zA-Z0-9]{2,}/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[a-zA-Z0-9]{2,}',
|
||||
errorMessage: 'Not a valid Airtable View ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
placeholder: 'viw3dirwqeidke',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const insertUpdateOptions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Typecast',
|
||||
name: 'typecast',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the Airtable API should attempt mapping of string values for linked records & select options',
|
||||
},
|
||||
{
|
||||
displayName: 'Ignore Fields From Input',
|
||||
name: 'ignoreFields',
|
||||
type: 'string',
|
||||
requiresDataPath: 'multiple',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/columns.mappingMode': ['autoMapInputData'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Comma-separated list of fields in input to ignore when updating',
|
||||
},
|
||||
{
|
||||
displayName: 'Update All Matches',
|
||||
name: 'updateAllMatches',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to update all records matching the value in the "Column to Match On". If not set, only the first matching record will be updated.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['update', 'upsert'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { AllEntities } from 'n8n-workflow';
|
||||
|
||||
type NodeMap = {
|
||||
record: 'create' | 'upsert' | 'deleteRecord' | 'get' | 'search' | 'update';
|
||||
base: 'getMany' | 'getSchema';
|
||||
table: 'create';
|
||||
};
|
||||
|
||||
export type AirtableType = AllEntities<NodeMap>;
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as create from './create.operation';
|
||||
import * as deleteRecord from './deleteRecord.operation';
|
||||
import * as get from './get.operation';
|
||||
import * as search from './search.operation';
|
||||
import * as update from './update.operation';
|
||||
import * as upsert from './upsert.operation';
|
||||
import { baseRLC, tableRLC } from '../common.descriptions';
|
||||
|
||||
export { create, deleteRecord, get, search, update, upsert };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new record in a table',
|
||||
action: 'Create a record',
|
||||
},
|
||||
{
|
||||
name: 'Create or Update',
|
||||
value: 'upsert',
|
||||
description: 'Create a new record, or update the current one if it already exists (upsert)',
|
||||
action: 'Create or update a record',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteRecord',
|
||||
description: 'Delete a record from a table',
|
||||
action: 'Delete a record',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a record from a table',
|
||||
action: 'Get a record',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search for specific records or list all',
|
||||
action: 'Search records',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a record in a table',
|
||||
action: 'Update record',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...baseRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...tableRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...create.description,
|
||||
...deleteRecord.description,
|
||||
...get.description,
|
||||
...search.description,
|
||||
...update.description,
|
||||
...upsert.description,
|
||||
];
|
||||
@@ -0,0 +1,101 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
IExecuteFunctions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
|
||||
import { processAirtableError, removeIgnored } from '../../helpers/utils';
|
||||
import { apiRequest } from '../../transport';
|
||||
import { insertUpdateOptions } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'resourceMapper',
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
noDataExpression: true,
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'base.value'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getColumns',
|
||||
mode: 'add',
|
||||
fieldWords: {
|
||||
singular: 'column',
|
||||
plural: 'columns',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...insertUpdateOptions,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
operation: ['create'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
base: string,
|
||||
table: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const endpoint = `${base}/${table}`;
|
||||
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', 0) as string;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const typecast = Boolean(options.typecast);
|
||||
|
||||
const body: IDataObject = { typecast };
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
body.fields = removeIgnored(items[i].json, options.ignoreFields as string);
|
||||
}
|
||||
|
||||
if (dataMode === 'defineBelow') {
|
||||
const fields = this.getNodeParameter('columns.value', i, [], {
|
||||
skipValidation: typecast,
|
||||
}) as IDataObject;
|
||||
|
||||
body.fields = fields;
|
||||
}
|
||||
|
||||
const responseData = await apiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
wrapData(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push.apply(returnData, executionData);
|
||||
} catch (error) {
|
||||
error = processAirtableError(error as NodeApiError, undefined, i);
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { message: error.message, error } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
NodeApiError,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
|
||||
import { processAirtableError } from '../../helpers/utils';
|
||||
import { apiRequest } from '../../transport';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Record ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. recf7EaZp707CEc8g',
|
||||
required: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-id
|
||||
description:
|
||||
'ID of the record to delete. <a href="https://support.airtable.com/docs/record-id" target="_blank">More info</a>.',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
operation: ['deleteRecord'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
base: string,
|
||||
table: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let id;
|
||||
try {
|
||||
id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const responseData = await apiRequest.call(this, 'DELETE', `${base}/${table}/${id}`);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
wrapData(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
error = processAirtableError(error as NodeApiError, id, i);
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
NodeApiError,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
|
||||
import type { IRecord } from '../../helpers/interfaces';
|
||||
import { flattenOutput, processAirtableError } from '../../helpers/utils';
|
||||
import { apiRequest, downloadRecordAttachments } from '../../transport';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Record ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. recf7EaZp707CEc8g',
|
||||
required: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-id
|
||||
description:
|
||||
'ID of the record to get. <a href="https://support.airtable.com/docs/record-id" target="_blank">More info</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
description: 'Additional options which decide which records should be returned',
|
||||
placeholder: 'Add option',
|
||||
options: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||
displayName: 'Download Attachments',
|
||||
name: 'downloadFields',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAttachmentColumns',
|
||||
loadOptionsDependsOn: ['base.value', 'table.value'],
|
||||
},
|
||||
default: [],
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
|
||||
description: "The fields of type 'attachment' that should be downloaded",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
operation: ['get'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
base: string,
|
||||
table: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let id;
|
||||
try {
|
||||
id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const responseData = await apiRequest.call(this, 'GET', `${base}/${table}/${id}`);
|
||||
|
||||
const options = this.getNodeParameter('options', 0, {});
|
||||
|
||||
if (options.downloadFields) {
|
||||
const itemWithAttachments = await downloadRecordAttachments.call(
|
||||
this,
|
||||
[responseData] as IRecord[],
|
||||
options.downloadFields as string[],
|
||||
);
|
||||
returnData.push(...itemWithAttachments);
|
||||
continue;
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
wrapData(flattenOutput(responseData as IDataObject)),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
error = processAirtableError(error as NodeApiError, id, i);
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import type { IRecord } from '../../helpers/interfaces';
|
||||
import { flattenOutput } from '../../helpers/utils';
|
||||
import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from '../../transport';
|
||||
import { viewRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Filter By Formula',
|
||||
name: 'filterByFormula',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: "e.g. NOT({Name} = 'Admin')",
|
||||
hint: 'If empty, all the records will be returned',
|
||||
description:
|
||||
'The formula will be evaluated for each record, and if the result is not 0, false, "", NaN, [], or #Error! the record will be included in the response. <a href="https://support.airtable.com/docs/formula-field-reference" target="_blank">More info</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
description: 'Additional options which decide which records should be returned',
|
||||
placeholder: 'Add option',
|
||||
options: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||
displayName: 'Download Attachments',
|
||||
name: 'downloadFields',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAttachmentColumns',
|
||||
loadOptionsDependsOn: ['base.value', 'table.value'],
|
||||
},
|
||||
default: [],
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
|
||||
description: "The fields of type 'attachment' that should be downloaded",
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||
displayName: 'Output Fields',
|
||||
name: 'fields',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getColumns',
|
||||
loadOptionsDependsOn: ['base.value', 'table.value'],
|
||||
},
|
||||
default: [],
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
|
||||
description: 'The fields you want to include in the output',
|
||||
},
|
||||
viewRLC,
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'sort',
|
||||
placeholder: 'Add Sort Rule',
|
||||
description: 'Defines how the returned records should be ordered',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'property',
|
||||
displayName: 'Property',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Field',
|
||||
name: 'field',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getColumns',
|
||||
loadOptionsDependsOn: ['base.value', 'table.value'],
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Name of the field to sort on. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Direction',
|
||||
name: 'direction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ASC',
|
||||
value: 'asc',
|
||||
description: 'Sort in ascending order (small -> large)',
|
||||
},
|
||||
{
|
||||
name: 'DESC',
|
||||
value: 'desc',
|
||||
description: 'Sort in descending order (large -> small)',
|
||||
},
|
||||
],
|
||||
default: 'asc',
|
||||
description: 'The sort direction',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
operation: ['search'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
base: string,
|
||||
table: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
const endpoint = `${base}/${table}`;
|
||||
|
||||
let itemsLength = items.length ? 1 : 0;
|
||||
let fallbackPairedItems;
|
||||
|
||||
if (nodeVersion >= 2.1) {
|
||||
itemsLength = items.length;
|
||||
} else {
|
||||
fallbackPairedItems = generatePairedItemData(items.length);
|
||||
}
|
||||
|
||||
for (let i = 0; i < itemsLength; i++) {
|
||||
try {
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const sort = this.getNodeParameter('sort', i, {}) as IDataObject;
|
||||
const filterByFormula = this.getNodeParameter('filterByFormula', i) as string;
|
||||
|
||||
const body: IDataObject = {};
|
||||
const qs: IDataObject = {};
|
||||
|
||||
if (filterByFormula) {
|
||||
qs.filterByFormula = filterByFormula;
|
||||
}
|
||||
|
||||
if (options.fields) {
|
||||
if (typeof options.fields === 'string') {
|
||||
qs.fields = options.fields.split(',').map((field) => field.trim());
|
||||
} else {
|
||||
qs.fields = options.fields as string[];
|
||||
}
|
||||
}
|
||||
|
||||
if (sort.property) {
|
||||
qs.sort = sort.property;
|
||||
}
|
||||
|
||||
if (options.view) {
|
||||
qs.view = (options.view as IDataObject).value as string;
|
||||
}
|
||||
|
||||
let responseData;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await apiRequestAllItems.call(this, 'GET', endpoint, body, qs);
|
||||
} else {
|
||||
qs.maxRecords = this.getNodeParameter('limit', i);
|
||||
responseData = await apiRequest.call(this, 'GET', endpoint, body, qs);
|
||||
}
|
||||
|
||||
if (options.downloadFields) {
|
||||
const itemWithAttachments = await downloadRecordAttachments.call(
|
||||
this,
|
||||
responseData.records as IRecord[],
|
||||
options.downloadFields as string[],
|
||||
fallbackPairedItems || [{ item: i }],
|
||||
);
|
||||
returnData.push(...itemWithAttachments);
|
||||
continue;
|
||||
}
|
||||
|
||||
let records = responseData.records;
|
||||
|
||||
records = (records as IDataObject[]).map((record) => ({
|
||||
json: flattenOutput(record),
|
||||
})) as INodeExecutionData[];
|
||||
|
||||
const itemData = fallbackPairedItems || [{ item: i }];
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(records, {
|
||||
itemData,
|
||||
});
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { message: error.message, error }, pairedItem: { item: i } });
|
||||
continue;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
NodeApiError,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
|
||||
import type { UpdateRecord } from '../../helpers/interfaces';
|
||||
import { findMatches, processAirtableError, removeIgnored } from '../../helpers/utils';
|
||||
import { apiRequestAllItems, batchUpdate } from '../../transport';
|
||||
import { insertUpdateOptions } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'resourceMapper',
|
||||
noDataExpression: true,
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'base.value'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getColumnsWithRecordId',
|
||||
mode: 'update',
|
||||
fieldWords: {
|
||||
singular: 'column',
|
||||
plural: 'columns',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...insertUpdateOptions,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
operation: ['update'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
base: string,
|
||||
table: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const endpoint = `${base}/${table}`;
|
||||
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', 0) as string;
|
||||
|
||||
const columnsToMatchOn = this.getNodeParameter('columns.matchingColumns', 0) as string[];
|
||||
|
||||
let tableData: UpdateRecord[] = [];
|
||||
if (!columnsToMatchOn.includes('id')) {
|
||||
const response = await apiRequestAllItems.call(
|
||||
this,
|
||||
'GET',
|
||||
endpoint,
|
||||
{},
|
||||
{ fields: columnsToMatchOn },
|
||||
);
|
||||
tableData = response.records as UpdateRecord[];
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let recordId = '';
|
||||
try {
|
||||
const records: UpdateRecord[] = [];
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const typecast = options.typecast ? true : false;
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
if (columnsToMatchOn.includes('id')) {
|
||||
const { id, ...fields } = items[i].json;
|
||||
recordId = id as string;
|
||||
|
||||
records.push({
|
||||
id: recordId,
|
||||
fields: removeIgnored(fields, options.ignoreFields as string),
|
||||
});
|
||||
} else {
|
||||
const matches = findMatches(
|
||||
tableData,
|
||||
columnsToMatchOn,
|
||||
items[i].json,
|
||||
options.updateAllMatches as boolean,
|
||||
);
|
||||
|
||||
for (const match of matches) {
|
||||
const id = match.id as string;
|
||||
const fields = items[i].json;
|
||||
records.push({ id, fields: removeIgnored(fields, options.ignoreFields as string) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMode === 'defineBelow') {
|
||||
const getNodeParameterOptions = typecast ? { skipValidation: true } : undefined;
|
||||
if (columnsToMatchOn.includes('id')) {
|
||||
const { id, ...fields } = this.getNodeParameter(
|
||||
'columns.value',
|
||||
i,
|
||||
[],
|
||||
getNodeParameterOptions,
|
||||
) as IDataObject;
|
||||
records.push({ id: id as string, fields });
|
||||
} else {
|
||||
const fields = this.getNodeParameter(
|
||||
'columns.value',
|
||||
i,
|
||||
[],
|
||||
getNodeParameterOptions,
|
||||
) as IDataObject;
|
||||
|
||||
const matches = findMatches(
|
||||
tableData,
|
||||
columnsToMatchOn,
|
||||
fields,
|
||||
options.updateAllMatches as boolean,
|
||||
);
|
||||
|
||||
for (const match of matches) {
|
||||
const id = match.id as string;
|
||||
records.push({ id, fields: removeIgnored(fields, columnsToMatchOn) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const body: IDataObject = { typecast };
|
||||
|
||||
const responseData = await batchUpdate.call(this, endpoint, body, records);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
wrapData(responseData.records as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
error = processAirtableError(error as NodeApiError, recordId, i);
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { message: error.message, error } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
IExecuteFunctions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
|
||||
import type { UpdateRecord } from '../../helpers/interfaces';
|
||||
import { processAirtableError, removeIgnored } from '../../helpers/utils';
|
||||
import { apiRequest, apiRequestAllItems, batchUpdate } from '../../transport';
|
||||
import { insertUpdateOptions } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'resourceMapper',
|
||||
noDataExpression: true,
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'base.value'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getColumnsWithRecordId',
|
||||
mode: 'update',
|
||||
fieldWords: {
|
||||
singular: 'column',
|
||||
plural: 'columns',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...insertUpdateOptions,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['record'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
base: string,
|
||||
table: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const endpoint = `${base}/${table}`;
|
||||
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', 0) as string;
|
||||
|
||||
const columnsToMatchOn = this.getNodeParameter('columns.matchingColumns', 0) as string[];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const records: UpdateRecord[] = [];
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
if (columnsToMatchOn.includes('id')) {
|
||||
const { id, ...fields } = items[i].json;
|
||||
|
||||
records.push({
|
||||
id: id as string,
|
||||
fields: removeIgnored(fields, options.ignoreFields as string),
|
||||
});
|
||||
} else {
|
||||
records.push({ fields: removeIgnored(items[i].json, options.ignoreFields as string) });
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMode === 'defineBelow') {
|
||||
const fields = this.getNodeParameter('columns.value', i, []) as IDataObject;
|
||||
|
||||
if (columnsToMatchOn.includes('id')) {
|
||||
const id = fields.id as string;
|
||||
delete fields.id;
|
||||
records.push({ id, fields });
|
||||
} else {
|
||||
records.push({ fields });
|
||||
}
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
typecast: options.typecast ? true : false,
|
||||
};
|
||||
|
||||
if (!columnsToMatchOn.includes('id')) {
|
||||
body.performUpsert = { fieldsToMergeOn: columnsToMatchOn };
|
||||
}
|
||||
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await batchUpdate.call(this, endpoint, body, records);
|
||||
} catch (error) {
|
||||
if (error.httpCode === '422' && columnsToMatchOn.includes('id')) {
|
||||
const createBody = {
|
||||
...body,
|
||||
records: records.map(({ fields }) => ({ fields })),
|
||||
};
|
||||
responseData = await apiRequest.call(this, 'POST', endpoint, createBody);
|
||||
} else if (error?.description?.includes('Cannot update more than one record')) {
|
||||
const conditions = columnsToMatchOn
|
||||
.map((column) => `{${column}} = '${records[0].fields[column]}'`)
|
||||
.join(',');
|
||||
const response = await apiRequestAllItems.call(
|
||||
this,
|
||||
'GET',
|
||||
endpoint,
|
||||
{},
|
||||
{
|
||||
fields: columnsToMatchOn,
|
||||
filterByFormula: `AND(${conditions})`,
|
||||
},
|
||||
);
|
||||
const matches = response.records as UpdateRecord[];
|
||||
|
||||
const updateRecords: UpdateRecord[] = [];
|
||||
|
||||
if (options.updateAllMatches) {
|
||||
updateRecords.push(...matches.map(({ id }) => ({ id, fields: records[0].fields })));
|
||||
} else {
|
||||
updateRecords.push({ id: matches[0].id, fields: records[0].fields });
|
||||
}
|
||||
|
||||
responseData = await batchUpdate.call(this, endpoint, body, updateRecords);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
wrapData(responseData.records as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
error = processAirtableError(error as NodeApiError, undefined, i);
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { message: error.message, error } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
59
n8n-n8n-1.109.2/packages/nodes-base/nodes/Airtable/v2/actions/router.ts
Executable file
59
n8n-n8n-1.109.2/packages/nodes-base/nodes/Airtable/v2/actions/router.ts
Executable file
@@ -0,0 +1,59 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import * as base from './base/Base.resource';
|
||||
import type { AirtableType } from './node.type';
|
||||
import * as record from './record/Record.resource';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const items = this.getInputData();
|
||||
const resource = this.getNodeParameter<AirtableType>('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
const airtableNodeData = {
|
||||
resource,
|
||||
operation,
|
||||
} as AirtableType;
|
||||
|
||||
try {
|
||||
switch (airtableNodeData.resource) {
|
||||
case 'record':
|
||||
const baseId = this.getNodeParameter('base', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const table = encodeURI(
|
||||
this.getNodeParameter('table', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string,
|
||||
);
|
||||
returnData = await record[airtableNodeData.operation].execute.call(
|
||||
this,
|
||||
items,
|
||||
baseId,
|
||||
table,
|
||||
);
|
||||
break;
|
||||
case 'base':
|
||||
returnData = await base[airtableNodeData.operation].execute.call(this, items);
|
||||
break;
|
||||
default:
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`The operation "${operation}" is not supported!`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error.description &&
|
||||
(error.description as string).includes('cannot accept the provided value')
|
||||
) {
|
||||
error.description = `${error.description}. Consider using 'Typecast' option`;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
15
n8n-n8n-1.109.2/packages/nodes-base/nodes/Airtable/v2/actions/types.ts
Executable file
15
n8n-n8n-1.109.2/packages/nodes-base/nodes/Airtable/v2/actions/types.ts
Executable file
@@ -0,0 +1,15 @@
|
||||
export type Table = {
|
||||
id: string;
|
||||
name: string;
|
||||
fields: Field[];
|
||||
};
|
||||
|
||||
export type TablesResponse = {
|
||||
tables: Table[];
|
||||
};
|
||||
|
||||
export type Field = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as base from './base/Base.resource';
|
||||
import * as record from './record/Record.resource';
|
||||
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Airtable',
|
||||
name: 'airtable',
|
||||
icon: 'file:airtable.svg',
|
||||
group: ['input'],
|
||||
version: [2, 2.1],
|
||||
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
|
||||
description: 'Read, update, write and delete data from Airtable',
|
||||
defaults: {
|
||||
name: 'Airtable',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'airtableTokenApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['airtableTokenApi'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'airtableOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['airtableOAuth2Api'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Access Token',
|
||||
value: 'airtableTokenApi',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'airtableOAuth2Api',
|
||||
},
|
||||
],
|
||||
default: 'airtableTokenApi',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Base',
|
||||
value: 'base',
|
||||
},
|
||||
{
|
||||
name: 'Record',
|
||||
value: 'record',
|
||||
},
|
||||
// {
|
||||
// name: 'Table',
|
||||
// value: 'table',
|
||||
// },
|
||||
],
|
||||
default: 'record',
|
||||
},
|
||||
...record.description,
|
||||
...base.description,
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user