From 3fd4884667b167d4773001efb8cc2406328a6113 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 15 Mar 2020 19:51:31 -0400 Subject: [PATCH] :sparkles: Microsoft Excel node --- packages/cli/src/Server.ts | 4 +- .../MicrosoftOAuth2Api.credentials.ts | 45 ++ .../nodes/Microsoft/GenericFunctions.ts | 73 +++ .../nodes/Microsoft/MicrosoftExcel.node.ts | 335 +++++++++++++ .../nodes/Microsoft/TableDescription.ts | 447 ++++++++++++++++++ .../nodes/Microsoft/WorkbookDescription.ts | 154 ++++++ .../nodes/Microsoft/WorksheetDescription.ts | 283 +++++++++++ packages/nodes-base/nodes/Microsoft/excel.png | Bin 0 -> 5984 bytes packages/nodes-base/package.json | 6 +- 9 files changed, 1344 insertions(+), 3 deletions(-) create mode 100644 packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Microsoft/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts create mode 100644 packages/nodes-base/nodes/Microsoft/TableDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/excel.png diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 4fe1ef70d7..5a1c0703bf 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -203,7 +203,9 @@ class App { }); } - jwt.verify(token, getKey, {}, (err: Error, decoded: string) => { + jwt.verify(token, getKey, {}, (err: Error, + //decoded: string + ) => { if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token"); next(); diff --git a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts new file mode 100644 index 0000000000..2519ba583b --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts @@ -0,0 +1,45 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftOAuth2Api implements ICredentialType { + name = 'microsoftOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/token', + }, + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'openid offline_access Files.ReadWrite', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: 'response_mode=query', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts new file mode 100644 index 0000000000..b4f68be343 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/GenericFunctions.ts @@ -0,0 +1,73 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { + IDataObject +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://graph.microsoft.com/v1.0/me${resource}`, + json: true + }; + try { + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'microsoftOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { + // Try to return the error prettier + throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); + } + throw error; + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + query['$top'] = 100; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query['$top'] = 100; + query['$skip'] = 0; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query); + query['$skip'] += query['$top']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['value'].length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts new file mode 100644 index 0000000000..c62730449d --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/MicrosoftExcel.node.ts @@ -0,0 +1,335 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + microsoftApiRequest, + microsoftApiRequestAllItems, + microsoftApiRequestAllItemsSkip, +} from './GenericFunctions'; + +import { + workbookOperations, + workbookFields, +} from './WorkbookDescription'; + +import { + worksheetOperations, + worksheetFields, +} from './WorksheetDescription'; + +import { + tableOperations, + tableFields, +} from './TableDescription'; + +export class MicrosoftExcel implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft Excel', + name: 'microsoftExcel', + icon: 'file:excel.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft Excel API.', + defaults: { + name: 'Microsoft Excel', + color: '#1c6d40', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Table', + value: 'table', + description: 'Represents an Excel table.', + }, + { + name: 'Workbook', + value: 'workbook', + description: 'Workbook is the top level object which contains related workbook objects such as worksheets, tables, ranges, etc.', + }, + { + name: 'Worksheet', + value: 'worksheet', + description: 'An Excel worksheet is a grid of cells. It can contain data, tables, charts, etc.', + }, + ], + default: 'workbook', + description: 'The resource to operate on.', + }, + ...workbookOperations, + ...workbookFields, + ...worksheetOperations, + ...worksheetFields, + ...tableOperations, + ...tableFields, + ], + }; + + methods = { + loadOptions: { + // Get all the workbooks to display them to user so that he can + // select them easily + async getWorkbooks(this: ILoadOptionsFunctions): Promise { + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const workbooks = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + for (const workbook of workbooks) { + const workbookName = workbook.name; + const workbookId = workbook.id; + returnData.push({ + name: workbookName, + value: workbookId, + }); + } + return returnData; + }, + // Get all the worksheets to display them to user so that he can + // select them easily + async getworksheets(this: ILoadOptionsFunctions): Promise { + const workbookId = this.getCurrentNodeParameter('workbook'); + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const worksheets = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + for (const worksheet of worksheets) { + const worksheetName = worksheet.name; + const worksheetId = worksheet.id; + returnData.push({ + name: worksheetName, + value: worksheetId, + }); + } + return returnData; + }, + // Get all the tables to display them to user so that he can + // select them easily + async getTables(this: ILoadOptionsFunctions): Promise { + const workbookId = this.getCurrentNodeParameter('workbook'); + const worksheetId = this.getCurrentNodeParameter('worksheet'); + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const tables = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables`, {}, qs); + 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 items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + const result: IDataObject[] = []; + const object: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'table') { + //https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http + if (operation === 'addRow') { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject; + const body: IDataObject = {}; + if (Object.keys(items[0].json).length === 0) { + throw new Error('Input cannot be empty'); + } + if (additionalFields.index) { + body.index = additionalFields.index as number; + } + const values: any[][] = []; + for (const item of items) { + values.push(Object.values(item.json)); + } + body.values = values; + const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows/add`, body, {}, '', { 'workbook-session-id': id }); + await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + } + //https://docs.microsoft.com/en-us/graph/api/table-list-columns?view=graph-rest-1.0&tabs=http + if (operation === 'getColumns') { + for (let i = 0; i < length; i++) { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + responseData = responseData.value; + } + if (!rawData) { + //@ts-ignore + responseData = responseData.map(column => ({ name: column.name })); + } + } + } + //https://docs.microsoft.com/en-us/graph/api/table-list-rows?view=graph-rest-1.0&tabs=http + if (operation === 'getRows') { + for (let i = 0; i < length; i++) { + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + responseData = responseData.value; + } + if (!rawData) { + qs['$select'] = 'name'; + let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + //@ts-ignore + columns = columns.map(column => column.name); + for (let i = 0; i < responseData.length; i++) { + for (let y = 0; y < columns.length; y++) { + object[columns[y]] = responseData[i].values[0][y]; + } + result.push({ ...object }); + } + responseData = result; + } + } + } + } + if (resource === 'workbook') { + for (let i = 0; i < length; i++) { + //https://docs.microsoft.com/en-us/graph/api/worksheetcollection-add?view=graph-rest-1.0&tabs=http + if (operation === 'addWorksheet') { + const workbookId = this.getNodeParameter('workbook', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = {}; + if (additionalFields.name) { + body.name = additionalFields.name; + } + const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/add`, body, {}, '', { 'workbook-session-id': id }); + await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + } + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + responseData = responseData.value; + } + } + } + } + if (resource === 'worksheet') { + for (let i = 0; i < length; i++) { + //https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const workbookId = this.getNodeParameter('workbook', i) as string; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + responseData = responseData.value; + } + } + //https://docs.microsoft.com/en-us/graph/api/worksheet-range?view=graph-rest-1.0&tabs=http + if (operation === 'getContent') { + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const range = this.getNodeParameter('range', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`, {}, qs); + if (!rawData) { + const keyRow = this.getNodeParameter('keyRow', i) as number; + const dataStartRow = this.getNodeParameter('dataStartRow', i) as number; + if (responseData.values === null) { + throw new Error('Range did not return data'); + } + const keyValues = responseData.values[keyRow]; + for (let i = dataStartRow; i < responseData.values.length; i++) { + for (let y = 0; y < keyValues.length; y++) { + object[keyValues[y]] = responseData.values[i][y]; + } + result.push({ ...object }); + } + responseData = result; + } + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Microsoft/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/TableDescription.ts new file mode 100644 index 0000000000..6dc6de780b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/TableDescription.ts @@ -0,0 +1,447 @@ +import { INodeProperties } from "n8n-workflow"; + +export const tableOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'table', + ], + }, + }, + options: [ + { + name: 'Add Row', + value: 'addRow', + description: 'Adds rows to the end of the table' + }, + { + name: 'Get Columns', + value: 'getColumns', + description: 'Retrieve a list of tablecolumns', + }, + { + name: 'Get Rows', + value: 'getRows', + description: 'Retrieve a list of tablerows', + }, + ], + default: 'addRow', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const tableFields = [ + +/* -------------------------------------------------------------------------- */ +/* table:addRow */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + options: [ + { + displayName: 'Index', + name: 'index', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: `Specifies the relative position of the new row. If not defined,
+ the addition happens at the end. Any rows below the inserted row are shifted downwards. Zero-indexed`, + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* table:getRows */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* table:getColumns */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + rawData: [ + true + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts b/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts new file mode 100644 index 0000000000..526a8ceb43 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/WorkbookDescription.ts @@ -0,0 +1,154 @@ +import { INodeProperties } from "n8n-workflow"; + +export const workbookOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + name: 'Add Worksheet', + value: 'addWorksheet', + description: 'Adds a new worksheet to the workbook.', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get data of all workbooks', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const workbookFields = [ + +/* -------------------------------------------------------------------------- */ +/* workbook:addWorksheet */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'addWorksheet', + ], + resource: [ + 'workbook', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'addWorksheet', + ], + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: `The name of the worksheet to be added. If specified, name should be unqiue.
+ If not specified, Excel determines the name of the new worksheet.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* workbook:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts b/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts new file mode 100644 index 0000000000..204e5f0e41 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/WorksheetDescription.ts @@ -0,0 +1,283 @@ +import { INodeProperties } from "n8n-workflow"; + +export const worksheetOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'worksheet', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all worksheets', + }, + { + name: 'Get Content', + value: 'getContent', + description: 'Get worksheet content', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const worksheetFields = [ + +/* -------------------------------------------------------------------------- */ +/* worksheet:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* worksheet:getContent */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Range', + name: 'range', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: 'A1:C3', + required: true, + description: 'The address or the name of the range. If not specified, the entire worksheet range is returned.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Data Start Row', + name: 'dataStartRow', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + hide: { + rawData: [ + true + ], + }, + }, + description: 'Index of the first row which contains
the actual data and not the keys. Starts with 0.', + }, + { + displayName: 'Key Row', + name: 'keyRow', + type: 'number', + typeOptions: { + minValue: 0, + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + hide: { + rawData: [ + true + ], + }, + }, + default: 0, + description: 'Index of the row which contains the keys. Starts at 0.
The incoming node data is matched to the keys for assignment. The matching is case sensitve.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + rawData: [ + true, + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/excel.png b/packages/nodes-base/nodes/Microsoft/excel.png new file mode 100644 index 0000000000000000000000000000000000000000..40631413a5f656ad5819f9faada8eecc78509bd4 GIT binary patch literal 5984 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 bdqb4Qmz%rb(0TCZ=~zQWN4Z?lGU$H*2Ppz* literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 746f91f723..47f21e8396 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -54,7 +54,8 @@ "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", @@ -132,7 +133,8 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", - "dist/nodes/Merge.node.js", + "dist/nodes/Merge.node.js", + "dist/nodes/Microsoft/MicrosoftExcel.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MySql/MySql.node.js",