import { IExecuteFunctions, } from 'n8n-core'; import { IDataObject, INodeTypeDescription, INodeExecutionData, INodeType, ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; import { codaApiRequest, codaApiRequestAllItems, } from './GenericFunctions'; import { tableFields, tableOperations, } from './TableDescription'; export class Coda implements INodeType { description: INodeTypeDescription = { displayName: 'Coda', name: 'Coda', icon: 'file:coda.png', group: ['output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Coda Beta API', defaults: { name: 'Coda', color: '#c02428', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'codaApi', required: true, } ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', options: [ { name: 'Table', value: 'table', description: `Access data of tables in documents.`, }, ], default: 'table', description: 'Resource to consume.', }, ...tableOperations, ...tableFields, ], }; methods = { loadOptions: { // Get all the available docs to display them to user so that he can // select them easily async getDocs(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; const qs = {}; let docs; try { docs = await codaApiRequestAllItems.call(this,'items', 'GET', `/docs`, {}, qs); } catch (err) { throw new Error(`Coda Error: ${err}`); } for (const doc of docs) { const docName = doc.name; const docId = doc.id; returnData.push({ name: docName, value: docId, }); } return returnData; }, // Get all the available tables to display them to user so that he can // select them easily async getTables(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; let tables; const docId = this.getCurrentNodeParameter('docId'); try { tables = await codaApiRequestAllItems.call(this, 'items', 'GET', `/docs/${docId}/tables`, {}); } catch (err) { throw new Error(`Coda Error: ${err}`); } for (const table of tables) { const tableName = table.name; const tableId = table.id; returnData.push({ name: tableName, value: tableId, }); } return returnData; }, }, }; async execute(this: IExecuteFunctions): Promise { const returnData: IDataObject[] = []; const items = this.getInputData(); let responseData; const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; let qs: IDataObject = {}; if (resource === 'table') { // https://coda.io/developers/apis/v1beta1#operation/upsertRows if (operation === 'createRow') { const sendData = {} as IDataObject; for (let i = 0; i < items.length; i++) { qs = {}; const docId = this.getNodeParameter('docId', i) as string; const tableId = this.getNodeParameter('tableId', i) as string; const options = this.getNodeParameter('options', i) as IDataObject; const endpoint = `/docs/${docId}/tables/${tableId}/rows`; if (options.keyColumns) { // @ts-ignore items[i].json['keyColumns'] = options.keyColumns.split(',') as string[]; } if (options.disableParsing) { qs.disableParsing = options.disableParsing as boolean; } const cells = []; cells.length = 0; for (const key of Object.keys(items[i].json)) { cells.push({ column: key, value: items[i].json[key], }); } // Collect all the data for the different docs/tables if (sendData[endpoint] === undefined) { sendData[endpoint] = { rows: [], // TODO: This is not perfect as it ignores if qs changes between // different items but should be OK for now qs, }; } ((sendData[endpoint]! as IDataObject).rows! as IDataObject[]).push({ cells }); } // Now that all data got collected make all the requests for (const endpoint of Object.keys(sendData)) { await codaApiRequest.call(this, 'POST', endpoint, sendData[endpoint], (sendData[endpoint]! as IDataObject).qs! as IDataObject); } // Return the incoming data return [items]; } // https://coda.io/developers/apis/v1beta1#operation/getRow if (operation === 'getRow') { for (let i = 0; i < items.length; i++) { const docId = this.getNodeParameter('docId', i) as string; const tableId = this.getNodeParameter('tableId', i) as string; const rowId = this.getNodeParameter('rowId', i) as string; const options = this.getNodeParameter('options', i) as IDataObject; const endpoint = `/docs/${docId}/tables/${tableId}/rows/${rowId}`; if (options.useColumnNames === false) { qs.useColumnNames = options.useColumnNames as boolean; } else { qs.useColumnNames = true; } if (options.valueFormat) { qs.valueFormat = options.valueFormat as string; } responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs); if (options.rawData === true) { returnData.push(responseData); } else { returnData.push({ id: responseData.id, ...responseData.values }); } } return [this.helpers.returnJsonArray(returnData)]; } // https://coda.io/developers/apis/v1beta1#operation/listRows if (operation === 'getAllRows') { const docId = this.getNodeParameter('docId', 0) as string; const returnAll = this.getNodeParameter('returnAll', 0) as boolean; const tableId = this.getNodeParameter('tableId', 0) as string; const options = this.getNodeParameter('options', 0) as IDataObject; const endpoint = `/docs/${docId}/tables/${tableId}/rows`; if (options.useColumnNames === false) { qs.useColumnNames = options.useColumnNames as boolean; } else { qs.useColumnNames = true; } if (options.valueFormat) { qs.valueFormat = options.valueFormat as string; } if (options.sortBy) { qs.sortBy = options.sortBy as string; } if (options.visibleOnly) { qs.visibleOnly = options.visibleOnly as boolean; } try { if (returnAll === true) { responseData = await codaApiRequestAllItems.call(this, 'items', 'GET', endpoint, {}, qs); } else { qs.limit = this.getNodeParameter('limit', 0) as number; responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs); responseData = responseData.items; } } catch (err) { throw new Error(`Flow Error: ${err.message}`); } if (options.rawData === true) { return [this.helpers.returnJsonArray(responseData)]; } else { for (const item of responseData) { returnData.push({ id: item.id, ...item.values }); } return [this.helpers.returnJsonArray(returnData)]; } } // https://coda.io/developers/apis/v1beta1#operation/deleteRows if (operation === 'deleteRow') { const sendData = {} as IDataObject; for (let i = 0; i < items.length; i++) { const docId = this.getNodeParameter('docId', i) as string; const tableId = this.getNodeParameter('tableId', i) as string; const rowId = this.getNodeParameter('rowId', i) as string; const endpoint = `/docs/${docId}/tables/${tableId}/rows`; // Collect all the data for the different docs/tables if (sendData[endpoint] === undefined) { sendData[endpoint] = []; } (sendData[endpoint] as string[]).push(rowId); } // Now that all data got collected make all the requests for (const endpoint of Object.keys(sendData)) { await codaApiRequest.call(this, 'DELETE', endpoint, { rowIds: sendData[endpoint]}, qs); } // Return the incoming data return [items]; } } return []; } }